fixed logo, applied pedantic, added startup timing TODOs
This commit is contained in:
parent
f49b9b2e24
commit
cee585d03c
19 changed files with 125 additions and 177 deletions
5
analysis_options.yaml
Normal file
5
analysis_options.yaml
Normal file
|
@ -0,0 +1,5 @@
|
|||
include: package:pedantic/analysis_options.yaml
|
||||
|
||||
analyzer:
|
||||
exclude:
|
||||
- lib/generated_plugin_registrant.dart
|
|
@ -1,87 +1 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<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>
|
||||
<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>
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 338 B |
|
@ -11,14 +11,20 @@ import 'package:aves/widgets/common/icons.dart';
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_native_timezone/flutter_native_timezone.dart';
|
||||
import 'package:pedantic/pedantic.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
import 'package:screen/screen.dart';
|
||||
|
||||
final stopwatch = Stopwatch()..start();
|
||||
|
||||
void main() {
|
||||
debugPrint('main start, elapsed=${stopwatch.elapsed}');
|
||||
// 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
|
||||
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());
|
||||
}
|
||||
|
||||
|
@ -64,42 +70,55 @@ class _HomePageState extends State<HomePage> {
|
|||
Screen.keepOn(true);
|
||||
}
|
||||
|
||||
setup() async {
|
||||
final permissions = await PermissionHandler().requestPermissions([PermissionGroup.storage]);
|
||||
Future<void> setup() async {
|
||||
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) {
|
||||
SystemNavigator.pop();
|
||||
unawaited(SystemNavigator.pop());
|
||||
return;
|
||||
}
|
||||
// debugPrint('$runtimeType setup permission check done, elapsed=${stopwatch.elapsed}');
|
||||
|
||||
await androidFileUtils.init();
|
||||
await IconUtils.init();
|
||||
await settings.init();
|
||||
androidFileUtils.init();
|
||||
// debugPrint('$runtimeType setup androidFileUtils.init done, elapsed=${stopwatch.elapsed}');
|
||||
// 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.sortFactor = settings.collectionSortFactor;
|
||||
debugPrint('$runtimeType setup settings.init done, elapsed=${stopwatch.elapsed}');
|
||||
|
||||
await metadataDb.init();
|
||||
final currentTimeZone = await FlutterNativeTimezone.getLocalTimezone();
|
||||
await metadataDb.init(); // <20ms
|
||||
final currentTimeZone = await FlutterNativeTimezone.getLocalTimezone(); // <20ms
|
||||
final catalogTimeZone = settings.catalogTimeZone;
|
||||
if (currentTimeZone != catalogTimeZone) {
|
||||
// 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();
|
||||
settings.catalogTimeZone = currentTimeZone;
|
||||
}
|
||||
// debugPrint('$runtimeType setup metadataDb.init done, elapsed=${stopwatch.elapsed}');
|
||||
|
||||
eventChannel.receiveBroadcastStream().cast<Map>().listen(
|
||||
(entryMap) => localMediaCollection.add(ImageEntry.fromMap(entryMap)),
|
||||
onDone: () async {
|
||||
debugPrint('mediastore stream done');
|
||||
localMediaCollection.updateSections();
|
||||
localMediaCollection.updateAlbums();
|
||||
await localMediaCollection.loadCatalogMetadata();
|
||||
await localMediaCollection.catalogEntries();
|
||||
await localMediaCollection.loadAddresses();
|
||||
await localMediaCollection.locateEntries();
|
||||
debugPrint('$runtimeType mediastore stream done, elapsed=${stopwatch.elapsed}');
|
||||
localMediaCollection.updateSections(); // <50ms
|
||||
// TODO reduce setup time until here
|
||||
localMediaCollection.updateAlbums(); // <50ms
|
||||
await localMediaCollection.loadCatalogMetadata(); // 650ms
|
||||
await localMediaCollection.catalogEntries(); // <50ms
|
||||
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
|
||||
|
|
|
@ -32,17 +32,17 @@ class ImageCollection with ChangeNotifier {
|
|||
|
||||
List<ImageEntry> get sortedEntries => List.unmodifiable(sections.entries.expand((e) => e.value));
|
||||
|
||||
sort(SortFactor sortFactor) {
|
||||
void sort(SortFactor sortFactor) {
|
||||
this.sortFactor = sortFactor;
|
||||
updateSections();
|
||||
}
|
||||
|
||||
group(GroupFactor groupFactor) {
|
||||
void group(GroupFactor groupFactor) {
|
||||
this.groupFactor = groupFactor;
|
||||
updateSections();
|
||||
}
|
||||
|
||||
updateSections() {
|
||||
void updateSections() {
|
||||
_applySort();
|
||||
switch (sortFactor) {
|
||||
case SortFactor.date:
|
||||
|
@ -65,7 +65,7 @@ class ImageCollection with ChangeNotifier {
|
|||
notifyListeners();
|
||||
}
|
||||
|
||||
_applySort() {
|
||||
void _applySort() {
|
||||
switch (sortFactor) {
|
||||
case SortFactor.date:
|
||||
_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 {
|
||||
final success = await ImageFileService.delete(entry);
|
||||
|
@ -87,7 +87,7 @@ class ImageCollection with ChangeNotifier {
|
|||
return success;
|
||||
}
|
||||
|
||||
updateAlbums() {
|
||||
void updateAlbums() {
|
||||
Set<String> albums = _rawEntries.map((entry) => entry.directory).toSet();
|
||||
List<String> sorted = albums.toList()
|
||||
..sort((a, b) {
|
||||
|
@ -98,19 +98,19 @@ class ImageCollection with ChangeNotifier {
|
|||
sortedAlbums = List.unmodifiable(sorted);
|
||||
}
|
||||
|
||||
updateTags() {
|
||||
void updateTags() {
|
||||
Set<String> tags = _rawEntries.expand((entry) => entry.xmpSubjects).toSet();
|
||||
List<String> sorted = tags.toList()..sort(compareAsciiUpperCase);
|
||||
sortedTags = List.unmodifiable(sorted);
|
||||
}
|
||||
|
||||
onMetadataChanged() {
|
||||
void onMetadataChanged() {
|
||||
// metadata dates impact sorting and grouping
|
||||
updateSections();
|
||||
updateTags();
|
||||
}
|
||||
|
||||
loadCatalogMetadata() async {
|
||||
Future<void> loadCatalogMetadata() async {
|
||||
final start = DateTime.now();
|
||||
final saved = await metadataDb.loadMetadataEntries();
|
||||
_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');
|
||||
}
|
||||
|
||||
loadAddresses() async {
|
||||
Future<void> loadAddresses() async {
|
||||
final start = DateTime.now();
|
||||
final saved = await metadataDb.loadAddresses();
|
||||
_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');
|
||||
}
|
||||
|
||||
catalogEntries() async {
|
||||
Future<void> catalogEntries() async {
|
||||
final start = DateTime.now();
|
||||
final uncataloguedEntries = _rawEntries.where((entry) => !entry.isCatalogued).toList();
|
||||
final newMetadata = List<CatalogMetadata>();
|
||||
|
@ -143,12 +143,12 @@ class ImageCollection with ChangeNotifier {
|
|||
await entry.catalog();
|
||||
newMetadata.add(entry.catalogMetadata);
|
||||
});
|
||||
metadataDb.saveMetadata(List.unmodifiable(newMetadata));
|
||||
await metadataDb.saveMetadata(List.unmodifiable(newMetadata));
|
||||
onMetadataChanged();
|
||||
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 unlocatedEntries = _rawEntries.where((entry) => entry.hasGps && !entry.isLocated).toList();
|
||||
final newAddresses = List<AddressDetails>();
|
||||
|
@ -156,11 +156,11 @@ class ImageCollection with ChangeNotifier {
|
|||
await entry.locate();
|
||||
newAddresses.add(entry.addressDetails);
|
||||
if (newAddresses.length >= 50) {
|
||||
metadataDb.saveAddresses(List.unmodifiable(newAddresses));
|
||||
await metadataDb.saveAddresses(List.unmodifiable(newAddresses));
|
||||
newAddresses.clear();
|
||||
}
|
||||
});
|
||||
metadataDb.saveAddresses(List.unmodifiable(newAddresses));
|
||||
await metadataDb.saveAddresses(List.unmodifiable(newAddresses));
|
||||
debugPrint('$runtimeType locateEntries complete in ${DateTime.now().difference(start).inSeconds}s');
|
||||
}
|
||||
|
||||
|
|
|
@ -30,7 +30,7 @@ class ImageEntry {
|
|||
CatalogMetadata catalogMetadata;
|
||||
AddressDetails addressDetails;
|
||||
|
||||
AChangeNotifier imageChangeNotifier = new AChangeNotifier(), metadataChangeNotifier = new AChangeNotifier(), addressChangeNotifier = new AChangeNotifier();
|
||||
final AChangeNotifier imageChangeNotifier = AChangeNotifier(), metadataChangeNotifier = AChangeNotifier(), addressChangeNotifier = AChangeNotifier();
|
||||
|
||||
ImageEntry({
|
||||
this.uri,
|
||||
|
@ -84,7 +84,7 @@ class ImageEntry {
|
|||
};
|
||||
}
|
||||
|
||||
dispose() {
|
||||
void dispose() {
|
||||
imageChangeNotifier.dispose();
|
||||
metadataChangeNotifier.dispose();
|
||||
addressChangeNotifier.dispose();
|
||||
|
@ -142,13 +142,13 @@ class ImageEntry {
|
|||
|
||||
List<String> get xmpSubjects => catalogMetadata?.xmpSubjects?.split(';')?.where((tag) => tag.isNotEmpty)?.toList() ?? [];
|
||||
|
||||
catalog() async {
|
||||
Future<void> catalog() async {
|
||||
if (isCatalogued) return;
|
||||
catalogMetadata = await MetadataService.getCatalogMetadata(this);
|
||||
metadataChangeNotifier.notifyListeners();
|
||||
}
|
||||
|
||||
locate() async {
|
||||
Future<void> locate() async {
|
||||
if (isLocated) return;
|
||||
|
||||
await catalog();
|
||||
|
@ -159,7 +159,7 @@ class ImageEntry {
|
|||
final coordinates = Coordinates(latitude, longitude);
|
||||
try {
|
||||
final addresses = await Geocoder.local.findAddressesFromCoordinates(coordinates);
|
||||
if (addresses != null && addresses.length > 0) {
|
||||
if (addresses != null && addresses.isNotEmpty) {
|
||||
final address = addresses.first;
|
||||
addressDetails = AddressDetails(
|
||||
contentId: contentId,
|
||||
|
|
|
@ -5,9 +5,9 @@ import 'package:flutter/foundation.dart';
|
|||
import 'package:flutter/services.dart';
|
||||
|
||||
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 {
|
||||
await platform.invokeMethod('getImageEntries');
|
||||
} on PlatformException catch (e) {
|
||||
|
@ -32,7 +32,7 @@ class ImageFileService {
|
|||
return Uint8List(0);
|
||||
}
|
||||
|
||||
static cancelGetImageBytes(String uri) async {
|
||||
static Future<void> cancelGetImageBytes(String uri) async {
|
||||
try {
|
||||
await platform.invokeMethod('cancelGetImageBytes', <String, dynamic>{
|
||||
'uri': uri,
|
||||
|
|
|
@ -15,7 +15,7 @@ class MetadataDb {
|
|||
|
||||
MetadataDb._private();
|
||||
|
||||
init() async {
|
||||
Future<void> init() async {
|
||||
debugPrint('$runtimeType init');
|
||||
_database = openDatabase(
|
||||
await path,
|
||||
|
@ -27,14 +27,14 @@ class MetadataDb {
|
|||
);
|
||||
}
|
||||
|
||||
reset() async {
|
||||
Future<void> reset() async {
|
||||
debugPrint('$runtimeType reset');
|
||||
(await _database).close();
|
||||
deleteDatabase(await path);
|
||||
await (await _database).close();
|
||||
await deleteDatabase(await path);
|
||||
await init();
|
||||
}
|
||||
|
||||
clearMetadataEntries() async {
|
||||
Future<void> clearMetadataEntries() async {
|
||||
final db = await _database;
|
||||
final count = await db.delete(metadataTable, where: '1');
|
||||
debugPrint('$runtimeType clearMetadataEntries deleted $count entries');
|
||||
|
@ -49,7 +49,7 @@ class MetadataDb {
|
|||
return metadataEntries;
|
||||
}
|
||||
|
||||
saveMetadata(Iterable<CatalogMetadata> metadataEntries) async {
|
||||
Future<void> saveMetadata(Iterable<CatalogMetadata> metadataEntries) async {
|
||||
if (metadataEntries == null || metadataEntries.isEmpty) return;
|
||||
final start = DateTime.now();
|
||||
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');
|
||||
}
|
||||
|
||||
clearAddresses() async {
|
||||
Future<void> clearAddresses() async {
|
||||
final db = await _database;
|
||||
final count = await db.delete(addressTable, where: '1');
|
||||
debugPrint('$runtimeType clearAddresses deleted $count entries');
|
||||
|
@ -78,7 +78,7 @@ class MetadataDb {
|
|||
return addresses;
|
||||
}
|
||||
|
||||
saveAddresses(Iterable<AddressDetails> addresses) async {
|
||||
Future<void> saveAddresses(Iterable<AddressDetails> addresses) async {
|
||||
if (addresses == null || addresses.isEmpty) return;
|
||||
final start = DateTime.now();
|
||||
final db = await _database;
|
||||
|
|
|
@ -4,7 +4,7 @@ import 'package:flutter/foundation.dart';
|
|||
import 'package:flutter/services.dart';
|
||||
|
||||
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)
|
||||
static Future<Map> getAllMetadata(ImageEntry entry) async {
|
||||
|
|
|
@ -25,7 +25,7 @@ class Settings {
|
|||
// state
|
||||
static const windowMetricsKey = 'window_metrics';
|
||||
|
||||
init() async {
|
||||
Future<void> init() async {
|
||||
prefs = await SharedPreferences.getInstance();
|
||||
// TODO TLAD try this as an alternative to MediaQuery, in order to rebuild only on specific property change
|
||||
// window.onMetricsChanged = onMetricsChanged;
|
||||
|
@ -33,7 +33,7 @@ class Settings {
|
|||
|
||||
WindowMetrics _metrics;
|
||||
|
||||
onMetricsChanged() {
|
||||
void onMetricsChanged() {
|
||||
final newValue = WindowMetrics(
|
||||
devicePixelRatio: window.devicePixelRatio,
|
||||
physicalSize: window.physicalSize,
|
||||
|
@ -46,11 +46,11 @@ class Settings {
|
|||
_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');
|
||||
if (_listeners != null) {
|
||||
final List<SettingsCallback> localListeners = _listeners.toList();
|
||||
|
@ -96,7 +96,7 @@ class Settings {
|
|||
return defaultValue;
|
||||
}
|
||||
|
||||
setAndNotify(String key, dynamic newValue) {
|
||||
void setAndNotify(String key, dynamic newValue) {
|
||||
var oldValue = prefs.get(key);
|
||||
if (newValue == null) {
|
||||
prefs.remove(key);
|
||||
|
|
|
@ -4,7 +4,7 @@ import 'package:flutter/foundation.dart';
|
|||
import 'package:flutter/services.dart';
|
||||
|
||||
class AndroidAppService {
|
||||
static const platform = const MethodChannel('deckers.thibault/aves/app');
|
||||
static const platform = MethodChannel('deckers.thibault/aves/app');
|
||||
|
||||
static Future<Map> getAppNames() async {
|
||||
try {
|
||||
|
@ -29,7 +29,7 @@ class AndroidAppService {
|
|||
return Uint8List(0);
|
||||
}
|
||||
|
||||
static edit(String uri, String mimeType) async {
|
||||
static Future<void> edit(String uri, String mimeType) async {
|
||||
try {
|
||||
await platform.invokeMethod('edit', <String, dynamic>{
|
||||
'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 {
|
||||
await platform.invokeMethod('open', <String, dynamic>{
|
||||
'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;
|
||||
try {
|
||||
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 {
|
||||
await platform.invokeMethod('setAs', <String, dynamic>{
|
||||
'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 {
|
||||
await platform.invokeMethod('share', <String, dynamic>{
|
||||
'title': 'Share via:',
|
||||
|
|
|
@ -7,7 +7,7 @@ class AndroidFileUtils {
|
|||
|
||||
AndroidFileUtils._private();
|
||||
|
||||
init() async {
|
||||
void init() {
|
||||
// path_provider getExternalStorageDirectory() gives '/storage/emulated/0/Android/data/deckers.thibault.aves/files'
|
||||
externalStorage = '/storage/emulated/0';
|
||||
dcimPath = join(externalStorage, 'DCIM');
|
||||
|
|
|
@ -5,12 +5,12 @@ import 'package:tuple/tuple.dart';
|
|||
|
||||
// adapted from Mike Mitterer's dart-latlong library
|
||||
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) {
|
||||
// NumberFormat is necessary to create digit after comma if the value
|
||||
// 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])];
|
||||
}
|
||||
|
||||
|
|
|
@ -36,7 +36,7 @@ class AppIconState extends State<AppIcon> {
|
|||
future: _byteLoader,
|
||||
builder: (futureContext, AsyncSnapshot<Uint8List> snapshot) {
|
||||
final bytes = (snapshot.connectionState == ConnectionState.done && !snapshot.hasError) ? snapshot.data : kTransparentImage;
|
||||
return bytes.length > 0
|
||||
return bytes.isNotEmpty
|
||||
? Image.memory(
|
||||
bytes,
|
||||
width: widget.size,
|
||||
|
|
|
@ -88,9 +88,9 @@ class OverlayIcon extends StatelessWidget {
|
|||
}
|
||||
|
||||
class IconUtils {
|
||||
static Map appNameMap;
|
||||
static Map appNameMap = {};
|
||||
|
||||
static init() async {
|
||||
static Future<void> init() async {
|
||||
appNameMap = await AndroidAppService.getAppNames();
|
||||
}
|
||||
|
||||
|
|
|
@ -66,7 +66,7 @@ class ImagePreviewState extends State<ImagePreview> {
|
|||
future: _byteLoader,
|
||||
builder: (futureContext, AsyncSnapshot<Uint8List> snapshot) {
|
||||
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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import 'package:aves/utils/android_app_service.dart';
|
|||
import 'package:flushbar/flushbar.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:pdf/widgets.dart' as pdf;
|
||||
import 'package:pedantic/pedantic.dart';
|
||||
import 'package:printing/printing.dart';
|
||||
|
||||
enum FullscreenAction { delete, edit, info, open, openMap, print, rename, rotateCCW, rotateCW, setAs, share }
|
||||
|
@ -19,7 +20,7 @@ class FullscreenActionDelegate {
|
|||
@required this.showInfo,
|
||||
});
|
||||
|
||||
onActionSelected(BuildContext context, ImageEntry entry, FullscreenAction action) {
|
||||
void onActionSelected(BuildContext context, ImageEntry entry, FullscreenAction action) {
|
||||
switch (action) {
|
||||
case FullscreenAction.delete:
|
||||
_showDeleteDialog(context, entry);
|
||||
|
@ -57,7 +58,7 @@ class FullscreenActionDelegate {
|
|||
}
|
||||
}
|
||||
|
||||
_showFeedback(BuildContext context, String message) {
|
||||
void _showFeedback(BuildContext context, String message) {
|
||||
Flushbar(
|
||||
message: message,
|
||||
margin: EdgeInsets.all(8),
|
||||
|
@ -70,25 +71,25 @@ class FullscreenActionDelegate {
|
|||
)..show(context);
|
||||
}
|
||||
|
||||
_print(ImageEntry entry) async {
|
||||
Future<void> _print(ImageEntry entry) async {
|
||||
final doc = pdf.Document(title: entry.title);
|
||||
final image = await pdfImageFromImageProvider(
|
||||
pdf: doc.document,
|
||||
image: FileImage(File(entry.path)),
|
||||
);
|
||||
doc.addPage(pdf.Page(build: (context) => pdf.Center(child: pdf.Image(image)))); // Page
|
||||
Printing.layoutPdf(
|
||||
unawaited(Printing.layoutPdf(
|
||||
onLayout: (format) => doc.save(),
|
||||
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);
|
||||
_showFeedback(context, success ? 'Done!' : 'Failed');
|
||||
}
|
||||
|
||||
_showDeleteDialog(BuildContext context, ImageEntry entry) async {
|
||||
Future<void> _showDeleteDialog(BuildContext context, ImageEntry entry) async {
|
||||
final confirmed = await showDialog<bool>(
|
||||
context: 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 controller = TextEditingController(text: currentName);
|
||||
final newName = await showDialog<String>(
|
||||
|
|
|
@ -65,11 +65,11 @@ class VideoControlOverlayState extends State<VideoControlOverlay> with SingleTic
|
|||
super.dispose();
|
||||
}
|
||||
|
||||
registerWidget(VideoControlOverlay widget) {
|
||||
void registerWidget(VideoControlOverlay widget) {
|
||||
widget.controller.addListener(_onValueChange);
|
||||
}
|
||||
|
||||
unregisterWidget(VideoControlOverlay widget) {
|
||||
void unregisterWidget(VideoControlOverlay widget) {
|
||||
widget.controller.removeListener(_onValueChange);
|
||||
}
|
||||
|
||||
|
@ -108,8 +108,8 @@ class VideoControlOverlayState extends State<VideoControlOverlay> with SingleTic
|
|||
icon: AnimatedIcons.play_pause,
|
||||
progress: _playPauseAnimation,
|
||||
),
|
||||
onPressed: () => _playPause(),
|
||||
tooltip: 'Play',
|
||||
onPressed: _playPause,
|
||||
tooltip: value.isPlaying ? 'Pause' : 'Play',
|
||||
),
|
||||
),
|
||||
],
|
||||
|
@ -118,7 +118,7 @@ class VideoControlOverlayState extends State<VideoControlOverlay> with SingleTic
|
|||
);
|
||||
}
|
||||
|
||||
SizeTransition _buildProgressBar() {
|
||||
Widget _buildProgressBar() {
|
||||
final progressBarBorderRadius = 123.0;
|
||||
return SizeTransition(
|
||||
sizeFactor: scale,
|
||||
|
@ -166,22 +166,22 @@ class VideoControlOverlayState extends State<VideoControlOverlay> with SingleTic
|
|||
);
|
||||
}
|
||||
|
||||
_onValueChange() {
|
||||
void _onValueChange() {
|
||||
setState(() {});
|
||||
updatePlayPauseIcon();
|
||||
}
|
||||
|
||||
_playPause() async {
|
||||
Future<void> _playPause() async {
|
||||
if (value.isPlaying) {
|
||||
controller.pause();
|
||||
await controller.pause();
|
||||
} else {
|
||||
if (!value.initialized) await controller.initialize();
|
||||
controller.play();
|
||||
await controller.play();
|
||||
}
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
updatePlayPauseIcon() {
|
||||
void updatePlayPauseIcon() {
|
||||
final isPlaying = value.isPlaying;
|
||||
final status = _playPauseAnimation.status;
|
||||
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 RenderBox box = keyContext.findRenderObject();
|
||||
final localPosition = box.globalToLocal(globalPosition);
|
||||
|
|
|
@ -139,6 +139,13 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.16.0"
|
||||
logger:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: logger
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.8.0"
|
||||
matcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -182,7 +189,7 @@ packages:
|
|||
source: hosted
|
||||
version: "1.3.25"
|
||||
pedantic:
|
||||
dependency: transitive
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: pedantic
|
||||
url: "https://pub.dartlang.org"
|
||||
|
|
|
@ -29,8 +29,10 @@ dependencies:
|
|||
geocoder:
|
||||
google_maps_flutter:
|
||||
intl:
|
||||
logger:
|
||||
path:
|
||||
pdf:
|
||||
pedantic:
|
||||
permission_handler:
|
||||
photo_view:
|
||||
printing:
|
||||
|
|
Loading…
Reference in a new issue