share_folder_widget.dart 7.3 KB

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