Add some buttons
This commit is contained in:
parent
81882999c1
commit
f5225cda20
11 changed files with 122 additions and 230 deletions
|
@ -53,13 +53,22 @@ class DatabaseHelper {
|
|||
|
||||
Future<int> insertPhoto(Photo photo) async {
|
||||
Database db = await instance.database;
|
||||
var row = new Map<String, dynamic>();
|
||||
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<List<dynamic>> insertPhotos(List<Photo> 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<List<Photo>> getAllPhotos() async {
|
||||
|
@ -108,4 +117,14 @@ class DatabaseHelper {
|
|||
}
|
||||
return photos;
|
||||
}
|
||||
|
||||
Map<String, dynamic> _getRowForPhoto(Photo photo) {
|
||||
var row = new Map<String, dynamic>();
|
||||
row[columnLocalPath] = photo.localPath;
|
||||
row[columnThumbnailPath] = photo.thumbnailPath;
|
||||
row[columnUrl] = photo.url;
|
||||
row[columnHash] = photo.hash;
|
||||
row[columnSyncTimestamp] = photo.syncTimestamp;
|
||||
return row;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<PhotoProvider>.value(
|
||||
value: provider,
|
||||
child: MaterialApp(
|
||||
title: 'Orma',
|
||||
theme: ThemeData.dark(),
|
||||
home: GalleryPage(path: provider.list[0]),
|
||||
),
|
||||
);
|
||||
}
|
||||
Future<void> init(List<AssetEntity> assets) async {
|
||||
var photoSyncManager = PhotoSyncManager(assets);
|
||||
photoSyncManager.init();
|
||||
}
|
||||
|
||||
class MyApp2 extends StatelessWidget {
|
||||
|
|
|
@ -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<String, String>();
|
||||
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<void> getThumbnailPath(Map<String, String> args) async {
|
||||
return File(args["thumbnailPath"])..writeAsBytes(_getThumbnail(args["assetPath"]));
|
||||
}
|
||||
|
||||
List<int> _getThumbnail(String path) {
|
||||
Image image = decodeImage(File(path).readAsBytesSync());
|
||||
return encodePng(copyResize(image, width: 250));
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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<void> init() async {
|
||||
await _updateDatabase();
|
||||
try {
|
||||
_updateDatabase().then((_) {
|
||||
_syncPhotos();
|
||||
} catch (e) {
|
||||
_logger.e(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Future<bool> _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<Photo>();
|
||||
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();
|
||||
}
|
||||
|
|
|
@ -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<DetailPage> {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
actions: <Widget>[
|
||||
// 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<DetailPage> {
|
|||
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),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<Gallery> {
|
|||
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(
|
||||
|
|
|
@ -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<GalleryPage> {
|
||||
AssetPathEntity get path => widget.path;
|
||||
|
||||
PathProvider get provider =>
|
||||
Provider.of<PhotoProvider>(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<void> onLoadMore() async {
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
await provider.onLoadMore();
|
||||
}
|
||||
|
||||
Future<void> _onRefresh() async {
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
await provider.onRefresh();
|
||||
}
|
||||
}
|
16
pubspec.lock
16
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:
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
}
|
Loading…
Reference in a new issue