fixed logo, applied pedantic, added startup timing TODOs

This commit is contained in:
Thibault Deckers 2019-12-21 00:29:14 +09:00
parent f49b9b2e24
commit cee585d03c
19 changed files with 125 additions and 177 deletions

5
analysis_options.yaml Normal file
View file

@ -0,0 +1,5 @@
include: package:pedantic/analysis_options.yaml
analyzer:
exclude:
- lib/generated_plugin_registrant.dart

View file

@ -1,87 +1 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <svg xmlns="http://www.w3.org/2000/svg" width="265.554" height="244.35"><path d="M0 10.84L166.56 177.4l43.778-43.775L87.553 10.84z" fill="#3f51b5"/><path d="M99.61 132.726V244.35l55.812-55.812z" fill="#4caf50"/><path d="M210.337 111.624V0l-55.812 55.812z" fill="#ffc107"/><path d="M265.554 39.465L226.089 0v39.465z" fill="#ff5722"/></svg>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="265.55396"
height="244.35027"
version="1.1"
id="svg16"
sodipodi:docname="aves_logo.svg"
inkscape:export-filename="C:\Users\tibo\Downloads\aves0030.png"
inkscape:export-xdpi="98.300003"
inkscape:export-ydpi="98.300003"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)">
<metadata
id="metadata20">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1600"
inkscape:window-height="837"
id="namedview18"
showgrid="false"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
inkscape:zoom="1.37"
inkscape:cx="171.45337"
inkscape:cy="140.60642"
inkscape:window-x="1358"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="svg16">
<inkscape:grid
type="xygrid"
id="grid28"
originx="-117.22302"
originy="-117.82488" />
</sodipodi:namedview>
<defs
id="defs6" />
<g
id="g4567"
transform="translate(-128.91211,-134.61749)">
<path
id="rect3756"
d="M 128.91211,145.45703 295.47266,312.01758 339.25,268.24219 216.46484,145.45703 Z"
style="fill:#3f51b5;fill-opacity:1;stroke-width:1.91993546"
inkscape:connector-curvature="0" />
<path
inkscape:connector-curvature="0"
id="rect4589"
d="m 228.52199,267.34358 v 111.62418 l 55.81209,-55.81209 z"
style="fill:#4caf50;fill-opacity:1;stroke-width:1.2847122" />
<path
inkscape:connector-curvature="0"
id="rect4589-5"
d="M 339.24927,246.24167 V 134.61749 l -55.81209,55.81209 z"
style="fill:#ffc107;fill-opacity:1;stroke-width:1.2847122" />
<path
inkscape:connector-curvature="0"
id="rect4589-5-8"
d="m 394.46607,174.08259 -39.46511,-39.4651 v 39.4651 z"
style="fill:#ff5722;fill-opacity:1;stroke-width:0.6423561" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 338 B

View file

