diff --git a/lib/db/db_helper.dart b/lib/db/db_helper.dart index 84cd8b98d7..570e93fdc4 100644 --- a/lib/db/db_helper.dart +++ b/lib/db/db_helper.dart @@ -53,13 +53,22 @@ class DatabaseHelper { Future insertPhoto(Photo photo) async { Database db = await instance.database; - var row = new Map(); - row[columnLocalPath] = photo.localPath; - row[columnThumbnailPath] = photo.thumbnailPath; - row[columnUrl] = photo.url; - row[columnHash] = photo.hash; - row[columnSyncTimestamp] = photo.syncTimestamp; - return await db.insert(table, row); + return await db.insert(table, _getRowForPhoto(photo)); + } + + Future> insertPhotos(List photos) async { + Database db = await instance.database; + var batch = db.batch(); + int batchCounter = 0; + for (Photo photo in photos) { + if (batchCounter == 400) { + await batch.commit(); + batch = db.batch(); + } + batch.insert(table, _getRowForPhoto(photo)); + batchCounter++; + } + return await batch.commit(); } Future> getAllPhotos() async { @@ -108,4 +117,14 @@ class DatabaseHelper { } return photos; } + + Map _getRowForPhoto(Photo photo) { + var row = new Map(); + row[columnLocalPath] = photo.localPath; + row[columnThumbnailPath] = photo.thumbnailPath; + row[columnUrl] = photo.url; + row[columnHash] = photo.hash; + row[columnSyncTimestamp] = photo.syncTimestamp; + return row; + } } diff --git a/lib/main.dart b/lib/main.dart index ace9a85f1d..182763544f 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'package:flutter/material.dart'; import 'package:logger/logger.dart'; @@ -7,35 +8,26 @@ import 'package:myapp/photo_provider.dart'; import 'package:myapp/photo_sync_manager.dart'; import 'package:myapp/ui/gallery.dart'; import 'package:myapp/ui/loading_widget.dart'; +import 'package:photo_manager/photo_manager.dart'; import 'package:provider/provider.dart'; -import 'package:myapp/ui/gallery_page.dart'; final provider = PhotoProvider(); final logger = Logger(); void main() async { WidgetsFlutterBinding.ensureInitialized(); - await provider.refreshGalleryList(); - var assets = await provider.list[0].assetList; - var photoSyncManager = PhotoSyncManager(assets); - photoSyncManager.init(); runApp(MyApp2()); + await provider.refreshGalleryList(); + + provider.list[0].assetList.then((assets) { + var photoSyncManager = PhotoSyncManager(assets); + photoSyncManager.init(); + }); } -class MyApp extends StatelessWidget { - // This widget is the root of your application. - @override - Widget build(BuildContext context) { - logger.i("hello, world"); - return ChangeNotifierProvider.value( - value: provider, - child: MaterialApp( - title: 'Orma', - theme: ThemeData.dark(), - home: GalleryPage(path: provider.list[0]), - ), - ); - } +Future init(List assets) async { + var photoSyncManager = PhotoSyncManager(assets); + photoSyncManager.init(); } class MyApp2 extends StatelessWidget { diff --git a/lib/models/photo.dart b/lib/models/photo.dart index ee0bb37e2a..865c769674 100644 --- a/lib/models/photo.dart +++ b/lib/models/photo.dart @@ -1,7 +1,9 @@ import 'dart:io'; import 'package:crypto/crypto.dart'; +import 'package:flutter/foundation.dart'; import 'package:image/image.dart'; +import 'package:path_provider/path_provider.dart'; import 'package:photo_manager/photo_manager.dart'; class Photo { @@ -31,20 +33,27 @@ class Photo { Photo photo = Photo(); var file = (await asset.originFile); photo.localPath = file.path; - photo.thumbnailPath = getThumbnailPath(file.path); photo.hash = getHash(file); + var externalPath = (await getApplicationDocumentsDirectory()).path; + var thumbnailPath = externalPath + "/photos/thumbnails/" + photo.hash + ".thumbnail"; + var args = Map(); + args["assetPath"] = file.path; + args["thumbnailPath"] = thumbnailPath; + photo.thumbnailPath = thumbnailPath; + await compute(getThumbnailPath, args); return photo; } static String getHash(File file) { return sha256.convert(file.readAsBytesSync()).toString(); } - - static String getThumbnailPath(String path) { - Image image = decodeImage(File(path).readAsBytesSync()); - Image thumbnail = copyResize(image, width: 150); - String thumbnailPath = path + ".thumbnail"; - File(thumbnailPath)..writeAsBytesSync(encodePng(thumbnail)); - return thumbnailPath; - } +} + +Future getThumbnailPath(Map args) async { + return File(args["thumbnailPath"])..writeAsBytes(_getThumbnail(args["assetPath"])); +} + +List _getThumbnail(String path) { + Image image = decodeImage(File(path).readAsBytesSync()); + return encodePng(copyResize(image, width: 250)); } diff --git a/lib/photo_provider.dart b/lib/photo_provider.dart index 4e1672a9de..8d91cc26f0 100644 --- a/lib/photo_provider.dart +++ b/lib/photo_provider.dart @@ -88,7 +88,7 @@ class PhotoProvider extends ChangeNotifier { if (!result) { print("Did not get permission"); } - var galleryList = await PhotoManager.getAssetPathList(); + var galleryList = await PhotoManager.getAssetPathList(type: RequestType.image); galleryList.sort((s1, s2) { return s2.assetCount.compareTo(s1.assetCount); @@ -96,7 +96,6 @@ class PhotoProvider extends ChangeNotifier { this.list.clear(); this.list.addAll(galleryList); - print("Final List: " + list.toString()); } PathProvider getOrCreatePathProvider(AssetPathEntity pathEntity) { diff --git a/lib/photo_sync_manager.dart b/lib/photo_sync_manager.dart index fdc88cf580..cf8c8aac55 100644 --- a/lib/photo_sync_manager.dart +++ b/lib/photo_sync_manager.dart @@ -1,3 +1,6 @@ +import 'dart:async'; +import 'dart:io'; + import 'package:logger/logger.dart'; import 'package:myapp/db/db_helper.dart'; import 'package:myapp/photo_loader.dart'; @@ -25,12 +28,9 @@ class PhotoSyncManager { } Future init() async { - await _updateDatabase(); - try { + _updateDatabase().then((_) { _syncPhotos(); - } catch (e) { - _logger.e(e); - } + }); } Future _updateDatabase() async { @@ -38,17 +38,28 @@ class PhotoSyncManager { var lastDBUpdateTimestamp = prefs.getInt(_lastDBUpdateTimestampKey); if (lastDBUpdateTimestamp == null) { lastDBUpdateTimestamp = 0; + var externalPath = (await getApplicationDocumentsDirectory()).path; + new Directory(externalPath + "/photos/thumbnails") + .createSync(recursive: true); } - // for (AssetEntity asset in _assets) { - // if (asset.createDateTime.millisecondsSinceEpoch > lastDBUpdateTimestamp) { - // try { - // var photo = await Photo.fromAsset(asset); - // await DatabaseHelper.instance.insertPhoto(photo); - // } catch (e) { - // _logger.e(e); - // } - // } - // } + var photos = List(); + for (AssetEntity asset in _assets) { + if (asset.createDateTime.millisecondsSinceEpoch > lastDBUpdateTimestamp) { + try { + photos.add(await Photo.fromAsset(asset)); + } catch (e) { + _logger.e((await asset.originFile).path, e); + } + if (photos.length > 10) { + await DatabaseHelper.instance.insertPhotos(photos); + photos.clear(); + PhotoLoader.instance.reloadPhotos(); + _logger.i("Inserted " + photos.length.toString() + " photos."); + await prefs.setInt(_lastDBUpdateTimestampKey, asset.createDateTime.millisecondsSinceEpoch); + } + } + } + await DatabaseHelper.instance.insertPhotos(photos); PhotoLoader.instance.reloadPhotos(); return await prefs.setInt( _lastDBUpdateTimestampKey, DateTime.now().millisecondsSinceEpoch); @@ -104,7 +115,8 @@ class PhotoSyncManager { .download(_endpoint + photo.url, localPath) .catchError(_onError); photo.localPath = localPath; - photo.thumbnailPath = Photo.getThumbnailPath(localPath); + // TODO: Fix me + photo.thumbnailPath = localPath; await DatabaseHelper.instance.insertPhoto(photo); PhotoLoader.instance.reloadPhotos(); } diff --git a/lib/ui/detail_page.dart b/lib/ui/detail_page.dart index 2506f747d5..fcd27eaf7f 100644 --- a/lib/ui/detail_page.dart +++ b/lib/ui/detail_page.dart @@ -2,11 +2,13 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:myapp/core/lru_map.dart'; +import 'package:myapp/models/photo.dart'; +import 'package:share_extend/share_extend.dart'; class DetailPage extends StatefulWidget { - final File file; + final Photo photo; - const DetailPage({Key key, this.file}) : super(key: key); + const DetailPage({Key key, this.photo}) : super(key: key); @override _DetailPageState createState() => _DetailPageState(); @@ -16,6 +18,17 @@ class _DetailPageState extends State { @override Widget build(BuildContext context) { return Scaffold( + appBar: AppBar( + actions: [ + // action button + IconButton( + icon: Icon(Icons.share), + onPressed: () { + ShareExtend.share(widget.photo.localPath, "image"); + }, + ) + ], + ), body: Center( child: Container( child: _buildContent(context), @@ -29,15 +42,12 @@ class _DetailPageState extends State { onVerticalDragUpdate: (details) { Navigator.pop(context); }, - child: Hero( - tag: 'photo_' + widget.file.path, - child: ImageLruCache.getData(widget.file.path) == null - ? Image.file( - widget.file, - filterQuality: FilterQuality.low, - ) - : ImageLruCache.getData(widget.file.path), - ), + child: ImageLruCache.getData(widget.photo.localPath) == null + ? Image.file( + File(widget.photo.localPath), + filterQuality: FilterQuality.low, + ) + : ImageLruCache.getData(widget.photo.localPath), ); } } diff --git a/lib/ui/gallery.dart b/lib/ui/gallery.dart index c05ca9b33e..ec963cfab0 100644 --- a/lib/ui/gallery.dart +++ b/lib/ui/gallery.dart @@ -1,8 +1,8 @@ import 'dart:io'; -import 'dart:math'; import 'package:flutter/material.dart'; import 'package:logger/logger.dart'; +import 'package:myapp/models/photo.dart'; import 'package:myapp/photo_loader.dart'; import 'package:myapp/ui/image_widget.dart'; import 'package:provider/provider.dart'; @@ -63,24 +63,21 @@ class _GalleryState extends State { var photo = photoLoader.getPhotos()[index]; return GestureDetector( onTap: () async { - routeToDetailPage(photo.localPath, context); + routeToDetailPage(photo, context); }, onLongPress: () { - Toast.show(photo.thumbnailPath, context); + Toast.show(photo.localPath, context); }, - child: Hero( - child: Padding( - padding: const EdgeInsets.all(1.0), - child: ImageWidget(path: photo.thumbnailPath), - ), - tag: 'photo_' + photo.localPath, + child: Padding( + padding: const EdgeInsets.all(1.0), + child: ImageWidget(path: photo.thumbnailPath), ), ); } - void routeToDetailPage(String path, BuildContext context) async { + void routeToDetailPage(Photo photo, BuildContext context) async { final page = DetailPage( - file: File(path), + photo: photo, ); Navigator.of(context).push( MaterialPageRoute( diff --git a/lib/ui/gallery_page.dart b/lib/ui/gallery_page.dart deleted file mode 100644 index 6045312491..0000000000 --- a/lib/ui/gallery_page.dart +++ /dev/null @@ -1,117 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:myapp/photo_provider.dart'; -import 'package:myapp/ui/image_widget.dart'; -import 'package:photo_manager/photo_manager.dart'; -import 'package:myapp/ui/change_notifier_builder.dart'; -import 'package:myapp/ui/loading_widget.dart'; -import 'package:myapp/ui/detail_page.dart'; -import 'package:provider/provider.dart'; - -class GalleryPage extends StatefulWidget { - final AssetPathEntity path; - - const GalleryPage({Key key, this.path}) : super(key: key); - - @override - _GalleryPageState createState() => _GalleryPageState(); -} - -class _GalleryPageState extends State { - AssetPathEntity get path => widget.path; - - PathProvider get provider => - Provider.of(context).getOrCreatePathProvider(path); - - @override - void initState() { - super.initState(); - } - - @override - Widget build(BuildContext context) { - return ChangeNotifierBuilder( - value: provider, - builder: (_, __) { - var length = path.assetCount; - return Scaffold( - appBar: AppBar( - title: Text("Orma"), - ), - body: buildRefreshIndicator(length), - ); - }, - ); - } - - Widget buildRefreshIndicator(int length) { - if (!provider.isInit) { - provider.onRefresh(); - return Center( - child: Text("loading"), - ); - } - return RefreshIndicator( - onRefresh: _onRefresh, - child: Scrollbar( - child: GridView.builder( - itemBuilder: _buildItem, - itemCount: provider.showItemCount, - gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 4, - ), - ), - ), - ); - } - - Widget _buildItem(BuildContext context, int index) { - final list = provider.list; - if (list.length == index) { - onLoadMore(); - return loadWidget; - } - - if (index > list.length) { - return Container(); - } - - final entity = list[index]; - return GestureDetector( - onTap: () async { - routeToDetailPage(entity); - }, - child: ImageWidget( - key: ValueKey(entity), - path: "", - ), - ); - } - - void routeToDetailPage(AssetEntity entity) async { - final originFile = await entity.originFile; - final page = DetailPage( - file: originFile, - ); - Navigator.of(context).push( - MaterialPageRoute( - builder: (BuildContext context) { - return page; - }, - ), - ); - } - - Future onLoadMore() async { - if (!mounted) { - return; - } - await provider.onLoadMore(); - } - - Future _onRefresh() async { - if (!mounted) { - return; - } - await provider.onRefresh(); - } -} \ No newline at end of file diff --git a/pubspec.lock b/pubspec.lock index 422497b3f1..f1949430e0 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -170,13 +170,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.0.1" - pedantic: - dependency: transitive - description: - name: pedantic - url: "https://pub.dartlang.org" - source: hosted - version: "1.8.0+1" petitparser: dependency: transitive description: @@ -219,6 +212,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.0.5" + share_extend: + dependency: "direct main" + description: + name: share_extend + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.2" shared_preferences: dependency: "direct main" description: @@ -314,7 +314,7 @@ packages: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.2.11" + version: "0.2.15" toast: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 56407ce8ee..e43bb5bb86 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -34,6 +34,7 @@ dependencies: crypto: ^2.1.3 toast: ^0.1.5 image: ^2.1.4 + share_extend: "^1.1.2" dev_dependencies: flutter_test: diff --git a/test/widget_test.dart b/test/widget_test.dart deleted file mode 100644 index 0ebee46597..0000000000 --- a/test/widget_test.dart +++ /dev/null @@ -1,30 +0,0 @@ -// This is a basic Flutter widget test. -// -// To perform an interaction with a widget in your test, use the WidgetTester -// utility that Flutter provides. For example, you can send tap and scroll -// gestures. You can also use WidgetTester to find child widgets in the widget -// tree, read text, and verify that the values of widget properties are correct. - -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; - -import 'package:myapp/main.dart'; - -void main() { - testWidgets('Counter increments smoke test', (WidgetTester tester) async { - // Build our app and trigger a frame. - await tester.pumpWidget(MyApp()); - - // Verify that our counter starts at 0. - expect(find.text('0'), findsOneWidget); - expect(find.text('1'), findsNothing); - - // Tap the '+' icon and trigger a frame. - await tester.tap(find.byIcon(Icons.add)); - await tester.pump(); - - // Verify that our counter has incremented. - expect(find.text('0'), findsNothing); - expect(find.text('1'), findsOneWidget); - }); -}