diff --git a/lib/core/constants.dart b/lib/core/constants.dart index 6678521fc..236419b4d 100644 --- a/lib/core/constants.dart +++ b/lib/core/constants.dart @@ -55,3 +55,5 @@ const int intMaxValue = 9223372036854775807; const double restrictedMaxWidth = 430; const double mobileSmallThreshold = 336; + +const publicLinkDeviceLimits = [50, 25, 10, 5, 2, 1]; diff --git a/lib/ui/account/sessions_page.dart b/lib/ui/account/sessions_page.dart index 26c80ad3a..be219e582 100644 --- a/lib/ui/account/sessions_page.dart +++ b/lib/ui/account/sessions_page.dart @@ -22,7 +22,9 @@ class _SessionsPageState extends State { @override void initState() { - _fetchActiveSessions(); + _fetchActiveSessions().onError((error, stackTrace) { + showToast(context, "Failed to fetch active sessions"); + }); super.initState(); } @@ -115,9 +117,9 @@ class _SessionsPageState extends State { await UserService.instance.terminateSession(session.token); await _fetchActiveSessions(); await dialog.hide(); - } catch (e, s) { + } catch (e) { await dialog.hide(); - _logger.severe('failed to terminate', e, s); + _logger.severe('failed to terminate'); showErrorDialog( context, 'Oops', @@ -129,9 +131,7 @@ class _SessionsPageState extends State { Future _fetchActiveSessions() async { _sessions = await UserService.instance.getActiveSessions().onError((e, s) { _logger.severe("failed to fetch active sessions", e, s); - if (mounted) { - showToast(context, "Failed to fetch active sessions"); - } + throw e!; }); if (_sessions != null) { _sessions!.sessions.sort((first, second) { diff --git a/lib/ui/advanced_settings_screen.dart b/lib/ui/advanced_settings_screen.dart index e63ed4f00..35cf72604 100644 --- a/lib/ui/advanced_settings_screen.dart +++ b/lib/ui/advanced_settings_screen.dart @@ -1,8 +1,4 @@ -import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; -import 'package:photos/core/constants.dart'; -import 'package:photos/core/event_bus.dart'; -import 'package:photos/events/force_reload_home_gallery_event.dart'; import 'package:photos/theme/ente_theme.dart'; import 'package:photos/ui/components/captioned_text_widget.dart'; import 'package:photos/ui/components/icon_button_widget.dart'; @@ -10,6 +6,7 @@ import 'package:photos/ui/components/menu_item_widget/menu_item_widget.dart'; import 'package:photos/ui/components/title_bar_title_widget.dart'; import 'package:photos/ui/components/title_bar_widget.dart'; import 'package:photos/ui/tools/debug/app_storage_viewer.dart'; +import 'package:photos/ui/viewer/gallery/photo_grid_size_picker_page.dart'; import 'package:photos/utils/local_settings.dart'; import 'package:photos/utils/navigation_util.dart'; @@ -21,12 +18,11 @@ class AdvancedSettingsScreen extends StatefulWidget { } class _AdvancedSettingsScreenState extends State { - late int _photoGridSize, _chosenGridSize; + late int _photoGridSize; @override void initState() { _photoGridSize = LocalSettings.instance.getPhotoGridSize(); - _chosenGridSize = _photoGridSize; super.initState(); } @@ -66,7 +62,15 @@ class _AdvancedSettingsScreenState extends State { children: [ GestureDetector( onTap: () { - _showPhotoGridSizePicker(delegateBuildContext); + routeToPage( + context, + const PhotoGridSizePickerPage(), + ).then((value) { + setState(() { + _photoGridSize = LocalSettings.instance + .getPhotoGridSize(); + }); + }); }, child: MenuItemWidget( captionedTextWidget: CaptionedTextWidget( @@ -80,7 +84,6 @@ class _AdvancedSettingsScreenState extends State { ), singleBorderRadius: 8, alignCaptionedTextToLeft: true, - // isBottomBorderRadiusRemoved: true, isGestureDetectorDisabled: true, ), ), @@ -116,106 +119,4 @@ class _AdvancedSettingsScreenState extends State { ), ); } - - Future _showPhotoGridSizePicker(BuildContext buildContext) async { - final textTheme = getEnteTextTheme(buildContext); - final List options = []; - for (int gridSize = photoGridSizeMin; - gridSize <= photoGridSizeMax; - gridSize++) { - options.add( - Text( - gridSize.toString(), - style: textTheme.body, - ), - ); - } - return showCupertinoModalPopup( - context: context, - builder: (context) { - return Column( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - Container( - decoration: BoxDecoration( - color: getEnteColorScheme(buildContext).backgroundElevated2, - border: const Border( - bottom: BorderSide( - color: Color(0xff999999), - width: 0.0, - ), - ), - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - CupertinoButton( - onPressed: () { - Navigator.of(context).pop('cancel'); - }, - padding: const EdgeInsets.symmetric( - horizontal: 8.0, - vertical: 5.0, - ), - child: Text( - 'Cancel', - style: textTheme.body, - ), - ), - CupertinoButton( - onPressed: () async { - await LocalSettings.instance - .setPhotoGridSize(_chosenGridSize); - Bus.instance.fire( - ForceReloadHomeGalleryEvent("grid size changed"), - ); - _photoGridSize = _chosenGridSize; - setState(() {}); - Navigator.of(context).pop(''); - }, - padding: const EdgeInsets.symmetric( - horizontal: 16.0, - vertical: 2.0, - ), - child: Text( - 'Confirm', - style: textTheme.body, - ), - ) - ], - ), - ), - Container( - height: 220.0, - color: const Color(0xfff7f7f7), - child: CupertinoPicker( - backgroundColor: - getEnteColorScheme(buildContext).backgroundElevated, - onSelectedItemChanged: (index) { - _chosenGridSize = _getPhotoGridSizeFromIndex(index); - setState(() {}); - }, - scrollController: FixedExtentScrollController( - initialItem: _getIndexFromPhotoGridSize(_chosenGridSize), - ), - magnification: 1.3, - useMagnifier: true, - itemExtent: 25, - diameterRatio: 1, - children: options, - ), - ) - ], - ); - }, - ); - } - - int _getPhotoGridSizeFromIndex(int index) { - return index + 2; - } - - int _getIndexFromPhotoGridSize(int gridSize) { - return gridSize - 2; - } } diff --git a/lib/ui/sharing/manage_links_widget.dart b/lib/ui/sharing/manage_links_widget.dart index ce87b8f29..22b13df2b 100644 --- a/lib/ui/sharing/manage_links_widget.dart +++ b/lib/ui/sharing/manage_links_widget.dart @@ -4,9 +4,7 @@ import 'dart:typed_data'; import 'package:collection/collection.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_datetime_picker/flutter_datetime_picker.dart'; import 'package:flutter_sodium/flutter_sodium.dart'; -import 'package:photos/ente_theme_data.dart'; import 'package:photos/models/collection.dart'; import 'package:photos/services/collections_service.dart'; import 'package:photos/theme/colors.dart'; @@ -16,11 +14,13 @@ import 'package:photos/ui/components/captioned_text_widget.dart'; import 'package:photos/ui/components/divider_widget.dart'; import 'package:photos/ui/components/menu_item_widget/menu_item_widget.dart'; import 'package:photos/ui/components/menu_section_description_widget.dart'; +import 'package:photos/ui/sharing/pickers/device_limit_picker_page.dart'; +import 'package:photos/ui/sharing/pickers/link_expiry_picker_page.dart'; import 'package:photos/utils/crypto_util.dart'; import 'package:photos/utils/date_time_util.dart'; import 'package:photos/utils/dialog_util.dart'; +import 'package:photos/utils/navigation_util.dart'; import 'package:photos/utils/toast_util.dart'; -import 'package:tuple/tuple.dart'; class ManageSharedLinkWidget extends StatefulWidget { final Collection? collection; @@ -32,26 +32,11 @@ class ManageSharedLinkWidget extends StatefulWidget { } class _ManageSharedLinkWidgetState extends State { - // index, title, milliseconds in future post which link should expire (when >0) - final List> _expiryOptions = [ - const Tuple3(0, "Never", 0), - Tuple3(1, "After 1 hour", const Duration(hours: 1).inMicroseconds), - Tuple3(2, "After 1 day", const Duration(days: 1).inMicroseconds), - Tuple3(3, "After 1 week", const Duration(days: 7).inMicroseconds), - // todo: make this time calculation perfect - Tuple3(4, "After 1 month", const Duration(days: 30).inMicroseconds), - Tuple3(5, "After 1 year", const Duration(days: 365).inMicroseconds), - const Tuple3(6, "Custom", -1), - ]; - - late Tuple3 _selectedExpiry; - int _selectedDeviceLimitIndex = 0; final CollectionActions sharingActions = CollectionActions(CollectionsService.instance); @override void initState() { - _selectedExpiry = _expiryOptions.first; super.initState(); } @@ -114,7 +99,12 @@ class _ManageSharedLinkWidgetState extends State { menuItemColor: enteColorScheme.fillFaint, surfaceExecutionStates: false, onTap: () async { - await showPicker(); + routeToPage( + context, + LinkExpiryPickerPage(widget.collection!), + ).then((value) { + setState(() {}); + }); }, ), url.hasExpiry @@ -138,7 +128,12 @@ class _ManageSharedLinkWidgetState extends State { alignCaptionedTextToLeft: true, isBottomBorderRadiusRemoved: true, onTap: () async { - await _showDeviceLimitPicker(); + routeToPage( + context, + DeviceLimitPickerPage(widget.collection!), + ).then((value) { + setState(() {}); + }); }, surfaceExecutionStates: false, ), @@ -231,7 +226,6 @@ class _ManageSharedLinkWidgetState extends State { ); if (result && mounted) { Navigator.of(context).pop(); - // setState(() => {}); } }, ), @@ -244,153 +238,6 @@ class _ManageSharedLinkWidgetState extends State { ); } - Future showPicker() async { - return showCupertinoModalPopup( - context: context, - builder: (context) { - return Column( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - Container( - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.cupertinoPickerTopColor, - border: const Border( - bottom: BorderSide( - color: Color(0xff999999), - width: 0.0, - ), - ), - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - CupertinoButton( - onPressed: () { - Navigator.of(context).pop('cancel'); - }, - padding: const EdgeInsets.symmetric( - horizontal: 8.0, - vertical: 5.0, - ), - child: Text( - 'Cancel', - style: Theme.of(context).textTheme.subtitle1, - ), - ), - CupertinoButton( - onPressed: () async { - int newValidTill = -1; - bool hasSelectedCustom = false; - final int expireAfterInMicroseconds = - _selectedExpiry.item3; - // need to manually select time - if (expireAfterInMicroseconds < 0) { - hasSelectedCustom = true; - Navigator.of(context).pop(''); - final timeInMicrosecondsFromEpoch = - await _showDateTimePicker(); - if (timeInMicrosecondsFromEpoch != null) { - newValidTill = timeInMicrosecondsFromEpoch; - } - } else if (expireAfterInMicroseconds == 0) { - // no expiry - newValidTill = 0; - } else { - newValidTill = DateTime.now().microsecondsSinceEpoch + - expireAfterInMicroseconds; - } - if (!hasSelectedCustom) { - Navigator.of(context).pop(''); - } - if (newValidTill >= 0) { - debugPrint("Setting expirty $newValidTill"); - await updateTime(newValidTill); - } - }, - padding: const EdgeInsets.symmetric( - horizontal: 16.0, - vertical: 2.0, - ), - child: Text( - 'Confirm', - style: Theme.of(context).textTheme.subtitle1, - ), - ) - ], - ), - ), - Container( - height: 220.0, - color: const Color(0xfff7f7f7), - child: CupertinoPicker( - backgroundColor: - Theme.of(context).backgroundColor.withOpacity(0.95), - onSelectedItemChanged: (value) { - final firstWhere = _expiryOptions - .firstWhere((element) => element.item1 == value); - setState(() { - _selectedExpiry = firstWhere; - }); - }, - magnification: 1.3, - useMagnifier: true, - itemExtent: 25, - diameterRatio: 1, - children: _expiryOptions - .map( - (e) => Text( - e.item2, - style: Theme.of(context).textTheme.subtitle1, - ), - ) - .toList(), - ), - ) - ], - ); - }, - ); - } - - Future updateTime(int newValidTill) async { - await _updateUrlSettings( - context, - {'validTill': newValidTill}, - ); - if (mounted) { - // reset to default value. THis is needed will we move to - // new selection menu as per figma/ - _selectedExpiry = _expiryOptions.first; - setState(() {}); - } - } - - // _showDateTimePicker return null if user doesn't select date-time - Future _showDateTimePicker() async { - final dateResult = await DatePicker.showDatePicker( - context, - minTime: DateTime.now(), - currentTime: DateTime.now(), - locale: LocaleType.en, - theme: Theme.of(context).colorScheme.dateTimePickertheme, - ); - if (dateResult == null) { - return null; - } - final dateWithTimeResult = await DatePicker.showTime12hPicker( - context, - showTitleActions: true, - currentTime: dateResult, - locale: LocaleType.en, - theme: Theme.of(context).colorScheme.dateTimePickertheme, - ); - if (dateWithTimeResult == null) { - return null; - } else { - return dateWithTimeResult.microsecondsSinceEpoch; - } - } - final TextEditingController _textFieldController = TextEditingController(); Future _displayLinkPasswordInput(BuildContext context) async { @@ -495,87 +342,4 @@ class _ManageSharedLinkWidgetState extends State { await showGenericErrorDialog(context: context); } } - - Future _showDeviceLimitPicker() async { - final List options = []; - for (int i = 50; i > 0; i--) { - options.add( - Text(i.toString(), style: Theme.of(context).textTheme.subtitle1), - ); - } - return showCupertinoModalPopup( - context: context, - builder: (context) { - return Column( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - Container( - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.cupertinoPickerTopColor, - border: const Border( - bottom: BorderSide( - color: Color(0xff999999), - width: 0.0, - ), - ), - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - CupertinoButton( - onPressed: () { - Navigator.of(context).pop('cancel'); - }, - padding: const EdgeInsets.symmetric( - horizontal: 8.0, - vertical: 5.0, - ), - child: Text( - 'Cancel', - style: Theme.of(context).textTheme.subtitle1, - ), - ), - CupertinoButton( - onPressed: () async { - await _updateUrlSettings(context, { - 'deviceLimit': int.tryParse( - options[_selectedDeviceLimitIndex].data!, - ), - }); - setState(() {}); - Navigator.of(context).pop(''); - }, - padding: const EdgeInsets.symmetric( - horizontal: 16.0, - vertical: 2.0, - ), - child: Text( - 'Confirm', - style: Theme.of(context).textTheme.subtitle1, - ), - ) - ], - ), - ), - Container( - height: 220.0, - color: const Color(0xfff7f7f7), - child: CupertinoPicker( - backgroundColor: - Theme.of(context).backgroundColor.withOpacity(0.95), - onSelectedItemChanged: (value) { - _selectedDeviceLimitIndex = value; - }, - magnification: 1.3, - useMagnifier: true, - itemExtent: 25, - diameterRatio: 1, - children: options, - ), - ) - ], - ); - }, - ); - } } diff --git a/lib/ui/sharing/pickers/device_limit_picker_page.dart b/lib/ui/sharing/pickers/device_limit_picker_page.dart new file mode 100644 index 000000000..8a9430773 --- /dev/null +++ b/lib/ui/sharing/pickers/device_limit_picker_page.dart @@ -0,0 +1,149 @@ +import 'package:flutter/material.dart'; +import 'package:photos/core/constants.dart'; +import 'package:photos/models/collection.dart'; +import 'package:photos/services/collections_service.dart'; +import 'package:photos/theme/ente_theme.dart'; +import 'package:photos/ui/components/captioned_text_widget.dart'; +import 'package:photos/ui/components/divider_widget.dart'; +import 'package:photos/ui/components/menu_item_widget/menu_item_widget.dart'; +import 'package:photos/ui/components/menu_section_description_widget.dart'; +import 'package:photos/ui/components/title_bar_title_widget.dart'; +import 'package:photos/ui/components/title_bar_widget.dart'; +import 'package:photos/utils/dialog_util.dart'; +import 'package:photos/utils/separators_util.dart'; + +class DeviceLimitPickerPage extends StatelessWidget { + final Collection collection; + const DeviceLimitPickerPage(this.collection, {super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + body: CustomScrollView( + primary: false, + slivers: [ + const TitleBarWidget( + flexibleSpaceTitle: TitleBarTitleWidget( + title: "Device Limit", + ), + ), + SliverList( + delegate: SliverChildBuilderDelegate( + (context, index) { + return Padding( + padding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 20, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + ClipRRect( + borderRadius: + const BorderRadius.all(Radius.circular(8)), + child: ItemsWidget(collection), + ), + const MenuSectionDescriptionWidget( + content: + "When set to the maximum (50), the device limit will be relaxed" + " to allow for temporary spikes of large number of viewers.", + ) + ], + ), + ); + }, + childCount: 1, + ), + ), + const SliverPadding(padding: EdgeInsets.symmetric(vertical: 12)), + ], + ), + ); + } +} + +class ItemsWidget extends StatefulWidget { + final Collection collection; + const ItemsWidget(this.collection, {super.key}); + + @override + State createState() => _ItemsWidgetState(); +} + +class _ItemsWidgetState extends State { + late int currentDeviceLimit; + late int initialDeviceLimit; + List items = []; + bool isCustomLimit = false; + @override + void initState() { + currentDeviceLimit = widget.collection.publicURLs!.first!.deviceLimit; + initialDeviceLimit = currentDeviceLimit; + if (!publicLinkDeviceLimits.contains(currentDeviceLimit)) { + isCustomLimit = true; + } + super.initState(); + } + + @override + Widget build(BuildContext context) { + items.clear(); + if (isCustomLimit) { + items.add( + _menuItemForPicker(initialDeviceLimit), + ); + } + for (int deviceLimit in publicLinkDeviceLimits) { + items.add( + _menuItemForPicker(deviceLimit), + ); + } + items = addSeparators( + items, + DividerWidget( + dividerType: DividerType.menuNoIcon, + bgColor: getEnteColorScheme(context).fillFaint, + ), + ); + return Column( + mainAxisSize: MainAxisSize.min, + children: items, + ); + } + + Widget _menuItemForPicker(int deviceLimit) { + return MenuItemWidget( + key: ValueKey(deviceLimit), + menuItemColor: getEnteColorScheme(context).fillFaint, + captionedTextWidget: CaptionedTextWidget( + title: "$deviceLimit", + ), + trailingIcon: currentDeviceLimit == deviceLimit ? Icons.check : null, + alignCaptionedTextToLeft: true, + isTopBorderRadiusRemoved: true, + isBottomBorderRadiusRemoved: true, + showOnlyLoadingState: true, + onTap: () async { + await _updateUrlSettings(context, { + 'deviceLimit': deviceLimit, + }).then( + (value) => setState(() { + currentDeviceLimit = deviceLimit; + }), + ); + }, + ); + } + + Future _updateUrlSettings( + BuildContext context, + Map prop, + ) async { + try { + await CollectionsService.instance.updateShareUrl(widget.collection, prop); + } catch (e) { + showGenericErrorDialog(context: context); + rethrow; + } + } +} diff --git a/lib/ui/sharing/pickers/link_expiry_picker_page.dart b/lib/ui/sharing/pickers/link_expiry_picker_page.dart new file mode 100644 index 000000000..7c86e437a --- /dev/null +++ b/lib/ui/sharing/pickers/link_expiry_picker_page.dart @@ -0,0 +1,181 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_datetime_picker/flutter_datetime_picker.dart'; +import 'package:photos/ente_theme_data.dart'; +import 'package:photos/models/collection.dart'; +import 'package:photos/services/collections_service.dart'; +import 'package:photos/theme/ente_theme.dart'; +import 'package:photos/ui/components/captioned_text_widget.dart'; +import 'package:photos/ui/components/divider_widget.dart'; +import 'package:photos/ui/components/menu_item_widget/menu_item_widget.dart'; +import 'package:photos/ui/components/title_bar_title_widget.dart'; +import 'package:photos/ui/components/title_bar_widget.dart'; +import 'package:photos/utils/dialog_util.dart'; +import 'package:photos/utils/separators_util.dart'; +import 'package:tuple/tuple.dart'; + +class LinkExpiryPickerPage extends StatelessWidget { + final Collection collection; + const LinkExpiryPickerPage(this.collection, {super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + body: CustomScrollView( + primary: false, + slivers: [ + const TitleBarWidget( + flexibleSpaceTitle: TitleBarTitleWidget( + title: "Link expiry", + ), + ), + SliverList( + delegate: SliverChildBuilderDelegate( + (context, index) { + return Padding( + padding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 20, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + ClipRRect( + borderRadius: + const BorderRadius.all(Radius.circular(8)), + child: ItemsWidget(collection), + ), + ], + ), + ); + }, + childCount: 1, + ), + ), + const SliverPadding(padding: EdgeInsets.symmetric(vertical: 12)), + ], + ), + ); + } +} + +class ItemsWidget extends StatelessWidget { + final Collection collection; + ItemsWidget(this.collection, {super.key}); + + // index, title, milliseconds in future post which link should expire (when >0) + final List> _expiryOptions = [ + const Tuple2("Never", 0), + Tuple2("After 1 hour", const Duration(hours: 1).inMicroseconds), + Tuple2("After 1 day", const Duration(days: 1).inMicroseconds), + Tuple2("After 1 week", const Duration(days: 7).inMicroseconds), + // todo: make this time calculation perfect + Tuple2("After 1 month", const Duration(days: 30).inMicroseconds), + Tuple2("After 1 year", const Duration(days: 365).inMicroseconds), + const Tuple2("Custom", -1), + ]; + + @override + Widget build(BuildContext context) { + List items = []; + for (Tuple2 expiryOpiton in _expiryOptions) { + items.add( + _menuItemForPicker(context, expiryOpiton), + ); + } + items = addSeparators( + items, + DividerWidget( + dividerType: DividerType.menuNoIcon, + bgColor: getEnteColorScheme(context).fillFaint, + ), + ); + return Column( + mainAxisSize: MainAxisSize.min, + children: items, + ); + } + + Widget _menuItemForPicker( + BuildContext context, + Tuple2 expiryOpiton, + ) { + return MenuItemWidget( + menuItemColor: getEnteColorScheme(context).fillFaint, + captionedTextWidget: CaptionedTextWidget( + title: expiryOpiton.item1, + ), + alignCaptionedTextToLeft: true, + isTopBorderRadiusRemoved: true, + isBottomBorderRadiusRemoved: true, + alwaysShowSuccessState: true, + surfaceExecutionStates: expiryOpiton.item2 == -1 ? false : true, + onTap: () async { + int newValidTill = -1; + final int expireAfterInMicroseconds = expiryOpiton.item2; + // need to manually select time + if (expireAfterInMicroseconds < 0) { + final timeInMicrosecondsFromEpoch = + await _showDateTimePicker(context); + if (timeInMicrosecondsFromEpoch != null) { + newValidTill = timeInMicrosecondsFromEpoch; + } + } else if (expireAfterInMicroseconds == 0) { + // no expiry + newValidTill = 0; + } else { + newValidTill = + DateTime.now().microsecondsSinceEpoch + expireAfterInMicroseconds; + } + if (newValidTill >= 0) { + debugPrint("Setting expirty $newValidTill"); + await updateTime(newValidTill, context); + } + }, + ); + } + + // _showDateTimePicker return null if user doesn't select date-time + Future _showDateTimePicker(BuildContext context) async { + final dateResult = await DatePicker.showDatePicker( + context, + minTime: DateTime.now(), + currentTime: DateTime.now(), + locale: LocaleType.en, + theme: Theme.of(context).colorScheme.dateTimePickertheme, + ); + if (dateResult == null) { + return null; + } + final dateWithTimeResult = await DatePicker.showTime12hPicker( + context, + showTitleActions: true, + currentTime: dateResult, + locale: LocaleType.en, + theme: Theme.of(context).colorScheme.dateTimePickertheme, + ); + if (dateWithTimeResult == null) { + return null; + } else { + return dateWithTimeResult.microsecondsSinceEpoch; + } + } + + Future updateTime(int newValidTill, BuildContext context) async { + await _updateUrlSettings( + context, + {'validTill': newValidTill}, + ); + } + + Future _updateUrlSettings( + BuildContext context, + Map prop, + ) async { + try { + await CollectionsService.instance.updateShareUrl(collection, prop); + } catch (e) { + showGenericErrorDialog(context: context); + rethrow; + } + } +} diff --git a/lib/ui/viewer/gallery/photo_grid_size_picker_page.dart b/lib/ui/viewer/gallery/photo_grid_size_picker_page.dart new file mode 100644 index 000000000..a14787d5e --- /dev/null +++ b/lib/ui/viewer/gallery/photo_grid_size_picker_page.dart @@ -0,0 +1,124 @@ +import 'package:flutter/material.dart'; +import 'package:photos/core/constants.dart'; +import 'package:photos/core/event_bus.dart'; +import 'package:photos/events/force_reload_home_gallery_event.dart'; +import 'package:photos/theme/ente_theme.dart'; +import 'package:photos/ui/components/captioned_text_widget.dart'; +import 'package:photos/ui/components/divider_widget.dart'; +import 'package:photos/ui/components/menu_item_widget/menu_item_widget.dart'; +import 'package:photos/ui/components/title_bar_title_widget.dart'; +import 'package:photos/ui/components/title_bar_widget.dart'; +import 'package:photos/utils/local_settings.dart'; +import 'package:photos/utils/separators_util.dart'; + +class PhotoGridSizePickerPage extends StatelessWidget { + const PhotoGridSizePickerPage({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + body: CustomScrollView( + primary: false, + slivers: [ + const TitleBarWidget( + flexibleSpaceTitle: TitleBarTitleWidget( + title: "Photo grid size", + ), + ), + SliverList( + delegate: SliverChildBuilderDelegate( + (context, index) { + return Padding( + padding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 20, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: const [ + ClipRRect( + borderRadius: BorderRadius.all(Radius.circular(8)), + child: ItemsWidget(), + ), + ], + ), + ); + }, + childCount: 1, + ), + ), + const SliverPadding(padding: EdgeInsets.symmetric(vertical: 12)), + ], + ), + ); + } +} + +class ItemsWidget extends StatefulWidget { + const ItemsWidget({super.key}); + + @override + State createState() => _ItemsWidgetState(); +} + +class _ItemsWidgetState extends State { + late int currentGridSize; + List items = []; + final List gridSizes = []; + @override + void initState() { + currentGridSize = LocalSettings.instance.getPhotoGridSize(); + for (int gridSize = photoGridSizeMin; + gridSize <= photoGridSizeMax; + gridSize++) { + gridSizes.add(gridSize); + } + super.initState(); + } + + @override + Widget build(BuildContext context) { + items.clear(); + for (int girdSize in gridSizes) { + items.add( + _menuItemForPicker(girdSize), + ); + } + items = addSeparators( + items, + DividerWidget( + dividerType: DividerType.menuNoIcon, + bgColor: getEnteColorScheme(context).fillFaint, + ), + ); + return Column( + mainAxisSize: MainAxisSize.min, + children: items, + ); + } + + Widget _menuItemForPicker(int gridSize) { + return MenuItemWidget( + key: ValueKey(gridSize), + menuItemColor: getEnteColorScheme(context).fillFaint, + captionedTextWidget: CaptionedTextWidget( + title: "$gridSize", + ), + trailingIcon: currentGridSize == gridSize ? Icons.check : null, + alignCaptionedTextToLeft: true, + isTopBorderRadiusRemoved: true, + isBottomBorderRadiusRemoved: true, + showOnlyLoadingState: true, + onTap: () async { + await LocalSettings.instance.setPhotoGridSize(gridSize).then( + (value) => setState(() { + currentGridSize = gridSize; + }), + ); + Bus.instance.fire( + ForceReloadHomeGalleryEvent("grid size changed"), + ); + }, + ); + } +}