Merge branch 'main' into menu_item_tweaks

This commit is contained in:
ashilkn 2023-02-03 17:17:46 +05:30
commit d8c9f87f4a
7 changed files with 488 additions and 367 deletions

View file

@ -55,3 +55,5 @@ const int intMaxValue = 9223372036854775807;
const double restrictedMaxWidth = 430;
const double mobileSmallThreshold = 336;
const publicLinkDeviceLimits = [50, 25, 10, 5, 2, 1];

View file

@ -22,7 +22,9 @@ class _SessionsPageState extends State<SessionsPage> {
@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<SessionsPage> {
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<SessionsPage> {
Future<void> _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) {

View file

@ -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<AdvancedSettingsScreen> {
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<AdvancedSettingsScreen> {
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<AdvancedSettingsScreen> {
),
singleBorderRadius: 8,
alignCaptionedTextToLeft: true,
// isBottomBorderRadiusRemoved: true,
isGestureDetectorDisabled: true,
),
),
@ -116,106 +119,4 @@ class _AdvancedSettingsScreenState extends State<AdvancedSettingsScreen> {
),
);
}
Future<void> _showPhotoGridSizePicker(BuildContext buildContext) async {
final textTheme = getEnteTextTheme(buildContext);
final List<Text> 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: <Widget>[
Container(
decoration: BoxDecoration(
color: getEnteColorScheme(buildContext).backgroundElevated2,
border: const Border(
bottom: BorderSide(
color: Color(0xff999999),
width: 0.0,
),
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
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;
}
}

View file

@ -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<ManageSharedLinkWidget> {
// index, title, milliseconds in future post which link should expire (when >0)
final List<Tuple3<int, String, int>> _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<int, String, int> _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<ManageSharedLinkWidget> {
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<ManageSharedLinkWidget> {
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<ManageSharedLinkWidget> {
);
if (result && mounted) {
Navigator.of(context).pop();
// setState(() => {});
}
},
),
@ -244,153 +238,6 @@ class _ManageSharedLinkWidgetState extends State<ManageSharedLinkWidget> {
);
}
Future<void> showPicker() async {
return showCupertinoModalPopup(
context: context,
builder: (context) {
return Column(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
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: <Widget>[
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<void> 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<int?> _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<String?> _displayLinkPasswordInput(BuildContext context) async {
@ -495,87 +342,4 @@ class _ManageSharedLinkWidgetState extends State<ManageSharedLinkWidget> {
await showGenericErrorDialog(context: context);
}
}
Future<void> _showDeviceLimitPicker() async {
final List<Text> 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: <Widget>[
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: <Widget>[
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,
),
)
],
);
},
);
}
}

View file

@ -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: <Widget>[
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<ItemsWidget> createState() => _ItemsWidgetState();
}
class _ItemsWidgetState extends State<ItemsWidget> {
late int currentDeviceLimit;
late int initialDeviceLimit;
List<Widget> 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<void> _updateUrlSettings(
BuildContext context,
Map<String, dynamic> prop,
) async {
try {
await CollectionsService.instance.updateShareUrl(widget.collection, prop);
} catch (e) {
showGenericErrorDialog(context: context);
rethrow;
}
}
}

View file

@ -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: <Widget>[
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<Tuple2<String, int>> _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<Widget> items = [];
for (Tuple2<String, int> 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<String, int> 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<int?> _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<void> updateTime(int newValidTill, BuildContext context) async {
await _updateUrlSettings(
context,
{'validTill': newValidTill},
);
}
Future<void> _updateUrlSettings(
BuildContext context,
Map<String, dynamic> prop,
) async {
try {
await CollectionsService.instance.updateShareUrl(collection, prop);
} catch (e) {
showGenericErrorDialog(context: context);
rethrow;
}
}
}

View file

@ -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: <Widget>[
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<ItemsWidget> createState() => _ItemsWidgetState();
}
class _ItemsWidgetState extends State<ItemsWidget> {
late int currentGridSize;
List<Widget> items = [];
final List<int> 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"),
);
},
);
}
}