album: group by album/date
This commit is contained in:
parent
af1b86dfaa
commit
5bb2e914c6
6 changed files with 173 additions and 105 deletions
|
@ -2,13 +2,31 @@ import 'package:aves/model/image_entry.dart';
|
|||
import 'package:aves/model/image_file_service.dart';
|
||||
import 'package:aves/model/image_metadata.dart';
|
||||
import 'package:aves/model/metadata_db.dart';
|
||||
import "package:collection/collection.dart";
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class ImageCollection with ChangeNotifier {
|
||||
final List<ImageEntry> entries;
|
||||
|
||||
GroupFactor groupFactor = GroupFactor.date;
|
||||
|
||||
ImageCollection(this.entries);
|
||||
|
||||
Map<dynamic, List<ImageEntry>> get sections {
|
||||
switch (groupFactor) {
|
||||
case GroupFactor.album:
|
||||
return groupBy(entries, (entry) => entry.bucketDisplayName);
|
||||
case GroupFactor.date:
|
||||
return groupBy(entries, (entry) => entry.monthTaken);
|
||||
}
|
||||
return Map();
|
||||
}
|
||||
|
||||
group(GroupFactor groupFactor) {
|
||||
this.groupFactor = groupFactor;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<bool> delete(ImageEntry entry) async {
|
||||
final success = await ImageFileService.delete(entry);
|
||||
if (success) {
|
||||
|
@ -77,3 +95,5 @@ class ImageCollection with ChangeNotifier {
|
|||
debugPrint('$runtimeType locateEntries complete in ${DateTime.now().difference(start).inSeconds}s');
|
||||
}
|
||||
}
|
||||
|
||||
enum GroupFactor { album, date }
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import 'package:aves/model/image_collection.dart';
|
||||
import 'package:aves/widgets/album/search_delegate.dart';
|
||||
import 'package:aves/widgets/album/thumbnail_collection.dart';
|
||||
import 'package:aves/widgets/common/menu_row.dart';
|
||||
import 'package:aves/widgets/debug_page.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
|
@ -23,13 +24,44 @@ class AllCollectionPage extends StatelessWidget {
|
|||
delegate: ImageSearchDelegate(collection),
|
||||
),
|
||||
),
|
||||
IconButton(icon: Icon(Icons.whatshot), onPressed: () => goToDebug(context)),
|
||||
PopupMenuButton<AlbumAction>(
|
||||
itemBuilder: (context) => [
|
||||
PopupMenuItem(
|
||||
value: AlbumAction.groupByAlbum,
|
||||
child: MenuRow(text: 'Group by album', checked: collection.groupFactor == GroupFactor.album),
|
||||
),
|
||||
PopupMenuItem(
|
||||
value: AlbumAction.groupByDate,
|
||||
child: MenuRow(text: 'Group by date', checked: collection.groupFactor == GroupFactor.date),
|
||||
),
|
||||
PopupMenuDivider(),
|
||||
PopupMenuItem(
|
||||
value: AlbumAction.groupByAlbum,
|
||||
child: MenuRow(text: 'Debug', icon: Icons.whatshot),
|
||||
),
|
||||
],
|
||||
onSelected: (action) => onActionSelected(context, action),
|
||||
),
|
||||
],
|
||||
floating: true,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
onActionSelected(BuildContext context, AlbumAction action) {
|
||||
switch (action) {
|
||||
case AlbumAction.groupByAlbum:
|
||||
collection.group(GroupFactor.album);
|
||||
break;
|
||||
case AlbumAction.groupByDate:
|
||||
collection.group(GroupFactor.date);
|
||||
break;
|
||||
case AlbumAction.debug:
|
||||
goToDebug(context);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Future goToDebug(BuildContext context) {
|
||||
return Navigator.push(
|
||||
context,
|
||||
|
@ -41,3 +73,5 @@ class AllCollectionPage extends StatelessWidget {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
enum AlbumAction { groupByDate, groupByAlbum, debug }
|
||||
|
|
77
lib/widgets/album/sections.dart
Normal file
77
lib/widgets/album/sections.dart
Normal file
|
@ -0,0 +1,77 @@
|
|||
import 'package:aves/utils/date_utils.dart';
|
||||
import 'package:aves/widgets/common/outlined_text.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
class DaySectionHeader extends StatelessWidget {
|
||||
final String text;
|
||||
|
||||
DaySectionHeader({Key key, DateTime date})
|
||||
: text = formatDate(date),
|
||||
super(key: key);
|
||||
|
||||
static DateFormat md = DateFormat.MMMMd();
|
||||
static DateFormat ymd = DateFormat.yMMMMd();
|
||||
|
||||
static formatDate(DateTime date) {
|
||||
if (isToday(date)) return 'Today';
|
||||
if (isThisYear(date)) return md.format(date);
|
||||
return ymd.format(date);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SectionHeader(text: text);
|
||||
}
|
||||
}
|
||||
|
||||
class MonthSectionHeader extends StatelessWidget {
|
||||
final String text;
|
||||
|
||||
MonthSectionHeader({Key key, DateTime date})
|
||||
: text = formatDate(date),
|
||||
super(key: key);
|
||||
|
||||
static DateFormat m = DateFormat.MMMM();
|
||||
static DateFormat ym = DateFormat.yMMMM();
|
||||
|
||||
static formatDate(DateTime date) {
|
||||
if (isThisMonth(date)) return 'This month';
|
||||
if (isThisYear(date)) return m.format(date);
|
||||
return ym.format(date);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SectionHeader(text: text);
|
||||
}
|
||||
}
|
||||
|
||||
class SectionHeader extends StatelessWidget {
|
||||
final String text;
|
||||
|
||||
const SectionHeader({Key key, this.text}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: EdgeInsets.all(16),
|
||||
child: OutlinedText(
|
||||
text,
|
||||
style: TextStyle(
|
||||
color: Colors.grey[200],
|
||||
fontSize: 20,
|
||||
shadows: [
|
||||
Shadow(
|
||||
offset: Offset(0, 2),
|
||||
blurRadius: 3,
|
||||
color: Colors.grey[900],
|
||||
),
|
||||
],
|
||||
),
|
||||
outlineColor: Colors.black87,
|
||||
outlineWidth: 2,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,14 +1,11 @@
|
|||
import 'package:aves/model/image_collection.dart';
|
||||
import 'package:aves/model/image_entry.dart';
|
||||
import 'package:aves/utils/date_utils.dart';
|
||||
import 'package:aves/widgets/album/sections.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/fullscreen/image_page.dart';
|
||||
import "package:collection/collection.dart";
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_sticky_header/flutter_sticky_header.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
class ThumbnailCollection extends AnimatedWidget {
|
||||
final ImageCollection collection;
|
||||
|
@ -33,14 +30,14 @@ class ThumbnailCollectionContent extends StatelessWidget {
|
|||
final ImageCollection collection;
|
||||
final Widget appBar;
|
||||
|
||||
final Map<DateTime, List<ImageEntry>> _sections;
|
||||
final Map<dynamic, List<ImageEntry>> _sections;
|
||||
final ScrollController _scrollController = ScrollController();
|
||||
|
||||
ThumbnailCollectionContent({
|
||||
Key key,
|
||||
this.collection,
|
||||
this.appBar,
|
||||
}) : _sections = groupBy(collection.entries, (entry) => entry.monthTaken),
|
||||
}) : _sections = collection.sections,
|
||||
super(key: key);
|
||||
|
||||
@override
|
||||
|
@ -82,8 +79,8 @@ class ThumbnailCollectionContent extends StatelessWidget {
|
|||
|
||||
class SectionSliver extends StatelessWidget {
|
||||
final ImageCollection collection;
|
||||
final Map<DateTime, List<ImageEntry>> sections;
|
||||
final DateTime sectionKey;
|
||||
final Map<dynamic, List<ImageEntry>> sections;
|
||||
final dynamic sectionKey;
|
||||
|
||||
const SectionSliver({
|
||||
Key key,
|
||||
|
@ -97,7 +94,7 @@ class SectionSliver extends StatelessWidget {
|
|||
// debugPrint('$runtimeType build with sectionKey=$sectionKey');
|
||||
final columnCount = 4;
|
||||
return SliverStickyHeader(
|
||||
header: MonthSectionHeader(date: sectionKey),
|
||||
header: collection.groupFactor == GroupFactor.date ? MonthSectionHeader(date: sectionKey) : SectionHeader(text: sectionKey),
|
||||
sliver: SliverGrid(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(sliverContext, index) {
|
||||
|
@ -135,76 +132,3 @@ class SectionSliver extends StatelessWidget {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
class DaySectionHeader extends StatelessWidget {
|
||||
final String text;
|
||||
|
||||
DaySectionHeader({Key key, DateTime date})
|
||||
: text = formatDate(date),
|
||||
super(key: key);
|
||||
|
||||
static DateFormat md = DateFormat.MMMMd();
|
||||
static DateFormat ymd = DateFormat.yMMMMd();
|
||||
|
||||
static formatDate(DateTime date) {
|
||||
if (isToday(date)) return 'Today';
|
||||
if (isThisYear(date)) return md.format(date);
|
||||
return ymd.format(date);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SectionHeader(text: text);
|
||||
}
|
||||
}
|
||||
|
||||
class MonthSectionHeader extends StatelessWidget {
|
||||
final String text;
|
||||
|
||||
MonthSectionHeader({Key key, DateTime date})
|
||||
: text = formatDate(date),
|
||||
super(key: key);
|
||||
|
||||
static DateFormat m = DateFormat.MMMM();
|
||||
static DateFormat ym = DateFormat.yMMMM();
|
||||
|
||||
static formatDate(DateTime date) {
|
||||
if (isThisMonth(date)) return 'This month';
|
||||
if (isThisYear(date)) return m.format(date);
|
||||
return ym.format(date);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SectionHeader(text: text);
|
||||
}
|
||||
}
|
||||
|
||||
class SectionHeader extends StatelessWidget {
|
||||
final String text;
|
||||
|
||||
const SectionHeader({Key key, this.text}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: EdgeInsets.all(16),
|
||||
child: OutlinedText(
|
||||
text,
|
||||
style: TextStyle(
|
||||
color: Colors.grey[200],
|
||||
fontSize: 20,
|
||||
shadows: [
|
||||
Shadow(
|
||||
offset: Offset(0, 2),
|
||||
blurRadius: 3,
|
||||
color: Colors.grey[900],
|
||||
),
|
||||
],
|
||||
),
|
||||
outlineColor: Colors.black87,
|
||||
outlineWidth: 2,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
34
lib/widgets/common/menu_row.dart
Normal file
34
lib/widgets/common/menu_row.dart
Normal file
|
@ -0,0 +1,34 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
class MenuRow extends StatelessWidget {
|
||||
final String text;
|
||||
final IconData icon;
|
||||
final bool checked;
|
||||
|
||||
const MenuRow({
|
||||
Key key,
|
||||
this.text,
|
||||
this.icon,
|
||||
this.checked,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
children: [
|
||||
if (checked != null) ...[
|
||||
Opacity(
|
||||
opacity: checked ? 1 : 0,
|
||||
child: Icon(Icons.done),
|
||||
),
|
||||
SizedBox(width: 8),
|
||||
],
|
||||
if (icon != null) ...[
|
||||
Icon(icon),
|
||||
SizedBox(width: 8),
|
||||
],
|
||||
Text(text),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
import 'package:aves/model/image_entry.dart';
|
||||
import 'package:aves/widgets/common/blurred.dart';
|
||||
import 'package:aves/widgets/common/menu_row.dart';
|
||||
import 'package:aves/widgets/fullscreen/image_page.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
|
@ -99,28 +100,6 @@ class FullscreenTopOverlay extends StatelessWidget {
|
|||
}
|
||||
}
|
||||
|
||||
class MenuRow extends StatelessWidget {
|
||||
final String text;
|
||||
final IconData icon;
|
||||
|
||||
const MenuRow({
|
||||
Key key,
|
||||
this.text,
|
||||
this.icon,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
children: [
|
||||
Icon(icon),
|
||||
SizedBox(width: 8),
|
||||
Text(text),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class OverlayButton extends StatelessWidget {
|
||||
final Animation<double> scale;
|
||||
final Widget child;
|
||||
|
|
Loading…
Reference in a new issue