search entries
This commit is contained in:
parent
3da9465b1e
commit
ac8b6176c3
6 changed files with 137 additions and 51 deletions
|
@ -1,23 +1,23 @@
|
||||||
import 'package:aves/model/image_decode_service.dart';
|
import 'package:aves/model/image_decode_service.dart';
|
||||||
import 'package:aves/model/image_entry.dart';
|
import 'package:aves/model/image_entry.dart';
|
||||||
import 'package:aves/model/metadata_storage_service.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:aves/widgets/common/fake_app_bar.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
runApp(MyApp());
|
runApp(AvesApp());
|
||||||
}
|
}
|
||||||
|
|
||||||
class MyApp extends StatelessWidget {
|
class AvesApp extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return MaterialApp(
|
return MaterialApp(
|
||||||
title: 'Aves',
|
title: 'Aves',
|
||||||
theme: ThemeData(
|
theme: ThemeData(
|
||||||
brightness: Brightness.dark,
|
brightness: Brightness.dark,
|
||||||
accentColor: Colors.amberAccent,
|
accentColor: Colors.indigoAccent,
|
||||||
scaffoldBackgroundColor: Colors.grey[900],
|
scaffoldBackgroundColor: Colors.grey[900],
|
||||||
),
|
),
|
||||||
home: HomePage(),
|
home: HomePage(),
|
||||||
|
@ -34,7 +34,6 @@ class _HomePageState extends State<HomePage> {
|
||||||
static const EventChannel eventChannel = EventChannel('deckers.thibault/aves/mediastore');
|
static const EventChannel eventChannel = EventChannel('deckers.thibault/aves/mediastore');
|
||||||
|
|
||||||
List<ImageEntry> entries = List();
|
List<ImageEntry> entries = List();
|
||||||
bool done = false;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
|
@ -48,12 +47,12 @@ class _HomePageState extends State<HomePage> {
|
||||||
|
|
||||||
eventChannel.receiveBroadcastStream().cast<Map>().listen(
|
eventChannel.receiveBroadcastStream().cast<Map>().listen(
|
||||||
(entryMap) => setState(() => entries.add(ImageEntry.fromMap(entryMap))),
|
(entryMap) => setState(() => entries.add(ImageEntry.fromMap(entryMap))),
|
||||||
onDone: () {
|
onDone: () {
|
||||||
debugPrint('mediastore stream done');
|
debugPrint('mediastore stream done');
|
||||||
setState(() => done = true);
|
setState(() {});
|
||||||
},
|
},
|
||||||
onError: (error) => debugPrint('mediastore stream error=$error'),
|
onError: (error) => debugPrint('mediastore stream error=$error'),
|
||||||
);
|
);
|
||||||
await ImageDecodeService.getImageEntries();
|
await ImageDecodeService.getImageEntries();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -62,10 +61,7 @@ class _HomePageState extends State<HomePage> {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
// fake app bar so that content is safe from status bar, even though we use a SliverAppBar
|
// fake app bar so that content is safe from status bar, even though we use a SliverAppBar
|
||||||
appBar: FakeAppBar(),
|
appBar: FakeAppBar(),
|
||||||
body: ThumbnailCollection(
|
body: AllCollectionPage(entries: entries),
|
||||||
entries: entries,
|
|
||||||
done: done,
|
|
||||||
),
|
|
||||||
resizeToAvoidBottomInset: false,
|
resizeToAvoidBottomInset: false,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 isGif => mimeType == MimeTypes.MIME_GIF;
|
||||||
|
|
||||||
bool get isVideo => mimeType.startsWith(MimeTypes.MIME_VIDEO);
|
bool get isVideo => mimeType.startsWith(MimeTypes.MIME_VIDEO);
|
||||||
|
|
41
lib/widgets/album/all_collection_page.dart
Normal file
41
lib/widgets/album/all_collection_page.dart
Normal file
|
@ -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<ImageEntry> 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(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
65
lib/widgets/album/search_delegate.dart
Normal file
65
lib/widgets/album/search_delegate.dart
Normal file
|
@ -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<ImageEntry> {
|
||||||
|
final List<ImageEntry> 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<Widget> 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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,7 +3,6 @@ import 'package:aves/utils/date_utils.dart';
|
||||||
import 'package:aves/widgets/album/thumbnail.dart';
|
import 'package:aves/widgets/album/thumbnail.dart';
|
||||||
import 'package:aves/widgets/common/draggable_scrollbar.dart';
|
import 'package:aves/widgets/common/draggable_scrollbar.dart';
|
||||||
import 'package:aves/widgets/common/outlined_text.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:aves/widgets/fullscreen/image_page.dart';
|
||||||
import "package:collection/collection.dart";
|
import "package:collection/collection.dart";
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
@ -12,43 +11,29 @@ import 'package:intl/intl.dart';
|
||||||
|
|
||||||
class ThumbnailCollection extends StatelessWidget {
|
class ThumbnailCollection extends StatelessWidget {
|
||||||
final List<ImageEntry> entries;
|
final List<ImageEntry> entries;
|
||||||
final bool done;
|
final Widget appBar;
|
||||||
final Map<DateTime, List<ImageEntry>> sections;
|
|
||||||
final ScrollController scrollController = ScrollController();
|
|
||||||
|
|
||||||
ThumbnailCollection({Key key, this.entries, this.done})
|
final Map<DateTime, List<ImageEntry>> _sections;
|
||||||
: sections = groupBy(entries, (entry) => entry.monthTaken),
|
final ScrollController _scrollController = ScrollController();
|
||||||
|
|
||||||
|
ThumbnailCollection({Key key, this.entries, this.appBar})
|
||||||
|
: _sections = groupBy(entries, (entry) => entry.monthTaken),
|
||||||
super(key: key);
|
super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
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 bottomInsets = MediaQuery.of(context).viewInsets.bottom;
|
||||||
final sectionKeys = sections.keys.toList();
|
final sectionKeys = _sections.keys.toList();
|
||||||
return SafeArea(
|
return SafeArea(
|
||||||
child: DraggableScrollbar.arrows(
|
child: DraggableScrollbar.arrows(
|
||||||
child: CustomScrollView(
|
child: CustomScrollView(
|
||||||
controller: scrollController,
|
controller: _scrollController,
|
||||||
slivers: [
|
slivers: [
|
||||||
SliverAppBar(
|
if (appBar != null) appBar,
|
||||||
title: Text('Aves - All'),
|
|
||||||
actions: [
|
|
||||||
IconButton(icon: Icon(Icons.whatshot), onPressed: () => goToDebug(context)),
|
|
||||||
],
|
|
||||||
floating: true,
|
|
||||||
),
|
|
||||||
...sectionKeys.map((sectionKey) {
|
...sectionKeys.map((sectionKey) {
|
||||||
Widget sliver = SectionSliver(
|
Widget sliver = SectionSliver(
|
||||||
entries: entries,
|
entries: entries,
|
||||||
sections: sections,
|
sections: _sections,
|
||||||
sectionKey: sectionKey,
|
sectionKey: sectionKey,
|
||||||
);
|
);
|
||||||
if (sectionKey == sectionKeys.last) {
|
if (sectionKey == sectionKeys.last) {
|
||||||
|
@ -61,7 +46,7 @@ class ThumbnailCollection extends StatelessWidget {
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
controller: scrollController,
|
controller: _scrollController,
|
||||||
padding: EdgeInsets.only(bottom: bottomInsets),
|
padding: EdgeInsets.only(bottom: bottomInsets),
|
||||||
labelTextBuilder: (double offset) => Text(
|
labelTextBuilder: (double offset) => Text(
|
||||||
"${offset ~/ 1}",
|
"${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 {
|
class SectionSliver extends StatelessWidget {
|
||||||
|
|
|
@ -13,7 +13,7 @@ import 'package:aves/main.dart';
|
||||||
void main() {
|
void main() {
|
||||||
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
|
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
|
||||||
// Build our app and trigger a frame.
|
// Build our app and trigger a frame.
|
||||||
await tester.pumpWidget(MyApp());
|
await tester.pumpWidget(AvesApp());
|
||||||
|
|
||||||
// Verify that our counter starts at 0.
|
// Verify that our counter starts at 0.
|
||||||
expect(find.text('0'), findsOneWidget);
|
expect(find.text('0'), findsOneWidget);
|
||||||
|
|
Loading…
Reference in a new issue