share_collection_page.dart 12 KB

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