Viewer app bar buttons now controlled by pref

This commit is contained in:
Ming Ming 2024-10-09 20:48:19 +08:00
parent d723a75ce1
commit 01697b6fbb
13 changed files with 256 additions and 22 deletions

View file

@ -12,6 +12,7 @@ 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/viewer.dart';
import 'package:np_codegen/np_codegen.dart';
import 'package:np_common/object_util.dart';
import 'package:np_gps_map/np_gps_map.dart';
@ -234,6 +235,21 @@ class PrefController {
value: value,
);
Future<bool> setViewerAppBarButtons(List<ViewerAppBarButtonType> value) =>
_set<List<ViewerAppBarButtonType>>(
controller: _viewerAppBarButtonsController,
setter: (pref, value) => pref.setViewerAppBarButtons(value),
value: value,
);
Future<bool> setViewerBottomAppBarButtons(
List<ViewerAppBarButtonType> value) =>
_set<List<ViewerAppBarButtonType>>(
controller: _viewerBottomAppBarButtonsController,
setter: (pref, value) => pref.setViewerBottomAppBarButtons(value),
value: value,
);
Future<bool> _set<T>({
required BehaviorSubject<T> controller,
required Future<bool> Function(Pref pref, T value) setter,
@ -373,6 +389,23 @@ class PrefController {
@npSubjectAccessor
late final _isSlideshowReverseController =
BehaviorSubject.seeded(pref.isSlideshowReverse() ?? false);
@npSubjectAccessor
late final _viewerAppBarButtonsController =
BehaviorSubject.seeded(pref.getViewerAppBarButtons() ??
const [
ViewerAppBarButtonType.livePhoto,
ViewerAppBarButtonType.favorite,
]);
@npSubjectAccessor
late final _viewerBottomAppBarButtonsController =
BehaviorSubject.seeded(pref.getViewerBottomAppBarButtons() ??
const [
ViewerAppBarButtonType.share,
ViewerAppBarButtonType.edit,
ViewerAppBarButtonType.enhance,
ViewerAppBarButtonType.download,
ViewerAppBarButtonType.delete,
]);
}
extension PrefControllerExtension on PrefController {

View file

@ -234,6 +234,24 @@ extension $PrefControllerNpSubjectAccessor on PrefController {
Stream<bool> get isSlideshowReverseChange =>
isSlideshowReverse.distinct().skip(1);
bool get isSlideshowReverseValue => _isSlideshowReverseController.value;
// _viewerAppBarButtonsController
ValueStream<List<ViewerAppBarButtonType>> get viewerAppBarButtons =>
_viewerAppBarButtonsController.stream;
Stream<List<ViewerAppBarButtonType>> get viewerAppBarButtonsNew =>
viewerAppBarButtons.skip(1);
Stream<List<ViewerAppBarButtonType>> get viewerAppBarButtonsChange =>
viewerAppBarButtons.distinct().skip(1);
List<ViewerAppBarButtonType> get viewerAppBarButtonsValue =>
_viewerAppBarButtonsController.value;
// _viewerBottomAppBarButtonsController
ValueStream<List<ViewerAppBarButtonType>> get viewerBottomAppBarButtons =>
_viewerBottomAppBarButtonsController.stream;
Stream<List<ViewerAppBarButtonType>> get viewerBottomAppBarButtonsNew =>
viewerBottomAppBarButtons.skip(1);
Stream<List<ViewerAppBarButtonType>> get viewerBottomAppBarButtonsChange =>
viewerBottomAppBarButtons.distinct().skip(1);
List<ViewerAppBarButtonType> get viewerBottomAppBarButtonsValue =>
_viewerBottomAppBarButtonsController.value;
}
extension $SecurePrefControllerNpSubjectAccessor on SecurePrefController {

View file

@ -149,6 +149,23 @@ extension on Pref {
bool? isSlideshowReverse() => provider.getBool(PrefKey.isSlideshowReverse);
Future<bool> setSlideshowReverse(bool value) =>
provider.setBool(PrefKey.isSlideshowReverse, value);
List<ViewerAppBarButtonType>? getViewerAppBarButtons() => provider
.getIntList(PrefKey.viewerAppBarButtons)
?.map(ViewerAppBarButtonType.fromValue)
.toList();
Future<bool> setViewerAppBarButtons(List<ViewerAppBarButtonType> value) =>
provider.setIntList(
PrefKey.viewerAppBarButtons, value.map((e) => e.index).toList());
List<ViewerAppBarButtonType>? getViewerBottomAppBarButtons() => provider
.getIntList(PrefKey.viewerBottomAppBarButtons)
?.map(ViewerAppBarButtonType.fromValue)
.toList();
Future<bool> setViewerBottomAppBarButtons(
List<ViewerAppBarButtonType> value) =>
provider.setIntList(PrefKey.viewerBottomAppBarButtons,
value.map((e) => e.index).toList());
}
MapCoord? _tryMapCoordFromJson(dynamic json) {

View file

@ -117,6 +117,8 @@ enum PrefKey implements PrefKeyInterface {
isNewHttpEngine,
mapDefaultRangeType,
mapDefaultCustomRange,
viewerAppBarButtons,
viewerBottomAppBarButtons,
;
@override
@ -211,6 +213,10 @@ enum PrefKey implements PrefKeyInterface {
return "mapDefaultRangeType";
case PrefKey.mapDefaultCustomRange:
return "mapDefaultCustomRange";
case PrefKey.viewerAppBarButtons:
return "viewerAppBarButtons";
case PrefKey.viewerBottomAppBarButtons:
return "viewerBottomAppBarButtons";
}
}
}
@ -260,6 +266,9 @@ abstract class PrefProvider {
List<String>? getStringList(PrefKeyInterface key);
Future<bool> setStringList(PrefKeyInterface key, List<String> value);
List<int>? getIntList(PrefKeyInterface key);
Future<bool> setIntList(PrefKeyInterface key, List<int> value);
Future<bool> remove(PrefKeyInterface key);
Future<bool> clear();
}

View file

@ -31,6 +31,12 @@ class PrefMemoryProvider extends PrefProvider {
Future<bool> setStringList(PrefKeyInterface key, List<String> value) =>
_set(key, value);
@override
List<int>? getIntList(PrefKeyInterface key) => _get<List<int>>(key);
@override
Future<bool> setIntList(PrefKeyInterface key, List<int> value) =>
_set(key, value);
@override
Future<bool> remove(PrefKeyInterface key) async {
_data.remove(key.toStringKey());

View file

@ -71,6 +71,16 @@ class PrefSecureStorageProvider implements PrefProvider {
Future<bool> setStringList(PrefKeyInterface key, List<String> value) =>
setString(key, jsonEncode(value));
@override
List<int>? getIntList(PrefKeyInterface key) {
final value = _rawData[key.toStringKey()];
return (value?.let(jsonDecode) as List).cast<int>();
}
@override
Future<bool> setIntList(PrefKeyInterface key, List<int> value) =>
setString(key, jsonEncode(value));
@override
Future<bool> remove(PrefKeyInterface key) async {
try {

View file

@ -47,6 +47,15 @@ class PrefSharedPreferencesProvider extends PrefProvider {
Future<bool> setStringList(PrefKeyInterface key, List<String> value) =>
_pref.setStringList(key.toStringKey(), value);
@override
List<int>? getIntList(PrefKeyInterface key) =>
_pref.getStringList(key.toStringKey())?.map(int.parse).toList();
@override
Future<bool> setIntList(PrefKeyInterface key, List<int> value) =>
_pref.setStringList(
key.toStringKey(), value.map((e) => e.toString()).toList());
@override
Future<bool> remove(PrefKeyInterface key) => _pref.remove(key.toStringKey());

View file

@ -36,6 +36,12 @@ class PrefUniversalStorageProvider extends PrefProvider {
Future<bool> setStringList(PrefKeyInterface key, List<String> value) =>
_set(key, value);
@override
List<int>? getIntList(PrefKeyInterface key) => _get<List<int>>(key);
@override
Future<bool> setIntList(PrefKeyInterface key, List<int> value) =>
_set(key, value);
@override
Future<bool> remove(PrefKeyInterface key) async {
final newData = Map.of(_data)..remove(key.toStringKey());

View file

@ -29,8 +29,10 @@ abstract class $_StateCopyWithWorker {
Unique<_OpenDetailPaneRequest>? openDetailPaneRequest,
Unique<bool>? closeDetailPane,
bool? isZoomed,
bool? isShowAppBar,
bool? isInitialLoad,
bool? isShowAppBar,
List<ViewerAppBarButtonType>? appBarButtons,
List<ViewerAppBarButtonType>? bottomAppBarButtons,
Unique<int?>? pendingRemovePage,
Unique<ImageEditorArguments?>? imageEditorRequest,
Unique<ImageEnhancerArguments?>? imageEnhancerRequest,
@ -59,8 +61,10 @@ class _$_StateCopyWithWorkerImpl implements $_StateCopyWithWorker {
dynamic openDetailPaneRequest,
dynamic closeDetailPane,
dynamic isZoomed,
dynamic isShowAppBar,
dynamic isInitialLoad,
dynamic isShowAppBar,
dynamic appBarButtons,
dynamic bottomAppBarButtons,
dynamic pendingRemovePage,
dynamic imageEditorRequest,
dynamic imageEnhancerRequest,
@ -98,8 +102,13 @@ class _$_StateCopyWithWorkerImpl implements $_StateCopyWithWorker {
closeDetailPane:
closeDetailPane as Unique<bool>? ?? that.closeDetailPane,
isZoomed: isZoomed as bool? ?? that.isZoomed,
isShowAppBar: isShowAppBar as bool? ?? that.isShowAppBar,
isInitialLoad: isInitialLoad as bool? ?? that.isInitialLoad,
isShowAppBar: isShowAppBar as bool? ?? that.isShowAppBar,
appBarButtons: appBarButtons as List<ViewerAppBarButtonType>? ??
that.appBarButtons,
bottomAppBarButtons:
bottomAppBarButtons as List<ViewerAppBarButtonType>? ??
that.bottomAppBarButtons,
pendingRemovePage:
pendingRemovePage as Unique<int?>? ?? that.pendingRemovePage,
imageEditorRequest:
@ -193,7 +202,7 @@ extension _$_PageViewStateNpLog on _PageViewState {
extension _$_StateToString on _State {
String _$toString() {
// ignore: unnecessary_string_interpolations
return "_State {fileIdOrders: $fileIdOrders, files: {length: ${files.length}}, fileStates: {length: ${fileStates.length}}, index: $index, currentFile: ${currentFile == null ? null : "${currentFile!.fdPath}"}, currentFileState: $currentFileState, collection: $collection, collectionItemsController: $collectionItemsController, collectionItems: ${collectionItems == null ? null : "{length: ${collectionItems!.length}}"}, isShowDetailPane: $isShowDetailPane, isClosingDetailPane: $isClosingDetailPane, isDetailPaneActive: $isDetailPaneActive, openDetailPaneRequest: $openDetailPaneRequest, closeDetailPane: $closeDetailPane, isZoomed: $isZoomed, isShowAppBar: $isShowAppBar, isInitialLoad: $isInitialLoad, pendingRemovePage: $pendingRemovePage, imageEditorRequest: $imageEditorRequest, imageEnhancerRequest: $imageEnhancerRequest, shareRequest: $shareRequest, slideshowRequest: $slideshowRequest, error: $error}";
return "_State {fileIdOrders: $fileIdOrders, files: {length: ${files.length}}, fileStates: {length: ${fileStates.length}}, index: $index, currentFile: ${currentFile == null ? null : "${currentFile!.fdPath}"}, currentFileState: $currentFileState, collection: $collection, collectionItemsController: $collectionItemsController, collectionItems: ${collectionItems == null ? null : "{length: ${collectionItems!.length}}"}, isShowDetailPane: $isShowDetailPane, isClosingDetailPane: $isClosingDetailPane, isDetailPaneActive: $isDetailPaneActive, openDetailPaneRequest: $openDetailPaneRequest, closeDetailPane: $closeDetailPane, isZoomed: $isZoomed, isInitialLoad: $isInitialLoad, isShowAppBar: $isShowAppBar, appBarButtons: [length: ${appBarButtons.length}], bottomAppBarButtons: [length: ${bottomAppBarButtons.length}], pendingRemovePage: $pendingRemovePage, imageEditorRequest: $imageEditorRequest, imageEnhancerRequest: $imageEnhancerRequest, shareRequest: $shareRequest, slideshowRequest: $slideshowRequest, error: $error}";
}
}
@ -260,6 +269,20 @@ extension _$_HideAppBarToString on _HideAppBar {
}
}
extension _$_SetAppBarButtonsToString on _SetAppBarButtons {
String _$toString() {
// ignore: unnecessary_string_interpolations
return "_SetAppBarButtons {value: [length: ${value.length}]}";
}
}
extension _$_SetBottomAppBarButtonsToString on _SetBottomAppBarButtons {
String _$toString() {
// ignore: unnecessary_string_interpolations
return "_SetBottomAppBarButtons {value: [length: ${value.length}]}";
}
}
extension _$_PauseLivePhotoToString on _PauseLivePhoto {
String _$toString() {
// ignore: unnecessary_string_interpolations

View file

@ -9,7 +9,10 @@ class _AppBar extends StatelessWidget {
return _BlocBuilder(
buildWhen: (previous, current) =>
previous.isDetailPaneActive != current.isDetailPaneActive ||
previous.isZoomed != current.isZoomed,
previous.isZoomed != current.isZoomed ||
previous.currentFile != current.currentFile ||
previous.collection != current.collection ||
previous.appBarButtons != current.appBarButtons,
builder: (context, state) => AppBar(
backgroundColor: Colors.transparent,
elevation: 0,
@ -29,8 +32,13 @@ class _AppBar extends StatelessWidget {
centerTitle: isTitleCentered,
actions: !state.isDetailPaneActive && !state.isZoomed
? [
const _AppBarLivePhotoButton(),
const _AppBarFavoriteButton(),
...state.appBarButtons
.map((e) => _buildAppBarButton(
e,
currentFile: state.currentFile,
collection: state.collection,
))
.nonNulls,
IconButton(
icon: const Icon(Icons.more_vert),
tooltip: L10n.global().detailsTooltip,
@ -100,21 +108,18 @@ class _BottomAppBar extends StatelessWidget {
child: _BlocBuilder(
buildWhen: (previous, current) =>
previous.currentFile != current.currentFile ||
previous.collection != current.collection,
previous.collection != current.collection ||
previous.bottomAppBarButtons != current.bottomAppBarButtons,
builder: (context, state) => Row(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.max,
children: [
const _AppBarShareButton(),
if (features.isSupportEnhancement &&
state.currentFile?.let(ImageEnhancer.isSupportedFormat) ==
true) ...[
const _AppBarEditButton(),
const _AppBarEnhanceButton(),
],
const _AppBarDownloadButton(),
if (state.collection == null) const _AppBarDeleteButton(),
]
children: state.bottomAppBarButtons
.map((e) => _buildAppBarButton(
e,
currentFile: state.currentFile,
collection: state.collection,
))
.nonNulls
.map((e) => Expanded(
flex: 1,
child: e,
@ -125,3 +130,36 @@ class _BottomAppBar extends StatelessWidget {
);
}
}
/// Build app bar buttons based on [type]. May return null if this button type
/// is not supported in the current context
Widget? _buildAppBarButton(
ViewerAppBarButtonType type, {
required FileDescriptor? currentFile,
required Collection? collection,
}) {
switch (type) {
case ViewerAppBarButtonType.livePhoto:
return currentFile?.let(getLivePhotoTypeFromFile) != null
? const _AppBarLivePhotoButton()
: null;
case ViewerAppBarButtonType.favorite:
return const _AppBarFavoriteButton();
case ViewerAppBarButtonType.share:
return const _AppBarShareButton();
case ViewerAppBarButtonType.edit:
return features.isSupportEnhancement &&
currentFile?.let(ImageEnhancer.isSupportedFormat) == true
? const _AppBarEditButton()
: null;
case ViewerAppBarButtonType.enhance:
return features.isSupportEnhancement &&
currentFile?.let(ImageEnhancer.isSupportedFormat) == true
? const _AppBarEnhanceButton()
: null;
case ViewerAppBarButtonType.download:
return const _AppBarDownloadButton();
case ViewerAppBarButtonType.delete:
return collection == null ? const _AppBarDeleteButton() : null;
}
}

View file

@ -1,5 +1,20 @@
part of '../viewer.dart';
enum ViewerAppBarButtonType {
// the order must not be changed
livePhoto,
favorite,
share,
edit,
enhance,
download,
delete,
;
static ViewerAppBarButtonType fromValue(int value) =>
ViewerAppBarButtonType.values[value];
}
class _AppBarLivePhotoButton extends StatelessWidget {
const _AppBarLivePhotoButton();

View file

@ -18,6 +18,8 @@ class _Bloc extends Bloc<_Event, _State>
index: startIndex,
currentFile:
filesController.stream.value.dataMap[fileIds[startIndex]]!,
appBarButtons: prefController.viewerAppBarButtonsValue,
bottomAppBarButtons: prefController.viewerBottomAppBarButtonsValue,
)) {
on<_Init>(_onInit);
on<_SetIndex>(_onSetIndex);
@ -28,6 +30,8 @@ class _Bloc extends Bloc<_Event, _State>
on<_ToggleAppBar>(_onToggleAppBar);
on<_ShowAppBar>(_onShowAppBar);
on<_HideAppBar>(_onHideAppBar);
on<_SetAppBarButtons>(_onAppBarButtons);
on<_SetBottomAppBarButtons>(_onBottomAppBarButtons);
on<_PauseLivePhoto>(_onPauseLivePhoto);
on<_PlayLivePhoto>(_onPlayLivePhoto);
on<_Unfavorite>(_onUnfavorite);
@ -74,6 +78,13 @@ class _Bloc extends Bloc<_Event, _State>
_collectionItemsSubscription?.cancel();
}));
}
_subscriptions.add(prefController.viewerAppBarButtonsChange.listen((event) {
add(_SetAppBarButtons(event));
}));
_subscriptions
.add(prefController.viewerBottomAppBarButtonsChange.listen((event) {
add(_SetBottomAppBarButtons(event));
}));
add(_SetIndex(startIndex));
}
@ -189,6 +200,16 @@ class _Bloc extends Bloc<_Event, _State>
SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersive);
}
void _onAppBarButtons(_SetAppBarButtons ev, _Emitter emit) {
_log.info(ev);
emit(state.copyWith(appBarButtons: ev.value));
}
void _onBottomAppBarButtons(_SetBottomAppBarButtons ev, _Emitter emit) {
_log.info(ev);
emit(state.copyWith(bottomAppBarButtons: ev.value));
}
void _onPauseLivePhoto(_PauseLivePhoto ev, _Emitter emit) {
_log.info(ev);
_updateFileState(ev.fileId, emit, shouldPlayLivePhoto: false);

View file

@ -19,8 +19,10 @@ class _State {
required this.openDetailPaneRequest,
required this.closeDetailPane,
required this.isZoomed,
required this.isShowAppBar,
required this.isInitialLoad,
required this.isShowAppBar,
required this.appBarButtons,
required this.bottomAppBarButtons,
required this.pendingRemovePage,
required this.imageEditorRequest,
required this.imageEnhancerRequest,
@ -33,6 +35,8 @@ class _State {
required List<int> fileIds,
required int index,
required FileDescriptor currentFile,
required List<ViewerAppBarButtonType> appBarButtons,
required List<ViewerAppBarButtonType> bottomAppBarButtons,
}) =>
_State(
fileIdOrders: fileIds,
@ -46,8 +50,10 @@ class _State {
openDetailPaneRequest: Unique(const _OpenDetailPaneRequest(false)),
closeDetailPane: Unique(false),
isZoomed: false,
isShowAppBar: true,
isInitialLoad: true,
isShowAppBar: true,
appBarButtons: appBarButtons,
bottomAppBarButtons: bottomAppBarButtons,
pendingRemovePage: Unique(null),
imageEditorRequest: Unique(null),
imageEnhancerRequest: Unique(null),
@ -76,9 +82,12 @@ class _State {
final Unique<_OpenDetailPaneRequest> openDetailPaneRequest;
final Unique<bool> closeDetailPane;
final bool isZoomed;
final bool isShowAppBar;
final bool isInitialLoad;
final bool isShowAppBar;
final List<ViewerAppBarButtonType> appBarButtons;
final List<ViewerAppBarButtonType> bottomAppBarButtons;
final Unique<int?> pendingRemovePage;
final Unique<ImageEditorArguments?> imageEditorRequest;
@ -189,6 +198,26 @@ class _HideAppBar implements _Event {
String toString() => _$toString();
}
@toString
class _SetAppBarButtons implements _Event {
const _SetAppBarButtons(this.value);
@override
String toString() => _$toString();
final List<ViewerAppBarButtonType> value;
}
@toString
class _SetBottomAppBarButtons implements _Event {
const _SetBottomAppBarButtons(this.value);
@override
String toString() => _$toString();
final List<ViewerAppBarButtonType> value;
}
@toString
class _PauseLivePhoto implements _Event {
const _PauseLivePhoto(this.fileId);