This commit is contained in:
Thibault Deckers 2019-12-24 10:41:43 +09:00
parent f965e329ad
commit cb28ad9272
33 changed files with 212 additions and 209 deletions

View file

@ -3,3 +3,10 @@ include: package:pedantic/analysis_options.yaml
analyzer: analyzer:
exclude: exclude:
- lib/generated_plugin_registrant.dart - lib/generated_plugin_registrant.dart
linter:
rules:
- always_declare_return_types
- prefer_const_constructors
- prefer_const_constructors_in_immutables
- prefer_const_declarations

View file

@ -16,10 +16,10 @@ import 'package:pedantic/pedantic.dart';
import 'package:permission_handler/permission_handler.dart'; import 'package:permission_handler/permission_handler.dart';
import 'package:screen/screen.dart'; import 'package:screen/screen.dart';
final stopwatch = Stopwatch()..start(); final _stopwatch = Stopwatch()..start();
void main() { void main() {
debugPrint('main start, elapsed=${stopwatch.elapsed}'); debugPrint('main start, elapsed=${_stopwatch.elapsed}');
// initialize binding/plugins to configure Skia before `runApp` // initialize binding/plugins to configure Skia before `runApp`
WidgetsFlutterBinding.ensureInitialized(); // 220ms WidgetsFlutterBinding.ensureInitialized(); // 220ms
// debugPrint('main WidgetsFlutterBinding.ensureInitialized done, elapsed=${stopwatch.elapsed}'); // debugPrint('main WidgetsFlutterBinding.ensureInitialized done, elapsed=${stopwatch.elapsed}');
@ -61,7 +61,7 @@ class HomePage extends StatefulWidget {
class _HomePageState extends State<HomePage> { class _HomePageState extends State<HomePage> {
static const EventChannel eventChannel = EventChannel('deckers.thibault/aves/mediastore'); static const EventChannel eventChannel = EventChannel('deckers.thibault/aves/mediastore');
ImageCollection localMediaCollection = ImageCollection(entries: List()); ImageCollection localMediaCollection = ImageCollection(entries: []);
@override @override
void initState() { void initState() {
@ -72,7 +72,7 @@ class _HomePageState extends State<HomePage> {
} }
Future<void> setup() async { Future<void> setup() async {
debugPrint('$runtimeType setup start, elapsed=${stopwatch.elapsed}'); debugPrint('$runtimeType setup start, elapsed=${_stopwatch.elapsed}');
// TODO reduce permission check time // TODO reduce permission check time
final permissions = await PermissionHandler().requestPermissions([ final permissions = await PermissionHandler().requestPermissions([
PermissionGroup.storage PermissionGroup.storage
@ -91,7 +91,7 @@ class _HomePageState extends State<HomePage> {
await settings.init(); // <20ms await settings.init(); // <20ms
localMediaCollection.groupFactor = settings.collectionGroupFactor; localMediaCollection.groupFactor = settings.collectionGroupFactor;
localMediaCollection.sortFactor = settings.collectionSortFactor; localMediaCollection.sortFactor = settings.collectionSortFactor;
debugPrint('$runtimeType setup settings.init done, elapsed=${stopwatch.elapsed}'); debugPrint('$runtimeType setup settings.init done, elapsed=${_stopwatch.elapsed}');
await metadataDb.init(); // <20ms await metadataDb.init(); // <20ms
final currentTimeZone = await FlutterNativeTimezone.getLocalTimezone(); // <20ms final currentTimeZone = await FlutterNativeTimezone.getLocalTimezone(); // <20ms
@ -107,7 +107,7 @@ class _HomePageState extends State<HomePage> {
eventChannel.receiveBroadcastStream().cast<Map>().listen( eventChannel.receiveBroadcastStream().cast<Map>().listen(
(entryMap) => localMediaCollection.add(ImageEntry.fromMap(entryMap)), (entryMap) => localMediaCollection.add(ImageEntry.fromMap(entryMap)),
onDone: () async { onDone: () async {
debugPrint('$runtimeType mediastore stream done, elapsed=${stopwatch.elapsed}'); debugPrint('$runtimeType mediastore stream done, elapsed=${_stopwatch.elapsed}');
localMediaCollection.updateSections(); // <50ms localMediaCollection.updateSections(); // <50ms
// TODO reduce setup time until here // TODO reduce setup time until here
localMediaCollection.updateAlbums(); // <50ms localMediaCollection.updateAlbums(); // <50ms
@ -115,7 +115,7 @@ class _HomePageState extends State<HomePage> {
await localMediaCollection.catalogEntries(); // <50ms await localMediaCollection.catalogEntries(); // <50ms
await localMediaCollection.loadAddresses(); // 350ms await localMediaCollection.loadAddresses(); // 350ms
await localMediaCollection.locateEntries(); // <50ms await localMediaCollection.locateEntries(); // <50ms
debugPrint('$runtimeType setup end, elapsed=${stopwatch.elapsed}'); debugPrint('$runtimeType setup end, elapsed=${_stopwatch.elapsed}');
}, },
onError: (error) => debugPrint('$runtimeType mediastore stream error=$error'), onError: (error) => debugPrint('$runtimeType mediastore stream error=$error'),
); );

View file

@ -8,11 +8,11 @@ import 'package:path/path.dart';
class ImageCollection with ChangeNotifier { class ImageCollection with ChangeNotifier {
final List<ImageEntry> _rawEntries; final List<ImageEntry> _rawEntries;
Map<dynamic, List<ImageEntry>> sections = Map(); Map<dynamic, List<ImageEntry>> sections = {};
GroupFactor groupFactor = GroupFactor.month; GroupFactor groupFactor = GroupFactor.month;
SortFactor sortFactor = SortFactor.date; SortFactor sortFactor = SortFactor.date;
List<String> sortedAlbums = List.unmodifiable(Iterable.empty()); List<String> sortedAlbums = List.unmodifiable(const Iterable.empty());
List<String> sortedTags = List.unmodifiable(Iterable.empty()); List<String> sortedTags = List.unmodifiable(const Iterable.empty());
ImageCollection({ ImageCollection({
@required List<ImageEntry> entries, @required List<ImageEntry> entries,
@ -140,7 +140,7 @@ class ImageCollection with ChangeNotifier {
Future<void> catalogEntries() async { Future<void> catalogEntries() async {
final start = DateTime.now(); final start = DateTime.now();
final uncataloguedEntries = _rawEntries.where((entry) => !entry.isCatalogued).toList(); final uncataloguedEntries = _rawEntries.where((entry) => !entry.isCatalogued).toList();
final newMetadata = List<CatalogMetadata>(); final newMetadata = <CatalogMetadata>[];
await Future.forEach<ImageEntry>(uncataloguedEntries, (entry) async { await Future.forEach<ImageEntry>(uncataloguedEntries, (entry) async {
await entry.catalog(); await entry.catalog();
newMetadata.add(entry.catalogMetadata); newMetadata.add(entry.catalogMetadata);
@ -153,7 +153,7 @@ class ImageCollection with ChangeNotifier {
Future<void> locateEntries() async { Future<void> locateEntries() async {
final start = DateTime.now(); final start = DateTime.now();
final unlocatedEntries = _rawEntries.where((entry) => entry.hasGps && !entry.isLocated).toList(); final unlocatedEntries = _rawEntries.where((entry) => entry.hasGps && !entry.isLocated).toList();
final newAddresses = List<AddressDetails>(); final newAddresses = <AddressDetails>[];
await Future.forEach<ImageEntry>(unlocatedEntries, (entry) async { await Future.forEach<ImageEntry>(unlocatedEntries, (entry) async {
await entry.locate(); await entry.locate();
newAddresses.add(entry.addressDetails); newAddresses.add(entry.addressDetails);

View file

@ -1,5 +1,3 @@
import 'dart:collection';
import 'package:aves/model/image_file_service.dart'; import 'package:aves/model/image_file_service.dart';
import 'package:aves/model/image_metadata.dart'; import 'package:aves/model/image_metadata.dart';
import 'package:aves/model/metadata_service.dart'; import 'package:aves/model/metadata_service.dart';
@ -50,19 +48,19 @@ class ImageEntry {
factory ImageEntry.fromMap(Map map) { factory ImageEntry.fromMap(Map map) {
return ImageEntry( return ImageEntry(
uri: map['uri'], uri: map['uri'] as String,
path: map['path'], path: map['path'] as String,
contentId: map['contentId'], contentId: map['contentId'] as int,
mimeType: map['mimeType'], mimeType: map['mimeType'] as String,
width: map['width'], width: map['width'] as int,
height: map['height'], height: map['height'] as int,
orientationDegrees: map['orientationDegrees'], orientationDegrees: map['orientationDegrees'] as int,
sizeBytes: map['sizeBytes'], sizeBytes: map['sizeBytes'] as int,
title: map['title'], title: map['title'] as String,
dateModifiedSecs: map['dateModifiedSecs'], dateModifiedSecs: map['dateModifiedSecs'] as int,
sourceDateTakenMillis: map['sourceDateTakenMillis'], sourceDateTakenMillis: map['sourceDateTakenMillis'] as int,
bucketDisplayName: map['bucketDisplayName'], bucketDisplayName: map['bucketDisplayName'] as String,
durationMillis: map['durationMillis'], durationMillis: map['durationMillis'] as int,
); );
} }
@ -180,13 +178,11 @@ class ImageEntry {
// admin area examples: Seoul, Geneva, null // admin area examples: Seoul, Geneva, null
// locality examples: Mapo-gu, Geneva, Annecy // locality examples: Mapo-gu, Geneva, Annecy
return LinkedHashSet.of( return {
[
addressDetails.countryName, addressDetails.countryName,
addressDetails.adminArea, addressDetails.adminArea,
addressDetails.locality addressDetails.locality
], }.where((part) => part != null && part.isNotEmpty).join(', ');
).where((part) => part != null && part.isNotEmpty).join(', ');
} }
bool search(String query) { bool search(String query) {
@ -203,13 +199,13 @@ class ImageEntry {
if (newFields.isEmpty) return false; if (newFields.isEmpty) return false;
final uri = newFields['uri']; final uri = newFields['uri'];
if (uri != null) this.uri = uri; if (uri is String) this.uri = uri;
final path = newFields['path']; final path = newFields['path'];
if (path != null) this.path = path; if (path is String) this.path = path;
final contentId = newFields['contentId']; final contentId = newFields['contentId'];
if (contentId != null) this.contentId = contentId; if (contentId is int) this.contentId = contentId;
final title = newFields['title']; final title = newFields['title'];
if (title != null) this.title = title; if (title is String) this.title = title;
metadataChangeNotifier.notifyListeners(); metadataChangeNotifier.notifyListeners();
return true; return true;
} }
@ -223,11 +219,11 @@ class ImageEntry {
if (newFields.isEmpty) return false; if (newFields.isEmpty) return false;
final width = newFields['width']; final width = newFields['width'];
if (width != null) this.width = width; if (width is int) this.width = width;
final height = newFields['height']; final height = newFields['height'];
if (height != null) this.height = height; if (height is int) this.height = height;
final orientationDegrees = newFields['orientationDegrees']; final orientationDegrees = newFields['orientationDegrees'];
if (orientationDegrees != null) this.orientationDegrees = orientationDegrees; if (orientationDegrees is int) this.orientationDegrees = orientationDegrees;
imageChangeNotifier.notifyListeners(); imageChangeNotifier.notifyListeners();
return true; return true;
} }

View file

@ -65,7 +65,7 @@ class ImageFileService {
} on PlatformException catch (e) { } on PlatformException catch (e) {
debugPrint('rename failed with exception=${e.message}'); debugPrint('rename failed with exception=${e.message}');
} }
return Map(); return {};
} }
static Future<Map> rotate(ImageEntry entry, {@required bool clockwise}) async { static Future<Map> rotate(ImageEntry entry, {@required bool clockwise}) async {
@ -79,6 +79,6 @@ class ImageFileService {
} on PlatformException catch (e) { } on PlatformException catch (e) {
debugPrint('rotate failed with exception=${e.message}'); debugPrint('rotate failed with exception=${e.message}');
} }
return Map(); return {};
} }
} }

View file

@ -10,8 +10,8 @@ class MetadataDb {
Future<String> get path async => join(await getDatabasesPath(), 'metadata.db'); Future<String> get path async => join(await getDatabasesPath(), 'metadata.db');
static final metadataTable = 'metadata'; static const metadataTable = 'metadata';
static final addressTable = 'address'; static const addressTable = 'address';
MetadataDb._private(); MetadataDb._private();

View file

@ -17,7 +17,7 @@ class MetadataService {
} on PlatformException catch (e) { } on PlatformException catch (e) {
debugPrint('getAllMetadata failed with exception=${e.message}'); debugPrint('getAllMetadata failed with exception=${e.message}');
} }
return Map(); return {};
} }
static Future<CatalogMetadata> getCatalogMetadata(ImageEntry entry) async { static Future<CatalogMetadata> getCatalogMetadata(ImageEntry entry) async {

View file

@ -5,12 +5,12 @@ import 'package:shared_preferences/shared_preferences.dart';
final Settings settings = Settings._private(); final Settings settings = Settings._private();
typedef void SettingsCallback(String key, dynamic oldValue, dynamic newValue); typedef SettingsCallback = void Function(String key, dynamic oldValue, dynamic newValue);
class Settings { class Settings {
static SharedPreferences prefs; static SharedPreferences prefs;
ObserverList<SettingsCallback> _listeners = ObserverList<SettingsCallback>(); final ObserverList<SettingsCallback> _listeners = ObserverList<SettingsCallback>();
Settings._private(); Settings._private();
@ -32,7 +32,7 @@ class Settings {
debugPrint('$runtimeType notifyListeners key=$key, old=$oldValue, new=$newValue'); debugPrint('$runtimeType notifyListeners key=$key, old=$oldValue, new=$newValue');
if (_listeners != null) { if (_listeners != null) {
final List<SettingsCallback> localListeners = _listeners.toList(); final List<SettingsCallback> localListeners = _listeners.toList();
for (SettingsCallback listener in localListeners) { for (final listener in localListeners) {
try { try {
if (_listeners.contains(listener)) { if (_listeners.contains(listener)) {
listener(key, oldValue, newValue); listener(key, oldValue, newValue);
@ -66,7 +66,7 @@ class Settings {
T getEnumOrDefault<T>(String key, T defaultValue, Iterable<T> values) { T getEnumOrDefault<T>(String key, T defaultValue, Iterable<T> values) {
final valueString = prefs.getString(key); final valueString = prefs.getString(key);
for (T element in values) { for (final element in values) {
if (element.toString() == valueString) { if (element.toString() == valueString) {
return element; return element;
} }

View file

@ -13,7 +13,7 @@ class AndroidAppService {
} on PlatformException catch (e) { } on PlatformException catch (e) {
debugPrint('getAppNames failed with exception=${e.message}'); debugPrint('getAppNames failed with exception=${e.message}');
} }
return Map(); return {};
} }
static Future<Uint8List> getAppIcon(String packageName, int size) async { static Future<Uint8List> getAppIcon(String packageName, int size) async {

View file

@ -27,7 +27,7 @@ String _decimal2sexagesimal(final double dec) {
final List<int> minParts = _split(min); final List<int> minParts = _split(min);
final int minFractionalPart = minParts[1]; final int minFractionalPart = minParts[1];
final double sec = (double.parse('0.$minFractionalPart') * 60); final double sec = double.parse('0.$minFractionalPart') * 60;
return '$deg° ${min.floor()} ${_round(sec, decimals: 2).toStringAsFixed(2)}'; return '$deg° ${min.floor()} ${_round(sec, decimals: 2).toStringAsFixed(2)}';
} }

View file

@ -6,7 +6,7 @@ bool isAtSameDayAs(DateTime d1, DateTime d2) => isAtSameMonthAs(d1, d2) && d1.da
bool isToday(DateTime d) => isAtSameDayAs(d, DateTime.now()); bool isToday(DateTime d) => isAtSameDayAs(d, DateTime.now());
bool isYesterday(DateTime d) => isAtSameDayAs(d, DateTime.now().subtract(Duration(days: 1))); bool isYesterday(DateTime d) => isAtSameDayAs(d, DateTime.now().subtract(const Duration(days: 1)));
bool isThisMonth(DateTime d) => isAtSameMonthAs(d, DateTime.now()); bool isThisMonth(DateTime d) => isAtSameMonthAs(d, DateTime.now());

View file

@ -21,6 +21,9 @@ class AllCollectionDrawer extends StatelessWidget {
padding: EdgeInsets.only(bottom: window.viewInsets.bottom), padding: EdgeInsets.only(bottom: window.viewInsets.bottom),
children: [ children: [
DrawerHeader( DrawerHeader(
decoration: BoxDecoration(
color: Theme.of(context).accentColor,
),
child: SafeArea( child: SafeArea(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@ -28,18 +31,18 @@ class AllCollectionDrawer extends StatelessWidget {
Row( Row(
children: [ children: [
CircleAvatar( CircleAvatar(
backgroundColor: Colors.white,
radius: 44,
child: Padding( child: Padding(
padding: EdgeInsets.only(top: 6.0), padding: const EdgeInsets.only(top: 6.0),
child: SvgPicture.asset( child: SvgPicture.asset(
'assets/aves_logo.svg', 'assets/aves_logo.svg',
width: 64, width: 64,
), ),
), ),
backgroundColor: Colors.white,
radius: 44,
), ),
SizedBox(width: 16), const SizedBox(width: 16),
Text( const Text(
'Aves', 'Aves',
style: TextStyle( style: TextStyle(
fontSize: 44, fontSize: 44,
@ -48,28 +51,28 @@ class AllCollectionDrawer extends StatelessWidget {
), ),
], ],
), ),
SizedBox(height: 16), const SizedBox(height: 16),
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Row(children: [ Row(children: [
Icon(Icons.photo_library), Icon(Icons.photo_library),
SizedBox(width: 4), const SizedBox(width: 4),
Text('${collection.imageCount}') Text('${collection.imageCount}')
]), ]),
Row(children: [ Row(children: [
Icon(Icons.video_library), Icon(Icons.video_library),
SizedBox(width: 4), const SizedBox(width: 4),
Text('${collection.videoCount}') Text('${collection.videoCount}')
]), ]),
Row(children: [ Row(children: [
Icon(Icons.photo_album), Icon(Icons.photo_album),
SizedBox(width: 4), const SizedBox(width: 4),
Text('${collection.albumCount}') Text('${collection.albumCount}')
]), ]),
Row(children: [ Row(children: [
Icon(Icons.label), Icon(Icons.label),
SizedBox(width: 4), const SizedBox(width: 4),
Text('${collection.tagCount}') Text('${collection.tagCount}')
]), ]),
], ],
@ -77,9 +80,6 @@ class AllCollectionDrawer extends StatelessWidget {
], ],
), ),
), ),
decoration: BoxDecoration(
color: Theme.of(context).accentColor,
),
), ),
_buildFilteredCollectionNavTile( _buildFilteredCollectionNavTile(
context: context, context: context,
@ -87,14 +87,14 @@ class AllCollectionDrawer extends StatelessWidget {
title: 'Videos', title: 'Videos',
filter: (entry) => entry.isVideo, filter: (entry) => entry.isVideo,
), ),
Divider(), const Divider(),
...albums.map((album) => _buildFilteredCollectionNavTile( ...albums.map((album) => _buildFilteredCollectionNavTile(
context: context, context: context,
leading: IconUtils.getAlbumIcon(context, album) ?? Icon(Icons.photo_album), leading: IconUtils.getAlbumIcon(context, album) ?? Icon(Icons.photo_album),
title: collection.getUniqueAlbumName(album, albums), title: collection.getUniqueAlbumName(album, albums),
filter: (entry) => entry.directory == album, filter: (entry) => entry.directory == album,
)), )),
Divider(), const Divider(),
...tags.map((tag) => _buildFilteredCollectionNavTile( ...tags.map((tag) => _buildFilteredCollectionNavTile(
context: context, context: context,
leading: Icon(Icons.label), leading: Icon(Icons.label),
@ -106,7 +106,7 @@ class AllCollectionDrawer extends StatelessWidget {
); );
} }
_buildFilteredCollectionNavTile({BuildContext context, Widget leading, String title, bool Function(ImageEntry) filter}) { Widget _buildFilteredCollectionNavTile({BuildContext context, Widget leading, String title, bool Function(ImageEntry) filter}) {
return SafeArea( return SafeArea(
top: false, top: false,
bottom: false, bottom: false,

View file

@ -16,7 +16,7 @@ class AllCollectionPage extends StatelessWidget {
return ThumbnailCollection( return ThumbnailCollection(
collection: collection, collection: collection,
appBar: SliverAppBar( appBar: SliverAppBar(
title: Text('All'), title: const Text('All'),
actions: [ actions: [
IconButton( IconButton(
icon: Icon(Icons.search), icon: Icon(Icons.search),
@ -35,7 +35,7 @@ class AllCollectionPage extends StatelessWidget {
value: AlbumAction.sortBySize, value: AlbumAction.sortBySize,
child: MenuRow(text: 'Sort by size', checked: collection.sortFactor == SortFactor.size), child: MenuRow(text: 'Sort by size', checked: collection.sortFactor == SortFactor.size),
), ),
PopupMenuDivider(), const PopupMenuDivider(),
if (collection.sortFactor == SortFactor.date) ...[ if (collection.sortFactor == SortFactor.date) ...[
PopupMenuItem( PopupMenuItem(
value: AlbumAction.groupByAlbum, value: AlbumAction.groupByAlbum,
@ -49,14 +49,14 @@ class AllCollectionPage extends StatelessWidget {
value: AlbumAction.groupByDay, value: AlbumAction.groupByDay,
child: MenuRow(text: 'Group by day', checked: collection.groupFactor == GroupFactor.day), child: MenuRow(text: 'Group by day', checked: collection.groupFactor == GroupFactor.day),
), ),
PopupMenuDivider(), const PopupMenuDivider(),
], ],
PopupMenuItem( PopupMenuItem(
value: AlbumAction.debug, value: AlbumAction.debug,
child: MenuRow(text: 'Debug', icon: Icons.whatshot), child: MenuRow(text: 'Debug', icon: Icons.whatshot),
), ),
], ],
onSelected: (action) => onActionSelected(context, action), onSelected: (action) => _onActionSelected(context, action),
), ),
], ],
floating: true, floating: true,
@ -64,7 +64,7 @@ class AllCollectionPage extends StatelessWidget {
); );
} }
onActionSelected(BuildContext context, AlbumAction action) { void _onActionSelected(BuildContext context, AlbumAction action) {
switch (action) { switch (action) {
case AlbumAction.debug: case AlbumAction.debug:
goToDebug(context); goToDebug(context);

View file

@ -43,14 +43,14 @@ class ImageSearchDelegate extends SearchDelegate<ImageEntry> {
@override @override
Widget buildSuggestions(BuildContext context) { Widget buildSuggestions(BuildContext context) {
return SizedBox.shrink(); return const SizedBox.shrink();
} }
@override @override
Widget buildResults(BuildContext context) { Widget buildResults(BuildContext context) {
if (query.isEmpty) { if (query.isEmpty) {
showSuggestions(context); showSuggestions(context);
return SizedBox.shrink(); return const SizedBox.shrink();
} }
final lowerQuery = query.toLowerCase(); final lowerQuery = query.toLowerCase();
final matches = collection.sortedEntries.where((entry) => entry.search(lowerQuery)).toList(); final matches = collection.sortedEntries.where((entry) => entry.search(lowerQuery)).toList();

View file

@ -7,13 +7,13 @@ class DaySectionHeader extends StatelessWidget {
final String text; final String text;
DaySectionHeader({Key key, DateTime date}) DaySectionHeader({Key key, DateTime date})
: text = formatDate(date), : text = _formatDate(date),
super(key: key); super(key: key);
static DateFormat md = DateFormat.MMMMd(); static DateFormat md = DateFormat.MMMMd();
static DateFormat ymd = DateFormat.yMMMMd(); static DateFormat ymd = DateFormat.yMMMMd();
static formatDate(DateTime date) { static String _formatDate(DateTime date) {
if (isToday(date)) return 'Today'; if (isToday(date)) return 'Today';
if (isYesterday(date)) return 'Yesterday'; if (isYesterday(date)) return 'Yesterday';
if (isThisYear(date)) return md.format(date); if (isThisYear(date)) return md.format(date);
@ -30,13 +30,13 @@ class MonthSectionHeader extends StatelessWidget {
final String text; final String text;
MonthSectionHeader({Key key, DateTime date}) MonthSectionHeader({Key key, DateTime date})
: text = formatDate(date), : text = _formatDate(date),
super(key: key); super(key: key);
static DateFormat m = DateFormat.MMMM(); static DateFormat m = DateFormat.MMMM();
static DateFormat ym = DateFormat.yMMMM(); static DateFormat ym = DateFormat.yMMMM();
static formatDate(DateTime date) { static String _formatDate(DateTime date) {
if (isThisMonth(date)) return 'This month'; if (isThisMonth(date)) return 'This month';
if (isThisYear(date)) return m.format(date); if (isThisYear(date)) return m.format(date);
return ym.format(date); return ym.format(date);
@ -64,7 +64,7 @@ class TitleSectionHeader extends StatelessWidget {
fontFamily: 'Concourse Caps', fontFamily: 'Concourse Caps',
shadows: [ shadows: [
Shadow( Shadow(
offset: Offset(0, 2), offset: const Offset(0, 2),
blurRadius: 3, blurRadius: 3,
color: Colors.grey[900], color: Colors.grey[900],
), ),
@ -74,12 +74,12 @@ class TitleSectionHeader extends StatelessWidget {
outlineWidth: 2, outlineWidth: 2,
); );
return Container( return Container(
padding: EdgeInsets.all(16), padding: const EdgeInsets.all(16),
child: leading != null child: leading != null
? Row( ? Row(
children: [ children: [
leading, leading,
SizedBox(width: 8), const SizedBox(width: 8),
text, text,
], ],
) )

View file

@ -13,7 +13,7 @@ class ThumbnailCollection extends AnimatedWidget {
final ImageCollection collection; final ImageCollection collection;
final Widget appBar; final Widget appBar;
ThumbnailCollection({ const ThumbnailCollection({
Key key, Key key,
this.collection, this.collection,
this.appBar, this.appBar,
@ -65,6 +65,12 @@ class ThumbnailCollectionContent extends StatelessWidget {
child: Selector<MediaQueryData, double>( child: Selector<MediaQueryData, double>(
selector: (c, mq) => mq.viewInsets.bottom, selector: (c, mq) => mq.viewInsets.bottom,
builder: (c, mqViewInsetsBottom, child) => DraggableScrollbar.arrows( builder: (c, mqViewInsetsBottom, child) => DraggableScrollbar.arrows(
controller: _scrollController,
padding: EdgeInsets.only(
// top padding to adjust scroll thumb
top: topPadding,
bottom: mqViewInsetsBottom,
),
child: CustomScrollView( child: CustomScrollView(
controller: _scrollController, controller: _scrollController,
slivers: [ slivers: [
@ -89,12 +95,6 @@ class ThumbnailCollectionContent extends StatelessWidget {
}), }),
], ],
), ),
controller: _scrollController,
padding: EdgeInsets.only(
// top padding to adjust scroll thumb
top: topPadding,
bottom: mqViewInsetsBottom,
),
), ),
), ),
); );
@ -117,7 +117,7 @@ class SectionSliver extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final columnCount = 4; const columnCount = 4;
return SliverStickyHeader( return SliverStickyHeader(
header: SectionHeader( header: SectionHeader(
collection: collection, collection: collection,
@ -143,7 +143,7 @@ class SectionSliver extends StatelessWidget {
addAutomaticKeepAlives: false, addAutomaticKeepAlives: false,
addRepaintBoundaries: true, addRepaintBoundaries: true,
), ),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: columnCount, crossAxisCount: columnCount,
), ),
), ),
@ -151,7 +151,7 @@ class SectionSliver extends StatelessWidget {
); );
} }
_showFullscreen(BuildContext context, ImageEntry entry) { void _showFullscreen(BuildContext context, ImageEntry entry) {
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
@ -178,11 +178,11 @@ class SectionHeader extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
Widget header = SizedBox.shrink(); Widget header = const SizedBox.shrink();
if (collection.sortFactor == SortFactor.date) { if (collection.sortFactor == SortFactor.date) {
switch (collection.groupFactor) { switch (collection.groupFactor) {
case GroupFactor.album: case GroupFactor.album:
Widget albumIcon = IconUtils.getAlbumIcon(context, sectionKey); Widget albumIcon = IconUtils.getAlbumIcon(context, sectionKey as String);
if (albumIcon != null) { if (albumIcon != null) {
albumIcon = Material( albumIcon = Material(
type: MaterialType.circle, type: MaterialType.circle,
@ -194,14 +194,14 @@ class SectionHeader extends StatelessWidget {
} }
header = TitleSectionHeader( header = TitleSectionHeader(
leading: albumIcon, leading: albumIcon,
title: collection.getUniqueAlbumName(sectionKey, sections.keys.cast<String>()), title: collection.getUniqueAlbumName(sectionKey as String, sections.keys.cast<String>()),
); );
break; break;
case GroupFactor.month: case GroupFactor.month:
header = MonthSectionHeader(date: sectionKey); header = MonthSectionHeader(date: sectionKey as DateTime);
break; break;
case GroupFactor.day: case GroupFactor.day:
header = DaySectionHeader(date: sectionKey); header = DaySectionHeader(date: sectionKey as DateTime);
break; break;
} }
} }

View file

@ -42,7 +42,7 @@ class AppIconState extends State<AppIcon> {
width: widget.size, width: widget.size,
height: widget.size, height: widget.size,
) )
: SizedBox.shrink(); : const SizedBox.shrink();
}, },
); );
} }

View file

@ -3,7 +3,7 @@ import 'package:flutter/widgets.dart';
class FakeAppBar extends StatelessWidget with PreferredSizeWidget { class FakeAppBar extends StatelessWidget with PreferredSizeWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return SafeArea(child: SizedBox.shrink()); return const SafeArea(child: SizedBox.shrink());
} }
@override @override

View file

@ -61,10 +61,10 @@ class OverlayIcon extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Container( return Container(
margin: EdgeInsets.all(1), margin: const EdgeInsets.all(1),
padding: text != null ? EdgeInsets.only(right: iconSize / 4) : null, padding: text != null ? EdgeInsets.only(right: iconSize / 4) : null,
decoration: BoxDecoration( decoration: BoxDecoration(
color: Color(0xBB000000), color: const Color(0xBB000000),
borderRadius: BorderRadius.all( borderRadius: BorderRadius.all(
Radius.circular(iconSize), Radius.circular(iconSize),
), ),
@ -78,7 +78,7 @@ class OverlayIcon extends StatelessWidget {
size: iconSize, size: iconSize,
), ),
if (text != null) ...[ if (text != null) ...[
SizedBox(width: 2), const SizedBox(width: 2),
Text(text), Text(text),
] ]
], ],

View file

@ -40,33 +40,33 @@ class ImagePreviewState extends State<ImagePreview> with AfterInitMixin {
entry.imageChangeNotifier, entry.imageChangeNotifier,
entry.metadataChangeNotifier entry.metadataChangeNotifier
]); ]);
_entryChangeNotifier.addListener(onEntryChange); _entryChangeNotifier.addListener(_onEntryChange);
} }
@override @override
void didInitState() { void didInitState() {
_devicePixelRatio = Provider.of<MediaQueryData>(context, listen: false).devicePixelRatio; _devicePixelRatio = Provider.of<MediaQueryData>(context, listen: false).devicePixelRatio;
initByteLoader(); _initByteLoader();
} }
@override @override
void didUpdateWidget(ImagePreview old) { void didUpdateWidget(ImagePreview old) {
super.didUpdateWidget(old); super.didUpdateWidget(old);
if (widget.width == old.width && widget.height == old.height && uri == old.entry.uri && widget.entry.width == old.entry.width && widget.entry.height == old.entry.height && widget.entry.orientationDegrees == old.entry.orientationDegrees) return; if (widget.width == old.width && widget.height == old.height && uri == old.entry.uri && widget.entry.width == old.entry.width && widget.entry.height == old.entry.height && widget.entry.orientationDegrees == old.entry.orientationDegrees) return;
initByteLoader(); _initByteLoader();
} }
initByteLoader() { void _initByteLoader() {
final width = (widget.width * _devicePixelRatio).round(); final width = (widget.width * _devicePixelRatio).round();
final height = (widget.height * _devicePixelRatio).round(); final height = (widget.height * _devicePixelRatio).round();
_byteLoader = ImageFileService.getImageBytes(widget.entry, width, height); _byteLoader = ImageFileService.getImageBytes(widget.entry, width, height);
} }
onEntryChange() => setState(() => initByteLoader()); void _onEntryChange() => setState(() => _initByteLoader());
@override @override
void dispose() { void dispose() {
_entryChangeNotifier.removeListener(onEntryChange); _entryChangeNotifier.removeListener(_onEntryChange);
super.dispose(); super.dispose();
} }

View file

@ -21,11 +21,11 @@ class MenuRow extends StatelessWidget {
opacity: checked ? 1 : 0, opacity: checked ? 1 : 0,
child: Icon(Icons.done), child: Icon(Icons.done),
), ),
SizedBox(width: 8), const SizedBox(width: 8),
], ],
if (icon != null) ...[ if (icon != null) ...[
Icon(icon), Icon(icon),
SizedBox(width: 8), const SizedBox(width: 8),
], ],
Text(text), Text(text),
], ],

View file

@ -39,48 +39,48 @@ class DebugPageState extends State<DebugPage> {
return MediaQueryDataProvider( return MediaQueryDataProvider(
child: Scaffold( child: Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text('Info'), title: const Text('Info'),
), ),
body: Column( body: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text('Paths'), const Text('Paths'),
Text('DCIM path: ${androidFileUtils.dcimPath}'), Text('DCIM path: ${androidFileUtils.dcimPath}'),
Text('pictures path: ${androidFileUtils.picturesPath}'), Text('pictures path: ${androidFileUtils.picturesPath}'),
Divider(), const Divider(),
Text('Settings'), const Text('Settings'),
Text('collectionGroupFactor: ${settings.collectionGroupFactor}'), Text('collectionGroupFactor: ${settings.collectionGroupFactor}'),
Text('collectionSortFactor: ${settings.collectionSortFactor}'), Text('collectionSortFactor: ${settings.collectionSortFactor}'),
Text('infoMapZoom: ${settings.infoMapZoom}'), Text('infoMapZoom: ${settings.infoMapZoom}'),
Divider(), const Divider(),
Text('Entries: ${entries.length}'), Text('Entries: ${entries.length}'),
...byMimeTypes.keys.map((mimeType) => Text('- $mimeType: ${byMimeTypes[mimeType].length}')), ...byMimeTypes.keys.map((mimeType) => Text('- $mimeType: ${byMimeTypes[mimeType].length}')),
Text('Catalogued: ${catalogued.length}'), Text('Catalogued: ${catalogued.length}'),
Text('With GPS: ${withGps.length}'), Text('With GPS: ${withGps.length}'),
Text('With address: ${located.length}'), Text('With address: ${located.length}'),
Divider(), const Divider(),
RaisedButton( RaisedButton(
onPressed: () => metadataDb.reset(), onPressed: () => metadataDb.reset(),
child: Text('Reset DB'), child: const Text('Reset DB'),
), ),
FutureBuilder( FutureBuilder(
future: _dbMetadataLoader, future: _dbMetadataLoader,
builder: (futureContext, AsyncSnapshot<List<CatalogMetadata>> snapshot) { builder: (futureContext, AsyncSnapshot<List<CatalogMetadata>> snapshot) {
if (snapshot.hasError) return Text(snapshot.error); if (snapshot.hasError) return Text(snapshot.error.toString());
if (snapshot.connectionState != ConnectionState.done) return SizedBox.shrink(); if (snapshot.connectionState != ConnectionState.done) return const SizedBox.shrink();
return Text('DB metadata rows: ${snapshot.data.length}'); return Text('DB metadata rows: ${snapshot.data.length}');
}, },
), ),
FutureBuilder( FutureBuilder(
future: _dbAddressLoader, future: _dbAddressLoader,
builder: (futureContext, AsyncSnapshot<List<AddressDetails>> snapshot) { builder: (futureContext, AsyncSnapshot<List<AddressDetails>> snapshot) {
if (snapshot.hasError) return Text(snapshot.error); if (snapshot.hasError) return Text(snapshot.error.toString());
if (snapshot.connectionState != ConnectionState.done) return SizedBox.shrink(); if (snapshot.connectionState != ConnectionState.done) return const SizedBox.shrink();
return Text('DB address rows: ${snapshot.data.length}'); return Text('DB address rows: ${snapshot.data.length}');
}, },
), ),
Divider(), const Divider(),
Text('Time dilation'), const Text('Time dilation'),
Slider( Slider(
value: timeDilation, value: timeDilation,
onChanged: (v) => setState(() => timeDilation = v), onChanged: (v) => setState(() => timeDilation = v),

View file

@ -61,14 +61,14 @@ class FullscreenActionDelegate {
void _showFeedback(BuildContext context, String message) { void _showFeedback(BuildContext context, String message) {
Flushbar( Flushbar(
message: message, message: message,
margin: EdgeInsets.all(8), margin: const EdgeInsets.all(8),
borderRadius: 8, borderRadius: 8,
borderColor: Colors.white30, borderColor: Colors.white30,
borderWidth: 0.5, borderWidth: 0.5,
duration: Duration(seconds: 2), duration: const Duration(seconds: 2),
flushbarPosition: FlushbarPosition.TOP, flushbarPosition: FlushbarPosition.TOP,
animationDuration: Duration(milliseconds: 600), animationDuration: const Duration(milliseconds: 600),
)..show(context); ).show(context);
} }
Future<void> _print(ImageEntry entry) async { Future<void> _print(ImageEntry entry) async {
@ -94,15 +94,15 @@ class FullscreenActionDelegate {
context: context, context: context,
builder: (BuildContext context) { builder: (BuildContext context) {
return AlertDialog( return AlertDialog(
content: Text('Are you sure?'), content: const Text('Are you sure?'),
actions: [ actions: [
FlatButton( FlatButton(
onPressed: () => Navigator.pop(context), onPressed: () => Navigator.pop(context),
child: Text('CANCEL'), child: const Text('CANCEL'),
), ),
FlatButton( FlatButton(
onPressed: () => Navigator.pop(context, true), onPressed: () => Navigator.pop(context, true),
child: Text('DELETE'), child: const Text('DELETE'),
), ),
], ],
); );
@ -130,11 +130,11 @@ class FullscreenActionDelegate {
actions: [ actions: [
FlatButton( FlatButton(
onPressed: () => Navigator.pop(context), onPressed: () => Navigator.pop(context),
child: Text('CANCEL'), child: const Text('CANCEL'),
), ),
FlatButton( FlatButton(
onPressed: () => Navigator.pop(context, controller.text), onPressed: () => Navigator.pop(context, controller.text),
child: Text('APPLY'), child: const Text('APPLY'),
), ),
], ],
); );

View file

@ -61,13 +61,13 @@ class FullscreenBodyState extends State<FullscreenBody> with SingleTickerProvide
bool _isInitialScale = true; bool _isInitialScale = true;
int _currentHorizontalPage, _currentVerticalPage = imagePage; int _currentHorizontalPage, _currentVerticalPage = imagePage;
PageController _horizontalPager, _verticalPager; PageController _horizontalPager, _verticalPager;
ValueNotifier<bool> _overlayVisible = ValueNotifier(true); final ValueNotifier<bool> _overlayVisible = ValueNotifier(true);
AnimationController _overlayAnimationController; AnimationController _overlayAnimationController;
Animation<double> _topOverlayScale; Animation<double> _topOverlayScale;
Animation<Offset> _bottomOverlayOffset; Animation<Offset> _bottomOverlayOffset;
EdgeInsets _frozenViewInsets, _frozenViewPadding; EdgeInsets _frozenViewInsets, _frozenViewPadding;
FullscreenActionDelegate _actionDelegate; FullscreenActionDelegate _actionDelegate;
List<Tuple2<String, VideoPlayerController>> _videoControllers = List(); final List<Tuple2<String, VideoPlayerController>> _videoControllers = [];
ImageCollection get collection => widget.collection; ImageCollection get collection => widget.collection;
@ -85,14 +85,14 @@ class FullscreenBodyState extends State<FullscreenBody> with SingleTickerProvide
_horizontalPager = PageController(initialPage: _currentHorizontalPage); _horizontalPager = PageController(initialPage: _currentHorizontalPage);
_verticalPager = PageController(initialPage: _currentVerticalPage); _verticalPager = PageController(initialPage: _currentVerticalPage);
_overlayAnimationController = AnimationController( _overlayAnimationController = AnimationController(
duration: Duration(milliseconds: 400), duration: const Duration(milliseconds: 400),
vsync: this, vsync: this,
); );
_topOverlayScale = CurvedAnimation( _topOverlayScale = CurvedAnimation(
parent: _overlayAnimationController, parent: _overlayAnimationController,
curve: Curves.easeOutQuart, curve: Curves.easeOutQuart,
); );
_bottomOverlayOffset = Tween(begin: Offset(0, 1), end: Offset(0, 0)).animate(CurvedAnimation( _bottomOverlayOffset = Tween(begin: const Offset(0, 1), end: const Offset(0, 0)).animate(CurvedAnimation(
parent: _overlayAnimationController, parent: _overlayAnimationController,
curve: Curves.easeOutQuart, curve: Curves.easeOutQuart,
)); ));
@ -105,7 +105,7 @@ class FullscreenBodyState extends State<FullscreenBody> with SingleTickerProvide
_initOverlay(); _initOverlay();
} }
_initOverlay() async { Future<void> _initOverlay() async {
// wait for MaterialPageRoute.transitionDuration // wait for MaterialPageRoute.transitionDuration
// to show overlay after hero animation is complete // to show overlay after hero animation is complete
await Future.delayed(Duration(milliseconds: (300 * timeDilation).toInt())); await Future.delayed(Duration(milliseconds: (300 * timeDilation).toInt()));
@ -136,10 +136,10 @@ class FullscreenBodyState extends State<FullscreenBody> with SingleTickerProvide
PageView( PageView(
scrollDirection: Axis.vertical, scrollDirection: Axis.vertical,
controller: _verticalPager, controller: _verticalPager,
physics: _isInitialScale ? PageScrollPhysics() : NeverScrollableScrollPhysics(), physics: _isInitialScale ? const PageScrollPhysics() : const NeverScrollableScrollPhysics(),
onPageChanged: _onVerticalPageChanged, onPageChanged: _onVerticalPageChanged,
children: [ children: [
SizedBox(), const SizedBox(),
Container( Container(
color: Colors.black, color: Colors.black,
child: ImagePage( child: ImagePage(
@ -208,12 +208,12 @@ class FullscreenBodyState extends State<FullscreenBody> with SingleTickerProvide
Future<void> _goToVerticalPage(int page) { Future<void> _goToVerticalPage(int page) {
return _verticalPager.animateToPage( return _verticalPager.animateToPage(
page, page,
duration: Duration(milliseconds: 350), duration: const Duration(milliseconds: 350),
curve: Curves.easeInOut, curve: Curves.easeInOut,
); );
} }
void _onVerticalPageChanged(page) { void _onVerticalPageChanged(int page) {
setState(() => _currentVerticalPage = page); setState(() => _currentVerticalPage = page);
if (_currentVerticalPage == transitionPage) { if (_currentVerticalPage == transitionPage) {
_onLeave(); _onLeave();

View file

@ -51,7 +51,7 @@ class ImagePageState extends State<ImagePage> with AutomaticKeepAliveClientMixin
entry: entry, entry: entry,
controller: videoController, controller: videoController,
) )
: SizedBox(), : const SizedBox(),
childSize: mqSize, childSize: mqSize,
// no hero as most videos fullscreen image is different from its thumbnail // no hero as most videos fullscreen image is different from its thumbnail
minScale: PhotoViewComputedScale.contained, minScale: PhotoViewComputedScale.contained,
@ -70,14 +70,14 @@ class ImagePageState extends State<ImagePage> with AutomaticKeepAliveClientMixin
onTapUp: (tapContext, details, value) => widget.onTap?.call(), onTapUp: (tapContext, details, value) => widget.onTap?.call(),
); );
}, },
loadingChild: Center( loadingChild: const Center(
child: CircularProgressIndicator(), child: CircularProgressIndicator(),
), ),
backgroundDecoration: BoxDecoration(color: Colors.transparent), backgroundDecoration: BoxDecoration(color: Colors.transparent),
pageController: widget.pageController, pageController: widget.pageController,
onPageChanged: widget.onPageChanged, onPageChanged: widget.onPageChanged,
scaleStateChangedCallback: widget.onScaleChanged, scaleStateChangedCallback: widget.onScaleChanged,
scrollPhysics: BouncingScrollPhysics(), scrollPhysics: const BouncingScrollPhysics(),
), ),
); );
} }

View file

@ -119,7 +119,7 @@ class SectionRow extends StatelessWidget {
padding: const EdgeInsets.all(16.0), padding: const EdgeInsets.all(16.0),
child: Text( child: Text(
title, title,
style: TextStyle( style: const TextStyle(
fontSize: 20, fontSize: 20,
fontFamily: 'Concourse Caps', fontFamily: 'Concourse Caps',
), ),
@ -142,9 +142,9 @@ class InfoRow extends StatelessWidget {
padding: const EdgeInsets.symmetric(vertical: 4.0), padding: const EdgeInsets.symmetric(vertical: 4.0),
child: RichText( child: RichText(
text: TextSpan( text: TextSpan(
style: TextStyle(fontFamily: 'Concourse'), style: const TextStyle(fontFamily: 'Concourse'),
children: [ children: [
TextSpan(text: '$label ', style: TextStyle(color: Colors.white70)), TextSpan(text: '$label ', style: const TextStyle(color: Colors.white70)),
TextSpan(text: value), TextSpan(text: value),
], ],
), ),

View file

@ -7,7 +7,7 @@ import 'package:google_maps_flutter/google_maps_flutter.dart';
class LocationSection extends AnimatedWidget { class LocationSection extends AnimatedWidget {
final ImageEntry entry; final ImageEntry entry;
final showTitle; final bool showTitle;
LocationSection({ LocationSection({
Key key, Key key,
@ -23,12 +23,12 @@ class LocationSection extends AnimatedWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return !entry.hasGps return !entry.hasGps
? SizedBox.shrink() ? const SizedBox.shrink()
: Column( : Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
if (showTitle) if (showTitle)
Padding( const Padding(
padding: EdgeInsets.only(bottom: 8), padding: EdgeInsets.only(bottom: 8),
child: SectionRow('Location'), child: SectionRow('Location'),
), ),
@ -43,7 +43,7 @@ class LocationSection extends AnimatedWidget {
), ),
if (entry.isLocated) if (entry.isLocated)
Padding( Padding(
padding: EdgeInsets.only(top: 8), padding: const EdgeInsets.only(top: 8),
child: InfoRow('Address', entry.addressDetails.addressLine), child: InfoRow('Address', entry.addressDetails.addressLine),
), ),
], ],
@ -90,7 +90,7 @@ class ImageMapState extends State<ImageMap> with AutomaticKeepAliveClientMixin {
child: SizedBox( child: SizedBox(
height: 200, height: 200,
child: ClipRRect( child: ClipRRect(
borderRadius: BorderRadius.all( borderRadius: const BorderRadius.all(
Radius.circular(16), Radius.circular(16),
), ),
child: GoogleMap( child: GoogleMap(
@ -104,18 +104,18 @@ class ImageMapState extends State<ImageMap> with AutomaticKeepAliveClientMixin {
zoomGesturesEnabled: false, zoomGesturesEnabled: false,
tiltGesturesEnabled: false, tiltGesturesEnabled: false,
myLocationButtonEnabled: false, myLocationButtonEnabled: false,
markers: [ markers: {
Marker( Marker(
markerId: MarkerId(widget.markerId), markerId: MarkerId(widget.markerId),
icon: BitmapDescriptor.defaultMarkerWithHue(accentHue), icon: BitmapDescriptor.defaultMarkerWithHue(accentHue),
position: widget.latLng, position: widget.latLng,
) )
].toSet(), },
), ),
), ),
), ),
), ),
SizedBox(width: 8), const SizedBox(width: 8),
Column(children: [ Column(children: [
IconButton( IconButton(
icon: Icon(Icons.add), icon: Icon(Icons.add),
@ -137,7 +137,7 @@ class ImageMapState extends State<ImageMap> with AutomaticKeepAliveClientMixin {
); );
} }
_zoomBy(double amount) { void _zoomBy(double amount) {
settings.infoMapZoom += amount; settings.infoMapZoom += amount;
_controller.animateCamera(CameraUpdate.zoomBy(amount)); _controller.animateCamera(CameraUpdate.zoomBy(amount));
} }

View file

@ -23,16 +23,16 @@ class MetadataSectionState extends State<MetadataSection> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
initMetadataLoader(); _initMetadataLoader();
} }
@override @override
void didUpdateWidget(MetadataSection oldWidget) { void didUpdateWidget(MetadataSection oldWidget) {
super.didUpdateWidget(oldWidget); super.didUpdateWidget(oldWidget);
initMetadataLoader(); _initMetadataLoader();
} }
initMetadataLoader() async { Future<void> _initMetadataLoader() async {
_metadataLoader = MetadataService.getAllMetadata(widget.entry); _metadataLoader = MetadataService.getAllMetadata(widget.entry);
} }
@ -43,9 +43,9 @@ class MetadataSectionState extends State<MetadataSection> {
builder: (c, mqWidth, child) => FutureBuilder( builder: (c, mqWidth, child) => FutureBuilder(
future: _metadataLoader, future: _metadataLoader,
builder: (futureContext, AsyncSnapshot<Map> snapshot) { builder: (futureContext, AsyncSnapshot<Map> snapshot) {
if (snapshot.hasError) return Text(snapshot.error);
if (snapshot.connectionState != ConnectionState.done) return SizedBox.shrink();
final metadataMap = snapshot.data.cast<String, Map>(); final metadataMap = snapshot.data.cast<String, Map>();
if (snapshot.hasError) return Text(snapshot.error.toString());
if (snapshot.connectionState != ConnectionState.done) return const SizedBox.shrink();
final directoryNames = metadataMap.keys.toList()..sort(); final directoryNames = metadataMap.keys.toList()..sort();
Widget content; Widget content;
@ -68,7 +68,7 @@ class MetadataSectionState extends State<MetadataSection> {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Expanded(child: getMetadataColumn(metadataMap, first)), Expanded(child: getMetadataColumn(metadataMap, first)),
SizedBox(width: 8), const SizedBox(width: 8),
Expanded(child: getMetadataColumn(metadataMap, second)), Expanded(child: getMetadataColumn(metadataMap, second)),
], ],
); );
@ -79,7 +79,7 @@ class MetadataSectionState extends State<MetadataSection> {
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
children: [ children: [
SectionRow('Metadata'), const SectionRow('Metadata'),
content, content,
], ],
); );
@ -98,19 +98,19 @@ class MetadataSectionState extends State<MetadataSection> {
return [ return [
if (directoryName.isNotEmpty) if (directoryName.isNotEmpty)
Padding( Padding(
padding: EdgeInsets.symmetric(vertical: 4.0), padding: const EdgeInsets.symmetric(vertical: 4.0),
child: Text(directoryName, child: Text(directoryName,
style: TextStyle( style: const TextStyle(
fontSize: 18, fontSize: 18,
fontFamily: 'Concourse Caps', fontFamily: 'Concourse Caps',
)), )),
), ),
...tagKeys.map((tagKey) { ...tagKeys.map((tagKey) {
final value = directory[tagKey] as String; final value = directory[tagKey] as String;
if (value == null || value.isEmpty) return SizedBox.shrink(); if (value == null || value.isEmpty) return const SizedBox.shrink();
return InfoRow(tagKey, value.length > maxValueLength ? '${value.substring(0, maxValueLength)}' : value); return InfoRow(tagKey, value.length > maxValueLength ? '${value.substring(0, maxValueLength)}' : value);
}), }),
SizedBox(height: 16), const SizedBox(height: 16),
]; ];
}), }),
], ],

View file

@ -18,15 +18,15 @@ class XmpTagSection extends AnimatedWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final tags = entry.xmpSubjects; final tags = entry.xmpSubjects;
return tags.isEmpty return tags.isEmpty
? SizedBox.shrink() ? const SizedBox.shrink()
: Column( : Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
SectionRow('XMP Tags'), const SectionRow('XMP Tags'),
Wrap( Wrap(
children: tags children: tags
.map((tag) => Padding( .map((tag) => Padding(
padding: EdgeInsets.symmetric(horizontal: 4.0), padding: const EdgeInsets.symmetric(horizontal: 4.0),
child: ActionChip( child: ActionChip(
label: Text(tag), label: Text(tag),
onPressed: () => Navigator.push( onPressed: () => Navigator.push(

View file

@ -45,16 +45,16 @@ class _FullscreenBottomOverlayState extends State<FullscreenBottomOverlay> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
initDetailLoader(); _initDetailLoader();
} }
@override @override
void didUpdateWidget(FullscreenBottomOverlay oldWidget) { void didUpdateWidget(FullscreenBottomOverlay oldWidget) {
super.didUpdateWidget(oldWidget); super.didUpdateWidget(oldWidget);
initDetailLoader(); _initDetailLoader();
} }
initDetailLoader() { void _initDetailLoader() {
_detailLoader = MetadataService.getOverlayMetadata(entry); _detailLoader = MetadataService.getOverlayMetadata(entry);
} }
@ -86,7 +86,7 @@ class _FullscreenBottomOverlayState extends State<FullscreenBottomOverlay> {
_lastEntry = entry; _lastEntry = entry;
} }
return _lastEntry == null return _lastEntry == null
? SizedBox.shrink() ? const SizedBox.shrink()
: _FullscreenBottomOverlayContent( : _FullscreenBottomOverlayContent(
entry: _lastEntry, entry: _lastEntry,
details: _lastDetails, details: _lastDetails,
@ -115,7 +115,7 @@ class _FullscreenBottomOverlayContent extends StatelessWidget {
final String position; final String position;
final double maxWidth; final double maxWidth;
_FullscreenBottomOverlayContent({ const _FullscreenBottomOverlayContent({
this.entry, this.entry,
this.details, this.details,
this.position, this.position,

View file

@ -28,14 +28,14 @@ class FullscreenTopOverlay extends StatelessWidget {
return SafeArea( return SafeArea(
minimum: (viewInsets ?? EdgeInsets.zero) + (viewPadding ?? EdgeInsets.zero), minimum: (viewInsets ?? EdgeInsets.zero) + (viewPadding ?? EdgeInsets.zero),
child: Padding( child: Padding(
padding: EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
child: Row( child: Row(
children: [ children: [
OverlayButton( OverlayButton(
scale: scale, scale: scale,
child: BackButton(), child: const BackButton(),
), ),
Spacer(), const Spacer(),
OverlayButton( OverlayButton(
scale: scale, scale: scale,
child: IconButton( child: IconButton(
@ -44,7 +44,7 @@ class FullscreenTopOverlay extends StatelessWidget {
tooltip: 'Share', tooltip: 'Share',
), ),
), ),
SizedBox(width: 8), const SizedBox(width: 8),
OverlayButton( OverlayButton(
scale: scale, scale: scale,
child: IconButton( child: IconButton(
@ -53,7 +53,7 @@ class FullscreenTopOverlay extends StatelessWidget {
tooltip: 'Delete', tooltip: 'Delete',
), ),
), ),
SizedBox(width: 8), const SizedBox(width: 8),
OverlayButton( OverlayButton(
scale: scale, scale: scale,
child: PopupMenuButton<FullscreenAction>( child: PopupMenuButton<FullscreenAction>(
@ -81,21 +81,21 @@ class FullscreenTopOverlay extends StatelessWidget {
value: FullscreenAction.print, value: FullscreenAction.print,
child: MenuRow(text: 'Print', icon: Icons.print), child: MenuRow(text: 'Print', icon: Icons.print),
), ),
PopupMenuDivider(), const PopupMenuDivider(),
PopupMenuItem( const PopupMenuItem(
value: FullscreenAction.edit, value: FullscreenAction.edit,
child: Text('Edit with…'), child: Text('Edit with…'),
), ),
PopupMenuItem( const PopupMenuItem(
value: FullscreenAction.open, value: FullscreenAction.open,
child: Text('Open with…'), child: Text('Open with…'),
), ),
PopupMenuItem( const PopupMenuItem(
value: FullscreenAction.setAs, value: FullscreenAction.setAs,
child: Text('Set as…'), child: Text('Set as…'),
), ),
if (entry.hasGps) if (entry.hasGps)
PopupMenuItem( const PopupMenuItem(
value: FullscreenAction.openMap, value: FullscreenAction.openMap,
child: Text('Show on map…'), child: Text('Show on map…'),
), ),

View file

@ -46,32 +46,32 @@ class VideoControlOverlayState extends State<VideoControlOverlay> with SingleTic
void initState() { void initState() {
super.initState(); super.initState();
_playPauseAnimation = AnimationController( _playPauseAnimation = AnimationController(
duration: Duration(milliseconds: 300), duration: const Duration(milliseconds: 300),
vsync: this, vsync: this,
); );
registerWidget(widget); _registerWidget(widget);
_onValueChange(); _onValueChange();
} }
@override @override
void didUpdateWidget(VideoControlOverlay oldWidget) { void didUpdateWidget(VideoControlOverlay oldWidget) {
super.didUpdateWidget(oldWidget); super.didUpdateWidget(oldWidget);
unregisterWidget(oldWidget); _unregisterWidget(oldWidget);
registerWidget(widget); _registerWidget(widget);
} }
@override @override
void dispose() { void dispose() {
unregisterWidget(widget); _unregisterWidget(widget);
_playPauseAnimation.dispose(); _playPauseAnimation.dispose();
super.dispose(); super.dispose();
} }
void registerWidget(VideoControlOverlay widget) { void _registerWidget(VideoControlOverlay widget) {
widget.controller.addListener(_onValueChange); widget.controller.addListener(_onValueChange);
} }
void unregisterWidget(VideoControlOverlay widget) { void _unregisterWidget(VideoControlOverlay widget) {
widget.controller.removeListener(_onValueChange); widget.controller.removeListener(_onValueChange);
} }
@ -86,7 +86,7 @@ class VideoControlOverlayState extends State<VideoControlOverlay> with SingleTic
final viewInsets = widget.viewInsets ?? mqViewInsets; final viewInsets = widget.viewInsets ?? mqViewInsets;
final viewPadding = widget.viewPadding ?? mqViewPadding; final viewPadding = widget.viewPadding ?? mqViewPadding;
final safePadding = (viewInsets + viewPadding).copyWith(bottom: 8) + EdgeInsets.symmetric(horizontal: 8.0); final safePadding = (viewInsets + viewPadding).copyWith(bottom: 8) + const EdgeInsets.symmetric(horizontal: 8.0);
return Padding( return Padding(
padding: safePadding, padding: safePadding,
@ -109,7 +109,7 @@ class VideoControlOverlayState extends State<VideoControlOverlay> with SingleTic
Expanded( Expanded(
child: _buildProgressBar(), child: _buildProgressBar(),
), ),
SizedBox(width: 8), const SizedBox(width: 8),
OverlayButton( OverlayButton(
scale: scale, scale: scale,
child: IconButton( child: IconButton(
@ -130,7 +130,7 @@ class VideoControlOverlayState extends State<VideoControlOverlay> with SingleTic
} }
Widget _buildProgressBar() { Widget _buildProgressBar() {
final progressBarBorderRadius = 123.0; const progressBarBorderRadius = 123.0;
return SizeTransition( return SizeTransition(
sizeFactor: scale, sizeFactor: scale,
child: BlurredRRect( child: BlurredRRect(
@ -150,11 +150,11 @@ class VideoControlOverlayState extends State<VideoControlOverlay> with SingleTic
if (_playingOnDragStart) controller.play(); if (_playingOnDragStart) controller.play();
}, },
child: Container( child: Container(
padding: EdgeInsets.symmetric(vertical: 4, horizontal: 16) + EdgeInsets.only(bottom: 16), padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 16) + const EdgeInsets.only(bottom: 16),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.black26, color: Colors.black26,
border: Border.all(color: Colors.white30, width: 0.5), border: Border.all(color: Colors.white30, width: 0.5),
borderRadius: BorderRadius.all( borderRadius: const BorderRadius.all(
Radius.circular(progressBarBorderRadius), Radius.circular(progressBarBorderRadius),
), ),
), ),
@ -164,7 +164,7 @@ class VideoControlOverlayState extends State<VideoControlOverlay> with SingleTic
Row( Row(
children: [ children: [
Text(formatDuration(value.position ?? Duration.zero)), Text(formatDuration(value.position ?? Duration.zero)),
Spacer(), const Spacer(),
Text(formatDuration(value.duration ?? Duration.zero)), Text(formatDuration(value.duration ?? Duration.zero)),
], ],
), ),

View file

@ -29,34 +29,34 @@ class AvesVideoState extends State<AvesVideo> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
registerWidget(widget); _registerWidget(widget);
_onValueChange(); _onValueChange();
} }
@override @override
void didUpdateWidget(AvesVideo oldWidget) { void didUpdateWidget(AvesVideo oldWidget) {
super.didUpdateWidget(oldWidget); super.didUpdateWidget(oldWidget);
unregisterWidget(oldWidget); _unregisterWidget(oldWidget);
registerWidget(widget); _registerWidget(widget);
} }
@override @override
void dispose() { void dispose() {
unregisterWidget(widget); _unregisterWidget(widget);
super.dispose(); super.dispose();
} }
registerWidget(AvesVideo widget) { void _registerWidget(AvesVideo widget) {
widget.controller.addListener(_onValueChange); widget.controller.addListener(_onValueChange);
} }
unregisterWidget(AvesVideo widget) { void _unregisterWidget(AvesVideo widget) {
widget.controller.removeListener(_onValueChange); widget.controller.removeListener(_onValueChange);
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (value == null) return SizedBox(); if (value == null) return const SizedBox();
if (value.hasError) { if (value.hasError) {
return Selector<MediaQueryData, double>( return Selector<MediaQueryData, double>(
selector: (c, mq) => mq.size.width, selector: (c, mq) => mq.size.width,
@ -79,12 +79,12 @@ class AvesVideoState extends State<AvesVideo> {
); );
} }
_onValueChange() { void _onValueChange() {
if (!value.isPlaying && value.position == value.duration) goToBeginning(); if (!value.isPlaying && value.position == value.duration) _goToStart();
setState(() {}); setState(() {});
} }
goToBeginning() async { Future<void> _goToStart() async {
await widget.controller.seekTo(Duration.zero); await widget.controller.seekTo(Duration.zero);
await widget.controller.pause(); await widget.controller.pause();
} }