#407 quick copy/move
This commit is contained in:
parent
5a6153b970
commit
f57e2306e2
33 changed files with 626 additions and 115 deletions
|
@ -7,7 +7,7 @@ All notable changes to this project will be documented in this file.
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Viewer: optionally show rating & tags on overlay
|
- Viewer: optionally show rating & tags on overlay
|
||||||
- Viewer: long press on rating quick action for quicker rating
|
- Viewer: long press on copy/move/rating quick action for quicker action
|
||||||
- Search: missing address, portrait, landscape filters
|
- Search: missing address, portrait, landscape filters
|
||||||
- Lithuanian translation (thanks Gediminas Murauskas)
|
- Lithuanian translation (thanks Gediminas Murauskas)
|
||||||
|
|
||||||
|
|
|
@ -29,6 +29,8 @@ class Settings extends ChangeNotifier {
|
||||||
|
|
||||||
Settings._private();
|
Settings._private();
|
||||||
|
|
||||||
|
static const int moveDestinationAlbumMax = 3;
|
||||||
|
|
||||||
static const Set<String> _internalKeys = {
|
static const Set<String> _internalKeys = {
|
||||||
hasAcceptedTermsKey,
|
hasAcceptedTermsKey,
|
||||||
catalogTimeZoneKey,
|
catalogTimeZoneKey,
|
||||||
|
@ -37,6 +39,7 @@ class Settings extends ChangeNotifier {
|
||||||
platformAccelerometerRotationKey,
|
platformAccelerometerRotationKey,
|
||||||
platformTransitionAnimationScaleKey,
|
platformTransitionAnimationScaleKey,
|
||||||
topEntryIdsKey,
|
topEntryIdsKey,
|
||||||
|
moveDestinationAlbumsKey,
|
||||||
};
|
};
|
||||||
static const _widgetKeyPrefix = 'widget_';
|
static const _widgetKeyPrefix = 'widget_';
|
||||||
|
|
||||||
|
@ -51,6 +54,7 @@ class Settings extends ChangeNotifier {
|
||||||
static const tileLayoutPrefixKey = 'tile_layout_';
|
static const tileLayoutPrefixKey = 'tile_layout_';
|
||||||
static const entryRenamingPatternKey = 'entry_renaming_pattern';
|
static const entryRenamingPatternKey = 'entry_renaming_pattern';
|
||||||
static const topEntryIdsKey = 'top_entry_ids';
|
static const topEntryIdsKey = 'top_entry_ids';
|
||||||
|
static const moveDestinationAlbumsKey = 'move_destination_albums';
|
||||||
|
|
||||||
// display
|
// display
|
||||||
static const displayRefreshRateModeKey = 'display_refresh_rate_mode';
|
static const displayRefreshRateModeKey = 'display_refresh_rate_mode';
|
||||||
|
@ -314,6 +318,10 @@ class Settings extends ChangeNotifier {
|
||||||
|
|
||||||
set topEntryIds(List<int>? newValue) => setAndNotify(topEntryIdsKey, newValue?.map((id) => id.toString()).whereNotNull().toList());
|
set topEntryIds(List<int>? newValue) => setAndNotify(topEntryIdsKey, newValue?.map((id) => id.toString()).whereNotNull().toList());
|
||||||
|
|
||||||
|
List<String> get moveDestinationAlbums => getStringList(moveDestinationAlbumsKey) ?? [];
|
||||||
|
|
||||||
|
set moveDestinationAlbums(List<String> newValue) => setAndNotify(moveDestinationAlbumsKey, newValue.take(Settings.moveDestinationAlbumMax).toList());
|
||||||
|
|
||||||
// display
|
// display
|
||||||
|
|
||||||
DisplayRefreshRateMode get displayRefreshRateMode => getEnumOrDefault(displayRefreshRateModeKey, SettingsDefaults.displayRefreshRateMode, DisplayRefreshRateMode.values);
|
DisplayRefreshRateMode get displayRefreshRateMode => getEnumOrDefault(displayRefreshRateModeKey, SettingsDefaults.displayRefreshRateMode, DisplayRefreshRateMode.values);
|
||||||
|
|
|
@ -317,7 +317,7 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware
|
||||||
|
|
||||||
Future<void> _move(BuildContext context, {required MoveType moveType}) async {
|
Future<void> _move(BuildContext context, {required MoveType moveType}) async {
|
||||||
final entries = _getTargetItems(context);
|
final entries = _getTargetItems(context);
|
||||||
await move(context, moveType: moveType, entries: entries);
|
await doMove(context, moveType: moveType, entries: entries);
|
||||||
|
|
||||||
_leaveSelectionMode(context);
|
_leaveSelectionMode(context);
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,50 +34,20 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
mixin EntryStorageMixin on FeedbackMixin, PermissionAwareMixin, SizeAwareMixin {
|
mixin EntryStorageMixin on FeedbackMixin, PermissionAwareMixin, SizeAwareMixin {
|
||||||
Future<void> move(
|
Future<void> doQuickMove(
|
||||||
BuildContext context, {
|
BuildContext context, {
|
||||||
required MoveType moveType,
|
required MoveType moveType,
|
||||||
required Set<AvesEntry> entries,
|
required Map<String, Iterable<AvesEntry>> entriesByDestination,
|
||||||
bool hideShowAction = false,
|
bool hideShowAction = false,
|
||||||
VoidCallback? onSuccess,
|
VoidCallback? onSuccess,
|
||||||
}) async {
|
}) async {
|
||||||
|
final entries = entriesByDestination.values.expand((v) => v).toSet();
|
||||||
final todoCount = entries.length;
|
final todoCount = entries.length;
|
||||||
assert(todoCount > 0);
|
assert(todoCount > 0);
|
||||||
|
|
||||||
final toBin = moveType == MoveType.toBin;
|
final toBin = moveType == MoveType.toBin;
|
||||||
final copy = moveType == MoveType.copy;
|
final copy = moveType == MoveType.copy;
|
||||||
|
|
||||||
final l10n = context.l10n;
|
|
||||||
if (toBin) {
|
|
||||||
if (!await showConfirmationDialog(
|
|
||||||
context: context,
|
|
||||||
type: ConfirmationDialog.moveToBin,
|
|
||||||
message: l10n.binEntriesConfirmationDialogMessage(todoCount),
|
|
||||||
confirmationButtonLabel: l10n.deleteButtonLabel,
|
|
||||||
)) return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final entriesByDestination = <String, Set<AvesEntry>>{};
|
|
||||||
switch (moveType) {
|
|
||||||
case MoveType.copy:
|
|
||||||
case MoveType.move:
|
|
||||||
case MoveType.export:
|
|
||||||
final destinationAlbum = await pickAlbum(context: context, moveType: moveType);
|
|
||||||
if (destinationAlbum == null) return;
|
|
||||||
entriesByDestination[destinationAlbum] = entries;
|
|
||||||
break;
|
|
||||||
case MoveType.toBin:
|
|
||||||
entriesByDestination[AndroidFileUtils.trashDirPath] = entries;
|
|
||||||
break;
|
|
||||||
case MoveType.fromBin:
|
|
||||||
groupBy<AvesEntry, String?>(entries, (e) => e.directory).forEach((originAlbum, dirEntries) {
|
|
||||||
if (originAlbum != null) {
|
|
||||||
entriesByDestination[originAlbum] = dirEntries.toSet();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// permission for modification at destinations
|
// permission for modification at destinations
|
||||||
final destinationAlbums = entriesByDestination.keys.toSet();
|
final destinationAlbums = entriesByDestination.keys.toSet();
|
||||||
if (!await checkStoragePermissionForAlbums(context, destinationAlbums)) return;
|
if (!await checkStoragePermissionForAlbums(context, destinationAlbums)) return;
|
||||||
|
@ -90,6 +60,7 @@ mixin EntryStorageMixin on FeedbackMixin, PermissionAwareMixin, SizeAwareMixin {
|
||||||
if (!await checkFreeSpaceForMove(context, entries, destinationAlbum, moveType)) return;
|
if (!await checkFreeSpaceForMove(context, entries, destinationAlbum, moveType)) return;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
final l10n = context.l10n;
|
||||||
var nameConflictStrategy = NameConflictStrategy.rename;
|
var nameConflictStrategy = NameConflictStrategy.rename;
|
||||||
if (!toBin && destinationAlbums.length == 1) {
|
if (!toBin && destinationAlbums.length == 1) {
|
||||||
final destinationDirectory = Directory(destinationAlbums.single);
|
final destinationDirectory = Directory(destinationAlbums.single);
|
||||||
|
@ -174,7 +145,7 @@ mixin EntryStorageMixin on FeedbackMixin, PermissionAwareMixin, SizeAwareMixin {
|
||||||
// local context may be deactivated when action is triggered after navigation
|
// local context may be deactivated when action is triggered after navigation
|
||||||
final context = AvesApp.navigatorKey.currentContext;
|
final context = AvesApp.navigatorKey.currentContext;
|
||||||
if (context != null) {
|
if (context != null) {
|
||||||
move(
|
doMove(
|
||||||
context,
|
context,
|
||||||
moveType: MoveType.fromBin,
|
moveType: MoveType.fromBin,
|
||||||
entries: movedEntries,
|
entries: movedEntries,
|
||||||
|
@ -213,6 +184,55 @@ mixin EntryStorageMixin on FeedbackMixin, PermissionAwareMixin, SizeAwareMixin {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> doMove(
|
||||||
|
BuildContext context, {
|
||||||
|
required MoveType moveType,
|
||||||
|
required Set<AvesEntry> entries,
|
||||||
|
bool hideShowAction = false,
|
||||||
|
VoidCallback? onSuccess,
|
||||||
|
}) async {
|
||||||
|
if (moveType == MoveType.toBin) {
|
||||||
|
final l10n = context.l10n;
|
||||||
|
if (!await showConfirmationDialog(
|
||||||
|
context: context,
|
||||||
|
type: ConfirmationDialog.moveToBin,
|
||||||
|
message: l10n.binEntriesConfirmationDialogMessage(entries.length),
|
||||||
|
confirmationButtonLabel: l10n.deleteButtonLabel,
|
||||||
|
)) return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final entriesByDestination = <String, Set<AvesEntry>>{};
|
||||||
|
switch (moveType) {
|
||||||
|
case MoveType.copy:
|
||||||
|
case MoveType.move:
|
||||||
|
case MoveType.export:
|
||||||
|
final destinationAlbum = await pickAlbum(context: context, moveType: moveType);
|
||||||
|
if (destinationAlbum == null) return;
|
||||||
|
|
||||||
|
settings.moveDestinationAlbums = settings.moveDestinationAlbums
|
||||||
|
..remove(destinationAlbum)
|
||||||
|
..insert(0, destinationAlbum);
|
||||||
|
entriesByDestination[destinationAlbum] = entries;
|
||||||
|
break;
|
||||||
|
case MoveType.toBin:
|
||||||
|
entriesByDestination[AndroidFileUtils.trashDirPath] = entries;
|
||||||
|
break;
|
||||||
|
case MoveType.fromBin:
|
||||||
|
groupBy<AvesEntry, String?>(entries, (e) => e.directory).forEach((originAlbum, dirEntries) {
|
||||||
|
if (originAlbum != null) {
|
||||||
|
entriesByDestination[originAlbum] = dirEntries.toSet();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
await doQuickMove(
|
||||||
|
context,
|
||||||
|
moveType: moveType,
|
||||||
|
entriesByDestination: entriesByDestination,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> rename(
|
Future<void> rename(
|
||||||
BuildContext context, {
|
BuildContext context, {
|
||||||
required Map<AvesEntry, String> entriesToNewName,
|
required Map<AvesEntry, String> entriesToNewName,
|
||||||
|
|
67
lib/widgets/common/app_bar/move_button.dart
Normal file
67
lib/widgets/common/app_bar/move_button.dart
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
import 'package:aves/model/actions/entry_actions.dart';
|
||||||
|
import 'package:aves/model/filters/album.dart';
|
||||||
|
import 'package:aves/model/filters/filters.dart';
|
||||||
|
import 'package:aves/model/settings/settings.dart';
|
||||||
|
import 'package:aves/model/source/collection_source.dart';
|
||||||
|
import 'package:aves/widgets/common/app_bar/quick_choosers/album_chooser.dart';
|
||||||
|
import 'package:aves/widgets/common/app_bar/quick_choosers/chooser_button.dart';
|
||||||
|
import 'package:aves/widgets/common/providers/media_query_data_provider.dart';
|
||||||
|
import 'package:aves/widgets/filter_grids/common/filter_nav_page.dart';
|
||||||
|
import 'package:collection/collection.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
class MoveButton extends ChooserQuickButton<String> {
|
||||||
|
final bool copy;
|
||||||
|
|
||||||
|
const MoveButton({
|
||||||
|
super.key,
|
||||||
|
required this.copy,
|
||||||
|
super.chooserPosition,
|
||||||
|
super.onChooserValue,
|
||||||
|
required super.onPressed,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<MoveButton> createState() => _MoveQuickButtonState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MoveQuickButtonState extends ChooserQuickButtonState<MoveButton, String> {
|
||||||
|
EntryAction get action => widget.copy ? EntryAction.copy : EntryAction.move;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget get icon => action.getIcon();
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get tooltip => action.getText(context);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String? get defaultValue => null;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget buildChooser(Animation<double> animation) {
|
||||||
|
final options = settings.moveDestinationAlbums;
|
||||||
|
final takeCount = Settings.moveDestinationAlbumMax - options.length;
|
||||||
|
if (takeCount > 0) {
|
||||||
|
final source = context.read<CollectionSource>();
|
||||||
|
final filters = source.rawAlbums.whereNot(options.contains).map((album) => AlbumFilter(album, null)).toSet();
|
||||||
|
final allMapEntries = filters.map((filter) => FilterGridItem(filter, source.recentEntry(filter))).toList();
|
||||||
|
allMapEntries.sort(FilterNavigationPage.compareFiltersByDate);
|
||||||
|
options.addAll(allMapEntries.take(takeCount).map((v) => v.filter.album));
|
||||||
|
}
|
||||||
|
|
||||||
|
return MediaQueryDataProvider(
|
||||||
|
child: FadeTransition(
|
||||||
|
opacity: animation,
|
||||||
|
child: ScaleTransition(
|
||||||
|
scale: animation,
|
||||||
|
child: AlbumQuickChooser(
|
||||||
|
valueNotifier: chooserValueNotifier,
|
||||||
|
pointerGlobalPosition: pointerGlobalPosition,
|
||||||
|
options: widget.chooserPosition == PopupMenuPosition.over ? options.reversed.toList() : options,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
35
lib/widgets/common/app_bar/quick_choosers/album_chooser.dart
Normal file
35
lib/widgets/common/app_bar/quick_choosers/album_chooser.dart
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:aves/model/filters/album.dart';
|
||||||
|
import 'package:aves/model/source/collection_source.dart';
|
||||||
|
import 'package:aves/widgets/common/app_bar/quick_choosers/filter_chooser.dart';
|
||||||
|
import 'package:aves/widgets/common/identity/aves_filter_chip.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
class AlbumQuickChooser extends StatelessWidget {
|
||||||
|
final ValueNotifier<String?> valueNotifier;
|
||||||
|
final List<String> options;
|
||||||
|
final Stream<Offset> pointerGlobalPosition;
|
||||||
|
|
||||||
|
const AlbumQuickChooser({
|
||||||
|
super.key,
|
||||||
|
required this.valueNotifier,
|
||||||
|
required this.options,
|
||||||
|
required this.pointerGlobalPosition,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final source = context.read<CollectionSource>();
|
||||||
|
return FilterQuickChooser<String>(
|
||||||
|
valueNotifier: valueNotifier,
|
||||||
|
options: options,
|
||||||
|
pointerGlobalPosition: pointerGlobalPosition,
|
||||||
|
buildFilterChip: (context, album) => AvesFilterChip(
|
||||||
|
filter: AlbumFilter(album, source.getAlbumDisplayName(context, album)),
|
||||||
|
showGenericIcon: false,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -39,7 +39,7 @@ abstract class ChooserQuickButtonState<T extends ChooserQuickButton<U>, U> exten
|
||||||
|
|
||||||
ValueNotifier<U?> get chooserValueNotifier => _chooserValueNotifier;
|
ValueNotifier<U?> get chooserValueNotifier => _chooserValueNotifier;
|
||||||
|
|
||||||
Stream<LongPressMoveUpdateDetails> get moveUpdates => _moveUpdateStreamController.stream;
|
Stream<Offset> get pointerGlobalPosition => _moveUpdateStreamController.stream.map((event) => event.globalPosition);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
|
|
146
lib/widgets/common/app_bar/quick_choosers/filter_chooser.dart
Normal file
146
lib/widgets/common/app_bar/quick_choosers/filter_chooser.dart
Normal file
|
@ -0,0 +1,146 @@
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:aves/widgets/dialogs/aves_dialog.dart';
|
||||||
|
import 'package:aves_ui/aves_ui.dart';
|
||||||
|
import 'package:collection/collection.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class FilterQuickChooser<T> extends StatefulWidget {
|
||||||
|
final ValueNotifier<T?> valueNotifier;
|
||||||
|
final List<T> options;
|
||||||
|
final Stream<Offset> pointerGlobalPosition;
|
||||||
|
final Widget Function(BuildContext context, T album) buildFilterChip;
|
||||||
|
|
||||||
|
const FilterQuickChooser({
|
||||||
|
super.key,
|
||||||
|
required this.valueNotifier,
|
||||||
|
required this.options,
|
||||||
|
required this.pointerGlobalPosition,
|
||||||
|
required this.buildFilterChip,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<FilterQuickChooser<T>> createState() => _FilterQuickChooserState<T>();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _FilterQuickChooserState<T> extends State<FilterQuickChooser<T>> {
|
||||||
|
final List<StreamSubscription> _subscriptions = [];
|
||||||
|
final ValueNotifier<Rect> _selectedRowRect = ValueNotifier(Rect.zero);
|
||||||
|
|
||||||
|
ValueNotifier<T?> get valueNotifier => widget.valueNotifier;
|
||||||
|
|
||||||
|
List<T> get options => widget.options;
|
||||||
|
|
||||||
|
static const margin = EdgeInsets.all(8);
|
||||||
|
static const padding = EdgeInsets.all(8);
|
||||||
|
static const double intraPadding = 8;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_registerWidget(widget);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didUpdateWidget(covariant FilterQuickChooser<T> oldWidget) {
|
||||||
|
super.didUpdateWidget(oldWidget);
|
||||||
|
_unregisterWidget(oldWidget);
|
||||||
|
_registerWidget(widget);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_unregisterWidget(widget);
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _registerWidget(FilterQuickChooser<T> widget) {
|
||||||
|
_subscriptions.add(widget.pointerGlobalPosition.listen(_onPointerMove));
|
||||||
|
}
|
||||||
|
|
||||||
|
void _unregisterWidget(FilterQuickChooser<T> widget) {
|
||||||
|
_subscriptions
|
||||||
|
..forEach((sub) => sub.cancel())
|
||||||
|
..clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Padding(
|
||||||
|
padding: margin,
|
||||||
|
child: Material(
|
||||||
|
shape: AvesDialog.shape(context),
|
||||||
|
child: Padding(
|
||||||
|
padding: padding,
|
||||||
|
child: ValueListenableBuilder<T?>(
|
||||||
|
valueListenable: valueNotifier,
|
||||||
|
builder: (context, selectedValue, child) {
|
||||||
|
return Stack(
|
||||||
|
children: [
|
||||||
|
ValueListenableBuilder<Rect>(
|
||||||
|
valueListenable: _selectedRowRect,
|
||||||
|
builder: (context, selectedRowRect, child) {
|
||||||
|
Widget child = const Center(child: AvesDot());
|
||||||
|
child = AnimatedOpacity(
|
||||||
|
opacity: selectedValue != null ? 1 : 0,
|
||||||
|
curve: Curves.easeInOutCubic,
|
||||||
|
duration: const Duration(milliseconds: 200),
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
child = AnimatedPositioned(
|
||||||
|
top: selectedRowRect.top,
|
||||||
|
height: selectedRowRect.height,
|
||||||
|
curve: Curves.easeInOutCubic,
|
||||||
|
duration: const Duration(milliseconds: 200),
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
return child;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsetsDirectional.only(start: 24),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: options.mapIndexed((index, value) {
|
||||||
|
return Padding(
|
||||||
|
padding: index == 0 ? EdgeInsets.zero : const EdgeInsets.only(top: intraPadding),
|
||||||
|
child: widget.buildFilterChip(context, value),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onPointerMove(Offset globalPosition) {
|
||||||
|
final chooserBox = context.findRenderObject() as RenderBox;
|
||||||
|
final chooserSize = chooserBox.size;
|
||||||
|
final contentWidth = chooserSize.width;
|
||||||
|
final contentHeight = chooserSize.height - (margin.vertical + padding.vertical);
|
||||||
|
|
||||||
|
final optionCount = options.length;
|
||||||
|
final itemHeight = (contentHeight - (optionCount - 1) * intraPadding) / optionCount;
|
||||||
|
|
||||||
|
final local = chooserBox.globalToLocal(globalPosition);
|
||||||
|
final dx = local.dx;
|
||||||
|
final dy = local.dy - (margin.vertical + padding.vertical) / 2;
|
||||||
|
|
||||||
|
T? selectedValue;
|
||||||
|
if (0 < dx && dx < contentWidth && 0 < dy && dy < contentHeight) {
|
||||||
|
final index = (options.length * dy / contentHeight).floor();
|
||||||
|
if (0 <= index && index < options.length) {
|
||||||
|
selectedValue = options[index];
|
||||||
|
final top = index * (itemHeight + intraPadding);
|
||||||
|
_selectedRowRect.value = Rect.fromLTWH(0, top, contentWidth, itemHeight);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
valueNotifier.value = selectedValue;
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,13 +5,13 @@ import 'package:aves/widgets/dialogs/aves_dialog.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class RateQuickChooser extends StatefulWidget {
|
class RateQuickChooser extends StatefulWidget {
|
||||||
final ValueNotifier<int?> ratingNotifier;
|
final ValueNotifier<int?> valueNotifier;
|
||||||
final Stream<LongPressMoveUpdateDetails> moveUpdates;
|
final Stream<Offset> pointerGlobalPosition;
|
||||||
|
|
||||||
const RateQuickChooser({
|
const RateQuickChooser({
|
||||||
super.key,
|
super.key,
|
||||||
required this.ratingNotifier,
|
required this.valueNotifier,
|
||||||
required this.moveUpdates,
|
required this.pointerGlobalPosition,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -21,6 +21,11 @@ class RateQuickChooser extends StatefulWidget {
|
||||||
class _RateQuickChooserState extends State<RateQuickChooser> {
|
class _RateQuickChooserState extends State<RateQuickChooser> {
|
||||||
final List<StreamSubscription> _subscriptions = [];
|
final List<StreamSubscription> _subscriptions = [];
|
||||||
|
|
||||||
|
ValueNotifier<int?> get valueNotifier => widget.valueNotifier;
|
||||||
|
|
||||||
|
static const margin = EdgeInsets.all(8);
|
||||||
|
static const padding = EdgeInsets.all(8);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
@ -41,7 +46,7 @@ class _RateQuickChooserState extends State<RateQuickChooser> {
|
||||||
}
|
}
|
||||||
|
|
||||||
void _registerWidget(RateQuickChooser widget) {
|
void _registerWidget(RateQuickChooser widget) {
|
||||||
_subscriptions.add(widget.moveUpdates.map((event) => event.globalPosition).listen(_onPointerMove));
|
_subscriptions.add(widget.pointerGlobalPosition.listen(_onPointerMove));
|
||||||
}
|
}
|
||||||
|
|
||||||
void _unregisterWidget(RateQuickChooser widget) {
|
void _unregisterWidget(RateQuickChooser widget) {
|
||||||
|
@ -53,19 +58,18 @@ class _RateQuickChooserState extends State<RateQuickChooser> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.all(8),
|
padding: margin,
|
||||||
child: Material(
|
child: Material(
|
||||||
shape: AvesDialog.shape(context),
|
shape: AvesDialog.shape(context),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(8),
|
padding: padding,
|
||||||
child: ValueListenableBuilder<int?>(
|
child: ValueListenableBuilder<int?>(
|
||||||
valueListenable: widget.ratingNotifier,
|
valueListenable: valueNotifier,
|
||||||
builder: (context, rating, child) {
|
builder: (context, selectedValue, child) {
|
||||||
final _rating = rating ?? 0;
|
final _rating = selectedValue ?? 0;
|
||||||
return Row(
|
return Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: List.generate(5, (i) {
|
||||||
...List.generate(5, (i) {
|
|
||||||
final thisRating = i + 1;
|
final thisRating = i + 1;
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.all(4),
|
padding: const EdgeInsets.all(4),
|
||||||
|
@ -74,8 +78,7 @@ class _RateQuickChooserState extends State<RateQuickChooser> {
|
||||||
color: _rating < thisRating ? Colors.grey : Colors.amber,
|
color: _rating < thisRating ? Colors.grey : Colors.amber,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
})
|
}).toList(),
|
||||||
],
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
@ -85,9 +88,13 @@ class _RateQuickChooserState extends State<RateQuickChooser> {
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onPointerMove(Offset globalPosition) {
|
void _onPointerMove(Offset globalPosition) {
|
||||||
final rowBox = context.findRenderObject() as RenderBox;
|
final chooserBox = context.findRenderObject() as RenderBox;
|
||||||
final rowSize = rowBox.size;
|
final chooserSize = chooserBox.size;
|
||||||
final local = rowBox.globalToLocal(globalPosition);
|
final contentWidth = chooserSize.width - (margin.horizontal + padding.horizontal);
|
||||||
widget.ratingNotifier.value = (5 * local.dx / rowSize.width).ceil().clamp(0, 5);
|
|
||||||
|
final local = chooserBox.globalToLocal(globalPosition);
|
||||||
|
final dx = local.dx - (margin.horizontal + padding.horizontal) / 2;
|
||||||
|
|
||||||
|
valueNotifier.value = (5 * dx / contentWidth).ceil().clamp(0, 5);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,8 +34,8 @@ class _RateQuickButtonState extends ChooserQuickButtonState<RateButton, int> {
|
||||||
child: ScaleTransition(
|
child: ScaleTransition(
|
||||||
scale: animation,
|
scale: animation,
|
||||||
child: RateQuickChooser(
|
child: RateQuickChooser(
|
||||||
ratingNotifier: chooserValueNotifier,
|
valueNotifier: chooserValueNotifier,
|
||||||
moveUpdates: moveUpdates,
|
pointerGlobalPosition: pointerGlobalPosition,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
@ -213,8 +213,8 @@ class _GeoMapState extends State<GeoMap> {
|
||||||
MapThemeData.markerImageExtent + MapThemeData.markerOuterBorderWidth * 2 + MapThemeData.markerArrowSize.height,
|
MapThemeData.markerImageExtent + MapThemeData.markerOuterBorderWidth * 2 + MapThemeData.markerArrowSize.height,
|
||||||
),
|
),
|
||||||
dotMarkerSize: const Size(
|
dotMarkerSize: const Size(
|
||||||
DotMarker.diameter + MapThemeData.markerOuterBorderWidth * 2,
|
MapThemeData.markerDotDiameter + MapThemeData.markerOuterBorderWidth * 2,
|
||||||
DotMarker.diameter + MapThemeData.markerOuterBorderWidth * 2,
|
MapThemeData.markerDotDiameter + MapThemeData.markerOuterBorderWidth * 2,
|
||||||
),
|
),
|
||||||
overlayOpacityNotifier: widget.overlayOpacityNotifier,
|
overlayOpacityNotifier: widget.overlayOpacityNotifier,
|
||||||
overlayEntry: widget.overlayEntry,
|
overlayEntry: widget.overlayEntry,
|
||||||
|
|
|
@ -53,7 +53,7 @@ class EntryLeafletMap<T> extends StatefulWidget {
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<StatefulWidget> createState() => _EntryLeafletMapState<T>();
|
State<EntryLeafletMap<T>> createState() => _EntryLeafletMapState<T>();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _EntryLeafletMapState<T> extends State<EntryLeafletMap<T>> with TickerProviderStateMixin {
|
class _EntryLeafletMapState<T> extends State<EntryLeafletMap<T>> with TickerProviderStateMixin {
|
||||||
|
|
|
@ -212,7 +212,7 @@ class AlbumChipSetActionDelegate extends ChipSetActionDelegate<AlbumFilter> with
|
||||||
final emptyAlbums = todoAlbums.whereNot(filledAlbums.contains).toSet();
|
final emptyAlbums = todoAlbums.whereNot(filledAlbums.contains).toSet();
|
||||||
|
|
||||||
if (settings.enableBin && filledAlbums.isNotEmpty) {
|
if (settings.enableBin && filledAlbums.isNotEmpty) {
|
||||||
await move(
|
await doMove(
|
||||||
context,
|
context,
|
||||||
moveType: MoveType.toBin,
|
moveType: MoveType.toBin,
|
||||||
entries: todoEntries,
|
entries: todoEntries,
|
||||||
|
|
|
@ -280,6 +280,19 @@ class EntryActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void quickMove(BuildContext context, String? album, {required bool copy}) {
|
||||||
|
final targetEntry = _getTargetEntry(context, EntryAction.editRating);
|
||||||
|
if (album == null || (!copy && targetEntry.directory == album)) return;
|
||||||
|
|
||||||
|
doQuickMove(
|
||||||
|
context,
|
||||||
|
moveType: copy ? MoveType.copy : MoveType.move,
|
||||||
|
entriesByDestination: {
|
||||||
|
album: {targetEntry}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
void quickRate(BuildContext context, int? rating) {
|
void quickRate(BuildContext context, int? rating) {
|
||||||
final targetEntry = _getTargetEntry(context, EntryAction.editRating);
|
final targetEntry = _getTargetEntry(context, EntryAction.editRating);
|
||||||
_metadataActionDelegate.quickRate(context, targetEntry, rating);
|
_metadataActionDelegate.quickRate(context, targetEntry, rating);
|
||||||
|
@ -441,7 +454,7 @@ class EntryActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _move(BuildContext context, AvesEntry targetEntry, {required MoveType moveType}) => move(
|
Future<void> _move(BuildContext context, AvesEntry targetEntry, {required MoveType moveType}) => doMove(
|
||||||
context,
|
context,
|
||||||
moveType: moveType,
|
moveType: moveType,
|
||||||
entries: {targetEntry},
|
entries: {targetEntry},
|
||||||
|
|
|
@ -148,7 +148,7 @@ class EntryInfoActionDelegate with FeedbackMixin, PermissionAwareMixin, EntryEdi
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> quickRate(BuildContext context, AvesEntry targetEntry, int? rating) async {
|
Future<void> quickRate(BuildContext context, AvesEntry targetEntry, int? rating) async {
|
||||||
if (rating == null) return;
|
if (rating == null || targetEntry.rating == rating) return;
|
||||||
|
|
||||||
await edit(context, targetEntry, () => targetEntry.editRating(rating));
|
await edit(context, targetEntry, () => targetEntry.editRating(rating));
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import 'package:aves/model/source/collection_lens.dart';
|
||||||
import 'package:aves/theme/durations.dart';
|
import 'package:aves/theme/durations.dart';
|
||||||
import 'package:aves/theme/icons.dart';
|
import 'package:aves/theme/icons.dart';
|
||||||
import 'package:aves/widgets/common/app_bar/favourite_toggler.dart';
|
import 'package:aves/widgets/common/app_bar/favourite_toggler.dart';
|
||||||
|
import 'package:aves/widgets/common/app_bar/move_button.dart';
|
||||||
import 'package:aves/widgets/common/app_bar/rate_button.dart';
|
import 'package:aves/widgets/common/app_bar/rate_button.dart';
|
||||||
import 'package:aves/widgets/common/basic/menu.dart';
|
import 'package:aves/widgets/common/basic/menu.dart';
|
||||||
import 'package:aves/widgets/common/basic/popup_menu_button.dart';
|
import 'package:aves/widgets/common/basic/popup_menu_button.dart';
|
||||||
|
@ -204,6 +205,22 @@ class ViewerButtonRowContent extends StatelessWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (action) {
|
switch (action) {
|
||||||
|
case EntryAction.copy:
|
||||||
|
child = MoveButton(
|
||||||
|
copy: true,
|
||||||
|
chooserPosition: PopupMenuPosition.over,
|
||||||
|
onChooserValue: (album) => _quickMove(context, album, copy: true),
|
||||||
|
onPressed: onPressed,
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case EntryAction.move:
|
||||||
|
child = MoveButton(
|
||||||
|
copy: false,
|
||||||
|
chooserPosition: PopupMenuPosition.over,
|
||||||
|
onChooserValue: (album) => _quickMove(context, album, copy: false),
|
||||||
|
onPressed: onPressed,
|
||||||
|
);
|
||||||
|
break;
|
||||||
case EntryAction.toggleFavourite:
|
case EntryAction.toggleFavourite:
|
||||||
child = FavouriteToggler(
|
child = FavouriteToggler(
|
||||||
entries: {favouriteTargetEntry},
|
entries: {favouriteTargetEntry},
|
||||||
|
@ -365,5 +382,7 @@ class ViewerButtonRowContent extends StatelessWidget {
|
||||||
|
|
||||||
void _onActionSelected(BuildContext context, EntryAction action) => _entryActionDelegate.onActionSelected(context, action);
|
void _onActionSelected(BuildContext context, EntryAction action) => _entryActionDelegate.onActionSelected(context, action);
|
||||||
|
|
||||||
|
void _quickMove(BuildContext context, String? album, {required bool copy}) => _entryActionDelegate.quickMove(context, album, copy: copy);
|
||||||
|
|
||||||
void _quickRate(BuildContext context, int? rating) => _entryActionDelegate.quickRate(context, rating);
|
void _quickRate(BuildContext context, int? rating) => _entryActionDelegate.quickRate(context, rating);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,54 +1,18 @@
|
||||||
import 'package:aves_map/src/theme.dart';
|
import 'package:aves_map/src/theme.dart';
|
||||||
|
import 'package:aves_ui/aves_ui.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class DotMarker extends StatelessWidget {
|
class DotMarker extends StatelessWidget {
|
||||||
const DotMarker({super.key});
|
const DotMarker({super.key});
|
||||||
|
|
||||||
static const double diameter = 16;
|
|
||||||
static const double outerBorderRadiusDim = diameter;
|
|
||||||
static const double outerBorderWidth = MapThemeData.markerOuterBorderWidth;
|
|
||||||
static const double innerBorderWidth = MapThemeData.markerInnerBorderWidth;
|
|
||||||
static const outerBorderRadius = BorderRadius.all(Radius.circular(outerBorderRadiusDim));
|
|
||||||
static const innerRadius = Radius.circular(outerBorderRadiusDim - outerBorderWidth);
|
|
||||||
static const innerBorderRadius = BorderRadius.all(innerRadius);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = Theme.of(context);
|
return const AvesDot(
|
||||||
final isDark = theme.brightness == Brightness.dark;
|
diameter: MapThemeData.markerDotDiameter,
|
||||||
final outerBorderColor = MapThemeData.markerThemedOuterBorderColor(isDark);
|
outerBorderWidth: MapThemeData.markerOuterBorderWidth,
|
||||||
final innerBorderColor = MapThemeData.markerThemedInnerBorderColor(isDark);
|
innerBorderWidth: MapThemeData.markerInnerBorderWidth,
|
||||||
|
getOuterBorderColor: MapThemeData.markerThemedOuterBorderColor,
|
||||||
final outerDecoration = BoxDecoration(
|
getInnerBorderColor: MapThemeData.markerThemedInnerBorderColor,
|
||||||
border: Border.fromBorderSide(BorderSide(
|
|
||||||
color: outerBorderColor,
|
|
||||||
width: outerBorderWidth,
|
|
||||||
)),
|
|
||||||
borderRadius: outerBorderRadius,
|
|
||||||
);
|
|
||||||
|
|
||||||
final innerDecoration = BoxDecoration(
|
|
||||||
border: Border.fromBorderSide(BorderSide(
|
|
||||||
color: innerBorderColor,
|
|
||||||
width: innerBorderWidth,
|
|
||||||
)),
|
|
||||||
borderRadius: innerBorderRadius,
|
|
||||||
);
|
|
||||||
|
|
||||||
return Container(
|
|
||||||
decoration: outerDecoration,
|
|
||||||
child: DecoratedBox(
|
|
||||||
decoration: innerDecoration,
|
|
||||||
position: DecorationPosition.foreground,
|
|
||||||
child: ClipRRect(
|
|
||||||
borderRadius: innerBorderRadius,
|
|
||||||
child: Container(
|
|
||||||
width: diameter,
|
|
||||||
height: diameter,
|
|
||||||
color: theme.colorScheme.secondary,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,7 @@ class MapThemeData {
|
||||||
static const double markerInnerBorderWidth = 2;
|
static const double markerInnerBorderWidth = 2;
|
||||||
static const double markerImageExtent = 48.0;
|
static const double markerImageExtent = 48.0;
|
||||||
static const Size markerArrowSize = Size(8, 6);
|
static const Size markerArrowSize = Size(8, 6);
|
||||||
|
static const double markerDotDiameter = 16;
|
||||||
|
|
||||||
static Color markerThemedOuterBorderColor(bool isDark) => isDark ? Colors.white30 : Colors.black26;
|
static Color markerThemedOuterBorderColor(bool isDark) => isDark ? Colors.white30 : Colors.black26;
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,13 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.10.0"
|
version: "2.10.0"
|
||||||
|
aves_ui:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
path: "../aves_ui"
|
||||||
|
relative: true
|
||||||
|
source: path
|
||||||
|
version: "0.0.1"
|
||||||
characters:
|
characters:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
|
@ -8,6 +8,8 @@ environment:
|
||||||
dependencies:
|
dependencies:
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
aves_ui:
|
||||||
|
path: ../aves_ui
|
||||||
collection:
|
collection:
|
||||||
# TODO TLAD as of 2022/02/22, null safe version is pre-release
|
# TODO TLAD as of 2022/02/22, null safe version is pre-release
|
||||||
custom_rounded_rectangle_border: '>=0.2.0-nullsafety.0'
|
custom_rounded_rectangle_border: '>=0.2.0-nullsafety.0'
|
||||||
|
|
|
@ -15,6 +15,13 @@ packages:
|
||||||
relative: true
|
relative: true
|
||||||
source: path
|
source: path
|
||||||
version: "0.0.1"
|
version: "0.0.1"
|
||||||
|
aves_ui:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
path: "../aves_ui"
|
||||||
|
relative: true
|
||||||
|
source: path
|
||||||
|
version: "0.0.1"
|
||||||
characters:
|
characters:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
|
@ -22,6 +22,13 @@ packages:
|
||||||
relative: true
|
relative: true
|
||||||
source: path
|
source: path
|
||||||
version: "0.0.1"
|
version: "0.0.1"
|
||||||
|
aves_ui:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
path: "../aves_ui"
|
||||||
|
relative: true
|
||||||
|
source: path
|
||||||
|
version: "0.0.1"
|
||||||
characters:
|
characters:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
|
@ -29,6 +29,13 @@ packages:
|
||||||
relative: true
|
relative: true
|
||||||
source: path
|
source: path
|
||||||
version: "0.0.1"
|
version: "0.0.1"
|
||||||
|
aves_ui:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
path: "../aves_ui"
|
||||||
|
relative: true
|
||||||
|
source: path
|
||||||
|
version: "0.0.1"
|
||||||
characters:
|
characters:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
|
@ -22,6 +22,13 @@ packages:
|
||||||
relative: true
|
relative: true
|
||||||
source: path
|
source: path
|
||||||
version: "0.0.1"
|
version: "0.0.1"
|
||||||
|
aves_ui:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
path: "../aves_ui"
|
||||||
|
relative: true
|
||||||
|
source: path
|
||||||
|
version: "0.0.1"
|
||||||
characters:
|
characters:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
30
plugins/aves_ui/.gitignore
vendored
Normal file
30
plugins/aves_ui/.gitignore
vendored
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
# Miscellaneous
|
||||||
|
*.class
|
||||||
|
*.log
|
||||||
|
*.pyc
|
||||||
|
*.swp
|
||||||
|
.DS_Store
|
||||||
|
.atom/
|
||||||
|
.buildlog/
|
||||||
|
.history
|
||||||
|
.svn/
|
||||||
|
migrate_working_dir/
|
||||||
|
|
||||||
|
# IntelliJ related
|
||||||
|
*.iml
|
||||||
|
*.ipr
|
||||||
|
*.iws
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
# The .vscode folder contains launch configuration and tasks you configure in
|
||||||
|
# VS Code which you may wish to be included in version control, so this line
|
||||||
|
# is commented out by default.
|
||||||
|
#.vscode/
|
||||||
|
|
||||||
|
# Flutter/Dart/Pub related
|
||||||
|
# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
|
||||||
|
#/pubspec.lock
|
||||||
|
**/doc/api/
|
||||||
|
.dart_tool/
|
||||||
|
.packages
|
||||||
|
build/
|
10
plugins/aves_ui/.metadata
Normal file
10
plugins/aves_ui/.metadata
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
# This file tracks properties of this Flutter project.
|
||||||
|
# Used by Flutter tool to assess capabilities and perform upgrades etc.
|
||||||
|
#
|
||||||
|
# This file should be version controlled and should not be manually edited.
|
||||||
|
|
||||||
|
version:
|
||||||
|
revision: 5464c5bac742001448fe4fc0597be939379f88ea
|
||||||
|
channel: stable
|
||||||
|
|
||||||
|
project_type: package
|
1
plugins/aves_ui/analysis_options.yaml
Normal file
1
plugins/aves_ui/analysis_options.yaml
Normal file
|
@ -0,0 +1 @@
|
||||||
|
include: ../../analysis_options.yaml
|
3
plugins/aves_ui/lib/aves_ui.dart
Normal file
3
plugins/aves_ui/lib/aves_ui.dart
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
library aves_ui;
|
||||||
|
|
||||||
|
export 'src/dot.dart';
|
62
plugins/aves_ui/lib/src/dot.dart
Normal file
62
plugins/aves_ui/lib/src/dot.dart
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class AvesDot extends StatelessWidget {
|
||||||
|
final double diameter, outerBorderWidth, innerBorderWidth;
|
||||||
|
final Color Function(bool isDark) getOuterBorderColor, getInnerBorderColor;
|
||||||
|
|
||||||
|
const AvesDot({
|
||||||
|
super.key,
|
||||||
|
this.diameter = 16,
|
||||||
|
this.outerBorderWidth = 1.5,
|
||||||
|
this.innerBorderWidth = 2,
|
||||||
|
this.getOuterBorderColor = themedOuterBorderColor,
|
||||||
|
this.getInnerBorderColor = themedInnerBorderColor,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final theme = Theme.of(context);
|
||||||
|
final isDark = theme.brightness == Brightness.dark;
|
||||||
|
final outerBorderColor = getOuterBorderColor(isDark);
|
||||||
|
final innerBorderColor = getInnerBorderColor(isDark);
|
||||||
|
final outerBorderRadius = BorderRadius.all(Radius.circular(diameter));
|
||||||
|
final innerRadius = Radius.circular(diameter - outerBorderWidth);
|
||||||
|
final innerBorderRadius = BorderRadius.all(innerRadius);
|
||||||
|
|
||||||
|
final outerDecoration = BoxDecoration(
|
||||||
|
border: Border.fromBorderSide(BorderSide(
|
||||||
|
color: outerBorderColor,
|
||||||
|
width: outerBorderWidth,
|
||||||
|
)),
|
||||||
|
borderRadius: outerBorderRadius,
|
||||||
|
);
|
||||||
|
|
||||||
|
final innerDecoration = BoxDecoration(
|
||||||
|
border: Border.fromBorderSide(BorderSide(
|
||||||
|
color: innerBorderColor,
|
||||||
|
width: innerBorderWidth,
|
||||||
|
)),
|
||||||
|
borderRadius: innerBorderRadius,
|
||||||
|
);
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
decoration: outerDecoration,
|
||||||
|
child: DecoratedBox(
|
||||||
|
decoration: innerDecoration,
|
||||||
|
position: DecorationPosition.foreground,
|
||||||
|
child: ClipRRect(
|
||||||
|
borderRadius: innerBorderRadius,
|
||||||
|
child: Container(
|
||||||
|
width: diameter,
|
||||||
|
height: diameter,
|
||||||
|
color: theme.colorScheme.secondary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Color themedOuterBorderColor(bool isDark) => isDark ? Colors.white30 : Colors.black26;
|
||||||
|
|
||||||
|
static Color themedInnerBorderColor(bool isDark) => isDark ? const Color(0xFF212121) : Colors.white;
|
||||||
|
}
|
64
plugins/aves_ui/pubspec.lock
Normal file
64
plugins/aves_ui/pubspec.lock
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
# Generated by pub
|
||||||
|
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||||
|
packages:
|
||||||
|
characters:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: characters
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.2.1"
|
||||||
|
collection:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: collection
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.16.0"
|
||||||
|
flutter:
|
||||||
|
dependency: "direct main"
|
||||||
|
description: flutter
|
||||||
|
source: sdk
|
||||||
|
version: "0.0.0"
|
||||||
|
flutter_lints:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description:
|
||||||
|
name: flutter_lints
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.1"
|
||||||
|
lints:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: lints
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.1"
|
||||||
|
material_color_utilities:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: material_color_utilities
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.1.5"
|
||||||
|
meta:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: meta
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.8.0"
|
||||||
|
sky_engine:
|
||||||
|
dependency: transitive
|
||||||
|
description: flutter
|
||||||
|
source: sdk
|
||||||
|
version: "0.0.99"
|
||||||
|
vector_math:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: vector_math
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.2"
|
||||||
|
sdks:
|
||||||
|
dart: ">=2.18.0 <3.0.0"
|
15
plugins/aves_ui/pubspec.yaml
Normal file
15
plugins/aves_ui/pubspec.yaml
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
name: aves_ui
|
||||||
|
version: 0.0.1
|
||||||
|
publish_to: none
|
||||||
|
|
||||||
|
environment:
|
||||||
|
sdk: ">=2.18.0 <3.0.0"
|
||||||
|
|
||||||
|
dependencies:
|
||||||
|
flutter:
|
||||||
|
sdk: flutter
|
||||||
|
|
||||||
|
dev_dependencies:
|
||||||
|
flutter_lints:
|
||||||
|
|
||||||
|
flutter:
|
|
@ -85,6 +85,13 @@ packages:
|
||||||
relative: true
|
relative: true
|
||||||
source: path
|
source: path
|
||||||
version: "0.0.1"
|
version: "0.0.1"
|
||||||
|
aves_ui:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
path: "plugins/aves_ui"
|
||||||
|
relative: true
|
||||||
|
source: path
|
||||||
|
version: "0.0.1"
|
||||||
barcode:
|
barcode:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -615,7 +622,7 @@ packages:
|
||||||
name: mime
|
name: mime
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.2"
|
version: "1.0.3"
|
||||||
motion_sensors:
|
motion_sensors:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
|
@ -37,6 +37,8 @@ dependencies:
|
||||||
path: plugins/aves_services
|
path: plugins/aves_services
|
||||||
aves_services_platform:
|
aves_services_platform:
|
||||||
path: plugins/aves_services_google
|
path: plugins/aves_services_google
|
||||||
|
aves_ui:
|
||||||
|
path: plugins/aves_ui
|
||||||
charts_flutter:
|
charts_flutter:
|
||||||
collection:
|
collection:
|
||||||
connectivity_plus:
|
connectivity_plus:
|
||||||
|
|
Loading…
Reference in a new issue