Merge branch 'main' into menu_item_tweaks
This commit is contained in:
commit
d8c9f87f4a
7 changed files with 488 additions and 367 deletions
|
@ -55,3 +55,5 @@ const int intMaxValue = 9223372036854775807;
|
|||
const double restrictedMaxWidth = 430;
|
||||
|
||||
const double mobileSmallThreshold = 336;
|
||||
|
||||
const publicLinkDeviceLimits = [50, 25, 10, 5, 2, 1];
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
149
lib/ui/sharing/pickers/device_limit_picker_page.dart
Normal file
149
lib/ui/sharing/pickers/device_limit_picker_page.dart
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
181
lib/ui/sharing/pickers/link_expiry_picker_page.dart
Normal file
181
lib/ui/sharing/pickers/link_expiry_picker_page.dart
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
124
lib/ui/viewer/gallery/photo_grid_size_picker_page.dart
Normal file
124
lib/ui/viewer/gallery/photo_grid_size_picker_page.dart
Normal 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"),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue