linting
This commit is contained in:
parent
f965e329ad
commit
cb28ad9272
33 changed files with 212 additions and 209 deletions
|
@ -3,3 +3,10 @@ include: package:pedantic/analysis_options.yaml
|
|||
analyzer:
|
||||
exclude:
|
||||
- lib/generated_plugin_registrant.dart
|
||||
|
||||
linter:
|
||||
rules:
|
||||
- always_declare_return_types
|
||||
- prefer_const_constructors
|
||||
- prefer_const_constructors_in_immutables
|
||||
- prefer_const_declarations
|
||||
|
|
|
@ -16,10 +16,10 @@ import 'package:pedantic/pedantic.dart';
|
|||
import 'package:permission_handler/permission_handler.dart';
|
||||
import 'package:screen/screen.dart';
|
||||
|
||||
final stopwatch = Stopwatch()..start();
|
||||
final _stopwatch = Stopwatch()..start();
|
||||
|
||||
void main() {
|
||||
debugPrint('main start, elapsed=${stopwatch.elapsed}');
|
||||
debugPrint('main start, elapsed=${_stopwatch.elapsed}');
|
||||
// initialize binding/plugins to configure Skia before `runApp`
|
||||
WidgetsFlutterBinding.ensureInitialized(); // 220ms
|
||||
// debugPrint('main WidgetsFlutterBinding.ensureInitialized done, elapsed=${stopwatch.elapsed}');
|
||||
|
@ -61,7 +61,7 @@ class HomePage extends StatefulWidget {
|
|||
class _HomePageState extends State<HomePage> {
|
||||
static const EventChannel eventChannel = EventChannel('deckers.thibault/aves/mediastore');
|
||||
|
||||
ImageCollection localMediaCollection = ImageCollection(entries: List());
|
||||
ImageCollection localMediaCollection = ImageCollection(entries: []);
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
|
@ -72,7 +72,7 @@ class _HomePageState extends State<HomePage> {
|
|||
}
|
||||
|
||||
Future<void> setup() async {
|
||||
debugPrint('$runtimeType setup start, elapsed=${stopwatch.elapsed}');
|
||||
debugPrint('$runtimeType setup start, elapsed=${_stopwatch.elapsed}');
|
||||
// TODO reduce permission check time
|
||||
final permissions = await PermissionHandler().requestPermissions([
|
||||
PermissionGroup.storage
|
||||
|
@ -91,7 +91,7 @@ class _HomePageState extends State<HomePage> {
|
|||
await settings.init(); // <20ms
|
||||
localMediaCollection.groupFactor = settings.collectionGroupFactor;
|
||||
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
|
||||
final currentTimeZone = await FlutterNativeTimezone.getLocalTimezone(); // <20ms
|
||||
|
@ -107,7 +107,7 @@ class _HomePageState extends State<HomePage> {
|
|||
eventChannel.receiveBroadcastStream().cast<Map>().listen(
|
||||
(entryMap) => localMediaCollection.add(ImageEntry.fromMap(entryMap)),
|
||||
onDone: () async {
|
||||
debugPrint('$runtimeType mediastore stream done, elapsed=${stopwatch.elapsed}');
|
||||
debugPrint('$runtimeType mediastore stream done, elapsed=${_stopwatch.elapsed}');
|
||||
localMediaCollection.updateSections(); // <50ms
|
||||
// TODO reduce setup time until here
|
||||
localMediaCollection.updateAlbums(); // <50ms
|
||||
|
@ -115,7 +115,7 @@ class _HomePageState extends State<HomePage> {
|
|||
await localMediaCollection.catalogEntries(); // <50ms
|
||||
await localMediaCollection.loadAddresses(); // 350ms
|
||||
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'),
|
||||
);
|
||||
|
|
|
@ -8,11 +8,11 @@ import 'package:path/path.dart';
|
|||
|
||||
class ImageCollection with ChangeNotifier {
|
||||
final List<ImageEntry> _rawEntries;
|
||||
Map<dynamic, List<ImageEntry>> sections = Map();
|
||||
Map<dynamic, List<ImageEntry>> sections = {};
|
||||
GroupFactor groupFactor = GroupFactor.month;
|
||||
SortFactor sortFactor = SortFactor.date;
|
||||
List<String> sortedAlbums = List.unmodifiable(Iterable.empty());
|
||||
List<String> sortedTags = List.unmodifiable(Iterable.empty());
|
||||
List<String> sortedAlbums = List.unmodifiable(const Iterable.empty());
|
||||
List<String> sortedTags = List.unmodifiable(const Iterable.empty());
|
||||
|
||||
ImageCollection({
|
||||
@required List<ImageEntry> entries,
|
||||
|
@ -140,7 +140,7 @@ class ImageCollection with ChangeNotifier {
|
|||
Future<void> catalogEntries() async {
|
||||
final start = DateTime.now();
|
||||
final uncataloguedEntries = _rawEntries.where((entry) => !entry.isCatalogued).toList();
|
||||
final newMetadata = List<CatalogMetadata>();
|
||||
final newMetadata = <CatalogMetadata>[];
|
||||
await Future.forEach<ImageEntry>(uncataloguedEntries, (entry) async {
|
||||
await entry.catalog();
|
||||
newMetadata.add(entry.catalogMetadata);
|
||||
|
@ -153,7 +153,7 @@ class ImageCollection with ChangeNotifier {
|
|||
Future<void> locateEntries() async {
|
||||
final start = DateTime.now();
|
||||
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 entry.locate();
|
||||
newAddresses.add(entry.addressDetails);
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
import 'dart:collection';
|
||||
|
||||
import 'package:aves/model/image_file_service.dart';
|
||||
import 'package:aves/model/image_metadata.dart';
|
||||
import 'package:aves/model/metadata_service.dart';
|
||||
|
@ -50,19 +48,19 @@ class ImageEntry {
|
|||
|
||||
factory ImageEntry.fromMap(Map map) {
|
||||
return ImageEntry(
|
||||
uri: map['uri'],
|
||||
path: map['path'],
|
||||
contentId: map['contentId'],
|
||||
mimeType: map['mimeType'],
|
||||
width: map['width'],
|
||||
height: map['height'],
|
||||
orientationDegrees: map['orientationDegrees'],
|
||||
sizeBytes: map['sizeBytes'],
|
||||
title: map['title'],
|
||||
dateModifiedSecs: map['dateModifiedSecs'],
|
||||
sourceDateTakenMillis: map['sourceDateTakenMillis'],
|
||||
bucketDisplayName: map['bucketDisplayName'],
|
||||
durationMillis: map['durationMillis'],
|
||||
uri: map['uri'] as String,
|
||||
path: map['path'] as String,
|
||||
contentId: map['contentId'] as int,
|
||||
mimeType: map['mimeType'] as String,
|
||||
width: map['width'] as int,
|
||||
height: map['height'] as int,
|
||||
orientationDegrees: map['orientationDegrees'] as int,
|
||||
sizeBytes: map['sizeBytes'] as int,
|
||||
title: map['title'] as String,
|
||||
dateModifiedSecs: map['dateModifiedSecs'] as int,
|
||||
sourceDateTakenMillis: map['sourceDateTakenMillis'] as int,
|
||||
bucketDisplayName: map['bucketDisplayName'] as String,
|
||||
durationMillis: map['durationMillis'] as int,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -180,13 +178,11 @@ class ImageEntry {
|
|||
|
||||
// admin area examples: Seoul, Geneva, null
|
||||
// locality examples: Mapo-gu, Geneva, Annecy
|
||||
return LinkedHashSet.of(
|
||||
[
|
||||
addressDetails.countryName,
|
||||
addressDetails.adminArea,
|
||||
addressDetails.locality
|
||||
],
|
||||
).where((part) => part != null && part.isNotEmpty).join(', ');
|
||||
return {
|
||||
addressDetails.countryName,
|
||||
addressDetails.adminArea,
|
||||
addressDetails.locality
|
||||
}.where((part) => part != null && part.isNotEmpty).join(', ');
|
||||
}
|
||||
|
||||
bool search(String query) {
|
||||
|
@ -203,13 +199,13 @@ class ImageEntry {
|
|||
if (newFields.isEmpty) return false;
|
||||
|
||||
final uri = newFields['uri'];
|
||||
if (uri != null) this.uri = uri;
|
||||
if (uri is String) this.uri = uri;
|
||||
final path = newFields['path'];
|
||||
if (path != null) this.path = path;
|
||||
if (path is String) this.path = path;
|
||||
final contentId = newFields['contentId'];
|
||||
if (contentId != null) this.contentId = contentId;
|
||||
if (contentId is int) this.contentId = contentId;
|
||||
final title = newFields['title'];
|
||||
if (title != null) this.title = title;
|
||||
if (title is String) this.title = title;
|
||||
metadataChangeNotifier.notifyListeners();
|
||||
return true;
|
||||
}
|
||||
|
@ -223,11 +219,11 @@ class ImageEntry {
|
|||
if (newFields.isEmpty) return false;
|
||||
|
||||
final width = newFields['width'];
|
||||
if (width != null) this.width = width;
|
||||
if (width is int) this.width = width;
|
||||
final height = newFields['height'];
|
||||
if (height != null) this.height = height;
|
||||
if (height is int) this.height = height;
|
||||
final orientationDegrees = newFields['orientationDegrees'];
|
||||
if (orientationDegrees != null) this.orientationDegrees = orientationDegrees;
|
||||
if (orientationDegrees is int) this.orientationDegrees = orientationDegrees;
|
||||
imageChangeNotifier.notifyListeners();
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -65,7 +65,7 @@ class ImageFileService {
|
|||
} on PlatformException catch (e) {
|
||||
debugPrint('rename failed with exception=${e.message}');
|
||||
}
|
||||
return Map();
|
||||
return {};
|
||||
}
|
||||
|
||||
static Future<Map> rotate(ImageEntry entry, {@required bool clockwise}) async {
|
||||
|
@ -79,6 +79,6 @@ class ImageFileService {
|
|||
} on PlatformException catch (e) {
|
||||
debugPrint('rotate failed with exception=${e.message}');
|
||||
}
|
||||
return Map();
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,8 +10,8 @@ class MetadataDb {
|
|||
|
||||
Future<String> get path async => join(await getDatabasesPath(), 'metadata.db');
|
||||
|
||||
static final metadataTable = 'metadata';
|
||||
static final addressTable = 'address';
|
||||
static const metadataTable = 'metadata';
|
||||
static const addressTable = 'address';
|
||||
|
||||
MetadataDb._private();
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ class MetadataService {
|
|||
} on PlatformException catch (e) {
|
||||
debugPrint('getAllMetadata failed with exception=${e.message}');
|
||||
}
|
||||
return Map();
|
||||
return {};
|
||||
}
|
||||
|
||||
static Future<CatalogMetadata> getCatalogMetadata(ImageEntry entry) async {
|
||||
|
|
|
@ -5,12 +5,12 @@ import 'package:shared_preferences/shared_preferences.dart';
|
|||
|
||||
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 {
|
||||
static SharedPreferences prefs;
|
||||
|
||||
ObserverList<SettingsCallback> _listeners = ObserverList<SettingsCallback>();
|
||||
final ObserverList<SettingsCallback> _listeners = ObserverList<SettingsCallback>();
|
||||
|
||||
Settings._private();
|
||||
|
||||
|
@ -32,7 +32,7 @@ class Settings {
|
|||
debugPrint('$runtimeType notifyListeners key=$key, old=$oldValue, new=$newValue');
|
||||
if (_listeners != null) {
|
||||
final List<SettingsCallback> localListeners = _listeners.toList();
|
||||
for (SettingsCallback listener in localListeners) {
|
||||
for (final listener in localListeners) {
|
||||
try {
|
||||
if (_listeners.contains(listener)) {
|
||||
listener(key, oldValue, newValue);
|
||||
|
@ -66,7 +66,7 @@ class Settings {
|
|||
|
||||
T getEnumOrDefault<T>(String key, T defaultValue, Iterable<T> values) {
|
||||
final valueString = prefs.getString(key);
|
||||
for (T element in values) {
|
||||
for (final element in values) {
|
||||
if (element.toString() == valueString) {
|
||||
return element;
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ class AndroidAppService {
|
|||
} on PlatformException catch (e) {
|
||||
debugPrint('getAppNames failed with exception=${e.message}');
|
||||
}
|
||||
return Map();
|
||||
return {};
|
||||
}
|
||||
|
||||
static Future<Uint8List> getAppIcon(String packageName, int size) async {
|
||||
|
|
|
@ -27,7 +27,7 @@ String _decimal2sexagesimal(final double dec) {
|
|||
final List<int> minParts = _split(min);
|
||||
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)}″';
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ bool isAtSameDayAs(DateTime d1, DateTime d2) => isAtSameMonthAs(d1, d2) && d1.da
|
|||
|
||||
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());
|
||||
|
||||
|
|
|
@ -21,6 +21,9 @@ class AllCollectionDrawer extends StatelessWidget {
|
|||
padding: EdgeInsets.only(bottom: window.viewInsets.bottom),
|
||||
children: [
|
||||
DrawerHeader(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).accentColor,
|
||||
),
|
||||
child: SafeArea(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
|
@ -28,18 +31,18 @@ class AllCollectionDrawer extends StatelessWidget {
|
|||
Row(
|
||||
children: [
|
||||
CircleAvatar(
|
||||
backgroundColor: Colors.white,
|
||||
radius: 44,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(top: 6.0),
|
||||
padding: const EdgeInsets.only(top: 6.0),
|
||||
child: SvgPicture.asset(
|
||||
'assets/aves_logo.svg',
|
||||
width: 64,
|
||||
),
|
||||
),
|
||||
backgroundColor: Colors.white,
|
||||
radius: 44,
|
||||
),
|
||||
SizedBox(width: 16),
|
||||
Text(
|
||||
const SizedBox(width: 16),
|
||||
const Text(
|
||||
'Aves',
|
||||
style: TextStyle(
|
||||
fontSize: 44,
|
||||
|
@ -48,28 +51,28 @@ class AllCollectionDrawer extends StatelessWidget {
|
|||
),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 16),
|
||||
const SizedBox(height: 16),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Row(children: [
|
||||
Icon(Icons.photo_library),
|
||||
SizedBox(width: 4),
|
||||
const SizedBox(width: 4),
|
||||
Text('${collection.imageCount}')
|
||||
]),
|
||||
Row(children: [
|
||||
Icon(Icons.video_library),
|
||||
SizedBox(width: 4),
|
||||
const SizedBox(width: 4),
|
||||
Text('${collection.videoCount}')
|
||||
]),
|
||||
Row(children: [
|
||||
Icon(Icons.photo_album),
|
||||
SizedBox(width: 4),
|
||||
const SizedBox(width: 4),
|
||||
Text('${collection.albumCount}')
|
||||
]),
|
||||
Row(children: [
|
||||
Icon(Icons.label),
|
||||
SizedBox(width: 4),
|
||||
const SizedBox(width: 4),
|
||||
Text('${collection.tagCount}')
|
||||
]),
|
||||
],
|
||||
|
@ -77,9 +80,6 @@ class AllCollectionDrawer extends StatelessWidget {
|
|||
],
|
||||
),
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).accentColor,
|
||||
),
|
||||
),
|
||||
_buildFilteredCollectionNavTile(
|
||||
context: context,
|
||||
|
@ -87,14 +87,14 @@ class AllCollectionDrawer extends StatelessWidget {
|
|||
title: 'Videos',
|
||||
filter: (entry) => entry.isVideo,
|
||||
),
|
||||
Divider(),
|
||||
const Divider(),
|
||||
...albums.map((album) => _buildFilteredCollectionNavTile(
|
||||
context: context,
|
||||
leading: IconUtils.getAlbumIcon(context, album) ?? Icon(Icons.photo_album),
|
||||
title: collection.getUniqueAlbumName(album, albums),
|
||||
filter: (entry) => entry.directory == album,
|
||||
)),
|
||||
Divider(),
|
||||
const Divider(),
|
||||
...tags.map((tag) => _buildFilteredCollectionNavTile(
|
||||
context: context,
|
||||
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(
|
||||
top: false,
|
||||
bottom: false,
|
||||
|
|
|
@ -16,7 +16,7 @@ class AllCollectionPage extends StatelessWidget {
|
|||
return ThumbnailCollection(
|
||||
collection: collection,
|
||||
appBar: SliverAppBar(
|
||||
title: Text('All'),
|
||||
title: const Text('All'),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: Icon(Icons.search),
|
||||
|
@ -35,7 +35,7 @@ class AllCollectionPage extends StatelessWidget {
|
|||
value: AlbumAction.sortBySize,
|
||||
child: MenuRow(text: 'Sort by size', checked: collection.sortFactor == SortFactor.size),
|
||||
),
|
||||
PopupMenuDivider(),
|
||||
const PopupMenuDivider(),
|
||||
if (collection.sortFactor == SortFactor.date) ...[
|
||||
PopupMenuItem(
|
||||
value: AlbumAction.groupByAlbum,
|
||||
|
@ -49,14 +49,14 @@ class AllCollectionPage extends StatelessWidget {
|
|||
value: AlbumAction.groupByDay,
|
||||
child: MenuRow(text: 'Group by day', checked: collection.groupFactor == GroupFactor.day),
|
||||
),
|
||||
PopupMenuDivider(),
|
||||
const PopupMenuDivider(),
|
||||
],
|
||||
PopupMenuItem(
|
||||
value: AlbumAction.debug,
|
||||
child: MenuRow(text: 'Debug', icon: Icons.whatshot),
|
||||
),
|
||||
],
|
||||
onSelected: (action) => onActionSelected(context, action),
|
||||
onSelected: (action) => _onActionSelected(context, action),
|
||||
),
|
||||
],
|
||||
floating: true,
|
||||
|
@ -64,7 +64,7 @@ class AllCollectionPage extends StatelessWidget {
|
|||
);
|
||||
}
|
||||
|
||||
onActionSelected(BuildContext context, AlbumAction action) {
|
||||
void _onActionSelected(BuildContext context, AlbumAction action) {
|
||||
switch (action) {
|
||||
case AlbumAction.debug:
|
||||
goToDebug(context);
|
||||
|
|
|
@ -43,14 +43,14 @@ class ImageSearchDelegate extends SearchDelegate<ImageEntry> {
|
|||
|
||||
@override
|
||||
Widget buildSuggestions(BuildContext context) {
|
||||
return SizedBox.shrink();
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget buildResults(BuildContext context) {
|
||||
if (query.isEmpty) {
|
||||
showSuggestions(context);
|
||||
return SizedBox.shrink();
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
final lowerQuery = query.toLowerCase();
|
||||
final matches = collection.sortedEntries.where((entry) => entry.search(lowerQuery)).toList();
|
||||
|
|
|
@ -7,13 +7,13 @@ class DaySectionHeader extends StatelessWidget {
|
|||
final String text;
|
||||
|
||||
DaySectionHeader({Key key, DateTime date})
|
||||
: text = formatDate(date),
|
||||
: text = _formatDate(date),
|
||||
super(key: key);
|
||||
|
||||
static DateFormat md = DateFormat.MMMMd();
|
||||
static DateFormat ymd = DateFormat.yMMMMd();
|
||||
|
||||
static formatDate(DateTime date) {
|
||||
static String _formatDate(DateTime date) {
|
||||
if (isToday(date)) return 'Today';
|
||||
if (isYesterday(date)) return 'Yesterday';
|
||||
if (isThisYear(date)) return md.format(date);
|
||||
|
@ -30,13 +30,13 @@ class MonthSectionHeader extends StatelessWidget {
|
|||
final String text;
|
||||
|
||||
MonthSectionHeader({Key key, DateTime date})
|
||||
: text = formatDate(date),
|
||||
: text = _formatDate(date),
|
||||
super(key: key);
|
||||
|
||||
static DateFormat m = DateFormat.MMMM();
|
||||
static DateFormat ym = DateFormat.yMMMM();
|
||||
|
||||
static formatDate(DateTime date) {
|
||||
static String _formatDate(DateTime date) {
|
||||
if (isThisMonth(date)) return 'This month';
|
||||
if (isThisYear(date)) return m.format(date);
|
||||
return ym.format(date);
|
||||
|
@ -64,7 +64,7 @@ class TitleSectionHeader extends StatelessWidget {
|
|||
fontFamily: 'Concourse Caps',
|
||||
shadows: [
|
||||
Shadow(
|
||||
offset: Offset(0, 2),
|
||||
offset: const Offset(0, 2),
|
||||
blurRadius: 3,
|
||||
color: Colors.grey[900],
|
||||
),
|
||||
|
@ -74,12 +74,12 @@ class TitleSectionHeader extends StatelessWidget {
|
|||
outlineWidth: 2,
|
||||
);
|
||||
return Container(
|
||||
padding: EdgeInsets.all(16),
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: leading != null
|
||||
? Row(
|
||||
children: [
|
||||
leading,
|
||||
SizedBox(width: 8),
|
||||
const SizedBox(width: 8),
|
||||
text,
|
||||
],
|
||||
)
|
||||
|
|
|
@ -13,7 +13,7 @@ class ThumbnailCollection extends AnimatedWidget {
|
|||
final ImageCollection collection;
|
||||
final Widget appBar;
|
||||
|
||||
ThumbnailCollection({
|
||||
const ThumbnailCollection({
|
||||
Key key,
|
||||
this.collection,
|
||||
this.appBar,
|
||||
|
@ -65,6 +65,12 @@ class ThumbnailCollectionContent extends StatelessWidget {
|
|||
child: Selector<MediaQueryData, double>(
|
||||
selector: (c, mq) => mq.viewInsets.bottom,
|
||||
builder: (c, mqViewInsetsBottom, child) => DraggableScrollbar.arrows(
|
||||
controller: _scrollController,
|
||||
padding: EdgeInsets.only(
|
||||
// top padding to adjust scroll thumb
|
||||
top: topPadding,
|
||||
bottom: mqViewInsetsBottom,
|
||||
),
|
||||
child: CustomScrollView(
|
||||
controller: _scrollController,
|
||||
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
|
||||
Widget build(BuildContext context) {
|
||||
final columnCount = 4;
|
||||
const columnCount = 4;
|
||||
return SliverStickyHeader(
|
||||
header: SectionHeader(
|
||||
collection: collection,
|
||||
|
@ -143,7 +143,7 @@ class SectionSliver extends StatelessWidget {
|
|||
addAutomaticKeepAlives: false,
|
||||
addRepaintBoundaries: true,
|
||||
),
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: columnCount,
|
||||
),
|
||||
),
|
||||
|
@ -151,7 +151,7 @@ class SectionSliver extends StatelessWidget {
|
|||
);
|
||||
}
|
||||
|
||||
_showFullscreen(BuildContext context, ImageEntry entry) {
|
||||
void _showFullscreen(BuildContext context, ImageEntry entry) {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
|
@ -178,11 +178,11 @@ class SectionHeader extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Widget header = SizedBox.shrink();
|
||||
Widget header = const SizedBox.shrink();
|
||||
if (collection.sortFactor == SortFactor.date) {
|
||||
switch (collection.groupFactor) {
|
||||
case GroupFactor.album:
|
||||
Widget albumIcon = IconUtils.getAlbumIcon(context, sectionKey);
|
||||
Widget albumIcon = IconUtils.getAlbumIcon(context, sectionKey as String);
|
||||
if (albumIcon != null) {
|
||||
albumIcon = Material(
|
||||
type: MaterialType.circle,
|
||||
|
@ -194,14 +194,14 @@ class SectionHeader extends StatelessWidget {
|
|||
}
|
||||
header = TitleSectionHeader(
|
||||
leading: albumIcon,
|
||||
title: collection.getUniqueAlbumName(sectionKey, sections.keys.cast<String>()),
|
||||
title: collection.getUniqueAlbumName(sectionKey as String, sections.keys.cast<String>()),
|
||||
);
|
||||
break;
|
||||
case GroupFactor.month:
|
||||
header = MonthSectionHeader(date: sectionKey);
|
||||
header = MonthSectionHeader(date: sectionKey as DateTime);
|
||||
break;
|
||||
case GroupFactor.day:
|
||||
header = DaySectionHeader(date: sectionKey);
|
||||
header = DaySectionHeader(date: sectionKey as DateTime);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -42,7 +42,7 @@ class AppIconState extends State<AppIcon> {
|
|||
width: widget.size,
|
||||
height: widget.size,
|
||||
)
|
||||
: SizedBox.shrink();
|
||||
: const SizedBox.shrink();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ import 'package:flutter/widgets.dart';
|
|||
class FakeAppBar extends StatelessWidget with PreferredSizeWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SafeArea(child: SizedBox.shrink());
|
||||
return const SafeArea(child: SizedBox.shrink());
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
|
@ -61,10 +61,10 @@ class OverlayIcon extends StatelessWidget {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
margin: EdgeInsets.all(1),
|
||||
margin: const EdgeInsets.all(1),
|
||||
padding: text != null ? EdgeInsets.only(right: iconSize / 4) : null,
|
||||
decoration: BoxDecoration(
|
||||
color: Color(0xBB000000),
|
||||
color: const Color(0xBB000000),
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(iconSize),
|
||||
),
|
||||
|
@ -78,7 +78,7 @@ class OverlayIcon extends StatelessWidget {
|
|||
size: iconSize,
|
||||
),
|
||||
if (text != null) ...[
|
||||
SizedBox(width: 2),
|
||||
const SizedBox(width: 2),
|
||||
Text(text),
|
||||
]
|
||||
],
|
||||
|
|
|
@ -40,33 +40,33 @@ class ImagePreviewState extends State<ImagePreview> with AfterInitMixin {
|
|||
entry.imageChangeNotifier,
|
||||
entry.metadataChangeNotifier
|
||||
]);
|
||||
_entryChangeNotifier.addListener(onEntryChange);
|
||||
_entryChangeNotifier.addListener(_onEntryChange);
|
||||
}
|
||||
|
||||
@override
|
||||
void didInitState() {
|
||||
_devicePixelRatio = Provider.of<MediaQueryData>(context, listen: false).devicePixelRatio;
|
||||
initByteLoader();
|
||||
_initByteLoader();
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(ImagePreview 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;
|
||||
initByteLoader();
|
||||
_initByteLoader();
|
||||
}
|
||||
|
||||
initByteLoader() {
|
||||
void _initByteLoader() {
|
||||
final width = (widget.width * _devicePixelRatio).round();
|
||||
final height = (widget.height * _devicePixelRatio).round();
|
||||
_byteLoader = ImageFileService.getImageBytes(widget.entry, width, height);
|
||||
}
|
||||
|
||||
onEntryChange() => setState(() => initByteLoader());
|
||||
void _onEntryChange() => setState(() => _initByteLoader());
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_entryChangeNotifier.removeListener(onEntryChange);
|
||||
_entryChangeNotifier.removeListener(_onEntryChange);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
|
|
|
@ -21,11 +21,11 @@ class MenuRow extends StatelessWidget {
|
|||
opacity: checked ? 1 : 0,
|
||||
child: Icon(Icons.done),
|
||||
),
|
||||
SizedBox(width: 8),
|
||||
const SizedBox(width: 8),
|
||||
],
|
||||
if (icon != null) ...[
|
||||
Icon(icon),
|
||||
SizedBox(width: 8),
|
||||
const SizedBox(width: 8),
|
||||
],
|
||||
Text(text),
|
||||
],
|
||||
|
|
|
@ -39,48 +39,48 @@ class DebugPageState extends State<DebugPage> {
|
|||
return MediaQueryDataProvider(
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text('Info'),
|
||||
title: const Text('Info'),
|
||||
),
|
||||
body: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('Paths'),
|
||||
const Text('Paths'),
|
||||
Text('DCIM path: ${androidFileUtils.dcimPath}'),
|
||||
Text('pictures path: ${androidFileUtils.picturesPath}'),
|
||||
Divider(),
|
||||
Text('Settings'),
|
||||
const Divider(),
|
||||
const Text('Settings'),
|
||||
Text('collectionGroupFactor: ${settings.collectionGroupFactor}'),
|
||||
Text('collectionSortFactor: ${settings.collectionSortFactor}'),
|
||||
Text('infoMapZoom: ${settings.infoMapZoom}'),
|
||||
Divider(),
|
||||
const Divider(),
|
||||
Text('Entries: ${entries.length}'),
|
||||
...byMimeTypes.keys.map((mimeType) => Text('- $mimeType: ${byMimeTypes[mimeType].length}')),
|
||||
Text('Catalogued: ${catalogued.length}'),
|
||||
Text('With GPS: ${withGps.length}'),
|
||||
Text('With address: ${located.length}'),
|
||||
Divider(),
|
||||
const Divider(),
|
||||
RaisedButton(
|
||||
onPressed: () => metadataDb.reset(),
|
||||
child: Text('Reset DB'),
|
||||
child: const Text('Reset DB'),
|
||||
),
|
||||
FutureBuilder(
|
||||
future: _dbMetadataLoader,
|
||||
builder: (futureContext, AsyncSnapshot<List<CatalogMetadata>> snapshot) {
|
||||
if (snapshot.hasError) return Text(snapshot.error);
|
||||
if (snapshot.connectionState != ConnectionState.done) return SizedBox.shrink();
|
||||
if (snapshot.hasError) return Text(snapshot.error.toString());
|
||||
if (snapshot.connectionState != ConnectionState.done) return const SizedBox.shrink();
|
||||
return Text('DB metadata rows: ${snapshot.data.length}');
|
||||
},
|
||||
),
|
||||
FutureBuilder(
|
||||
future: _dbAddressLoader,
|
||||
builder: (futureContext, AsyncSnapshot<List<AddressDetails>> snapshot) {
|
||||
if (snapshot.hasError) return Text(snapshot.error);
|
||||
if (snapshot.connectionState != ConnectionState.done) return SizedBox.shrink();
|
||||
if (snapshot.hasError) return Text(snapshot.error.toString());
|
||||
if (snapshot.connectionState != ConnectionState.done) return const SizedBox.shrink();
|
||||
return Text('DB address rows: ${snapshot.data.length}');
|
||||
},
|
||||
),
|
||||
Divider(),
|
||||
Text('Time dilation'),
|
||||
const Divider(),
|
||||
const Text('Time dilation'),
|
||||
Slider(
|
||||
value: timeDilation,
|
||||
onChanged: (v) => setState(() => timeDilation = v),
|
||||
|
|
|
@ -61,14 +61,14 @@ class FullscreenActionDelegate {
|
|||
void _showFeedback(BuildContext context, String message) {
|
||||
Flushbar(
|
||||
message: message,
|
||||
margin: EdgeInsets.all(8),
|
||||
margin: const EdgeInsets.all(8),
|
||||
borderRadius: 8,
|
||||
borderColor: Colors.white30,
|
||||
borderWidth: 0.5,
|
||||
duration: Duration(seconds: 2),
|
||||
duration: const Duration(seconds: 2),
|
||||
flushbarPosition: FlushbarPosition.TOP,
|
||||
animationDuration: Duration(milliseconds: 600),
|
||||
)..show(context);
|
||||
animationDuration: const Duration(milliseconds: 600),
|
||||
).show(context);
|
||||
}
|
||||
|
||||
Future<void> _print(ImageEntry entry) async {
|
||||
|
@ -94,15 +94,15 @@ class FullscreenActionDelegate {
|
|||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
content: Text('Are you sure?'),
|
||||
content: const Text('Are you sure?'),
|
||||
actions: [
|
||||
FlatButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text('CANCEL'),
|
||||
child: const Text('CANCEL'),
|
||||
),
|
||||
FlatButton(
|
||||
onPressed: () => Navigator.pop(context, true),
|
||||
child: Text('DELETE'),
|
||||
child: const Text('DELETE'),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
@ -130,11 +130,11 @@ class FullscreenActionDelegate {
|
|||
actions: [
|
||||
FlatButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text('CANCEL'),
|
||||
child: const Text('CANCEL'),
|
||||
),
|
||||
FlatButton(
|
||||
onPressed: () => Navigator.pop(context, controller.text),
|
||||
child: Text('APPLY'),
|
||||
child: const Text('APPLY'),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
|
|
@ -61,13 +61,13 @@ class FullscreenBodyState extends State<FullscreenBody> with SingleTickerProvide
|
|||
bool _isInitialScale = true;
|
||||
int _currentHorizontalPage, _currentVerticalPage = imagePage;
|
||||
PageController _horizontalPager, _verticalPager;
|
||||
ValueNotifier<bool> _overlayVisible = ValueNotifier(true);
|
||||
final ValueNotifier<bool> _overlayVisible = ValueNotifier(true);
|
||||
AnimationController _overlayAnimationController;
|
||||
Animation<double> _topOverlayScale;
|
||||
Animation<Offset> _bottomOverlayOffset;
|
||||
EdgeInsets _frozenViewInsets, _frozenViewPadding;
|
||||
FullscreenActionDelegate _actionDelegate;
|
||||
List<Tuple2<String, VideoPlayerController>> _videoControllers = List();
|
||||
final List<Tuple2<String, VideoPlayerController>> _videoControllers = [];
|
||||
|
||||
ImageCollection get collection => widget.collection;
|
||||
|
||||
|
@ -85,14 +85,14 @@ class FullscreenBodyState extends State<FullscreenBody> with SingleTickerProvide
|
|||
_horizontalPager = PageController(initialPage: _currentHorizontalPage);
|
||||
_verticalPager = PageController(initialPage: _currentVerticalPage);
|
||||
_overlayAnimationController = AnimationController(
|
||||
duration: Duration(milliseconds: 400),
|
||||
duration: const Duration(milliseconds: 400),
|
||||
vsync: this,
|
||||
);
|
||||
_topOverlayScale = CurvedAnimation(
|
||||
parent: _overlayAnimationController,
|
||||
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,
|
||||
curve: Curves.easeOutQuart,
|
||||
));
|
||||
|
@ -105,7 +105,7 @@ class FullscreenBodyState extends State<FullscreenBody> with SingleTickerProvide
|
|||
_initOverlay();
|
||||
}
|
||||
|
||||
_initOverlay() async {
|
||||
Future<void> _initOverlay() async {
|
||||
// wait for MaterialPageRoute.transitionDuration
|
||||
// to show overlay after hero animation is complete
|
||||
await Future.delayed(Duration(milliseconds: (300 * timeDilation).toInt()));
|
||||
|
@ -136,10 +136,10 @@ class FullscreenBodyState extends State<FullscreenBody> with SingleTickerProvide
|
|||
PageView(
|
||||
scrollDirection: Axis.vertical,
|
||||
controller: _verticalPager,
|
||||
physics: _isInitialScale ? PageScrollPhysics() : NeverScrollableScrollPhysics(),
|
||||
physics: _isInitialScale ? const PageScrollPhysics() : const NeverScrollableScrollPhysics(),
|
||||
onPageChanged: _onVerticalPageChanged,
|
||||
children: [
|
||||
SizedBox(),
|
||||
const SizedBox(),
|
||||
Container(
|
||||
color: Colors.black,
|
||||
child: ImagePage(
|
||||
|
@ -208,12 +208,12 @@ class FullscreenBodyState extends State<FullscreenBody> with SingleTickerProvide
|
|||
Future<void> _goToVerticalPage(int page) {
|
||||
return _verticalPager.animateToPage(
|
||||
page,
|
||||
duration: Duration(milliseconds: 350),
|
||||
duration: const Duration(milliseconds: 350),
|
||||
curve: Curves.easeInOut,
|
||||
);
|
||||
}
|
||||
|
||||
void _onVerticalPageChanged(page) {
|
||||
void _onVerticalPageChanged(int page) {
|
||||
setState(() => _currentVerticalPage = page);
|
||||
if (_currentVerticalPage == transitionPage) {
|
||||
_onLeave();
|
||||
|
|
|
@ -51,7 +51,7 @@ class ImagePageState extends State<ImagePage> with AutomaticKeepAliveClientMixin
|
|||
entry: entry,
|
||||
controller: videoController,
|
||||
)
|
||||
: SizedBox(),
|
||||
: const SizedBox(),
|
||||
childSize: mqSize,
|
||||
// no hero as most videos fullscreen image is different from its thumbnail
|
||||
minScale: PhotoViewComputedScale.contained,
|
||||
|
@ -70,14 +70,14 @@ class ImagePageState extends State<ImagePage> with AutomaticKeepAliveClientMixin
|
|||
onTapUp: (tapContext, details, value) => widget.onTap?.call(),
|
||||
);
|
||||
},
|
||||
loadingChild: Center(
|
||||
loadingChild: const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
backgroundDecoration: BoxDecoration(color: Colors.transparent),
|
||||
pageController: widget.pageController,
|
||||
onPageChanged: widget.onPageChanged,
|
||||
scaleStateChangedCallback: widget.onScaleChanged,
|
||||
scrollPhysics: BouncingScrollPhysics(),
|
||||
scrollPhysics: const BouncingScrollPhysics(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -119,7 +119,7 @@ class SectionRow extends StatelessWidget {
|
|||
padding: const EdgeInsets.all(16.0),
|
||||
child: Text(
|
||||
title,
|
||||
style: TextStyle(
|
||||
style: const TextStyle(
|
||||
fontSize: 20,
|
||||
fontFamily: 'Concourse Caps',
|
||||
),
|
||||
|
@ -142,9 +142,9 @@ class InfoRow extends StatelessWidget {
|
|||
padding: const EdgeInsets.symmetric(vertical: 4.0),
|
||||
child: RichText(
|
||||
text: TextSpan(
|
||||
style: TextStyle(fontFamily: 'Concourse'),
|
||||
style: const TextStyle(fontFamily: 'Concourse'),
|
||||
children: [
|
||||
TextSpan(text: '$label ', style: TextStyle(color: Colors.white70)),
|
||||
TextSpan(text: '$label ', style: const TextStyle(color: Colors.white70)),
|
||||
TextSpan(text: value),
|
||||
],
|
||||
),
|
||||
|
|
|
@ -7,7 +7,7 @@ import 'package:google_maps_flutter/google_maps_flutter.dart';
|
|||
|
||||
class LocationSection extends AnimatedWidget {
|
||||
final ImageEntry entry;
|
||||
final showTitle;
|
||||
final bool showTitle;
|
||||
|
||||
LocationSection({
|
||||
Key key,
|
||||
|
@ -23,12 +23,12 @@ class LocationSection extends AnimatedWidget {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return !entry.hasGps
|
||||
? SizedBox.shrink()
|
||||
? const SizedBox.shrink()
|
||||
: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (showTitle)
|
||||
Padding(
|
||||
const Padding(
|
||||
padding: EdgeInsets.only(bottom: 8),
|
||||
child: SectionRow('Location'),
|
||||
),
|
||||
|
@ -43,7 +43,7 @@ class LocationSection extends AnimatedWidget {
|
|||
),
|
||||
if (entry.isLocated)
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 8),
|
||||
padding: const EdgeInsets.only(top: 8),
|
||||
child: InfoRow('Address', entry.addressDetails.addressLine),
|
||||
),
|
||||
],
|
||||
|
@ -90,7 +90,7 @@ class ImageMapState extends State<ImageMap> with AutomaticKeepAliveClientMixin {
|
|||
child: SizedBox(
|
||||
height: 200,
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.all(
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(16),
|
||||
),
|
||||
child: GoogleMap(
|
||||
|
@ -104,18 +104,18 @@ class ImageMapState extends State<ImageMap> with AutomaticKeepAliveClientMixin {
|
|||
zoomGesturesEnabled: false,
|
||||
tiltGesturesEnabled: false,
|
||||
myLocationButtonEnabled: false,
|
||||
markers: [
|
||||
markers: {
|
||||
Marker(
|
||||
markerId: MarkerId(widget.markerId),
|
||||
icon: BitmapDescriptor.defaultMarkerWithHue(accentHue),
|
||||
position: widget.latLng,
|
||||
)
|
||||
].toSet(),
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(width: 8),
|
||||
const SizedBox(width: 8),
|
||||
Column(children: [
|
||||
IconButton(
|
||||
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;
|
||||
_controller.animateCamera(CameraUpdate.zoomBy(amount));
|
||||
}
|
||||
|
|
|
@ -23,16 +23,16 @@ class MetadataSectionState extends State<MetadataSection> {
|
|||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
initMetadataLoader();
|
||||
_initMetadataLoader();
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(MetadataSection oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
initMetadataLoader();
|
||||
_initMetadataLoader();
|
||||
}
|
||||
|
||||
initMetadataLoader() async {
|
||||
Future<void> _initMetadataLoader() async {
|
||||
_metadataLoader = MetadataService.getAllMetadata(widget.entry);
|
||||
}
|
||||
|
||||
|
@ -43,9 +43,9 @@ class MetadataSectionState extends State<MetadataSection> {
|
|||
builder: (c, mqWidth, child) => FutureBuilder(
|
||||
future: _metadataLoader,
|
||||
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>();
|
||||
if (snapshot.hasError) return Text(snapshot.error.toString());
|
||||
if (snapshot.connectionState != ConnectionState.done) return const SizedBox.shrink();
|
||||
final directoryNames = metadataMap.keys.toList()..sort();
|
||||
|
||||
Widget content;
|
||||
|
@ -68,7 +68,7 @@ class MetadataSectionState extends State<MetadataSection> {
|
|||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(child: getMetadataColumn(metadataMap, first)),
|
||||
SizedBox(width: 8),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(child: getMetadataColumn(metadataMap, second)),
|
||||
],
|
||||
);
|
||||
|
@ -79,7 +79,7 @@ class MetadataSectionState extends State<MetadataSection> {
|
|||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
SectionRow('Metadata'),
|
||||
const SectionRow('Metadata'),
|
||||
content,
|
||||
],
|
||||
);
|
||||
|
@ -98,19 +98,19 @@ class MetadataSectionState extends State<MetadataSection> {
|
|||
return [
|
||||
if (directoryName.isNotEmpty)
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 4.0),
|
||||
padding: const EdgeInsets.symmetric(vertical: 4.0),
|
||||
child: Text(directoryName,
|
||||
style: TextStyle(
|
||||
style: const TextStyle(
|
||||
fontSize: 18,
|
||||
fontFamily: 'Concourse Caps',
|
||||
)),
|
||||
),
|
||||
...tagKeys.map((tagKey) {
|
||||
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);
|
||||
}),
|
||||
SizedBox(height: 16),
|
||||
const SizedBox(height: 16),
|
||||
];
|
||||
}),
|
||||
],
|
||||
|
|
|
@ -18,15 +18,15 @@ class XmpTagSection extends AnimatedWidget {
|
|||
Widget build(BuildContext context) {
|
||||
final tags = entry.xmpSubjects;
|
||||
return tags.isEmpty
|
||||
? SizedBox.shrink()
|
||||
? const SizedBox.shrink()
|
||||
: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SectionRow('XMP Tags'),
|
||||
const SectionRow('XMP Tags'),
|
||||
Wrap(
|
||||
children: tags
|
||||
.map((tag) => Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 4.0),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4.0),
|
||||
child: ActionChip(
|
||||
label: Text(tag),
|
||||
onPressed: () => Navigator.push(
|
||||
|
|
|
@ -45,16 +45,16 @@ class _FullscreenBottomOverlayState extends State<FullscreenBottomOverlay> {
|
|||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
initDetailLoader();
|
||||
_initDetailLoader();
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(FullscreenBottomOverlay oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
initDetailLoader();
|
||||
_initDetailLoader();
|
||||
}
|
||||
|
||||
initDetailLoader() {
|
||||
void _initDetailLoader() {
|
||||
_detailLoader = MetadataService.getOverlayMetadata(entry);
|
||||
}
|
||||
|
||||
|
@ -86,7 +86,7 @@ class _FullscreenBottomOverlayState extends State<FullscreenBottomOverlay> {
|
|||
_lastEntry = entry;
|
||||
}
|
||||
return _lastEntry == null
|
||||
? SizedBox.shrink()
|
||||
? const SizedBox.shrink()
|
||||
: _FullscreenBottomOverlayContent(
|
||||
entry: _lastEntry,
|
||||
details: _lastDetails,
|
||||
|
@ -115,7 +115,7 @@ class _FullscreenBottomOverlayContent extends StatelessWidget {
|
|||
final String position;
|
||||
final double maxWidth;
|
||||
|
||||
_FullscreenBottomOverlayContent({
|
||||
const _FullscreenBottomOverlayContent({
|
||||
this.entry,
|
||||
this.details,
|
||||
this.position,
|
||||
|
|
|
@ -28,14 +28,14 @@ class FullscreenTopOverlay extends StatelessWidget {
|
|||
return SafeArea(
|
||||
minimum: (viewInsets ?? EdgeInsets.zero) + (viewPadding ?? EdgeInsets.zero),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(8.0),
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Row(
|
||||
children: [
|
||||
OverlayButton(
|
||||
scale: scale,
|
||||
child: BackButton(),
|
||||
child: const BackButton(),
|
||||
),
|
||||
Spacer(),
|
||||
const Spacer(),
|
||||
OverlayButton(
|
||||
scale: scale,
|
||||
child: IconButton(
|
||||
|
@ -44,7 +44,7 @@ class FullscreenTopOverlay extends StatelessWidget {
|
|||
tooltip: 'Share',
|
||||
),
|
||||
),
|
||||
SizedBox(width: 8),
|
||||
const SizedBox(width: 8),
|
||||
OverlayButton(
|
||||
scale: scale,
|
||||
child: IconButton(
|
||||
|
@ -53,7 +53,7 @@ class FullscreenTopOverlay extends StatelessWidget {
|
|||
tooltip: 'Delete',
|
||||
),
|
||||
),
|
||||
SizedBox(width: 8),
|
||||
const SizedBox(width: 8),
|
||||
OverlayButton(
|
||||
scale: scale,
|
||||
child: PopupMenuButton<FullscreenAction>(
|
||||
|
@ -81,21 +81,21 @@ class FullscreenTopOverlay extends StatelessWidget {
|
|||
value: FullscreenAction.print,
|
||||
child: MenuRow(text: 'Print', icon: Icons.print),
|
||||
),
|
||||
PopupMenuDivider(),
|
||||
PopupMenuItem(
|
||||
const PopupMenuDivider(),
|
||||
const PopupMenuItem(
|
||||
value: FullscreenAction.edit,
|
||||
child: Text('Edit with…'),
|
||||
),
|
||||
PopupMenuItem(
|
||||
const PopupMenuItem(
|
||||
value: FullscreenAction.open,
|
||||
child: Text('Open with…'),
|
||||
),
|
||||
PopupMenuItem(
|
||||
const PopupMenuItem(
|
||||
value: FullscreenAction.setAs,
|
||||
child: Text('Set as…'),
|
||||
),
|
||||
if (entry.hasGps)
|
||||
PopupMenuItem(
|
||||
const PopupMenuItem(
|
||||
value: FullscreenAction.openMap,
|
||||
child: Text('Show on map…'),
|
||||
),
|
||||
|
|
|
@ -46,32 +46,32 @@ class VideoControlOverlayState extends State<VideoControlOverlay> with SingleTic
|
|||
void initState() {
|
||||
super.initState();
|
||||
_playPauseAnimation = AnimationController(
|
||||
duration: Duration(milliseconds: 300),
|
||||
duration: const Duration(milliseconds: 300),
|
||||
vsync: this,
|
||||
);
|
||||
registerWidget(widget);
|
||||
_registerWidget(widget);
|
||||
_onValueChange();
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(VideoControlOverlay oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
unregisterWidget(oldWidget);
|
||||
registerWidget(widget);
|
||||
_unregisterWidget(oldWidget);
|
||||
_registerWidget(widget);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
unregisterWidget(widget);
|
||||
_unregisterWidget(widget);
|
||||
_playPauseAnimation.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void registerWidget(VideoControlOverlay widget) {
|
||||
void _registerWidget(VideoControlOverlay widget) {
|
||||
widget.controller.addListener(_onValueChange);
|
||||
}
|
||||
|
||||
void unregisterWidget(VideoControlOverlay widget) {
|
||||
void _unregisterWidget(VideoControlOverlay widget) {
|
||||
widget.controller.removeListener(_onValueChange);
|
||||
}
|
||||
|
||||
|
@ -86,7 +86,7 @@ class VideoControlOverlayState extends State<VideoControlOverlay> with SingleTic
|
|||
|
||||
final viewInsets = widget.viewInsets ?? mqViewInsets;
|
||||
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(
|
||||
padding: safePadding,
|
||||
|
@ -109,7 +109,7 @@ class VideoControlOverlayState extends State<VideoControlOverlay> with SingleTic
|
|||
Expanded(
|
||||
child: _buildProgressBar(),
|
||||
),
|
||||
SizedBox(width: 8),
|
||||
const SizedBox(width: 8),
|
||||
OverlayButton(
|
||||
scale: scale,
|
||||
child: IconButton(
|
||||
|
@ -130,7 +130,7 @@ class VideoControlOverlayState extends State<VideoControlOverlay> with SingleTic
|
|||
}
|
||||
|
||||
Widget _buildProgressBar() {
|
||||
final progressBarBorderRadius = 123.0;
|
||||
const progressBarBorderRadius = 123.0;
|
||||
return SizeTransition(
|
||||
sizeFactor: scale,
|
||||
child: BlurredRRect(
|
||||
|
@ -150,11 +150,11 @@ class VideoControlOverlayState extends State<VideoControlOverlay> with SingleTic
|
|||
if (_playingOnDragStart) controller.play();
|
||||
},
|
||||
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(
|
||||
color: Colors.black26,
|
||||
border: Border.all(color: Colors.white30, width: 0.5),
|
||||
borderRadius: BorderRadius.all(
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(progressBarBorderRadius),
|
||||
),
|
||||
),
|
||||
|
@ -164,7 +164,7 @@ class VideoControlOverlayState extends State<VideoControlOverlay> with SingleTic
|
|||
Row(
|
||||
children: [
|
||||
Text(formatDuration(value.position ?? Duration.zero)),
|
||||
Spacer(),
|
||||
const Spacer(),
|
||||
Text(formatDuration(value.duration ?? Duration.zero)),
|
||||
],
|
||||
),
|
||||
|
|
|
@ -29,34 +29,34 @@ class AvesVideoState extends State<AvesVideo> {
|
|||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
registerWidget(widget);
|
||||
_registerWidget(widget);
|
||||
_onValueChange();
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(AvesVideo oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
unregisterWidget(oldWidget);
|
||||
registerWidget(widget);
|
||||
_unregisterWidget(oldWidget);
|
||||
_registerWidget(widget);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
unregisterWidget(widget);
|
||||
_unregisterWidget(widget);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
registerWidget(AvesVideo widget) {
|
||||
void _registerWidget(AvesVideo widget) {
|
||||
widget.controller.addListener(_onValueChange);
|
||||
}
|
||||
|
||||
unregisterWidget(AvesVideo widget) {
|
||||
void _unregisterWidget(AvesVideo widget) {
|
||||
widget.controller.removeListener(_onValueChange);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (value == null) return SizedBox();
|
||||
if (value == null) return const SizedBox();
|
||||
if (value.hasError) {
|
||||
return Selector<MediaQueryData, double>(
|
||||
selector: (c, mq) => mq.size.width,
|
||||
|
@ -79,12 +79,12 @@ class AvesVideoState extends State<AvesVideo> {
|
|||
);
|
||||
}
|
||||
|
||||
_onValueChange() {
|
||||
if (!value.isPlaying && value.position == value.duration) goToBeginning();
|
||||
void _onValueChange() {
|
||||
if (!value.isPlaying && value.position == value.duration) _goToStart();
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
goToBeginning() async {
|
||||
Future<void> _goToStart() async {
|
||||
await widget.controller.seekTo(Duration.zero);
|
||||
await widget.controller.pause();
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue