mirror of
https://gitlab.com/nkming2/nc-photos.git
synced 2025-02-02 06:46:22 +01:00
(web) Shift+click to select range
This commit is contained in:
parent
17964ed695
commit
4ad0b673fc
4 changed files with 129 additions and 10 deletions
|
@ -356,6 +356,10 @@
|
|||
"@previousTooltip": {
|
||||
"description": "Tooltip of the previous button"
|
||||
},
|
||||
"webSelectRangeNotification": "Hold shift + click to select all in between",
|
||||
"@webSelectRangeNotification": {
|
||||
"description": "Inform web user how to select items in range"
|
||||
},
|
||||
"changelogTitle": "Changelog",
|
||||
"@changelogTitle": {
|
||||
"description": "Title of the changelog dialog"
|
||||
|
|
13
lib/session_storage.dart
Normal file
13
lib/session_storage.dart
Normal file
|
@ -0,0 +1,13 @@
|
|||
/// Hold non-persisted global variables
|
||||
class SessionStorage {
|
||||
factory SessionStorage() {
|
||||
return _inst;
|
||||
}
|
||||
|
||||
SessionStorage._();
|
||||
|
||||
/// Whether the range select notification has been shown to user
|
||||
bool hasShowRangeSelectNotification = false;
|
||||
|
||||
static SessionStorage _inst = SessionStorage._();
|
||||
}
|
|
@ -1,4 +1,7 @@
|
|||
import 'dart:math' as math;
|
||||
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
@ -15,6 +18,7 @@ import 'package:nc_photos/iterable_extension.dart';
|
|||
import 'package:nc_photos/k.dart' as k;
|
||||
import 'package:nc_photos/list_extension.dart';
|
||||
import 'package:nc_photos/pref.dart';
|
||||
import 'package:nc_photos/session_storage.dart';
|
||||
import 'package:nc_photos/snack_bar_manager.dart';
|
||||
import 'package:nc_photos/theme.dart';
|
||||
import 'package:nc_photos/use_case/update_album.dart';
|
||||
|
@ -61,13 +65,16 @@ class _AlbumViewerState extends State<AlbumViewer>
|
|||
_transformItems();
|
||||
_initCover();
|
||||
_thumbZoomLevel = Pref.inst().getAlbumViewerZoomLevel(0);
|
||||
_keyboardFocus.requestFocus();
|
||||
}
|
||||
|
||||
@override
|
||||
build(BuildContext context) {
|
||||
return AppTheme(
|
||||
child: Scaffold(
|
||||
body: Builder(builder: (context) => _buildContent(context)),
|
||||
body: Builder(
|
||||
builder: (context) =>
|
||||
kIsWeb ? _buildWebContent(context) : _buildContent(context)),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -84,6 +91,18 @@ class _AlbumViewerState extends State<AlbumViewer>
|
|||
} catch (_) {}
|
||||
}
|
||||
|
||||
Widget _buildWebContent(BuildContext context) {
|
||||
assert(kIsWeb);
|
||||
// support switching pages with keyboard on web
|
||||
return RawKeyboardListener(
|
||||
onKey: (ev) {
|
||||
_isRangeSelectionMode = ev.isShiftPressed;
|
||||
},
|
||||
focusNode: _keyboardFocus,
|
||||
child: _buildContent(context),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildContent(BuildContext context) {
|
||||
return Theme(
|
||||
data: Theme.of(context).copyWith(
|
||||
|
@ -231,12 +250,23 @@ class _AlbumViewerState extends State<AlbumViewer>
|
|||
// unselect
|
||||
setState(() {
|
||||
_selectedItems.remove(item);
|
||||
_lastSelectPosition = null;
|
||||
});
|
||||
} else {
|
||||
// select
|
||||
setState(() {
|
||||
_selectedItems.add(item);
|
||||
});
|
||||
if (_isRangeSelectionMode && _lastSelectPosition != null) {
|
||||
setState(() {
|
||||
_selectedItems.addAll(_items.sublist(
|
||||
math.min(_lastSelectPosition, index),
|
||||
math.max(_lastSelectPosition, index) + 1));
|
||||
_lastSelectPosition = index;
|
||||
});
|
||||
} else {
|
||||
setState(() {
|
||||
_lastSelectPosition = index;
|
||||
_selectedItems.add(item);
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Navigator.pushNamed(context, Viewer.routeName,
|
||||
|
@ -246,8 +276,17 @@ class _AlbumViewerState extends State<AlbumViewer>
|
|||
|
||||
void _onItemLongPress(_GridItem item, int index) {
|
||||
setState(() {
|
||||
_lastSelectPosition = index;
|
||||
_selectedItems.add(item);
|
||||
});
|
||||
|
||||
if (kIsWeb && !SessionStorage().hasShowRangeSelectNotification) {
|
||||
SnackBarManager().showSnackBar(SnackBar(
|
||||
content: Text(AppLocalizations.of(context).webSelectRangeNotification),
|
||||
duration: k.snackBarDurationNormal,
|
||||
));
|
||||
SessionStorage().hasShowRangeSelectNotification = true;
|
||||
}
|
||||
}
|
||||
|
||||
void _onSelectionAppBarRemovePressed() {
|
||||
|
@ -329,6 +368,8 @@ class _AlbumViewerState extends State<AlbumViewer>
|
|||
}
|
||||
|
||||
bool get _isSelectionMode => _selectedItems.isNotEmpty;
|
||||
int _lastSelectPosition;
|
||||
bool _isRangeSelectionMode = false;
|
||||
|
||||
Album _album;
|
||||
final _items = <_GridItem>[];
|
||||
|
@ -337,7 +378,10 @@ class _AlbumViewerState extends State<AlbumViewer>
|
|||
String _coverPreviewUrl;
|
||||
var _thumbZoomLevel = 0;
|
||||
|
||||
final _selectedItems = <_GridItem>[];
|
||||
final _selectedItems = <_GridItem>{};
|
||||
|
||||
/// used to gain focus on web for keyboard support
|
||||
final _keyboardFocus = FocusNode();
|
||||
|
||||
static final _log = Logger("widget.album_viewer._AlbumViewerState");
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import 'dart:math' as math;
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
@ -18,6 +20,7 @@ import 'package:nc_photos/iterable_extension.dart';
|
|||
import 'package:nc_photos/k.dart' as k;
|
||||
import 'package:nc_photos/metadata_task_manager.dart';
|
||||
import 'package:nc_photos/pref.dart';
|
||||
import 'package:nc_photos/session_storage.dart';
|
||||
import 'package:nc_photos/snack_bar_manager.dart';
|
||||
import 'package:nc_photos/theme.dart';
|
||||
import 'package:nc_photos/use_case/remove.dart';
|
||||
|
@ -46,6 +49,7 @@ class _HomePhotosState extends State<HomePhotos> {
|
|||
super.initState();
|
||||
_initBloc();
|
||||
_thumbZoomLevel = Pref.inst().getHomePhotosZoomLevel(0);
|
||||
_keyboardFocus.requestFocus();
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -55,7 +59,9 @@ class _HomePhotosState extends State<HomePhotos> {
|
|||
listener: (context, state) => _onStateChange(context, state),
|
||||
child: BlocBuilder<ScanDirBloc, ScanDirBlocState>(
|
||||
bloc: _bloc,
|
||||
builder: (context, state) => _buildContent(context, state),
|
||||
builder: (context, state) => kIsWeb
|
||||
? _buildWebContent(context, state)
|
||||
: _buildContent(context, state),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -71,6 +77,18 @@ class _HomePhotosState extends State<HomePhotos> {
|
|||
}
|
||||
}
|
||||
|
||||
Widget _buildWebContent(BuildContext context, ScanDirBlocState state) {
|
||||
assert(kIsWeb);
|
||||
// support switching pages with keyboard on web
|
||||
return RawKeyboardListener(
|
||||
onKey: (ev) {
|
||||
_isRangeSelectionMode = ev.isShiftPressed;
|
||||
},
|
||||
focusNode: _keyboardFocus,
|
||||
child: _buildContent(context, state),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildContent(BuildContext context, ScanDirBlocState state) {
|
||||
return Stack(
|
||||
children: [
|
||||
|
@ -254,12 +272,24 @@ class _HomePhotosState extends State<HomePhotos> {
|
|||
// unselect
|
||||
setState(() {
|
||||
_selectedItems.remove(item);
|
||||
_lastSelectPosition = null;
|
||||
});
|
||||
} else {
|
||||
// select
|
||||
setState(() {
|
||||
_selectedItems.add(item);
|
||||
});
|
||||
if (_isRangeSelectionMode && _lastSelectPosition != null) {
|
||||
setState(() {
|
||||
_selectedItems.addAll(_items
|
||||
.sublist(math.min(_lastSelectPosition, index),
|
||||
math.max(_lastSelectPosition, index) + 1)
|
||||
.whereType<_GridFileItem>());
|
||||
_lastSelectPosition = index;
|
||||
});
|
||||
} else {
|
||||
setState(() {
|
||||
_lastSelectPosition = index;
|
||||
_selectedItems.add(item);
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
final fileIndex = _itemIndexToFileIndex(index);
|
||||
|
@ -275,8 +305,17 @@ class _HomePhotosState extends State<HomePhotos> {
|
|||
return;
|
||||
}
|
||||
setState(() {
|
||||
_lastSelectPosition = index;
|
||||
_selectedItems.add(item);
|
||||
});
|
||||
|
||||
if (kIsWeb && !SessionStorage().hasShowRangeSelectNotification) {
|
||||
SnackBarManager().showSnackBar(SnackBar(
|
||||
content: Text(AppLocalizations.of(context).webSelectRangeNotification),
|
||||
duration: k.snackBarDurationNormal,
|
||||
));
|
||||
SessionStorage().hasShowRangeSelectNotification = true;
|
||||
}
|
||||
}
|
||||
|
||||
void _onSelectionAppBarAddToAlbumPressed(BuildContext context) {
|
||||
|
@ -404,6 +443,8 @@ class _HomePhotosState extends State<HomePhotos> {
|
|||
.where((element) => file_util.isSupportedFormat(element))
|
||||
.sorted(compareFileDateTimeDescending);
|
||||
|
||||
final lastSelectedItem =
|
||||
_lastSelectPosition != null ? _items[_lastSelectPosition] : null;
|
||||
_items.clear();
|
||||
String currentDateStr;
|
||||
for (final f in _backingFiles) {
|
||||
|
@ -424,6 +465,18 @@ class _HomePhotosState extends State<HomePhotos> {
|
|||
}
|
||||
|
||||
_transformSelectedItems();
|
||||
|
||||
// Keep _lastSelectPosition if no changes, drop otherwise
|
||||
int newLastSelectPosition;
|
||||
try {
|
||||
if (lastSelectedItem != null &&
|
||||
lastSelectedItem is _GridFileItem &&
|
||||
(_items[_lastSelectPosition] as _GridFileItem).file.path ==
|
||||
lastSelectedItem.file.path) {
|
||||
newLastSelectPosition = _lastSelectPosition;
|
||||
}
|
||||
} catch (_) {}
|
||||
_lastSelectPosition = newLastSelectPosition;
|
||||
}
|
||||
|
||||
/// Map selected items to the new item list
|
||||
|
@ -488,6 +541,8 @@ class _HomePhotosState extends State<HomePhotos> {
|
|||
}
|
||||
|
||||
bool get _isSelectionMode => _selectedItems.isNotEmpty;
|
||||
int _lastSelectPosition;
|
||||
bool _isRangeSelectionMode = false;
|
||||
|
||||
ScanDirBloc _bloc;
|
||||
|
||||
|
@ -496,7 +551,10 @@ class _HomePhotosState extends State<HomePhotos> {
|
|||
|
||||
var _thumbZoomLevel = 0;
|
||||
|
||||
final _selectedItems = <_GridFileItem>[];
|
||||
final _selectedItems = <_GridFileItem>{};
|
||||
|
||||
/// used to gain focus on web for keyboard support
|
||||
final _keyboardFocus = FocusNode();
|
||||
|
||||
static final _log = Logger("widget.home_photos._HomePhotosState");
|
||||
static const _menuValueRefresh = 0;
|
||||
|
|
Loading…
Reference in a new issue