mirror of
https://github.com/immich-app/immich.git
synced 2025-10-26 16:22:33 -04:00
* feat(mobile): Add Kid (Readonly) Mode toggle This commit introduces a "Kid (Readonly) Mode" feature. - Adds a `KidModeProvider` to manage the state of Kid Mode. - Implements a `KidModeCheckbox` widget in the app bar dialog to toggle Kid Mode. - When Kid Mode is enabled, - Disables selecting the multigrid & the bottom bar - Removes the top bar from view Signed-off-by: Sudheer Puthana <Sud-Puth@users.noreply.github.com> Reverts the changes to devtools_options.yaml file Signed-off-by: Sudheer Puthana <Sud-Puth@users.noreply.github.com> refactor: replace Kid Mode with Readonly Mode This commit replaces the "Kid Mode" feature with a more generic "Readonly Mode". - Renamed `KidModeProvider` to `ReadonlyModeProvider`. - Readonly Mode state is now persisted in app settings. - Added a new app setting `allowUserAvatarOverride` to toggle read-only mode. - Updated translations. - Added a message in the app bar dialog indicating when read-only mode is active. Signed-off-by: Sudheer Puthana <Sud-Puth@users.noreply.github.com> Address comments - - Removes the `allowUserAvatarOverride` setting. - Hides the bottom gallery bar when read-only mode is enabled. - Adds an icon on the main app bar when read-only mode is enabled with a snackbar. Signed-off-by: Sudheer Puthana <Sud-Puth@users.noreply.github.com> Update to snackbar - When toggling readonly mode from either the settings or the app bar, a snackbar notification will now appear. - The readonly mode message in the profile drawer has been restyled. - The upload button in the app bar is now hidden when readonly mode is enabled. Signed-off-by: Sudheer Puthana <Sud-Puth@users.noreply.github.com> Removes clearing of snackbar Signed-off-by: Sudheer Puthana <Sud-Puth@users.noreply.github.com> Address Comments - Consolidated snackbar messages for enabling/disabling readonly mode. - Ensured the "Select All" icon in asset group titles is hidden in readonly mode. Signed-off-by: Sudheer Puthana <Sud-Puth@users.noreply.github.com> Adds in the missing translation keys for readonly_mode Signed-off-by: Sudheer Puthana <Sud-Puth@users.noreply.github.com> Fix translation Signed-off-by: Sudheer Puthana <Sud-Puth@users.noreply.github.com> Fix check failure for BorderRadius Signed-off-by: Sudheer Puthana <Sud-Puth@users.noreply.github.com> Changes: - Adjusted AppBar background color in readonly mode. - Removes cross-out pencil icon button in favor of above. - Hides the "Edit" icon next to date/time, disable description and onTap for people and location when readonly mode is enabled. Signed-off-by: Sudheer Puthana <Sud-Puth@users.noreply.github.com> Address comments from Alex - Moved readonly mode check to `GalleryAppBar` to hide the entire `TopControlAppBar` when readonly mode is enabled. - Changed `toggleReadonlyMode` in `ImmichAppBar` to directly toggle the state. Signed-off-by: Sudheer Puthana <Sud-Puth@users.noreply.github.com> migrate readonly mode to new beta timeline remove readonly mode from legacy UI only show readonly functionality when on beta timeline simplify selection icon update generated provider chore: more formatting * fix: bad merge * chore: use Notifier for readonlyModeProvider * fix: drag select now honors readonly mode * fix: disable asset bottom sheet in readonly * fix: disable editing user icon when in readonly * chore: remove generated file * fix: disable tabs instead entire tab bar This solves the issues with the scrubber * chore: remove unneeded import * chore: lint * remove unused condition in bottomsheet --------- Co-authored-by: Brandon Wees <brandonwees@gmail.com> Co-authored-by: Alex <alex.tran1502@gmail.com>
117 lines
4.2 KiB
Dart
117 lines
4.2 KiB
Dart
import 'package:easy_localization/easy_localization.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
|
import 'package:immich_mobile/domain/models/timeline.model.dart';
|
|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
|
import 'package:immich_mobile/extensions/theme_extensions.dart';
|
|
import 'package:immich_mobile/providers/haptic_feedback.provider.dart';
|
|
import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';
|
|
import 'package:immich_mobile/providers/infrastructure/readonly_mode.provider.dart';
|
|
import 'package:immich_mobile/providers/timeline/multiselect.provider.dart';
|
|
|
|
class TimelineHeader extends HookConsumerWidget {
|
|
final Bucket bucket;
|
|
final HeaderType header;
|
|
final double height;
|
|
final int assetOffset;
|
|
|
|
const TimelineHeader({
|
|
super.key,
|
|
required this.bucket,
|
|
required this.header,
|
|
required this.height,
|
|
required this.assetOffset,
|
|
});
|
|
|
|
String _formatMonth(BuildContext context, DateTime date) {
|
|
final formatter = date.year == DateTime.now().year
|
|
? DateFormat.MMMM(context.locale.toLanguageTag())
|
|
: DateFormat.yMMMM(context.locale.toLanguageTag());
|
|
return formatter.format(date);
|
|
}
|
|
|
|
String _formatDay(BuildContext context, DateTime date) {
|
|
final formatter = DateFormat.yMMMEd(context.locale.toLanguageTag());
|
|
return formatter.format(date);
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context, WidgetRef ref) {
|
|
if (bucket is! TimeBucket || header == HeaderType.none) {
|
|
return const SizedBox.shrink();
|
|
}
|
|
|
|
final date = (bucket as TimeBucket).date;
|
|
final isMonthHeader = header == HeaderType.month || header == HeaderType.monthAndDay;
|
|
final isDayHeader = header == HeaderType.day || header == HeaderType.monthAndDay;
|
|
|
|
return Padding(
|
|
padding: EdgeInsets.only(top: isMonthHeader ? 8.0 : 0.0, left: 12.0, right: 12.0),
|
|
child: SizedBox(
|
|
height: height,
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
|
children: [
|
|
if (isMonthHeader)
|
|
Row(
|
|
children: [
|
|
Text(
|
|
toBeginningOfSentenceCase(_formatMonth(context, date)),
|
|
style: context.textTheme.labelLarge?.copyWith(fontSize: 24),
|
|
),
|
|
const Spacer(),
|
|
if (header != HeaderType.monthAndDay) _BulkSelectIconButton(bucket: bucket, assetOffset: assetOffset),
|
|
],
|
|
),
|
|
if (isDayHeader)
|
|
Row(
|
|
children: [
|
|
Text(
|
|
toBeginningOfSentenceCase(_formatDay(context, date)),
|
|
style: context.textTheme.labelLarge?.copyWith(fontSize: 15),
|
|
),
|
|
const Spacer(),
|
|
_BulkSelectIconButton(bucket: bucket, assetOffset: assetOffset),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _BulkSelectIconButton extends ConsumerWidget {
|
|
final Bucket bucket;
|
|
final int assetOffset;
|
|
|
|
const _BulkSelectIconButton({required this.bucket, required this.assetOffset});
|
|
|
|
@override
|
|
Widget build(BuildContext context, WidgetRef ref) {
|
|
List<BaseAsset> bucketAssets;
|
|
try {
|
|
bucketAssets = ref.watch(timelineServiceProvider).getAssets(assetOffset, bucket.assetCount);
|
|
} catch (e) {
|
|
bucketAssets = <BaseAsset>[];
|
|
}
|
|
|
|
final isReadonlyModeEnabled = ref.watch(readonlyModeProvider);
|
|
final isAllSelected = ref.watch(bucketSelectionProvider(bucketAssets));
|
|
|
|
return isReadonlyModeEnabled
|
|
? const SizedBox.shrink()
|
|
: IconButton(
|
|
onPressed: () {
|
|
ref.read(multiSelectProvider.notifier).toggleBucketSelection(assetOffset, bucket.assetCount);
|
|
ref.read(hapticFeedbackProvider.notifier).heavyImpact();
|
|
},
|
|
icon: isAllSelected
|
|
? Icon(Icons.check_circle_rounded, size: 26, color: context.primaryColor)
|
|
: Icon(Icons.check_circle_outline_rounded, size: 26, color: context.colorScheme.onSurfaceSecondary),
|
|
);
|
|
}
|
|
}
|