@ -11,14 +11,20 @@ import 'package:aves/widgets/common/icons.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_native_timezone/flutter_native_timezone.dart'; import 'package:flutter_native_timezone/flutter_native_timezone.dart';
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();
void main() { void main() {
debugPrint('main start, elapsed=${stopwatch.elapsed}');
// initialize binding/plugins to configure Skia before `runApp` // initialize binding/plugins to configure Skia before `runApp`
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized(); // 220ms
// debugPrint('main WidgetsFlutterBinding.ensureInitialized done, elapsed=${stopwatch.elapsed}');
// configure Skia cache to prevent zoomed images becoming black, cf https://github.com/flutter/flutter/issues/36191 // configure Skia cache to prevent zoomed images becoming black, cf https://github.com/flutter/flutter/issues/36191
SystemChannels.skia.invokeMethod('Skia.setResourceCacheMaxBytes', 512 * (1 << 20)); SystemChannels.skia.invokeMethod('Skia.setResourceCacheMaxBytes', 512 * (1 << 20)); // <20ms
// debugPrint('main Skia.setResourceCacheMaxBytes done, elapsed=${stopwatch.elapsed}');
runApp(AvesApp()); runApp(AvesApp());
} }
@ -64,42 +70,55 @@ class _HomePageState extends State<HomePage> {
Screen.keepOn(true); Screen.keepOn(true);
} }
setup() async { Future<void> setup() async {
final permissions = await PermissionHandler().requestPermissions([PermissionGroup.storage]); debugPrint('$runtimeType setup start, elapsed=${stopwatch.elapsed}');
// TODO reduce permission check time
final permissions = await PermissionHandler().requestPermissions([PermissionGroup.storage]); // 350ms
if (permissions[PermissionGroup.storage] != PermissionStatus.granted) { if (permissions[PermissionGroup.storage] != PermissionStatus.granted) {
SystemNavigator.pop(); unawaited(SystemNavigator.pop());
return; return;
} }
// debugPrint('$runtimeType setup permission check done, elapsed=${stopwatch.elapsed}');
await androidFileUtils.init(); androidFileUtils.init();
await IconUtils.init(); // debugPrint('$runtimeType setup androidFileUtils.init done, elapsed=${stopwatch.elapsed}');
await settings.init(); // TODO notify when icons are ready for drawer and section header refresh
unawaited(IconUtils.init()); // 170ms
// debugPrint('$runtimeType setup IconUtils.init done, elapsed=${stopwatch.elapsed}');
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}');
await metadataDb.init(); await metadataDb.init(); // <20ms
final currentTimeZone = await FlutterNativeTimezone.getLocalTimezone(); final currentTimeZone = await FlutterNativeTimezone.getLocalTimezone(); // <20ms
final catalogTimeZone = settings.catalogTimeZone; final catalogTimeZone = settings.catalogTimeZone;
if (currentTimeZone != catalogTimeZone) { if (currentTimeZone != catalogTimeZone) {
// clear catalog metadata to get correct date/times when moving to a different time zone // clear catalog metadata to get correct date/times when moving to a different time zone
debugPrint('$runtimeType clear catalog metadata to get correct date/times');
await metadataDb.clearMetadataEntries(); await metadataDb.clearMetadataEntries();
settings.catalogTimeZone = currentTimeZone; settings.catalogTimeZone = currentTimeZone;
} }
// debugPrint('$runtimeType setup metadataDb.init done, elapsed=${stopwatch.elapsed}');
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('mediastore stream done'); debugPrint('$runtimeType mediastore stream done, elapsed=${stopwatch.elapsed}');
localMediaCollection.updateSections(); localMediaCollection.updateSections(); // <50ms
localMediaCollection.updateAlbums(); // TODO reduce setup time until here
await localMediaCollection.loadCatalogMetadata(); localMediaCollection.updateAlbums(); // <50ms
await localMediaCollection.catalogEntries(); await localMediaCollection.loadCatalogMetadata(); // 650ms
await localMediaCollection.loadAddresses(); await localMediaCollection.catalogEntries(); // <50ms
await localMediaCollection.locateEntries(); await localMediaCollection.loadAddresses(); // 350ms
await localMediaCollection.locateEntries(); // <50ms
debugPrint('$runtimeType setup end, elapsed=${stopwatch.elapsed}');
}, },
onError: (error) => debugPrint('mediastore stream error=$error'), onError: (error) => debugPrint('$runtimeType mediastore stream error=$error'),
); );
await ImageFileService.getImageEntries(); // debugPrint('$runtimeType setup fetch images, elapsed=${stopwatch.elapsed}');
// TODO split image fetch AND/OR cache fetch across sessions
await ImageFileService.getImageEntries(); // 460ms
} }
@override @override

View file

