share_collection_widget.dart 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  1. import 'package:flutter/cupertino.dart';
  2. import 'package:flutter/material.dart';
  3. import 'package:flutter/widgets.dart';
  4. import 'package:flutter_typeahead/flutter_typeahead.dart';
  5. import 'package:logging/logging.dart';
  6. import 'package:photos/core/configuration.dart';
  7. import 'package:photos/db/public_keys_db.dart';
  8. import 'package:photos/models/collection.dart';
  9. import 'package:photos/models/public_key.dart';
  10. import 'package:photos/services/collections_service.dart';
  11. import 'package:photos/services/sync_service.dart';
  12. import 'package:photos/services/user_service.dart';
  13. import 'package:photos/ui/common_elements.dart';
  14. import 'package:photos/ui/loading_widget.dart';
  15. import 'package:photos/utils/dialog_util.dart';
  16. import 'package:photos/utils/email_util.dart';
  17. import 'package:photos/utils/share_util.dart';
  18. import 'package:photos/utils/toast_util.dart';
  19. class SharingDialog extends StatefulWidget {
  20. final Collection collection;
  21. final List<User> sharees;
  22. SharingDialog(this.collection, this.sharees, {Key key}) : super(key: key);
  23. @override
  24. _SharingDialogState createState() => _SharingDialogState();
  25. }
  26. class _SharingDialogState extends State<SharingDialog> {
  27. bool _showEntryField = false;
  28. List<User> _sharees;
  29. String _email;
  30. @override
  31. Widget build(BuildContext context) {
  32. _sharees = widget.sharees;
  33. final children = List<Widget>();
  34. if (!_showEntryField &&
  35. (widget.collection == null || _sharees.length == 0)) {
  36. children.add(Text("Click the + button to share this " +
  37. Collection.typeToString(widget.collection.type) +
  38. "."));
  39. } else {
  40. for (final user in _sharees) {
  41. children.add(EmailItemWidget(widget.collection.id, user.email));
  42. }
  43. }
  44. if (_showEntryField) {
  45. children.add(_getEmailField());
  46. }
  47. children.add(Padding(
  48. padding: EdgeInsets.all(8),
  49. ));
  50. if (!_showEntryField) {
  51. children.add(Container(
  52. width: 220,
  53. child: OutlineButton(
  54. child: Icon(
  55. Icons.add,
  56. ),
  57. onPressed: () {
  58. setState(() {
  59. _showEntryField = true;
  60. });
  61. },
  62. ),
  63. ));
  64. } else {
  65. children.add(Container(
  66. width: 220,
  67. child: button(
  68. "Add",
  69. onPressed: () {
  70. _addEmailToCollection(_email, null);
  71. },
  72. ),
  73. ));
  74. }
  75. return AlertDialog(
  76. title: Text("Sharing"),
  77. content: SingleChildScrollView(
  78. child: ListBody(
  79. children: <Widget>[
  80. Padding(
  81. padding: const EdgeInsets.all(4.0),
  82. child: Column(
  83. children: children,
  84. )),
  85. ],
  86. ),
  87. ),
  88. );
  89. }
  90. Widget _getEmailField() {
  91. return TypeAheadField(
  92. textFieldConfiguration: TextFieldConfiguration(
  93. keyboardType: TextInputType.emailAddress,
  94. autofocus: true,
  95. decoration: InputDecoration(
  96. border: InputBorder.none,
  97. hintText: "email@your-friend.com",
  98. ),
  99. ),
  100. hideOnEmpty: true,
  101. loadingBuilder: (context) {
  102. return loadWidget;
  103. },
  104. suggestionsCallback: (pattern) async {
  105. _email = pattern;
  106. return PublicKeysDB.instance.searchByEmail(_email);
  107. },
  108. itemBuilder: (context, suggestion) {
  109. return Container(
  110. padding: EdgeInsets.fromLTRB(12, 8, 12, 8),
  111. child: Container(
  112. child: Text(
  113. suggestion.email,
  114. overflow: TextOverflow.clip,
  115. ),
  116. ),
  117. );
  118. },
  119. onSuggestionSelected: (PublicKey suggestion) {
  120. _addEmailToCollection(suggestion.email, suggestion.publicKey);
  121. },
  122. );
  123. }
  124. Future<void> _addEmailToCollection(String email, String publicKey) async {
  125. if (!isValidEmail(email)) {
  126. showErrorDialog(context, "Invalid email address",
  127. "Please enter a valid email address.");
  128. return;
  129. } else if (email == Configuration.instance.getEmail()) {
  130. showErrorDialog(context, "Oops", "You cannot share with yourself.");
  131. return;
  132. }
  133. if (publicKey == null) {
  134. final dialog = createProgressDialog(context, "Searching for user...");
  135. await dialog.show();
  136. publicKey = await UserService.instance.getPublicKey(email);
  137. await dialog.hide();
  138. }
  139. if (publicKey == null) {
  140. Navigator.of(context).pop();
  141. final dialog = AlertDialog(
  142. title: Text("Invite to ente?"),
  143. content: Text("Looks like " +
  144. email +
  145. " hasn't signed up for ente yet. Would you like to invite them?"),
  146. actions: [
  147. FlatButton(
  148. child: Text("Invite"),
  149. onPressed: () {
  150. shareText(
  151. "Hey, I have some really nice photos to share. Please install ente.io so that I can share them privately.");
  152. },
  153. ),
  154. ],
  155. );
  156. showDialog(
  157. context: context,
  158. builder: (BuildContext context) {
  159. return dialog;
  160. },
  161. );
  162. } else {
  163. final dialog = createProgressDialog(context, "Sharing...");
  164. await dialog.show();
  165. final collection = widget.collection;
  166. if (collection.type == CollectionType.folder) {
  167. final path =
  168. CollectionsService.instance.decryptCollectionPath(collection);
  169. if (!Configuration.instance.getPathsToBackUp().contains(path)) {
  170. await Configuration.instance.addPathToFoldersToBeBackedUp(path);
  171. SyncService.instance.sync();
  172. }
  173. }
  174. try {
  175. await CollectionsService.instance
  176. .share(widget.collection.id, email, publicKey);
  177. await dialog.hide();
  178. showToast("Shared successfully!");
  179. setState(() {
  180. _sharees.add(User(email: email));
  181. _showEntryField = false;
  182. });
  183. } catch (e) {
  184. await dialog.hide();
  185. showGenericErrorDialog(context);
  186. }
  187. }
  188. }
  189. }
  190. class EmailItemWidget extends StatelessWidget {
  191. final int collectionID;
  192. final String email;
  193. const EmailItemWidget(
  194. this.collectionID,
  195. this.email, {
  196. Key key,
  197. }) : super(key: key);
  198. @override
  199. Widget build(BuildContext context) {
  200. return Padding(
  201. padding: const EdgeInsets.fromLTRB(0, 4, 0, 4),
  202. child: Row(
  203. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  204. children: [
  205. Flexible(
  206. child: Text(
  207. email,
  208. style: TextStyle(fontSize: 16),
  209. ),
  210. ),
  211. IconButton(
  212. icon: Icon(Icons.delete_forever),
  213. color: Colors.redAccent,
  214. onPressed: () async {
  215. final dialog = createProgressDialog(context, "Please wait...");
  216. await dialog.show();
  217. try {
  218. await CollectionsService.instance
  219. .unshare(collectionID, email);
  220. await dialog.hide();
  221. showToast("Stopped sharing with " + email + ".");
  222. Navigator.of(context).pop();
  223. } catch (e, s) {
  224. Logger("EmailItemWidget").severe(e, s);
  225. await dialog.hide();
  226. showGenericErrorDialog(context);
  227. }
  228. },
  229. ),
  230. ],
  231. ));
  232. }
  233. }