diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 5805b603c..ce07b4cc5 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -62,6 +62,10 @@ PODS: - sqflite (0.0.1): - Flutter - FMDB (~> 2.7.2) + - video_player (0.0.1): + - Flutter + - video_player_web (0.0.1): + - Flutter DEPENDENCIES: - connectivity (from `.symlinks/plugins/connectivity/ios`) @@ -84,6 +88,8 @@ DEPENDENCIES: - shared_preferences_macos (from `.symlinks/plugins/shared_preferences_macos/ios`) - shared_preferences_web (from `.symlinks/plugins/shared_preferences_web/ios`) - sqflite (from `.symlinks/plugins/sqflite/ios`) + - video_player (from `.symlinks/plugins/video_player/ios`) + - video_player_web (from `.symlinks/plugins/video_player_web/ios`) SPEC REPOS: trunk: @@ -135,6 +141,10 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/shared_preferences_web/ios" sqflite: :path: ".symlinks/plugins/sqflite/ios" + video_player: + :path: ".symlinks/plugins/video_player/ios" + video_player_web: + :path: ".symlinks/plugins/video_player_web/ios" SPEC CHECKSUMS: connectivity: 6e94255659cc86dcbef1d452ad3e0491bb1b3e75 @@ -163,6 +173,8 @@ SPEC CHECKSUMS: shared_preferences_macos: f3f29b71ccbb56bf40c9dd6396c9acf15e214087 shared_preferences_web: 141cce0c3ed1a1c5bf2a0e44f52d31eeb66e5ea9 sqflite: 4001a31ff81d210346b500c55b17f4d6c7589dd0 + video_player: 9cc823b1d9da7e8427ee591e8438bfbcde500e6e + video_player_web: da8cadb8274ed4f8dbee8d7171b420dedd437ce7 PODFILE CHECKSUM: dc81df99923cb3d9115f3b6c3d7e730abfec780b diff --git a/lib/core/cache/image_cache.dart b/lib/core/cache/image_cache.dart index 8d63b69fd..c9b178f5b 100644 --- a/lib/core/cache/image_cache.dart +++ b/lib/core/cache/image_cache.dart @@ -1,29 +1,29 @@ -import 'dart:io'; +import 'dart:io' as dart; import 'dart:typed_data'; import 'package:photos/core/cache/lru_map.dart'; -import 'package:photos/models/photo.dart'; +import 'package:photos/models/file.dart'; class FileLruCache { - static LRUMap _map = LRUMap(25); + static LRUMap _map = LRUMap(25); - static File get(Photo photo) { - return _map.get(photo.hashCode); + static dart.File get(File file) { + return _map.get(file.hashCode); } - static void put(Photo photo, File imageData) { - _map.put(photo.hashCode, imageData); + static void put(File file, dart.File imageData) { + _map.put(file.hashCode, imageData); } } class BytesLruCache { static LRUMap _map = LRUMap(25); - static Uint8List get(Photo photo) { - return _map.get(photo.hashCode); + static Uint8List get(File file) { + return _map.get(file.hashCode); } - static void put(Photo photo, Uint8List imageData) { - _map.put(photo.hashCode, imageData); + static void put(File file, Uint8List imageData) { + _map.put(file.hashCode, imageData); } } diff --git a/lib/core/cache/thumbnail_cache.dart b/lib/core/cache/thumbnail_cache.dart index 47252c23b..71748863b 100644 --- a/lib/core/cache/thumbnail_cache.dart +++ b/lib/core/cache/thumbnail_cache.dart @@ -1,22 +1,22 @@ import 'dart:typed_data'; import 'package:photos/core/cache/lru_map.dart'; -import 'package:photos/models/photo.dart'; +import 'package:photos/models/file.dart'; class ThumbnailLruCache { static LRUMap<_ThumbnailCacheKey, Uint8List> _map = LRUMap(1000); - static Uint8List get(Photo photo, int size) { + static Uint8List get(File photo, int size) { return _map.get(_ThumbnailCacheKey(photo, size)); } - static void put(Photo photo, int size, Uint8List imageData) { + static void put(File photo, int size, Uint8List imageData) { _map.put(_ThumbnailCacheKey(photo, size), imageData); } } class _ThumbnailCacheKey { - Photo photo; + File photo; int size; _ThumbnailCacheKey(this.photo, this.size); diff --git a/lib/db/photo_db.dart b/lib/db/photo_db.dart index cae5bad6d..25c3e7b30 100644 --- a/lib/db/photo_db.dart +++ b/lib/db/photo_db.dart @@ -1,20 +1,21 @@ import 'dart:io'; import 'package:logging/logging.dart'; +import 'package:photos/models/file_type.dart'; import 'package:photos/models/location.dart'; -import 'package:photos/models/photo.dart'; +import 'package:photos/models/file.dart'; import 'package:path/path.dart'; import 'package:sqflite/sqflite.dart'; import 'package:path_provider/path_provider.dart'; -class PhotoDB { +class FileDB { // TODO: Use different tables within the same database - static final _databaseName = "ente.photos.db"; + static final _databaseName = "ente.files.db"; static final _databaseVersion = 1; - static final Logger _logger = Logger("PhotoDB"); + static final Logger _logger = Logger("FileDB"); - static final table = 'photos'; + static final table = 'files'; static final columnGeneratedId = '_id'; static final columnUploadedFileId = 'uploaded_file_id'; @@ -23,6 +24,7 @@ class PhotoDB { static final columnDeviceFolder = 'device_folder'; static final columnLatitude = 'latitude'; static final columnLongitude = 'longitude'; + static final columnFileType = 'file_type'; static final columnRemoteFolderId = 'remote_folder_id'; static final columnRemotePath = 'remote_path'; static final columnThumbnailPath = 'thumbnail_path'; @@ -31,8 +33,8 @@ class PhotoDB { static final columnUpdateTimestamp = 'update_timestamp'; // make this a singleton class - PhotoDB._privateConstructor(); - static final PhotoDB instance = PhotoDB._privateConstructor(); + FileDB._privateConstructor(); + static final FileDB instance = FileDB._privateConstructor(); // only have a single app-wide reference to the database static Database _database; @@ -62,6 +64,7 @@ class PhotoDB { $columnDeviceFolder TEXT NOT NULL, $columnLatitude REAL, $columnLongitude REAL, + $columnFileType INTEGER, $columnRemoteFolderId INTEGER, $columnRemotePath TEXT, $columnThumbnailPath TEXT, @@ -72,37 +75,37 @@ class PhotoDB { '''); } - Future insertPhoto(Photo photo) async { + Future insert(File file) async { final db = await instance.database; - return await db.insert(table, _getRowForPhoto(photo)); + return await db.insert(table, _getRowForFile(file)); } - Future> insertPhotos(List photos) async { + Future> insertMultiple(List files) async { final db = await instance.database; var batch = db.batch(); int batchCounter = 0; - for (Photo photo in photos) { + for (File file in files) { if (batchCounter == 400) { await batch.commit(); batch = db.batch(); } - batch.insert(table, _getRowForPhoto(photo)); + batch.insert(table, _getRowForFile(file)); batchCounter++; } return await batch.commit(); } - Future> getAllPhotos() async { + Future> getAll() async { final db = await instance.database; final results = await db.query( table, where: '$columnLocalId IS NOT NULL AND $columnIsDeleted = 0', orderBy: '$columnCreateTimestamp DESC', ); - return _convertToPhotos(results); + return _convertToFiles(results); } - Future> getAllPhotosInFolder(int folderId) async { + Future> getAllInFolder(int folderId) async { final db = await instance.database; final results = await db.query( table, @@ -110,30 +113,30 @@ class PhotoDB { whereArgs: [folderId], orderBy: '$columnCreateTimestamp DESC', ); - return _convertToPhotos(results); + return _convertToFiles(results); } - Future> getAllDeletedPhotos() async { + Future> getAllDeleted() async { final db = await instance.database; final results = await db.query( table, where: '$columnIsDeleted = 1', orderBy: '$columnCreateTimestamp DESC', ); - return _convertToPhotos(results); + return _convertToFiles(results); } - Future> getPhotosToBeUploaded() async { + Future> getFilesToBeUploaded() async { final db = await instance.database; final results = await db.query( table, where: '$columnUploadedFileId IS NULL', orderBy: '$columnCreateTimestamp DESC', ); - return _convertToPhotos(results); + return _convertToFiles(results); } - Future getMatchingPhoto( + Future getMatchingFile( String localId, String title, String deviceFolder, int createTimestamp, {String alternateTitle}) async { final db = await instance.database; @@ -150,13 +153,13 @@ class PhotoDB { ], ); if (rows.isNotEmpty) { - return _getPhotoFromRow(rows[0]); + return _getFileFromRow(rows[0]); } else { - throw ("No matching photo found"); + throw ("No matching file found"); } } - Future getMatchingRemotePhoto(int uploadedFileId) async { + Future getMatchingRemoteFile(int uploadedFileId) async { final db = await instance.database; final rows = await db.query( table, @@ -164,13 +167,13 @@ class PhotoDB { whereArgs: [uploadedFileId], ); if (rows.isNotEmpty) { - return _getPhotoFromRow(rows[0]); + return _getFileFromRow(rows[0]); } else { - throw ("No matching photo found"); + throw ("No matching file found"); } } - Future updatePhoto( + Future update( int generatedId, int uploadedId, String remotePath, int updateTimestamp, [String thumbnailPath]) async { final db = await instance.database; @@ -187,22 +190,8 @@ class PhotoDB { ); } - Future getPhotoByPath(String path) async { - 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"); - } - } - - // TODO: Remove deleted photos on remote - Future markPhotoForDeletion(Photo photo) async { + // TODO: Remove deleted files on remote + Future markForDeletion(File file) async { final db = await instance.database; var values = new Map(); values[columnIsDeleted] = 1; @@ -210,20 +199,20 @@ class PhotoDB { table, values, where: '$columnGeneratedId =?', - whereArgs: [photo.generatedId], + whereArgs: [file.generatedId], ); } - Future deletePhoto(Photo photo) async { + Future delete(File file) async { final db = await instance.database; return db.delete( table, where: '$columnGeneratedId =?', - whereArgs: [photo.generatedId], + whereArgs: [file.generatedId], ); } - Future deletePhotosInRemoteFolder(int folderId) async { + Future deleteFilesInRemoteFolder(int folderId) async { final db = await instance.database; return db.delete( table, @@ -247,7 +236,7 @@ class PhotoDB { return result; } - Future getLatestPhotoInPath(String path) async { + Future getLatestFileInPath(String path) async { final db = await instance.database; var rows = await db.query( table, @@ -257,13 +246,13 @@ class PhotoDB { limit: 1, ); if (rows.isNotEmpty) { - return _getPhotoFromRow(rows[0]); + return _getFileFromRow(rows[0]); } else { - throw ("No photo found in path"); + throw ("No file found in path"); } } - Future getLatestPhotoInRemoteFolder(int folderId) async { + Future getLatestFileInRemoteFolder(int folderId) async { final db = await instance.database; var rows = await db.query( table, @@ -273,13 +262,13 @@ class PhotoDB { limit: 1, ); if (rows.isNotEmpty) { - return _getPhotoFromRow(rows[0]); + return _getFileFromRow(rows[0]); } else { - throw ("No photo found in remote folder " + folderId.toString()); + throw ("No file found in remote folder " + folderId.toString()); } } - Future getLastSyncedPhotoInRemoteFolder(int folderId) async { + Future getLastSyncedFileInRemoteFolder(int folderId) async { final db = await instance.database; var rows = await db.query( table, @@ -289,14 +278,13 @@ class PhotoDB { limit: 1, ); if (rows.isNotEmpty) { - return _getPhotoFromRow(rows[0]); + return _getFileFromRow(rows[0]); } else { - throw ("No photo found in remote folder " + folderId.toString()); + throw ("No file found in remote folder " + folderId.toString()); } } - Future getLatestPhotoAmongGeneratedIds( - List generatedIds) async { + Future getLatestFileAmongGeneratedIds(List generatedIds) async { final db = await instance.database; var rows = await db.query( table, @@ -305,55 +293,66 @@ class PhotoDB { limit: 1, ); if (rows.isNotEmpty) { - return _getPhotoFromRow(rows[0]); + return _getFileFromRow(rows[0]); } else { - throw ("No photo found with ids " + generatedIds.join(", ").toString()); + throw ("No file found with ids " + generatedIds.join(", ").toString()); } } - List _convertToPhotos(List> results) { - var photos = List(); + List _convertToFiles(List> results) { + var files = List(); for (var result in results) { - photos.add(_getPhotoFromRow(result)); + files.add(_getFileFromRow(result)); } - return photos; + return files; } - Map _getRowForPhoto(Photo photo) { + Map _getRowForFile(File file) { var row = new Map(); - row[columnLocalId] = photo.localId; - row[columnUploadedFileId] = photo.uploadedFileId; - row[columnTitle] = photo.title; - row[columnDeviceFolder] = photo.deviceFolder; - if (photo.location != null) { - row[columnLatitude] = photo.location.latitude; - row[columnLongitude] = photo.location.longitude; + row[columnLocalId] = file.localId; + row[columnUploadedFileId] = file.uploadedFileId; + row[columnTitle] = file.title; + row[columnDeviceFolder] = file.deviceFolder; + if (file.location != null) { + row[columnLatitude] = file.location.latitude; + row[columnLongitude] = file.location.longitude; } - row[columnRemoteFolderId] = photo.remoteFolderId; - row[columnRemotePath] = photo.remotePath; - row[columnThumbnailPath] = photo.thumbnailPath; - row[columnCreateTimestamp] = photo.createTimestamp; - row[columnUpdateTimestamp] = photo.updateTimestamp; + switch (file.fileType) { + case FileType.image: + row[columnFileType] = 0; + break; + case FileType.video: + row[columnFileType] = 1; + break; + default: + row[columnFileType] = -1; + } + row[columnRemoteFolderId] = file.remoteFolderId; + row[columnRemotePath] = file.remotePath; + row[columnThumbnailPath] = file.previewURL; + row[columnCreateTimestamp] = file.createTimestamp; + row[columnUpdateTimestamp] = file.updateTimestamp; return row; } - Photo _getPhotoFromRow(Map row) { - Photo photo = Photo(); - photo.generatedId = row[columnGeneratedId]; - photo.localId = row[columnLocalId]; - photo.uploadedFileId = row[columnUploadedFileId]; - photo.title = row[columnTitle]; - photo.deviceFolder = row[columnDeviceFolder]; + File _getFileFromRow(Map row) { + File file = File(); + file.generatedId = row[columnGeneratedId]; + file.localId = row[columnLocalId]; + file.uploadedFileId = row[columnUploadedFileId]; + file.title = row[columnTitle]; + file.deviceFolder = row[columnDeviceFolder]; if (row[columnLatitude] != null && row[columnLongitude] != null) { - photo.location = Location(row[columnLatitude], row[columnLongitude]); + file.location = Location(row[columnLatitude], row[columnLongitude]); } - photo.remoteFolderId = row[columnRemoteFolderId]; - photo.remotePath = row[columnRemotePath]; - photo.thumbnailPath = row[columnThumbnailPath]; - photo.createTimestamp = int.parse(row[columnCreateTimestamp]); - photo.updateTimestamp = row[columnUpdateTimestamp] == null + file.fileType = getFileType(row[columnFileType]); + file.remoteFolderId = row[columnRemoteFolderId]; + file.remotePath = row[columnRemotePath]; + file.previewURL = row[columnThumbnailPath]; + file.createTimestamp = int.parse(row[columnCreateTimestamp]); + file.updateTimestamp = row[columnUpdateTimestamp] == null ? -1 : int.parse(row[columnUpdateTimestamp]); - return photo; + return file; } } diff --git a/lib/face_search_manager.dart b/lib/face_search_manager.dart index cbf14c1d4..1cb972f93 100644 --- a/lib/face_search_manager.dart +++ b/lib/face_search_manager.dart @@ -4,7 +4,7 @@ import 'package:photos/db/photo_db.dart'; import 'package:logging/logging.dart'; import 'package:photos/models/face.dart'; -import 'package:photos/models/photo.dart'; +import 'package:photos/models/file.dart'; import 'package:photos/utils/file_name_util.dart'; class FaceSearchManager { @@ -28,7 +28,7 @@ class FaceSearchManager { .catchError(_onError); } - Future> getFaceSearchResults(Face face) async { + Future> getFaceSearchResults(Face face) async { final result = await _dio .get( Configuration.instance.getHttpEndpoint() + @@ -42,24 +42,24 @@ class FaceSearchManager { ) .then((response) { return (response.data["result"] as List) - .map((p) => Photo.fromJson(p)) + .map((p) => File.fromJson(p)) .toList(); }).catchError(_onError); - final photos = List(); - for (Photo photo in result) { + final files = List(); + for (File file in result) { try { - photos.add(await PhotoDB.instance.getMatchingPhoto(photo.localId, - photo.title, photo.deviceFolder, photo.createTimestamp, - alternateTitle: getHEICFileNameForJPG(photo))); + files.add(await FileDB.instance.getMatchingFile(file.localId, + file.title, file.deviceFolder, file.createTimestamp, + alternateTitle: getHEICFileNameForJPG(file))); } catch (e) { // Not available locally - photos.add(photo); + files.add(file); } } - photos.sort((first, second) { + files.sort((first, second) { return second.createTimestamp.compareTo(first.createTimestamp); }); - return photos; + return files; } void _onError(error) { diff --git a/lib/favorite_photos_repository.dart b/lib/favorite_photos_repository.dart index 09fd6f7b4..44aa06265 100644 --- a/lib/favorite_photos_repository.dart +++ b/lib/favorite_photos_repository.dart @@ -1,13 +1,13 @@ import 'package:photos/core/event_bus.dart'; import 'package:photos/events/local_photos_updated_event.dart'; -import 'package:photos/models/photo.dart'; +import 'package:photos/models/file.dart'; import 'package:shared_preferences/shared_preferences.dart'; -class FavoritePhotosRepository { +class FavoriteFilesRepository { static final _favoritePhotoIdsKey = "favorite_photo_ids"; - FavoritePhotosRepository._privateConstructor(); - static FavoritePhotosRepository instance = - FavoritePhotosRepository._privateConstructor(); + FavoriteFilesRepository._privateConstructor(); + static FavoriteFilesRepository instance = + FavoriteFilesRepository._privateConstructor(); SharedPreferences _preferences; @@ -15,7 +15,7 @@ class FavoritePhotosRepository { _preferences = await SharedPreferences.getInstance(); } - bool isLiked(Photo photo) { + bool isLiked(File photo) { return getLiked().contains(photo.generatedId.toString()); } @@ -23,7 +23,7 @@ class FavoritePhotosRepository { return getLiked().isNotEmpty; } - Future setLiked(Photo photo, bool isLiked) { + Future setLiked(File photo, bool isLiked) { final liked = getLiked(); if (isLiked) { liked.add(photo.generatedId.toString()); diff --git a/lib/file_repository.dart b/lib/file_repository.dart new file mode 100644 index 000000000..0b856cc1d --- /dev/null +++ b/lib/file_repository.dart @@ -0,0 +1,33 @@ +import 'package:logging/logging.dart'; +import 'package:photos/core/event_bus.dart'; +import 'package:photos/db/photo_db.dart'; +import 'package:photos/events/local_photos_updated_event.dart'; +import 'package:photos/models/file.dart'; + +class FileRepository { + final _logger = Logger("PhotoRepository"); + final _files = List(); + + FileRepository._privateConstructor(); + static final FileRepository instance = FileRepository._privateConstructor(); + + List get files { + return _files; + } + + Future loadFiles() async { + FileDB db = FileDB.instance; + var files = await db.getAll(); + + _files.clear(); + _files.addAll(files); + + return true; + } + + Future reloadFiles() async { + _logger.info("Reloading..."); + await loadFiles(); + Bus.instance.fire(LocalPhotosUpdatedEvent()); + } +} diff --git a/lib/folder_service.dart b/lib/folder_service.dart index 13c3755f2..280be0d10 100644 --- a/lib/folder_service.dart +++ b/lib/folder_service.dart @@ -8,7 +8,7 @@ import 'package:photos/db/photo_db.dart'; import 'package:photos/events/remote_sync_event.dart'; import 'package:photos/events/user_authenticated_event.dart'; import 'package:photos/models/folder.dart'; -import 'package:photos/models/photo.dart'; +import 'package:photos/models/file.dart'; import 'core/event_bus.dart'; @@ -39,7 +39,7 @@ class FolderSharingService { for (final currentFolder in currentFolders) { if (!folders.contains(currentFolder)) { _logger.info("Folder deleted: " + currentFolder.toString()); - await PhotoDB.instance.deletePhotosInRemoteFolder(currentFolder.id); + await FileDB.instance.deleteFilesInRemoteFolder(currentFolder.id); await FolderDB.instance.deleteFolder(currentFolder); } } @@ -58,25 +58,25 @@ class FolderSharingService { Future syncDiff(Folder folder) async { int lastSyncTimestamp = 0; try { - Photo photo = - await PhotoDB.instance.getLastSyncedPhotoInRemoteFolder(folder.id); - lastSyncTimestamp = photo.updateTimestamp; + File file = + await FileDB.instance.getLastSyncedFileInRemoteFolder(folder.id); + lastSyncTimestamp = file.updateTimestamp; } catch (e) { // Folder has never been synced } var diff = await getDiff(folder.id, lastSyncTimestamp, _diffLimit); - for (Photo photo in diff) { + for (File file in diff) { try { var existingPhoto = - await PhotoDB.instance.getMatchingRemotePhoto(photo.uploadedFileId); - await PhotoDB.instance.updatePhoto( + await FileDB.instance.getMatchingRemoteFile(file.uploadedFileId); + await FileDB.instance.update( existingPhoto.generatedId, - photo.uploadedFileId, - photo.remotePath, - photo.updateTimestamp, - photo.thumbnailPath); + file.uploadedFileId, + file.remotePath, + file.updateTimestamp, + file.previewURL); } catch (e) { - await PhotoDB.instance.insertPhoto(photo); + await FileDB.instance.insert(file); } } if (diff.length == _diffLimit) { @@ -84,7 +84,7 @@ class FolderSharingService { } } - Future> getDiff( + Future> getDiff( int folderId, int sinceTimestamp, int limit) async { Response response = await _dio.get( Configuration.instance.getHttpEndpoint() + @@ -99,13 +99,13 @@ class FolderSharingService { ).catchError((e) => _logger.severe(e)); if (response != null) { return (response.data["diff"] as List).map((p) { - Photo photo = new Photo.fromJson(p); - photo.localId = null; - photo.remoteFolderId = folderId; - return photo; + File file = new File.fromJson(p); + file.localId = null; + file.remoteFolderId = folderId; + return file; }).toList(); } else { - return List(); + return List(); } } diff --git a/lib/main.dart b/lib/main.dart index fb1fa3bb0..56cfabd24 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -27,7 +27,7 @@ void _main() async { WidgetsFlutterBinding.ensureInitialized(); await Configuration.instance.init(); - FavoritePhotosRepository.instance.init(); + FavoriteFilesRepository.instance.init(); _sync(); final SentryClient sentry = new SentryClient(dsn: SENTRY_DSN); diff --git a/lib/models/device_folder.dart b/lib/models/device_folder.dart index 48104220b..35d9dc563 100644 --- a/lib/models/device_folder.dart +++ b/lib/models/device_folder.dart @@ -1,10 +1,10 @@ import 'package:photos/models/filters/gallery_items_filter.dart'; -import 'package:photos/models/photo.dart'; +import 'package:photos/models/file.dart'; class DeviceFolder { final String name; - final Photo thumbnailPhoto; + final File thumbnail; final GalleryItemsFilter filter; - DeviceFolder(this.name, this.thumbnailPhoto, this.filter); + DeviceFolder(this.name, this.thumbnail, this.filter); } diff --git a/lib/models/photo.dart b/lib/models/file.dart similarity index 55% rename from lib/models/photo.dart rename to lib/models/file.dart index 925aa3b7f..bc7db3197 100644 --- a/lib/models/photo.dart +++ b/lib/models/file.dart @@ -6,9 +6,10 @@ import 'package:flutter_image_compress/flutter_image_compress.dart'; import 'package:photo_manager/photo_manager.dart'; import 'package:path/path.dart'; import 'package:photos/core/configuration.dart'; +import 'package:photos/models/file_type.dart'; import 'package:photos/models/location.dart'; -class Photo { +class File { int generatedId; int uploadedFileId; String localId; @@ -16,43 +17,57 @@ class Photo { String deviceFolder; int remoteFolderId; String remotePath; - String thumbnailPath; + String previewURL; int createTimestamp; int updateTimestamp; Location location; + FileType fileType; - Photo(); - Photo.fromJson(Map json) - : uploadedFileId = json["fileID"], - localId = json["deviceFileID"], - deviceFolder = json["deviceFolder"], - title = json["title"], - remotePath = json["path"], - thumbnailPath = json["previewURL"], - createTimestamp = json["createTimestamp"], - updateTimestamp = json["updateTimestamp"]; + File(); + File.fromJson(Map json) { + uploadedFileId = json["fileID"]; + localId = json["deviceFileID"]; + deviceFolder = json["deviceFolder"]; + title = json["title"]; + fileType = getFileType(json["fileType"]); + remotePath = json["path"]; + previewURL = json["previewURL"]; + createTimestamp = json["createTimestamp"]; + updateTimestamp = json["updateTimestamp"]; + } - static Future fromAsset( + static Future fromAsset( AssetPathEntity pathEntity, AssetEntity asset) async { - Photo photo = Photo(); - photo.localId = asset.id; - photo.title = asset.title; - photo.deviceFolder = pathEntity.name; - photo.location = Location(asset.latitude, asset.longitude); - photo.createTimestamp = asset.createDateTime.microsecondsSinceEpoch; - if (photo.createTimestamp == 0) { + File file = File(); + file.localId = asset.id; + file.title = asset.title; + file.deviceFolder = pathEntity.name; + file.location = Location(asset.latitude, asset.longitude); + switch (asset.type) { + case AssetType.image: + file.fileType = FileType.image; + break; + case AssetType.video: + file.fileType = FileType.video; + break; + default: + file.fileType = FileType.other; + break; + } + file.createTimestamp = asset.createDateTime.microsecondsSinceEpoch; + if (file.createTimestamp == 0) { try { final parsedDateTime = DateTime.parse( - basenameWithoutExtension(photo.title) + basenameWithoutExtension(file.title) .replaceAll("IMG_", "") .replaceAll("DCIM_", "") .replaceAll("_", " ")); - photo.createTimestamp = parsedDateTime.microsecondsSinceEpoch; + file.createTimestamp = parsedDateTime.microsecondsSinceEpoch; } catch (e) { - photo.createTimestamp = asset.modifiedDateTime.microsecondsSinceEpoch; + file.createTimestamp = asset.modifiedDateTime.microsecondsSinceEpoch; } } - return photo; + return file; } Future getAsset() { @@ -88,19 +103,22 @@ class Photo { } String getThumbnailUrl() { - return Configuration.instance.getHttpEndpoint() + "/" + thumbnailPath; + return Configuration.instance.getHttpEndpoint() + "/" + previewURL; } @override String toString() { - return 'Photo(generatedId: $generatedId, uploadedFileId: $uploadedFileId, localId: $localId, title: $title, deviceFolder: $deviceFolder, location: $location, remotePath: $remotePath, createTimestamp: $createTimestamp, updateTimestamp: $updateTimestamp)'; + return '''File(generatedId: $generatedId, uploadedFileId: $uploadedFileId, + localId: $localId, title: $title, deviceFolder: $deviceFolder, + location: $location, remotePath: $remotePath, fileType: $fileType, + createTimestamp: $createTimestamp, updateTimestamp: $updateTimestamp)'''; } @override bool operator ==(Object o) { if (identical(this, o)) return true; - return o is Photo && + return o is File && o.generatedId == generatedId && o.uploadedFileId == uploadedFileId && o.localId == localId; diff --git a/lib/models/file_type.dart b/lib/models/file_type.dart new file mode 100644 index 000000000..a7616be40 --- /dev/null +++ b/lib/models/file_type.dart @@ -0,0 +1,16 @@ +enum FileType { + image, + video, + other, +} + +FileType getFileType(int fileType) { + switch (fileType) { + case 0: + return FileType.image; + case 1: + return FileType.video; + default: + return FileType.other; + } +} diff --git a/lib/models/filters/favorite_items_filter.dart b/lib/models/filters/favorite_items_filter.dart index 16a471d4a..24579a11f 100644 --- a/lib/models/filters/favorite_items_filter.dart +++ b/lib/models/filters/favorite_items_filter.dart @@ -1,10 +1,10 @@ import 'package:photos/favorite_photos_repository.dart'; import 'package:photos/models/filters/gallery_items_filter.dart'; -import 'package:photos/models/photo.dart'; +import 'package:photos/models/file.dart'; class FavoriteItemsFilter implements GalleryItemsFilter { @override - bool shouldInclude(Photo photo) { - return FavoritePhotosRepository.instance.isLiked(photo); + bool shouldInclude(File file) { + return FavoriteFilesRepository.instance.isLiked(file); } } diff --git a/lib/models/filters/folder_name_filter.dart b/lib/models/filters/folder_name_filter.dart index e2475dedc..d70f6d32d 100644 --- a/lib/models/filters/folder_name_filter.dart +++ b/lib/models/filters/folder_name_filter.dart @@ -1,5 +1,5 @@ import 'package:photos/models/filters/gallery_items_filter.dart'; -import 'package:photos/models/photo.dart'; +import 'package:photos/models/file.dart'; import 'package:path/path.dart' as path; class FolderNameFilter implements GalleryItemsFilter { @@ -8,7 +8,7 @@ class FolderNameFilter implements GalleryItemsFilter { FolderNameFilter(this.folderName); @override - bool shouldInclude(Photo photo) { - return path.basename(photo.deviceFolder) == folderName; + bool shouldInclude(File file) { + return path.basename(file.deviceFolder) == folderName; } } diff --git a/lib/models/filters/gallery_items_filter.dart b/lib/models/filters/gallery_items_filter.dart index 0e8349b19..f0e643aea 100644 --- a/lib/models/filters/gallery_items_filter.dart +++ b/lib/models/filters/gallery_items_filter.dart @@ -1,7 +1,7 @@ -import 'package:photos/models/photo.dart'; +import 'package:photos/models/file.dart'; class GalleryItemsFilter { - bool shouldInclude(Photo photo) { + bool shouldInclude(File file) { return true; } } diff --git a/lib/models/filters/important_items_filter.dart b/lib/models/filters/important_items_filter.dart index ca1eeb9d0..0a161815a 100644 --- a/lib/models/filters/important_items_filter.dart +++ b/lib/models/filters/important_items_filter.dart @@ -1,14 +1,14 @@ import 'dart:io'; import 'package:photos/models/filters/gallery_items_filter.dart'; -import 'package:photos/models/photo.dart'; +import 'package:photos/models/file.dart'; import 'package:path/path.dart'; class ImportantItemsFilter implements GalleryItemsFilter { @override - bool shouldInclude(Photo photo) { + bool shouldInclude(File file) { if (Platform.isAndroid) { - final String folder = basename(photo.deviceFolder); + final String folder = basename(file.deviceFolder); return folder == "Camera" || folder == "DCIM" || folder == "Download" || diff --git a/lib/models/folder.dart b/lib/models/folder.dart index 295f03f14..d666b15d3 100644 --- a/lib/models/folder.dart +++ b/lib/models/folder.dart @@ -1,6 +1,6 @@ import 'dart:convert'; -import 'package:photos/models/photo.dart'; +import 'package:photos/models/file.dart'; class Folder { final int id; @@ -9,7 +9,7 @@ class Folder { final String deviceFolder; final Set sharedWith; final int updateTimestamp; - Photo thumbnailPhoto; + File thumbnailPhoto; Folder( this.id, diff --git a/lib/photo_repository.dart b/lib/photo_repository.dart deleted file mode 100644 index ec30e76f5..000000000 --- a/lib/photo_repository.dart +++ /dev/null @@ -1,33 +0,0 @@ -import 'package:logging/logging.dart'; -import 'package:photos/core/event_bus.dart'; -import 'package:photos/db/photo_db.dart'; -import 'package:photos/events/local_photos_updated_event.dart'; -import 'package:photos/models/photo.dart'; - -class PhotoRepository { - final _logger = Logger("PhotoRepository"); - final _photos = List(); - - PhotoRepository._privateConstructor(); - static final PhotoRepository instance = PhotoRepository._privateConstructor(); - - List get photos { - return _photos; - } - - Future loadPhotos() async { - PhotoDB db = PhotoDB.instance; - var photos = await db.getAllPhotos(); - - _photos.clear(); - _photos.addAll(photos); - - return true; - } - - Future reloadPhotos() async { - _logger.info("Reloading..."); - await loadPhotos(); - Bus.instance.fire(LocalPhotosUpdatedEvent()); - } -} diff --git a/lib/photo_sync_manager.dart b/lib/photo_sync_manager.dart index 8af5418c1..4ad11aa5a 100644 --- a/lib/photo_sync_manager.dart +++ b/lib/photo_sync_manager.dart @@ -7,13 +7,13 @@ import 'package:photos/core/event_bus.dart'; import 'package:photos/db/photo_db.dart'; import 'package:photos/events/photo_upload_event.dart'; import 'package:photos/events/user_authenticated_event.dart'; -import 'package:photos/photo_repository.dart'; +import 'package:photos/file_repository.dart'; import 'package:path_provider/path_provider.dart'; import 'package:photo_manager/photo_manager.dart'; import 'package:photos/utils/file_name_util.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:dio/dio.dart'; -import 'package:photos/models/photo.dart'; +import 'package:photos/models/file.dart'; import 'package:photos/core/configuration.dart'; import 'package:photos/events/remote_sync_event.dart'; @@ -21,7 +21,7 @@ import 'package:photos/events/remote_sync_event.dart'; class PhotoSyncManager { final _logger = Logger("PhotoSyncManager"); final _dio = Dio(); - final _db = PhotoDB.instance; + final _db = FileDB.instance; bool _isSyncInProgress = false; Future _existingSync; @@ -73,25 +73,25 @@ class PhotoSyncManager { final pathEntities = await _getGalleryList(lastDBUpdateTimestamp, syncStartTimestamp); - final photos = List(); + final files = List(); AssetPathEntity recents; for (AssetPathEntity pathEntity in pathEntities) { if (pathEntity.name == "Recent" || pathEntity.name == "Recents") { recents = pathEntity; } else { - await _addToPhotos(pathEntity, lastDBUpdateTimestamp, photos); + await _addToPhotos(pathEntity, lastDBUpdateTimestamp, files); } } if (recents != null) { - await _addToPhotos(recents, lastDBUpdateTimestamp, photos); + await _addToPhotos(recents, lastDBUpdateTimestamp, files); } - if (photos.isNotEmpty) { - photos.sort((first, second) => + if (files.isNotEmpty) { + files.sort((first, second) => first.createTimestamp.compareTo(second.createTimestamp)); await _updateDatabase( - photos, prefs, lastDBUpdateTimestamp, syncStartTimestamp); - await PhotoRepository.instance.reloadPhotos(); + files, prefs, lastDBUpdateTimestamp, syncStartTimestamp); + await FileRepository.instance.reloadFiles(); } await _syncWithRemote(prefs); } @@ -104,13 +104,14 @@ class PhotoSyncManager { } final filterOptionGroup = FilterOptionGroup(); filterOptionGroup.setOption(AssetType.image, FilterOption(needTitle: true)); + filterOptionGroup.setOption(AssetType.video, FilterOption(needTitle: true)); filterOptionGroup.dateTimeCond = DateTimeCond( min: DateTime.fromMicrosecondsSinceEpoch(fromTimestamp), max: DateTime.fromMicrosecondsSinceEpoch(toTimestamp), ); var galleryList = await PhotoManager.getAssetPathList( hasAll: true, - type: RequestType.image, + type: RequestType.common, filterOption: filterOptionGroup, ); @@ -122,16 +123,16 @@ class PhotoSyncManager { } Future _addToPhotos(AssetPathEntity pathEntity, int lastDBUpdateTimestamp, - List photos) async { + List files) async { final assetList = await pathEntity.assetList; for (AssetEntity entity in assetList) { if (max(entity.createDateTime.microsecondsSinceEpoch, entity.modifiedDateTime.microsecondsSinceEpoch) > lastDBUpdateTimestamp) { try { - final photo = await Photo.fromAsset(pathEntity, entity); - if (!photos.contains(photo)) { - photos.add(photo); + final file = await File.fromAsset(pathEntity, entity); + if (!files.contains(file)) { + files.add(file); } } catch (e) { _logger.severe(e); @@ -151,25 +152,22 @@ class PhotoSyncManager { await _deletePhotosOnServer(); } - Future _updateDatabase( - final List photos, - SharedPreferences prefs, - int lastDBUpdateTimestamp, - int syncStartTimestamp) async { - var photosToBeAdded = List(); - for (Photo photo in photos) { - if (photo.createTimestamp > lastDBUpdateTimestamp) { - photosToBeAdded.add(photo); + Future _updateDatabase(final List files, SharedPreferences prefs, + int lastDBUpdateTimestamp, int syncStartTimestamp) async { + var filesToBeAdded = List(); + for (File file in files) { + if (file.createTimestamp > lastDBUpdateTimestamp) { + filesToBeAdded.add(file); } } - return await _insertPhotosToDB(photosToBeAdded, prefs, syncStartTimestamp); + return await _insertFilesToDB(filesToBeAdded, prefs, syncStartTimestamp); } Future _downloadDiff(SharedPreferences prefs) async { var diff = await _getDiff(_getLastSyncTimestamp(prefs), _diffLimit); if (diff != null && diff.isNotEmpty) { await _storeDiff(diff, prefs); - PhotoRepository.instance.reloadPhotos(); + FileRepository.instance.reloadFiles(); if (diff.length == _diffLimit) { return await _downloadDiff(prefs); } @@ -185,15 +183,15 @@ class PhotoSyncManager { } Future _uploadDiff(SharedPreferences prefs) async { - List photosToBeUploaded = await _db.getPhotosToBeUploaded(); + List photosToBeUploaded = await _db.getFilesToBeUploaded(); for (int i = 0; i < photosToBeUploaded.length; i++) { - Photo photo = photosToBeUploaded[i]; - _logger.info("Uploading " + photo.toString()); + File file = photosToBeUploaded[i]; + _logger.info("Uploading " + file.toString()); try { - var uploadedPhoto = await _uploadFile(photo); - await _db.updatePhoto(photo.generatedId, uploadedPhoto.uploadedFileId, - uploadedPhoto.remotePath, uploadedPhoto.updateTimestamp); - prefs.setInt(_lastSyncTimestampKey, uploadedPhoto.updateTimestamp); + var uploadedFile = await _uploadFile(file); + await _db.update(file.generatedId, uploadedFile.uploadedFileId, + uploadedFile.remotePath, uploadedFile.updateTimestamp); + prefs.setInt(_lastSyncTimestampKey, uploadedFile.updateTimestamp); Bus.instance.fire(PhotoUploadEvent( completed: i + 1, total: photosToBeUploaded.length)); @@ -204,24 +202,22 @@ class PhotoSyncManager { } } - Future _storeDiff(List diff, SharedPreferences prefs) async { - for (Photo photo in diff) { + Future _storeDiff(List diff, SharedPreferences prefs) async { + for (File file in diff) { try { - var existingPhoto = await _db.getMatchingPhoto(photo.localId, - photo.title, photo.deviceFolder, photo.createTimestamp, - alternateTitle: getHEICFileNameForJPG(photo)); - await _db.updatePhoto(existingPhoto.generatedId, photo.uploadedFileId, - photo.remotePath, photo.updateTimestamp, photo.thumbnailPath); + var existingPhoto = await _db.getMatchingFile( + file.localId, file.title, file.deviceFolder, file.createTimestamp, + alternateTitle: getHEICFileNameForJPG(file)); + await _db.update(existingPhoto.generatedId, file.uploadedFileId, + file.remotePath, file.updateTimestamp, file.previewURL); } catch (e) { - await _db.insertPhoto(photo); + await _db.insert(file); } - // _logger.info( - // "Setting update timestamp to " + photo.updateTimestamp.toString()); - await prefs.setInt(_lastSyncTimestampKey, photo.updateTimestamp); + await prefs.setInt(_lastSyncTimestampKey, file.updateTimestamp); } } - Future> _getDiff(int lastSyncTimestamp, int limit) async { + Future> _getDiff(int lastSyncTimestamp, int limit) async { Response response = await _dio.get( Configuration.instance.getHttpEndpoint() + "/files/diff", options: @@ -234,7 +230,7 @@ class PhotoSyncManager { if (response != null) { Bus.instance.fire(RemoteSyncEvent(true)); return (response.data["diff"] as List) - .map((photo) => new Photo.fromJson(photo)) + .map((file) => new File.fromJson(file)) .toList(); } else { Bus.instance.fire(RemoteSyncEvent(false)); @@ -242,7 +238,7 @@ class PhotoSyncManager { } } - Future _uploadFile(Photo localPhoto) async { + Future _uploadFile(File localPhoto) async { var title = getJPGFileNameForHEIC(localPhoto); var formData = FormData.fromMap({ "file": MultipartFile.fromBytes((await localPhoto.getBytes()), @@ -260,25 +256,25 @@ class PhotoSyncManager { data: formData, ) .then((response) { - return Photo.fromJson(response.data); + return File.fromJson(response.data); }); } Future _deletePhotosOnServer() async { - return _db.getAllDeletedPhotos().then((deletedPhotos) async { - for (Photo deletedPhoto in deletedPhotos) { - await _deletePhotoOnServer(deletedPhoto); - await _db.deletePhoto(deletedPhoto); + return _db.getAllDeleted().then((deletedPhotos) async { + for (File deletedPhoto in deletedPhotos) { + await _deleteFileOnServer(deletedPhoto); + await _db.delete(deletedPhoto); } }); } - Future _deletePhotoOnServer(Photo photo) async { + Future _deleteFileOnServer(File file) async { return _dio .delete( Configuration.instance.getHttpEndpoint() + "/files/" + - photo.uploadedFileId.toString(), + file.uploadedFileId.toString(), options: Options( headers: {"X-Auth-Token": Configuration.instance.getToken()}), ) @@ -291,10 +287,10 @@ class PhotoSyncManager { .createSync(recursive: true); } - Future _insertPhotosToDB( - List photos, SharedPreferences prefs, int timestamp) async { - await _db.insertPhotos(photos); - _logger.info("Inserted " + photos.length.toString() + " photos."); + Future _insertFilesToDB( + List files, SharedPreferences prefs, int timestamp) async { + await _db.insertMultiple(files); + _logger.info("Inserted " + files.length.toString() + " files."); return await prefs.setInt(_lastDBUpdateTimestampKey, timestamp); } } diff --git a/lib/ui/detail_page.dart b/lib/ui/detail_page.dart index be475d06d..4f0d822cb 100644 --- a/lib/ui/detail_page.dart +++ b/lib/ui/detail_page.dart @@ -3,17 +3,19 @@ import 'package:flutter/material.dart'; import 'package:like_button/like_button.dart'; import 'package:photos/core/cache/image_cache.dart'; import 'package:photos/favorite_photos_repository.dart'; -import 'package:photos/models/photo.dart'; +import 'package:photos/models/file_type.dart'; +import 'package:photos/models/file.dart'; +import 'package:photos/ui/video_widget.dart'; import 'package:photos/ui/zoomable_image.dart'; import 'package:photos/utils/date_time_util.dart'; import 'package:photos/utils/share_util.dart'; import 'package:logging/logging.dart'; class DetailPage extends StatefulWidget { - final List photos; + final List files; final int selectedIndex; - DetailPage(this.photos, this.selectedIndex, {key}) : super(key: key); + DetailPage(this.files, this.selectedIndex, {key}) : super(key: key); @override _DetailPageState createState() => _DetailPageState(); @@ -22,12 +24,12 @@ class DetailPage extends StatefulWidget { class _DetailPageState extends State { final _logger = Logger("DetailPageState"); bool _shouldDisableScroll = false; - List _photos; + List _files; int _selectedIndex = 0; @override void initState() { - _photos = widget.photos; + _files = widget.files; _selectedIndex = widget.selectedIndex; super.initState(); } @@ -35,12 +37,12 @@ class _DetailPageState extends State { @override Widget build(BuildContext context) { _logger.info("Opening " + - _photos[_selectedIndex].toString() + + _files[_selectedIndex].toString() + ". " + _selectedIndex.toString() + " / " + - _photos.length.toString() + - " photos ."); + _files.length.toString() + + " files ."); return Scaffold( appBar: _buildAppBar(), body: Center( @@ -54,52 +56,59 @@ class _DetailPageState extends State { Widget _buildPageView() { return PageView.builder( itemBuilder: (context, index) { - final photo = _photos[index]; - final image = ZoomableImage( - photo, - shouldDisableScroll: (value) { - setState(() { - _shouldDisableScroll = value; - }); - }, - ); - _preloadPhotos(index); - return image; + final file = _files[index]; + Widget content; + if (file.fileType == FileType.image) { + content = ZoomableImage( + file, + shouldDisableScroll: (value) { + setState(() { + _shouldDisableScroll = value; + }); + }, + ); + } else if (file.fileType == FileType.video) { + content = VideoWidget(file); + } else { + content = Icon(Icons.error); + } + _preloadFiles(index); + return content; }, onPageChanged: (index) { setState(() { _selectedIndex = index; }); - _preloadPhotos(index); + _preloadFiles(index); }, physics: _shouldDisableScroll ? NeverScrollableScrollPhysics() : PageScrollPhysics(), controller: PageController(initialPage: _selectedIndex), - itemCount: _photos.length, + itemCount: _files.length, ); } - void _preloadPhotos(int index) { + void _preloadFiles(int index) { if (index > 0) { - _preloadPhoto(_photos[index - 1]); + _preloadFile(_files[index - 1]); } - if (index < _photos.length - 1) { - _preloadPhoto(_photos[index + 1]); + if (index < _files.length - 1) { + _preloadFile(_files[index + 1]); } } - void _preloadPhoto(Photo photo) { - if (photo.localId == null) { - photo.getBytes().then((data) { - BytesLruCache.put(photo, data); + void _preloadFile(File file) { + if (file.localId == null) { + file.getBytes().then((data) { + BytesLruCache.put(file, data); }); } else { - final cachedFile = FileLruCache.get(photo); + final cachedFile = FileLruCache.get(file); if (cachedFile == null) { - photo.getAsset().then((asset) { - asset.file.then((file) { - FileLruCache.put(photo, file); + file.getAsset().then((asset) { + asset.file.then((assetFile) { + FileLruCache.put(file, assetFile); }); }); } @@ -108,7 +117,7 @@ class _DetailPageState extends State { AppBar _buildAppBar() { final actions = List(); - if (_photos[_selectedIndex].localId != null) { + if (_files[_selectedIndex].localId != null) { actions.add(_getFavoriteButton()); } actions.add(PopupMenuButton( @@ -142,9 +151,9 @@ class _DetailPageState extends State { }, onSelected: (value) { if (value == 1) { - share(_photos[_selectedIndex]); + share(_files[_selectedIndex]); } else if (value == 2) { - _displayInfo(_photos[_selectedIndex]); + _displayInfo(_files[_selectedIndex]); } }, )); @@ -154,52 +163,61 @@ class _DetailPageState extends State { } Widget _getFavoriteButton() { - final photo = _photos[_selectedIndex]; + final file = _files[_selectedIndex]; return LikeButton( - isLiked: FavoritePhotosRepository.instance.isLiked(photo), + isLiked: FavoriteFilesRepository.instance.isLiked(file), onTap: (oldValue) { - return FavoritePhotosRepository.instance.setLiked(photo, !oldValue); + return FavoriteFilesRepository.instance.setLiked(file, !oldValue); }, ); } - Future _displayInfo(Photo photo) async { - final asset = await photo.getAsset(); + Future _displayInfo(File file) async { + final asset = await file.getAsset(); return showDialog( context: context, builder: (BuildContext context) { + var items = [ + Row( + children: [ + Icon(Icons.timer), + Padding(padding: EdgeInsets.all(4)), + Text(getFormattedTime( + DateTime.fromMicrosecondsSinceEpoch(file.createTimestamp))), + ], + ), + Padding(padding: EdgeInsets.all(4)), + Row( + children: [ + Icon(Icons.folder), + Padding(padding: EdgeInsets.all(4)), + Text(file.deviceFolder), + ], + ), + Padding(padding: EdgeInsets.all(4)), + ]; + if (file.fileType == FileType.image) { + items.add(Row( + children: [ + Icon(Icons.photo_size_select_actual), + Padding(padding: EdgeInsets.all(4)), + Text(asset.width.toString() + " x " + asset.height.toString()), + ], + )); + } else { + items.add(Row( + children: [ + Icon(Icons.timer), + Padding(padding: EdgeInsets.all(4)), + Text(asset.videoDuration.toString()), + ], + )); + } return AlertDialog( - title: Text(photo.title), + title: Text(file.title), content: SingleChildScrollView( child: ListBody( - children: [ - Row( - children: [ - Icon(Icons.timer), - Padding(padding: EdgeInsets.all(4)), - Text(getFormattedTime(DateTime.fromMicrosecondsSinceEpoch( - photo.createTimestamp))), - ], - ), - Padding(padding: EdgeInsets.all(4)), - Row( - children: [ - Icon(Icons.folder), - Padding(padding: EdgeInsets.all(4)), - Text(photo.deviceFolder), - ], - ), - Padding(padding: EdgeInsets.all(4)), - Row( - children: [ - Icon(Icons.photo_size_select_actual), - Padding(padding: EdgeInsets.all(4)), - Text(asset.width.toString() + - " x " + - asset.height.toString()), - ], - ), - ], + children: items, ), ), actions: [ diff --git a/lib/ui/device_folder_page.dart b/lib/ui/device_folder_page.dart index 7eb0e59c0..c89ccfb7d 100644 --- a/lib/ui/device_folder_page.dart +++ b/lib/ui/device_folder_page.dart @@ -4,8 +4,8 @@ import 'package:flutter/material.dart'; import 'package:photos/core/event_bus.dart'; import 'package:photos/events/local_photos_updated_event.dart'; import 'package:photos/models/device_folder.dart'; -import 'package:photos/models/photo.dart'; -import 'package:photos/photo_repository.dart'; +import 'package:photos/models/file.dart'; +import 'package:photos/file_repository.dart'; import 'package:photos/ui/gallery.dart'; import 'package:photos/ui/gallery_app_bar_widget.dart'; import 'package:logging/logging.dart'; @@ -21,7 +21,7 @@ class DeviceFolderPage extends StatefulWidget { class _DeviceFolderPageState extends State { final logger = Logger("DeviceFolderPageState"); - Set _selectedPhotos = Set(); + Set _selectedFiles = Set(); StreamSubscription _subscription; @override @@ -38,34 +38,34 @@ class _DeviceFolderPageState extends State { appBar: GalleryAppBarWidget( GalleryAppBarType.local_folder, widget.folder.name, - widget.folder.thumbnailPhoto.deviceFolder, - _selectedPhotos, + widget.folder.thumbnail.deviceFolder, + _selectedFiles, onSelectionClear: () { setState(() { - _selectedPhotos.clear(); + _selectedFiles.clear(); }); }, ), body: Gallery( - () => Future.value(_getFilteredPhotos(PhotoRepository.instance.photos)), - selectedPhotos: _selectedPhotos, - onPhotoSelectionChange: (Set selectedPhotos) { + () => Future.value(_getFilteredFiles(FileRepository.instance.files)), + selectedFiles: _selectedFiles, + onFileSelectionChange: (Set selectedFiles) { setState(() { - _selectedPhotos = selectedPhotos; + _selectedFiles = selectedFiles; }); }, ), ); } - List _getFilteredPhotos(List unfilteredPhotos) { - final List filteredPhotos = List(); - for (Photo photo in unfilteredPhotos) { - if (widget.folder.filter.shouldInclude(photo)) { - filteredPhotos.add(photo); + List _getFilteredFiles(List unfilteredFiles) { + final List filteredFiles = List(); + for (File file in unfilteredFiles) { + if (widget.folder.filter.shouldInclude(file)) { + filteredFiles.add(file); } } - return filteredPhotos; + return filteredFiles; } @override diff --git a/lib/ui/device_folders_gallery_widget.dart b/lib/ui/device_folders_gallery_widget.dart index cd682f714..66ec291b1 100644 --- a/lib/ui/device_folders_gallery_widget.dart +++ b/lib/ui/device_folders_gallery_widget.dart @@ -68,23 +68,23 @@ class _DeviceFolderGalleryWidgetState extends State { } Future> _getDeviceFolders() async { - final paths = await PhotoDB.instance.getLocalPaths(); + final paths = await FileDB.instance.getLocalPaths(); final folders = List(); for (final path in paths) { - final photo = await PhotoDB.instance.getLatestPhotoInPath(path); + final file = await FileDB.instance.getLatestFileInPath(path); final folderName = p.basename(path); folders - .add(DeviceFolder(folderName, photo, FolderNameFilter(folderName))); + .add(DeviceFolder(folderName, file, FolderNameFilter(folderName))); } folders.sort((first, second) { - return second.thumbnailPhoto.createTimestamp - .compareTo(first.thumbnailPhoto.createTimestamp); + return second.thumbnail.createTimestamp + .compareTo(first.thumbnail.createTimestamp); }); - if (FavoritePhotosRepository.instance.hasFavorites()) { - final photo = await PhotoDB.instance.getLatestPhotoAmongGeneratedIds( - FavoritePhotosRepository.instance.getLiked().toList()); + if (FavoriteFilesRepository.instance.hasFavorites()) { + final file = await FileDB.instance.getLatestFileAmongGeneratedIds( + FavoriteFilesRepository.instance.getLiked().toList()); folders.insert( - 0, DeviceFolder("Favorites", photo, FavoriteItemsFilter())); + 0, DeviceFolder("Favorites", file, FavoriteItemsFilter())); } return folders; } @@ -94,7 +94,7 @@ class _DeviceFolderGalleryWidgetState extends State { child: Column( children: [ Container( - child: ThumbnailWidget(folder.thumbnailPhoto), + child: ThumbnailWidget(folder.thumbnail), height: 150, width: 150, ), diff --git a/lib/ui/gallery.dart b/lib/ui/gallery.dart index 0762b61ec..3db2da4ac 100644 --- a/lib/ui/gallery.dart +++ b/lib/ui/gallery.dart @@ -6,7 +6,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:logging/logging.dart'; import 'package:photos/events/event.dart'; -import 'package:photos/models/photo.dart'; +import 'package:photos/models/file.dart'; import 'package:photos/ui/detail_page.dart'; import 'package:photos/ui/loading_widget.dart'; import 'package:photos/ui/sync_indicator.dart'; @@ -15,20 +15,20 @@ import 'package:photos/utils/date_time_util.dart'; import 'package:pull_to_refresh/pull_to_refresh.dart'; class Gallery extends StatefulWidget { - final Future> Function() loader; + final Future> Function() loader; // TODO: Verify why the event is necessary when calling loader post onRefresh // should have done the job. final Stream reloadEvent; final Future Function() onRefresh; - final Set selectedPhotos; - final Function(Set) onPhotoSelectionChange; + final Set selectedFiles; + final Function(Set) onFileSelectionChange; Gallery( this.loader, { this.reloadEvent, this.onRefresh, - this.selectedPhotos, - this.onPhotoSelectionChange, + this.selectedFiles, + this.onFileSelectionChange, }); @override @@ -40,12 +40,12 @@ class Gallery extends StatefulWidget { class _GalleryState extends State { final Logger _logger = Logger("Gallery"); final ScrollController _scrollController = ScrollController(); - final List> _collatedPhotos = List>(); + final List> _collatedFiles = List>(); bool _requiresLoad = false; - AsyncSnapshot> _lastSnapshot; - Set _selectedPhotos = HashSet(); - List _photos; + AsyncSnapshot> _lastSnapshot; + Set _selectedFiles = HashSet(); + List _files; RefreshController _refreshController = RefreshController(); @override @@ -66,7 +66,7 @@ class _GalleryState extends State { if (!_requiresLoad) { return _onSnapshotAvailable(_lastSnapshot); } - return FutureBuilder>( + return FutureBuilder>( future: widget.loader(), builder: (context, snapshot) { _lastSnapshot = snapshot; @@ -75,7 +75,7 @@ class _GalleryState extends State { ); } - Widget _onSnapshotAvailable(AsyncSnapshot> snapshot) { + Widget _onSnapshotAvailable(AsyncSnapshot> snapshot) { if (snapshot.hasData) { _requiresLoad = false; return _onDataLoaded(snapshot.data); @@ -87,15 +87,15 @@ class _GalleryState extends State { } } - Widget _onDataLoaded(List photos) { - _photos = photos; - if (_photos.isEmpty) { + Widget _onDataLoaded(List files) { + _files = files; + if (_files.isEmpty) { return Center(child: Text("Nothing to see here! 👀")); } - _selectedPhotos = widget.selectedPhotos ?? Set(); - _collatePhotos(); + _selectedFiles = widget.selectedFiles ?? Set(); + _collateFiles(); final list = ListView.builder( - itemCount: _collatedPhotos.length, + itemCount: _collatedFiles.length, itemBuilder: _buildListItem, controller: _scrollController, cacheExtent: 1000, @@ -123,12 +123,9 @@ class _GalleryState extends State { } Widget _buildListItem(BuildContext context, int index) { - var photos = _collatedPhotos[index]; + var files = _collatedFiles[index]; return Column( - children: [ - _getDay(photos[0].createTimestamp), - _getGallery(photos) - ], + children: [_getDay(files[0].createTimestamp), _getGallery(files)], ); } @@ -143,64 +140,64 @@ class _GalleryState extends State { ); } - Widget _getGallery(List photos) { + Widget _getGallery(List files) { return GridView.builder( shrinkWrap: true, padding: EdgeInsets.only(bottom: 12), physics: ScrollPhysics(), // to disable GridView's scrolling itemBuilder: (context, index) { - return _buildPhoto(context, photos[index]); + return _buildFile(context, files[index]); }, - itemCount: photos.length, + itemCount: files.length, gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 4, ), ); } - Widget _buildPhoto(BuildContext context, Photo photo) { + Widget _buildFile(BuildContext context, File file) { return GestureDetector( onTap: () { - if (_selectedPhotos.isNotEmpty) { - _selectPhoto(photo); + if (_selectedFiles.isNotEmpty) { + _selectFile(file); } else { - _routeToDetailPage(photo, context); + _routeToDetailPage(file, context); } }, onLongPress: () { HapticFeedback.lightImpact(); - _selectPhoto(photo); + _selectFile(file); }, child: Container( margin: const EdgeInsets.all(2.0), decoration: BoxDecoration( - border: _selectedPhotos.contains(photo) + border: _selectedFiles.contains(file) ? Border.all(width: 4.0, color: Colors.blue) : null, ), child: Hero( - tag: photo.generatedId.toString(), - child: ThumbnailWidget(photo), + tag: file.generatedId.toString(), + child: ThumbnailWidget(file), ), ), ); } - void _selectPhoto(Photo photo) { + void _selectFile(File file) { setState(() { - if (_selectedPhotos.contains(photo)) { - _selectedPhotos.remove(photo); + if (_selectedFiles.contains(file)) { + _selectedFiles.remove(file); } else { - _selectedPhotos.add(photo); + _selectedFiles.add(file); } - widget.onPhotoSelectionChange(_selectedPhotos); + widget.onFileSelectionChange(_selectedFiles); }); } - void _routeToDetailPage(Photo photo, BuildContext context) { + void _routeToDetailPage(File file, BuildContext context) { final page = DetailPage( - _photos, - _photos.indexOf(photo), + _files, + _files.indexOf(file), ); Navigator.of(context).push( MaterialPageRoute( @@ -211,31 +208,30 @@ class _GalleryState extends State { ); } - void _collatePhotos() { - final dailyPhotos = List(); - final collatedPhotos = List>(); - for (int index = 0; index < _photos.length; index++) { + void _collateFiles() { + final dailyFiles = List(); + final collatedFiles = List>(); + for (int index = 0; index < _files.length; index++) { if (index > 0 && - !_arePhotosFromSameDay(_photos[index], _photos[index - 1])) { - var collatedDailyPhotos = List(); - collatedDailyPhotos.addAll(dailyPhotos); - collatedPhotos.add(collatedDailyPhotos); - dailyPhotos.clear(); + !_areFilesFromSameDay(_files[index], _files[index - 1])) { + var collatedDailyFiles = List(); + collatedDailyFiles.addAll(dailyFiles); + collatedFiles.add(collatedDailyFiles); + dailyFiles.clear(); } - dailyPhotos.add(_photos[index]); + dailyFiles.add(_files[index]); } - if (dailyPhotos.isNotEmpty) { - collatedPhotos.add(dailyPhotos); + if (dailyFiles.isNotEmpty) { + collatedFiles.add(dailyFiles); } - _collatedPhotos.clear(); - _collatedPhotos.addAll(collatedPhotos); + _collatedFiles.clear(); + _collatedFiles.addAll(collatedFiles); } - bool _arePhotosFromSameDay(Photo firstPhoto, Photo secondPhoto) { - var firstDate = - DateTime.fromMicrosecondsSinceEpoch(firstPhoto.createTimestamp); + bool _areFilesFromSameDay(File first, File second) { + var firstDate = DateTime.fromMicrosecondsSinceEpoch(first.createTimestamp); var secondDate = - DateTime.fromMicrosecondsSinceEpoch(secondPhoto.createTimestamp); + DateTime.fromMicrosecondsSinceEpoch(second.createTimestamp); return firstDate.year == secondDate.year && firstDate.month == secondDate.month && firstDate.day == secondDate.day; diff --git a/lib/ui/gallery_app_bar_widget.dart b/lib/ui/gallery_app_bar_widget.dart index e82ac15c8..9e7be9b53 100644 --- a/lib/ui/gallery_app_bar_widget.dart +++ b/lib/ui/gallery_app_bar_widget.dart @@ -5,8 +5,8 @@ import 'package:photos/core/configuration.dart'; import 'package:photos/core/event_bus.dart'; import 'package:photos/db/photo_db.dart'; import 'package:photos/events/remote_sync_event.dart'; -import 'package:photos/models/photo.dart'; -import 'package:photos/photo_repository.dart'; +import 'package:photos/models/file.dart'; +import 'package:photos/file_repository.dart'; import 'package:photos/ui/setup_page.dart'; import 'package:photo_manager/photo_manager.dart'; import 'package:photos/ui/share_folder_widget.dart'; @@ -23,10 +23,10 @@ class GalleryAppBarWidget extends StatefulWidget final GalleryAppBarType type; final String title; final String path; - final Set selectedPhotos; + final Set selectedFiles; final Function() onSelectionClear; - GalleryAppBarWidget(this.type, this.title, this.path, this.selectedPhotos, + GalleryAppBarWidget(this.type, this.title, this.path, this.selectedFiles, {this.onSelectionClear}); @override @@ -52,7 +52,7 @@ class _GalleryAppBarWidgetState extends State { @override Widget build(BuildContext context) { - if (widget.selectedPhotos.isEmpty) { + if (widget.selectedFiles.isEmpty) { return AppBar( title: Text(widget.title), actions: _getDefaultActions(context), @@ -63,11 +63,11 @@ class _GalleryAppBarWidgetState extends State { leading: IconButton( icon: Icon(Icons.close), onPressed: () { - _clearSelectedPhotos(); + _clearSelectedFiles(); }, ), - title: Text(widget.selectedPhotos.length.toString()), - actions: _getPhotoActions(context), + title: Text(widget.selectedFiles.length.toString()), + actions: _getActions(context), ); } @@ -103,46 +103,46 @@ class _GalleryAppBarWidgetState extends State { ); } - List _getPhotoActions(BuildContext context) { + List _getActions(BuildContext context) { List actions = List(); - if (widget.selectedPhotos.isNotEmpty) { + if (widget.selectedFiles.isNotEmpty) { if (widget.type != GalleryAppBarType.remote_folder) { actions.add(IconButton( icon: Icon(Icons.delete), onPressed: () { - _showDeletePhotosSheet(context); + _showDeleteSheet(context); }, )); } actions.add(IconButton( icon: Icon(Icons.share), onPressed: () { - _shareSelectedPhotos(context); + _shareSelected(context); }, )); } return actions; } - void _shareSelectedPhotos(BuildContext context) { - shareMultiple(widget.selectedPhotos.toList()); + void _shareSelected(BuildContext context) { + shareMultiple(widget.selectedFiles.toList()); } - void _showDeletePhotosSheet(BuildContext context) { + void _showDeleteSheet(BuildContext context) { final action = CupertinoActionSheet( actions: [ CupertinoActionSheetAction( child: Text("Delete on device"), isDestructiveAction: true, onPressed: () async { - await _deleteSelectedPhotos(context, false); + await _deleteSelected(context, false); }, ), CupertinoActionSheetAction( child: Text("Delete everywhere [WiP]"), isDestructiveAction: true, onPressed: () async { - await _deleteSelectedPhotos(context, true); + await _deleteSelected(context, true); }, ) ], @@ -156,24 +156,23 @@ class _GalleryAppBarWidgetState extends State { showCupertinoModalPopup(context: context, builder: (_) => action); } - Future _deleteSelectedPhotos( - BuildContext context, bool deleteEverywhere) async { + Future _deleteSelected(BuildContext context, bool deleteEverywhere) async { await PhotoManager.editor - .deleteWithIds(widget.selectedPhotos.map((p) => p.localId).toList()); + .deleteWithIds(widget.selectedFiles.map((p) => p.localId).toList()); - for (Photo photo in widget.selectedPhotos) { + for (File file in widget.selectedFiles) { deleteEverywhere - ? await PhotoDB.instance.markPhotoForDeletion(photo) - : await PhotoDB.instance.deletePhoto(photo); + ? await FileDB.instance.markForDeletion(file) + : await FileDB.instance.delete(file); } Navigator.of(context, rootNavigator: true).pop(); - PhotoRepository.instance.reloadPhotos(); - _clearSelectedPhotos(); + FileRepository.instance.reloadFiles(); + _clearSelectedFiles(); } - void _clearSelectedPhotos() { + void _clearSelectedFiles() { setState(() { - widget.selectedPhotos.clear(); + widget.selectedFiles.clear(); }); if (widget.onSelectionClear != null) { widget.onSelectionClear(); diff --git a/lib/ui/home_widget.dart b/lib/ui/home_widget.dart index 608ca5e56..d78e9b76c 100644 --- a/lib/ui/home_widget.dart +++ b/lib/ui/home_widget.dart @@ -7,8 +7,8 @@ import 'package:flutter/widgets.dart'; import 'package:photos/core/event_bus.dart'; import 'package:photos/events/local_photos_updated_event.dart'; import 'package:photos/models/filters/important_items_filter.dart'; -import 'package:photos/models/photo.dart'; -import 'package:photos/photo_repository.dart'; +import 'package:photos/models/file.dart'; +import 'package:photos/file_repository.dart'; import 'package:photos/photo_sync_manager.dart'; import 'package:photos/ui/device_folders_gallery_widget.dart'; import 'package:photos/ui/gallery.dart'; @@ -38,7 +38,7 @@ class _HomeWidgetState extends State { ShakeDetector _detector; int _selectedNavBarItem = 0; - Set _selectedPhotos = HashSet(); + Set _selectedPhotos = HashSet(); StreamSubscription _localPhotosUpdatedEventSubscription; @@ -110,18 +110,18 @@ class _HomeWidgetState extends State { Widget _getMainGalleryWidget() { return FutureBuilder( - future: PhotoRepository.instance.loadPhotos(), + future: FileRepository.instance.loadFiles(), builder: (context, snapshot) { if (snapshot.hasData) { return Gallery( - () => Future.value( - _getFilteredPhotos(PhotoRepository.instance.photos)), + () => + Future.value(_getFilteredPhotos(FileRepository.instance.files)), reloadEvent: Bus.instance.on(), onRefresh: () { return PhotoSyncManager.instance.sync(); }, - selectedPhotos: _selectedPhotos, - onPhotoSelectionChange: (Set selectedPhotos) { + selectedFiles: _selectedPhotos, + onFileSelectionChange: (Set selectedPhotos) { setState(() { _selectedPhotos = selectedPhotos; }); @@ -162,11 +162,11 @@ class _HomeWidgetState extends State { ); } - List _getFilteredPhotos(List unfilteredPhotos) { - final List filteredPhotos = List(); - for (Photo photo in unfilteredPhotos) { - if (importantItemsFilter.shouldInclude(photo)) { - filteredPhotos.add(photo); + List _getFilteredPhotos(List unfilteredFiles) { + final List filteredPhotos = List(); + for (File file in unfilteredFiles) { + if (importantItemsFilter.shouldInclude(file)) { + filteredPhotos.add(file); } } return filteredPhotos; diff --git a/lib/ui/location_search_results_page.dart b/lib/ui/location_search_results_page.dart index 42eb71ed9..aa90fe676 100644 --- a/lib/ui/location_search_results_page.dart +++ b/lib/ui/location_search_results_page.dart @@ -3,10 +3,9 @@ import 'dart:async'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:photos/models/location.dart'; -import 'package:photos/models/photo.dart'; -import 'package:photos/photo_repository.dart'; +import 'package:photos/models/file.dart'; +import 'package:photos/file_repository.dart'; import 'package:photos/ui/gallery.dart'; -import 'package:photos/ui/loading_widget.dart'; class ViewPort { final Location northEast; @@ -45,25 +44,25 @@ class _LocationSearchResultsPageState extends State { ); } - FutureOr> _getResult() async { - final photos = PhotoRepository.instance.photos; + FutureOr> _getResult() async { + final files = FileRepository.instance.files; final args = Map(); - args['photos'] = photos; + args['files'] = files; args['viewPort'] = widget.viewPort; return _filterPhotos(args); } - static List _filterPhotos(Map args) { - List photos = args['photos']; + static List _filterPhotos(Map args) { + List files = args['files']; ViewPort viewPort = args['viewPort']; - final result = List(); - for (final photo in photos) { - if (photo.location != null && - viewPort.northEast.latitude > photo.location.latitude && - viewPort.southWest.latitude < photo.location.latitude && - viewPort.northEast.longitude > photo.location.longitude && - viewPort.southWest.longitude < photo.location.longitude) { - result.add(photo); + final result = List(); + for (final file in files) { + if (file.location != null && + viewPort.northEast.latitude > file.location.latitude && + viewPort.southWest.latitude < file.location.latitude && + viewPort.northEast.longitude > file.location.longitude && + viewPort.southWest.longitude < file.location.longitude) { + result.add(file); } else {} } return result; diff --git a/lib/ui/remote_folder_gallery_widget.dart b/lib/ui/remote_folder_gallery_widget.dart index d8745880f..1be53dd40 100644 --- a/lib/ui/remote_folder_gallery_widget.dart +++ b/lib/ui/remote_folder_gallery_widget.dart @@ -83,7 +83,7 @@ class _RemoteFolderGalleryWidgetState extends State { } try { folder.thumbnailPhoto = - await PhotoDB.instance.getLatestPhotoInRemoteFolder(folder.id); + await FileDB.instance.getLatestFileInRemoteFolder(folder.id); } catch (e) { _logger.warning(e.toString()); } diff --git a/lib/ui/remote_folder_page.dart b/lib/ui/remote_folder_page.dart index 8518c027c..f492bf44d 100644 --- a/lib/ui/remote_folder_page.dart +++ b/lib/ui/remote_folder_page.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:photos/db/photo_db.dart'; import 'package:photos/folder_service.dart'; import 'package:photos/models/folder.dart'; -import 'package:photos/models/photo.dart'; +import 'package:photos/models/file.dart'; import 'package:photos/ui/gallery.dart'; import 'package:photos/ui/gallery_app_bar_widget.dart'; @@ -16,7 +16,7 @@ class RemoteFolderPage extends StatefulWidget { } class _RemoteFolderPageState extends State { - Set _selectedPhotos = Set(); + Set _selectedPhotos = Set(); @override Widget build(Object context) { @@ -32,12 +32,11 @@ class _RemoteFolderPageState extends State { }); }, ), - body: Gallery( - () => PhotoDB.instance.getAllPhotosInFolder(widget.folder.id), + body: Gallery(() => FileDB.instance.getAllInFolder(widget.folder.id), onRefresh: () => FolderSharingService.instance.syncDiff(widget.folder), - selectedPhotos: _selectedPhotos, - onPhotoSelectionChange: (Set selectedPhotos) { + selectedFiles: _selectedPhotos, + onFileSelectionChange: (Set selectedPhotos) { setState( () { _selectedPhotos = selectedPhotos; diff --git a/lib/ui/thumbnail_widget.dart b/lib/ui/thumbnail_widget.dart index 64102a08a..753c8fe15 100644 --- a/lib/ui/thumbnail_widget.dart +++ b/lib/ui/thumbnail_widget.dart @@ -1,11 +1,11 @@ import 'package:flutter/material.dart'; import 'package:photos/core/cache/thumbnail_cache.dart'; -import 'package:photos/models/photo.dart'; +import 'package:photos/models/file.dart'; import 'package:logging/logging.dart'; import 'package:photos/core/constants.dart'; class ThumbnailWidget extends StatefulWidget { - final Photo photo; + final File photo; const ThumbnailWidget( this.photo, { @@ -78,7 +78,7 @@ class _ThumbnailWidgetState extends State { } void _loadNetworkImage() { - final url = widget.photo.thumbnailPath.isNotEmpty + final url = widget.photo.previewURL.isNotEmpty ? widget.photo.getThumbnailUrl() : widget.photo.getRemoteUrl(); _imageProvider = Image.network(url).image; diff --git a/lib/ui/video_widget.dart b/lib/ui/video_widget.dart new file mode 100644 index 000000000..e50d3f438 --- /dev/null +++ b/lib/ui/video_widget.dart @@ -0,0 +1,64 @@ +import 'package:chewie/chewie.dart'; +import 'package:flutter/widgets.dart'; +import 'package:photos/models/file.dart'; +import 'package:video_player/video_player.dart'; + +import 'loading_widget.dart'; + +class VideoWidget extends StatefulWidget { + final File file; + VideoWidget(this.file, {Key key}) : super(key: key); + + @override + _VideoWidgetState createState() => _VideoWidgetState(); +} + +class _VideoWidgetState extends State { + ChewieController _chewieController; + VideoPlayerController _videoPlayerController; + Future _future; + + @override + void initState() { + super.initState(); + _future = _initVideoPlayer(); + } + + @override + void dispose() { + _videoPlayerController.dispose(); + _chewieController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return FutureBuilder( + future: _future, + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return loadWidget; + } + return Chewie( + controller: _chewieController, + ); + }, + ); + } + + Future _initVideoPlayer() async { + final url = widget.file.localId == null + ? widget.file.getRemoteUrl() + : await (await widget.file.getAsset()).getMediaUrl(); + _videoPlayerController = VideoPlayerController.network(url); + await _videoPlayerController.initialize(); + setState(() { + _chewieController = ChewieController( + videoPlayerController: _videoPlayerController, + aspectRatio: _videoPlayerController.value.aspectRatio, + autoPlay: true, + autoInitialize: true, + ); + }); + } +} diff --git a/lib/ui/zoomable_image.dart b/lib/ui/zoomable_image.dart index 0b2efd5c2..20cb0201b 100644 --- a/lib/ui/zoomable_image.dart +++ b/lib/ui/zoomable_image.dart @@ -3,13 +3,13 @@ import 'package:flutter/widgets.dart'; import 'package:logging/logging.dart'; import 'package:photos/core/cache/image_cache.dart'; import 'package:photos/core/cache/thumbnail_cache.dart'; -import 'package:photos/models/photo.dart'; +import 'package:photos/models/file.dart'; import 'package:photos/ui/loading_widget.dart'; import 'package:photo_view/photo_view.dart'; import 'package:photos/core/constants.dart'; class ZoomableImage extends StatefulWidget { - final Photo photo; + final File photo; final Function(bool) shouldDisableScroll; ZoomableImage( @@ -67,7 +67,7 @@ class _ZoomableImageState extends State } void _loadNetworkImage() { - if (!_loadedSmallThumbnail && widget.photo.thumbnailPath.isNotEmpty) { + if (!_loadedSmallThumbnail && widget.photo.previewURL.isNotEmpty) { _imageProvider = Image.network( widget.photo.getThumbnailUrl(), gaplessPlayback: true, diff --git a/lib/utils/file_name_util.dart b/lib/utils/file_name_util.dart index 6661694b8..d9ae84975 100644 --- a/lib/utils/file_name_util.dart +++ b/lib/utils/file_name_util.dart @@ -1,14 +1,14 @@ -import 'package:photos/models/photo.dart'; +import 'package:photos/models/file.dart'; import 'package:path/path.dart'; -String getJPGFileNameForHEIC(Photo photo) { - return extension(photo.title) == ".HEIC" - ? basenameWithoutExtension(photo.title) + ".JPG" - : photo.title; +String getJPGFileNameForHEIC(File file) { + return extension(file.title) == ".HEIC" + ? basenameWithoutExtension(file.title) + ".JPG" + : file.title; } -String getHEICFileNameForJPG(Photo photo) { - return extension(photo.title) == ".JPG" - ? basenameWithoutExtension(photo.title) + ".HEIC" - : photo.title; +String getHEICFileNameForJPG(File file) { + return extension(file.title) == ".JPG" + ? basenameWithoutExtension(file.title) + ".HEIC" + : file.title; } diff --git a/lib/utils/share_util.dart b/lib/utils/share_util.dart index cc199a052..115edab42 100644 --- a/lib/utils/share_util.dart +++ b/lib/utils/share_util.dart @@ -1,23 +1,23 @@ import 'dart:typed_data'; import 'package:esys_flutter_share/esys_flutter_share.dart'; -import 'package:photos/models/photo.dart'; +import 'package:photos/models/file.dart'; import 'package:path/path.dart'; -Future share(Photo photo) async { - final bytes = await photo.getBytes(); - final filename = _getFilename(photo.title); - final ext = extension(photo.title); - final shareExt = photo.title.endsWith(".HEIC") +Future share(File file) async { + final bytes = await file.getBytes(); + final filename = _getFilename(file.title); + final ext = extension(file.title); + final shareExt = file.title.endsWith(".HEIC") ? "jpg" : ext.substring(1, ext.length).toLowerCase(); return Share.file(filename, filename, bytes, "image/" + shareExt); } -Future shareMultiple(List photos) async { +Future shareMultiple(List files) async { final shareContent = Map(); - for (Photo photo in photos) { - shareContent[_getFilename(photo.title)] = await photo.getBytes(); + for (File file in files) { + shareContent[_getFilename(file.title)] = await file.getBytes(); } return Share.files("images", shareContent, "*/*"); } diff --git a/pubspec.lock b/pubspec.lock index 3564edc89..a946b7a2c 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -36,6 +36,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.1.3" + chewie: + dependency: "direct main" + description: + name: chewie + url: "https://pub.dartlang.org" + source: hosted + version: "0.9.10" clock: dependency: transitive description: @@ -289,6 +296,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.1.8" + open_iconic_flutter: + dependency: transitive + description: + name: open_iconic_flutter + url: "https://pub.dartlang.org" + source: hosted + version: "0.3.0" package_info: dependency: transitive description: @@ -532,6 +546,27 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.0.8" + video_player: + dependency: "direct main" + description: + name: video_player + url: "https://pub.dartlang.org" + source: hosted + version: "0.10.11+1" + video_player_platform_interface: + dependency: transitive + description: + name: video_player_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.2" + video_player_web: + dependency: transitive + description: + name: video_player_web + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.3+1" visibility_detector: dependency: "direct main" description: @@ -539,6 +574,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.1.5" + wakelock: + dependency: transitive + description: + name: wakelock + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.4+1" xml: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index be159c1df..6b95e13d4 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -51,6 +51,8 @@ dependencies: pull_to_refresh: ^1.5.7 fluttertoast: ^4.0.1 extended_image: ^0.9.0 + video_player: ^0.10.11+1 + chewie: ^0.9.10 dev_dependencies: flutter_test: