thumbnail theme provider, app mode provider, thumbnail overlay review
This commit is contained in:
parent
ff6aef1e82
commit
cf8d182cfe
16 changed files with 268 additions and 216 deletions
|
@ -6,3 +6,5 @@
|
||||||
|
|
||||||
preferred-supported-locales:
|
preferred-supported-locales:
|
||||||
- en
|
- en
|
||||||
|
|
||||||
|
# untranslated-messages-file: untranslated.json
|
||||||
|
|
|
@ -43,13 +43,12 @@ void main() {
|
||||||
enum AppMode { main, pick, view }
|
enum AppMode { main, pick, view }
|
||||||
|
|
||||||
class AvesApp extends StatefulWidget {
|
class AvesApp extends StatefulWidget {
|
||||||
static AppMode mode;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_AvesAppState createState() => _AvesAppState();
|
_AvesAppState createState() => _AvesAppState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _AvesAppState extends State<AvesApp> {
|
class _AvesAppState extends State<AvesApp> {
|
||||||
|
final ValueNotifier<AppMode> appModeNotifier = ValueNotifier(AppMode.main);
|
||||||
Future<void> _appSetup;
|
Future<void> _appSetup;
|
||||||
final _mediaStoreSource = MediaStoreSource();
|
final _mediaStoreSource = MediaStoreSource();
|
||||||
final Debouncer _contentChangeDebouncer = Debouncer(delay: Durations.contentChangeDebounceDelay);
|
final Debouncer _contentChangeDebouncer = Debouncer(delay: Durations.contentChangeDebounceDelay);
|
||||||
|
@ -123,6 +122,8 @@ class _AvesAppState extends State<AvesApp> {
|
||||||
// so it can be used during navigation transitions
|
// so it can be used during navigation transitions
|
||||||
return ChangeNotifierProvider<Settings>.value(
|
return ChangeNotifierProvider<Settings>.value(
|
||||||
value: settings,
|
value: settings,
|
||||||
|
child: ListenableProvider<ValueNotifier<AppMode>>.value(
|
||||||
|
value: appModeNotifier,
|
||||||
child: Provider<CollectionSource>.value(
|
child: Provider<CollectionSource>.value(
|
||||||
value: _mediaStoreSource,
|
value: _mediaStoreSource,
|
||||||
child: HighlightInfoProvider(
|
child: HighlightInfoProvider(
|
||||||
|
@ -159,6 +160,7 @@ class _AvesAppState extends State<AvesApp> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -204,7 +206,7 @@ class _AvesAppState extends State<AvesApp> {
|
||||||
debugPrint('$runtimeType onNewIntent with intentData=$intentData');
|
debugPrint('$runtimeType onNewIntent with intentData=$intentData');
|
||||||
|
|
||||||
// do not reset when relaunching the app
|
// do not reset when relaunching the app
|
||||||
if (AvesApp.mode == AppMode.main && (intentData == null || intentData.isEmpty == true)) return;
|
if (appModeNotifier.value == AppMode.main && (intentData == null || intentData.isEmpty == true)) return;
|
||||||
|
|
||||||
FirebaseCrashlytics.instance.log('New intent');
|
FirebaseCrashlytics.instance.log('New intent');
|
||||||
_navigatorKey.currentState.pushReplacement(DirectMaterialPageRoute(
|
_navigatorKey.currentState.pushReplacement(DirectMaterialPageRoute(
|
||||||
|
|
|
@ -26,6 +26,7 @@ import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/scheduler.dart';
|
import 'package:flutter/scheduler.dart';
|
||||||
import 'package:pedantic/pedantic.dart';
|
import 'package:pedantic/pedantic.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
class CollectionAppBar extends StatefulWidget {
|
class CollectionAppBar extends StatefulWidget {
|
||||||
final ValueNotifier<double> appBarHeightNotifier;
|
final ValueNotifier<double> appBarHeightNotifier;
|
||||||
|
@ -141,8 +142,9 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
|
||||||
|
|
||||||
Widget _buildAppBarTitle() {
|
Widget _buildAppBarTitle() {
|
||||||
if (collection.isBrowsing) {
|
if (collection.isBrowsing) {
|
||||||
Widget title = Text(AvesApp.mode == AppMode.pick ? context.l10n.collectionPickPageTitle : context.l10n.collectionPageTitle);
|
final appMode = context.watch<ValueNotifier<AppMode>>().value;
|
||||||
if (AvesApp.mode == AppMode.main) {
|
Widget title = Text(appMode == AppMode.pick ? context.l10n.collectionPickPageTitle : context.l10n.collectionPageTitle);
|
||||||
|
if (appMode == AppMode.main) {
|
||||||
title = SourceStateAwareAppBarTitle(
|
title = SourceStateAwareAppBarTitle(
|
||||||
title: title,
|
title: title,
|
||||||
source: source,
|
source: source,
|
||||||
|
@ -191,6 +193,7 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
|
||||||
itemBuilder: (context) {
|
itemBuilder: (context) {
|
||||||
final isNotEmpty = !collection.isEmpty;
|
final isNotEmpty = !collection.isEmpty;
|
||||||
final hasSelection = collection.selection.isNotEmpty;
|
final hasSelection = collection.selection.isNotEmpty;
|
||||||
|
final isMainMode = context.read<ValueNotifier<AppMode>>().value == AppMode.main;
|
||||||
return [
|
return [
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
key: Key('menu-sort'),
|
key: Key('menu-sort'),
|
||||||
|
@ -204,7 +207,7 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
|
||||||
child: MenuRow(text: context.l10n.menuActionGroup, icon: AIcons.group),
|
child: MenuRow(text: context.l10n.menuActionGroup, icon: AIcons.group),
|
||||||
),
|
),
|
||||||
if (collection.isBrowsing) ...[
|
if (collection.isBrowsing) ...[
|
||||||
if (AvesApp.mode == AppMode.main)
|
if (isMainMode)
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
value: CollectionAction.select,
|
value: CollectionAction.select,
|
||||||
enabled: isNotEmpty,
|
enabled: isNotEmpty,
|
||||||
|
@ -215,7 +218,7 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
|
||||||
enabled: isNotEmpty,
|
enabled: isNotEmpty,
|
||||||
child: MenuRow(text: context.l10n.menuActionStats, icon: AIcons.stats),
|
child: MenuRow(text: context.l10n.menuActionStats, icon: AIcons.stats),
|
||||||
),
|
),
|
||||||
if (AvesApp.mode == AppMode.main && canAddShortcuts)
|
if (isMainMode && canAddShortcuts)
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
value: CollectionAction.addShortcut,
|
value: CollectionAction.addShortcut,
|
||||||
child: MenuRow(text: context.l10n.collectionActionAddShortcut, icon: AIcons.addShortcut),
|
child: MenuRow(text: context.l10n.collectionActionAddShortcut, icon: AIcons.addShortcut),
|
||||||
|
|
|
@ -15,6 +15,7 @@ import 'package:aves/widgets/collection/grid/section_layout.dart';
|
||||||
import 'package:aves/widgets/collection/grid/selector.dart';
|
import 'package:aves/widgets/collection/grid/selector.dart';
|
||||||
import 'package:aves/widgets/collection/grid/thumbnail.dart';
|
import 'package:aves/widgets/collection/grid/thumbnail.dart';
|
||||||
import 'package:aves/widgets/collection/thumbnail/decorated.dart';
|
import 'package:aves/widgets/collection/thumbnail/decorated.dart';
|
||||||
|
import 'package:aves/widgets/collection/thumbnail/theme.dart';
|
||||||
import 'package:aves/widgets/common/basic/draggable_scrollbar.dart';
|
import 'package:aves/widgets/common/basic/draggable_scrollbar.dart';
|
||||||
import 'package:aves/widgets/common/basic/insets.dart';
|
import 'package:aves/widgets/common/basic/insets.dart';
|
||||||
import 'package:aves/widgets/common/behaviour/sloppy_scroll_physics.dart';
|
import 'package:aves/widgets/common/behaviour/sloppy_scroll_physics.dart';
|
||||||
|
@ -65,7 +66,9 @@ class _CollectionGridContent extends StatelessWidget {
|
||||||
final sectionedListLayoutProvider = ValueListenableBuilder<double>(
|
final sectionedListLayoutProvider = ValueListenableBuilder<double>(
|
||||||
valueListenable: context.select<TileExtentController, ValueNotifier<double>>((controller) => controller.extentNotifier),
|
valueListenable: context.select<TileExtentController, ValueNotifier<double>>((controller) => controller.extentNotifier),
|
||||||
builder: (context, tileExtent, child) {
|
builder: (context, tileExtent, child) {
|
||||||
return SectionedEntryListLayoutProvider(
|
return ThumbnailTheme(
|
||||||
|
extent: tileExtent,
|
||||||
|
child: SectionedEntryListLayoutProvider(
|
||||||
collection: collection,
|
collection: collection,
|
||||||
scrollableWidth: context.select<TileExtentController, double>((controller) => controller.viewportSize.width),
|
scrollableWidth: context.select<TileExtentController, double>((controller) => controller.viewportSize.width),
|
||||||
tileExtent: tileExtent,
|
tileExtent: tileExtent,
|
||||||
|
@ -82,6 +85,7 @@ class _CollectionGridContent extends StatelessWidget {
|
||||||
isScrollingNotifier: _isScrollingNotifier,
|
isScrollingNotifier: _isScrollingNotifier,
|
||||||
scrollController: PrimaryScrollController.of(context),
|
scrollController: PrimaryScrollController.of(context),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -136,8 +140,9 @@ class _CollectionSectionedContentState extends State<_CollectionSectionedContent
|
||||||
child: scrollView,
|
child: scrollView,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
final isMainMode = context.select<ValueNotifier<AppMode>, bool>((vn) => vn.value == AppMode.main);
|
||||||
final selector = GridSelectionGestureDetector(
|
final selector = GridSelectionGestureDetector(
|
||||||
selectable: AvesApp.mode == AppMode.main,
|
selectable: isMainMode,
|
||||||
collection: collection,
|
collection: collection,
|
||||||
scrollController: scrollController,
|
scrollController: scrollController,
|
||||||
appBarHeightNotifier: _appBarHeightNotifier,
|
appBarHeightNotifier: _appBarHeightNotifier,
|
||||||
|
@ -177,12 +182,15 @@ class _CollectionScaler extends StatelessWidget {
|
||||||
),
|
),
|
||||||
child: child,
|
child: child,
|
||||||
),
|
),
|
||||||
scaledBuilder: (entry, extent) => DecoratedThumbnail(
|
scaledBuilder: (entry, extent) => ThumbnailTheme(
|
||||||
|
extent: extent,
|
||||||
|
child: DecoratedThumbnail(
|
||||||
entry: entry,
|
entry: entry,
|
||||||
extent: extent,
|
extent: extent,
|
||||||
selectable: false,
|
selectable: false,
|
||||||
highlightable: false,
|
highlightable: false,
|
||||||
),
|
),
|
||||||
|
),
|
||||||
getScaledItemTileRect: (context, entry) {
|
getScaledItemTileRect: (context, entry) {
|
||||||
final sectionedListLayout = context.read<SectionedListLayout<AvesEntry>>();
|
final sectionedListLayout = context.read<SectionedListLayout<AvesEntry>>();
|
||||||
return sectionedListLayout.getTileRect(entry) ?? Rect.zero;
|
return sectionedListLayout.getTileRect(entry) ?? Rect.zero;
|
||||||
|
|
|
@ -7,6 +7,7 @@ import 'package:aves/widgets/common/behaviour/routes.dart';
|
||||||
import 'package:aves/widgets/common/scaling.dart';
|
import 'package:aves/widgets/common/scaling.dart';
|
||||||
import 'package:aves/widgets/viewer/entry_viewer_page.dart';
|
import 'package:aves/widgets/viewer/entry_viewer_page.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
class InteractiveThumbnail extends StatelessWidget {
|
class InteractiveThumbnail extends StatelessWidget {
|
||||||
final CollectionLens collection;
|
final CollectionLens collection;
|
||||||
|
@ -27,13 +28,14 @@ class InteractiveThumbnail extends StatelessWidget {
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
key: ValueKey(entry.uri),
|
key: ValueKey(entry.uri),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
if (AvesApp.mode == AppMode.main) {
|
final appMode = context.read<ValueNotifier<AppMode>>().value;
|
||||||
|
if (appMode == AppMode.main) {
|
||||||
if (collection.isBrowsing) {
|
if (collection.isBrowsing) {
|
||||||
_goToViewer(context);
|
_goToViewer(context);
|
||||||
} else if (collection.isSelecting) {
|
} else if (collection.isSelecting) {
|
||||||
collection.toggleSelection(entry);
|
collection.toggleSelection(entry);
|
||||||
}
|
}
|
||||||
} else if (AvesApp.mode == AppMode.pick) {
|
} else if (appMode == AppMode.pick) {
|
||||||
ViewerService.pick(entry.uri);
|
ViewerService.pick(entry.uri);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -31,7 +31,8 @@ class DecoratedThumbnail extends StatelessWidget {
|
||||||
// between different views of the entry in the same collection (e.g. thumbnails <-> viewer)
|
// between different views of the entry in the same collection (e.g. thumbnails <-> viewer)
|
||||||
// but not between different collection instances, even with the same attributes (e.g. reloading collection page via drawer)
|
// but not between different collection instances, even with the same attributes (e.g. reloading collection page via drawer)
|
||||||
final heroTag = hashValues(collection?.id, entry);
|
final heroTag = hashValues(collection?.id, entry);
|
||||||
var child = entry.isSvg
|
final isSvg = entry.isSvg;
|
||||||
|
var child = isSvg
|
||||||
? VectorImageThumbnail(
|
? VectorImageThumbnail(
|
||||||
entry: entry,
|
entry: entry,
|
||||||
extent: extent,
|
extent: extent,
|
||||||
|
@ -45,17 +46,14 @@ class DecoratedThumbnail extends StatelessWidget {
|
||||||
);
|
);
|
||||||
|
|
||||||
child = Stack(
|
child = Stack(
|
||||||
alignment: Alignment.center,
|
alignment: isSvg ? Alignment.center : AlignmentDirectional.bottomStart,
|
||||||
children: [
|
children: [
|
||||||
child,
|
child,
|
||||||
Positioned(
|
if (!isSvg)
|
||||||
bottom: 0,
|
ThumbnailEntryOverlay(
|
||||||
left: 0,
|
|
||||||
child: ThumbnailEntryOverlay(
|
|
||||||
entry: entry,
|
entry: entry,
|
||||||
extent: extent,
|
extent: extent,
|
||||||
),
|
),
|
||||||
),
|
|
||||||
if (selectable)
|
if (selectable)
|
||||||
ThumbnailSelectionOverlay(
|
ThumbnailSelectionOverlay(
|
||||||
entry: entry,
|
entry: entry,
|
||||||
|
|
|
@ -2,16 +2,15 @@ import 'dart:math';
|
||||||
|
|
||||||
import 'package:aves/model/entry.dart';
|
import 'package:aves/model/entry.dart';
|
||||||
import 'package:aves/model/highlight.dart';
|
import 'package:aves/model/highlight.dart';
|
||||||
import 'package:aves/model/settings/settings.dart';
|
|
||||||
import 'package:aves/model/source/collection_lens.dart';
|
import 'package:aves/model/source/collection_lens.dart';
|
||||||
import 'package:aves/model/source/enums.dart';
|
import 'package:aves/model/source/enums.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/collection/thumbnail/theme.dart';
|
||||||
import 'package:aves/widgets/common/fx/sweeper.dart';
|
import 'package:aves/widgets/common/fx/sweeper.dart';
|
||||||
import 'package:aves/widgets/common/identity/aves_icons.dart';
|
import 'package:aves/widgets/common/identity/aves_icons.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:tuple/tuple.dart';
|
|
||||||
|
|
||||||
class ThumbnailEntryOverlay extends StatelessWidget {
|
class ThumbnailEntryOverlay extends StatelessWidget {
|
||||||
final AvesEntry entry;
|
final AvesEntry entry;
|
||||||
|
@ -25,38 +24,28 @@ class ThumbnailEntryOverlay extends StatelessWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final fontSize = min(14.0, (extent / 8)).roundToDouble();
|
final children = [
|
||||||
final iconSize = fontSize * 2;
|
if (entry.hasGps && context.select<ThumbnailThemeData, bool>((t) => t.showLocation)) GpsIcon(),
|
||||||
return Selector<Settings, Tuple3<bool, bool, bool>>(
|
if (entry.isVideo)
|
||||||
selector: (context, s) => Tuple3(s.showThumbnailLocation, s.showThumbnailRaw, s.showThumbnailVideoDuration),
|
VideoIcon(
|
||||||
builder: (context, s, child) {
|
entry: entry,
|
||||||
|
)
|
||||||
|
else if (entry.isAnimated)
|
||||||
|
AnimatedImageIcon()
|
||||||
|
else ...[
|
||||||
|
if (entry.isRaw && context.select<ThumbnailThemeData, bool>((t) => t.showRaw)) RawIcon(),
|
||||||
|
if (entry.isMultipage) MultipageIcon(),
|
||||||
|
if (entry.isGeotiff) GeotiffIcon(),
|
||||||
|
if (entry.is360) SphericalImageIcon(),
|
||||||
|
]
|
||||||
|
];
|
||||||
|
if (children.isEmpty) return SizedBox.shrink();
|
||||||
|
if (children.length == 1) return children.first;
|
||||||
return Column(
|
return Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: children,
|
||||||
if (entry.hasGps && settings.showThumbnailLocation) GpsIcon(iconSize: iconSize),
|
|
||||||
if (entry.isRaw && settings.showThumbnailRaw) RawIcon(iconSize: iconSize),
|
|
||||||
if (entry.isMultipage) MultipageIcon(iconSize: iconSize),
|
|
||||||
if (entry.isGeotiff) GeotiffIcon(iconSize: iconSize),
|
|
||||||
if (entry.isAnimated)
|
|
||||||
AnimatedImageIcon(iconSize: iconSize)
|
|
||||||
else if (entry.isVideo)
|
|
||||||
DefaultTextStyle(
|
|
||||||
style: TextStyle(
|
|
||||||
color: Colors.grey[200],
|
|
||||||
fontSize: fontSize,
|
|
||||||
),
|
|
||||||
child: VideoIcon(
|
|
||||||
entry: entry,
|
|
||||||
iconSize: iconSize,
|
|
||||||
showDuration: settings.showThumbnailVideoDuration,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
else if (entry.is360)
|
|
||||||
SphericalImageIcon(iconSize: iconSize),
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,6 +53,8 @@ class ThumbnailSelectionOverlay extends StatelessWidget {
|
||||||
final AvesEntry entry;
|
final AvesEntry entry;
|
||||||
final double extent;
|
final double extent;
|
||||||
|
|
||||||
|
static const duration = Durations.thumbnailOverlayAnimation;
|
||||||
|
|
||||||
const ThumbnailSelectionOverlay({
|
const ThumbnailSelectionOverlay({
|
||||||
Key key,
|
Key key,
|
||||||
@required this.entry,
|
@required this.entry,
|
||||||
|
@ -72,9 +63,6 @@ class ThumbnailSelectionOverlay extends StatelessWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
const duration = Durations.thumbnailOverlayAnimation;
|
|
||||||
final fontSize = min(14.0, (extent / 8)).roundToDouble();
|
|
||||||
final iconSize = fontSize * 2;
|
|
||||||
final collection = context.watch<CollectionLens>();
|
final collection = context.watch<CollectionLens>();
|
||||||
return ValueListenableBuilder<Activity>(
|
return ValueListenableBuilder<Activity>(
|
||||||
valueListenable: collection.activityNotifier,
|
valueListenable: collection.activityNotifier,
|
||||||
|
@ -88,7 +76,7 @@ class ThumbnailSelectionOverlay extends StatelessWidget {
|
||||||
? OverlayIcon(
|
? OverlayIcon(
|
||||||
key: ValueKey(selected),
|
key: ValueKey(selected),
|
||||||
icon: selected ? AIcons.selected : AIcons.unselected,
|
icon: selected ? AIcons.selected : AIcons.unselected,
|
||||||
size: iconSize,
|
size: context.select<ThumbnailThemeData, double>((t) => t.iconSize),
|
||||||
)
|
)
|
||||||
: SizedBox.shrink();
|
: SizedBox.shrink();
|
||||||
child = AnimatedSwitcher(
|
child = AnimatedSwitcher(
|
||||||
|
@ -139,6 +127,8 @@ class _ThumbnailHighlightOverlayState extends State<ThumbnailHighlightOverlay> {
|
||||||
|
|
||||||
AvesEntry get entry => widget.entry;
|
AvesEntry get entry => widget.entry;
|
||||||
|
|
||||||
|
static const startAngle = pi * -3 / 4;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final highlightInfo = context.watch<HighlightInfo>();
|
final highlightInfo = context.watch<HighlightInfo>();
|
||||||
|
@ -153,7 +143,7 @@ class _ThumbnailHighlightOverlayState extends State<ThumbnailHighlightOverlay> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
toggledNotifier: _highlightedNotifier,
|
toggledNotifier: _highlightedNotifier,
|
||||||
startAngle: pi * -3 / 4,
|
startAngle: startAngle,
|
||||||
centerSweep: false,
|
centerSweep: false,
|
||||||
onSweepEnd: highlightInfo.clear,
|
onSweepEnd: highlightInfo.clear,
|
||||||
);
|
);
|
||||||
|
|
46
lib/widgets/collection/thumbnail/theme.dart
Normal file
46
lib/widgets/collection/thumbnail/theme.dart
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
import 'dart:math';
|
||||||
|
|
||||||
|
import 'package:aves/model/settings/settings.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
class ThumbnailTheme extends StatelessWidget {
|
||||||
|
final double extent;
|
||||||
|
final Widget child;
|
||||||
|
|
||||||
|
const ThumbnailTheme({
|
||||||
|
@required this.extent,
|
||||||
|
@required this.child,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return ProxyProvider<Settings, ThumbnailThemeData>(
|
||||||
|
update: (_, settings, __) {
|
||||||
|
final iconSize = min(28.0, (extent / 4)).roundToDouble();
|
||||||
|
final fontSize = (iconSize / 2).floorToDouble();
|
||||||
|
return ThumbnailThemeData(
|
||||||
|
iconSize: iconSize,
|
||||||
|
fontSize: fontSize,
|
||||||
|
showLocation: settings.showThumbnailLocation,
|
||||||
|
showRaw: settings.showThumbnailRaw,
|
||||||
|
showVideoDuration: settings.showThumbnailVideoDuration,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ThumbnailThemeData {
|
||||||
|
final double iconSize, fontSize;
|
||||||
|
final bool showLocation, showRaw, showVideoDuration;
|
||||||
|
|
||||||
|
const ThumbnailThemeData({
|
||||||
|
@required this.iconSize,
|
||||||
|
@required this.fontSize,
|
||||||
|
@required this.showLocation,
|
||||||
|
@required this.showRaw,
|
||||||
|
@required this.showVideoDuration,
|
||||||
|
});
|
||||||
|
}
|
|
@ -11,6 +11,7 @@ import 'package:aves/widgets/common/basic/menu_row.dart';
|
||||||
import 'package:aves/widgets/filter_grids/common/chip_action_delegate.dart';
|
import 'package:aves/widgets/filter_grids/common/chip_action_delegate.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/scheduler.dart';
|
import 'package:flutter/scheduler.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
typedef FilterCallback = void Function(CollectionFilter filter);
|
typedef FilterCallback = void Function(CollectionFilter filter);
|
||||||
typedef OffsetFilterCallback = void Function(BuildContext context, CollectionFilter filter, Offset tapPosition);
|
typedef OffsetFilterCallback = void Function(BuildContext context, CollectionFilter filter, Offset tapPosition);
|
||||||
|
@ -52,7 +53,7 @@ class AvesFilterChip extends StatefulWidget {
|
||||||
super(key: key);
|
super(key: key);
|
||||||
|
|
||||||
static Future<void> showDefaultLongPressMenu(BuildContext context, CollectionFilter filter, Offset tapPosition) async {
|
static Future<void> showDefaultLongPressMenu(BuildContext context, CollectionFilter filter, Offset tapPosition) async {
|
||||||
if (AvesApp.mode == AppMode.main) {
|
if (context.read<ValueNotifier<AppMode>>().value == AppMode.main) {
|
||||||
final actions = [
|
final actions = [
|
||||||
if (filter is AlbumFilter) ChipAction.goToAlbumPage,
|
if (filter is AlbumFilter) ChipAction.goToAlbumPage,
|
||||||
if ((filter is LocationFilter && filter.level == LocationLevel.country)) ChipAction.goToCountryPage,
|
if ((filter is LocationFilter && filter.level == LocationLevel.country)) ChipAction.goToCountryPage,
|
||||||
|
|
|
@ -5,114 +5,111 @@ import 'package:aves/model/entry.dart';
|
||||||
import 'package:aves/theme/icons.dart';
|
import 'package:aves/theme/icons.dart';
|
||||||
import 'package:aves/utils/android_file_utils.dart';
|
import 'package:aves/utils/android_file_utils.dart';
|
||||||
import 'package:aves/utils/constants.dart';
|
import 'package:aves/utils/constants.dart';
|
||||||
|
import 'package:aves/widgets/collection/thumbnail/theme.dart';
|
||||||
import 'package:decorated_icon/decorated_icon.dart';
|
import 'package:decorated_icon/decorated_icon.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
class VideoIcon extends StatelessWidget {
|
class VideoIcon extends StatelessWidget {
|
||||||
final AvesEntry entry;
|
final AvesEntry entry;
|
||||||
final double iconSize;
|
|
||||||
final bool showDuration;
|
|
||||||
|
|
||||||
const VideoIcon({
|
const VideoIcon({
|
||||||
Key key,
|
Key key,
|
||||||
this.entry,
|
this.entry,
|
||||||
this.iconSize,
|
|
||||||
this.showDuration,
|
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return OverlayIcon(
|
final thumbnailTheme = context.watch<ThumbnailThemeData>();
|
||||||
|
final showDuration = thumbnailTheme.showVideoDuration;
|
||||||
|
Widget child = OverlayIcon(
|
||||||
icon: entry.is360 ? AIcons.threesixty : AIcons.play,
|
icon: entry.is360 ? AIcons.threesixty : AIcons.play,
|
||||||
size: iconSize,
|
size: thumbnailTheme.iconSize,
|
||||||
text: showDuration ? entry.durationText : null,
|
text: showDuration ? entry.durationText : null,
|
||||||
iconScale: entry.is360 && showDuration ? .9 : 1,
|
iconScale: entry.is360 && showDuration ? .9 : 1,
|
||||||
);
|
);
|
||||||
|
if (showDuration) {
|
||||||
|
child = DefaultTextStyle(
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.grey[200],
|
||||||
|
fontSize: thumbnailTheme.fontSize,
|
||||||
|
),
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return child;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class AnimatedImageIcon extends StatelessWidget {
|
class AnimatedImageIcon extends StatelessWidget {
|
||||||
final double iconSize;
|
const AnimatedImageIcon({Key key}) : super(key: key);
|
||||||
|
|
||||||
const AnimatedImageIcon({Key key, this.iconSize}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return OverlayIcon(
|
return OverlayIcon(
|
||||||
icon: AIcons.animated,
|
icon: AIcons.animated,
|
||||||
size: iconSize,
|
size: context.select<ThumbnailThemeData, double>((t) => t.iconSize),
|
||||||
iconScale: .8,
|
iconScale: .8,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class GeotiffIcon extends StatelessWidget {
|
class GeotiffIcon extends StatelessWidget {
|
||||||
final double iconSize;
|
const GeotiffIcon({Key key}) : super(key: key);
|
||||||
|
|
||||||
const GeotiffIcon({Key key, this.iconSize}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return OverlayIcon(
|
return OverlayIcon(
|
||||||
icon: AIcons.geo,
|
icon: AIcons.geo,
|
||||||
size: iconSize,
|
size: context.select<ThumbnailThemeData, double>((t) => t.iconSize),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class SphericalImageIcon extends StatelessWidget {
|
class SphericalImageIcon extends StatelessWidget {
|
||||||
final double iconSize;
|
const SphericalImageIcon({Key key}) : super(key: key);
|
||||||
|
|
||||||
const SphericalImageIcon({Key key, this.iconSize}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return OverlayIcon(
|
return OverlayIcon(
|
||||||
icon: AIcons.threesixty,
|
icon: AIcons.threesixty,
|
||||||
size: iconSize,
|
size: context.select<ThumbnailThemeData, double>((t) => t.iconSize),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class GpsIcon extends StatelessWidget {
|
class GpsIcon extends StatelessWidget {
|
||||||
final double iconSize;
|
const GpsIcon({Key key}) : super(key: key);
|
||||||
|
|
||||||
const GpsIcon({Key key, this.iconSize}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return OverlayIcon(
|
return OverlayIcon(
|
||||||
icon: AIcons.location,
|
icon: AIcons.location,
|
||||||
size: iconSize,
|
size: context.select<ThumbnailThemeData, double>((t) => t.iconSize),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class RawIcon extends StatelessWidget {
|
class RawIcon extends StatelessWidget {
|
||||||
final double iconSize;
|
const RawIcon({Key key}) : super(key: key);
|
||||||
|
|
||||||
const RawIcon({Key key, this.iconSize}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return OverlayIcon(
|
return OverlayIcon(
|
||||||
icon: AIcons.raw,
|
icon: AIcons.raw,
|
||||||
size: iconSize,
|
size: context.select<ThumbnailThemeData, double>((t) => t.iconSize),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class MultipageIcon extends StatelessWidget {
|
class MultipageIcon extends StatelessWidget {
|
||||||
final double iconSize;
|
const MultipageIcon({Key key}) : super(key: key);
|
||||||
|
|
||||||
const MultipageIcon({Key key, this.iconSize}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return OverlayIcon(
|
return OverlayIcon(
|
||||||
icon: AIcons.multipage,
|
icon: AIcons.multipage,
|
||||||
size: iconSize,
|
size: context.select<ThumbnailThemeData, double>((t) => t.iconSize),
|
||||||
iconScale: .8,
|
iconScale: .8,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,7 @@ import 'package:aves/widgets/search/search_delegate.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/scheduler.dart';
|
import 'package:flutter/scheduler.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
class FilterNavigationPage<T extends CollectionFilter> extends StatelessWidget {
|
class FilterNavigationPage<T extends CollectionFilter> extends StatelessWidget {
|
||||||
final CollectionSource source;
|
final CollectionSource source;
|
||||||
|
@ -47,6 +48,7 @@ class FilterNavigationPage<T extends CollectionFilter> extends StatelessWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final isMainMode = context.select<ValueNotifier<AppMode>, bool>((vn) => vn.value == AppMode.main);
|
||||||
return FilterGridPage<T>(
|
return FilterGridPage<T>(
|
||||||
key: ValueKey('filter-grid-page'),
|
key: ValueKey('filter-grid-page'),
|
||||||
appBar: SliverAppBar(
|
appBar: SliverAppBar(
|
||||||
|
@ -80,7 +82,7 @@ class FilterNavigationPage<T extends CollectionFilter> extends StatelessWidget {
|
||||||
)),
|
)),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
onLongPress: AvesApp.mode == AppMode.main ? _showMenu : null,
|
onLongPress: isMainMode ? _showMenu : null,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -66,7 +66,7 @@ class _HomePageState extends State<HomePage> {
|
||||||
await androidFileUtils.init();
|
await androidFileUtils.init();
|
||||||
unawaited(androidFileUtils.initAppNames());
|
unawaited(androidFileUtils.initAppNames());
|
||||||
|
|
||||||
AvesApp.mode = AppMode.main;
|
var appMode = AppMode.main;
|
||||||
final intentData = widget.intentData ?? await ViewerService.getIntentData();
|
final intentData = widget.intentData ?? await ViewerService.getIntentData();
|
||||||
if (intentData?.isNotEmpty == true) {
|
if (intentData?.isNotEmpty == true) {
|
||||||
final action = intentData['action'];
|
final action = intentData['action'];
|
||||||
|
@ -77,11 +77,11 @@ class _HomePageState extends State<HomePage> {
|
||||||
mimeType: intentData['mimeType'],
|
mimeType: intentData['mimeType'],
|
||||||
);
|
);
|
||||||
if (_viewerEntry != null) {
|
if (_viewerEntry != null) {
|
||||||
AvesApp.mode = AppMode.view;
|
appMode = AppMode.view;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'pick':
|
case 'pick':
|
||||||
AvesApp.mode = AppMode.pick;
|
appMode = AppMode.pick;
|
||||||
// TODO TLAD apply pick mimetype(s)
|
// TODO TLAD apply pick mimetype(s)
|
||||||
// some apps define multiple types, separated by a space (maybe other signs too, like `,` `;`?)
|
// some apps define multiple types, separated by a space (maybe other signs too, like `,` `;`?)
|
||||||
String pickMimeTypes = intentData['mimeType'];
|
String pickMimeTypes = intentData['mimeType'];
|
||||||
|
@ -97,15 +97,16 @@ class _HomePageState extends State<HomePage> {
|
||||||
_shortcutFilters = extraFilters != null ? (extraFilters as List).cast<String>() : null;
|
_shortcutFilters = extraFilters != null ? (extraFilters as List).cast<String>() : null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
unawaited(FirebaseCrashlytics.instance.setCustomKey('app_mode', AvesApp.mode.toString()));
|
context.read<ValueNotifier<AppMode>>().value = appMode;
|
||||||
|
unawaited(FirebaseCrashlytics.instance.setCustomKey('app_mode', appMode.toString()));
|
||||||
|
|
||||||
if (AvesApp.mode != AppMode.view) {
|
if (appMode != AppMode.view) {
|
||||||
final source = context.read<CollectionSource>();
|
final source = context.read<CollectionSource>();
|
||||||
await source.init();
|
await source.init();
|
||||||
unawaited(source.refresh());
|
unawaited(source.refresh());
|
||||||
}
|
}
|
||||||
|
|
||||||
unawaited(Navigator.pushReplacement(context, _getRedirectRoute()));
|
unawaited(Navigator.pushReplacement(context, _getRedirectRoute(appMode)));
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<AvesEntry> _initViewerEntry({@required String uri, @required String mimeType}) async {
|
Future<AvesEntry> _initViewerEntry({@required String uri, @required String mimeType}) async {
|
||||||
|
@ -117,8 +118,8 @@ class _HomePageState extends State<HomePage> {
|
||||||
return entry;
|
return entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
Route _getRedirectRoute() {
|
Route _getRedirectRoute(AppMode appMode) {
|
||||||
if (AvesApp.mode == AppMode.view) {
|
if (appMode == AppMode.view) {
|
||||||
return DirectMaterialPageRoute(
|
return DirectMaterialPageRoute(
|
||||||
settings: RouteSettings(name: EntryViewerPage.routeName),
|
settings: RouteSettings(name: EntryViewerPage.routeName),
|
||||||
builder: (_) => EntryViewerPage(
|
builder: (_) => EntryViewerPage(
|
||||||
|
@ -129,7 +130,7 @@ class _HomePageState extends State<HomePage> {
|
||||||
|
|
||||||
String routeName;
|
String routeName;
|
||||||
Iterable<CollectionFilter> filters;
|
Iterable<CollectionFilter> filters;
|
||||||
if (AvesApp.mode == AppMode.pick) {
|
if (appMode == AppMode.pick) {
|
||||||
routeName = CollectionPage.routeName;
|
routeName = CollectionPage.routeName;
|
||||||
} else {
|
} else {
|
||||||
routeName = _shortcutRouteName ?? settings.homePage.routeName;
|
routeName = _shortcutRouteName ?? settings.homePage.routeName;
|
||||||
|
|
|
@ -8,6 +8,7 @@ import 'package:aves/widgets/viewer/debug/metadata.dart';
|
||||||
import 'package:aves/widgets/viewer/info/common.dart';
|
import 'package:aves/widgets/viewer/info/common.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_svg/flutter_svg.dart';
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
import 'package:tuple/tuple.dart';
|
import 'package:tuple/tuple.dart';
|
||||||
|
|
||||||
class ViewerDebugPage extends StatelessWidget {
|
class ViewerDebugPage extends StatelessWidget {
|
||||||
|
@ -21,7 +22,7 @@ class ViewerDebugPage extends StatelessWidget {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final tabs = <Tuple2<Tab, Widget>>[
|
final tabs = <Tuple2<Tab, Widget>>[
|
||||||
Tuple2(Tab(text: 'Entry'), _buildEntryTabView()),
|
Tuple2(Tab(text: 'Entry'), _buildEntryTabView()),
|
||||||
if (AvesApp.mode != AppMode.view) Tuple2(Tab(text: 'DB'), DbTab(entry: entry)),
|
if (context.select<ValueNotifier<AppMode>, bool>((vn) => vn.value != AppMode.view)) Tuple2(Tab(text: 'DB'), DbTab(entry: entry)),
|
||||||
Tuple2(Tab(icon: Icon(AIcons.android)), MetadataTab(entry: entry)),
|
Tuple2(Tab(icon: Icon(AIcons.android)), MetadataTab(entry: entry)),
|
||||||
Tuple2(Tab(icon: Icon(AIcons.image)), _buildThumbnailsTabView()),
|
Tuple2(Tab(icon: Icon(AIcons.image)), _buildThumbnailsTabView()),
|
||||||
];
|
];
|
||||||
|
|
|
@ -4,6 +4,7 @@ import 'package:aves/model/entry.dart';
|
||||||
import 'package:aves/model/multipage.dart';
|
import 'package:aves/model/multipage.dart';
|
||||||
import 'package:aves/theme/durations.dart';
|
import 'package:aves/theme/durations.dart';
|
||||||
import 'package:aves/widgets/collection/thumbnail/decorated.dart';
|
import 'package:aves/widgets/collection/thumbnail/decorated.dart';
|
||||||
|
import 'package:aves/widgets/collection/thumbnail/theme.dart';
|
||||||
import 'package:aves/widgets/viewer/multipage.dart';
|
import 'package:aves/widgets/viewer/multipage.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
@ -80,7 +81,9 @@ class _MultiPageOverlayState extends State<MultiPageOverlay> {
|
||||||
final horizontalMargin = SizedBox(width: marginWidth);
|
final horizontalMargin = SizedBox(width: marginWidth);
|
||||||
final separator = SizedBox(width: separatorWidth);
|
final separator = SizedBox(width: separatorWidth);
|
||||||
|
|
||||||
return FutureBuilder<MultiPageInfo>(
|
return ThumbnailTheme(
|
||||||
|
extent: extent,
|
||||||
|
child: FutureBuilder<MultiPageInfo>(
|
||||||
future: controller.info,
|
future: controller.info,
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
final multiPageInfo = snapshot.data;
|
final multiPageInfo = snapshot.data;
|
||||||
|
@ -140,6 +143,7 @@ class _MultiPageOverlayState extends State<MultiPageOverlay> {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -559,7 +559,7 @@ packages:
|
||||||
name: overlay_support
|
name: overlay_support
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.2.0-nullsafety.0"
|
version: "1.2.0"
|
||||||
package_config:
|
package_config:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -706,7 +706,7 @@ packages:
|
||||||
name: printing
|
name: printing
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "5.0.2"
|
version: "5.0.3"
|
||||||
process:
|
process:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -1026,7 +1026,7 @@ packages:
|
||||||
name: version
|
name: version
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.1"
|
version: "2.0.0"
|
||||||
vm_service:
|
vm_service:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -1068,7 +1068,7 @@ packages:
|
||||||
name: win32
|
name: win32
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.3"
|
version: "2.0.4"
|
||||||
wkt_parser:
|
wkt_parser:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
|
@ -7,8 +7,6 @@ publish_to: none
|
||||||
environment:
|
environment:
|
||||||
sdk: '>=2.10.0 <3.0.0'
|
sdk: '>=2.10.0 <3.0.0'
|
||||||
|
|
||||||
# TODO TLAD remove explicit `overlay_support` version when 1.2.0 is stable (1.0.5 uses deprecated `ancestorWidgetOfExactType`)
|
|
||||||
|
|
||||||
# TODO TLAD switch to Flutter stable when possible, currently on dev/beta because of the following mess:
|
# TODO TLAD switch to Flutter stable when possible, currently on dev/beta because of the following mess:
|
||||||
# printing >=5.0.1 depends on pdf ^3.0.1, pdf >=3.0.1 depends on crypto ^3.0.0 and archive ^3.1.0
|
# printing >=5.0.1 depends on pdf ^3.0.1, pdf >=3.0.1 depends on crypto ^3.0.0 and archive ^3.1.0
|
||||||
# but `flutter_driver` (shipped with Flutter) dependencies are too old in stable v2.0.1
|
# but `flutter_driver` (shipped with Flutter) dependencies are too old in stable v2.0.1
|
||||||
|
@ -57,7 +55,7 @@ dependencies:
|
||||||
intl:
|
intl:
|
||||||
latlong:
|
latlong:
|
||||||
material_design_icons_flutter:
|
material_design_icons_flutter:
|
||||||
overlay_support: 1.2.0-nullsafety.0
|
overlay_support:
|
||||||
package_info:
|
package_info:
|
||||||
palette_generator:
|
palette_generator:
|
||||||
panorama:
|
panorama:
|
||||||
|
@ -100,9 +98,6 @@ flutter:
|
||||||
# generate `AppLocalizations`
|
# generate `AppLocalizations`
|
||||||
# % flutter gen-l10n
|
# % flutter gen-l10n
|
||||||
|
|
||||||
# list untranslated messages
|
|
||||||
# % flutter gen-l10n --untranslated-messages-file untranslated.json
|
|
||||||
|
|
||||||
################################################################################
|
################################################################################
|
||||||
# Test driver
|
# Test driver
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue