Browse Source

Merge pull request #4 from ente-io/collections

Update collection interactions
Vishnu Mohandas 4 năm trước cách đây
mục cha
commit
0f0eb4767d

+ 9 - 3
lib/core/configuration.dart

@@ -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());

+ 0 - 106
lib/db/folders_db.dart

@@ -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 - 0
lib/db/public_keys_db.dart

@@ -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 - 0
lib/models/filters/device_folder_name_filter.dart

@@ -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;
+  }
+}

+ 0 - 14
lib/models/filters/folder_name_filter.dart

@@ -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;
-  }
-}

+ 0 - 67
lib/models/folder.dart

@@ -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 - 0
lib/models/public_key.dart

@@ -0,0 +1,6 @@
+class PublicKey {
+  final String email;
+  final String publicKey;
+
+  PublicKey(this.email, this.publicKey);
+}

+ 1 - 1
lib/services/sync_service.dart

@@ -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++) {

+ 7 - 9
lib/services/user_service.dart

@@ -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;

+ 2 - 2
lib/ui/device_folders_gallery_widget.dart

@@ -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

+ 0 - 1
lib/ui/home_widget.dart

@@ -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';

+ 0 - 139
lib/ui/remote_folder_gallery_widget.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();
-  }
-}

+ 0 - 43
lib/ui/remote_folder_page.dart

@@ -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,
-    );
-  }
-}

+ 2 - 2
lib/ui/settings_page.dart

@@ -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(() {});
                 },
               ),

+ 81 - 39
lib/ui/share_folder_widget.dart

@@ -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;
+    }
+    if (publicKey == null) {
+      final dialog = createProgressDialog(context, "Searching for user...");
+      await dialog.show();
+      publicKey = await UserService.instance.getPublicKey(email);
+      await dialog.hide();
     }
-    final dialog = createProgressDialog(context, "Searching for user...");
-    await dialog.show();
-    final publicKey = await UserService.instance.getPublicKey(email: _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);
+      }
     }
   }
 }

+ 9 - 1
lib/utils/file_uploader.dart

@@ -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;
     });
   }

+ 6 - 0
lib/utils/file_util.dart

@@ -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());