Merge pull request #4 from ente-io/collections
Update collection interactions
This commit is contained in:
commit
0f0eb4767d
17 changed files with 216 additions and 427 deletions
|
@ -102,7 +102,7 @@ class Configuration {
|
|||
|
||||
String getHttpEndpoint() {
|
||||
if (kDebugMode) {
|
||||
return "http://192.168.1.3:80";
|
||||
return "http://192.168.0.100";
|
||||
}
|
||||
return "https://api.staging.ente.io";
|
||||
}
|
||||
|
@ -144,7 +144,7 @@ class Configuration {
|
|||
// return _preferences.getBool(hasOptedForE2EKey);
|
||||
}
|
||||
|
||||
Set<String> getFoldersToBackUp() {
|
||||
Set<String> getPathsToBackUp() {
|
||||
if (_preferences.containsKey(foldersToBackUpKey)) {
|
||||
return _preferences.getStringList(foldersToBackUpKey).toSet();
|
||||
} else {
|
||||
|
@ -158,10 +158,16 @@ class Configuration {
|
|||
}
|
||||
}
|
||||
|
||||
Future<void> setFoldersToBackUp(Set<String> folders) async {
|
||||
Future<void> setPathsToBackUp(Set<String> folders) async {
|
||||
await _preferences.setStringList(foldersToBackUpKey, folders.toList());
|
||||
}
|
||||
|
||||
Future<void> addPathToFoldersToBeBackedUp(String path) async {
|
||||
final currentPaths = getPathsToBackUp();
|
||||
currentPaths.add(path);
|
||||
return setPathsToBackUp(currentPaths);
|
||||
}
|
||||
|
||||
Future<void> setKeyAttributes(KeyAttributes attributes) async {
|
||||
await _preferences.setString(
|
||||
keyAttributesKey, attributes == null ? null : attributes.toJson());
|
||||
|
|
|
@ -1,106 +0,0 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:path/path.dart';
|
||||
import 'package:photos/models/folder.dart';
|
||||
import 'package:sqflite/sqflite.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
|
||||
class FoldersDB {
|
||||
static final _databaseName = "ente.folder.db";
|
||||
static final _databaseVersion = 1;
|
||||
|
||||
static final table = 'folders';
|
||||
|
||||
static final columnId = 'id';
|
||||
static final columnName = 'name';
|
||||
static final columnOwnerID = 'owner_id';
|
||||
static final columnDeviceFolder = 'device_folder';
|
||||
static final columnSharedWith = 'shared_with';
|
||||
static final columnUpdationTime = 'updation_time';
|
||||
|
||||
FoldersDB._privateConstructor();
|
||||
static final FoldersDB instance = FoldersDB._privateConstructor();
|
||||
|
||||
static Database _database;
|
||||
Future<Database> get database async {
|
||||
if (_database != null) return _database;
|
||||
_database = await _initDatabase();
|
||||
return _database;
|
||||
}
|
||||
|
||||
_initDatabase() async {
|
||||
Directory documentsDirectory = await getApplicationDocumentsDirectory();
|
||||
String path = join(documentsDirectory.path, _databaseName);
|
||||
return await openDatabase(path,
|
||||
version: _databaseVersion, onCreate: _onCreate);
|
||||
}
|
||||
|
||||
Future _onCreate(Database db, int version) async {
|
||||
await db.execute('''
|
||||
CREATE TABLE $table (
|
||||
$columnId INTEGER PRIMARY KEY NOT NULL,
|
||||
$columnName TEXT NOT NULL,
|
||||
$columnOwnerID INTEGER NOT NULL,
|
||||
$columnDeviceFolder TEXT NOT NULL,
|
||||
$columnSharedWith TEXT NOT NULL,
|
||||
$columnUpdationTime INTEGER NOT NULL,
|
||||
UNIQUE($columnOwnerID, $columnDeviceFolder)
|
||||
)
|
||||
''');
|
||||
}
|
||||
|
||||
Future<int> putFolder(Folder folder) async {
|
||||
final db = await instance.database;
|
||||
return await db.insert(table, _getRowForFolder(folder),
|
||||
conflictAlgorithm: ConflictAlgorithm.replace);
|
||||
}
|
||||
|
||||
Future<List<Folder>> getFolders() async {
|
||||
final db = await instance.database;
|
||||
final results = await db.query(
|
||||
table,
|
||||
orderBy: '$columnUpdationTime DESC',
|
||||
);
|
||||
return _convertToFolders(results);
|
||||
}
|
||||
|
||||
Future<int> deleteFolder(Folder folder) async {
|
||||
final db = await instance.database;
|
||||
return db.delete(
|
||||
table,
|
||||
where: '$columnId =?',
|
||||
whereArgs: [folder.id],
|
||||
);
|
||||
}
|
||||
|
||||
List<Folder> _convertToFolders(List<Map<String, dynamic>> results) {
|
||||
final folders = List<Folder>();
|
||||
for (final result in results) {
|
||||
folders.add(_getFolderFromRow(result));
|
||||
}
|
||||
return folders;
|
||||
}
|
||||
|
||||
Map<String, dynamic> _getRowForFolder(Folder folder) {
|
||||
final row = new Map<String, dynamic>();
|
||||
row[columnId] = folder.id;
|
||||
row[columnName] = folder.name;
|
||||
row[columnOwnerID] = folder.ownerID;
|
||||
row[columnDeviceFolder] = folder.deviceFolder;
|
||||
row[columnSharedWith] = jsonEncode(folder.sharedWith.toList());
|
||||
row[columnUpdationTime] = folder.updationTime;
|
||||
return row;
|
||||
}
|
||||
|
||||
Folder _getFolderFromRow(Map<String, dynamic> row) {
|
||||
return Folder(
|
||||
row[columnId],
|
||||
row[columnName],
|
||||
row[columnOwnerID],
|
||||
row[columnDeviceFolder],
|
||||
(jsonDecode(row[columnSharedWith]) as List<dynamic>).cast<int>().toSet(),
|
||||
row[columnUpdationTime],
|
||||
);
|
||||
}
|
||||
}
|
76
lib/db/public_keys_db.dart
Normal file
76
lib/db/public_keys_db.dart
Normal file
|
@ -0,0 +1,76 @@
|
|||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:path/path.dart';
|
||||
import 'package:photos/models/public_key.dart';
|
||||
import 'package:sqflite/sqflite.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
|
||||
class PublicKeysDB {
|
||||
static final _databaseName = "ente.public_keys.db";
|
||||
static final _databaseVersion = 1;
|
||||
|
||||
static final table = 'public_keys';
|
||||
|
||||
static final columnEmail = 'email';
|
||||
static final columnPublicKey = 'public_key';
|
||||
|
||||
PublicKeysDB._privateConstructor();
|
||||
static final PublicKeysDB instance = PublicKeysDB._privateConstructor();
|
||||
|
||||
static Database _database;
|
||||
Future<Database> get database async {
|
||||
if (_database != null) return _database;
|
||||
_database = await _initDatabase();
|
||||
return _database;
|
||||
}
|
||||
|
||||
_initDatabase() async {
|
||||
Directory documentsDirectory = await getApplicationDocumentsDirectory();
|
||||
String path = join(documentsDirectory.path, _databaseName);
|
||||
return await openDatabase(
|
||||
path,
|
||||
version: _databaseVersion,
|
||||
onCreate: _onCreate,
|
||||
);
|
||||
}
|
||||
|
||||
Future _onCreate(Database db, int version) async {
|
||||
await db.execute('''
|
||||
CREATE TABLE $table (
|
||||
$columnEmail TEXT PRIMARY KEY NOT NULL,
|
||||
$columnPublicKey TEXT NOT NULL
|
||||
)
|
||||
''');
|
||||
}
|
||||
|
||||
Future<int> setKey(PublicKey key) async {
|
||||
final db = await instance.database;
|
||||
return db.insert(table, _getRow(key),
|
||||
conflictAlgorithm: ConflictAlgorithm.replace);
|
||||
}
|
||||
|
||||
Future<List<PublicKey>> searchByEmail(String email) async {
|
||||
final db = await instance.database;
|
||||
return _convertRows(await db.query(
|
||||
table,
|
||||
where: '$columnEmail LIKE ?',
|
||||
whereArgs: ['%$email%'],
|
||||
));
|
||||
}
|
||||
|
||||
Map<String, dynamic> _getRow(PublicKey key) {
|
||||
var row = new Map<String, dynamic>();
|
||||
row[columnEmail] = key.email;
|
||||
row[columnPublicKey] = key.publicKey;
|
||||
return row;
|
||||
}
|
||||
|
||||
List<PublicKey> _convertRows(List<Map<String, dynamic>> rows) {
|
||||
final keys = List<PublicKey>();
|
||||
for (final row in rows) {
|
||||
keys.add(PublicKey(row[columnEmail], row[columnPublicKey]));
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
}
|
17
lib/models/filters/device_folder_name_filter.dart
Normal file
17
lib/models/filters/device_folder_name_filter.dart
Normal file
|
@ -0,0 +1,17 @@
|
|||
import 'package:photos/core/configuration.dart';
|
||||
import 'package:photos/models/filters/gallery_items_filter.dart';
|
||||
import 'package:photos/models/file.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
|
||||
class DeviceFolderNameFilter implements GalleryItemsFilter {
|
||||
final String folderName;
|
||||
|
||||
DeviceFolderNameFilter(this.folderName);
|
||||
|
||||
@override
|
||||
bool shouldInclude(File file) {
|
||||
return (file.ownerID == null ||
|
||||
file.ownerID == Configuration.instance.getUserID()) &&
|
||||
path.basename(file.deviceFolder) == folderName;
|
||||
}
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
import 'package:photos/models/filters/gallery_items_filter.dart';
|
||||
import 'package:photos/models/file.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
|
||||
class FolderNameFilter implements GalleryItemsFilter {
|
||||
final String folderName;
|
||||
|
||||
FolderNameFilter(this.folderName);
|
||||
|
||||
@override
|
||||
bool shouldInclude(File file) {
|
||||
return path.basename(file.deviceFolder) == folderName;
|
||||
}
|
||||
}
|
|
@ -1,67 +0,0 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:photos/models/file.dart';
|
||||
|
||||
class Folder {
|
||||
final int id;
|
||||
final String name;
|
||||
final int ownerID;
|
||||
final String deviceFolder;
|
||||
final Set<int> sharedWith;
|
||||
final int updationTime;
|
||||
File thumbnailPhoto;
|
||||
|
||||
Folder(
|
||||
this.id,
|
||||
this.name,
|
||||
this.ownerID,
|
||||
this.deviceFolder,
|
||||
this.sharedWith,
|
||||
this.updationTime,
|
||||
);
|
||||
|
||||
static Folder fromMap(Map<String, dynamic> map) {
|
||||
if (map == null) return null;
|
||||
|
||||
return Folder(
|
||||
map['id'],
|
||||
map['name'],
|
||||
map['ownerID'],
|
||||
map['deviceFolder'],
|
||||
Set<int>.from(map['sharedWith']),
|
||||
map['updationTime'],
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'Folder(id: $id, name: $name, ownerID: $ownerID, deviceFolder: $deviceFolder, sharedWith: $sharedWith, updationTime: $updationTime)';
|
||||
}
|
||||
|
||||
Map<String, dynamic> toMap() {
|
||||
return {
|
||||
'id': id,
|
||||
'name': name,
|
||||
'ownerID': ownerID,
|
||||
'deviceFolder': deviceFolder,
|
||||
'sharedWith': sharedWith.toList(),
|
||||
'updationTime': updationTime,
|
||||
};
|
||||
}
|
||||
|
||||
String toJson() => json.encode(toMap());
|
||||
|
||||
static Folder fromJson(String source) => fromMap(json.decode(source));
|
||||
|
||||
@override
|
||||
bool operator ==(Object o) {
|
||||
if (identical(this, o)) return true;
|
||||
|
||||
return o is Folder && o.id == id;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
return id.hashCode;
|
||||
}
|
||||
}
|
6
lib/models/public_key.dart
Normal file
6
lib/models/public_key.dart
Normal file
|
@ -0,0 +1,6 @@
|
|||
class PublicKey {
|
||||
final String email;
|
||||
final String publicKey;
|
||||
|
||||
PublicKey(this.email, this.publicKey);
|
||||
}
|
|
@ -184,7 +184,7 @@ class SyncService {
|
|||
}
|
||||
|
||||
Future<void> _uploadDiff() async {
|
||||
final foldersToBackUp = Configuration.instance.getFoldersToBackUp();
|
||||
final foldersToBackUp = Configuration.instance.getPathsToBackUp();
|
||||
List<File> filesToBeUploaded =
|
||||
await _db.getFilesToBeUploadedWithinFolders(foldersToBackUp);
|
||||
for (int i = 0; i < filesToBeUploaded.length; i++) {
|
||||
|
|
|
@ -4,9 +4,11 @@ import 'package:flutter/widgets.dart';
|
|||
import 'package:logging/logging.dart';
|
||||
import 'package:photos/core/configuration.dart';
|
||||
import 'package:photos/core/event_bus.dart';
|
||||
import 'package:photos/db/public_keys_db.dart';
|
||||
|
||||
import 'package:photos/events/user_authenticated_event.dart';
|
||||
import 'package:photos/models/key_attributes.dart';
|
||||
import 'package:photos/models/public_key.dart';
|
||||
import 'package:photos/ui/ott_verification_page.dart';
|
||||
import 'package:photos/ui/passphrase_entry_page.dart';
|
||||
import 'package:photos/ui/passphrase_reentry_page.dart';
|
||||
|
@ -47,24 +49,20 @@ class UserService {
|
|||
});
|
||||
}
|
||||
|
||||
Future<String> getPublicKey({String email, int userID}) async {
|
||||
final queryParams = Map<String, dynamic>();
|
||||
if (userID != null) {
|
||||
queryParams["userID"] = userID;
|
||||
} else {
|
||||
queryParams["email"] = email;
|
||||
}
|
||||
Future<String> getPublicKey(String email) async {
|
||||
try {
|
||||
final response = await _dio.get(
|
||||
Configuration.instance.getHttpEndpoint() + "/users/public-key",
|
||||
queryParameters: queryParams,
|
||||
queryParameters: {"email": email},
|
||||
options: Options(
|
||||
headers: {
|
||||
"X-Auth-Token": Configuration.instance.getToken(),
|
||||
},
|
||||
),
|
||||
);
|
||||
return response.data["publicKey"];
|
||||
final publicKey = response.data["publicKey"];
|
||||
await PublicKeysDB.instance.setKey(PublicKey(email, publicKey));
|
||||
return publicKey;
|
||||
} on DioError catch (e) {
|
||||
_logger.info(e);
|
||||
return null;
|
||||
|
|
|
@ -8,7 +8,7 @@ import 'package:photos/events/local_photos_updated_event.dart';
|
|||
import 'package:photos/services/favorites_service.dart';
|
||||
import 'package:photos/models/device_folder.dart';
|
||||
import 'package:photos/models/filters/favorite_items_filter.dart';
|
||||
import 'package:photos/models/filters/folder_name_filter.dart';
|
||||
import 'package:photos/models/filters/device_folder_name_filter.dart';
|
||||
import 'package:photos/models/filters/video_file_filter.dart';
|
||||
import 'package:photos/ui/common_elements.dart';
|
||||
import 'package:photos/ui/device_folder_page.dart';
|
||||
|
@ -79,7 +79,7 @@ class _DeviceFolderGalleryWidgetState extends State<DeviceFolderGalleryWidget> {
|
|||
final file = await FilesDB.instance.getLatestFileInPath(path);
|
||||
final folderName = p.basename(path);
|
||||
folders.add(
|
||||
DeviceFolder(folderName, path, file, FolderNameFilter(folderName)));
|
||||
DeviceFolder(folderName, path, file, DeviceFolderNameFilter(folderName)));
|
||||
}
|
||||
folders.sort((first, second) {
|
||||
return second.thumbnail.creationTime
|
||||
|
|
|
@ -18,7 +18,6 @@ import 'package:photos/ui/gallery_app_bar_widget.dart';
|
|||
import 'package:photos/ui/loading_photos_widget.dart';
|
||||
import 'package:photos/ui/loading_widget.dart';
|
||||
import 'package:photos/ui/memories_widget.dart';
|
||||
import 'package:photos/ui/remote_folder_gallery_widget.dart';
|
||||
import 'package:photos/ui/search_page.dart';
|
||||
import 'package:photos/services/user_service.dart';
|
||||
import 'package:photos/ui/shared_collections_gallery.dart';
|
||||
|
|
|
@ -1,139 +0,0 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:photos/core/configuration.dart';
|
||||
import 'package:photos/core/event_bus.dart';
|
||||
import 'package:photos/db/folders_db.dart';
|
||||
import 'package:photos/db/files_db.dart';
|
||||
import 'package:photos/events/remote_sync_event.dart';
|
||||
import 'package:photos/models/folder.dart';
|
||||
import 'package:photos/ui/common_elements.dart';
|
||||
import 'package:photos/ui/loading_widget.dart';
|
||||
import 'package:photos/ui/remote_folder_page.dart';
|
||||
import 'package:photos/ui/thumbnail_widget.dart';
|
||||
|
||||
class RemoteFolderGalleryWidget extends StatefulWidget {
|
||||
const RemoteFolderGalleryWidget({Key key}) : super(key: key);
|
||||
|
||||
@override
|
||||
_RemoteFolderGalleryWidgetState createState() =>
|
||||
_RemoteFolderGalleryWidgetState();
|
||||
}
|
||||
|
||||
class _RemoteFolderGalleryWidgetState extends State<RemoteFolderGalleryWidget> {
|
||||
Logger _logger = Logger("RemoteFolderGalleryWidget");
|
||||
StreamSubscription<RemoteSyncEvent> _subscription;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_subscription = Bus.instance.on<RemoteSyncEvent>().listen((event) {
|
||||
if (event.success) {
|
||||
setState(() {});
|
||||
}
|
||||
});
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FutureBuilder<List<Folder>>(
|
||||
future: _getRemoteFolders(),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData) {
|
||||
if (snapshot.data.isEmpty) {
|
||||
return nothingToSeeHere;
|
||||
} else {
|
||||
return _getRemoteFolderGalleryWidget(snapshot.data);
|
||||
}
|
||||
} else if (snapshot.hasError) {
|
||||
_logger.shout(snapshot.error);
|
||||
return Center(child: Text(snapshot.error.toString()));
|
||||
} else {
|
||||
return loadWidget;
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _getRemoteFolderGalleryWidget(List<Folder> folders) {
|
||||
return Container(
|
||||
margin: EdgeInsets.only(top: 24),
|
||||
child: GridView.builder(
|
||||
shrinkWrap: true,
|
||||
padding: EdgeInsets.only(bottom: 12),
|
||||
physics: ScrollPhysics(), // to disable GridView's scrolling
|
||||
itemBuilder: (context, index) {
|
||||
return _buildFolder(context, folders[index]);
|
||||
},
|
||||
itemCount: folders.length,
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: 2,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<List<Folder>> _getRemoteFolders() async {
|
||||
final folders = await FoldersDB.instance.getFolders();
|
||||
final filteredFolders = List<Folder>();
|
||||
for (final folder in folders) {
|
||||
if (folder.ownerID == Configuration.instance.getUserID()) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
folder.thumbnailPhoto =
|
||||
await FilesDB.instance.getLatestFileInRemoteFolder(folder.id);
|
||||
} catch (e) {
|
||||
_logger.warning(e.toString());
|
||||
}
|
||||
filteredFolders.add(folder);
|
||||
}
|
||||
return filteredFolders;
|
||||
}
|
||||
|
||||
Widget _buildFolder(BuildContext context, Folder folder) {
|
||||
return GestureDetector(
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
Container(
|
||||
child: folder.thumbnailPhoto ==
|
||||
null // When the user has shared a folder without photos
|
||||
? Icon(Icons.error)
|
||||
: Hero(
|
||||
tag: "remote_folder" + folder.thumbnailPhoto.tag(),
|
||||
child: ThumbnailWidget(folder.thumbnailPhoto)),
|
||||
height: 150,
|
||||
width: 150,
|
||||
),
|
||||
Padding(padding: EdgeInsets.all(2)),
|
||||
Expanded(
|
||||
child: Text(
|
||||
folder.name,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
onTap: () {
|
||||
final page = RemoteFolderPage(folder);
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (BuildContext context) {
|
||||
return page;
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_subscription.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
|
@ -1,43 +0,0 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:photos/db/files_db.dart';
|
||||
import 'package:photos/models/folder.dart';
|
||||
import 'package:photos/models/selected_files.dart';
|
||||
import 'package:photos/ui/gallery.dart';
|
||||
import 'package:photos/ui/gallery_app_bar_widget.dart';
|
||||
|
||||
class RemoteFolderPage extends StatefulWidget {
|
||||
final Folder folder;
|
||||
|
||||
const RemoteFolderPage(this.folder, {Key key}) : super(key: key);
|
||||
|
||||
@override
|
||||
_RemoteFolderPageState createState() => _RemoteFolderPageState();
|
||||
}
|
||||
|
||||
class _RemoteFolderPageState extends State<RemoteFolderPage> {
|
||||
final _selectedFiles = SelectedFiles();
|
||||
|
||||
@override
|
||||
Widget build(Object context) {
|
||||
var gallery = Gallery(
|
||||
asyncLoader: (lastFile, limit) => FilesDB.instance.getAllInFolder(
|
||||
widget.folder.id,
|
||||
lastFile == null
|
||||
? DateTime.now().microsecondsSinceEpoch
|
||||
: lastFile.creationTime,
|
||||
limit),
|
||||
// onRefresh: () => FolderSharingService.instance.syncDiff(widget.folder),
|
||||
tagPrefix: "remote_folder",
|
||||
selectedFiles: _selectedFiles,
|
||||
);
|
||||
return Scaffold(
|
||||
appBar: GalleryAppBarWidget(
|
||||
GalleryAppBarType.shared_collection,
|
||||
widget.folder.name,
|
||||
_selectedFiles,
|
||||
widget.folder.deviceFolder,
|
||||
),
|
||||
body: gallery,
|
||||
);
|
||||
}
|
||||
}
|
|
@ -169,7 +169,7 @@ class _BackedUpFoldersWidgetState extends State<BackedUpFoldersWidget> {
|
|||
snapshot.data.sort((first, second) {
|
||||
return first.toLowerCase().compareTo(second.toLowerCase());
|
||||
});
|
||||
final backedUpFolders = Configuration.instance.getFoldersToBackUp();
|
||||
final backedUpFolders = Configuration.instance.getPathsToBackUp();
|
||||
final foldersWidget = List<Row>();
|
||||
for (final folder in snapshot.data) {
|
||||
foldersWidget.add(Row(children: [
|
||||
|
@ -182,7 +182,7 @@ class _BackedUpFoldersWidgetState extends State<BackedUpFoldersWidget> {
|
|||
backedUpFolders.remove(folder);
|
||||
}
|
||||
await Configuration.instance
|
||||
.setFoldersToBackUp(backedUpFolders);
|
||||
.setPathsToBackUp(backedUpFolders);
|
||||
setState(() {});
|
||||
},
|
||||
),
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
import 'dart:developer';
|
||||
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_typeahead/flutter_typeahead.dart';
|
||||
import 'package:photos/core/configuration.dart';
|
||||
import 'package:photos/db/public_keys_db.dart';
|
||||
import 'package:photos/models/collection.dart';
|
||||
import 'package:photos/models/public_key.dart';
|
||||
import 'package:photos/services/collections_service.dart';
|
||||
import 'package:photos/services/sync_service.dart';
|
||||
import 'package:photos/services/user_service.dart';
|
||||
import 'package:photos/ui/common_elements.dart';
|
||||
import 'package:photos/ui/loading_widget.dart';
|
||||
|
@ -38,7 +41,7 @@ class _ShareFolderWidgetState extends State<ShareFolderWidget> {
|
|||
: CollectionsService.instance.getSharees(widget.collection.id),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData) {
|
||||
return SharingDialog(widget.collection, snapshot.data);
|
||||
return SharingDialog(widget.collection, snapshot.data, widget.path);
|
||||
} else if (snapshot.hasError) {
|
||||
return Text(snapshot.error.toString());
|
||||
} else {
|
||||
|
@ -52,8 +55,10 @@ class _ShareFolderWidgetState extends State<ShareFolderWidget> {
|
|||
class SharingDialog extends StatefulWidget {
|
||||
final Collection collection;
|
||||
final List<String> sharees;
|
||||
final String path;
|
||||
|
||||
SharingDialog(this.collection, this.sharees, {Key key}) : super(key: key);
|
||||
SharingDialog(this.collection, this.sharees, this.path, {Key key})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
_SharingDialogState createState() => _SharingDialogState();
|
||||
|
@ -77,22 +82,7 @@ class _SharingDialogState extends State<SharingDialog> {
|
|||
}
|
||||
}
|
||||
if (_showEntryField) {
|
||||
children.add(TextField(
|
||||
keyboardType: TextInputType.emailAddress,
|
||||
decoration: InputDecoration(
|
||||
border: InputBorder.none,
|
||||
hintText: "email@your-friend.com",
|
||||
),
|
||||
autofocus: true,
|
||||
onChanged: (s) {
|
||||
setState(() {
|
||||
_email = s;
|
||||
});
|
||||
},
|
||||
onSubmitted: (s) {
|
||||
_addEmailToCollection(context);
|
||||
},
|
||||
));
|
||||
children.add(_getEmailField());
|
||||
}
|
||||
children.add(Padding(
|
||||
padding: EdgeInsets.all(8),
|
||||
|
@ -116,8 +106,8 @@ class _SharingDialogState extends State<SharingDialog> {
|
|||
width: 220,
|
||||
child: button(
|
||||
"Add",
|
||||
onPressed: () async {
|
||||
await _addEmailToCollection(context);
|
||||
onPressed: () {
|
||||
_addEmailToCollection(_email, null);
|
||||
},
|
||||
),
|
||||
));
|
||||
|
@ -139,22 +129,63 @@ class _SharingDialogState extends State<SharingDialog> {
|
|||
);
|
||||
}
|
||||
|
||||
Future<void> _addEmailToCollection(BuildContext context) async {
|
||||
if (!isValidEmail(_email)) {
|
||||
Widget _getEmailField() {
|
||||
return TypeAheadField(
|
||||
textFieldConfiguration: TextFieldConfiguration(
|
||||
keyboardType: TextInputType.emailAddress,
|
||||
autofocus: true,
|
||||
decoration: InputDecoration(
|
||||
border: InputBorder.none,
|
||||
hintText: "email@your-friend.com",
|
||||
),
|
||||
),
|
||||
hideOnEmpty: true,
|
||||
loadingBuilder: (context) {
|
||||
return loadWidget;
|
||||
},
|
||||
suggestionsCallback: (pattern) async {
|
||||
_email = pattern;
|
||||
return PublicKeysDB.instance.searchByEmail(_email);
|
||||
},
|
||||
itemBuilder: (context, suggestion) {
|
||||
return Container(
|
||||
padding: EdgeInsets.fromLTRB(12, 8, 12, 8),
|
||||
child: Container(
|
||||
child: Text(
|
||||
suggestion.email,
|
||||
overflow: TextOverflow.clip,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
onSuggestionSelected: (PublicKey suggestion) {
|
||||
_addEmailToCollection(suggestion.email, suggestion.publicKey);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _addEmailToCollection(String email, String publicKey) async {
|
||||
if (!isValidEmail(email)) {
|
||||
showErrorDialog(context, "Invalid email address",
|
||||
"Please enter a valid email address");
|
||||
"Please enter a valid email address.");
|
||||
return;
|
||||
} else if (email == Configuration.instance.getEmail()) {
|
||||
showErrorDialog(
|
||||
context, "Oops", "You cannot share the album with yourself.");
|
||||
return;
|
||||
}
|
||||
final dialog = createProgressDialog(context, "Searching for user...");
|
||||
await dialog.show();
|
||||
final publicKey = await UserService.instance.getPublicKey(email: _email);
|
||||
await dialog.hide();
|
||||
if (publicKey == null) {
|
||||
final dialog = createProgressDialog(context, "Searching for user...");
|
||||
await dialog.show();
|
||||
publicKey = await UserService.instance.getPublicKey(email);
|
||||
await dialog.hide();
|
||||
}
|
||||
if (publicKey == null) {
|
||||
Navigator.of(context).pop();
|
||||
final dialog = AlertDialog(
|
||||
title: Text("Invite to ente?"),
|
||||
content: Text("Looks like " +
|
||||
_email +
|
||||
email +
|
||||
" hasn't signed up for ente yet. Would you like to invite them?"),
|
||||
actions: [
|
||||
FlatButton(
|
||||
|
@ -173,19 +204,30 @@ class _SharingDialogState extends State<SharingDialog> {
|
|||
},
|
||||
);
|
||||
} else {
|
||||
if (widget.collection == null) {
|
||||
log("Collection is null");
|
||||
// TODO: Create collection
|
||||
// TODO: Add files to collection
|
||||
final dialog = createProgressDialog(context, "Sharing...");
|
||||
await dialog.show();
|
||||
var collectionID;
|
||||
if (widget.collection != null) {
|
||||
collectionID = widget.collection.id;
|
||||
} else {
|
||||
collectionID =
|
||||
(await CollectionsService.instance.getOrCreateForPath(widget.path))
|
||||
.id;
|
||||
await Configuration.instance.addPathToFoldersToBeBackedUp(widget.path);
|
||||
SyncService.instance.sync();
|
||||
}
|
||||
CollectionsService.instance
|
||||
.share(widget.collection.id, _email, publicKey)
|
||||
.then((value) {
|
||||
try {
|
||||
await CollectionsService.instance.share(collectionID, email, publicKey);
|
||||
await dialog.hide();
|
||||
showToast("Folder shared successfully!");
|
||||
setState(() {
|
||||
_sharees.add(_email);
|
||||
_sharees.add(email);
|
||||
_showEntryField = false;
|
||||
});
|
||||
});
|
||||
} catch (e) {
|
||||
await dialog.hide();
|
||||
showGenericErrorDialog(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,7 +27,9 @@ class FileUploader {
|
|||
}
|
||||
|
||||
Future<String> putFile(UploadURL uploadURL, io.File file) async {
|
||||
_logger.info("Putting file to " + uploadURL.url);
|
||||
final fileSize = file.lengthSync().toString();
|
||||
final startTime = DateTime.now().millisecondsSinceEpoch;
|
||||
_logger.info("Putting file of size " + fileSize + " to " + uploadURL.url);
|
||||
return Dio()
|
||||
.put(uploadURL.url,
|
||||
data: file.openRead(),
|
||||
|
@ -36,7 +38,13 @@ class FileUploader {
|
|||
}))
|
||||
.catchError((e) {
|
||||
_logger.severe(e);
|
||||
throw e;
|
||||
}).then((value) {
|
||||
_logger.info("Upload speed : " +
|
||||
(file.lengthSync() /
|
||||
(DateTime.now().millisecondsSinceEpoch - startTime))
|
||||
.toString() +
|
||||
" kilo bytes per second");
|
||||
return uploadURL.objectKey;
|
||||
});
|
||||
}
|
||||
|
|
|
@ -160,6 +160,7 @@ Future<io.File> _downloadAndDecrypt(File file, BaseCacheManager cacheManager,
|
|||
|
||||
final encryptedFile = io.File(encryptedFilePath);
|
||||
final decryptedFile = io.File(decryptedFilePath);
|
||||
final startTime = DateTime.now().millisecondsSinceEpoch;
|
||||
return Dio()
|
||||
.download(
|
||||
file.getDownloadUrl(),
|
||||
|
@ -175,6 +176,11 @@ Future<io.File> _downloadAndDecrypt(File file, BaseCacheManager cacheManager,
|
|||
return null;
|
||||
}
|
||||
logger.info("File downloaded: " + file.uploadedFileID.toString());
|
||||
logger.info("Download speed: " +
|
||||
(io.File(encryptedFilePath).lengthSync() /
|
||||
(DateTime.now().millisecondsSinceEpoch - startTime))
|
||||
.toString() +
|
||||
"kBps");
|
||||
await CryptoUtil.decryptFile(encryptedFilePath, decryptedFilePath,
|
||||
Sodium.base642bin(file.fileDecryptionHeader), decryptFileKey(file));
|
||||
logger.info("File decrypted: " + file.uploadedFileID.toString());
|
||||
|
|
Loading…
Add table
Reference in a new issue