mirror of
https://gitlab.com/nkming2/nc-photos.git
synced 2025-02-13 04:56:21 +01:00
Merge branch 'scrollbar-month-label' into dev
This commit is contained in:
commit
cf2e0e4c48
5 changed files with 110 additions and 10 deletions
|
@ -33,6 +33,7 @@ import 'package:nc_photos/platform/features.dart' as features;
|
||||||
import 'package:nc_photos/platform/k.dart' as platform_k;
|
import 'package:nc_photos/platform/k.dart' as platform_k;
|
||||||
import 'package:nc_photos/pref.dart';
|
import 'package:nc_photos/pref.dart';
|
||||||
import 'package:nc_photos/pref_util.dart' as pref_util;
|
import 'package:nc_photos/pref_util.dart' as pref_util;
|
||||||
|
import 'package:visibility_detector/visibility_detector.dart';
|
||||||
|
|
||||||
Future<void> initAppLaunch() async {
|
Future<void> initAppLaunch() async {
|
||||||
if (_hasInitedInThisIsolate) {
|
if (_hasInitedInThisIsolate) {
|
||||||
|
@ -51,6 +52,7 @@ Future<void> initAppLaunch() async {
|
||||||
_initSelfSignedCertManager();
|
_initSelfSignedCertManager();
|
||||||
}
|
}
|
||||||
_initDiContainer();
|
_initDiContainer();
|
||||||
|
_initVisibilityDetector();
|
||||||
|
|
||||||
_hasInitedInThisIsolate = true;
|
_hasInitedInThisIsolate = true;
|
||||||
}
|
}
|
||||||
|
@ -172,6 +174,10 @@ void _initDiContainer() {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _initVisibilityDetector() {
|
||||||
|
VisibilityDetectorController.instance.updateInterval = Duration.zero;
|
||||||
|
}
|
||||||
|
|
||||||
final _log = Logger("app_init");
|
final _log = Logger("app_init");
|
||||||
var _hasInitedInThisIsolate = false;
|
var _hasInitedInThisIsolate = false;
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
|
import 'dart:collection';
|
||||||
import 'dart:math' as math;
|
import 'dart:math' as math;
|
||||||
import 'dart:ui';
|
import 'dart:ui';
|
||||||
|
|
||||||
|
import 'package:collection/collection.dart';
|
||||||
import 'package:draggable_scrollbar/draggable_scrollbar.dart';
|
import 'package:draggable_scrollbar/draggable_scrollbar.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
import 'package:kiwi/kiwi.dart';
|
import 'package:kiwi/kiwi.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:nc_photos/account.dart';
|
import 'package:nc_photos/account.dart';
|
||||||
|
@ -31,6 +34,7 @@ import 'package:nc_photos/service.dart' as service;
|
||||||
import 'package:nc_photos/share_handler.dart';
|
import 'package:nc_photos/share_handler.dart';
|
||||||
import 'package:nc_photos/snack_bar_manager.dart';
|
import 'package:nc_photos/snack_bar_manager.dart';
|
||||||
import 'package:nc_photos/theme.dart';
|
import 'package:nc_photos/theme.dart';
|
||||||
|
import 'package:nc_photos/throttler.dart';
|
||||||
import 'package:nc_photos/use_case/sync_favorite.dart';
|
import 'package:nc_photos/use_case/sync_favorite.dart';
|
||||||
import 'package:nc_photos/widget/album_browser_util.dart' as album_browser_util;
|
import 'package:nc_photos/widget/album_browser_util.dart' as album_browser_util;
|
||||||
import 'package:nc_photos/widget/builder/photo_list_item_builder.dart';
|
import 'package:nc_photos/widget/builder/photo_list_item_builder.dart';
|
||||||
|
@ -46,6 +50,7 @@ import 'package:nc_photos/widget/selection_app_bar.dart';
|
||||||
import 'package:nc_photos/widget/settings.dart';
|
import 'package:nc_photos/widget/settings.dart';
|
||||||
import 'package:nc_photos/widget/viewer.dart';
|
import 'package:nc_photos/widget/viewer.dart';
|
||||||
import 'package:nc_photos/widget/zoom_menu_button.dart';
|
import 'package:nc_photos/widget/zoom_menu_button.dart';
|
||||||
|
import 'package:visibility_detector/visibility_detector.dart';
|
||||||
|
|
||||||
class HomePhotos extends StatefulWidget {
|
class HomePhotos extends StatefulWidget {
|
||||||
const HomePhotos({
|
const HomePhotos({
|
||||||
|
@ -105,6 +110,17 @@ class _HomePhotosState extends State<HomePhotos>
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
onVisibilityChanged(VisibilityInfo info, int index, SelectableItem item) {
|
||||||
|
if (info.visibleFraction >= 0.2) {
|
||||||
|
_visibleItems.add(_VisibleItem(index, item));
|
||||||
|
} else {
|
||||||
|
_visibleItems.remove(_VisibleItem(index, item));
|
||||||
|
}
|
||||||
|
_visibilityThrottler.trigger(
|
||||||
|
maxResponceTime: const Duration(milliseconds: 500));
|
||||||
|
}
|
||||||
|
|
||||||
void _initBloc() {
|
void _initBloc() {
|
||||||
if (_bloc.state is ScanAccountDirBlocInit) {
|
if (_bloc.state is ScanAccountDirBlocInit) {
|
||||||
_log.info("[_initBloc] Initialize bloc");
|
_log.info("[_initBloc] Initialize bloc");
|
||||||
|
@ -138,6 +154,8 @@ class _HomePhotosState extends State<HomePhotos>
|
||||||
// status bar + app bar
|
// status bar + app bar
|
||||||
topOffset: _calcAppBarExtent(context),
|
topOffset: _calcAppBarExtent(context),
|
||||||
bottomOffset: _calcBottomAppBarExtent(context),
|
bottomOffset: _calcBottomAppBarExtent(context),
|
||||||
|
labelTextBuilder: (_) => _buildScrollLabel(context),
|
||||||
|
labelPadding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
child: ScrollConfiguration(
|
child: ScrollConfiguration(
|
||||||
behavior: ScrollConfiguration.of(context)
|
behavior: ScrollConfiguration.of(context)
|
||||||
.copyWith(scrollbars: false),
|
.copyWith(scrollbars: false),
|
||||||
|
@ -163,6 +181,7 @@ class _HomePhotosState extends State<HomePhotos>
|
||||||
_itemListMaxExtent = value;
|
_itemListMaxExtent = value;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
isEnableVisibilityCallback: true,
|
||||||
),
|
),
|
||||||
SliverToBoxAdapter(
|
SliverToBoxAdapter(
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
|
@ -318,6 +337,32 @@ class _HomePhotosState extends State<HomePhotos>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _buildScrollLabel(BuildContext context) {
|
||||||
|
final date = _visibleItems
|
||||||
|
.sorted()
|
||||||
|
.firstWhereOrNull((e) => e.item is PhotoListFileItem)
|
||||||
|
?.item
|
||||||
|
.as<PhotoListFileItem>()
|
||||||
|
?.file
|
||||||
|
.bestDateTime;
|
||||||
|
if (date != null) {
|
||||||
|
final text = DateFormat(DateFormat.YEAR_ABBR_MONTH,
|
||||||
|
Localizations.localeOf(context).languageCode)
|
||||||
|
.format(date.toLocal());
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||||
|
child: Text(
|
||||||
|
text,
|
||||||
|
style: const TextStyle(
|
||||||
|
color: AppTheme.primaryTextColorLight,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return const SizedBox();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void _onStateChange(BuildContext context, ScanAccountDirBlocState state) {
|
void _onStateChange(BuildContext context, ScanAccountDirBlocState state) {
|
||||||
if (state is ScanAccountDirBlocInit) {
|
if (state is ScanAccountDirBlocInit) {
|
||||||
itemStreamListItems = [];
|
itemStreamListItems = [];
|
||||||
|
@ -640,6 +685,16 @@ class _HomePhotosState extends State<HomePhotos>
|
||||||
|
|
||||||
double? _itemListMaxExtent;
|
double? _itemListMaxExtent;
|
||||||
|
|
||||||
|
final _visibleItems = HashSet<_VisibleItem>();
|
||||||
|
late final _visibilityThrottler = Throttler(onTriggered: (_) {
|
||||||
|
// label text is always 1 frame behind, so we need to update the text for
|
||||||
|
// the last frame
|
||||||
|
if (mounted) {
|
||||||
|
_log.fine("[_visibilityThrottler] Update screen");
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
late final _prefUpdatedListener =
|
late final _prefUpdatedListener =
|
||||||
AppEventListener<PrefUpdatedEvent>(_onPrefUpdated);
|
AppEventListener<PrefUpdatedEvent>(_onPrefUpdated);
|
||||||
|
|
||||||
|
@ -946,3 +1001,19 @@ enum _SelectionMenuOption {
|
||||||
delete,
|
delete,
|
||||||
download,
|
download,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _VisibleItem implements Comparable<_VisibleItem> {
|
||||||
|
const _VisibleItem(this.index, this.item);
|
||||||
|
|
||||||
|
@override
|
||||||
|
operator ==(Object other) => other is _VisibleItem && other.index == index;
|
||||||
|
|
||||||
|
@override
|
||||||
|
compareTo(_VisibleItem other) => index.compareTo(other.index);
|
||||||
|
|
||||||
|
@override
|
||||||
|
get hashCode => index.hashCode;
|
||||||
|
|
||||||
|
final int index;
|
||||||
|
final SelectableItem item;
|
||||||
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ import 'package:nc_photos/session_storage.dart';
|
||||||
import 'package:nc_photos/snack_bar_manager.dart';
|
import 'package:nc_photos/snack_bar_manager.dart';
|
||||||
import 'package:nc_photos/widget/measurable_item_list.dart';
|
import 'package:nc_photos/widget/measurable_item_list.dart';
|
||||||
import 'package:nc_photos/widget/selectable.dart';
|
import 'package:nc_photos/widget/selectable.dart';
|
||||||
|
import 'package:visibility_detector/visibility_detector.dart';
|
||||||
|
|
||||||
abstract class SelectableItem {
|
abstract class SelectableItem {
|
||||||
const SelectableItem();
|
const SelectableItem();
|
||||||
|
@ -33,6 +34,10 @@ mixin SelectableItemStreamListMixin<T extends StatefulWidget> on State<T> {
|
||||||
@protected
|
@protected
|
||||||
void onItemTap(SelectableItem item, int index);
|
void onItemTap(SelectableItem item, int index);
|
||||||
|
|
||||||
|
@protected
|
||||||
|
void onVisibilityChanged(
|
||||||
|
VisibilityInfo info, int index, SelectableItem item) {}
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
Widget buildItemStreamListOuter(
|
Widget buildItemStreamListOuter(
|
||||||
BuildContext context, {
|
BuildContext context, {
|
||||||
|
@ -57,6 +62,7 @@ mixin SelectableItemStreamListMixin<T extends StatefulWidget> on State<T> {
|
||||||
required double maxCrossAxisExtent,
|
required double maxCrossAxisExtent,
|
||||||
double mainAxisSpacing = 0,
|
double mainAxisSpacing = 0,
|
||||||
ValueChanged<double?>? onMaxExtentChanged,
|
ValueChanged<double?>? onMaxExtentChanged,
|
||||||
|
bool isEnableVisibilityCallback = false,
|
||||||
}) {
|
}) {
|
||||||
final Widget content;
|
final Widget content;
|
||||||
if (onMaxExtentChanged != null) {
|
if (onMaxExtentChanged != null) {
|
||||||
|
@ -64,7 +70,8 @@ mixin SelectableItemStreamListMixin<T extends StatefulWidget> on State<T> {
|
||||||
key: _listKey,
|
key: _listKey,
|
||||||
maxCrossAxisExtent: maxCrossAxisExtent,
|
maxCrossAxisExtent: maxCrossAxisExtent,
|
||||||
itemCount: _items.length,
|
itemCount: _items.length,
|
||||||
itemBuilder: _buildItem,
|
itemBuilder: (context, i) =>
|
||||||
|
_buildItem(context, i, isEnableVisibilityCallback),
|
||||||
staggeredTileBuilder: (index) => _items[index].staggeredTile,
|
staggeredTileBuilder: (index) => _items[index].staggeredTile,
|
||||||
mainAxisSpacing: mainAxisSpacing,
|
mainAxisSpacing: mainAxisSpacing,
|
||||||
onMaxExtentChanged: onMaxExtentChanged,
|
onMaxExtentChanged: onMaxExtentChanged,
|
||||||
|
@ -74,7 +81,8 @@ mixin SelectableItemStreamListMixin<T extends StatefulWidget> on State<T> {
|
||||||
key: ObjectKey(maxCrossAxisExtent),
|
key: ObjectKey(maxCrossAxisExtent),
|
||||||
maxCrossAxisExtent: maxCrossAxisExtent,
|
maxCrossAxisExtent: maxCrossAxisExtent,
|
||||||
itemCount: _items.length,
|
itemCount: _items.length,
|
||||||
itemBuilder: _buildItem,
|
itemBuilder: (context, i) =>
|
||||||
|
_buildItem(context, i, isEnableVisibilityCallback),
|
||||||
staggeredTileBuilder: (index) => _items[index].staggeredTile,
|
staggeredTileBuilder: (index) => _items[index].staggeredTile,
|
||||||
mainAxisSpacing: mainAxisSpacing,
|
mainAxisSpacing: mainAxisSpacing,
|
||||||
);
|
);
|
||||||
|
@ -127,11 +135,12 @@ mixin SelectableItemStreamListMixin<T extends StatefulWidget> on State<T> {
|
||||||
?.updateListHeight());
|
?.updateListHeight());
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildItem(BuildContext context, int index) {
|
Widget _buildItem(
|
||||||
|
BuildContext context, int index, bool isEnableVisibilityCallback) {
|
||||||
final item = _items[index];
|
final item = _items[index];
|
||||||
final content = item.buildWidget(context);
|
Widget content = item.buildWidget(context);
|
||||||
if (item.isSelectable) {
|
if (item.isSelectable) {
|
||||||
return Selectable(
|
content = Selectable(
|
||||||
isSelected: _selectedItems.contains(item),
|
isSelected: _selectedItems.contains(item),
|
||||||
iconSize: 32,
|
iconSize: 32,
|
||||||
onTap: () => _onItemTap(item, index),
|
onTap: () => _onItemTap(item, index),
|
||||||
|
@ -140,9 +149,15 @@ mixin SelectableItemStreamListMixin<T extends StatefulWidget> on State<T> {
|
||||||
: () => _onItemLongPress(item, index),
|
: () => _onItemLongPress(item, index),
|
||||||
child: content,
|
child: content,
|
||||||
);
|
);
|
||||||
} else {
|
|
||||||
return content;
|
|
||||||
}
|
}
|
||||||
|
if (isEnableVisibilityCallback) {
|
||||||
|
content = VisibilityDetector(
|
||||||
|
key: Key("$index"),
|
||||||
|
child: content,
|
||||||
|
onVisibilityChanged: (info) => onVisibilityChanged(info, index, item),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return content;
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onItemTap(SelectableItem item, int index) {
|
void _onItemTap(SelectableItem item, int index) {
|
||||||
|
|
|
@ -294,8 +294,8 @@ packages:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
path: "."
|
path: "."
|
||||||
ref: "v0.1.0-nc-photos-3"
|
ref: "v0.1.0-nc-photos-5"
|
||||||
resolved-ref: "805fb925a6129f693abaa8f30ebb11900543ffc8"
|
resolved-ref: a1a34c57a069dc1cffa9759f86ffab2e0c7420ca
|
||||||
url: "https://gitlab.com/nc-photos/flutter-draggable-scrollbar"
|
url: "https://gitlab.com/nc-photos/flutter-draggable-scrollbar"
|
||||||
source: git
|
source: git
|
||||||
version: "0.1.0"
|
version: "0.1.0"
|
||||||
|
@ -1199,6 +1199,13 @@ packages:
|
||||||
url: "https://gitlab.com/nc-photos/flutter-plugins"
|
url: "https://gitlab.com/nc-photos/flutter-plugins"
|
||||||
source: git
|
source: git
|
||||||
version: "2.0.4"
|
version: "2.0.4"
|
||||||
|
visibility_detector:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: visibility_detector
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.2.2"
|
||||||
vm_service:
|
vm_service:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
|
@ -43,7 +43,7 @@ dependencies:
|
||||||
draggable_scrollbar:
|
draggable_scrollbar:
|
||||||
git:
|
git:
|
||||||
url: https://gitlab.com/nc-photos/flutter-draggable-scrollbar
|
url: https://gitlab.com/nc-photos/flutter-draggable-scrollbar
|
||||||
ref: v0.1.0-nc-photos-3
|
ref: v0.1.0-nc-photos-5
|
||||||
equatable: ^2.0.0
|
equatable: ^2.0.0
|
||||||
event_bus: ^2.0.0
|
event_bus: ^2.0.0
|
||||||
exifdart:
|
exifdart:
|
||||||
|
@ -97,6 +97,7 @@ dependencies:
|
||||||
url: https://gitlab.com/nc-photos/flutter-plugins
|
url: https://gitlab.com/nc-photos/flutter-plugins
|
||||||
ref: video_player-v2.2.6-nc-photos-2
|
ref: video_player-v2.2.6-nc-photos-2
|
||||||
path: packages/video_player/video_player
|
path: packages/video_player/video_player
|
||||||
|
visibility_detector: ^0.2.2
|
||||||
wakelock: ^0.5.2
|
wakelock: ^0.5.2
|
||||||
woozy_search: ^2.0.3
|
woozy_search: ^2.0.3
|
||||||
xml: ^5.0.2
|
xml: ^5.0.2
|
||||||
|
|
Loading…
Reference in a new issue