New settings page to configure nav bar in collections tab

This commit is contained in:
Ming Ming 2024-10-26 21:35:48 +08:00
parent 1adbb453fd
commit 121f611c7f
21 changed files with 1220 additions and 213 deletions

View file

@ -12,9 +12,11 @@ import 'package:nc_photos/k.dart' as k;
import 'package:nc_photos/language_util.dart';
import 'package:nc_photos/protected_page_handler.dart';
import 'package:nc_photos/size.dart';
import 'package:nc_photos/widget/home_collections.dart';
import 'package:nc_photos/widget/viewer.dart';
import 'package:np_codegen/np_codegen.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_string/np_string.dart';
import 'package:rxdart/rxdart.dart';
@ -261,6 +263,17 @@ class PrefController {
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>({
required BehaviorSubject<T> controller,
required Future<bool> Function(Pref pref, T value) setter,
@ -406,6 +419,14 @@ class PrefController {
@npSubjectAccessor
late final _viewerBottomAppBarButtonsController = BehaviorSubject.seeded(
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 {
@ -561,6 +582,24 @@ const _viewerBottomAppBarButtonsDefault = [
ViewerAppBarButtonType.download,
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
// ignore: camel_case_types

View file

@ -252,6 +252,18 @@ extension $PrefControllerNpSubjectAccessor on PrefController {
viewerBottomAppBarButtons.distinct().skip(1);
List<ViewerAppBarButtonType> get viewerBottomAppBarButtonsValue =>
_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 {

View file

@ -14,3 +14,24 @@ enum PrefMapDefaultRangeType {
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;
}

View file

@ -185,6 +185,16 @@ extension on Pref {
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) {

View file

@ -117,6 +117,7 @@ enum PrefKey implements PrefKeyInterface {
mapDefaultCustomRange,
viewerAppBarButtons,
viewerBottomAppBarButtons,
homeCollectionsNavBarButtons,
;
@override
@ -215,6 +216,8 @@ enum PrefKey implements PrefKeyInterface {
return "viewerAppBarButtons";
case PrefKey.viewerBottomAppBarButtons:
return "viewerBottomAppBarButtons";
case PrefKey.homeCollectionsNavBarButtons:
return "homeCollectionsNavBarButtons";
}
}
}

View file

@ -360,6 +360,7 @@
"settingsViewerCustomizeBottomAppBarTitle": "Customize bottom app bar",
"settingsShowDateInAlbumTitle": "Group photos by date",
"settingsShowDateInAlbumDescription": "Apply only when the album is sorted by time",
"settingsCollectionsCustomizeNavigationBarTitle": "Customize navigation bar",
"settingsImageEditTitle": "Editor",
"@settingsImageEditTitle": {
"description": "Include settings for image enhancements and the image editor"
@ -1512,6 +1513,10 @@
"@dragAndDropRearrangeButtons": {
"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": {

View file

@ -18,6 +18,7 @@
"settingsViewerCustomizeBottomAppBarTitle",
"settingsShowDateInAlbumTitle",
"settingsShowDateInAlbumDescription",
"settingsCollectionsCustomizeNavigationBarTitle",
"settingsImageEditTitle",
"settingsImageEditDescription",
"settingsEnhanceMaxResolutionTitle2",
@ -265,6 +266,7 @@
"alternativeSignIn",
"livePhotoTooltip",
"dragAndDropRearrangeButtons",
"customizeCollectionsNavBarDescription",
"errorUnauthenticated",
"errorDisconnected",
"errorLocked",
@ -278,17 +280,21 @@
"cs": [
"settingsViewerCustomizeAppBarTitle",
"settingsViewerCustomizeBottomAppBarTitle",
"settingsCollectionsCustomizeNavigationBarTitle",
"alternativeSignIn",
"livePhotoTooltip",
"dragAndDropRearrangeButtons"
"dragAndDropRearrangeButtons",
"customizeCollectionsNavBarDescription"
],
"de": [
"settingsViewerCustomizeAppBarTitle",
"settingsViewerCustomizeBottomAppBarTitle",
"settingsCollectionsCustomizeNavigationBarTitle",
"alternativeSignIn",
"livePhotoTooltip",
"dragAndDropRearrangeButtons"
"dragAndDropRearrangeButtons",
"customizeCollectionsNavBarDescription"
],
"el": [
@ -308,6 +314,7 @@
"settingsMemoriesRangeValueText",
"settingsViewerCustomizeAppBarTitle",
"settingsViewerCustomizeBottomAppBarTitle",
"settingsCollectionsCustomizeNavigationBarTitle",
"settingsImageEditTitle",
"settingsImageEditDescription",
"settingsEnhanceMaxResolutionTitle2",
@ -441,20 +448,24 @@
"todayText",
"alternativeSignIn",
"livePhotoTooltip",
"dragAndDropRearrangeButtons"
"dragAndDropRearrangeButtons",
"customizeCollectionsNavBarDescription"
],
"es": [
"settingsViewerCustomizeAppBarTitle",
"settingsViewerCustomizeBottomAppBarTitle",
"settingsCollectionsCustomizeNavigationBarTitle",
"alternativeSignIn",
"livePhotoTooltip",
"dragAndDropRearrangeButtons"
"dragAndDropRearrangeButtons",
"customizeCollectionsNavBarDescription"
],
"fi": [
"settingsViewerCustomizeAppBarTitle",
"settingsViewerCustomizeBottomAppBarTitle",
"settingsCollectionsCustomizeNavigationBarTitle",
"settingsThemePrimaryColor",
"settingsThemeSecondaryColor",
"settingsThemePresets",
@ -493,12 +504,14 @@
"todayText",
"alternativeSignIn",
"livePhotoTooltip",
"dragAndDropRearrangeButtons"
"dragAndDropRearrangeButtons",
"customizeCollectionsNavBarDescription"
],
"fr": [
"settingsViewerCustomizeAppBarTitle",
"settingsViewerCustomizeBottomAppBarTitle",
"settingsCollectionsCustomizeNavigationBarTitle",
"settingsThemePrimaryColor",
"settingsThemeSecondaryColor",
"settingsThemePresets",
@ -537,13 +550,15 @@
"todayText",
"alternativeSignIn",
"livePhotoTooltip",
"dragAndDropRearrangeButtons"
"dragAndDropRearrangeButtons",
"customizeCollectionsNavBarDescription"
],
"it": [
"settingsPersonProviderTitle",
"settingsViewerCustomizeAppBarTitle",
"settingsViewerCustomizeBottomAppBarTitle",
"settingsCollectionsCustomizeNavigationBarTitle",
"settingsImageEditTitle",
"settingsThemePrimaryColor",
"settingsThemeSecondaryColor",
@ -586,7 +601,8 @@
"todayText",
"alternativeSignIn",
"livePhotoTooltip",
"dragAndDropRearrangeButtons"
"dragAndDropRearrangeButtons",
"customizeCollectionsNavBarDescription"
],
"nl": [
@ -647,6 +663,7 @@
"settingsViewerCustomizeBottomAppBarTitle",
"settingsShowDateInAlbumTitle",
"settingsShowDateInAlbumDescription",
"settingsCollectionsCustomizeNavigationBarTitle",
"settingsImageEditTitle",
"settingsImageEditDescription",
"settingsEnhanceMaxResolutionTitle2",
@ -972,6 +989,7 @@
"alternativeSignIn",
"livePhotoTooltip",
"dragAndDropRearrangeButtons",
"customizeCollectionsNavBarDescription",
"errorUnauthenticated",
"errorDisconnected",
"errorLocked",
@ -986,6 +1004,7 @@
"settingsMemoriesRangeValueText",
"settingsViewerCustomizeAppBarTitle",
"settingsViewerCustomizeBottomAppBarTitle",
"settingsCollectionsCustomizeNavigationBarTitle",
"settingsThemePrimaryColor",
"settingsThemeSecondaryColor",
"settingsThemePresets",
@ -1027,7 +1046,8 @@
"todayText",
"alternativeSignIn",
"livePhotoTooltip",
"dragAndDropRearrangeButtons"
"dragAndDropRearrangeButtons",
"customizeCollectionsNavBarDescription"
],
"pt": [
@ -1036,6 +1056,7 @@
"settingsPersonProviderTitle",
"settingsViewerCustomizeAppBarTitle",
"settingsViewerCustomizeBottomAppBarTitle",
"settingsCollectionsCustomizeNavigationBarTitle",
"settingsSeedColorSystemColorDescription",
"settingsThemePrimaryColor",
"settingsThemeSecondaryColor",
@ -1091,12 +1112,14 @@
"todayText",
"alternativeSignIn",
"livePhotoTooltip",
"dragAndDropRearrangeButtons"
"dragAndDropRearrangeButtons",
"customizeCollectionsNavBarDescription"
],
"ru": [
"settingsViewerCustomizeAppBarTitle",
"settingsViewerCustomizeBottomAppBarTitle",
"settingsCollectionsCustomizeNavigationBarTitle",
"settingsThemePrimaryColor",
"settingsThemeSecondaryColor",
"settingsThemePresets",
@ -1135,15 +1158,18 @@
"todayText",
"alternativeSignIn",
"livePhotoTooltip",
"dragAndDropRearrangeButtons"
"dragAndDropRearrangeButtons",
"customizeCollectionsNavBarDescription"
],
"tr": [
"settingsViewerCustomizeAppBarTitle",
"settingsViewerCustomizeBottomAppBarTitle",
"settingsCollectionsCustomizeNavigationBarTitle",
"alternativeSignIn",
"livePhotoTooltip",
"dragAndDropRearrangeButtons"
"dragAndDropRearrangeButtons",
"customizeCollectionsNavBarDescription"
],
"zh": [
@ -1152,6 +1178,7 @@
"settingsMemoriesRangeValueText",
"settingsViewerCustomizeAppBarTitle",
"settingsViewerCustomizeBottomAppBarTitle",
"settingsCollectionsCustomizeNavigationBarTitle",
"settingsSeedColorDescription",
"settingsSeedColorSystemColorDescription",
"settingsThemePrimaryColor",
@ -1218,7 +1245,8 @@
"todayText",
"alternativeSignIn",
"livePhotoTooltip",
"dragAndDropRearrangeButtons"
"dragAndDropRearrangeButtons",
"customizeCollectionsNavBarDescription"
],
"zh_Hant": [
@ -1238,6 +1266,7 @@
"settingsMemoriesRangeValueText",
"settingsViewerCustomizeAppBarTitle",
"settingsViewerCustomizeBottomAppBarTitle",
"settingsCollectionsCustomizeNavigationBarTitle",
"settingsImageEditTitle",
"settingsImageEditDescription",
"settingsEnhanceMaxResolutionTitle2",
@ -1387,6 +1416,7 @@
"todayText",
"alternativeSignIn",
"livePhotoTooltip",
"dragAndDropRearrangeButtons"
"dragAndDropRearrangeButtons",
"customizeCollectionsNavBarDescription"
]
}

View file

@ -51,6 +51,7 @@ import 'package:to_string/to_string.dart';
part 'home_collections.g.dart';
part 'home_collections/app_bar.dart';
part 'home_collections/bloc.dart';
part 'home_collections/nav_bar_buttons.dart';
part 'home_collections/navigation_bar.dart';
part 'home_collections/state_event.dart';
part 'home_collections/type.dart';
@ -226,6 +227,7 @@ 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>();

View file

@ -20,6 +20,7 @@ abstract class $_StateCopyWithWorker {
List<_Item>? transformedItems,
Set<_Item>? selectedItems,
Map<String, int>? itemCounts,
List<PrefHomeCollectionsNavButton>? navBarButtons,
ExceptionEvent? error,
ExceptionEvent? removeError});
}
@ -35,6 +36,7 @@ class _$_StateCopyWithWorkerImpl implements $_StateCopyWithWorker {
dynamic transformedItems,
dynamic selectedItems,
dynamic itemCounts,
dynamic navBarButtons,
dynamic error = copyWithNull,
dynamic removeError = copyWithNull}) {
return _State(
@ -45,6 +47,8 @@ class _$_StateCopyWithWorkerImpl implements $_StateCopyWithWorker {
transformedItems as List<_Item>? ?? that.transformedItems,
selectedItems: selectedItems as Set<_Item>? ?? that.selectedItems,
itemCounts: itemCounts as Map<String, int>? ?? that.itemCounts,
navBarButtons: navBarButtons as List<PrefHomeCollectionsNavButton>? ??
that.navBarButtons,
error: error == copyWithNull ? that.error : error as ExceptionEvent?,
removeError: removeError == copyWithNull
? that.removeError
@ -106,7 +110,7 @@ extension _$_ItemNpLog on _Item {
extension _$_StateToString on _State {
String _$toString() {
// 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 {
String _$toString() {
// ignore: unnecessary_string_interpolations

View file

@ -9,6 +9,7 @@ class _Bloc extends Bloc<_Event, _State>
required this.prefController,
}) : super(_State.init(
sort: prefController.homeAlbumsSortValue,
navBarButtons: prefController.homeCollectionsNavBarButtonsValue,
)) {
on<_LoadCollections>(_onLoad);
on<_ReloadCollections>(_onReload);
@ -21,6 +22,8 @@ class _Bloc extends Bloc<_Event, _State>
on<_SetCollectionSort>(_onSetCollectionSort);
on<_SetItemCount>(_onSetItemCount);
on<_SetNavBarButtons>(_onSetNavBarButtons);
on<_SetError>(_onSetError);
_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
@ -134,6 +141,11 @@ class _Bloc extends Bloc<_Event, _State>
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) {
_log.info(ev);
emit(state.copyWith(error: ExceptionEvent(ev.error, ev.stackTrace)));

View 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;
}

View file

@ -37,8 +37,6 @@ class _NavigationBarState extends State<_NavigationBar> {
@override
Widget build(BuildContext context) {
final buttons =
_buttons.map((e) => _buildButton(context, e)).nonNulls.toList();
return SliverToBoxAdapter(
child: SizedBox(
height: 48,
@ -47,13 +45,25 @@ class _NavigationBarState extends State<_NavigationBar> {
Expanded(
child: Stack(
children: [
ListView.separated(
controller: _scrollController,
scrollDirection: Axis.horizontal,
padding: const EdgeInsets.only(left: 16),
itemCount: buttons.length,
itemBuilder: (context, i) => buttons[i],
separatorBuilder: (context, _) => const SizedBox(width: 16),
_BlocSelector(
selector: (state) => state.navBarButtons,
builder: (context, navBarButtons) {
final buttons = navBarButtons
.map((e) => _buildButton(context, e))
.nonNulls
.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)
Positioned(
@ -113,19 +123,18 @@ class _NavigationBarState extends State<_NavigationBar> {
);
}
Widget? _buildButton(
BuildContext context, HomeCollectionsNavBarButtonType type) {
switch (type) {
Widget? _buildButton(BuildContext context, PrefHomeCollectionsNavButton btn) {
switch (btn.type) {
case HomeCollectionsNavBarButtonType.sharing:
return const _NavBarSharingButton();
return _NavBarSharingButton(isMinimized: btn.isMinimized);
case HomeCollectionsNavBarButtonType.edited:
return features.isSupportEnhancement
? const _NavBarEditedButton()
? _NavBarEditedButton(isMinimized: btn.isMinimized)
: null;
case HomeCollectionsNavBarButtonType.archive:
return const _NavBarArchiveButton();
return _NavBarArchiveButton(isMinimized: btn.isMinimized);
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);
}
static const _buttons = [
HomeCollectionsNavBarButtonType.sharing,
HomeCollectionsNavBarButtonType.edited,
HomeCollectionsNavBarButtonType.archive,
HomeCollectionsNavBarButtonType.trash,
];
late final ScrollController _scrollController;
var _hasFirstScrollUpdate = 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),
);
},
);
}
}

View file

@ -10,12 +10,14 @@ class _State {
required this.transformedItems,
required this.selectedItems,
required this.itemCounts,
required this.navBarButtons,
this.error,
required this.removeError,
});
factory _State.init({
required collection_util.CollectionSort sort,
required List<PrefHomeCollectionsNavButton> navBarButtons,
}) {
return _State(
collections: [],
@ -24,6 +26,7 @@ class _State {
transformedItems: [],
selectedItems: {},
itemCounts: {},
navBarButtons: navBarButtons,
removeError: null,
);
}
@ -38,6 +41,8 @@ class _State {
final Set<_Item> selectedItems;
final Map<String, int> itemCounts;
final List<PrefHomeCollectionsNavButton> navBarButtons;
final ExceptionEvent? error;
final ExceptionEvent? removeError;
}
@ -128,6 +133,16 @@ class _SetItemCount implements _Event {
final int value;
}
@toString
class _SetNavBarButtons implements _Event {
const _SetNavBarButtons(this.value);
@override
String toString() => _$toString();
final List<PrefHomeCollectionsNavButton> value;
}
@toString
class _SetError implements _Event {
const _SetError(this.error, [this.stackTrace]);

View file

@ -8,6 +8,7 @@ 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/page_visibility_mixin.dart';
import 'package:nc_photos/widget/settings/collections_nav_bar_settings.dart';
import 'package:np_codegen/np_codegen.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(),
),
);
},
),
],
),
),

View 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;
}

View 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;
}

View 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;
}

View 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;
}

View 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);
}

View 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}";
}
}

View file

@ -88,4 +88,7 @@ extension ListExtension<T> on List<T> {
List<T> removed(T value) => toList()..remove(value);
List<T> removedAt(int index) => toList()..removeAt(index);
List<T> removedWhere(bool Function(T element) test) =>
toList()..removeWhere(test);
}