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="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 |
|
@ -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
|
||||||
|
|
|
@ -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');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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:',
|
||||||
|
|
|
@ -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');
|
||||||
|
|
|
@ -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])];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>(
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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:
|
||||||
|
|
Loading…
Reference in a new issue