share_collection_page.dart 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378
  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.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. borderRadius: 4.0,
  68. isTopBorderRadiusRemoved: _sharees.isNotEmpty,
  69. isBottomBorderRadiusRemoved: true,
  70. onTap: () async {
  71. routeToPage(
  72. context,
  73. AddParticipantPage(widget.collection, true),
  74. ).then(
  75. (value) => {
  76. if (mounted) {setState(() => {})}
  77. },
  78. );
  79. },
  80. ),
  81. );
  82. children.add(
  83. DividerWidget(
  84. dividerType: DividerType.menu,
  85. bgColor: getEnteColorScheme(context).fillFaint,
  86. ),
  87. );
  88. children.add(
  89. MenuItemWidget(
  90. captionedTextWidget: const CaptionedTextWidget(
  91. title: "Add collaborator",
  92. makeTextBold: true,
  93. ),
  94. leadingIcon: Icons.add,
  95. menuItemColor: getEnteColorScheme(context).fillFaint,
  96. borderRadius: 4.0,
  97. isTopBorderRadiusRemoved: _sharees.isNotEmpty,
  98. onTap: () async {
  99. routeToPage(context, AddParticipantPage(widget.collection, false))
  100. .then(
  101. (value) => {
  102. if (mounted) {setState(() => {})}
  103. },
  104. );
  105. },
  106. ),
  107. );
  108. if (_sharees.isEmpty && !hasUrl) {
  109. children.add(
  110. const MenuSectionDescriptionWidget(
  111. content:
  112. "Create shared and collaborative albums with other ente users, "
  113. "including users on free plans.",
  114. ),
  115. );
  116. }
  117. final bool hasExpired =
  118. widget.collection.publicURLs?.firstOrNull?.isExpired ?? false;
  119. children.addAll([
  120. const SizedBox(
  121. height: 24,
  122. ),
  123. MenuSectionTitle(
  124. title: hasUrl ? "Public link enabled" : "Share a link",
  125. iconData: Icons.public,
  126. ),
  127. ]);
  128. if (hasUrl) {
  129. if (hasExpired) {
  130. children.add(
  131. MenuItemWidget(
  132. captionedTextWidget: CaptionedTextWidget(
  133. title: "Link has expired",
  134. textColor: getEnteColorScheme(context).warning500,
  135. ),
  136. leadingIcon: Icons.error_outline,
  137. leadingIconColor: getEnteColorScheme(context).warning500,
  138. menuItemColor: getEnteColorScheme(context).fillFaint,
  139. onTap: () async {},
  140. isBottomBorderRadiusRemoved: true,
  141. ),
  142. );
  143. } else {
  144. final String collectionKey = Base58Encode(
  145. CollectionsService.instance.getCollectionKey(widget.collection.id),
  146. );
  147. final String url =
  148. "${widget.collection.publicURLs!.first!.url}#$collectionKey";
  149. children.addAll(
  150. [
  151. MenuItemWidget(
  152. captionedTextWidget: const CaptionedTextWidget(
  153. title: "Copy link",
  154. makeTextBold: true,
  155. ),
  156. leadingIcon: Icons.copy,
  157. menuItemColor: getEnteColorScheme(context).fillFaint,
  158. onTap: () async {
  159. await Clipboard.setData(ClipboardData(text: url));
  160. showShortToast(context, "Link copied to clipboard");
  161. },
  162. isBottomBorderRadiusRemoved: true,
  163. ),
  164. DividerWidget(
  165. dividerType: DividerType.menu,
  166. bgColor: getEnteColorScheme(context).fillFaint,
  167. ),
  168. MenuItemWidget(
  169. captionedTextWidget: const CaptionedTextWidget(
  170. title: "Send link",
  171. makeTextBold: true,
  172. ),
  173. leadingIcon: Icons.adaptive.share,
  174. menuItemColor: getEnteColorScheme(context).fillFaint,
  175. onTap: () async {
  176. shareText(url);
  177. },
  178. isTopBorderRadiusRemoved: true,
  179. isBottomBorderRadiusRemoved: true,
  180. ),
  181. ],
  182. );
  183. }
  184. children.addAll(
  185. [
  186. DividerWidget(
  187. dividerType: DividerType.menu,
  188. bgColor: getEnteColorScheme(context).fillFaint,
  189. ),
  190. MenuItemWidget(
  191. captionedTextWidget: const CaptionedTextWidget(
  192. title: "Manage link",
  193. makeTextBold: true,
  194. ),
  195. leadingIcon: Icons.link,
  196. trailingIcon: Icons.navigate_next,
  197. menuItemColor: getEnteColorScheme(context).fillFaint,
  198. trailingIconIsMuted: true,
  199. onTap: () async {
  200. routeToPage(
  201. context,
  202. ManageSharedLinkWidget(collection: widget.collection),
  203. ).then(
  204. (value) => {
  205. if (mounted) {setState(() => {})}
  206. },
  207. );
  208. },
  209. isTopBorderRadiusRemoved: true,
  210. ),
  211. ],
  212. );
  213. } else {
  214. children.addAll([
  215. MenuItemWidget(
  216. captionedTextWidget: const CaptionedTextWidget(
  217. title: "Create public link",
  218. makeTextBold: true,
  219. ),
  220. leadingIcon: Icons.link,
  221. menuItemColor: getEnteColorScheme(context).fillFaint,
  222. isBottomBorderRadiusRemoved: 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. onTap: () async {
  251. final bool result = await collectionActions.enableUrl(
  252. context,
  253. widget.collection,
  254. enableCollect: true,
  255. );
  256. if (result && mounted) {
  257. setState(() => {});
  258. }
  259. },
  260. ),
  261. _sharees.isEmpty
  262. ? const MenuSectionDescriptionWidget(
  263. content:
  264. "Create a link to allow people to add and view photos in "
  265. "your shared album without needing an ente app or account. Great for collecting event photos.",
  266. )
  267. : const SizedBox.shrink(),
  268. ]);
  269. }
  270. return Scaffold(
  271. appBar: AppBar(
  272. title: Text(
  273. widget.collection.name ?? "Unnamed",
  274. style: Theme.of(context).textTheme.headline5?.copyWith(fontSize: 16),
  275. ),
  276. elevation: 0,
  277. centerTitle: false,
  278. ),
  279. body: SingleChildScrollView(
  280. child: ListBody(
  281. children: <Widget>[
  282. Padding(
  283. padding:
  284. const EdgeInsets.symmetric(vertical: 4.0, horizontal: 16),
  285. child: Column(
  286. crossAxisAlignment: CrossAxisAlignment.start,
  287. children: children,
  288. ),
  289. ),
  290. ],
  291. ),
  292. ),
  293. );
  294. }
  295. }
  296. class EmailItemWidget extends StatelessWidget {
  297. final Collection collection;
  298. final Function? onTap;
  299. const EmailItemWidget(
  300. this.collection, {
  301. this.onTap,
  302. Key? key,
  303. }) : super(key: key);
  304. @override
  305. Widget build(BuildContext context) {
  306. if (collection.getSharees().isEmpty) {
  307. return const SizedBox.shrink();
  308. } else if (collection.getSharees().length == 1) {
  309. return Column(
  310. mainAxisAlignment: MainAxisAlignment.start,
  311. children: [
  312. MenuItemWidget(
  313. captionedTextWidget: CaptionedTextWidget(
  314. title: collection.getSharees().firstOrNull?.email ?? '',
  315. ),
  316. leadingIconWidget: UserAvatarWidget(
  317. collection.getSharees().first,
  318. thumbnailView: true,
  319. ),
  320. leadingIconSize: 24,
  321. menuItemColor: getEnteColorScheme(context).fillFaint,
  322. trailingIconIsMuted: true,
  323. trailingIcon: Icons.chevron_right,
  324. onTap: () async {
  325. if (onTap != null) {
  326. onTap!();
  327. }
  328. },
  329. isBottomBorderRadiusRemoved: true,
  330. ),
  331. DividerWidget(
  332. dividerType: DividerType.menu,
  333. bgColor: getEnteColorScheme(context).fillFaint,
  334. ),
  335. ],
  336. );
  337. } else {
  338. return Column(
  339. mainAxisAlignment: MainAxisAlignment.start,
  340. children: [
  341. MenuItemWidget(
  342. captionedTextWidget: const CaptionedTextWidget(
  343. title: 'Manage',
  344. ),
  345. leadingIcon: Icons.people_outline,
  346. menuItemColor: getEnteColorScheme(context).fillFaint,
  347. trailingIconIsMuted: true,
  348. trailingIcon: Icons.chevron_right,
  349. onTap: () async {
  350. if (onTap != null) {
  351. onTap!();
  352. }
  353. },
  354. isBottomBorderRadiusRemoved: true,
  355. ),
  356. DividerWidget(
  357. dividerType: DividerType.menu,
  358. bgColor: getEnteColorScheme(context).fillFaint,
  359. ),
  360. ],
  361. );
  362. }
  363. }
  364. }