diff --git a/lib/main.dart b/lib/main.dart index a12bffd0d..ea0d18241 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,23 +1,23 @@ import 'package:aves/model/image_decode_service.dart'; import 'package:aves/model/image_entry.dart'; import 'package:aves/model/metadata_storage_service.dart'; -import 'package:aves/widgets/album/thumbnail_collection.dart'; +import 'package:aves/widgets/album/all_collection_page.dart'; import 'package:aves/widgets/common/fake_app_bar.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; void main() { - runApp(MyApp()); + runApp(AvesApp()); } -class MyApp extends StatelessWidget { +class AvesApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Aves', theme: ThemeData( brightness: Brightness.dark, - accentColor: Colors.amberAccent, + accentColor: Colors.indigoAccent, scaffoldBackgroundColor: Colors.grey[900], ), home: HomePage(), @@ -34,7 +34,6 @@ class _HomePageState extends State { static const EventChannel eventChannel = EventChannel('deckers.thibault/aves/mediastore'); List entries = List(); - bool done = false; @override void initState() { @@ -48,12 +47,12 @@ class _HomePageState extends State { eventChannel.receiveBroadcastStream().cast().listen( (entryMap) => setState(() => entries.add(ImageEntry.fromMap(entryMap))), - onDone: () { - debugPrint('mediastore stream done'); - setState(() => done = true); - }, - onError: (error) => debugPrint('mediastore stream error=$error'), - ); + onDone: () { + debugPrint('mediastore stream done'); + setState(() {}); + }, + onError: (error) => debugPrint('mediastore stream error=$error'), + ); await ImageDecodeService.getImageEntries(); } @@ -62,10 +61,7 @@ class _HomePageState extends State { return Scaffold( // fake app bar so that content is safe from status bar, even though we use a SliverAppBar appBar: FakeAppBar(), - body: ThumbnailCollection( - entries: entries, - done: done, - ), + body: AllCollectionPage(entries: entries), resizeToAvoidBottomInset: false, ); } diff --git a/lib/model/image_entry.dart b/lib/model/image_entry.dart index 6bad9a0d2..de48a45f5 100644 --- a/lib/model/image_entry.dart +++ b/lib/model/image_entry.dart @@ -69,6 +69,14 @@ class ImageEntry { }; } + @override + String toString() { + return 'ImageEntry{uri=$uri, path=$path}'; + } + + // TODO TLAD add xmp subjects, address, etc. + String get searchable => title.toLowerCase(); + bool get isGif => mimeType == MimeTypes.MIME_GIF; bool get isVideo => mimeType.startsWith(MimeTypes.MIME_VIDEO); diff --git a/lib/widgets/album/all_collection_page.dart b/lib/widgets/album/all_collection_page.dart new file mode 100644 index 000000000..5faa52386 --- /dev/null +++ b/lib/widgets/album/all_collection_page.dart @@ -0,0 +1,41 @@ +import 'package:aves/model/image_entry.dart'; +import 'package:aves/widgets/album/search_delegate.dart'; +import 'package:aves/widgets/album/thumbnail_collection.dart'; +import 'package:aves/widgets/debug_page.dart'; +import 'package:flutter/material.dart'; + +class AllCollectionPage extends StatelessWidget { + final List entries; + + const AllCollectionPage({Key key, this.entries}) : super(key: key); + + @override + Widget build(BuildContext context) { + return ThumbnailCollection( + entries: entries, + appBar: SliverAppBar( + title: Text('Aves - All'), + actions: [ + IconButton( + icon: Icon(Icons.search), + onPressed: () => showSearch( + context: context, + delegate: ImageSearchDelegate(entries), + ), + ), + IconButton(icon: Icon(Icons.whatshot), onPressed: () => goToDebug(context)), + ], + floating: true, + ), + ); + } + + Future goToDebug(BuildContext context) { + return Navigator.push( + context, + MaterialPageRoute( + builder: (context) => DebugPage(), + ), + ); + } +} diff --git a/lib/widgets/album/search_delegate.dart b/lib/widgets/album/search_delegate.dart new file mode 100644 index 000000000..b52af5aca --- /dev/null +++ b/lib/widgets/album/search_delegate.dart @@ -0,0 +1,65 @@ +import 'package:aves/model/image_entry.dart'; +import 'package:aves/widgets/album/thumbnail_collection.dart'; +import 'package:flutter/material.dart'; + +class ImageSearchDelegate extends SearchDelegate { + final List entries; + + ImageSearchDelegate(this.entries); + + @override + ThemeData appBarTheme(BuildContext context) { + return Theme.of(context); + } + + @override + Widget buildLeading(BuildContext context) { + return IconButton( + tooltip: 'Back', + icon: AnimatedIcon( + icon: AnimatedIcons.menu_arrow, + progress: transitionAnimation, + ), + onPressed: () => close(context, null), + ); + } + + @override + List buildActions(BuildContext context) { + return [ + if (query.isNotEmpty) + IconButton( + tooltip: 'Clear', + icon: Icon(Icons.clear), + onPressed: () { + query = ''; + showSuggestions(context); + }, + ), + ]; + } + + @override + Widget buildSuggestions(BuildContext context) { + return SizedBox.shrink(); + } + + @override + Widget buildResults(BuildContext context) { + if (query.isEmpty) { + showSuggestions(context); + return SizedBox.shrink(); + } + final lowerQuery = query.toLowerCase(); + final matches = entries.where((entry) => entry.searchable.contains(lowerQuery)).toList(); + if (matches.isEmpty) { + return Center( + child: Text( + 'No match', + textAlign: TextAlign.center, + ), + ); + } + return ThumbnailCollection(entries: matches); + } +} diff --git a/lib/widgets/album/thumbnail_collection.dart b/lib/widgets/album/thumbnail_collection.dart index b15abb25f..a2b4b1dd9 100644 --- a/lib/widgets/album/thumbnail_collection.dart +++ b/lib/widgets/album/thumbnail_collection.dart @@ -3,7 +3,6 @@ import 'package:aves/utils/date_utils.dart'; import 'package:aves/widgets/album/thumbnail.dart'; import 'package:aves/widgets/common/draggable_scrollbar.dart'; import 'package:aves/widgets/common/outlined_text.dart'; -import 'package:aves/widgets/debug_page.dart'; import 'package:aves/widgets/fullscreen/image_page.dart'; import "package:collection/collection.dart"; import 'package:flutter/material.dart'; @@ -12,43 +11,29 @@ import 'package:intl/intl.dart'; class ThumbnailCollection extends StatelessWidget { final List entries; - final bool done; - final Map> sections; - final ScrollController scrollController = ScrollController(); + final Widget appBar; - ThumbnailCollection({Key key, this.entries, this.done}) - : sections = groupBy(entries, (entry) => entry.monthTaken), + final Map> _sections; + final ScrollController _scrollController = ScrollController(); + + ThumbnailCollection({Key key, this.entries, this.appBar}) + : _sections = groupBy(entries, (entry) => entry.monthTaken), super(key: key); @override Widget build(BuildContext context) { -// debugPrint('$runtimeType build with sections=${sections.length}'); - if (!done) { - return Center( - child: Text( - 'streamed ${entries.length} items', - style: TextStyle(fontSize: 16), - ), - ); - } final bottomInsets = MediaQuery.of(context).viewInsets.bottom; - final sectionKeys = sections.keys.toList(); + final sectionKeys = _sections.keys.toList(); return SafeArea( child: DraggableScrollbar.arrows( child: CustomScrollView( - controller: scrollController, + controller: _scrollController, slivers: [ - SliverAppBar( - title: Text('Aves - All'), - actions: [ - IconButton(icon: Icon(Icons.whatshot), onPressed: () => goToDebug(context)), - ], - floating: true, - ), + if (appBar != null) appBar, ...sectionKeys.map((sectionKey) { Widget sliver = SectionSliver( entries: entries, - sections: sections, + sections: _sections, sectionKey: sectionKey, ); if (sectionKey == sectionKeys.last) { @@ -61,7 +46,7 @@ class ThumbnailCollection extends StatelessWidget { }), ], ), - controller: scrollController, + controller: _scrollController, padding: EdgeInsets.only(bottom: bottomInsets), labelTextBuilder: (double offset) => Text( "${offset ~/ 1}", @@ -70,15 +55,6 @@ class ThumbnailCollection extends StatelessWidget { ), ); } - - Future goToDebug(BuildContext context) { - return Navigator.push( - context, - MaterialPageRoute( - builder: (context) => DebugPage(), - ), - ); - } } class SectionSliver extends StatelessWidget { diff --git a/test/widget_test.dart b/test/widget_test.dart index c74a2e373..325b04eb7 100644 --- a/test/widget_test.dart +++ b/test/widget_test.dart @@ -13,7 +13,7 @@ import 'package:aves/main.dart'; void main() { testWidgets('Counter increments smoke test', (WidgetTester tester) async { // Build our app and trigger a frame. - await tester.pumpWidget(MyApp()); + await tester.pumpWidget(AvesApp()); // Verify that our counter starts at 0. expect(find.text('0'), findsOneWidget);