misc fixes
This commit is contained in:
parent
bc38edfea1
commit
48a62e85c5
11 changed files with 172 additions and 173 deletions
|
@ -1,4 +1,3 @@
|
||||||
import 'package:aves/model/collection_lens.dart';
|
|
||||||
import 'package:aves/model/image_entry.dart';
|
import 'package:aves/model/image_entry.dart';
|
||||||
import 'package:aves/model/image_file_service.dart';
|
import 'package:aves/model/image_file_service.dart';
|
||||||
import 'package:aves/model/settings.dart';
|
import 'package:aves/model/settings.dart';
|
||||||
|
@ -12,7 +11,6 @@ import 'package:flutter/services.dart';
|
||||||
import 'package:outline_material_icons/outline_material_icons.dart';
|
import 'package:outline_material_icons/outline_material_icons.dart';
|
||||||
import 'package:pedantic/pedantic.dart';
|
import 'package:pedantic/pedantic.dart';
|
||||||
import 'package:permission_handler/permission_handler.dart';
|
import 'package:permission_handler/permission_handler.dart';
|
||||||
import 'package:provider/provider.dart';
|
|
||||||
import 'package:screen/screen.dart';
|
import 'package:screen/screen.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
|
@ -51,6 +49,7 @@ class HomePage extends StatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _HomePageState extends State<HomePage> {
|
class _HomePageState extends State<HomePage> {
|
||||||
|
MediaStoreSource _mediaStore;
|
||||||
ImageEntry _sharedEntry;
|
ImageEntry _sharedEntry;
|
||||||
Future<void> _appSetup;
|
Future<void> _appSetup;
|
||||||
|
|
||||||
|
@ -88,6 +87,9 @@ class _HomePageState extends State<HomePage> {
|
||||||
// cataloging is essential for geolocation and video rotation
|
// cataloging is essential for geolocation and video rotation
|
||||||
await _sharedEntry.catalog();
|
await _sharedEntry.catalog();
|
||||||
unawaited(_sharedEntry.locate());
|
unawaited(_sharedEntry.locate());
|
||||||
|
} else {
|
||||||
|
_mediaStore = MediaStoreSource();
|
||||||
|
unawaited(_mediaStore.fetch());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -103,11 +105,7 @@ class _HomePageState extends State<HomePage> {
|
||||||
? SingleFullscreenPage(
|
? SingleFullscreenPage(
|
||||||
entry: _sharedEntry,
|
entry: _sharedEntry,
|
||||||
)
|
)
|
||||||
: MediaStoreCollectionProvider(
|
: CollectionPage(_mediaStore.collection);
|
||||||
child: Consumer<CollectionLens>(
|
|
||||||
builder: (context, collection, child) => CollectionPage(collection),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -128,14 +128,14 @@ class CollectionLens with ChangeNotifier {
|
||||||
case SortFactor.date:
|
case SortFactor.date:
|
||||||
_filteredEntries.sort((a, b) {
|
_filteredEntries.sort((a, b) {
|
||||||
final c = b.bestDate.compareTo(a.bestDate);
|
final c = b.bestDate.compareTo(a.bestDate);
|
||||||
return c != 0 ? c : compareAsciiUpperCase(a.title, b.title);
|
return c != 0 ? c : compareAsciiUpperCase(a.bestTitle, b.bestTitle);
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case SortFactor.size:
|
case SortFactor.size:
|
||||||
_filteredEntries.sort((a, b) => b.sizeBytes.compareTo(a.sizeBytes));
|
_filteredEntries.sort((a, b) => b.sizeBytes.compareTo(a.sizeBytes));
|
||||||
break;
|
break;
|
||||||
case SortFactor.name:
|
case SortFactor.name:
|
||||||
_filteredEntries.sort((a, b) => compareAsciiUpperCase(a.title, b.title));
|
_filteredEntries.sort((a, b) => compareAsciiUpperCase(a.bestTitle, b.bestTitle));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -130,11 +130,19 @@ class ImageEntry {
|
||||||
|
|
||||||
int get megaPixels => width != null && height != null ? (width * height / 1000000).round() : null;
|
int get megaPixels => width != null && height != null ? (width * height / 1000000).round() : null;
|
||||||
|
|
||||||
|
DateTime _bestDate;
|
||||||
|
|
||||||
DateTime get bestDate {
|
DateTime get bestDate {
|
||||||
if ((catalogMetadata?.dateMillis ?? 0) > 0) return DateTime.fromMillisecondsSinceEpoch(catalogMetadata.dateMillis);
|
if (_bestDate == null) {
|
||||||
if (sourceDateTakenMillis != null && sourceDateTakenMillis > 0) return DateTime.fromMillisecondsSinceEpoch(sourceDateTakenMillis);
|
if ((catalogMetadata?.dateMillis ?? 0) > 0) {
|
||||||
if (dateModifiedSecs != null && dateModifiedSecs > 0) return DateTime.fromMillisecondsSinceEpoch(dateModifiedSecs * 1000);
|
_bestDate = DateTime.fromMillisecondsSinceEpoch(catalogMetadata.dateMillis);
|
||||||
return null;
|
} else if (sourceDateTakenMillis != null && sourceDateTakenMillis > 0) {
|
||||||
|
_bestDate = DateTime.fromMillisecondsSinceEpoch(sourceDateTakenMillis);
|
||||||
|
} else if (dateModifiedSecs != null && dateModifiedSecs > 0) {
|
||||||
|
_bestDate = DateTime.fromMillisecondsSinceEpoch(dateModifiedSecs * 1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return _bestDate;
|
||||||
}
|
}
|
||||||
|
|
||||||
DateTime get monthTaken {
|
DateTime get monthTaken {
|
||||||
|
@ -159,14 +167,18 @@ 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() ?? [];
|
||||||
|
|
||||||
String get title {
|
String _bestTitle;
|
||||||
if (catalogMetadata != null && catalogMetadata.xmpTitleDescription.isNotEmpty) return catalogMetadata.xmpTitleDescription;
|
|
||||||
return sourceTitle;
|
String get bestTitle {
|
||||||
|
_bestTitle ??= (catalogMetadata != null && catalogMetadata.xmpTitleDescription.isNotEmpty) ? catalogMetadata.xmpTitleDescription : sourceTitle;
|
||||||
|
return _bestTitle;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> catalog() async {
|
Future<void> catalog() async {
|
||||||
if (isCatalogued) return;
|
if (isCatalogued) return;
|
||||||
catalogMetadata = await MetadataService.getCatalogMetadata(this);
|
catalogMetadata = await MetadataService.getCatalogMetadata(this);
|
||||||
|
_bestDate = null;
|
||||||
|
_bestTitle = null;
|
||||||
if (catalogMetadata != null) {
|
if (catalogMetadata != null) {
|
||||||
metadataChangeNotifier.notifyListeners();
|
metadataChangeNotifier.notifyListeners();
|
||||||
}
|
}
|
||||||
|
@ -212,7 +224,7 @@ class ImageEntry {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool search(String query) {
|
bool search(String query) {
|
||||||
if (title?.toUpperCase()?.contains(query) ?? false) return true;
|
if (bestTitle?.toUpperCase()?.contains(query) ?? false) return true;
|
||||||
if (catalogMetadata?.xmpSubjects?.toUpperCase()?.contains(query) ?? false) return true;
|
if (catalogMetadata?.xmpSubjects?.toUpperCase()?.contains(query) ?? false) return true;
|
||||||
if (addressDetails?.addressLine?.toUpperCase()?.contains(query) ?? false) return true;
|
if (addressDetails?.addressLine?.toUpperCase()?.contains(query) ?? false) return true;
|
||||||
return false;
|
return false;
|
||||||
|
@ -232,6 +244,7 @@ class ImageEntry {
|
||||||
if (contentId is int) this.contentId = contentId;
|
if (contentId is int) this.contentId = contentId;
|
||||||
final sourceTitle = newFields['sourceTitle'];
|
final sourceTitle = newFields['sourceTitle'];
|
||||||
if (sourceTitle is String) this.sourceTitle = sourceTitle;
|
if (sourceTitle is String) this.sourceTitle = sourceTitle;
|
||||||
|
_bestTitle = null;
|
||||||
metadataChangeNotifier.notifyListeners();
|
metadataChangeNotifier.notifyListeners();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,6 @@ class CollectionPage extends StatelessWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
debugPrint('$runtimeType build');
|
|
||||||
return MediaQueryDataProvider(
|
return MediaQueryDataProvider(
|
||||||
child: ChangeNotifierProvider<CollectionLens>.value(
|
child: ChangeNotifierProvider<CollectionLens>.value(
|
||||||
value: collection,
|
value: collection,
|
||||||
|
|
|
@ -86,18 +86,18 @@ class GridThumbnail extends StatelessWidget {
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
key: ValueKey(entry.uri),
|
key: ValueKey(entry.uri),
|
||||||
onTap: () => _goToFullscreen(context),
|
onTap: () => _goToFullscreen(context),
|
||||||
child: Selector<MediaQueryData, double>(
|
child: MetaData(
|
||||||
selector: (c, mq) => mq.size.width,
|
metaData: ThumbnailMetadata(index, entry),
|
||||||
builder: (c, mqWidth, child) {
|
child: Selector<MediaQueryData, double>(
|
||||||
return MetaData(
|
selector: (c, mq) => mq.size.width,
|
||||||
metaData: ThumbnailMetadata(index, entry),
|
builder: (c, mqWidth, child) {
|
||||||
child: Thumbnail(
|
return Thumbnail(
|
||||||
entry: entry,
|
entry: entry,
|
||||||
extent: mqWidth / columnCount,
|
extent: mqWidth / columnCount,
|
||||||
heroTag: collection.heroTag(entry),
|
heroTag: collection.heroTag(entry),
|
||||||
),
|
);
|
||||||
);
|
},
|
||||||
},
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,77 +27,81 @@ class ThumbnailCollection extends StatelessWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
debugPrint('$runtimeType build');
|
|
||||||
final collection = Provider.of<CollectionLens>(context);
|
|
||||||
final sections = collection.sections;
|
|
||||||
final sectionKeys = sections.keys.toList();
|
|
||||||
final showHeaders = collection.showHeaders;
|
|
||||||
|
|
||||||
return SafeArea(
|
return SafeArea(
|
||||||
child: Selector<MediaQueryData, double>(
|
child: Selector<MediaQueryData, double>(
|
||||||
selector: (c, mq) => mq.viewInsets.bottom,
|
selector: (c, mq) => mq.viewInsets.bottom,
|
||||||
builder: (c, mqViewInsetsBottom, child) {
|
builder: (c, mqViewInsetsBottom, child) {
|
||||||
return GridScaleGestureDetector(
|
return Consumer<CollectionLens>(
|
||||||
scrollableKey: _scrollableKey,
|
builder: (context, collection, child) {
|
||||||
columnCountNotifier: _columnCountNotifier,
|
debugPrint('$runtimeType collection builder entries=${collection.entryCount}');
|
||||||
child: ValueListenableBuilder<int>(
|
final sectionKeys = collection.sections.keys.toList();
|
||||||
valueListenable: _columnCountNotifier,
|
final showHeaders = collection.showHeaders;
|
||||||
builder: (context, columnCount, child) {
|
return GridScaleGestureDetector(
|
||||||
debugPrint('$runtimeType builder columnCount=$columnCount entries=${collection.entryCount}');
|
scrollableKey: _scrollableKey,
|
||||||
final scrollView = CustomScrollView(
|
columnCountNotifier: _columnCountNotifier,
|
||||||
key: _scrollableKey,
|
child: ValueListenableBuilder<int>(
|
||||||
primary: true,
|
valueListenable: _columnCountNotifier,
|
||||||
// workaround to prevent scrolling the app bar away
|
builder: (context, columnCount, child) {
|
||||||
// when there is no content and we use `SliverFillRemaining`
|
debugPrint('$runtimeType columnCount builder entries=${collection.entryCount} columnCount=$columnCount');
|
||||||
physics: collection.isEmpty ? const NeverScrollableScrollPhysics() : null,
|
final scrollView = CustomScrollView(
|
||||||
slivers: [
|
key: _scrollableKey,
|
||||||
CollectionAppBar(
|
primary: true,
|
||||||
stateNotifier: stateNotifier,
|
// workaround to prevent scrolling the app bar away
|
||||||
appBarHeightNotifier: _appBarHeightNotifier,
|
// when there is no content and we use `SliverFillRemaining`
|
||||||
collection: collection,
|
physics: collection.isEmpty ? const NeverScrollableScrollPhysics() : null,
|
||||||
),
|
slivers: [
|
||||||
if (collection.isEmpty)
|
CollectionAppBar(
|
||||||
SliverFillRemaining(
|
stateNotifier: stateNotifier,
|
||||||
child: _buildEmptyCollectionPlaceholder(collection),
|
appBarHeightNotifier: _appBarHeightNotifier,
|
||||||
hasScrollBody: false,
|
|
||||||
),
|
|
||||||
...sectionKeys.map((sectionKey) => SectionSliver(
|
|
||||||
collection: collection,
|
collection: collection,
|
||||||
sectionKey: sectionKey,
|
),
|
||||||
columnCount: columnCount,
|
if (collection.isEmpty)
|
||||||
showHeader: showHeaders,
|
SliverFillRemaining(
|
||||||
)),
|
child: _buildEmptyCollectionPlaceholder(collection),
|
||||||
SliverToBoxAdapter(
|
hasScrollBody: false,
|
||||||
child: Selector<MediaQueryData, double>(
|
),
|
||||||
selector: (c, mq) => mq.viewInsets.bottom,
|
...sectionKeys.map((sectionKey) => SectionSliver(
|
||||||
builder: (c, mqViewInsetsBottom, child) {
|
collection: collection,
|
||||||
return SizedBox(height: mqViewInsetsBottom);
|
sectionKey: sectionKey,
|
||||||
},
|
columnCount: columnCount,
|
||||||
),
|
showHeader: showHeaders,
|
||||||
),
|
)),
|
||||||
],
|
SliverToBoxAdapter(
|
||||||
);
|
child: Selector<MediaQueryData, double>(
|
||||||
|
selector: (c, mq) => mq.viewInsets.bottom,
|
||||||
|
builder: (c, mqViewInsetsBottom, child) {
|
||||||
|
return SizedBox(height: mqViewInsetsBottom);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
return ValueListenableBuilder<double>(
|
return ValueListenableBuilder<double>(
|
||||||
valueListenable: _appBarHeightNotifier,
|
valueListenable: _appBarHeightNotifier,
|
||||||
builder: (context, appBarHeight, child) {
|
builder: (context, appBarHeight, child) {
|
||||||
return DraggableScrollbar(
|
return DraggableScrollbar(
|
||||||
heightScrollThumb: avesScrollThumbHeight,
|
heightScrollThumb: avesScrollThumbHeight,
|
||||||
backgroundColor: Colors.white,
|
backgroundColor: Colors.white,
|
||||||
scrollThumbBuilder: avesScrollThumbBuilder(),
|
scrollThumbBuilder: avesScrollThumbBuilder(
|
||||||
controller: PrimaryScrollController.of(context),
|
height: avesScrollThumbHeight,
|
||||||
padding: EdgeInsets.only(
|
backgroundColor: Colors.white,
|
||||||
// padding to keep scroll thumb between app bar above and nav bar below
|
),
|
||||||
top: appBarHeight,
|
controller: PrimaryScrollController.of(context),
|
||||||
bottom: mqViewInsetsBottom,
|
padding: EdgeInsets.only(
|
||||||
),
|
// padding to keep scroll thumb between app bar above and nav bar below
|
||||||
child: child,
|
top: appBarHeight,
|
||||||
|
bottom: mqViewInsetsBottom,
|
||||||
|
),
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: scrollView,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
child: scrollView,
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
|
@ -8,37 +8,26 @@ import 'package:aves/model/settings.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:provider/provider.dart';
|
|
||||||
|
|
||||||
class MediaStoreCollectionProvider extends StatefulWidget {
|
class MediaStoreSource {
|
||||||
final Widget child;
|
CollectionSource _source;
|
||||||
|
CollectionLens _baseLens;
|
||||||
|
|
||||||
const MediaStoreCollectionProvider({@required this.child});
|
CollectionLens get collection => _baseLens;
|
||||||
|
|
||||||
@override
|
static const EventChannel _eventChannel = EventChannel('deckers.thibault/aves/mediastore');
|
||||||
_MediaStoreCollectionProviderState createState() => _MediaStoreCollectionProviderState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _MediaStoreCollectionProviderState extends State<MediaStoreCollectionProvider> {
|
MediaStoreSource() {
|
||||||
Future<CollectionLens> collectionFuture;
|
_source = CollectionSource();
|
||||||
|
_baseLens = CollectionLens(
|
||||||
static const EventChannel eventChannel = EventChannel('deckers.thibault/aves/mediastore');
|
source: _source,
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
collectionFuture = _create();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<CollectionLens> _create() async {
|
|
||||||
final stopwatch = Stopwatch()..start();
|
|
||||||
final mediaStoreSource = CollectionSource();
|
|
||||||
final mediaStoreBaseLens = CollectionLens(
|
|
||||||
source: mediaStoreSource,
|
|
||||||
groupFactor: settings.collectionGroupFactor,
|
groupFactor: settings.collectionGroupFactor,
|
||||||
sortFactor: settings.collectionSortFactor,
|
sortFactor: settings.collectionSortFactor,
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> fetch() async {
|
||||||
|
final stopwatch = Stopwatch()..start();
|
||||||
await metadataDb.init(); // <20ms
|
await metadataDb.init(); // <20ms
|
||||||
await favourites.init();
|
await favourites.init();
|
||||||
final currentTimeZone = await FlutterNativeTimezone.getLocalTimezone(); // <20ms
|
final currentTimeZone = await FlutterNativeTimezone.getLocalTimezone(); // <20ms
|
||||||
|
@ -51,39 +40,31 @@ class _MediaStoreCollectionProviderState extends State<MediaStoreCollectionProvi
|
||||||
}
|
}
|
||||||
|
|
||||||
final allEntries = <ImageEntry>[];
|
final allEntries = <ImageEntry>[];
|
||||||
eventChannel.receiveBroadcastStream().cast<Map>().listen(
|
_eventChannel.receiveBroadcastStream().cast<Map>().listen(
|
||||||
(entryMap) => allEntries.add(ImageEntry.fromMap(entryMap)),
|
(entryMap) {
|
||||||
onDone: () async {
|
allEntries.add(ImageEntry.fromMap(entryMap));
|
||||||
debugPrint('$runtimeType stream complete in ${stopwatch.elapsed.inMilliseconds}ms');
|
if (allEntries.length >= 100) {
|
||||||
mediaStoreSource.addAll(allEntries);
|
_source.addAll(allEntries);
|
||||||
// TODO reduce setup time until here
|
allEntries.clear();
|
||||||
mediaStoreSource.updateAlbums(); // <50ms
|
// debugPrint('$runtimeType streamed ${_source.entries.length} entries at ${stopwatch.elapsed.inMilliseconds}ms');
|
||||||
await mediaStoreSource.loadCatalogMetadata(); // 650ms
|
}
|
||||||
await mediaStoreSource.catalogEntries(); // <50ms
|
},
|
||||||
await mediaStoreSource.loadAddresses(); // 350ms
|
onDone: () async {
|
||||||
await mediaStoreSource.locateEntries(); // <50ms
|
debugPrint('$runtimeType stream complete at ${stopwatch.elapsed.inMilliseconds}ms');
|
||||||
debugPrint('$runtimeType setup end, elapsed=${stopwatch.elapsed}');
|
_source.addAll(allEntries);
|
||||||
},
|
// TODO reduce setup time until here
|
||||||
onError: (error) => debugPrint('$runtimeType mediastore stream error=$error'),
|
_source.updateAlbums(); // <50ms
|
||||||
);
|
await _source.loadCatalogMetadata(); // 650ms
|
||||||
|
await _source.catalogEntries(); // <50ms
|
||||||
|
await _source.loadAddresses(); // 350ms
|
||||||
|
await _source.locateEntries(); // <50ms
|
||||||
|
debugPrint('$runtimeType setup end, elapsed=${stopwatch.elapsed}');
|
||||||
|
},
|
||||||
|
onError: (error) => debugPrint('$runtimeType mediastore stream error=$error'),
|
||||||
|
);
|
||||||
|
|
||||||
// TODO split image fetch AND/OR cache fetch across sessions
|
// TODO split image fetch AND/OR cache fetch across sessions
|
||||||
|
debugPrint('$runtimeType stream start at ${stopwatch.elapsed.inMilliseconds}ms');
|
||||||
await ImageFileService.getImageEntries(); // 460ms
|
await ImageFileService.getImageEntries(); // 460ms
|
||||||
|
|
||||||
return mediaStoreBaseLens;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return FutureBuilder(
|
|
||||||
future: collectionFuture,
|
|
||||||
builder: (futureContext, AsyncSnapshot<CollectionLens> snapshot) {
|
|
||||||
final collection = (snapshot.connectionState == ConnectionState.done && !snapshot.hasError) ? snapshot.data : CollectionLens.empty();
|
|
||||||
return ChangeNotifierProvider<CollectionLens>.value(
|
|
||||||
value: collection,
|
|
||||||
child: widget.child,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,35 @@ import 'package:flutter/material.dart';
|
||||||
|
|
||||||
const double avesScrollThumbHeight = 48;
|
const double avesScrollThumbHeight = 48;
|
||||||
|
|
||||||
ScrollThumbBuilder avesScrollThumbBuilder() {
|
// height and background color do not change
|
||||||
|
// so we do not rely on the builder props
|
||||||
|
ScrollThumbBuilder avesScrollThumbBuilder({
|
||||||
|
@required double height,
|
||||||
|
@required Color backgroundColor,
|
||||||
|
}) {
|
||||||
|
final scrollThumb = Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.black26,
|
||||||
|
borderRadius: const BorderRadius.all(
|
||||||
|
Radius.circular(12.0),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
height: height,
|
||||||
|
margin: const EdgeInsets.only(right: .5),
|
||||||
|
padding: const EdgeInsets.all(2),
|
||||||
|
child: ClipPath(
|
||||||
|
child: Container(
|
||||||
|
width: 20.0,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: backgroundColor,
|
||||||
|
borderRadius: const BorderRadius.all(
|
||||||
|
Radius.circular(12.0),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
clipper: ArrowClipper(),
|
||||||
|
),
|
||||||
|
);
|
||||||
return (
|
return (
|
||||||
Color backgroundColor,
|
Color backgroundColor,
|
||||||
Animation<double> thumbAnimation,
|
Animation<double> thumbAnimation,
|
||||||
|
@ -11,30 +39,6 @@ ScrollThumbBuilder avesScrollThumbBuilder() {
|
||||||
double height, {
|
double height, {
|
||||||
Widget labelText,
|
Widget labelText,
|
||||||
}) {
|
}) {
|
||||||
final scrollThumb = Container(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.black26,
|
|
||||||
borderRadius: const BorderRadius.all(
|
|
||||||
Radius.circular(12.0),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
height: height,
|
|
||||||
margin: const EdgeInsets.only(right: .5),
|
|
||||||
padding: const EdgeInsets.all(2),
|
|
||||||
child: ClipPath(
|
|
||||||
child: Container(
|
|
||||||
width: 20.0,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: backgroundColor,
|
|
||||||
borderRadius: const BorderRadius.all(
|
|
||||||
Radius.circular(12.0),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
clipper: ArrowClipper(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
return DraggableScrollbar.buildScrollThumbAndLabel(
|
return DraggableScrollbar.buildScrollThumbAndLabel(
|
||||||
scrollThumb: scrollThumb,
|
scrollThumb: scrollThumb,
|
||||||
backgroundColor: backgroundColor,
|
backgroundColor: backgroundColor,
|
||||||
|
|
|
@ -82,7 +82,7 @@ class FullscreenActionDelegate {
|
||||||
Future<void> _print(ImageEntry entry) async {
|
Future<void> _print(ImageEntry entry) async {
|
||||||
final uri = entry.uri;
|
final uri = entry.uri;
|
||||||
final mimeType = entry.mimeType;
|
final mimeType = entry.mimeType;
|
||||||
final documentName = entry.title ?? 'Aves';
|
final documentName = entry.bestTitle ?? 'Aves';
|
||||||
final doc = pdf.Document(title: documentName);
|
final doc = pdf.Document(title: documentName);
|
||||||
|
|
||||||
PdfImage pdfImage;
|
PdfImage pdfImage;
|
||||||
|
@ -152,7 +152,7 @@ class FullscreenActionDelegate {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _showRenameDialog(BuildContext context, ImageEntry entry) async {
|
Future<void> _showRenameDialog(BuildContext context, ImageEntry entry) async {
|
||||||
final currentName = entry.title;
|
final currentName = entry.bestTitle;
|
||||||
final controller = TextEditingController(text: currentName);
|
final controller = TextEditingController(text: currentName);
|
||||||
final newName = await showDialog<String>(
|
final newName = await showDialog<String>(
|
||||||
context: context,
|
context: context,
|
||||||
|
|
|
@ -37,7 +37,7 @@ class BasicSection extends StatelessWidget {
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
InfoRowGroup({
|
InfoRowGroup({
|
||||||
'Title': entry.title ?? '?',
|
'Title': entry.bestTitle ?? '?',
|
||||||
'Date': dateText,
|
'Date': dateText,
|
||||||
if (entry.isVideo) ..._buildVideoRows(),
|
if (entry.isVideo) ..._buildVideoRows(),
|
||||||
if (!entry.isSvg) 'Resolution': resolutionText,
|
if (!entry.isSvg) 'Resolution': resolutionText,
|
||||||
|
|
|
@ -152,7 +152,7 @@ class _FullscreenBottomOverlayContent extends AnimatedWidget {
|
||||||
final subRowWidth = twoColumns ? min(_subRowMinWidth, maxWidth / 2) : maxWidth;
|
final subRowWidth = twoColumns ? min(_subRowMinWidth, maxWidth / 2) : maxWidth;
|
||||||
final positionTitle = [
|
final positionTitle = [
|
||||||
if (position != null) position,
|
if (position != null) position,
|
||||||
if (entry.title != null) entry.title,
|
if (entry.bestTitle != null) entry.bestTitle,
|
||||||
].join(' – ');
|
].join(' – ');
|
||||||
final hasShootingDetails = details != null && !details.isEmpty;
|
final hasShootingDetails = details != null && !details.isEmpty;
|
||||||
return Column(
|
return Column(
|
||||||
|
|
Loading…
Reference in a new issue