share_collection_page.dart 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331
  1. import 'package:collection/collection.dart';
  2. import 'package:fast_base58/fast_base58.dart';
  3. import 'package:flutter/material.dart';
  4. import 'package:flutter/services.dart';
  5. import 'package:logging/logging.dart';
  6. import 'package:photos/models/collection.dart';
  7. import 'package:photos/services/collections_service.dart';
  8. import 'package:photos/theme/ente_theme.dart';
  9. import 'package:photos/ui/actions/collection/collection_sharing_actions.dart';
  10. import 'package:photos/ui/components/captioned_text_widget.dart';
  11. import 'package:photos/ui/components/divider_widget.dart';
  12. import 'package:photos/ui/components/menu_item_widget.dart';
  13. import 'package:photos/ui/components/menu_section_description_widget.dart';
  14. import 'package:photos/ui/components/menu_section_title.dart';
  15. import 'package:photos/ui/sharing/add_partipant_page.dart';
  16. import 'package:photos/ui/sharing/album_participants_page.dart';
  17. import 'package:photos/ui/sharing/manage_links_widget.dart';
  18. import 'package:photos/ui/sharing/user_avator_widget.dart';
  19. import 'package:photos/utils/navigation_util.dart';
  20. import 'package:photos/utils/share_util.dart';
  21. import 'package:photos/utils/toast_util.dart';
  22. class ShareCollectionPage extends StatefulWidget {
  23. final Collection collection;
  24. const ShareCollectionPage(this.collection, {Key? key}) : super(key: key);
  25. @override
  26. State<ShareCollectionPage> createState() => _ShareCollectionPageState();
  27. }
  28. class _ShareCollectionPageState extends State<ShareCollectionPage> {
  29. late List<User?> _sharees;
  30. final Logger _logger = Logger("SharingDialogState");
  31. final CollectionActions collectionActions =
  32. CollectionActions(CollectionsService.instance);
  33. Future<void> _navigateToManageUser() async {
  34. final result = await routeToPage(
  35. context,
  36. AlbumParticipantsPage(widget.collection),
  37. );
  38. if (mounted) {
  39. setState(() => {});
  40. }
  41. }
  42. @override
  43. Widget build(BuildContext context) {
  44. _sharees = widget.collection.sharees ?? [];
  45. final bool hasUrl = widget.collection.publicURLs?.isNotEmpty ?? false;
  46. final children = <Widget>[];
  47. children.add(
  48. MenuSectionTitle(
  49. title: _sharees.isEmpty
  50. ? "Share with specific people"
  51. : "Shared with ${_sharees.length} ${_sharees.length == 1 ? 'person' : 'people'}",
  52. iconData: Icons.workspaces,
  53. ),
  54. );
  55. children.add(
  56. EmailItemWidget(
  57. widget.collection,
  58. onTap: _navigateToManageUser,
  59. ),
  60. );
  61. children.add(
  62. MenuItemWidget(
  63. captionedTextWidget: CaptionedTextWidget(
  64. title: _sharees.isEmpty ? "Add email" : "Add more",
  65. makeTextBold: true,
  66. ),
  67. leadingIcon: Icons.add,
  68. menuItemColor: getEnteColorScheme(context).fillFaint,
  69. pressedColor: getEnteColorScheme(context).fillFaint,
  70. borderRadius: 4.0,
  71. isTopBorderRadiusRemoved: _sharees.isNotEmpty,
  72. onTap: () async {
  73. routeToPage(context, AddParticipantPage(widget.collection)).then(
  74. (value) => {
  75. if (mounted) {setState(() => {})}
  76. },
  77. );
  78. },
  79. ),
  80. );
  81. if (_sharees.isEmpty && !hasUrl) {
  82. children.add(
  83. const MenuSectionDescriptionWidget(
  84. content:
  85. "Create shared and collaborative albums with other ente users, "
  86. "including users on free plans.",
  87. ),
  88. );
  89. }
  90. final bool hasExpired =
  91. widget.collection?.publicURLs?.firstOrNull?.isExpired ?? false;
  92. children.addAll([
  93. const SizedBox(
  94. height: 24,
  95. ),
  96. MenuSectionTitle(
  97. title: hasUrl
  98. ? "Public link enabled"
  99. : (_sharees.isEmpty ? "Or share a link" : "Share a link"),
  100. iconData: Icons.public,
  101. ),
  102. ]);
  103. if (hasUrl) {
  104. if (hasExpired) {
  105. children.add(
  106. MenuItemWidget(
  107. captionedTextWidget: CaptionedTextWidget(
  108. title: "Link has expired",
  109. textColor: getEnteColorScheme(context).warning500,
  110. ),
  111. leadingIcon: Icons.error_outline,
  112. leadingIconColor: getEnteColorScheme(context).warning500,
  113. menuItemColor: getEnteColorScheme(context).fillFaint,
  114. pressedColor: getEnteColorScheme(context).fillFaint,
  115. onTap: () async {},
  116. isBottomBorderRadiusRemoved: true,
  117. ),
  118. );
  119. } else {
  120. final String collectionKey = Base58Encode(
  121. CollectionsService.instance.getCollectionKey(widget.collection.id),
  122. );
  123. final String url =
  124. "${widget.collection.publicURLs!.first!.url}#$collectionKey";
  125. children.addAll(
  126. [
  127. MenuItemWidget(
  128. captionedTextWidget: const CaptionedTextWidget(
  129. title: "Copy link",
  130. makeTextBold: true,
  131. ),
  132. leadingIcon: Icons.copy,
  133. menuItemColor: getEnteColorScheme(context).fillFaint,
  134. pressedColor: getEnteColorScheme(context).fillFaint,
  135. onTap: () async {
  136. await Clipboard.setData(ClipboardData(text: url));
  137. showShortToast(context, "Link copied to clipboard");
  138. },
  139. isBottomBorderRadiusRemoved: true,
  140. ),
  141. DividerWidget(
  142. dividerType: DividerType.menu,
  143. bgColor: getEnteColorScheme(context).fillFaint,
  144. ),
  145. MenuItemWidget(
  146. captionedTextWidget: const CaptionedTextWidget(
  147. title: "Send link",
  148. makeTextBold: true,
  149. ),
  150. leadingIcon: Icons.adaptive.share,
  151. menuItemColor: getEnteColorScheme(context).fillFaint,
  152. pressedColor: getEnteColorScheme(context).fillFaint,
  153. onTap: () async {
  154. shareText(url);
  155. },
  156. isTopBorderRadiusRemoved: true,
  157. isBottomBorderRadiusRemoved: true,
  158. ),
  159. ],
  160. );
  161. }
  162. children.addAll(
  163. [
  164. DividerWidget(
  165. dividerType: DividerType.menu,
  166. bgColor: getEnteColorScheme(context).fillFaint,
  167. ),
  168. MenuItemWidget(
  169. captionedTextWidget: const CaptionedTextWidget(
  170. title: "Manage link",
  171. makeTextBold: true,
  172. ),
  173. leadingIcon: Icons.link,
  174. trailingIcon: Icons.navigate_next,
  175. menuItemColor: getEnteColorScheme(context).fillFaint,
  176. pressedColor: getEnteColorScheme(context).fillFaint,
  177. trailingIconIsMuted: true,
  178. onTap: () async {
  179. routeToPage(
  180. context,
  181. ManageSharedLinkWidget(collection: widget.collection),
  182. ).then(
  183. (value) => {
  184. if (mounted) {setState(() => {})}
  185. },
  186. );
  187. },
  188. isTopBorderRadiusRemoved: true,
  189. ),
  190. ],
  191. );
  192. } else {
  193. children.add(
  194. MenuItemWidget(
  195. captionedTextWidget: const CaptionedTextWidget(
  196. title: "Create public link",
  197. ),
  198. leadingIcon: Icons.link,
  199. menuItemColor: getEnteColorScheme(context).fillFaint,
  200. pressedColor: getEnteColorScheme(context).fillFaint,
  201. onTap: () async {
  202. final bool result = await collectionActions.publicLinkToggle(
  203. context,
  204. widget.collection,
  205. true,
  206. );
  207. if (result && mounted) {
  208. setState(() => {});
  209. }
  210. },
  211. ),
  212. );
  213. if (_sharees.isEmpty && !hasUrl) {
  214. children.add(
  215. const MenuSectionDescriptionWidget(
  216. content:
  217. "Links allow people without an ente account to view and add photos to your shared albums.",
  218. ),
  219. );
  220. }
  221. }
  222. return Scaffold(
  223. appBar: AppBar(
  224. title: Text(
  225. widget.collection.name ?? "Unnamed",
  226. style: Theme.of(context).textTheme.headline5?.copyWith(fontSize: 16),
  227. ),
  228. elevation: 0,
  229. centerTitle: false,
  230. ),
  231. body: SingleChildScrollView(
  232. child: ListBody(
  233. children: <Widget>[
  234. Padding(
  235. padding:
  236. const EdgeInsets.symmetric(vertical: 4.0, horizontal: 16),
  237. child: Column(
  238. children: children,
  239. ),
  240. ),
  241. ],
  242. ),
  243. ),
  244. );
  245. }
  246. }
  247. class EmailItemWidget extends StatelessWidget {
  248. final Collection collection;
  249. final Function? onTap;
  250. const EmailItemWidget(
  251. this.collection, {
  252. this.onTap,
  253. Key? key,
  254. }) : super(key: key);
  255. @override
  256. Widget build(BuildContext context) {
  257. if (collection.getSharees().isEmpty) {
  258. return const SizedBox.shrink();
  259. } else if (collection.getSharees().length == 1) {
  260. return Column(
  261. mainAxisAlignment: MainAxisAlignment.start,
  262. children: [
  263. MenuItemWidget(
  264. captionedTextWidget: CaptionedTextWidget(
  265. title: collection.getSharees().firstOrNull?.email ?? '',
  266. ),
  267. leadingIconWidget: UserAvatarWidget(
  268. collection.getSharees().first,
  269. thumbnailView: true,
  270. ),
  271. leadingIconSize: 24,
  272. menuItemColor: getEnteColorScheme(context).fillFaint,
  273. pressedColor: getEnteColorScheme(context).fillFaint,
  274. trailingIconIsMuted: true,
  275. trailingIcon: Icons.chevron_right,
  276. onTap: () async {
  277. if (onTap != null) {
  278. onTap!();
  279. }
  280. },
  281. isBottomBorderRadiusRemoved: true,
  282. ),
  283. DividerWidget(
  284. dividerType: DividerType.menu,
  285. bgColor: getEnteColorScheme(context).fillFaint,
  286. ),
  287. ],
  288. );
  289. } else {
  290. return Column(
  291. mainAxisAlignment: MainAxisAlignment.start,
  292. children: [
  293. MenuItemWidget(
  294. captionedTextWidget: const CaptionedTextWidget(
  295. title: 'Manage',
  296. ),
  297. leadingIcon: Icons.people_outline,
  298. menuItemColor: getEnteColorScheme(context).fillFaint,
  299. pressedColor: getEnteColorScheme(context).fillFaint,
  300. trailingIconIsMuted: true,
  301. trailingIcon: Icons.chevron_right,
  302. onTap: () async {
  303. if (onTap != null) {
  304. onTap!();
  305. }
  306. },
  307. isBottomBorderRadiusRemoved: true,
  308. ),
  309. DividerWidget(
  310. dividerType: DividerType.menu,
  311. bgColor: getEnteColorScheme(context).fillFaint,
  312. ),
  313. ],
  314. );
  315. }
  316. }
  317. }