@ -32,17 +32,17 @@ class ImageCollection with ChangeNotifier {
List<ImageEntry> get sortedEntries => List.unmodifiable(sections.entries.expand((e) => e.value)); List<ImageEntry> get sortedEntries => List.unmodifiable(sections.entries.expand((e) => e.value));
sort(SortFactor sortFactor) { void sort(SortFactor sortFactor) {
this.sortFactor = sortFactor; this.sortFactor = sortFactor;
updateSections(); updateSections();
} }
group(GroupFactor groupFactor) { void group(GroupFactor groupFactor) {
this.groupFactor = groupFactor; this.groupFactor = groupFactor;
updateSections(); updateSections();
} }
updateSections() { void updateSections() {
_applySort(); _applySort();
switch (sortFactor) { switch (sortFactor) {
case SortFactor.date: case SortFactor.date:
@ -65,7 +65,7 @@ class ImageCollection with ChangeNotifier {
notifyListeners(); notifyListeners();
} }
_applySort() { void _applySort() {
switch (sortFactor) { switch (sortFactor) {
case SortFactor.date: case SortFactor.date:
_rawEntries.sort((a, b) => b.bestDate.compareTo(a.bestDate)); _rawEntries.sort((a, b) => b.bestDate.compareTo(a.bestDate));
@ -76,7 +76,7 @@ class ImageCollection with ChangeNotifier {
} }
} }
add(ImageEntry entry) => _rawEntries.add(entry); void add(ImageEntry entry) => _rawEntries.add(entry);
Future<bool> delete(ImageEntry entry) async { Future<bool> delete(ImageEntry entry) async {
final success = await ImageFileService.delete(entry); final success = await ImageFileService.delete(entry);
@ -87,7 +87,7 @@ class ImageCollection with ChangeNotifier {
return success; return success;
} }
updateAlbums() { void updateAlbums() {
Set<String> albums = _rawEntries.map((entry) => entry.directory).toSet(); Set<String> albums = _rawEntries.map((entry) => entry.directory).toSet();
List<String> sorted = albums.toList() List<String> sorted = albums.toList()
..sort((a, b) { ..sort((a, b) {
@ -98,19 +98,19 @@ class ImageCollection with ChangeNotifier {
sortedAlbums = List.unmodifiable(sorted); sortedAlbums = List.unmodifiable(sorted);
} }
updateTags() { void updateTags() {
Set<String> tags = _rawEntries.expand((entry) => entry.xmpSubjects).toSet(); Set<String> tags = _rawEntries.expand((entry) => entry.xmpSubjects).toSet();
List<String> sorted = tags.toList()..sort(compareAsciiUpperCase); List<String> sorted = tags.toList()..sort(compareAsciiUpperCase);
sortedTags = List.unmodifiable(sorted); sortedTags = List.unmodifiable(sorted);
} }
onMetadataChanged() { void onMetadataChanged() {
// metadata dates impact sorting and grouping // metadata dates impact sorting and grouping
updateSections(); updateSections();
updateTags(); updateTags();
} }
loadCatalogMetadata() async { Future<void> loadCatalogMetadata() async {
final start = DateTime.now(); final start = DateTime.now();
final saved = await metadataDb.loadMetadataEntries(); final saved = await metadataDb.loadMetadataEntries();
_rawEntries.forEach((entry) { _rawEntries.forEach((entry) {
@ -123,7 +123,7 @@ class ImageCollection with ChangeNotifier {
debugPrint('$runtimeType loadCatalogMetadata complete in ${DateTime.now().difference(start).inSeconds}s with ${saved.length} saved entries'); debugPrint('$runtimeType loadCatalogMetadata complete in ${DateTime.now().difference(start).inSeconds}s with ${saved.length} saved entries');
} }
loadAddresses() async { Future<void> loadAddresses() async {
final start = DateTime.now(); final start = DateTime.now();
final saved = await metadataDb.loadAddresses(); final saved = await metadataDb.loadAddresses();
_rawEntries.forEach((entry) { _rawEntries.forEach((entry) {
@ -135,7 +135,7 @@ class ImageCollection with ChangeNotifier {
debugPrint('$runtimeType loadAddresses complete in ${DateTime.now().difference(start).inSeconds}s with ${saved.length} saved entries'); debugPrint('$runtimeType loadAddresses complete in ${DateTime.now().difference(start).inSeconds}s with ${saved.length} saved entries');
} }
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 = List<CatalogMetadata>();
@ -143,12 +143,12 @@ class ImageCollection with ChangeNotifier {
await entry.catalog(); await entry.catalog();
newMetadata.add(entry.catalogMetadata); newMetadata.add(entry.catalogMetadata);
}); });
metadataDb.saveMetadata(List.unmodifiable(newMetadata)); await metadataDb.saveMetadata(List.unmodifiable(newMetadata));
onMetadataChanged(); onMetadataChanged();
debugPrint('$runtimeType catalogEntries complete in ${DateTime.now().difference(start).inSeconds}s with ${newMetadata.length} new entries'); debugPrint('$runtimeType catalogEntries complete in ${DateTime.now().difference(start).inSeconds}s with ${newMetadata.length} new entries');
} }
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 = List<AddressDetails>();
@ -156,11 +156,11 @@ class ImageCollection with ChangeNotifier {
await entry.locate(); await entry.locate();
newAddresses.add(entry.addressDetails); newAddresses.add(entry.addressDetails);
if (newAddresses.length >= 50) { if (newAddresses.length >= 50) {
metadataDb.saveAddresses(List.unmodifiable(newAddresses)); await metadataDb.saveAddresses(List.unmodifiable(newAddresses));
newAddresses.clear(); newAddresses.clear();
} }
}); });
metadataDb.saveAddresses(List.unmodifiable(newAddresses)); await metadataDb.saveAddresses(List.unmodifiable(newAddresses));
debugPrint('$runtimeType locateEntries complete in ${DateTime.now().difference(start).inSeconds}s'); debugPrint('$runtimeType locateEntries complete in ${DateTime.now().difference(start).inSeconds}s');
} }

View file

@ -30,7 +30,7 @@ class ImageEntry {
CatalogMetadata catalogMetadata; CatalogMetadata catalogMetadata;
AddressDetails addressDetails; AddressDetails addressDetails;
AChangeNotifier imageChangeNotifier = new AChangeNotifier(), metadataChangeNotifier = new AChangeNotifier(), addressChangeNotifier = new AChangeNotifier(); final AChangeNotifier imageChangeNotifier = AChangeNotifier(), metadataChangeNotifier = AChangeNotifier(), addressChangeNotifier = AChangeNotifier();
ImageEntry({ ImageEntry({
this.uri, this.uri,
@ -84,7 +84,7 @@ class ImageEntry {
}; };
} }
dispose() { void dispose() {
imageChangeNotifier.dispose(); imageChangeNotifier.dispose();
metadataChangeNotifier.dispose(); metadataChangeNotifier.dispose();
addressChangeNotifier.dispose(); addressChangeNotifier.dispose();
@ -142,13 +142,13 @@ class ImageEntry {
List<String> get xmpSubjects => catalogMetadata?.xmpSubjects?.split(';')?.where((tag) => tag.isNotEmpty)?.toList() ?? []; List<String> get xmpSubjects => catalogMetadata?.xmpSubjects?.split(';')?.where((tag) => tag.isNotEmpty)?.toList() ?? [];
catalog() async { Future<void> catalog() async {
if (isCatalogued) return; if (isCatalogued) return;
catalogMetadata = await MetadataService.getCatalogMetadata(this); catalogMetadata = await MetadataService.getCatalogMetadata(this);
metadataChangeNotifier.notifyListeners(); metadataChangeNotifier.notifyListeners();
} }
locate() async { Future<void> locate() async {
if (isLocated) return; if (isLocated) return;
await catalog(); await catalog();
@ -159,7 +159,7 @@ class ImageEntry {
final coordinates = Coordinates(latitude, longitude); final coordinates = Coordinates(latitude, longitude);
try { try {
final addresses = await Geocoder.local.findAddressesFromCoordinates(coordinates); final addresses = await Geocoder.local.findAddressesFromCoordinates(coordinates);
if (addresses != null && addresses.length > 0) { if (addresses != null && addresses.isNotEmpty) {
final address = addresses.first; final address = addresses.first;
addressDetails = AddressDetails( addressDetails = AddressDetails(
contentId: contentId, contentId: contentId,

View file

@ -5,9 +5,9 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
class ImageFileService { class ImageFileService {
static const platform = const MethodChannel('deckers.thibault/aves/image'); static const platform = MethodChannel('deckers.thibault/aves/image');
static getImageEntries() async { static Future<void> getImageEntries() async {
try { try {
await platform.invokeMethod('getImageEntries'); await platform.invokeMethod('getImageEntries');
} on PlatformException catch (e) { } on PlatformException catch (e) {
@ -32,7 +32,7 @@ class ImageFileService {
return Uint8List(0); return Uint8List(0);
} }
static cancelGetImageBytes(String uri) async { static Future<void> cancelGetImageBytes(String uri) async {
try { try {
await platform.invokeMethod('cancelGetImageBytes', <String, dynamic>{ await platform.invokeMethod('cancelGetImageBytes', <String, dynamic>{
'uri': uri, 'uri': uri,

View file

@ -15,7 +15,7 @@ class MetadataDb {
MetadataDb._private(); MetadataDb._private();
init() async { Future<void> init() async {
debugPrint('$runtimeType init'); debugPrint('$runtimeType init');
_database = openDatabase( _database = openDatabase(
await path, await path,
@ -27,14 +27,14 @@ class MetadataDb {
); );
} }
reset() async { Future<void> reset() async {
debugPrint('$runtimeType reset'); debugPrint('$runtimeType reset');
(await _database).close(); await (await _database).close();
deleteDatabase(await path); await deleteDatabase(await path);
await init(); await init();
} }
clearMetadataEntries() async { Future<void> clearMetadataEntries() async {
final db = await _database; final db = await _database;
final count = await db.delete(metadataTable, where: '1'); final count = await db.delete(metadataTable, where: '1');
debugPrint('$runtimeType clearMetadataEntries deleted $count entries'); debugPrint('$runtimeType clearMetadataEntries deleted $count entries');
@ -49,7 +49,7 @@ class MetadataDb {
return metadataEntries; return metadataEntries;
} }
saveMetadata(Iterable<CatalogMetadata> metadataEntries) async { Future<void> saveMetadata(Iterable<CatalogMetadata> metadataEntries) async {
if (metadataEntries == null || metadataEntries.isEmpty) return; if (metadataEntries == null || metadataEntries.isEmpty) return;
final start = DateTime.now(); final start = DateTime.now();
final db = await _database; final db = await _database;
@ -63,7 +63,7 @@ class MetadataDb {
debugPrint('$runtimeType saveMetadata complete in ${DateTime.now().difference(start).inMilliseconds}ms with ${metadataEntries.length} entries'); debugPrint('$runtimeType saveMetadata complete in ${DateTime.now().difference(start).inMilliseconds}ms with ${metadataEntries.length} entries');
} }
clearAddresses() async { Future<void> clearAddresses() async {
final db = await _database; final db = await _database;
final count = await db.delete(addressTable, where: '1'); final count = await db.delete(addressTable, where: '1');
debugPrint('$runtimeType clearAddresses deleted $count entries'); debugPrint('$runtimeType clearAddresses deleted $count entries');
@ -78,7 +78,7 @@ class MetadataDb {
return addresses; return addresses;
} }
saveAddresses(Iterable<AddressDetails> addresses) async { Future<void> saveAddresses(Iterable<AddressDetails> addresses) async {
if (addresses == null || addresses.isEmpty) return; if (addresses == null || addresses.isEmpty) return;
final start = DateTime.now(); final start = DateTime.now();
final db = await _database; final db = await _database;

View file

@ -4,7 +4,7 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
class MetadataService { class MetadataService {
static const platform = const MethodChannel('deckers.thibault/aves/metadata'); static const platform = MethodChannel('deckers.thibault/aves/metadata');
// return Map<Map<Key, Value>> (map of directories, each directory being a map of metadata label and value description) // return Map<Map<Key, Value>> (map of directories, each directory being a map of metadata label and value description)
static Future<Map> getAllMetadata(ImageEntry entry) async { static Future<Map> getAllMetadata(ImageEntry entry) async {

View file

@ -25,7 +25,7 @@ class Settings {
// state // state
static const windowMetricsKey = 'window_metrics'; static const windowMetricsKey = 'window_metrics';
init() async { Future<void> init() async {
prefs = await SharedPreferences.getInstance(); prefs = await SharedPreferences.getInstance();
// TODO TLAD try this as an alternative to MediaQuery, in order to rebuild only on specific property change // TODO TLAD try this as an alternative to MediaQuery, in order to rebuild only on specific property change
// window.onMetricsChanged = onMetricsChanged; // window.onMetricsChanged = onMetricsChanged;
@ -33,7 +33,7 @@ class Settings {
WindowMetrics _metrics; WindowMetrics _metrics;
onMetricsChanged() { void onMetricsChanged() {
final newValue = WindowMetrics( final newValue = WindowMetrics(
devicePixelRatio: window.devicePixelRatio, devicePixelRatio: window.devicePixelRatio,
physicalSize: window.physicalSize, physicalSize: window.physicalSize,
@ -46,11 +46,11 @@ class Settings {
_metrics = newValue; _metrics = newValue;
} }
addListener(SettingsCallback listener) => _listeners.add(listener); void addListener(SettingsCallback listener) => _listeners.add(listener);
removeListener(SettingsCallback listener) => _listeners.remove(listener); void removeListener(SettingsCallback listener) => _listeners.remove(listener);
notifyListeners(String key, dynamic oldValue, dynamic newValue) { void notifyListeners(String key, dynamic oldValue, dynamic newValue) {
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();
@ -96,7 +96,7 @@ class Settings {
return defaultValue; return defaultValue;
} }
setAndNotify(String key, dynamic newValue) { void setAndNotify(String key, dynamic newValue) {
var oldValue = prefs.get(key); var oldValue = prefs.get(key);
if (newValue == null) { if (newValue == null) {
prefs.remove(key); prefs.remove(key);

View file

@ -4,7 +4,7 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
class AndroidAppService { class AndroidAppService {
static const platform = const MethodChannel('deckers.thibault/aves/app'); static const platform = MethodChannel('deckers.thibault/aves/app');
static Future<Map> getAppNames() async { static Future<Map> getAppNames() async {
try { try {
@ -29,7 +29,7 @@ class AndroidAppService {
return Uint8List(0); return Uint8List(0);
} }
static edit(String uri, String mimeType) async { static Future<void> edit(String uri, String mimeType) async {
try { try {
await platform.invokeMethod('edit', <String, dynamic>{ await platform.invokeMethod('edit', <String, dynamic>{
'title': 'Edit with:', 'title': 'Edit with:',
@ -41,7 +41,7 @@ class AndroidAppService {
} }
} }
static open(String uri, String mimeType) async { static Future<void> open(String uri, String mimeType) async {
try { try {
await platform.invokeMethod('open', <String, dynamic>{ await platform.invokeMethod('open', <String, dynamic>{
'title': 'Open with:', 'title': 'Open with:',
@ -53,7 +53,7 @@ class AndroidAppService {
} }
} }
static openMap(String geoUri) async { static Future<void> openMap(String geoUri) async {
if (geoUri == null) return; if (geoUri == null) return;
try { try {
await platform.invokeMethod('openMap', <String, dynamic>{ await platform.invokeMethod('openMap', <String, dynamic>{
@ -64,7 +64,7 @@ class AndroidAppService {
} }
} }
static setAs(String uri, String mimeType) async { static Future<void> setAs(String uri, String mimeType) async {
try { try {
await platform.invokeMethod('setAs', <String, dynamic>{ await platform.invokeMethod('setAs', <String, dynamic>{
'title': 'Set as:', 'title': 'Set as:',
@ -76,7 +76,7 @@ class AndroidAppService {
} }
} }
static share(String uri, String mimeType) async { static Future<void> share(String uri, String mimeType) async {
try { try {
await platform.invokeMethod('share', <String, dynamic>{ await platform.invokeMethod('share', <String, dynamic>{
'title': 'Share via:', 'title': 'Share via:',

View file

@ -7,7 +7,7 @@ class AndroidFileUtils {
AndroidFileUtils._private(); AndroidFileUtils._private();
init() async { void init() {
// path_provider getExternalStorageDirectory() gives '/storage/emulated/0/Android/data/deckers.thibault.aves/files' // path_provider getExternalStorageDirectory() gives '/storage/emulated/0/Android/data/deckers.thibault.aves/files'
externalStorage = '/storage/emulated/0'; externalStorage = '/storage/emulated/0';
dcimPath = join(externalStorage, 'DCIM'); dcimPath = join(externalStorage, 'DCIM');

View file

@ -5,12 +5,12 @@ import 'package:tuple/tuple.dart';
// adapted from Mike Mitterer's dart-latlong library // adapted from Mike Mitterer's dart-latlong library
String _decimal2sexagesimal(final double dec) { String _decimal2sexagesimal(final double dec) {
double _round(final double value, {final int decimals: 6}) => (value * math.pow(10, decimals)).round() / math.pow(10, decimals); double _round(final double value, {final int decimals = 6}) => (value * math.pow(10, decimals)).round() / math.pow(10, decimals);
List<int> _split(final double value) { List<int> _split(final double value) {
// NumberFormat is necessary to create digit after comma if the value // NumberFormat is necessary to create digit after comma if the value
// has no decimal point (only necessary for browser) // has no decimal point (only necessary for browser)
final List<String> tmp = new NumberFormat('0.0#####').format(_round(value, decimals: 10)).split('.'); final List<String> tmp = NumberFormat('0.0#####').format(_round(value, decimals: 10)).split('.');
return <int>[int.parse(tmp[0]).abs(), int.parse(tmp[1])]; return <int>[int.parse(tmp[0]).abs(), int.parse(tmp[1])];
} }

View file

@ -36,7 +36,7 @@ class AppIconState extends State<AppIcon> {
future: _byteLoader, future: _byteLoader,
builder: (futureContext, AsyncSnapshot<Uint8List> snapshot) { builder: (futureContext, AsyncSnapshot<Uint8List> snapshot) {
final bytes = (snapshot.connectionState == ConnectionState.done && !snapshot.hasError) ? snapshot.data : kTransparentImage; final bytes = (snapshot.connectionState == ConnectionState.done && !snapshot.hasError) ? snapshot.data : kTransparentImage;
return bytes.length > 0 return bytes.isNotEmpty
? Image.memory( ? Image.memory(
bytes, bytes,
width: widget.size, width: widget.size,

View file

@ -88,9 +88,9 @@ class OverlayIcon extends StatelessWidget {
} }
class IconUtils { class IconUtils {
static Map appNameMap; static Map appNameMap = {};
static init() async { static Future<void> init() async {
appNameMap = await AndroidAppService.getAppNames(); appNameMap = await AndroidAppService.getAppNames();
} }

View file

@ -66,7 +66,7 @@ class ImagePreviewState extends State<ImagePreview> {
future: _byteLoader, future: _byteLoader,
builder: (futureContext, AsyncSnapshot<Uint8List> snapshot) { builder: (futureContext, AsyncSnapshot<Uint8List> snapshot) {
final bytes = (snapshot.connectionState == ConnectionState.done && !snapshot.hasError) ? snapshot.data : kTransparentImage; final bytes = (snapshot.connectionState == ConnectionState.done && !snapshot.hasError) ? snapshot.data : kTransparentImage;
return bytes.length > 0 ? widget.builder(bytes) : Icon(Icons.error); return bytes.isNotEmpty ? widget.builder(bytes) : Icon(Icons.error);
}); });
} }
} }

View file

@ -6,6 +6,7 @@ import 'package:aves/utils/android_app_service.dart';
import 'package:flushbar/flushbar.dart'; import 'package:flushbar/flushbar.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:pdf/widgets.dart' as pdf; import 'package:pdf/widgets.dart' as pdf;
import 'package:pedantic/pedantic.dart';
import 'package:printing/printing.dart'; import 'package:printing/printing.dart';
enum FullscreenAction { delete, edit, info, open, openMap, print, rename, rotateCCW, rotateCW, setAs, share } enum FullscreenAction { delete, edit, info, open, openMap, print, rename, rotateCCW, rotateCW, setAs, share }
@ -19,7 +20,7 @@ class FullscreenActionDelegate {
@required this.showInfo, @required this.showInfo,
}); });
onActionSelected(BuildContext context, ImageEntry entry, FullscreenAction action) { void onActionSelected(BuildContext context, ImageEntry entry, FullscreenAction action) {
switch (action) { switch (action) {
case FullscreenAction.delete: case FullscreenAction.delete:
_showDeleteDialog(context, entry); _showDeleteDialog(context, entry);
@ -57,7 +58,7 @@ class FullscreenActionDelegate {
} }
} }
_showFeedback(BuildContext context, String message) { void _showFeedback(BuildContext context, String message) {
Flushbar( Flushbar(
message: message, message: message,
margin: EdgeInsets.all(8), margin: EdgeInsets.all(8),
@ -70,25 +71,25 @@ class FullscreenActionDelegate {
)..show(context); )..show(context);
} }
_print(ImageEntry entry) async { Future<void> _print(ImageEntry entry) async {
final doc = pdf.Document(title: entry.title); final doc = pdf.Document(title: entry.title);
final image = await pdfImageFromImageProvider( final image = await pdfImageFromImageProvider(
pdf: doc.document, pdf: doc.document,
image: FileImage(File(entry.path)), image: FileImage(File(entry.path)),
); );
doc.addPage(pdf.Page(build: (context) => pdf.Center(child: pdf.Image(image)))); // Page doc.addPage(pdf.Page(build: (context) => pdf.Center(child: pdf.Image(image)))); // Page
Printing.layoutPdf( unawaited(Printing.layoutPdf(
onLayout: (format) => doc.save(), onLayout: (format) => doc.save(),
name: entry.title, name: entry.title,
); ));
} }
_rotate(BuildContext context, ImageEntry entry, {@required bool clockwise}) async { Future<void> _rotate(BuildContext context, ImageEntry entry, {@required bool clockwise}) async {
final success = await entry.rotate(clockwise: clockwise); final success = await entry.rotate(clockwise: clockwise);
_showFeedback(context, success ? 'Done!' : 'Failed'); _showFeedback(context, success ? 'Done!' : 'Failed');
} }
_showDeleteDialog(BuildContext context, ImageEntry entry) async { Future<void> _showDeleteDialog(BuildContext context, ImageEntry entry) async {
final confirmed = await showDialog<bool>( final confirmed = await showDialog<bool>(
context: context, context: context,
builder: (BuildContext context) { builder: (BuildContext context) {
@ -115,7 +116,7 @@ class FullscreenActionDelegate {
} }
} }
_showRenameDialog(BuildContext context, ImageEntry entry) async { Future<void> _showRenameDialog(BuildContext context, ImageEntry entry) async {
final currentName = entry.title; final currentName = entry.title;
final controller = TextEditingController(text: currentName); final controller = TextEditingController(text: currentName);
final newName = await showDialog<String>( final newName = await showDialog<String>(

View file

@ -65,11 +65,11 @@ class VideoControlOverlayState extends State<VideoControlOverlay> with SingleTic
super.dispose(); super.dispose();
} }
registerWidget(VideoControlOverlay widget) { void registerWidget(VideoControlOverlay widget) {
widget.controller.addListener(_onValueChange); widget.controller.addListener(_onValueChange);
} }
unregisterWidget(VideoControlOverlay widget) { void unregisterWidget(VideoControlOverlay widget) {
widget.controller.removeListener(_onValueChange); widget.controller.removeListener(_onValueChange);
} }
@ -108,8 +108,8 @@ class VideoControlOverlayState extends State<VideoControlOverlay> with SingleTic
icon: AnimatedIcons.play_pause, icon: AnimatedIcons.play_pause,
progress: _playPauseAnimation, progress: _playPauseAnimation,
), ),
onPressed: () => _playPause(), onPressed: _playPause,
tooltip: 'Play', tooltip: value.isPlaying ? 'Pause' : 'Play',
), ),
), ),
], ],
@ -118,7 +118,7 @@ class VideoControlOverlayState extends State<VideoControlOverlay> with SingleTic
); );
} }
SizeTransition _buildProgressBar() { Widget _buildProgressBar() {
final progressBarBorderRadius = 123.0; final progressBarBorderRadius = 123.0;
return SizeTransition( return SizeTransition(
sizeFactor: scale, sizeFactor: scale,
@ -166,22 +166,22 @@ class VideoControlOverlayState extends State<VideoControlOverlay> with SingleTic
); );
} }
_onValueChange() { void _onValueChange() {
setState(() {}); setState(() {});
updatePlayPauseIcon(); updatePlayPauseIcon();
} }
_playPause() async { Future<void> _playPause() async {
if (value.isPlaying) { if (value.isPlaying) {
controller.pause(); await controller.pause();
} else { } else {
if (!value.initialized) await controller.initialize(); if (!value.initialized) await controller.initialize();
controller.play(); await controller.play();
} }
setState(() {}); setState(() {});
} }
updatePlayPauseIcon() { void updatePlayPauseIcon() {
final isPlaying = value.isPlaying; final isPlaying = value.isPlaying;
final status = _playPauseAnimation.status; final status = _playPauseAnimation.status;
if (isPlaying && status != AnimationStatus.forward && status != AnimationStatus.completed) { if (isPlaying && status != AnimationStatus.forward && status != AnimationStatus.completed) {
@ -191,7 +191,7 @@ class VideoControlOverlayState extends State<VideoControlOverlay> with SingleTic
} }
} }
_seek(Offset globalPosition) { void _seek(Offset globalPosition) {
final keyContext = _progressBarKey.currentContext; final keyContext = _progressBarKey.currentContext;
final RenderBox box = keyContext.findRenderObject(); final RenderBox box = keyContext.findRenderObject();
final localPosition = box.globalToLocal(globalPosition); final localPosition = box.globalToLocal(globalPosition);

View file

@ -139,6 +139,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.16.0" version: "0.16.0"
logger:
dependency: "direct main"
description:
name: logger
url: "https://pub.dartlang.org"
source: hosted
version: "0.8.0"
matcher: matcher:
dependency: transitive dependency: transitive
description: description:
@ -182,7 +189,7 @@ packages:
source: hosted source: hosted
version: "1.3.25" version: "1.3.25"
pedantic: pedantic:
dependency: transitive dependency: "direct main"
description: description:
name: pedantic name: pedantic
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"

View file

@ -29,8 +29,10 @@ dependencies:
geocoder: geocoder:
google_maps_flutter: google_maps_flutter:
intl: intl:
logger:
path: path:
pdf: pdf:
pedantic:
permission_handler: permission_handler:
photo_view: photo_view:
printing: printing: