diff --git a/lib/db/db_helper.dart b/lib/db/db_helper.dart index 3423a71d8..4c7be2226 100644 --- a/lib/db/db_helper.dart +++ b/lib/db/db_helper.dart @@ -60,12 +60,12 @@ class DatabaseHelper { } Future insertPhoto(Photo photo) async { - Database db = await instance.database; + final db = await instance.database; return await db.insert(table, _getRowForPhoto(photo)); } Future> insertPhotos(List photos) async { - Database db = await instance.database; + final db = await instance.database; var batch = db.batch(); int batchCounter = 0; for (Photo photo in photos) { @@ -80,40 +80,56 @@ class DatabaseHelper { } Future> getAllPhotos() async { - Database db = await instance.database; - var results = await db.query(table, - where: '$columnIsDeleted = 0', orderBy: '$columnCreateTimestamp DESC'); + final db = await instance.database; + final results = await db.query( + table, + where: '$columnIsDeleted = 0', + orderBy: '$columnCreateTimestamp DESC', + ); return _convertToPhotos(results); } Future> getAllDeletedPhotos() async { - Database db = await instance.database; - var results = await db.query(table, - where: '$columnIsDeleted = 1', orderBy: '$columnCreateTimestamp DESC'); + final db = await instance.database; + final results = await db.query( + table, + where: '$columnIsDeleted = 1', + orderBy: '$columnCreateTimestamp DESC', + ); return _convertToPhotos(results); } Future> getPhotosToBeUploaded() async { - Database db = await instance.database; - var results = await db.query(table, where: '$columnUploadedFileId = -1'); + final db = await instance.database; + final results = await db.query( + table, + where: '$columnUploadedFileId = -1', + ); return _convertToPhotos(results); } Future updatePhoto( int generatedId, String remotePath, int syncTimestamp) async { - Database db = await instance.database; - var values = new Map(); + final db = await instance.database; + final values = new Map(); values[columnRemotePath] = remotePath; values[columnSyncTimestamp] = syncTimestamp; - return await db.update(table, values, - where: '$columnGeneratedId = ?', whereArgs: [generatedId]); + return await db.update( + table, + values, + where: '$columnGeneratedId = ?', + whereArgs: [generatedId], + ); } Future getPhotoByPath(String path) async { - Database db = await instance.database; - var rows = - await db.query(table, where: '$columnRemotePath =?', whereArgs: [path]); - if (rows.length > 0) { + final db = await instance.database; + final rows = await db.query( + table, + where: '$columnRemotePath =?', + whereArgs: [path], + ); + if (rows.isNotEmpty) { return _getPhotofromRow(rows[0]); } else { throw ("No cached photo"); @@ -121,17 +137,70 @@ class DatabaseHelper { } Future markPhotoForDeletion(Photo photo) async { - Database db = await instance.database; + final db = await instance.database; var values = new Map(); values[columnIsDeleted] = 1; - return db.update(table, values, - where: '$columnGeneratedId =?', whereArgs: [photo.generatedId]); + return db.update( + table, + values, + where: '$columnGeneratedId =?', + whereArgs: [photo.generatedId], + ); } Future deletePhoto(Photo photo) async { - Database db = await instance.database; - return db.delete(table, - where: '$columnGeneratedId =?', whereArgs: [photo.generatedId]); + final db = await instance.database; + return db.delete( + table, + where: '$columnGeneratedId =?', + whereArgs: [photo.generatedId], + ); + } + + Future> getDistinctPaths() async { + final db = await instance.database; + final rows = await db.query( + table, + columns: [columnPathName], + distinct: true, + ); + List result = List(); + for (final row in rows) { + result.add(row[columnPathName]); + } + return result; + } + + Future getLatestPhotoInPath(String path) async { + final db = await instance.database; + var rows = await db.query( + table, + where: '$columnPathName =?', + whereArgs: [path], + orderBy: '$columnCreateTimestamp DESC', + limit: 1, + ); + if (rows.isNotEmpty) { + return _getPhotofromRow(rows[0]); + } else { + throw ("No photo found in path"); + } + } + + Future getLatestPhotoAmongGeneratedIds( + List generatedIds) async { + final db = await instance.database; + var rows = await db.query( + table, + where: '$columnGeneratedId IN (${generatedIds.join(",")})', + orderBy: '$columnCreateTimestamp DESC', + limit: 1, + ); + if (rows.isNotEmpty) { + return _getPhotofromRow(rows[0]); + } else { + throw ("No photo found with ids " + generatedIds.join(", ").toString()); + } } List _convertToPhotos(List> results) { diff --git a/lib/favorite_photos_repository.dart b/lib/favorite_photos_repository.dart index 2ba3bad8c..23e66e3bf 100644 --- a/lib/favorite_photos_repository.dart +++ b/lib/favorite_photos_repository.dart @@ -14,11 +14,15 @@ class FavoritePhotosRepository { } bool isLiked(Photo photo) { - return _getLiked().contains(photo.generatedId.toString()); + return getLiked().contains(photo.generatedId.toString()); + } + + bool hasFavorites() { + return getLiked().isNotEmpty; } Future setLiked(Photo photo, bool isLiked) { - final liked = _getLiked(); + final liked = getLiked(); if (isLiked) { liked.add(photo.generatedId.toString()); } else { @@ -29,7 +33,7 @@ class FavoritePhotosRepository { .then((_) => isLiked); } - Set _getLiked() { + Set getLiked() { final value = _preferences.getStringList(_favoritePhotoIdsKey); if (value == null) { return Set(); diff --git a/lib/models/album.dart b/lib/models/album.dart index 315dc7fe9..89e119406 100644 --- a/lib/models/album.dart +++ b/lib/models/album.dart @@ -3,8 +3,8 @@ import 'package:photos/models/photo.dart'; class Album { final String name; - final List photos; + final Photo thumbnailPhoto; final GalleryItemsFilter filter; - Album(this.name, this.photos, this.filter); + Album(this.name, this.thumbnailPhoto, this.filter); } diff --git a/lib/ui/album_list_widget.dart b/lib/ui/album_list_widget.dart index ca09f9367..7fa85c841 100644 --- a/lib/ui/album_list_widget.dart +++ b/lib/ui/album_list_widget.dart @@ -2,14 +2,16 @@ import 'dart:collection'; import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; +import 'package:photos/db/db_helper.dart'; import 'package:photos/favorite_photos_repository.dart'; import 'package:photos/models/album.dart'; import 'package:photos/models/filters/favorite_items_filter.dart'; import 'package:photos/models/filters/folder_name_filter.dart'; import 'package:photos/models/photo.dart'; import 'package:photos/ui/album_page.dart'; +import 'package:photos/ui/loading_widget.dart'; import 'package:photos/ui/thumbnail_widget.dart'; -import 'package:path/path.dart' as path; +import 'package:path/path.dart' as p; class AlbumListWidget extends StatefulWidget { final List photos; @@ -23,8 +25,21 @@ class AlbumListWidget extends StatefulWidget { class _AlbumListWidgetState extends State { @override Widget build(BuildContext context) { - List albums = _getAlbums(widget.photos); + return FutureBuilder( + future: _getAlbums(), + builder: (context, snapshot) { + if (snapshot.hasData) { + return _getAlbumListWidget(snapshot.data); + } else if (snapshot.hasError) { + return Text(snapshot.error.toString()); + } else { + return loadWidget; + } + }, + ); + } + Widget _getAlbumListWidget(List albums) { return Container( margin: EdgeInsets.only(top: 24), child: GridView.builder( @@ -42,27 +57,23 @@ class _AlbumListWidgetState extends State { ); } - List _getAlbums(List photos) { - final albumMap = new LinkedHashMap>(); - final favorites = Album("Favorites", List(), FavoriteItemsFilter()); - for (Photo photo in photos) { - final folder = path.basename(photo.pathName); - if (!albumMap.containsKey(folder)) { - albumMap[folder] = new List(); - } - albumMap[folder].add(photo); - - if (FavoritePhotosRepository.instance.isLiked(photo)) { - favorites.photos.add(photo); - } + Future> _getAlbums() async { + final paths = await DatabaseHelper.instance.getDistinctPaths(); + final albums = List(); + for (final path in paths) { + final photo = await DatabaseHelper.instance.getLatestPhotoInPath(path); + final albumName = p.basename(path); + albums.add(Album(albumName, photo, FolderNameFilter(albumName))); } - List albums = new List(); - if (favorites.photos.isNotEmpty) { - albums.add(favorites); - } - for (String albumName in albumMap.keys) { - albums.add( - Album(albumName, albumMap[albumName], FolderNameFilter(albumName))); + albums.sort((firstAlbum, secondAlbum) { + return secondAlbum.thumbnailPhoto.createTimestamp + .compareTo(firstAlbum.thumbnailPhoto.createTimestamp); + }); + if (FavoritePhotosRepository.instance.hasFavorites()) { + final photo = await DatabaseHelper.instance + .getLatestPhotoAmongGeneratedIds( + FavoritePhotosRepository.instance.getLiked().toList()); + albums.insert(0, Album("Favorites", photo, FavoriteItemsFilter())); } return albums; } @@ -72,7 +83,7 @@ class _AlbumListWidgetState extends State { child: Column( children: [ Container( - child: ThumbnailWidget(album.photos[0]), + child: ThumbnailWidget(album.thumbnailPhoto), height: 150, width: 150, ),