mirror of
https://gitlab.com/nkming2/nc-photos.git
synced 2025-03-25 00:14:42 +01:00
New settings page to configure nav bar in collections tab
This commit is contained in:
parent
1adbb453fd
commit
121f611c7f
21 changed files with 1220 additions and 213 deletions
|
@ -12,9 +12,11 @@ import 'package:nc_photos/k.dart' as k;
|
||||||
import 'package:nc_photos/language_util.dart';
|
import 'package:nc_photos/language_util.dart';
|
||||||
import 'package:nc_photos/protected_page_handler.dart';
|
import 'package:nc_photos/protected_page_handler.dart';
|
||||||
import 'package:nc_photos/size.dart';
|
import 'package:nc_photos/size.dart';
|
||||||
|
import 'package:nc_photos/widget/home_collections.dart';
|
||||||
import 'package:nc_photos/widget/viewer.dart';
|
import 'package:nc_photos/widget/viewer.dart';
|
||||||
import 'package:np_codegen/np_codegen.dart';
|
import 'package:np_codegen/np_codegen.dart';
|
||||||
import 'package:np_common/object_util.dart';
|
import 'package:np_common/object_util.dart';
|
||||||
|
import 'package:np_common/type.dart';
|
||||||
import 'package:np_gps_map/np_gps_map.dart';
|
import 'package:np_gps_map/np_gps_map.dart';
|
||||||
import 'package:np_string/np_string.dart';
|
import 'package:np_string/np_string.dart';
|
||||||
import 'package:rxdart/rxdart.dart';
|
import 'package:rxdart/rxdart.dart';
|
||||||
|
@ -261,6 +263,17 @@ class PrefController {
|
||||||
defaultValue: _viewerBottomAppBarButtonsDefault,
|
defaultValue: _viewerBottomAppBarButtonsDefault,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
Future<bool> setHomeCollectionsNavBarButtons(
|
||||||
|
List<PrefHomeCollectionsNavButton>? value) =>
|
||||||
|
_setOrRemove(
|
||||||
|
controller: _homeCollectionsNavBarButtonsController,
|
||||||
|
setter: (pref, value) => pref.setHomeCollectionsNavBarButtonsJson(
|
||||||
|
jsonEncode(value.map((e) => e.toJson()).toList())),
|
||||||
|
remover: (pref) => pref.setHomeCollectionsNavBarButtonsJson(null),
|
||||||
|
value: value,
|
||||||
|
defaultValue: _homeCollectionsNavBarButtonsDefault,
|
||||||
|
);
|
||||||
|
|
||||||
Future<bool> _set<T>({
|
Future<bool> _set<T>({
|
||||||
required BehaviorSubject<T> controller,
|
required BehaviorSubject<T> controller,
|
||||||
required Future<bool> Function(Pref pref, T value) setter,
|
required Future<bool> Function(Pref pref, T value) setter,
|
||||||
|
@ -406,6 +419,14 @@ class PrefController {
|
||||||
@npSubjectAccessor
|
@npSubjectAccessor
|
||||||
late final _viewerBottomAppBarButtonsController = BehaviorSubject.seeded(
|
late final _viewerBottomAppBarButtonsController = BehaviorSubject.seeded(
|
||||||
pref.getViewerBottomAppBarButtons() ?? _viewerBottomAppBarButtonsDefault);
|
pref.getViewerBottomAppBarButtons() ?? _viewerBottomAppBarButtonsDefault);
|
||||||
|
@npSubjectAccessor
|
||||||
|
late final _homeCollectionsNavBarButtonsController = BehaviorSubject.seeded(
|
||||||
|
pref.getHomeCollectionsNavBarButtonsJson()?.let((s) =>
|
||||||
|
(jsonDecode(s) as List)
|
||||||
|
.cast<JsonObj>()
|
||||||
|
.map(PrefHomeCollectionsNavButton.fromJson)
|
||||||
|
.toList()) ??
|
||||||
|
_homeCollectionsNavBarButtonsDefault);
|
||||||
}
|
}
|
||||||
|
|
||||||
extension PrefControllerExtension on PrefController {
|
extension PrefControllerExtension on PrefController {
|
||||||
|
@ -561,6 +582,24 @@ const _viewerBottomAppBarButtonsDefault = [
|
||||||
ViewerAppBarButtonType.download,
|
ViewerAppBarButtonType.download,
|
||||||
ViewerAppBarButtonType.delete,
|
ViewerAppBarButtonType.delete,
|
||||||
];
|
];
|
||||||
|
const _homeCollectionsNavBarButtonsDefault = [
|
||||||
|
PrefHomeCollectionsNavButton(
|
||||||
|
type: HomeCollectionsNavBarButtonType.sharing,
|
||||||
|
isMinimized: false,
|
||||||
|
),
|
||||||
|
PrefHomeCollectionsNavButton(
|
||||||
|
type: HomeCollectionsNavBarButtonType.edited,
|
||||||
|
isMinimized: false,
|
||||||
|
),
|
||||||
|
PrefHomeCollectionsNavButton(
|
||||||
|
type: HomeCollectionsNavBarButtonType.archive,
|
||||||
|
isMinimized: false,
|
||||||
|
),
|
||||||
|
PrefHomeCollectionsNavButton(
|
||||||
|
type: HomeCollectionsNavBarButtonType.trash,
|
||||||
|
isMinimized: false,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
@npLog
|
@npLog
|
||||||
// ignore: camel_case_types
|
// ignore: camel_case_types
|
||||||
|
|
|
@ -252,6 +252,18 @@ extension $PrefControllerNpSubjectAccessor on PrefController {
|
||||||
viewerBottomAppBarButtons.distinct().skip(1);
|
viewerBottomAppBarButtons.distinct().skip(1);
|
||||||
List<ViewerAppBarButtonType> get viewerBottomAppBarButtonsValue =>
|
List<ViewerAppBarButtonType> get viewerBottomAppBarButtonsValue =>
|
||||||
_viewerBottomAppBarButtonsController.value;
|
_viewerBottomAppBarButtonsController.value;
|
||||||
|
// _homeCollectionsNavBarButtonsController
|
||||||
|
ValueStream<List<PrefHomeCollectionsNavButton>>
|
||||||
|
get homeCollectionsNavBarButtons =>
|
||||||
|
_homeCollectionsNavBarButtonsController.stream;
|
||||||
|
Stream<List<PrefHomeCollectionsNavButton>>
|
||||||
|
get homeCollectionsNavBarButtonsNew =>
|
||||||
|
homeCollectionsNavBarButtons.skip(1);
|
||||||
|
Stream<List<PrefHomeCollectionsNavButton>>
|
||||||
|
get homeCollectionsNavBarButtonsChange =>
|
||||||
|
homeCollectionsNavBarButtons.distinct().skip(1);
|
||||||
|
List<PrefHomeCollectionsNavButton> get homeCollectionsNavBarButtonsValue =>
|
||||||
|
_homeCollectionsNavBarButtonsController.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
extension $SecurePrefControllerNpSubjectAccessor on SecurePrefController {
|
extension $SecurePrefControllerNpSubjectAccessor on SecurePrefController {
|
||||||
|
|
|
@ -14,3 +14,24 @@ enum PrefMapDefaultRangeType {
|
||||||
|
|
||||||
final int value;
|
final int value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class PrefHomeCollectionsNavButton {
|
||||||
|
const PrefHomeCollectionsNavButton({
|
||||||
|
required this.type,
|
||||||
|
required this.isMinimized,
|
||||||
|
});
|
||||||
|
|
||||||
|
static PrefHomeCollectionsNavButton fromJson(JsonObj json) =>
|
||||||
|
PrefHomeCollectionsNavButton(
|
||||||
|
type: HomeCollectionsNavBarButtonType.fromValue(json["type"]),
|
||||||
|
isMinimized: json["isMinimized"],
|
||||||
|
);
|
||||||
|
|
||||||
|
JsonObj toJson() => {
|
||||||
|
"type": type.index,
|
||||||
|
"isMinimized": isMinimized,
|
||||||
|
};
|
||||||
|
|
||||||
|
final HomeCollectionsNavBarButtonType type;
|
||||||
|
final bool isMinimized;
|
||||||
|
}
|
||||||
|
|
|
@ -185,6 +185,16 @@ extension on Pref {
|
||||||
value.map((e) => e.index).toList());
|
value.map((e) => e.index).toList());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String? getHomeCollectionsNavBarButtonsJson() =>
|
||||||
|
provider.getString(PrefKey.homeCollectionsNavBarButtons);
|
||||||
|
Future<bool> setHomeCollectionsNavBarButtonsJson(String? value) {
|
||||||
|
if (value == null) {
|
||||||
|
return provider.remove(PrefKey.homeCollectionsNavBarButtons);
|
||||||
|
} else {
|
||||||
|
return provider.setString(PrefKey.homeCollectionsNavBarButtons, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MapCoord? _tryMapCoordFromJson(dynamic json) {
|
MapCoord? _tryMapCoordFromJson(dynamic json) {
|
||||||
|
|
|
@ -117,6 +117,7 @@ enum PrefKey implements PrefKeyInterface {
|
||||||
mapDefaultCustomRange,
|
mapDefaultCustomRange,
|
||||||
viewerAppBarButtons,
|
viewerAppBarButtons,
|
||||||
viewerBottomAppBarButtons,
|
viewerBottomAppBarButtons,
|
||||||
|
homeCollectionsNavBarButtons,
|
||||||
;
|
;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -215,6 +216,8 @@ enum PrefKey implements PrefKeyInterface {
|
||||||
return "viewerAppBarButtons";
|
return "viewerAppBarButtons";
|
||||||
case PrefKey.viewerBottomAppBarButtons:
|
case PrefKey.viewerBottomAppBarButtons:
|
||||||
return "viewerBottomAppBarButtons";
|
return "viewerBottomAppBarButtons";
|
||||||
|
case PrefKey.homeCollectionsNavBarButtons:
|
||||||
|
return "homeCollectionsNavBarButtons";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -360,6 +360,7 @@
|
||||||
"settingsViewerCustomizeBottomAppBarTitle": "Customize bottom app bar",
|
"settingsViewerCustomizeBottomAppBarTitle": "Customize bottom app bar",
|
||||||
"settingsShowDateInAlbumTitle": "Group photos by date",
|
"settingsShowDateInAlbumTitle": "Group photos by date",
|
||||||
"settingsShowDateInAlbumDescription": "Apply only when the album is sorted by time",
|
"settingsShowDateInAlbumDescription": "Apply only when the album is sorted by time",
|
||||||
|
"settingsCollectionsCustomizeNavigationBarTitle": "Customize navigation bar",
|
||||||
"settingsImageEditTitle": "Editor",
|
"settingsImageEditTitle": "Editor",
|
||||||
"@settingsImageEditTitle": {
|
"@settingsImageEditTitle": {
|
||||||
"description": "Include settings for image enhancements and the image editor"
|
"description": "Include settings for image enhancements and the image editor"
|
||||||
|
@ -1512,6 +1513,10 @@
|
||||||
"@dragAndDropRearrangeButtons": {
|
"@dragAndDropRearrangeButtons": {
|
||||||
"description": "Instruction to customize buttons layout"
|
"description": "Instruction to customize buttons layout"
|
||||||
},
|
},
|
||||||
|
"customizeCollectionsNavBarDescription": "Drag and drop to rearrange buttons, tap the buttons above to minimize them",
|
||||||
|
"@customizeCollectionsNavBarDescription": {
|
||||||
|
"description": "Instruction to customize navigation bar buttons in the Collections page"
|
||||||
|
},
|
||||||
|
|
||||||
"errorUnauthenticated": "Unauthenticated access. Please sign-in again if the problem continues",
|
"errorUnauthenticated": "Unauthenticated access. Please sign-in again if the problem continues",
|
||||||
"@errorUnauthenticated": {
|
"@errorUnauthenticated": {
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
"settingsViewerCustomizeBottomAppBarTitle",
|
"settingsViewerCustomizeBottomAppBarTitle",
|
||||||
"settingsShowDateInAlbumTitle",
|
"settingsShowDateInAlbumTitle",
|
||||||
"settingsShowDateInAlbumDescription",
|
"settingsShowDateInAlbumDescription",
|
||||||
|
"settingsCollectionsCustomizeNavigationBarTitle",
|
||||||
"settingsImageEditTitle",
|
"settingsImageEditTitle",
|
||||||
"settingsImageEditDescription",
|
"settingsImageEditDescription",
|
||||||
"settingsEnhanceMaxResolutionTitle2",
|
"settingsEnhanceMaxResolutionTitle2",
|
||||||
|
@ -265,6 +266,7 @@
|
||||||
"alternativeSignIn",
|
"alternativeSignIn",
|
||||||
"livePhotoTooltip",
|
"livePhotoTooltip",
|
||||||
"dragAndDropRearrangeButtons",
|
"dragAndDropRearrangeButtons",
|
||||||
|
"customizeCollectionsNavBarDescription",
|
||||||
"errorUnauthenticated",
|
"errorUnauthenticated",
|
||||||
"errorDisconnected",
|
"errorDisconnected",
|
||||||
"errorLocked",
|
"errorLocked",
|
||||||
|
@ -278,17 +280,21 @@
|
||||||
"cs": [
|
"cs": [
|
||||||
"settingsViewerCustomizeAppBarTitle",
|
"settingsViewerCustomizeAppBarTitle",
|
||||||
"settingsViewerCustomizeBottomAppBarTitle",
|
"settingsViewerCustomizeBottomAppBarTitle",
|
||||||
|
"settingsCollectionsCustomizeNavigationBarTitle",
|
||||||
"alternativeSignIn",
|
"alternativeSignIn",
|
||||||
"livePhotoTooltip",
|
"livePhotoTooltip",
|
||||||
"dragAndDropRearrangeButtons"
|
"dragAndDropRearrangeButtons",
|
||||||
|
"customizeCollectionsNavBarDescription"
|
||||||
],
|
],
|
||||||
|
|
||||||
"de": [
|
"de": [
|
||||||
"settingsViewerCustomizeAppBarTitle",
|
"settingsViewerCustomizeAppBarTitle",
|
||||||
"settingsViewerCustomizeBottomAppBarTitle",
|
"settingsViewerCustomizeBottomAppBarTitle",
|
||||||
|
"settingsCollectionsCustomizeNavigationBarTitle",
|
||||||
"alternativeSignIn",
|
"alternativeSignIn",
|
||||||
"livePhotoTooltip",
|
"livePhotoTooltip",
|
||||||
"dragAndDropRearrangeButtons"
|
"dragAndDropRearrangeButtons",
|
||||||
|
"customizeCollectionsNavBarDescription"
|
||||||
],
|
],
|
||||||
|
|
||||||
"el": [
|
"el": [
|
||||||
|
@ -308,6 +314,7 @@
|
||||||
"settingsMemoriesRangeValueText",
|
"settingsMemoriesRangeValueText",
|
||||||
"settingsViewerCustomizeAppBarTitle",
|
"settingsViewerCustomizeAppBarTitle",
|
||||||
"settingsViewerCustomizeBottomAppBarTitle",
|
"settingsViewerCustomizeBottomAppBarTitle",
|
||||||
|
"settingsCollectionsCustomizeNavigationBarTitle",
|
||||||
"settingsImageEditTitle",
|
"settingsImageEditTitle",
|
||||||
"settingsImageEditDescription",
|
"settingsImageEditDescription",
|
||||||
"settingsEnhanceMaxResolutionTitle2",
|
"settingsEnhanceMaxResolutionTitle2",
|
||||||
|
@ -441,20 +448,24 @@
|
||||||
"todayText",
|
"todayText",
|
||||||
"alternativeSignIn",
|
"alternativeSignIn",
|
||||||
"livePhotoTooltip",
|
"livePhotoTooltip",
|
||||||
"dragAndDropRearrangeButtons"
|
"dragAndDropRearrangeButtons",
|
||||||
|
"customizeCollectionsNavBarDescription"
|
||||||
],
|
],
|
||||||
|
|
||||||
"es": [
|
"es": [
|
||||||
"settingsViewerCustomizeAppBarTitle",
|
"settingsViewerCustomizeAppBarTitle",
|
||||||
"settingsViewerCustomizeBottomAppBarTitle",
|
"settingsViewerCustomizeBottomAppBarTitle",
|
||||||
|
"settingsCollectionsCustomizeNavigationBarTitle",
|
||||||
"alternativeSignIn",
|
"alternativeSignIn",
|
||||||
"livePhotoTooltip",
|
"livePhotoTooltip",
|
||||||
"dragAndDropRearrangeButtons"
|
"dragAndDropRearrangeButtons",
|
||||||
|
"customizeCollectionsNavBarDescription"
|
||||||
],
|
],
|
||||||
|
|
||||||
"fi": [
|
"fi": [
|
||||||
"settingsViewerCustomizeAppBarTitle",
|
"settingsViewerCustomizeAppBarTitle",
|
||||||
"settingsViewerCustomizeBottomAppBarTitle",
|
"settingsViewerCustomizeBottomAppBarTitle",
|
||||||
|
"settingsCollectionsCustomizeNavigationBarTitle",
|
||||||
"settingsThemePrimaryColor",
|
"settingsThemePrimaryColor",
|
||||||
"settingsThemeSecondaryColor",
|
"settingsThemeSecondaryColor",
|
||||||
"settingsThemePresets",
|
"settingsThemePresets",
|
||||||
|
@ -493,12 +504,14 @@
|
||||||
"todayText",
|
"todayText",
|
||||||
"alternativeSignIn",
|
"alternativeSignIn",
|
||||||
"livePhotoTooltip",
|
"livePhotoTooltip",
|
||||||
"dragAndDropRearrangeButtons"
|
"dragAndDropRearrangeButtons",
|
||||||
|
"customizeCollectionsNavBarDescription"
|
||||||
],
|
],
|
||||||
|
|
||||||
"fr": [
|
"fr": [
|
||||||
"settingsViewerCustomizeAppBarTitle",
|
"settingsViewerCustomizeAppBarTitle",
|
||||||
"settingsViewerCustomizeBottomAppBarTitle",
|
"settingsViewerCustomizeBottomAppBarTitle",
|
||||||
|
"settingsCollectionsCustomizeNavigationBarTitle",
|
||||||
"settingsThemePrimaryColor",
|
"settingsThemePrimaryColor",
|
||||||
"settingsThemeSecondaryColor",
|
"settingsThemeSecondaryColor",
|
||||||
"settingsThemePresets",
|
"settingsThemePresets",
|
||||||
|
@ -537,13 +550,15 @@
|
||||||
"todayText",
|
"todayText",
|
||||||
"alternativeSignIn",
|
"alternativeSignIn",
|
||||||
"livePhotoTooltip",
|
"livePhotoTooltip",
|
||||||
"dragAndDropRearrangeButtons"
|
"dragAndDropRearrangeButtons",
|
||||||
|
"customizeCollectionsNavBarDescription"
|
||||||
],
|
],
|
||||||
|
|
||||||
"it": [
|
"it": [
|
||||||
"settingsPersonProviderTitle",
|
"settingsPersonProviderTitle",
|
||||||
"settingsViewerCustomizeAppBarTitle",
|
"settingsViewerCustomizeAppBarTitle",
|
||||||
"settingsViewerCustomizeBottomAppBarTitle",
|
"settingsViewerCustomizeBottomAppBarTitle",
|
||||||
|
"settingsCollectionsCustomizeNavigationBarTitle",
|
||||||
"settingsImageEditTitle",
|
"settingsImageEditTitle",
|
||||||
"settingsThemePrimaryColor",
|
"settingsThemePrimaryColor",
|
||||||
"settingsThemeSecondaryColor",
|
"settingsThemeSecondaryColor",
|
||||||
|
@ -586,7 +601,8 @@
|
||||||
"todayText",
|
"todayText",
|
||||||
"alternativeSignIn",
|
"alternativeSignIn",
|
||||||
"livePhotoTooltip",
|
"livePhotoTooltip",
|
||||||
"dragAndDropRearrangeButtons"
|
"dragAndDropRearrangeButtons",
|
||||||
|
"customizeCollectionsNavBarDescription"
|
||||||
],
|
],
|
||||||
|
|
||||||
"nl": [
|
"nl": [
|
||||||
|
@ -647,6 +663,7 @@
|
||||||
"settingsViewerCustomizeBottomAppBarTitle",
|
"settingsViewerCustomizeBottomAppBarTitle",
|
||||||
"settingsShowDateInAlbumTitle",
|
"settingsShowDateInAlbumTitle",
|
||||||
"settingsShowDateInAlbumDescription",
|
"settingsShowDateInAlbumDescription",
|
||||||
|
"settingsCollectionsCustomizeNavigationBarTitle",
|
||||||
"settingsImageEditTitle",
|
"settingsImageEditTitle",
|
||||||
"settingsImageEditDescription",
|
"settingsImageEditDescription",
|
||||||
"settingsEnhanceMaxResolutionTitle2",
|
"settingsEnhanceMaxResolutionTitle2",
|
||||||
|
@ -972,6 +989,7 @@
|
||||||
"alternativeSignIn",
|
"alternativeSignIn",
|
||||||
"livePhotoTooltip",
|
"livePhotoTooltip",
|
||||||
"dragAndDropRearrangeButtons",
|
"dragAndDropRearrangeButtons",
|
||||||
|
"customizeCollectionsNavBarDescription",
|
||||||
"errorUnauthenticated",
|
"errorUnauthenticated",
|
||||||
"errorDisconnected",
|
"errorDisconnected",
|
||||||
"errorLocked",
|
"errorLocked",
|
||||||
|
@ -986,6 +1004,7 @@
|
||||||
"settingsMemoriesRangeValueText",
|
"settingsMemoriesRangeValueText",
|
||||||
"settingsViewerCustomizeAppBarTitle",
|
"settingsViewerCustomizeAppBarTitle",
|
||||||
"settingsViewerCustomizeBottomAppBarTitle",
|
"settingsViewerCustomizeBottomAppBarTitle",
|
||||||
|
"settingsCollectionsCustomizeNavigationBarTitle",
|
||||||
"settingsThemePrimaryColor",
|
"settingsThemePrimaryColor",
|
||||||
"settingsThemeSecondaryColor",
|
"settingsThemeSecondaryColor",
|
||||||
"settingsThemePresets",
|
"settingsThemePresets",
|
||||||
|
@ -1027,7 +1046,8 @@
|
||||||
"todayText",
|
"todayText",
|
||||||
"alternativeSignIn",
|
"alternativeSignIn",
|
||||||
"livePhotoTooltip",
|
"livePhotoTooltip",
|
||||||
"dragAndDropRearrangeButtons"
|
"dragAndDropRearrangeButtons",
|
||||||
|
"customizeCollectionsNavBarDescription"
|
||||||
],
|
],
|
||||||
|
|
||||||
"pt": [
|
"pt": [
|
||||||
|
@ -1036,6 +1056,7 @@
|
||||||
"settingsPersonProviderTitle",
|
"settingsPersonProviderTitle",
|
||||||
"settingsViewerCustomizeAppBarTitle",
|
"settingsViewerCustomizeAppBarTitle",
|
||||||
"settingsViewerCustomizeBottomAppBarTitle",
|
"settingsViewerCustomizeBottomAppBarTitle",
|
||||||
|
"settingsCollectionsCustomizeNavigationBarTitle",
|
||||||
"settingsSeedColorSystemColorDescription",
|
"settingsSeedColorSystemColorDescription",
|
||||||
"settingsThemePrimaryColor",
|
"settingsThemePrimaryColor",
|
||||||
"settingsThemeSecondaryColor",
|
"settingsThemeSecondaryColor",
|
||||||
|
@ -1091,12 +1112,14 @@
|
||||||
"todayText",
|
"todayText",
|
||||||
"alternativeSignIn",
|
"alternativeSignIn",
|
||||||
"livePhotoTooltip",
|
"livePhotoTooltip",
|
||||||
"dragAndDropRearrangeButtons"
|
"dragAndDropRearrangeButtons",
|
||||||
|
"customizeCollectionsNavBarDescription"
|
||||||
],
|
],
|
||||||
|
|
||||||
"ru": [
|
"ru": [
|
||||||
"settingsViewerCustomizeAppBarTitle",
|
"settingsViewerCustomizeAppBarTitle",
|
||||||
"settingsViewerCustomizeBottomAppBarTitle",
|
"settingsViewerCustomizeBottomAppBarTitle",
|
||||||
|
"settingsCollectionsCustomizeNavigationBarTitle",
|
||||||
"settingsThemePrimaryColor",
|
"settingsThemePrimaryColor",
|
||||||
"settingsThemeSecondaryColor",
|
"settingsThemeSecondaryColor",
|
||||||
"settingsThemePresets",
|
"settingsThemePresets",
|
||||||
|
@ -1135,15 +1158,18 @@
|
||||||
"todayText",
|
"todayText",
|
||||||
"alternativeSignIn",
|
"alternativeSignIn",
|
||||||
"livePhotoTooltip",
|
"livePhotoTooltip",
|
||||||
"dragAndDropRearrangeButtons"
|
"dragAndDropRearrangeButtons",
|
||||||
|
"customizeCollectionsNavBarDescription"
|
||||||
],
|
],
|
||||||
|
|
||||||
"tr": [
|
"tr": [
|
||||||
"settingsViewerCustomizeAppBarTitle",
|
"settingsViewerCustomizeAppBarTitle",
|
||||||
"settingsViewerCustomizeBottomAppBarTitle",
|
"settingsViewerCustomizeBottomAppBarTitle",
|
||||||
|
"settingsCollectionsCustomizeNavigationBarTitle",
|
||||||
"alternativeSignIn",
|
"alternativeSignIn",
|
||||||
"livePhotoTooltip",
|
"livePhotoTooltip",
|
||||||
"dragAndDropRearrangeButtons"
|
"dragAndDropRearrangeButtons",
|
||||||
|
"customizeCollectionsNavBarDescription"
|
||||||
],
|
],
|
||||||
|
|
||||||
"zh": [
|
"zh": [
|
||||||
|
@ -1152,6 +1178,7 @@
|
||||||
"settingsMemoriesRangeValueText",
|
"settingsMemoriesRangeValueText",
|
||||||
"settingsViewerCustomizeAppBarTitle",
|
"settingsViewerCustomizeAppBarTitle",
|
||||||
"settingsViewerCustomizeBottomAppBarTitle",
|
"settingsViewerCustomizeBottomAppBarTitle",
|
||||||
|
"settingsCollectionsCustomizeNavigationBarTitle",
|
||||||
"settingsSeedColorDescription",
|
"settingsSeedColorDescription",
|
||||||
"settingsSeedColorSystemColorDescription",
|
"settingsSeedColorSystemColorDescription",
|
||||||
"settingsThemePrimaryColor",
|
"settingsThemePrimaryColor",
|
||||||
|
@ -1218,7 +1245,8 @@
|
||||||
"todayText",
|
"todayText",
|
||||||
"alternativeSignIn",
|
"alternativeSignIn",
|
||||||
"livePhotoTooltip",
|
"livePhotoTooltip",
|
||||||
"dragAndDropRearrangeButtons"
|
"dragAndDropRearrangeButtons",
|
||||||
|
"customizeCollectionsNavBarDescription"
|
||||||
],
|
],
|
||||||
|
|
||||||
"zh_Hant": [
|
"zh_Hant": [
|
||||||
|
@ -1238,6 +1266,7 @@
|
||||||
"settingsMemoriesRangeValueText",
|
"settingsMemoriesRangeValueText",
|
||||||
"settingsViewerCustomizeAppBarTitle",
|
"settingsViewerCustomizeAppBarTitle",
|
||||||
"settingsViewerCustomizeBottomAppBarTitle",
|
"settingsViewerCustomizeBottomAppBarTitle",
|
||||||
|
"settingsCollectionsCustomizeNavigationBarTitle",
|
||||||
"settingsImageEditTitle",
|
"settingsImageEditTitle",
|
||||||
"settingsImageEditDescription",
|
"settingsImageEditDescription",
|
||||||
"settingsEnhanceMaxResolutionTitle2",
|
"settingsEnhanceMaxResolutionTitle2",
|
||||||
|
@ -1387,6 +1416,7 @@
|
||||||
"todayText",
|
"todayText",
|
||||||
"alternativeSignIn",
|
"alternativeSignIn",
|
||||||
"livePhotoTooltip",
|
"livePhotoTooltip",
|
||||||
"dragAndDropRearrangeButtons"
|
"dragAndDropRearrangeButtons",
|
||||||
|
"customizeCollectionsNavBarDescription"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,6 +51,7 @@ import 'package:to_string/to_string.dart';
|
||||||
part 'home_collections.g.dart';
|
part 'home_collections.g.dart';
|
||||||
part 'home_collections/app_bar.dart';
|
part 'home_collections/app_bar.dart';
|
||||||
part 'home_collections/bloc.dart';
|
part 'home_collections/bloc.dart';
|
||||||
|
part 'home_collections/nav_bar_buttons.dart';
|
||||||
part 'home_collections/navigation_bar.dart';
|
part 'home_collections/navigation_bar.dart';
|
||||||
part 'home_collections/state_event.dart';
|
part 'home_collections/state_event.dart';
|
||||||
part 'home_collections/type.dart';
|
part 'home_collections/type.dart';
|
||||||
|
@ -226,6 +227,7 @@ typedef _BlocBuilder = BlocBuilder<_Bloc, _State>;
|
||||||
typedef _BlocListener = BlocListener<_Bloc, _State>;
|
typedef _BlocListener = BlocListener<_Bloc, _State>;
|
||||||
// typedef _BlocListenerT<T> = BlocListenerT<_Bloc, _State, T>;
|
// typedef _BlocListenerT<T> = BlocListenerT<_Bloc, _State, T>;
|
||||||
typedef _BlocSelector<T> = BlocSelector<_Bloc, _State, T>;
|
typedef _BlocSelector<T> = BlocSelector<_Bloc, _State, T>;
|
||||||
|
typedef _Emitter = Emitter<_State>;
|
||||||
|
|
||||||
extension on BuildContext {
|
extension on BuildContext {
|
||||||
_Bloc get bloc => read<_Bloc>();
|
_Bloc get bloc => read<_Bloc>();
|
||||||
|
|
|
@ -20,6 +20,7 @@ abstract class $_StateCopyWithWorker {
|
||||||
List<_Item>? transformedItems,
|
List<_Item>? transformedItems,
|
||||||
Set<_Item>? selectedItems,
|
Set<_Item>? selectedItems,
|
||||||
Map<String, int>? itemCounts,
|
Map<String, int>? itemCounts,
|
||||||
|
List<PrefHomeCollectionsNavButton>? navBarButtons,
|
||||||
ExceptionEvent? error,
|
ExceptionEvent? error,
|
||||||
ExceptionEvent? removeError});
|
ExceptionEvent? removeError});
|
||||||
}
|
}
|
||||||
|
@ -35,6 +36,7 @@ class _$_StateCopyWithWorkerImpl implements $_StateCopyWithWorker {
|
||||||
dynamic transformedItems,
|
dynamic transformedItems,
|
||||||
dynamic selectedItems,
|
dynamic selectedItems,
|
||||||
dynamic itemCounts,
|
dynamic itemCounts,
|
||||||
|
dynamic navBarButtons,
|
||||||
dynamic error = copyWithNull,
|
dynamic error = copyWithNull,
|
||||||
dynamic removeError = copyWithNull}) {
|
dynamic removeError = copyWithNull}) {
|
||||||
return _State(
|
return _State(
|
||||||
|
@ -45,6 +47,8 @@ class _$_StateCopyWithWorkerImpl implements $_StateCopyWithWorker {
|
||||||
transformedItems as List<_Item>? ?? that.transformedItems,
|
transformedItems as List<_Item>? ?? that.transformedItems,
|
||||||
selectedItems: selectedItems as Set<_Item>? ?? that.selectedItems,
|
selectedItems: selectedItems as Set<_Item>? ?? that.selectedItems,
|
||||||
itemCounts: itemCounts as Map<String, int>? ?? that.itemCounts,
|
itemCounts: itemCounts as Map<String, int>? ?? that.itemCounts,
|
||||||
|
navBarButtons: navBarButtons as List<PrefHomeCollectionsNavButton>? ??
|
||||||
|
that.navBarButtons,
|
||||||
error: error == copyWithNull ? that.error : error as ExceptionEvent?,
|
error: error == copyWithNull ? that.error : error as ExceptionEvent?,
|
||||||
removeError: removeError == copyWithNull
|
removeError: removeError == copyWithNull
|
||||||
? that.removeError
|
? that.removeError
|
||||||
|
@ -106,7 +110,7 @@ extension _$_ItemNpLog on _Item {
|
||||||
extension _$_StateToString on _State {
|
extension _$_StateToString on _State {
|
||||||
String _$toString() {
|
String _$toString() {
|
||||||
// ignore: unnecessary_string_interpolations
|
// ignore: unnecessary_string_interpolations
|
||||||
return "_State {collections: [length: ${collections.length}], sort: ${sort.name}, isLoading: $isLoading, transformedItems: [length: ${transformedItems.length}], selectedItems: {length: ${selectedItems.length}}, itemCounts: {length: ${itemCounts.length}}, error: $error, removeError: $removeError}";
|
return "_State {collections: [length: ${collections.length}], sort: ${sort.name}, isLoading: $isLoading, transformedItems: [length: ${transformedItems.length}], selectedItems: {length: ${selectedItems.length}}, itemCounts: {length: ${itemCounts.length}}, navBarButtons: [length: ${navBarButtons.length}], error: $error, removeError: $removeError}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -166,6 +170,13 @@ extension _$_SetItemCountToString on _SetItemCount {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension _$_SetNavBarButtonsToString on _SetNavBarButtons {
|
||||||
|
String _$toString() {
|
||||||
|
// ignore: unnecessary_string_interpolations
|
||||||
|
return "_SetNavBarButtons {value: [length: ${value.length}]}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
extension _$_SetErrorToString on _SetError {
|
extension _$_SetErrorToString on _SetError {
|
||||||
String _$toString() {
|
String _$toString() {
|
||||||
// ignore: unnecessary_string_interpolations
|
// ignore: unnecessary_string_interpolations
|
||||||
|
|
|
@ -9,6 +9,7 @@ class _Bloc extends Bloc<_Event, _State>
|
||||||
required this.prefController,
|
required this.prefController,
|
||||||
}) : super(_State.init(
|
}) : super(_State.init(
|
||||||
sort: prefController.homeAlbumsSortValue,
|
sort: prefController.homeAlbumsSortValue,
|
||||||
|
navBarButtons: prefController.homeCollectionsNavBarButtonsValue,
|
||||||
)) {
|
)) {
|
||||||
on<_LoadCollections>(_onLoad);
|
on<_LoadCollections>(_onLoad);
|
||||||
on<_ReloadCollections>(_onReload);
|
on<_ReloadCollections>(_onReload);
|
||||||
|
@ -21,6 +22,8 @@ class _Bloc extends Bloc<_Event, _State>
|
||||||
on<_SetCollectionSort>(_onSetCollectionSort);
|
on<_SetCollectionSort>(_onSetCollectionSort);
|
||||||
on<_SetItemCount>(_onSetItemCount);
|
on<_SetItemCount>(_onSetItemCount);
|
||||||
|
|
||||||
|
on<_SetNavBarButtons>(_onSetNavBarButtons);
|
||||||
|
|
||||||
on<_SetError>(_onSetError);
|
on<_SetError>(_onSetError);
|
||||||
|
|
||||||
_subscriptions.add(prefController.homeAlbumsSortChange.listen((event) {
|
_subscriptions.add(prefController.homeAlbumsSortChange.listen((event) {
|
||||||
|
@ -39,6 +42,10 @@ class _Bloc extends Bloc<_Event, _State>
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
_subscriptions
|
||||||
|
.add(prefController.homeCollectionsNavBarButtonsChange.listen((event) {
|
||||||
|
add(_SetNavBarButtons(event));
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -134,6 +141,11 @@ class _Bloc extends Bloc<_Event, _State>
|
||||||
emit(state.copyWith(itemCounts: next));
|
emit(state.copyWith(itemCounts: next));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _onSetNavBarButtons(_SetNavBarButtons ev, _Emitter emit) {
|
||||||
|
_log.info(ev);
|
||||||
|
emit(state.copyWith(navBarButtons: ev.value));
|
||||||
|
}
|
||||||
|
|
||||||
void _onSetError(_SetError ev, Emitter<_State> emit) {
|
void _onSetError(_SetError ev, Emitter<_State> emit) {
|
||||||
_log.info(ev);
|
_log.info(ev);
|
||||||
emit(state.copyWith(error: ExceptionEvent(ev.error, ev.stackTrace)));
|
emit(state.copyWith(error: ExceptionEvent(ev.error, ev.stackTrace)));
|
||||||
|
|
241
app/lib/widget/home_collections/nav_bar_buttons.dart
Normal file
241
app/lib/widget/home_collections/nav_bar_buttons.dart
Normal file
|
@ -0,0 +1,241 @@
|
||||||
|
part of '../home_collections.dart';
|
||||||
|
|
||||||
|
class HomeCollectionsNavBarButton extends StatelessWidget {
|
||||||
|
const HomeCollectionsNavBarButton({
|
||||||
|
super.key,
|
||||||
|
required this.icon,
|
||||||
|
required this.label,
|
||||||
|
required this.isMinimized,
|
||||||
|
this.isShowIndicator = false,
|
||||||
|
this.isEnabled = true,
|
||||||
|
this.isUseTooltipWhenMinimized = true,
|
||||||
|
this.onPressed,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
if (isMinimized) {
|
||||||
|
return IconButtonTheme(
|
||||||
|
data: const IconButtonThemeData(
|
||||||
|
style: ButtonStyle(
|
||||||
|
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: IconButton.outlined(
|
||||||
|
icon: Stack(
|
||||||
|
children: [
|
||||||
|
IconTheme(
|
||||||
|
data: IconThemeData(
|
||||||
|
size: 18,
|
||||||
|
color: Theme.of(context).colorScheme.secondary,
|
||||||
|
),
|
||||||
|
child: icon,
|
||||||
|
),
|
||||||
|
if (isShowIndicator)
|
||||||
|
const Positioned(
|
||||||
|
right: 2,
|
||||||
|
top: 2,
|
||||||
|
child: _NavBarButtonIndicator(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
tooltip: isUseTooltipWhenMinimized ? label : null,
|
||||||
|
onPressed: isEnabled ? onPressed : null,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return ActionChip(
|
||||||
|
avatar: icon,
|
||||||
|
label: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Text(label),
|
||||||
|
if (isShowIndicator) ...const [
|
||||||
|
SizedBox(width: 4),
|
||||||
|
_NavBarButtonIndicator(),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
onPressed: isEnabled ? onPressed : null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final Widget icon;
|
||||||
|
final String label;
|
||||||
|
final bool isMinimized;
|
||||||
|
final bool isShowIndicator;
|
||||||
|
final bool isEnabled;
|
||||||
|
final bool isUseTooltipWhenMinimized;
|
||||||
|
final VoidCallback? onPressed;
|
||||||
|
}
|
||||||
|
|
||||||
|
class _NavBarButton extends StatelessWidget {
|
||||||
|
const _NavBarButton({
|
||||||
|
required this.icon,
|
||||||
|
required this.label,
|
||||||
|
required this.isMinimized,
|
||||||
|
this.isShowIndicator = false,
|
||||||
|
this.onPressed,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return _BlocSelector(
|
||||||
|
selector: (state) => state.selectedItems.isEmpty,
|
||||||
|
builder: (context, isEnabled) => HomeCollectionsNavBarButton(
|
||||||
|
icon: icon,
|
||||||
|
label: label,
|
||||||
|
isMinimized: isMinimized,
|
||||||
|
isShowIndicator: isShowIndicator,
|
||||||
|
isEnabled: isEnabled,
|
||||||
|
onPressed: onPressed,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final Widget icon;
|
||||||
|
final String label;
|
||||||
|
final bool isMinimized;
|
||||||
|
final bool isShowIndicator;
|
||||||
|
final VoidCallback? onPressed;
|
||||||
|
}
|
||||||
|
|
||||||
|
@npLog
|
||||||
|
class _NavBarNewButton extends StatelessWidget {
|
||||||
|
const _NavBarNewButton();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return _NavBarButton(
|
||||||
|
icon: const Icon(Icons.add_outlined),
|
||||||
|
label: L10n.global().createCollectionTooltip,
|
||||||
|
isMinimized: true,
|
||||||
|
onPressed: () async {
|
||||||
|
try {
|
||||||
|
final collection = await showDialog<Collection>(
|
||||||
|
context: context,
|
||||||
|
builder: (_) => NewCollectionDialog(
|
||||||
|
account: context.bloc.account,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (collection == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Right now we don't have a way to add photos inside the
|
||||||
|
// CollectionBrowser, eventually we should add that and remove this
|
||||||
|
// branching
|
||||||
|
if (collection.isDynamicCollection) {
|
||||||
|
// open the newly created collection
|
||||||
|
unawaited(Navigator.of(context).pushNamed(
|
||||||
|
CollectionBrowser.routeName,
|
||||||
|
arguments: CollectionBrowserArguments(collection),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
} catch (e, stacktrace) {
|
||||||
|
_log.shout("[build] Uncaught exception", e, stacktrace);
|
||||||
|
context.addEvent(_SetError(AppMessageException(
|
||||||
|
L10n.global().createCollectionFailureNotification)));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _NavBarSharingButton extends StatelessWidget {
|
||||||
|
const _NavBarSharingButton({
|
||||||
|
required this.isMinimized,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return ValueStreamBuilderEx(
|
||||||
|
stream: context
|
||||||
|
.read<AccountController>()
|
||||||
|
.accountPrefController
|
||||||
|
.hasNewSharedAlbum,
|
||||||
|
builder: StreamWidgetBuilder.value(
|
||||||
|
(context, hasNewSharedAlbum) => _NavBarButton(
|
||||||
|
icon: const Icon(Icons.share_outlined),
|
||||||
|
label: L10n.global().collectionSharingLabel,
|
||||||
|
isMinimized: isMinimized,
|
||||||
|
isShowIndicator: hasNewSharedAlbum,
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pushNamed(
|
||||||
|
SharingBrowser.routeName,
|
||||||
|
arguments: SharingBrowserArguments(context.bloc.account),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final bool isMinimized;
|
||||||
|
}
|
||||||
|
|
||||||
|
class _NavBarEditedButton extends StatelessWidget {
|
||||||
|
const _NavBarEditedButton({
|
||||||
|
required this.isMinimized,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return _NavBarButton(
|
||||||
|
icon: const Icon(Icons.auto_fix_high_outlined),
|
||||||
|
label: L10n.global().collectionEditedPhotosLabel,
|
||||||
|
isMinimized: isMinimized,
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pushNamed(
|
||||||
|
EnhancedPhotoBrowser.routeName,
|
||||||
|
arguments: const EnhancedPhotoBrowserArguments(null),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final bool isMinimized;
|
||||||
|
}
|
||||||
|
|
||||||
|
class _NavBarArchiveButton extends StatelessWidget {
|
||||||
|
const _NavBarArchiveButton({
|
||||||
|
required this.isMinimized,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return _NavBarButton(
|
||||||
|
icon: const Icon(Icons.archive_outlined),
|
||||||
|
label: L10n.global().albumArchiveLabel,
|
||||||
|
isMinimized: isMinimized,
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pushNamed(ArchiveBrowser.routeName);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final bool isMinimized;
|
||||||
|
}
|
||||||
|
|
||||||
|
class _NavBarTrashButton extends StatelessWidget {
|
||||||
|
const _NavBarTrashButton({
|
||||||
|
required this.isMinimized,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return _NavBarButton(
|
||||||
|
icon: const Icon(Icons.delete_outlined),
|
||||||
|
label: L10n.global().albumTrashLabel,
|
||||||
|
isMinimized: isMinimized,
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pushNamed(
|
||||||
|
TrashbinBrowser.routeName,
|
||||||
|
arguments: TrashbinBrowserArguments(context.bloc.account),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final bool isMinimized;
|
||||||
|
}
|
|
@ -37,8 +37,6 @@ class _NavigationBarState extends State<_NavigationBar> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final buttons =
|
|
||||||
_buttons.map((e) => _buildButton(context, e)).nonNulls.toList();
|
|
||||||
return SliverToBoxAdapter(
|
return SliverToBoxAdapter(
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
height: 48,
|
height: 48,
|
||||||
|
@ -47,13 +45,25 @@ class _NavigationBarState extends State<_NavigationBar> {
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Stack(
|
child: Stack(
|
||||||
children: [
|
children: [
|
||||||
ListView.separated(
|
_BlocSelector(
|
||||||
controller: _scrollController,
|
selector: (state) => state.navBarButtons,
|
||||||
scrollDirection: Axis.horizontal,
|
builder: (context, navBarButtons) {
|
||||||
padding: const EdgeInsets.only(left: 16),
|
final buttons = navBarButtons
|
||||||
itemCount: buttons.length,
|
.map((e) => _buildButton(context, e))
|
||||||
itemBuilder: (context, i) => buttons[i],
|
.nonNulls
|
||||||
separatorBuilder: (context, _) => const SizedBox(width: 16),
|
.toList();
|
||||||
|
return ListView.separated(
|
||||||
|
controller: _scrollController,
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
padding: const EdgeInsets.only(left: 16),
|
||||||
|
itemCount: buttons.length,
|
||||||
|
itemBuilder: (context, i) => Center(
|
||||||
|
child: buttons[i],
|
||||||
|
),
|
||||||
|
separatorBuilder: (context, _) =>
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
if (_hasLeftContent)
|
if (_hasLeftContent)
|
||||||
Positioned(
|
Positioned(
|
||||||
|
@ -113,19 +123,18 @@ class _NavigationBarState extends State<_NavigationBar> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget? _buildButton(
|
Widget? _buildButton(BuildContext context, PrefHomeCollectionsNavButton btn) {
|
||||||
BuildContext context, HomeCollectionsNavBarButtonType type) {
|
switch (btn.type) {
|
||||||
switch (type) {
|
|
||||||
case HomeCollectionsNavBarButtonType.sharing:
|
case HomeCollectionsNavBarButtonType.sharing:
|
||||||
return const _NavBarSharingButton();
|
return _NavBarSharingButton(isMinimized: btn.isMinimized);
|
||||||
case HomeCollectionsNavBarButtonType.edited:
|
case HomeCollectionsNavBarButtonType.edited:
|
||||||
return features.isSupportEnhancement
|
return features.isSupportEnhancement
|
||||||
? const _NavBarEditedButton()
|
? _NavBarEditedButton(isMinimized: btn.isMinimized)
|
||||||
: null;
|
: null;
|
||||||
case HomeCollectionsNavBarButtonType.archive:
|
case HomeCollectionsNavBarButtonType.archive:
|
||||||
return const _NavBarArchiveButton();
|
return _NavBarArchiveButton(isMinimized: btn.isMinimized);
|
||||||
case HomeCollectionsNavBarButtonType.trash:
|
case HomeCollectionsNavBarButtonType.trash:
|
||||||
return const _NavBarTrashButton();
|
return _NavBarTrashButton(isMinimized: btn.isMinimized);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -175,13 +184,6 @@ class _NavigationBarState extends State<_NavigationBar> {
|
||||||
Timer(const Duration(milliseconds: 100), _ensureUpdateButtonScroll);
|
Timer(const Duration(milliseconds: 100), _ensureUpdateButtonScroll);
|
||||||
}
|
}
|
||||||
|
|
||||||
static const _buttons = [
|
|
||||||
HomeCollectionsNavBarButtonType.sharing,
|
|
||||||
HomeCollectionsNavBarButtonType.edited,
|
|
||||||
HomeCollectionsNavBarButtonType.archive,
|
|
||||||
HomeCollectionsNavBarButtonType.trash,
|
|
||||||
];
|
|
||||||
|
|
||||||
late final ScrollController _scrollController;
|
late final ScrollController _scrollController;
|
||||||
var _hasFirstScrollUpdate = false;
|
var _hasFirstScrollUpdate = false;
|
||||||
var _hasLeftContent = false;
|
var _hasLeftContent = false;
|
||||||
|
@ -202,179 +204,3 @@ class _NavBarButtonIndicator extends StatelessWidget {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _NavBarButton extends StatelessWidget {
|
|
||||||
const _NavBarButton({
|
|
||||||
required this.icon,
|
|
||||||
required this.label,
|
|
||||||
required this.isMinimized,
|
|
||||||
this.isShowIndicator = false,
|
|
||||||
required this.onPressed,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return _BlocSelector(
|
|
||||||
selector: (state) => state.selectedItems.isEmpty,
|
|
||||||
builder: (context, isEnabled) => isMinimized
|
|
||||||
? IconButton.outlined(
|
|
||||||
icon: Stack(
|
|
||||||
children: [
|
|
||||||
icon,
|
|
||||||
if (isShowIndicator)
|
|
||||||
const Positioned(
|
|
||||||
right: 2,
|
|
||||||
top: 2,
|
|
||||||
child: _NavBarButtonIndicator(),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
tooltip: label,
|
|
||||||
onPressed: isEnabled ? onPressed : null,
|
|
||||||
)
|
|
||||||
: ActionChip(
|
|
||||||
avatar: icon,
|
|
||||||
label: Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
Text(label),
|
|
||||||
if (isShowIndicator) ...const [
|
|
||||||
SizedBox(width: 4),
|
|
||||||
_NavBarButtonIndicator(),
|
|
||||||
],
|
|
||||||
],
|
|
||||||
),
|
|
||||||
onPressed: isEnabled ? onPressed : null,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
final Widget icon;
|
|
||||||
final String label;
|
|
||||||
final bool isMinimized;
|
|
||||||
final bool isShowIndicator;
|
|
||||||
final VoidCallback onPressed;
|
|
||||||
}
|
|
||||||
|
|
||||||
@npLog
|
|
||||||
class _NavBarNewButton extends StatelessWidget {
|
|
||||||
const _NavBarNewButton();
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return _NavBarButton(
|
|
||||||
icon: const Icon(Icons.add_outlined),
|
|
||||||
label: L10n.global().createCollectionTooltip,
|
|
||||||
isMinimized: true,
|
|
||||||
onPressed: () async {
|
|
||||||
try {
|
|
||||||
final collection = await showDialog<Collection>(
|
|
||||||
context: context,
|
|
||||||
builder: (_) => NewCollectionDialog(
|
|
||||||
account: context.bloc.account,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
if (collection == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Right now we don't have a way to add photos inside the
|
|
||||||
// CollectionBrowser, eventually we should add that and remove this
|
|
||||||
// branching
|
|
||||||
if (collection.isDynamicCollection) {
|
|
||||||
// open the newly created collection
|
|
||||||
unawaited(Navigator.of(context).pushNamed(
|
|
||||||
CollectionBrowser.routeName,
|
|
||||||
arguments: CollectionBrowserArguments(collection),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
} catch (e, stacktrace) {
|
|
||||||
_log.shout("[build] Uncaught exception", e, stacktrace);
|
|
||||||
context.addEvent(_SetError(AppMessageException(
|
|
||||||
L10n.global().createCollectionFailureNotification)));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _NavBarSharingButton extends StatelessWidget {
|
|
||||||
const _NavBarSharingButton();
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return ValueStreamBuilderEx(
|
|
||||||
stream: context
|
|
||||||
.read<AccountController>()
|
|
||||||
.accountPrefController
|
|
||||||
.hasNewSharedAlbum,
|
|
||||||
builder: StreamWidgetBuilder.value(
|
|
||||||
(context, hasNewSharedAlbum) => _NavBarButton(
|
|
||||||
icon: const Icon(Icons.share_outlined),
|
|
||||||
label: L10n.global().collectionSharingLabel,
|
|
||||||
isMinimized: false,
|
|
||||||
isShowIndicator: hasNewSharedAlbum,
|
|
||||||
onPressed: () {
|
|
||||||
Navigator.of(context).pushNamed(
|
|
||||||
SharingBrowser.routeName,
|
|
||||||
arguments: SharingBrowserArguments(context.bloc.account),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _NavBarEditedButton extends StatelessWidget {
|
|
||||||
const _NavBarEditedButton();
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return _NavBarButton(
|
|
||||||
icon: const Icon(Icons.auto_fix_high_outlined),
|
|
||||||
label: L10n.global().collectionEditedPhotosLabel,
|
|
||||||
isMinimized: false,
|
|
||||||
onPressed: () {
|
|
||||||
Navigator.of(context).pushNamed(
|
|
||||||
EnhancedPhotoBrowser.routeName,
|
|
||||||
arguments: const EnhancedPhotoBrowserArguments(null),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _NavBarArchiveButton extends StatelessWidget {
|
|
||||||
const _NavBarArchiveButton();
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return _NavBarButton(
|
|
||||||
icon: const Icon(Icons.archive_outlined),
|
|
||||||
label: L10n.global().albumArchiveLabel,
|
|
||||||
isMinimized: false,
|
|
||||||
onPressed: () {
|
|
||||||
Navigator.of(context).pushNamed(ArchiveBrowser.routeName);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _NavBarTrashButton extends StatelessWidget {
|
|
||||||
const _NavBarTrashButton();
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return _NavBarButton(
|
|
||||||
icon: const Icon(Icons.delete_outlined),
|
|
||||||
label: L10n.global().albumTrashLabel,
|
|
||||||
isMinimized: false,
|
|
||||||
onPressed: () {
|
|
||||||
Navigator.of(context).pushNamed(
|
|
||||||
TrashbinBrowser.routeName,
|
|
||||||
arguments: TrashbinBrowserArguments(context.bloc.account),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -10,12 +10,14 @@ class _State {
|
||||||
required this.transformedItems,
|
required this.transformedItems,
|
||||||
required this.selectedItems,
|
required this.selectedItems,
|
||||||
required this.itemCounts,
|
required this.itemCounts,
|
||||||
|
required this.navBarButtons,
|
||||||
this.error,
|
this.error,
|
||||||
required this.removeError,
|
required this.removeError,
|
||||||
});
|
});
|
||||||
|
|
||||||
factory _State.init({
|
factory _State.init({
|
||||||
required collection_util.CollectionSort sort,
|
required collection_util.CollectionSort sort,
|
||||||
|
required List<PrefHomeCollectionsNavButton> navBarButtons,
|
||||||
}) {
|
}) {
|
||||||
return _State(
|
return _State(
|
||||||
collections: [],
|
collections: [],
|
||||||
|
@ -24,6 +26,7 @@ class _State {
|
||||||
transformedItems: [],
|
transformedItems: [],
|
||||||
selectedItems: {},
|
selectedItems: {},
|
||||||
itemCounts: {},
|
itemCounts: {},
|
||||||
|
navBarButtons: navBarButtons,
|
||||||
removeError: null,
|
removeError: null,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -38,6 +41,8 @@ class _State {
|
||||||
final Set<_Item> selectedItems;
|
final Set<_Item> selectedItems;
|
||||||
final Map<String, int> itemCounts;
|
final Map<String, int> itemCounts;
|
||||||
|
|
||||||
|
final List<PrefHomeCollectionsNavButton> navBarButtons;
|
||||||
|
|
||||||
final ExceptionEvent? error;
|
final ExceptionEvent? error;
|
||||||
final ExceptionEvent? removeError;
|
final ExceptionEvent? removeError;
|
||||||
}
|
}
|
||||||
|
@ -128,6 +133,16 @@ class _SetItemCount implements _Event {
|
||||||
final int value;
|
final int value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@toString
|
||||||
|
class _SetNavBarButtons implements _Event {
|
||||||
|
const _SetNavBarButtons(this.value);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => _$toString();
|
||||||
|
|
||||||
|
final List<PrefHomeCollectionsNavButton> value;
|
||||||
|
}
|
||||||
|
|
||||||
@toString
|
@toString
|
||||||
class _SetError implements _Event {
|
class _SetError implements _Event {
|
||||||
const _SetError(this.error, [this.stackTrace]);
|
const _SetError(this.error, [this.stackTrace]);
|
||||||
|
|
|
@ -8,6 +8,7 @@ import 'package:nc_photos/controller/pref_controller.dart';
|
||||||
import 'package:nc_photos/exception_event.dart';
|
import 'package:nc_photos/exception_event.dart';
|
||||||
import 'package:nc_photos/snack_bar_manager.dart';
|
import 'package:nc_photos/snack_bar_manager.dart';
|
||||||
import 'package:nc_photos/widget/page_visibility_mixin.dart';
|
import 'package:nc_photos/widget/page_visibility_mixin.dart';
|
||||||
|
import 'package:nc_photos/widget/settings/collections_nav_bar_settings.dart';
|
||||||
import 'package:np_codegen/np_codegen.dart';
|
import 'package:np_codegen/np_codegen.dart';
|
||||||
import 'package:to_string/to_string.dart';
|
import 'package:to_string/to_string.dart';
|
||||||
|
|
||||||
|
@ -85,6 +86,17 @@ class _WrappedAlbumSettingsState extends State<_WrappedAlbumSettings>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
ListTile(
|
||||||
|
title: Text(L10n.global()
|
||||||
|
.settingsCollectionsCustomizeNavigationBarTitle),
|
||||||
|
onTap: () {
|
||||||
|
Navigator.of(context).push(
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (_) => const CollectionsNavBarSettings(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
122
app/lib/widget/settings/collections_nav_bar/bloc.dart
Normal file
122
app/lib/widget/settings/collections_nav_bar/bloc.dart
Normal file
|
@ -0,0 +1,122 @@
|
||||||
|
part of '../collections_nav_bar_settings.dart';
|
||||||
|
|
||||||
|
@npLog
|
||||||
|
class _Bloc extends Bloc<_Event, _State>
|
||||||
|
with BlocLogger, BlocForEachMixin<_Event, _State> {
|
||||||
|
_Bloc({
|
||||||
|
required this.prefController,
|
||||||
|
required this.isBottom,
|
||||||
|
}) : super(_State.init(
|
||||||
|
buttons: prefController.homeCollectionsNavBarButtonsValue,
|
||||||
|
)) {
|
||||||
|
on<_MoveButton>(_onMoveButton);
|
||||||
|
on<_RemoveButton>(_onRemoveButton);
|
||||||
|
on<_ToggleMinimized>(_onToggleMinimized);
|
||||||
|
on<_RevertDefault>(_onRevertDefault);
|
||||||
|
|
||||||
|
on<_SetError>(_onSetError);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> close() {
|
||||||
|
for (final s in _subscriptions) {
|
||||||
|
s.cancel();
|
||||||
|
}
|
||||||
|
return super.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get tag => _log.fullName;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onError(Object error, StackTrace stackTrace) {
|
||||||
|
// we need this to prevent onError being triggered recursively
|
||||||
|
if (!isClosed && !_isHandlingError) {
|
||||||
|
_isHandlingError = true;
|
||||||
|
try {
|
||||||
|
add(_SetError(error, stackTrace));
|
||||||
|
} catch (_) {}
|
||||||
|
_isHandlingError = false;
|
||||||
|
}
|
||||||
|
super.onError(error, stackTrace);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onMoveButton(_MoveButton ev, _Emitter emit) {
|
||||||
|
_log.info(ev);
|
||||||
|
final pos = state.buttons.indexWhere((e) => e.type == ev.which);
|
||||||
|
final found = pos >= 0 ? state.buttons[pos] : null;
|
||||||
|
final insert = found ??
|
||||||
|
PrefHomeCollectionsNavButton(type: ev.which, isMinimized: false);
|
||||||
|
var result =
|
||||||
|
pos >= 0 ? state.buttons.removedAt(pos) : List.of(state.buttons);
|
||||||
|
if (ev.before == null && ev.after == null) {
|
||||||
|
// add at the beginning
|
||||||
|
emit(state.copyWith(buttons: result..insert(0, insert)));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final target = (ev.before ?? ev.after)!;
|
||||||
|
if (ev.which == target) {
|
||||||
|
// dropping on itself, do nothing
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final targetPos = result.indexWhere((e) => e.type == target);
|
||||||
|
if (targetPos == -1) {
|
||||||
|
_log.severe("[_onMoveButton] Target not found: $target");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (ev.before != null) {
|
||||||
|
// insert before
|
||||||
|
result.insert(targetPos, insert);
|
||||||
|
} else {
|
||||||
|
// insert after
|
||||||
|
result.insert(targetPos + 1, insert);
|
||||||
|
}
|
||||||
|
_log.fine(
|
||||||
|
"[_onMoveButton] From ${state.buttons.toReadableString()} -> ${result.toReadableString()}");
|
||||||
|
emit(state.copyWith(buttons: result));
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onRemoveButton(_RemoveButton ev, _Emitter emit) {
|
||||||
|
_log.info(ev);
|
||||||
|
emit(state.copyWith(
|
||||||
|
buttons: state.buttons.removedWhere((e) => e.type == ev.value),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onToggleMinimized(_ToggleMinimized ev, _Emitter emit) {
|
||||||
|
_log.info(ev);
|
||||||
|
final result = List.of(state.buttons);
|
||||||
|
final pos = result.indexWhere((e) => e.type == ev.value);
|
||||||
|
if (pos == -1) {
|
||||||
|
// button not enabled
|
||||||
|
_log.severe(
|
||||||
|
"[_onToggleMinimized] Type not found in buttons: ${ev.value}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
result[pos] = PrefHomeCollectionsNavButton(
|
||||||
|
type: ev.value,
|
||||||
|
isMinimized: !result[pos].isMinimized,
|
||||||
|
);
|
||||||
|
emit(state.copyWith(buttons: result));
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _onRevertDefault(_RevertDefault ev, _Emitter emit) async {
|
||||||
|
_log.info(ev);
|
||||||
|
await prefController.setHomeCollectionsNavBarButtons(null);
|
||||||
|
emit(state.copyWith(
|
||||||
|
buttons: prefController.homeCollectionsNavBarButtonsValue,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onSetError(_SetError ev, _Emitter emit) {
|
||||||
|
_log.info(ev);
|
||||||
|
emit(state.copyWith(error: ExceptionEvent(ev.error, ev.stackTrace)));
|
||||||
|
}
|
||||||
|
|
||||||
|
final PrefController prefController;
|
||||||
|
final bool isBottom;
|
||||||
|
|
||||||
|
final _subscriptions = <StreamSubscription>[];
|
||||||
|
var _isHandlingError = false;
|
||||||
|
}
|
108
app/lib/widget/settings/collections_nav_bar/buttons.dart
Normal file
108
app/lib/widget/settings/collections_nav_bar/buttons.dart
Normal file
|
@ -0,0 +1,108 @@
|
||||||
|
part of '../collections_nav_bar_settings.dart';
|
||||||
|
|
||||||
|
class _NewButton extends StatelessWidget {
|
||||||
|
const _NewButton();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return HomeCollectionsNavBarButton(
|
||||||
|
icon: const Icon(Icons.add_outlined),
|
||||||
|
label: L10n.global().createCollectionTooltip,
|
||||||
|
isMinimized: true,
|
||||||
|
isUseTooltipWhenMinimized: false,
|
||||||
|
onPressed: () {},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SharingButton extends StatelessWidget {
|
||||||
|
const _SharingButton({
|
||||||
|
required this.isMinimized,
|
||||||
|
this.onPressed,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return HomeCollectionsNavBarButton(
|
||||||
|
icon: const Icon(Icons.share_outlined),
|
||||||
|
label: L10n.global().collectionSharingLabel,
|
||||||
|
isMinimized: isMinimized,
|
||||||
|
isUseTooltipWhenMinimized: false,
|
||||||
|
onPressed: () {
|
||||||
|
onPressed?.call();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final bool isMinimized;
|
||||||
|
final VoidCallback? onPressed;
|
||||||
|
}
|
||||||
|
|
||||||
|
class _EditedButton extends StatelessWidget {
|
||||||
|
const _EditedButton({
|
||||||
|
required this.isMinimized,
|
||||||
|
this.onPressed,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return HomeCollectionsNavBarButton(
|
||||||
|
icon: const Icon(Icons.auto_fix_high_outlined),
|
||||||
|
label: L10n.global().collectionEditedPhotosLabel,
|
||||||
|
isMinimized: isMinimized,
|
||||||
|
isUseTooltipWhenMinimized: false,
|
||||||
|
onPressed: () {
|
||||||
|
onPressed?.call();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final bool isMinimized;
|
||||||
|
final VoidCallback? onPressed;
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ArchiveButton extends StatelessWidget {
|
||||||
|
const _ArchiveButton({
|
||||||
|
required this.isMinimized,
|
||||||
|
this.onPressed,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return HomeCollectionsNavBarButton(
|
||||||
|
icon: const Icon(Icons.archive_outlined),
|
||||||
|
label: L10n.global().albumArchiveLabel,
|
||||||
|
isMinimized: isMinimized,
|
||||||
|
isUseTooltipWhenMinimized: false,
|
||||||
|
onPressed: () {
|
||||||
|
onPressed?.call();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final bool isMinimized;
|
||||||
|
final VoidCallback? onPressed;
|
||||||
|
}
|
||||||
|
|
||||||
|
class _TrashButton extends StatelessWidget {
|
||||||
|
const _TrashButton({
|
||||||
|
required this.isMinimized,
|
||||||
|
this.onPressed,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return HomeCollectionsNavBarButton(
|
||||||
|
icon: const Icon(Icons.delete_outlined),
|
||||||
|
label: L10n.global().albumTrashLabel,
|
||||||
|
isMinimized: isMinimized,
|
||||||
|
isUseTooltipWhenMinimized: false,
|
||||||
|
onPressed: () {
|
||||||
|
onPressed?.call();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final bool isMinimized;
|
||||||
|
final VoidCallback? onPressed;
|
||||||
|
}
|
106
app/lib/widget/settings/collections_nav_bar/state_event.dart
Normal file
106
app/lib/widget/settings/collections_nav_bar/state_event.dart
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
part of '../collections_nav_bar_settings.dart';
|
||||||
|
|
||||||
|
@genCopyWith
|
||||||
|
@toString
|
||||||
|
class _State {
|
||||||
|
const _State({
|
||||||
|
required this.buttons,
|
||||||
|
this.error,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory _State.init({
|
||||||
|
required List<PrefHomeCollectionsNavButton> buttons,
|
||||||
|
}) {
|
||||||
|
return _State(
|
||||||
|
buttons: buttons,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => _$toString();
|
||||||
|
|
||||||
|
final List<PrefHomeCollectionsNavButton> buttons;
|
||||||
|
|
||||||
|
final ExceptionEvent? error;
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class _Event {
|
||||||
|
const _Event();
|
||||||
|
}
|
||||||
|
|
||||||
|
@toString
|
||||||
|
class _Init implements _Event {
|
||||||
|
const _Init();
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => _$toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@toString
|
||||||
|
class _MoveButton implements _Event {
|
||||||
|
const _MoveButton._({
|
||||||
|
required this.which,
|
||||||
|
this.before,
|
||||||
|
this.after,
|
||||||
|
});
|
||||||
|
|
||||||
|
const _MoveButton.first({
|
||||||
|
required HomeCollectionsNavBarButtonType which,
|
||||||
|
}) : this._(which: which);
|
||||||
|
|
||||||
|
const _MoveButton.before({
|
||||||
|
required HomeCollectionsNavBarButtonType which,
|
||||||
|
required HomeCollectionsNavBarButtonType target,
|
||||||
|
}) : this._(which: which, before: target);
|
||||||
|
|
||||||
|
const _MoveButton.after({
|
||||||
|
required HomeCollectionsNavBarButtonType which,
|
||||||
|
required HomeCollectionsNavBarButtonType target,
|
||||||
|
}) : this._(which: which, after: target);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => _$toString();
|
||||||
|
|
||||||
|
final HomeCollectionsNavBarButtonType which;
|
||||||
|
final HomeCollectionsNavBarButtonType? before;
|
||||||
|
final HomeCollectionsNavBarButtonType? after;
|
||||||
|
}
|
||||||
|
|
||||||
|
@toString
|
||||||
|
class _RemoveButton implements _Event {
|
||||||
|
const _RemoveButton(this.value);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => _$toString();
|
||||||
|
|
||||||
|
final HomeCollectionsNavBarButtonType value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@toString
|
||||||
|
class _ToggleMinimized implements _Event {
|
||||||
|
const _ToggleMinimized(this.value);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => _$toString();
|
||||||
|
|
||||||
|
final HomeCollectionsNavBarButtonType value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@toString
|
||||||
|
class _RevertDefault implements _Event {
|
||||||
|
const _RevertDefault();
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => _$toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@toString
|
||||||
|
class _SetError implements _Event {
|
||||||
|
const _SetError(this.error, [this.stackTrace]);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => _$toString();
|
||||||
|
|
||||||
|
final Object error;
|
||||||
|
final StackTrace? stackTrace;
|
||||||
|
}
|
199
app/lib/widget/settings/collections_nav_bar/view.dart
Normal file
199
app/lib/widget/settings/collections_nav_bar/view.dart
Normal file
|
@ -0,0 +1,199 @@
|
||||||
|
part of '../collections_nav_bar_settings.dart';
|
||||||
|
|
||||||
|
class _DemoView extends StatelessWidget {
|
||||||
|
const _DemoView();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return _BlocSelector(
|
||||||
|
selector: (state) => state.buttons,
|
||||||
|
builder: (context, buttons) {
|
||||||
|
final navBar = SizedBox(
|
||||||
|
height: 48,
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: ListView.builder(
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
padding: const EdgeInsets.only(left: 16 - 6),
|
||||||
|
itemCount: buttons.length,
|
||||||
|
itemBuilder: (context, i) {
|
||||||
|
final btn = buttons[i];
|
||||||
|
return my.Draggable<HomeCollectionsNavBarButtonType>(
|
||||||
|
data: btn.type,
|
||||||
|
feedback: _CandidateButtonDelegate(btn.type),
|
||||||
|
onDropBefore: (data) {
|
||||||
|
context.addEvent(_MoveButton.before(
|
||||||
|
which: data,
|
||||||
|
target: btn.type,
|
||||||
|
));
|
||||||
|
},
|
||||||
|
onDropAfter: (data) {
|
||||||
|
context.addEvent(_MoveButton.after(
|
||||||
|
which: data,
|
||||||
|
target: btn.type,
|
||||||
|
));
|
||||||
|
},
|
||||||
|
child: Center(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 6),
|
||||||
|
child: _DemoButtonDelegate(
|
||||||
|
btn.type,
|
||||||
|
isMinimized: btn.isMinimized,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
const _NewButton(),
|
||||||
|
const SizedBox(width: 16),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (buttons.isEmpty) {
|
||||||
|
return DragTarget<HomeCollectionsNavBarButtonType>(
|
||||||
|
builder: (context, candidateData, rejectedData) => SizedBox(
|
||||||
|
height: 48,
|
||||||
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
navBar,
|
||||||
|
IgnorePointer(
|
||||||
|
child: Opacity(
|
||||||
|
opacity: candidateData.isNotEmpty ? .35 : 0,
|
||||||
|
child: Container(
|
||||||
|
color: Theme.of(context).colorScheme.onSurface,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onAcceptWithDetails: (details) {
|
||||||
|
context.addEvent(_MoveButton.first(which: details.data));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return navBar;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DemoButtonDelegate extends StatelessWidget {
|
||||||
|
const _DemoButtonDelegate(
|
||||||
|
this.type, {
|
||||||
|
required this.isMinimized,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
switch (type) {
|
||||||
|
case HomeCollectionsNavBarButtonType.sharing:
|
||||||
|
return _SharingButton(
|
||||||
|
isMinimized: isMinimized,
|
||||||
|
onPressed: () {
|
||||||
|
context.addEvent(_ToggleMinimized(type));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
case HomeCollectionsNavBarButtonType.edited:
|
||||||
|
return _EditedButton(
|
||||||
|
isMinimized: isMinimized,
|
||||||
|
onPressed: () {
|
||||||
|
context.addEvent(_ToggleMinimized(type));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
case HomeCollectionsNavBarButtonType.archive:
|
||||||
|
return _ArchiveButton(
|
||||||
|
isMinimized: isMinimized,
|
||||||
|
onPressed: () {
|
||||||
|
context.addEvent(_ToggleMinimized(type));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
case HomeCollectionsNavBarButtonType.trash:
|
||||||
|
return _TrashButton(
|
||||||
|
isMinimized: isMinimized,
|
||||||
|
onPressed: () {
|
||||||
|
context.addEvent(_ToggleMinimized(type));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final HomeCollectionsNavBarButtonType type;
|
||||||
|
final bool isMinimized;
|
||||||
|
}
|
||||||
|
|
||||||
|
class _CandidateGrid extends StatelessWidget {
|
||||||
|
const _CandidateGrid();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return DragTarget<HomeCollectionsNavBarButtonType>(
|
||||||
|
builder: (context, candidateData, rejectedData) => Stack(
|
||||||
|
children: [
|
||||||
|
_BlocSelector(
|
||||||
|
selector: (state) => state.buttons,
|
||||||
|
builder: (context, buttons) => Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 16,
|
||||||
|
vertical: 8,
|
||||||
|
),
|
||||||
|
child: Wrap(
|
||||||
|
direction: Axis.horizontal,
|
||||||
|
spacing: 16,
|
||||||
|
runSpacing: 8,
|
||||||
|
children: HomeCollectionsNavBarButtonType.values
|
||||||
|
.where((e) => !buttons.any((b) => b.type == e))
|
||||||
|
.map((e) => my.Draggable<HomeCollectionsNavBarButtonType>(
|
||||||
|
data: e,
|
||||||
|
feedback: _CandidateButtonDelegate(e),
|
||||||
|
child: _CandidateButtonDelegate(e),
|
||||||
|
))
|
||||||
|
.toList(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
IgnorePointer(
|
||||||
|
child: Opacity(
|
||||||
|
opacity: candidateData.isNotEmpty ? .1 : 0,
|
||||||
|
child: Container(
|
||||||
|
color: Theme.of(context).colorScheme.onSurface,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
onAcceptWithDetails: (details) {
|
||||||
|
context.addEvent(_RemoveButton(details.data));
|
||||||
|
},
|
||||||
|
onWillAcceptWithDetails: (details) {
|
||||||
|
// moving down
|
||||||
|
return context.state.buttons.any((e) => e.type == details.data);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _CandidateButtonDelegate extends StatelessWidget {
|
||||||
|
const _CandidateButtonDelegate(this.type);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
switch (type) {
|
||||||
|
case HomeCollectionsNavBarButtonType.sharing:
|
||||||
|
return const _SharingButton(isMinimized: false);
|
||||||
|
case HomeCollectionsNavBarButtonType.edited:
|
||||||
|
return const _EditedButton(isMinimized: false);
|
||||||
|
case HomeCollectionsNavBarButtonType.archive:
|
||||||
|
return const _ArchiveButton(isMinimized: false);
|
||||||
|
case HomeCollectionsNavBarButtonType.trash:
|
||||||
|
return const _TrashButton(isMinimized: false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final HomeCollectionsNavBarButtonType type;
|
||||||
|
}
|
120
app/lib/widget/settings/collections_nav_bar_settings.dart
Normal file
120
app/lib/widget/settings/collections_nav_bar_settings.dart
Normal file
|
@ -0,0 +1,120 @@
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:copy_with/copy_with.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:logging/logging.dart';
|
||||||
|
import 'package:nc_photos/app_localizations.dart';
|
||||||
|
import 'package:nc_photos/bloc_util.dart';
|
||||||
|
import 'package:nc_photos/controller/pref_controller.dart';
|
||||||
|
import 'package:nc_photos/exception_event.dart';
|
||||||
|
import 'package:nc_photos/snack_bar_manager.dart';
|
||||||
|
import 'package:nc_photos/widget/draggable.dart' as my;
|
||||||
|
import 'package:nc_photos/widget/home_collections.dart';
|
||||||
|
import 'package:nc_photos/widget/page_visibility_mixin.dart';
|
||||||
|
import 'package:np_codegen/np_codegen.dart';
|
||||||
|
import 'package:np_collection/np_collection.dart';
|
||||||
|
import 'package:to_string/to_string.dart';
|
||||||
|
|
||||||
|
part 'collections_nav_bar/bloc.dart';
|
||||||
|
part 'collections_nav_bar/buttons.dart';
|
||||||
|
part 'collections_nav_bar/state_event.dart';
|
||||||
|
part 'collections_nav_bar/view.dart';
|
||||||
|
part 'collections_nav_bar_settings.g.dart';
|
||||||
|
|
||||||
|
class CollectionsNavBarSettings extends StatelessWidget {
|
||||||
|
const CollectionsNavBarSettings({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return BlocProvider(
|
||||||
|
create: (_) => _Bloc(
|
||||||
|
isBottom: true,
|
||||||
|
prefController: context.read(),
|
||||||
|
),
|
||||||
|
child: const _WrappedCollectionsNavBarSettings(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _WrappedCollectionsNavBarSettings extends StatefulWidget {
|
||||||
|
const _WrappedCollectionsNavBarSettings();
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<StatefulWidget> createState() =>
|
||||||
|
_WrappedCollectionsNavBarSettingsState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@npLog
|
||||||
|
class _WrappedCollectionsNavBarSettingsState
|
||||||
|
extends State<_WrappedCollectionsNavBarSettings>
|
||||||
|
with RouteAware, PageVisibilityMixin {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return PopScope(
|
||||||
|
canPop: true,
|
||||||
|
onPopInvoked: (_) {
|
||||||
|
final prefController = context.bloc.prefController;
|
||||||
|
final from = prefController.homeCollectionsNavBarButtonsValue;
|
||||||
|
final to = context.state.buttons;
|
||||||
|
if (!listEquals(from, to)) {
|
||||||
|
_log.info("[build] Updated: ${to.toReadableString()}");
|
||||||
|
prefController.setHomeCollectionsNavBarButtons(to);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: Text(
|
||||||
|
L10n.global().settingsCollectionsCustomizeNavigationBarTitle),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
context.addEvent(const _RevertDefault());
|
||||||
|
},
|
||||||
|
child: Text(L10n.global().defaultButtonLabel),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
body: MultiBlocListener(
|
||||||
|
listeners: [
|
||||||
|
_BlocListener(
|
||||||
|
listenWhen: (previous, current) =>
|
||||||
|
previous.error != current.error,
|
||||||
|
listener: (context, state) {
|
||||||
|
if (state.error != null && isPageVisible()) {
|
||||||
|
SnackBarManager()
|
||||||
|
.showSnackBarForException(state.error!.error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
const _DemoView(),
|
||||||
|
Padding(
|
||||||
|
padding:
|
||||||
|
const EdgeInsets.symmetric(horizontal: 16, vertical: 16),
|
||||||
|
child:
|
||||||
|
Text(L10n.global().customizeCollectionsNavBarDescription),
|
||||||
|
),
|
||||||
|
const Expanded(child: _CandidateGrid()),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// typedef _BlocBuilder = BlocBuilder<_Bloc, _State>;
|
||||||
|
typedef _BlocListener = BlocListener<_Bloc, _State>;
|
||||||
|
// typedef _BlocListenerT<T> = BlocListenerT<_Bloc, _State, T>;
|
||||||
|
typedef _BlocSelector<T> = BlocSelector<_Bloc, _State, T>;
|
||||||
|
typedef _Emitter = Emitter<_State>;
|
||||||
|
|
||||||
|
extension on BuildContext {
|
||||||
|
_Bloc get bloc => read<_Bloc>();
|
||||||
|
_State get state => bloc.state;
|
||||||
|
void addEvent(_Event event) => bloc.add(event);
|
||||||
|
}
|
110
app/lib/widget/settings/collections_nav_bar_settings.g.dart
Normal file
110
app/lib/widget/settings/collections_nav_bar_settings.g.dart
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'collections_nav_bar_settings.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// CopyWithLintRuleGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
// ignore_for_file: library_private_types_in_public_api, duplicate_ignore
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// CopyWithGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
abstract class $_StateCopyWithWorker {
|
||||||
|
_State call(
|
||||||
|
{List<PrefHomeCollectionsNavButton>? buttons, ExceptionEvent? error});
|
||||||
|
}
|
||||||
|
|
||||||
|
class _$_StateCopyWithWorkerImpl implements $_StateCopyWithWorker {
|
||||||
|
_$_StateCopyWithWorkerImpl(this.that);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_State call({dynamic buttons, dynamic error = copyWithNull}) {
|
||||||
|
return _State(
|
||||||
|
buttons: buttons as List<PrefHomeCollectionsNavButton>? ?? that.buttons,
|
||||||
|
error: error == copyWithNull ? that.error : error as ExceptionEvent?);
|
||||||
|
}
|
||||||
|
|
||||||
|
final _State that;
|
||||||
|
}
|
||||||
|
|
||||||
|
extension $_StateCopyWith on _State {
|
||||||
|
$_StateCopyWithWorker get copyWith => _$copyWith;
|
||||||
|
$_StateCopyWithWorker get _$copyWith => _$_StateCopyWithWorkerImpl(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// NpLogGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
extension _$_WrappedCollectionsNavBarSettingsStateNpLog
|
||||||
|
on _WrappedCollectionsNavBarSettingsState {
|
||||||
|
// ignore: unused_element
|
||||||
|
Logger get _log => log;
|
||||||
|
|
||||||
|
static final log = Logger(
|
||||||
|
"widget.settings.collections_nav_bar_settings._WrappedCollectionsNavBarSettingsState");
|
||||||
|
}
|
||||||
|
|
||||||
|
extension _$_BlocNpLog on _Bloc {
|
||||||
|
// ignore: unused_element
|
||||||
|
Logger get _log => log;
|
||||||
|
|
||||||
|
static final log =
|
||||||
|
Logger("widget.settings.collections_nav_bar_settings._Bloc");
|
||||||
|
}
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// ToStringGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
extension _$_StateToString on _State {
|
||||||
|
String _$toString() {
|
||||||
|
// ignore: unnecessary_string_interpolations
|
||||||
|
return "_State {buttons: [length: ${buttons.length}], error: $error}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension _$_InitToString on _Init {
|
||||||
|
String _$toString() {
|
||||||
|
// ignore: unnecessary_string_interpolations
|
||||||
|
return "_Init {}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension _$_MoveButtonToString on _MoveButton {
|
||||||
|
String _$toString() {
|
||||||
|
// ignore: unnecessary_string_interpolations
|
||||||
|
return "_MoveButton {which: ${which.name}, before: ${before == null ? null : "${before!.name}"}, after: ${after == null ? null : "${after!.name}"}}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension _$_RemoveButtonToString on _RemoveButton {
|
||||||
|
String _$toString() {
|
||||||
|
// ignore: unnecessary_string_interpolations
|
||||||
|
return "_RemoveButton {value: ${value.name}}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension _$_ToggleMinimizedToString on _ToggleMinimized {
|
||||||
|
String _$toString() {
|
||||||
|
// ignore: unnecessary_string_interpolations
|
||||||
|
return "_ToggleMinimized {value: ${value.name}}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension _$_RevertDefaultToString on _RevertDefault {
|
||||||
|
String _$toString() {
|
||||||
|
// ignore: unnecessary_string_interpolations
|
||||||
|
return "_RevertDefault {}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension _$_SetErrorToString on _SetError {
|
||||||
|
String _$toString() {
|
||||||
|
// ignore: unnecessary_string_interpolations
|
||||||
|
return "_SetError {error: $error, stackTrace: $stackTrace}";
|
||||||
|
}
|
||||||
|
}
|
|
@ -88,4 +88,7 @@ extension ListExtension<T> on List<T> {
|
||||||
List<T> removed(T value) => toList()..remove(value);
|
List<T> removed(T value) => toList()..remove(value);
|
||||||
|
|
||||||
List<T> removedAt(int index) => toList()..removeAt(index);
|
List<T> removedAt(int index) => toList()..removeAt(index);
|
||||||
|
|
||||||
|
List<T> removedWhere(bool Function(T element) test) =>
|
||||||
|
toList()..removeWhere(test);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue