mirror of
https://gitlab.com/nkming2/nc-photos.git
synced 2025-01-22 16:56:19 +01:00
Extract photo item list
This commit is contained in:
parent
d729834185
commit
f191317627
3 changed files with 146 additions and 81 deletions
|
@ -94,16 +94,6 @@ class _HomePhotosState extends State<HomePhotos>
|
||||||
|
|
||||||
Widget _buildContent(BuildContext context, ScanDirBlocState state) {
|
Widget _buildContent(BuildContext context, ScanDirBlocState state) {
|
||||||
return LayoutBuilder(builder: (context, constraints) {
|
return LayoutBuilder(builder: (context, constraints) {
|
||||||
if (_prevListWidth == null) {
|
|
||||||
_prevListWidth = constraints.maxWidth;
|
|
||||||
}
|
|
||||||
if (constraints.maxWidth != _prevListWidth) {
|
|
||||||
_log.info(
|
|
||||||
"[_buildContent] updateListHeight: list viewport width changed");
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) => updateListHeight());
|
|
||||||
_prevListWidth = constraints.maxWidth;
|
|
||||||
}
|
|
||||||
|
|
||||||
final scrollExtent = _getScrollViewExtent(constraints);
|
final scrollExtent = _getScrollViewExtent(constraints);
|
||||||
return Stack(
|
return Stack(
|
||||||
children: [
|
children: [
|
||||||
|
@ -583,7 +573,6 @@ class _HomePhotosState extends State<HomePhotos>
|
||||||
|
|
||||||
final ScrollController _scrollController = ScrollController();
|
final ScrollController _scrollController = ScrollController();
|
||||||
|
|
||||||
double _prevListWidth;
|
|
||||||
double _appBarExtent;
|
double _appBarExtent;
|
||||||
|
|
||||||
static final _log = Logger("widget.home_photos._HomePhotosState");
|
static final _log = Logger("widget.home_photos._HomePhotosState");
|
||||||
|
|
134
lib/widget/measurable_item_list.dart
Normal file
134
lib/widget/measurable_item_list.dart
Normal file
|
@ -0,0 +1,134 @@
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
|
||||||
|
import 'package:logging/logging.dart';
|
||||||
|
import 'package:nc_photos/widget/measureable_sliver_staggered_grid.dart';
|
||||||
|
import 'package:uuid/uuid.dart';
|
||||||
|
|
||||||
|
abstract class MeasurableItemListState {
|
||||||
|
void updateListHeight();
|
||||||
|
}
|
||||||
|
|
||||||
|
class MeasurableItemList extends StatefulWidget {
|
||||||
|
MeasurableItemList({
|
||||||
|
Key key,
|
||||||
|
@required this.maxCrossAxisExtent,
|
||||||
|
@required this.itemCount,
|
||||||
|
@required this.itemBuilder,
|
||||||
|
@required this.staggeredTileBuilder,
|
||||||
|
this.onMaxExtentChanged,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
createState() => _MeasurableItemListState();
|
||||||
|
|
||||||
|
final double maxCrossAxisExtent;
|
||||||
|
final int itemCount;
|
||||||
|
final IndexedWidgetBuilder itemBuilder;
|
||||||
|
final IndexedStaggeredTileBuilder staggeredTileBuilder;
|
||||||
|
final ValueChanged<double> onMaxExtentChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MeasurableItemListState extends State<MeasurableItemList>
|
||||||
|
with WidgetsBindingObserver
|
||||||
|
implements MeasurableItemListState {
|
||||||
|
@override
|
||||||
|
initState() {
|
||||||
|
super.initState();
|
||||||
|
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
_prevOrientation = MediaQuery.of(context).orientation;
|
||||||
|
WidgetsBinding.instance.addObserver(this);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
dispose() {
|
||||||
|
WidgetsBinding.instance.removeObserver(this);
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
didChangeMetrics() {
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
final orientation = MediaQuery.of(context).orientation;
|
||||||
|
if (orientation != _prevOrientation) {
|
||||||
|
_log.info(
|
||||||
|
"[didChangeMetrics] updateListHeight: orientation changed: $orientation");
|
||||||
|
_prevOrientation = orientation;
|
||||||
|
updateListHeight();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
build(BuildContext context) {
|
||||||
|
return SliverLayoutBuilder(builder: (context, constraints) {
|
||||||
|
if (_prevListWidth == null) {
|
||||||
|
_prevListWidth = constraints.crossAxisExtent;
|
||||||
|
}
|
||||||
|
if (constraints.crossAxisExtent != _prevListWidth) {
|
||||||
|
_log.info("[build] updateListHeight: list viewport width changed");
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) => updateListHeight());
|
||||||
|
_prevListWidth = constraints.crossAxisExtent;
|
||||||
|
}
|
||||||
|
|
||||||
|
// need to rebuild grid after cell size changed
|
||||||
|
final cellSize = widget.maxCrossAxisExtent;
|
||||||
|
if (cellSize != _prevCellSize) {
|
||||||
|
_log.info("[build] updateListHeight: cell size changed");
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) => updateListHeight());
|
||||||
|
_prevCellSize = cellSize;
|
||||||
|
}
|
||||||
|
_gridKey = _GridKey("$_uniqueToken $cellSize");
|
||||||
|
return MeasurableSliverStaggeredGrid.extentBuilder(
|
||||||
|
key: _gridKey,
|
||||||
|
maxCrossAxisExtent: widget.maxCrossAxisExtent,
|
||||||
|
itemCount: widget.itemCount,
|
||||||
|
itemBuilder: widget.itemBuilder,
|
||||||
|
staggeredTileBuilder: widget.staggeredTileBuilder,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
updateListHeight() {
|
||||||
|
double newMaxExtent;
|
||||||
|
try {
|
||||||
|
final renderObj = _gridKey.currentContext.findRenderObject()
|
||||||
|
as RenderMeasurableSliverStaggeredGrid;
|
||||||
|
final maxExtent = renderObj.calculateExtent();
|
||||||
|
_log.info("[updateListHeight] Max extent: $maxExtent");
|
||||||
|
if (maxExtent == 0) {
|
||||||
|
// ?
|
||||||
|
newMaxExtent = null;
|
||||||
|
} else {
|
||||||
|
newMaxExtent = maxExtent;
|
||||||
|
}
|
||||||
|
} catch (e, stacktrace) {
|
||||||
|
_log.shout("[updateListHeight] Failed while calculateMaxScrollExtent", e,
|
||||||
|
stacktrace);
|
||||||
|
newMaxExtent = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newMaxExtent != _maxExtent) {
|
||||||
|
_maxExtent = newMaxExtent;
|
||||||
|
widget.onMaxExtentChanged?.call(newMaxExtent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
double _prevListWidth;
|
||||||
|
double _prevCellSize;
|
||||||
|
double _maxExtent;
|
||||||
|
Orientation _prevOrientation;
|
||||||
|
|
||||||
|
// this unique token is there to keep the global key unique
|
||||||
|
final _uniqueToken = Uuid().v4();
|
||||||
|
GlobalObjectKey _gridKey;
|
||||||
|
|
||||||
|
static final _log =
|
||||||
|
Logger("widget.measurable_item_list._MeasurableItemListState");
|
||||||
|
}
|
||||||
|
|
||||||
|
class _GridKey extends GlobalObjectKey {
|
||||||
|
const _GridKey(Object value) : super(value);
|
||||||
|
}
|
|
@ -12,8 +12,7 @@ import 'package:nc_photos/platform/k.dart' as platform_k;
|
||||||
import 'package:nc_photos/session_storage.dart';
|
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/theme.dart';
|
import 'package:nc_photos/theme.dart';
|
||||||
import 'package:nc_photos/widget/measureable_sliver_staggered_grid.dart';
|
import 'package:nc_photos/widget/measurable_item_list.dart';
|
||||||
import 'package:uuid/uuid.dart';
|
|
||||||
|
|
||||||
abstract class SelectableItemStreamListItem {
|
abstract class SelectableItemStreamListItem {
|
||||||
const SelectableItemStreamListItem({
|
const SelectableItemStreamListItem({
|
||||||
|
@ -29,36 +28,11 @@ abstract class SelectableItemStreamListItem {
|
||||||
final StaggeredTile staggeredTile;
|
final StaggeredTile staggeredTile;
|
||||||
}
|
}
|
||||||
|
|
||||||
mixin SelectableItemStreamListMixin<T extends StatefulWidget>
|
mixin SelectableItemStreamListMixin<T extends StatefulWidget> on State<T> {
|
||||||
on State<T>, WidgetsBindingObserver {
|
|
||||||
@override
|
@override
|
||||||
initState() {
|
initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_keyboardFocus.requestFocus();
|
_keyboardFocus.requestFocus();
|
||||||
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
||||||
_prevOrientation = MediaQuery.of(context).orientation;
|
|
||||||
WidgetsBinding.instance.addObserver(this);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
dispose() {
|
|
||||||
WidgetsBinding.instance.removeObserver(this);
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
didChangeMetrics() {
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
||||||
final orientation = MediaQuery.of(context).orientation;
|
|
||||||
if (orientation != _prevOrientation) {
|
|
||||||
_log.info(
|
|
||||||
"[didChangeMetrics] updateListHeight: orientation changed: $orientation");
|
|
||||||
_prevOrientation = orientation;
|
|
||||||
updateListHeight();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
|
@ -82,20 +56,16 @@ mixin SelectableItemStreamListMixin<T extends StatefulWidget>
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
Widget buildItemStreamList(BuildContext context) {
|
Widget buildItemStreamList(BuildContext context) {
|
||||||
// need to rebuild grid after cell size changed
|
return MeasurableItemList(
|
||||||
final cellSize = itemStreamListCellSize;
|
key: _listKey,
|
||||||
if (cellSize != _prevItemStreamListCellSize) {
|
|
||||||
_log.info("[buildItemStreamList] updateListHeight: cell size changed");
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) => updateListHeight());
|
|
||||||
_prevItemStreamListCellSize = cellSize;
|
|
||||||
}
|
|
||||||
_gridKey = _GridKey("$_uniqueToken $cellSize");
|
|
||||||
return MeasurableSliverStaggeredGrid.extentBuilder(
|
|
||||||
key: _gridKey,
|
|
||||||
maxCrossAxisExtent: itemStreamListCellSize.toDouble(),
|
maxCrossAxisExtent: itemStreamListCellSize.toDouble(),
|
||||||
itemCount: _items.length,
|
itemCount: _items.length,
|
||||||
itemBuilder: _buildItem,
|
itemBuilder: _buildItem,
|
||||||
staggeredTileBuilder: (index) => _items[index].staggeredTile,
|
staggeredTileBuilder: (index) => _items[index].staggeredTile,
|
||||||
|
onMaxExtentChanged: (newExtent) {
|
||||||
|
_calculatedMaxExtent = newExtent;
|
||||||
|
onMaxExtentChanged(newExtent);
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,27 +77,6 @@ mixin SelectableItemStreamListMixin<T extends StatefulWidget>
|
||||||
_selectedItems.clear();
|
_selectedItems.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
@protected
|
|
||||||
void updateListHeight() {
|
|
||||||
try {
|
|
||||||
final renderObj = _gridKey.currentContext.findRenderObject()
|
|
||||||
as RenderMeasurableSliverStaggeredGrid;
|
|
||||||
final maxExtent = renderObj.calculateExtent();
|
|
||||||
_log.info("[updateListHeight] Max extent: $maxExtent");
|
|
||||||
if (maxExtent == 0) {
|
|
||||||
// ?
|
|
||||||
_calculatedMaxExtent = null;
|
|
||||||
} else {
|
|
||||||
_calculatedMaxExtent = maxExtent;
|
|
||||||
onMaxExtentChanged(maxExtent);
|
|
||||||
}
|
|
||||||
} catch (e, stacktrace) {
|
|
||||||
_log.shout("[updateListHeight] Failed while calculateMaxScrollExtent", e,
|
|
||||||
stacktrace);
|
|
||||||
_calculatedMaxExtent = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
bool get isSelectionMode => _selectedItems.isNotEmpty;
|
bool get isSelectionMode => _selectedItems.isNotEmpty;
|
||||||
|
|
||||||
|
@ -160,7 +109,8 @@ mixin SelectableItemStreamListMixin<T extends StatefulWidget>
|
||||||
_lastSelectPosition = newLastSelectPosition;
|
_lastSelectPosition = newLastSelectPosition;
|
||||||
|
|
||||||
_log.info("[itemStreamListItems] updateListHeight: list item changed");
|
_log.info("[itemStreamListItems] updateListHeight: list item changed");
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) => updateListHeight());
|
WidgetsBinding.instance.addPostFrameCallback((_) =>
|
||||||
|
(_listKey.currentState as MeasurableItemListState)?.updateListHeight());
|
||||||
}
|
}
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
|
@ -281,16 +231,12 @@ mixin SelectableItemStreamListMixin<T extends StatefulWidget>
|
||||||
|
|
||||||
int _lastSelectPosition;
|
int _lastSelectPosition;
|
||||||
bool _isRangeSelectionMode = false;
|
bool _isRangeSelectionMode = false;
|
||||||
int _prevItemStreamListCellSize;
|
|
||||||
double _calculatedMaxExtent;
|
|
||||||
Orientation _prevOrientation;
|
|
||||||
|
|
||||||
final _items = <SelectableItemStreamListItem>[];
|
final _items = <SelectableItemStreamListItem>[];
|
||||||
final _selectedItems = <SelectableItemStreamListItem>{};
|
final _selectedItems = <SelectableItemStreamListItem>{};
|
||||||
|
|
||||||
// this unique token is there to keep the global key unique
|
final _listKey = GlobalKey();
|
||||||
final _uniqueToken = Uuid().v4();
|
double _calculatedMaxExtent;
|
||||||
GlobalObjectKey _gridKey;
|
|
||||||
|
|
||||||
/// used to gain focus on web for keyboard support
|
/// used to gain focus on web for keyboard support
|
||||||
final _keyboardFocus = FocusNode();
|
final _keyboardFocus = FocusNode();
|
||||||
|
@ -299,10 +245,6 @@ mixin SelectableItemStreamListMixin<T extends StatefulWidget>
|
||||||
"widget.selectable_item_stream_list_mixin.SelectableItemStreamListMixin");
|
"widget.selectable_item_stream_list_mixin.SelectableItemStreamListMixin");
|
||||||
}
|
}
|
||||||
|
|
||||||
class _GridKey extends GlobalObjectKey {
|
|
||||||
const _GridKey(Object value) : super(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
class _SelectableItemWidget extends StatelessWidget {
|
class _SelectableItemWidget extends StatelessWidget {
|
||||||
_SelectableItemWidget({
|
_SelectableItemWidget({
|
||||||
Key key,
|
Key key,
|
||||||
|
|
Loading…
Reference in a new issue