mirror of
https://gitlab.com/nkming2/nc-photos.git
synced 2025-01-22 16:56:19 +01:00
Rewrite double tap to exit code
This commit is contained in:
parent
59fff6d679
commit
f789d0473b
7 changed files with 405 additions and 165 deletions
63
app/lib/widget/double_tap_exit_container/bloc.dart
Normal file
63
app/lib/widget/double_tap_exit_container/bloc.dart
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
part of 'double_tap_exit_container.dart';
|
||||||
|
|
||||||
|
@npLog
|
||||||
|
class _Bloc extends Bloc<_Event, _State> with BlocLogger {
|
||||||
|
_Bloc({
|
||||||
|
required this.prefController,
|
||||||
|
}) : super(_State.init(
|
||||||
|
isDoubleTapExit: prefController.isDoubleTapExitValue,
|
||||||
|
)) {
|
||||||
|
on<_SetDoubleTapExit>(_onSetDoubleTapExit);
|
||||||
|
on<_SetCanPop>(_onSetCanPop);
|
||||||
|
on<_OnPopInvoked>(_onOnPopInvoked);
|
||||||
|
|
||||||
|
_subscriptions.add(prefController.isDoubleTapExitChange.listen((ev) {
|
||||||
|
add(_SetDoubleTapExit(ev));
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> close() {
|
||||||
|
for (final s in _subscriptions) {
|
||||||
|
s.cancel();
|
||||||
|
}
|
||||||
|
_timer?.cancel();
|
||||||
|
return super.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get tag => _log.fullName;
|
||||||
|
|
||||||
|
void _onSetDoubleTapExit(_SetDoubleTapExit ev, _Emitter emit) {
|
||||||
|
_log.info(ev);
|
||||||
|
emit(state.copyWith(isDoubleTapExit: ev.value));
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onSetCanPop(_SetCanPop ev, _Emitter emit) {
|
||||||
|
_log.info(ev);
|
||||||
|
emit(state.copyWith(canPop: ev.value));
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onOnPopInvoked(_OnPopInvoked ev, _Emitter emit) {
|
||||||
|
_log.info(ev);
|
||||||
|
if (state.isDoubleTapExit && !state.canPop) {
|
||||||
|
emit(state.copyWith(canPop: true));
|
||||||
|
_timer?.cancel();
|
||||||
|
_timer = Timer(
|
||||||
|
const Duration(seconds: 5),
|
||||||
|
() {
|
||||||
|
add(const _SetCanPop(false));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
SnackBarManager().showSnackBar(SnackBar(
|
||||||
|
content: Text(L10n.global().doubleTapExitNotification),
|
||||||
|
duration: k.snackBarDurationShort,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final PrefController prefController;
|
||||||
|
|
||||||
|
final _subscriptions = <StreamSubscription>[];
|
||||||
|
Timer? _timer;
|
||||||
|
}
|
|
@ -0,0 +1,72 @@
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:copy_with/copy_with.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/k.dart' as k;
|
||||||
|
import 'package:nc_photos/snack_bar_manager.dart';
|
||||||
|
import 'package:np_codegen/np_codegen.dart';
|
||||||
|
import 'package:to_string/to_string.dart';
|
||||||
|
|
||||||
|
part 'bloc.dart';
|
||||||
|
part 'double_tap_exit_container.g.dart';
|
||||||
|
part 'state_event.dart';
|
||||||
|
|
||||||
|
class DoubleTapExitContainer extends StatelessWidget {
|
||||||
|
const DoubleTapExitContainer({
|
||||||
|
super.key,
|
||||||
|
required this.child,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return BlocProvider(
|
||||||
|
create: (_) => _Bloc(
|
||||||
|
prefController: context.read(),
|
||||||
|
),
|
||||||
|
child: _WrappedDoubleTapExitContainer(child: child),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final Widget child;
|
||||||
|
}
|
||||||
|
|
||||||
|
class _WrappedDoubleTapExitContainer extends StatelessWidget {
|
||||||
|
const _WrappedDoubleTapExitContainer({
|
||||||
|
required this.child,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return _BlocBuilder(
|
||||||
|
buildWhen: (previous, current) =>
|
||||||
|
previous.isDoubleTapExit != current.isDoubleTapExit ||
|
||||||
|
previous.canPop != current.canPop,
|
||||||
|
builder: (context, state) => PopScope(
|
||||||
|
canPop: !state.isDoubleTapExit || state.canPop,
|
||||||
|
onPopInvoked: (didPop) {
|
||||||
|
context.addEvent(_OnPopInvoked(didPop));
|
||||||
|
},
|
||||||
|
child: child,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final Widget child;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
|
@ -0,0 +1,79 @@
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'double_tap_exit_container.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// CopyWithLintRuleGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
// ignore_for_file: library_private_types_in_public_api, duplicate_ignore
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// CopyWithGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
abstract class $_StateCopyWithWorker {
|
||||||
|
_State call({bool? isDoubleTapExit, bool? canPop});
|
||||||
|
}
|
||||||
|
|
||||||
|
class _$_StateCopyWithWorkerImpl implements $_StateCopyWithWorker {
|
||||||
|
_$_StateCopyWithWorkerImpl(this.that);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_State call({dynamic isDoubleTapExit, dynamic canPop}) {
|
||||||
|
return _State(
|
||||||
|
isDoubleTapExit: isDoubleTapExit as bool? ?? that.isDoubleTapExit,
|
||||||
|
canPop: canPop as bool? ?? that.canPop);
|
||||||
|
}
|
||||||
|
|
||||||
|
final _State that;
|
||||||
|
}
|
||||||
|
|
||||||
|
extension $_StateCopyWith on _State {
|
||||||
|
$_StateCopyWithWorker get copyWith => _$copyWith;
|
||||||
|
$_StateCopyWithWorker get _$copyWith => _$_StateCopyWithWorkerImpl(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// NpLogGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
extension _$_BlocNpLog on _Bloc {
|
||||||
|
// ignore: unused_element
|
||||||
|
Logger get _log => log;
|
||||||
|
|
||||||
|
static final log = Logger(
|
||||||
|
"widget.double_tap_exit_container.double_tap_exit_container._Bloc");
|
||||||
|
}
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// ToStringGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
extension _$_StateToString on _State {
|
||||||
|
String _$toString() {
|
||||||
|
// ignore: unnecessary_string_interpolations
|
||||||
|
return "_State {isDoubleTapExit: $isDoubleTapExit, canPop: $canPop}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension _$_SetDoubleTapExitToString on _SetDoubleTapExit {
|
||||||
|
String _$toString() {
|
||||||
|
// ignore: unnecessary_string_interpolations
|
||||||
|
return "_SetDoubleTapExit {value: $value}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension _$_SetCanPopToString on _SetCanPop {
|
||||||
|
String _$toString() {
|
||||||
|
// ignore: unnecessary_string_interpolations
|
||||||
|
return "_SetCanPop {value: $value}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension _$_OnPopInvokedToString on _OnPopInvoked {
|
||||||
|
String _$toString() {
|
||||||
|
// ignore: unnecessary_string_interpolations
|
||||||
|
return "_OnPopInvoked {didPop: $didPop}";
|
||||||
|
}
|
||||||
|
}
|
56
app/lib/widget/double_tap_exit_container/state_event.dart
Normal file
56
app/lib/widget/double_tap_exit_container/state_event.dart
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
part of 'double_tap_exit_container.dart';
|
||||||
|
|
||||||
|
@genCopyWith
|
||||||
|
@toString
|
||||||
|
class _State {
|
||||||
|
const _State({
|
||||||
|
required this.isDoubleTapExit,
|
||||||
|
required this.canPop,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory _State.init({
|
||||||
|
required bool isDoubleTapExit,
|
||||||
|
}) =>
|
||||||
|
_State(
|
||||||
|
isDoubleTapExit: isDoubleTapExit,
|
||||||
|
canPop: false,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => _$toString();
|
||||||
|
|
||||||
|
final bool isDoubleTapExit;
|
||||||
|
final bool canPop;
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class _Event {}
|
||||||
|
|
||||||
|
@toString
|
||||||
|
class _SetDoubleTapExit implements _Event {
|
||||||
|
const _SetDoubleTapExit(this.value);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => _$toString();
|
||||||
|
|
||||||
|
final bool value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@toString
|
||||||
|
class _SetCanPop implements _Event {
|
||||||
|
const _SetCanPop(this.value);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => _$toString();
|
||||||
|
|
||||||
|
final bool value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@toString
|
||||||
|
class _OnPopInvoked implements _Event {
|
||||||
|
const _OnPopInvoked(this.didPop);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => _$toString();
|
||||||
|
|
||||||
|
final bool didPop;
|
||||||
|
}
|
|
@ -1,35 +0,0 @@
|
||||||
import 'package:clock/clock.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:nc_photos/app_localizations.dart';
|
|
||||||
import 'package:nc_photos/entity/pref.dart';
|
|
||||||
import 'package:nc_photos/k.dart' as k;
|
|
||||||
import 'package:nc_photos/snack_bar_manager.dart';
|
|
||||||
|
|
||||||
class DoubleTapExitHandler {
|
|
||||||
factory DoubleTapExitHandler() => _inst;
|
|
||||||
|
|
||||||
DoubleTapExitHandler._();
|
|
||||||
|
|
||||||
/// Return if this back button event should actually exit the app
|
|
||||||
bool call() {
|
|
||||||
if (!Pref().isDoubleTapExitOr()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
final now = clock.now().toUtc();
|
|
||||||
_lastBackButtonAt ??= now.subtract(const Duration(days: 1));
|
|
||||||
if (now.difference(_lastBackButtonAt!) < const Duration(seconds: 5)) {
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
_lastBackButtonAt = now;
|
|
||||||
SnackBarManager().showSnackBar(SnackBar(
|
|
||||||
content: Text(L10n.global().doubleTapExitNotification),
|
|
||||||
duration: k.snackBarDurationShort,
|
|
||||||
));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static final _inst = DoubleTapExitHandler._();
|
|
||||||
|
|
||||||
DateTime? _lastBackButtonAt;
|
|
||||||
}
|
|
|
@ -33,9 +33,9 @@ import 'package:nc_photos/widget/album_importer.dart';
|
||||||
import 'package:nc_photos/widget/archive_browser.dart';
|
import 'package:nc_photos/widget/archive_browser.dart';
|
||||||
import 'package:nc_photos/widget/collection_browser.dart';
|
import 'package:nc_photos/widget/collection_browser.dart';
|
||||||
import 'package:nc_photos/widget/collection_grid_item.dart';
|
import 'package:nc_photos/widget/collection_grid_item.dart';
|
||||||
|
import 'package:nc_photos/widget/double_tap_exit_container/double_tap_exit_container.dart';
|
||||||
import 'package:nc_photos/widget/enhanced_photo_browser.dart';
|
import 'package:nc_photos/widget/enhanced_photo_browser.dart';
|
||||||
import 'package:nc_photos/widget/fade_out_list.dart';
|
import 'package:nc_photos/widget/fade_out_list.dart';
|
||||||
import 'package:nc_photos/widget/handler/double_tap_exit_handler.dart';
|
|
||||||
import 'package:nc_photos/widget/home_app_bar.dart';
|
import 'package:nc_photos/widget/home_app_bar.dart';
|
||||||
import 'package:nc_photos/widget/map_browser.dart';
|
import 'package:nc_photos/widget/map_browser.dart';
|
||||||
import 'package:nc_photos/widget/navigation_bar_blur_filter.dart';
|
import 'package:nc_photos/widget/navigation_bar_blur_filter.dart';
|
||||||
|
@ -46,7 +46,6 @@ import 'package:nc_photos/widget/selection_app_bar.dart';
|
||||||
import 'package:nc_photos/widget/sharing_browser.dart';
|
import 'package:nc_photos/widget/sharing_browser.dart';
|
||||||
import 'package:nc_photos/widget/trashbin_browser.dart';
|
import 'package:nc_photos/widget/trashbin_browser.dart';
|
||||||
import 'package:np_codegen/np_codegen.dart';
|
import 'package:np_codegen/np_codegen.dart';
|
||||||
import 'package:np_platform_util/np_platform_util.dart';
|
|
||||||
import 'package:np_ui/np_ui.dart';
|
import 'package:np_ui/np_ui.dart';
|
||||||
import 'package:to_string/to_string.dart';
|
import 'package:to_string/to_string.dart';
|
||||||
|
|
||||||
|
@ -96,7 +95,7 @@ class _WrappedHomeCollectionsState extends State<_WrappedHomeCollections>
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final content = MultiBlocListener(
|
return MultiBlocListener(
|
||||||
listeners: [
|
listeners: [
|
||||||
_BlocListener(
|
_BlocListener(
|
||||||
listenWhen: (previous, current) =>
|
listenWhen: (previous, current) =>
|
||||||
|
@ -127,103 +126,108 @@ class _WrappedHomeCollectionsState extends State<_WrappedHomeCollections>
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
child: Stack(
|
child: _BlocSelector(
|
||||||
children: [
|
selector: (state) => state.selectedItems.isEmpty,
|
||||||
RefreshIndicator(
|
builder: (context, isSelectedEmpty) => isSelectedEmpty
|
||||||
onRefresh: () async {
|
? const DoubleTapExitContainer(
|
||||||
_bloc.add(const _ReloadCollections());
|
child: _BodyView(),
|
||||||
await _bloc.stream.first;
|
)
|
||||||
},
|
: PopScope(
|
||||||
child: CustomScrollView(
|
canPop: false,
|
||||||
slivers: [
|
onPopInvoked: (_) {
|
||||||
_BlocBuilder(
|
context.addEvent(const _SetSelectedItems(items: {}));
|
||||||
buildWhen: (previous, current) =>
|
},
|
||||||
previous.selectedItems.isEmpty !=
|
child: const _BodyView(),
|
||||||
current.selectedItems.isEmpty,
|
),
|
||||||
builder: (context, state) => state.selectedItems.isEmpty
|
|
||||||
? const _AppBar()
|
|
||||||
: const _SelectionAppBar(),
|
|
||||||
),
|
|
||||||
const SliverToBoxAdapter(
|
|
||||||
child: SizedBox(height: 8),
|
|
||||||
),
|
|
||||||
_BlocBuilder(
|
|
||||||
buildWhen: (previous, current) =>
|
|
||||||
previous.transformedItems != current.transformedItems ||
|
|
||||||
previous.selectedItems != current.selectedItems,
|
|
||||||
builder: (context, state) => SliverPadding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
|
||||||
sliver: SelectableItemList(
|
|
||||||
maxCrossAxisExtent: 256,
|
|
||||||
childBorderRadius: BorderRadius.zero,
|
|
||||||
indicatorAlignment: const Alignment(-.92, -.92),
|
|
||||||
items: state.transformedItems,
|
|
||||||
itemBuilder: (_, __, item) {
|
|
||||||
return _BlocSelector<int?>(
|
|
||||||
selector: (state) =>
|
|
||||||
state.itemCounts[item.collection.id],
|
|
||||||
builder: (context, itemCount) => _ItemView(
|
|
||||||
account: _bloc.account,
|
|
||||||
item: item,
|
|
||||||
collectionItemCountOverride: itemCount,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
staggeredTileBuilder: (_, __) =>
|
|
||||||
const StaggeredTile.count(1, 1),
|
|
||||||
selectedItems: state.selectedItems,
|
|
||||||
onSelectionChange: (_, selected) {
|
|
||||||
_bloc.add(_SetSelectedItems(items: selected.cast()));
|
|
||||||
},
|
|
||||||
onItemTap: (context, _, item) {
|
|
||||||
Navigator.of(context).pushNamed(
|
|
||||||
CollectionBrowser.routeName,
|
|
||||||
arguments:
|
|
||||||
CollectionBrowserArguments(item.collection),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SliverToBoxAdapter(
|
|
||||||
child: SizedBox(
|
|
||||||
height: AppDimension.of(context).homeBottomAppBarHeight,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Align(
|
|
||||||
alignment: Alignment.bottomCenter,
|
|
||||||
child: NavigationBarBlurFilter(
|
|
||||||
height: AppDimension.of(context).homeBottomAppBarHeight,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
if (getRawPlatform() == NpPlatform.android) {
|
|
||||||
return WillPopScope(
|
|
||||||
onWillPop: () => _onBackButtonPressed(context),
|
|
||||||
child: content,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return content;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<bool> _onBackButtonPressed(BuildContext context) async {
|
|
||||||
if (context.state.selectedItems.isEmpty) {
|
|
||||||
return DoubleTapExitHandler()();
|
|
||||||
} else {
|
|
||||||
context.addEvent(const _SetSelectedItems(items: {}));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
late final _Bloc _bloc = context.read();
|
late final _Bloc _bloc = context.read();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _BodyView extends StatelessWidget {
|
||||||
|
const _BodyView();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Stack(
|
||||||
|
children: [
|
||||||
|
RefreshIndicator(
|
||||||
|
onRefresh: () async {
|
||||||
|
context.addEvent(const _ReloadCollections());
|
||||||
|
await context.bloc.stream.first;
|
||||||
|
},
|
||||||
|
child: CustomScrollView(
|
||||||
|
slivers: [
|
||||||
|
_BlocBuilder(
|
||||||
|
buildWhen: (previous, current) =>
|
||||||
|
previous.selectedItems.isEmpty !=
|
||||||
|
current.selectedItems.isEmpty,
|
||||||
|
builder: (context, state) => state.selectedItems.isEmpty
|
||||||
|
? const _AppBar()
|
||||||
|
: const _SelectionAppBar(),
|
||||||
|
),
|
||||||
|
const SliverToBoxAdapter(
|
||||||
|
child: SizedBox(height: 8),
|
||||||
|
),
|
||||||
|
_BlocBuilder(
|
||||||
|
buildWhen: (previous, current) =>
|
||||||
|
previous.transformedItems != current.transformedItems ||
|
||||||
|
previous.selectedItems != current.selectedItems,
|
||||||
|
builder: (context, state) => SliverPadding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||||
|
sliver: SelectableItemList(
|
||||||
|
maxCrossAxisExtent: 256,
|
||||||
|
childBorderRadius: BorderRadius.zero,
|
||||||
|
indicatorAlignment: const Alignment(-.92, -.92),
|
||||||
|
items: state.transformedItems,
|
||||||
|
itemBuilder: (_, __, item) {
|
||||||
|
return _BlocSelector<int?>(
|
||||||
|
selector: (state) =>
|
||||||
|
state.itemCounts[item.collection.id],
|
||||||
|
builder: (context, itemCount) => _ItemView(
|
||||||
|
account: context.bloc.account,
|
||||||
|
item: item,
|
||||||
|
collectionItemCountOverride: itemCount,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
staggeredTileBuilder: (_, __) =>
|
||||||
|
const StaggeredTile.count(1, 1),
|
||||||
|
selectedItems: state.selectedItems,
|
||||||
|
onSelectionChange: (_, selected) {
|
||||||
|
context
|
||||||
|
.addEvent(_SetSelectedItems(items: selected.cast()));
|
||||||
|
},
|
||||||
|
onItemTap: (context, _, item) {
|
||||||
|
Navigator.of(context).pushNamed(
|
||||||
|
CollectionBrowser.routeName,
|
||||||
|
arguments: CollectionBrowserArguments(item.collection),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SliverToBoxAdapter(
|
||||||
|
child: SizedBox(
|
||||||
|
height: AppDimension.of(context).homeBottomAppBarHeight,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.bottomCenter,
|
||||||
|
child: NavigationBarBlurFilter(
|
||||||
|
height: AppDimension.of(context).homeBottomAppBarHeight,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
typedef _BlocBuilder = BlocBuilder<_Bloc, _State>;
|
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>;
|
||||||
|
|
|
@ -45,9 +45,9 @@ import 'package:nc_photos/theme/dimension.dart';
|
||||||
import 'package:nc_photos/url_launcher_util.dart';
|
import 'package:nc_photos/url_launcher_util.dart';
|
||||||
import 'package:nc_photos/widget/collection_browser.dart';
|
import 'package:nc_photos/widget/collection_browser.dart';
|
||||||
import 'package:nc_photos/widget/collection_picker.dart';
|
import 'package:nc_photos/widget/collection_picker.dart';
|
||||||
|
import 'package:nc_photos/widget/double_tap_exit_container/double_tap_exit_container.dart';
|
||||||
import 'package:nc_photos/widget/file_sharer_dialog.dart';
|
import 'package:nc_photos/widget/file_sharer_dialog.dart';
|
||||||
import 'package:nc_photos/widget/finger_listener.dart';
|
import 'package:nc_photos/widget/finger_listener.dart';
|
||||||
import 'package:nc_photos/widget/handler/double_tap_exit_handler.dart';
|
|
||||||
import 'package:nc_photos/widget/home_app_bar.dart';
|
import 'package:nc_photos/widget/home_app_bar.dart';
|
||||||
import 'package:nc_photos/widget/navigation_bar_blur_filter.dart';
|
import 'package:nc_photos/widget/navigation_bar_blur_filter.dart';
|
||||||
import 'package:nc_photos/widget/network_thumbnail.dart';
|
import 'package:nc_photos/widget/network_thumbnail.dart';
|
||||||
|
@ -64,7 +64,6 @@ import 'package:np_common/object_util.dart';
|
||||||
import 'package:np_common/or_null.dart';
|
import 'package:np_common/or_null.dart';
|
||||||
import 'package:np_datetime/np_datetime.dart';
|
import 'package:np_datetime/np_datetime.dart';
|
||||||
import 'package:np_db/np_db.dart';
|
import 'package:np_db/np_db.dart';
|
||||||
import 'package:np_platform_util/np_platform_util.dart';
|
|
||||||
import 'package:np_ui/np_ui.dart';
|
import 'package:np_ui/np_ui.dart';
|
||||||
import 'package:to_string/to_string.dart';
|
import 'package:to_string/to_string.dart';
|
||||||
import 'package:visibility_detector/visibility_detector.dart';
|
import 'package:visibility_detector/visibility_detector.dart';
|
||||||
|
@ -116,12 +115,12 @@ class _WrappedHomePhotosState extends State<_WrappedHomePhotos> {
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_bloc.add(const _LoadItems());
|
context.addEvent(const _LoadItems());
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final content = VisibilityDetector(
|
return VisibilityDetector(
|
||||||
key: _key,
|
key: _key,
|
||||||
onVisibilityChanged: (info) {
|
onVisibilityChanged: (info) {
|
||||||
final isVisible = info.visibleFraction >= 0.2;
|
final isVisible = info.visibleFraction >= 0.2;
|
||||||
|
@ -177,47 +176,49 @@ class _WrappedHomePhotosState extends State<_WrappedHomePhotos> {
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
child: _BlocSelector<bool>(
|
child: _BlocSelector(
|
||||||
selector: (state) =>
|
selector: (state) => state.selectedItems.isEmpty,
|
||||||
state.files.isEmpty && state.syncProgress != null,
|
builder: (context, isSelectedEmpty) => isSelectedEmpty
|
||||||
builder: (context, isInitialSyncing) {
|
? const DoubleTapExitContainer(
|
||||||
if (isInitialSyncing) {
|
child: _BodyView(),
|
||||||
return const _InitialSyncBody();
|
)
|
||||||
} else {
|
: PopScope(
|
||||||
return Shimmer(
|
canPop: false,
|
||||||
linearGradient: Theme.of(context).photoGridShimmerGradient,
|
onPopInvoked: (_) {
|
||||||
child: const _Body(),
|
context.addEvent(const _SetSelectedItems(items: {}));
|
||||||
);
|
},
|
||||||
}
|
child: const _BodyView(),
|
||||||
},
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
if (getRawPlatform() == NpPlatform.android) {
|
|
||||||
return WillPopScope(
|
|
||||||
onWillPop: () => _onBackButtonPressed(context),
|
|
||||||
child: content,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return content;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> _onBackButtonPressed(BuildContext context) async {
|
|
||||||
if (context.state.selectedItems.isEmpty) {
|
|
||||||
return DoubleTapExitHandler()();
|
|
||||||
} else {
|
|
||||||
context.addEvent(const _SetSelectedItems(items: {}));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
late final _bloc = context.bloc;
|
|
||||||
|
|
||||||
final _key = GlobalKey();
|
final _key = GlobalKey();
|
||||||
bool? _isVisible;
|
bool? _isVisible;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _BodyView extends StatelessWidget {
|
||||||
|
const _BodyView();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return _BlocSelector<bool>(
|
||||||
|
selector: (state) => state.files.isEmpty && state.syncProgress != null,
|
||||||
|
builder: (context, isInitialSyncing) {
|
||||||
|
if (isInitialSyncing) {
|
||||||
|
return const _InitialSyncBody();
|
||||||
|
} else {
|
||||||
|
return Shimmer(
|
||||||
|
linearGradient: Theme.of(context).photoGridShimmerGradient,
|
||||||
|
child: const _Body(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class _InitialSyncBody extends StatelessWidget {
|
class _InitialSyncBody extends StatelessWidget {
|
||||||
const _InitialSyncBody();
|
const _InitialSyncBody();
|
||||||
|
|
||||||
|
@ -521,7 +522,7 @@ typedef _BlocSelector<T> = BlocSelector<_Bloc, _State, T>;
|
||||||
|
|
||||||
extension on BuildContext {
|
extension on BuildContext {
|
||||||
_Bloc get bloc => read<_Bloc>();
|
_Bloc get bloc => read<_Bloc>();
|
||||||
_State get state => bloc.state;
|
// _State get state => bloc.state;
|
||||||
void addEvent(_Event event) => bloc.add(event);
|
void addEvent(_Event event) => bloc.add(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue