From 7c31282d97310545eb3bf8da42eb5b6bd84177f7 Mon Sep 17 00:00:00 2001 From: Sayed Mahmood Sayedi Date: Tue, 29 Jul 2025 11:02:33 +0430 Subject: [PATCH 1/3] icons: Add `link` icon, from Figma Figma link: https://www.figma.com/design/1JTNtYo9memgW7vV6d0ygq/Zulip-Mobile?node-id=6098-6656&t=Ieci9rv9MCUTw9bq-0 --- assets/icons/ZulipIcons.ttf | Bin 16076 -> 16380 bytes assets/icons/link.svg | 4 +++ lib/widgets/icons.dart | 47 +++++++++++++++++++----------------- 3 files changed, 29 insertions(+), 22 deletions(-) create mode 100644 assets/icons/link.svg diff --git a/assets/icons/ZulipIcons.ttf b/assets/icons/ZulipIcons.ttf index 85f393019a00470c3f77ab60b05ae32b1863547b..612d8a04faa16384c7fb6440ac0530a296e30037 100644 GIT binary patch delta 2275 zcmb7FTWl0%6h1RM-7d>+x3t^sZh^JkZc7!|mSuORdzrmodZ!6sVq>5y^a8X{r9uI9 zd@zPb$f6HEnCOEc290TnVkAb?n3yz;F+LEZi6$6LB&HwSv zx&1S<`uv}|P7)Dqq!-CVeWktovvY07o+Og`iIn9%dwYYAf3>fXsO~njCJ)V*7tVfg zxPz$vC9J(SGk0eC^!m>|MD}lpcJ7#+Djz=e)91_3{Q-!~V!62tncX`SQ|&G%ZE2>BhYMczJ%RdCjzJ z`dD!(Jyb_F3Q&;_&?3pyND8&j6IrMY ziEV`pqbeC1fXraW3+^;lO7sXux(bq#q3(u*Cb%7jQWP{v+>KC)K|MvApu;;HNIN-E z6BS_>LCX1$fL_H&qh{=@$Si?pKdh|?B8J^QSY%W6w1nTTbq z1`VPiIL||I7uuj=pJz?tzzMEXA z3O8|p^dY#7!Kel59CHDtoTHao(XlXGbGMj0jD2?RrVaPjYwfY1a*x5mI8Mn?I)&Gy z*XVZ`Zfd>e(-NZ{u+GAsD*;{|E^{!oV9LjXJCngm4?^HhN8m6^+YwyNXs3~*ad_xa zk$B zd0Ad1s;t-TmO>T3`BG)lY^y8*tLBe-6>#x&7b)^+2c#7?3VeNyxfLanaUY~3bmmT| z@_v2it|)vL;46mMl1M*`HkS}t5ixSnMvRD4h;$II+b9iJtd<%0Ua(AUhHW7q(~_Ai zY!bz|Iy4f=r3ME3M~5qSEHCS*8!pooe&QMh4hlpe%K~A@rvwU+hXnGFhXrDgQvw>~ zv_KMaMj!(@E0BfMkKn;b1IGkPkaGeF$a#Sx^MdocmhqNfT(AEBn`q^#{f$;#hC?#F zW4eJ?WIz6wKC?qIJ6*w)l$3&A*@e0gx3QmsoPlhDwsz?-9A?G1>}&i z?S&$=U?N}Zo9Har+&){;9nZuA9=Ge9Cs#_ktQ}hvYx{wtQ+n5P9e$}RKXLc7@KkL) zInh~UqryXtOrQ8WO12J|)l0-$X@=ke6}w%Y5QKKu4ieOLFn&WZ99`L4USlzU@| uZG7%~Htw@kn+;VX)L>;_*Y_^`^n;1lWNFXJYNAy#9XWoi@}u^vME?Lv6HcQ5 delta 1925 zcmb7_NlaXI9LIleW*A_~Ff7AT+G1M}YsIq7&g={ft2VYSG0}F|Y1y=haA?NFs|TCs z!I&PZCPc5rL!+Dw;m~m4U}6tF7!nUnJm|$kQ=^~X<5B9tgY)wJ|Nj5~Z(rVC9hxJ$r3&?q?ArBI4S3^-@Ro8+(^4L`r@^>gvQoVfoGN&p#0V)MZDjMw71Sq6((1I-M)*g%7^$f9}Y(ax>kx{NWi*_bKD#3!xtjXaRYMyv6fv0>~x+>VIjN9Ts~uFK;(DJ4=N z7i2)*l@)PFl>~W05|)VcN?a0>WEYf_q$R`NNs*2uj_ymiPx1t$54i|>Db?4ydY(~; zTBc*-l2XfX)-oK!v0Ki{^Ww#2K%$n*IHuz8hmg!l8xnEpM}H8i@{Mvz%8-r`S{|C_ zz8T9Q^acpD42j}dslnAStO$$*PZc6j%qFE8A?2vC)8eNNAwuoPl;%8w(Ga`1)WQ#u zO)qO0Z4VWV5k?0Z88qUS^&!?Efrj99BNJv9DH8qBb(dvm28x_ISizTGtJctgrMCG;xL2hlBY`TR*u%d$T{+gqY;&t*=dMD^P=l`Q7fW-jj*OH z8|pklzA?+*0awE*rY<(Mn%Nm* z`nN!WvvQupj8-krTjXNZu~jEMxQP+i!Ho`qUs_m?X0jEjUiew$eOhhlHTO#D%5{$T z_&L5de=kWHJ!Yr-GxJlo-+bu)p~Hc!{+^J6?%JUqK12>|?Q!-{0te3(nUnQDdd!{D zmaYi4b0ieScY=Ay(8U2f_Ywdl59S(k%z9@7=(UkBMe=$p%q%UF}%6$nRb`~Z|a@4imMe{-iUX%@?7QR z%E!L*zGdI->Ol2YO;ydET32n>U*Uhtzgst1U-SQ;&7ZtqHf-K(dsxr^)7yzJHvddC J7-miC(LX{~1j+ya diff --git a/assets/icons/link.svg b/assets/icons/link.svg new file mode 100644 index 0000000000..0d560f15ed --- /dev/null +++ b/assets/icons/link.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lib/widgets/icons.dart b/lib/widgets/icons.dart index 1b5c424b0b..2392e054c5 100644 --- a/lib/widgets/icons.dart +++ b/lib/widgets/icons.dart @@ -111,71 +111,74 @@ abstract final class ZulipIcons { /// The Zulip custom icon "language". static const IconData language = IconData(0xf11d, fontFamily: "Zulip Icons"); + /// The Zulip custom icon "link". + static const IconData link = IconData(0xf11e, fontFamily: "Zulip Icons"); + /// The Zulip custom icon "lock". - static const IconData lock = IconData(0xf11e, fontFamily: "Zulip Icons"); + static const IconData lock = IconData(0xf11f, fontFamily: "Zulip Icons"); /// The Zulip custom icon "menu". - static const IconData menu = IconData(0xf11f, fontFamily: "Zulip Icons"); + static const IconData menu = IconData(0xf120, fontFamily: "Zulip Icons"); /// The Zulip custom icon "message_checked". - static const IconData message_checked = IconData(0xf120, fontFamily: "Zulip Icons"); + static const IconData message_checked = IconData(0xf121, fontFamily: "Zulip Icons"); /// The Zulip custom icon "message_feed". - static const IconData message_feed = IconData(0xf121, fontFamily: "Zulip Icons"); + static const IconData message_feed = IconData(0xf122, fontFamily: "Zulip Icons"); /// The Zulip custom icon "mute". - static const IconData mute = IconData(0xf122, fontFamily: "Zulip Icons"); + static const IconData mute = IconData(0xf123, fontFamily: "Zulip Icons"); /// The Zulip custom icon "person". - static const IconData person = IconData(0xf123, fontFamily: "Zulip Icons"); + static const IconData person = IconData(0xf124, fontFamily: "Zulip Icons"); /// The Zulip custom icon "plus". - static const IconData plus = IconData(0xf124, fontFamily: "Zulip Icons"); + static const IconData plus = IconData(0xf125, fontFamily: "Zulip Icons"); /// The Zulip custom icon "read_receipts". - static const IconData read_receipts = IconData(0xf125, fontFamily: "Zulip Icons"); + static const IconData read_receipts = IconData(0xf126, fontFamily: "Zulip Icons"); /// The Zulip custom icon "remove". - static const IconData remove = IconData(0xf126, fontFamily: "Zulip Icons"); + static const IconData remove = IconData(0xf127, fontFamily: "Zulip Icons"); /// The Zulip custom icon "search". - static const IconData search = IconData(0xf127, fontFamily: "Zulip Icons"); + static const IconData search = IconData(0xf128, fontFamily: "Zulip Icons"); /// The Zulip custom icon "send". - static const IconData send = IconData(0xf128, fontFamily: "Zulip Icons"); + static const IconData send = IconData(0xf129, fontFamily: "Zulip Icons"); /// The Zulip custom icon "settings". - static const IconData settings = IconData(0xf129, fontFamily: "Zulip Icons"); + static const IconData settings = IconData(0xf12a, fontFamily: "Zulip Icons"); /// The Zulip custom icon "share". - static const IconData share = IconData(0xf12a, fontFamily: "Zulip Icons"); + static const IconData share = IconData(0xf12b, fontFamily: "Zulip Icons"); /// The Zulip custom icon "share_ios". - static const IconData share_ios = IconData(0xf12b, fontFamily: "Zulip Icons"); + static const IconData share_ios = IconData(0xf12c, fontFamily: "Zulip Icons"); /// The Zulip custom icon "smile". - static const IconData smile = IconData(0xf12c, fontFamily: "Zulip Icons"); + static const IconData smile = IconData(0xf12d, fontFamily: "Zulip Icons"); /// The Zulip custom icon "star". - static const IconData star = IconData(0xf12d, fontFamily: "Zulip Icons"); + static const IconData star = IconData(0xf12e, fontFamily: "Zulip Icons"); /// The Zulip custom icon "star_filled". - static const IconData star_filled = IconData(0xf12e, fontFamily: "Zulip Icons"); + static const IconData star_filled = IconData(0xf12f, fontFamily: "Zulip Icons"); /// The Zulip custom icon "three_person". - static const IconData three_person = IconData(0xf12f, fontFamily: "Zulip Icons"); + static const IconData three_person = IconData(0xf130, fontFamily: "Zulip Icons"); /// The Zulip custom icon "topic". - static const IconData topic = IconData(0xf130, fontFamily: "Zulip Icons"); + static const IconData topic = IconData(0xf131, fontFamily: "Zulip Icons"); /// The Zulip custom icon "topics". - static const IconData topics = IconData(0xf131, fontFamily: "Zulip Icons"); + static const IconData topics = IconData(0xf132, fontFamily: "Zulip Icons"); /// The Zulip custom icon "two_person". - static const IconData two_person = IconData(0xf132, fontFamily: "Zulip Icons"); + static const IconData two_person = IconData(0xf133, fontFamily: "Zulip Icons"); /// The Zulip custom icon "unmute". - static const IconData unmute = IconData(0xf133, fontFamily: "Zulip Icons"); + static const IconData unmute = IconData(0xf134, fontFamily: "Zulip Icons"); // END GENERATED ICON DATA } From 3e9b4de00c3a59dcf2713a4f82acf17bc6711357 Mon Sep 17 00:00:00 2001 From: Sayed Mahmood Sayedi Date: Tue, 29 Jul 2025 11:06:19 +0430 Subject: [PATCH 2/3] action_sheet: Use `ZulipIcons.link` instead of `Icons.link` --- lib/widgets/action_sheet.dart | 2 +- test/widgets/action_sheet_test.dart | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/widgets/action_sheet.dart b/lib/widgets/action_sheet.dart index 6b280df6ee..f794e6ae70 100644 --- a/lib/widgets/action_sheet.dart +++ b/lib/widgets/action_sheet.dart @@ -1023,7 +1023,7 @@ class CopyMessageTextButton extends MessageActionSheetMenuItemButton { class CopyMessageLinkButton extends MessageActionSheetMenuItemButton { CopyMessageLinkButton({super.key, required super.message, required super.pageContext}); - @override IconData get icon => Icons.link; + @override IconData get icon => ZulipIcons.link; @override String label(ZulipLocalizations zulipLocalizations) { diff --git a/test/widgets/action_sheet_test.dart b/test/widgets/action_sheet_test.dart index 631856efde..84d92fb587 100644 --- a/test/widgets/action_sheet_test.dart +++ b/test/widgets/action_sheet_test.dart @@ -1573,8 +1573,8 @@ void main() { }); Future tapCopyMessageLinkButton(WidgetTester tester) async { - await tester.ensureVisible(find.byIcon(Icons.link, skipOffstage: false)); - await tester.tap(find.byIcon(Icons.link)); + await tester.ensureVisible(find.byIcon(ZulipIcons.link, skipOffstage: false)); + await tester.tap(find.byIcon(ZulipIcons.link)); await tester.pump(); // [MenuItemButton.onPressed] called in a post-frame callback: flutter/flutter@e4a39fa2e } From 373cfb7b1451ac0118753d605a653cc43d525a9a Mon Sep 17 00:00:00 2001 From: Sayed Mahmood Sayedi Date: Tue, 29 Jul 2025 20:40:01 +0430 Subject: [PATCH 3/3] action_sheet: Add "Copy link to channel" button Fixes: #1227 --- assets/l10n/app_en.arb | 8 +++++ lib/generated/l10n/zulip_localizations.dart | 12 +++++++ .../l10n/zulip_localizations_ar.dart | 6 ++++ .../l10n/zulip_localizations_de.dart | 6 ++++ .../l10n/zulip_localizations_en.dart | 6 ++++ .../l10n/zulip_localizations_fr.dart | 6 ++++ .../l10n/zulip_localizations_it.dart | 6 ++++ .../l10n/zulip_localizations_ja.dart | 6 ++++ .../l10n/zulip_localizations_nb.dart | 6 ++++ .../l10n/zulip_localizations_pl.dart | 6 ++++ .../l10n/zulip_localizations_ru.dart | 6 ++++ .../l10n/zulip_localizations_sk.dart | 6 ++++ .../l10n/zulip_localizations_sl.dart | 6 ++++ .../l10n/zulip_localizations_uk.dart | 6 ++++ .../l10n/zulip_localizations_zh.dart | 6 ++++ lib/widgets/action_sheet.dart | 31 +++++++++++++++++++ test/widgets/action_sheet_test.dart | 27 ++++++++++++++++ 17 files changed, 156 insertions(+) diff --git a/assets/l10n/app_en.arb b/assets/l10n/app_en.arb index c24f23dce9..1d529f95f0 100644 --- a/assets/l10n/app_en.arb +++ b/assets/l10n/app_en.arb @@ -100,6 +100,10 @@ "@actionSheetOptionMarkChannelAsRead": { "description": "Label for marking a channel as read." }, + "actionSheetOptionCopyChannelLink": "Copy link to channel", + "@actionSheetOptionCopyChannelLink": { + "description": "Label for copy channel link button on action sheet." + }, "actionSheetOptionListOfTopics": "List of topics", "@actionSheetOptionListOfTopics": { "description": "Label for navigating to a channel's topic-list page." @@ -357,6 +361,10 @@ "@successMessageLinkCopied": { "description": "Message when link of a message was copied to the user's system clipboard." }, + "successChannelLinkCopied": "Channel link copied", + "@successChannelLinkCopied": { + "description": "Message when link of a channel was copied to the user's system clipboard." + }, "errorBannerDeactivatedDmLabel": "You cannot send messages to deactivated users.", "@errorBannerDeactivatedDmLabel": { "description": "Label text for error banner when sending a message to one or multiple deactivated users." diff --git a/lib/generated/l10n/zulip_localizations.dart b/lib/generated/l10n/zulip_localizations.dart index 9668b50f26..907e29d538 100644 --- a/lib/generated/l10n/zulip_localizations.dart +++ b/lib/generated/l10n/zulip_localizations.dart @@ -281,6 +281,12 @@ abstract class ZulipLocalizations { /// **'Mark channel as read'** String get actionSheetOptionMarkChannelAsRead; + /// Label for copy channel link button on action sheet. + /// + /// In en, this message translates to: + /// **'Copy link to channel'** + String get actionSheetOptionCopyChannelLink; + /// Label for navigating to a channel's topic-list page. /// /// In en, this message translates to: @@ -619,6 +625,12 @@ abstract class ZulipLocalizations { /// **'Message link copied'** String get successMessageLinkCopied; + /// Message when link of a channel was copied to the user's system clipboard. + /// + /// In en, this message translates to: + /// **'Channel link copied'** + String get successChannelLinkCopied; + /// Label text for error banner when sending a message to one or multiple deactivated users. /// /// In en, this message translates to: diff --git a/lib/generated/l10n/zulip_localizations_ar.dart b/lib/generated/l10n/zulip_localizations_ar.dart index 110b0dbe24..96cafc3cc9 100644 --- a/lib/generated/l10n/zulip_localizations_ar.dart +++ b/lib/generated/l10n/zulip_localizations_ar.dart @@ -90,6 +90,9 @@ class ZulipLocalizationsAr extends ZulipLocalizations { @override String get actionSheetOptionMarkChannelAsRead => 'Mark channel as read'; + @override + String get actionSheetOptionCopyChannelLink => 'Copy link to channel'; + @override String get actionSheetOptionListOfTopics => 'List of topics'; @@ -300,6 +303,9 @@ class ZulipLocalizationsAr extends ZulipLocalizations { @override String get successMessageLinkCopied => 'Message link copied'; + @override + String get successChannelLinkCopied => 'Channel link copied'; + @override String get errorBannerDeactivatedDmLabel => 'You cannot send messages to deactivated users.'; diff --git a/lib/generated/l10n/zulip_localizations_de.dart b/lib/generated/l10n/zulip_localizations_de.dart index f3b1bdad67..a016576322 100644 --- a/lib/generated/l10n/zulip_localizations_de.dart +++ b/lib/generated/l10n/zulip_localizations_de.dart @@ -92,6 +92,9 @@ class ZulipLocalizationsDe extends ZulipLocalizations { String get actionSheetOptionMarkChannelAsRead => 'Kanal als gelesen markieren'; + @override + String get actionSheetOptionCopyChannelLink => 'Copy link to channel'; + @override String get actionSheetOptionListOfTopics => 'Themenliste'; @@ -313,6 +316,9 @@ class ZulipLocalizationsDe extends ZulipLocalizations { @override String get successMessageLinkCopied => 'Nachrichtenlink kopiert'; + @override + String get successChannelLinkCopied => 'Channel link copied'; + @override String get errorBannerDeactivatedDmLabel => 'Du kannst keine Nachrichten an deaktivierte Nutzer:innen senden.'; diff --git a/lib/generated/l10n/zulip_localizations_en.dart b/lib/generated/l10n/zulip_localizations_en.dart index f99c386087..fa76f8decc 100644 --- a/lib/generated/l10n/zulip_localizations_en.dart +++ b/lib/generated/l10n/zulip_localizations_en.dart @@ -90,6 +90,9 @@ class ZulipLocalizationsEn extends ZulipLocalizations { @override String get actionSheetOptionMarkChannelAsRead => 'Mark channel as read'; + @override + String get actionSheetOptionCopyChannelLink => 'Copy link to channel'; + @override String get actionSheetOptionListOfTopics => 'List of topics'; @@ -300,6 +303,9 @@ class ZulipLocalizationsEn extends ZulipLocalizations { @override String get successMessageLinkCopied => 'Message link copied'; + @override + String get successChannelLinkCopied => 'Channel link copied'; + @override String get errorBannerDeactivatedDmLabel => 'You cannot send messages to deactivated users.'; diff --git a/lib/generated/l10n/zulip_localizations_fr.dart b/lib/generated/l10n/zulip_localizations_fr.dart index cbc18b6d35..a0cff72bff 100644 --- a/lib/generated/l10n/zulip_localizations_fr.dart +++ b/lib/generated/l10n/zulip_localizations_fr.dart @@ -90,6 +90,9 @@ class ZulipLocalizationsFr extends ZulipLocalizations { @override String get actionSheetOptionMarkChannelAsRead => 'Mark channel as read'; + @override + String get actionSheetOptionCopyChannelLink => 'Copy link to channel'; + @override String get actionSheetOptionListOfTopics => 'List of topics'; @@ -300,6 +303,9 @@ class ZulipLocalizationsFr extends ZulipLocalizations { @override String get successMessageLinkCopied => 'Message link copied'; + @override + String get successChannelLinkCopied => 'Channel link copied'; + @override String get errorBannerDeactivatedDmLabel => 'You cannot send messages to deactivated users.'; diff --git a/lib/generated/l10n/zulip_localizations_it.dart b/lib/generated/l10n/zulip_localizations_it.dart index 2d7d35e23e..b93ddcdb0e 100644 --- a/lib/generated/l10n/zulip_localizations_it.dart +++ b/lib/generated/l10n/zulip_localizations_it.dart @@ -91,6 +91,9 @@ class ZulipLocalizationsIt extends ZulipLocalizations { @override String get actionSheetOptionMarkChannelAsRead => 'Segna il canale come letto'; + @override + String get actionSheetOptionCopyChannelLink => 'Copy link to channel'; + @override String get actionSheetOptionListOfTopics => 'Elenco degli argomenti'; @@ -310,6 +313,9 @@ class ZulipLocalizationsIt extends ZulipLocalizations { @override String get successMessageLinkCopied => 'Collegamento messaggio copiato'; + @override + String get successChannelLinkCopied => 'Channel link copied'; + @override String get errorBannerDeactivatedDmLabel => 'Non è possibile inviare messaggi agli utenti disattivati.'; diff --git a/lib/generated/l10n/zulip_localizations_ja.dart b/lib/generated/l10n/zulip_localizations_ja.dart index edf5c759f9..9e5eba47f8 100644 --- a/lib/generated/l10n/zulip_localizations_ja.dart +++ b/lib/generated/l10n/zulip_localizations_ja.dart @@ -89,6 +89,9 @@ class ZulipLocalizationsJa extends ZulipLocalizations { @override String get actionSheetOptionMarkChannelAsRead => 'チャンネルを既読にする'; + @override + String get actionSheetOptionCopyChannelLink => 'Copy link to channel'; + @override String get actionSheetOptionListOfTopics => 'トピック一覧'; @@ -297,6 +300,9 @@ class ZulipLocalizationsJa extends ZulipLocalizations { @override String get successMessageLinkCopied => 'Message link copied'; + @override + String get successChannelLinkCopied => 'Channel link copied'; + @override String get errorBannerDeactivatedDmLabel => 'You cannot send messages to deactivated users.'; diff --git a/lib/generated/l10n/zulip_localizations_nb.dart b/lib/generated/l10n/zulip_localizations_nb.dart index 0568bc0ae7..73b5a00f0e 100644 --- a/lib/generated/l10n/zulip_localizations_nb.dart +++ b/lib/generated/l10n/zulip_localizations_nb.dart @@ -90,6 +90,9 @@ class ZulipLocalizationsNb extends ZulipLocalizations { @override String get actionSheetOptionMarkChannelAsRead => 'Mark channel as read'; + @override + String get actionSheetOptionCopyChannelLink => 'Copy link to channel'; + @override String get actionSheetOptionListOfTopics => 'List of topics'; @@ -300,6 +303,9 @@ class ZulipLocalizationsNb extends ZulipLocalizations { @override String get successMessageLinkCopied => 'Message link copied'; + @override + String get successChannelLinkCopied => 'Channel link copied'; + @override String get errorBannerDeactivatedDmLabel => 'You cannot send messages to deactivated users.'; diff --git a/lib/generated/l10n/zulip_localizations_pl.dart b/lib/generated/l10n/zulip_localizations_pl.dart index c96ab24679..2861c82c8c 100644 --- a/lib/generated/l10n/zulip_localizations_pl.dart +++ b/lib/generated/l10n/zulip_localizations_pl.dart @@ -92,6 +92,9 @@ class ZulipLocalizationsPl extends ZulipLocalizations { String get actionSheetOptionMarkChannelAsRead => 'Oznacz kanał jako przeczytany'; + @override + String get actionSheetOptionCopyChannelLink => 'Copy link to channel'; + @override String get actionSheetOptionListOfTopics => 'Lista wątków'; @@ -308,6 +311,9 @@ class ZulipLocalizationsPl extends ZulipLocalizations { @override String get successMessageLinkCopied => 'Skopiowano odnośnik wiadomości'; + @override + String get successChannelLinkCopied => 'Channel link copied'; + @override String get errorBannerDeactivatedDmLabel => 'Nie można wysyłać wiadomości do dezaktywowanych użytkowników.'; diff --git a/lib/generated/l10n/zulip_localizations_ru.dart b/lib/generated/l10n/zulip_localizations_ru.dart index be5de60e97..5fdf3c879c 100644 --- a/lib/generated/l10n/zulip_localizations_ru.dart +++ b/lib/generated/l10n/zulip_localizations_ru.dart @@ -92,6 +92,9 @@ class ZulipLocalizationsRu extends ZulipLocalizations { String get actionSheetOptionMarkChannelAsRead => 'Отметить канал как прочитанный'; + @override + String get actionSheetOptionCopyChannelLink => 'Copy link to channel'; + @override String get actionSheetOptionListOfTopics => 'Список тем'; @@ -309,6 +312,9 @@ class ZulipLocalizationsRu extends ZulipLocalizations { @override String get successMessageLinkCopied => 'Ссылка на сообщение скопирована'; + @override + String get successChannelLinkCopied => 'Channel link copied'; + @override String get errorBannerDeactivatedDmLabel => 'Нельзя отправить сообщение отключенным пользователям.'; diff --git a/lib/generated/l10n/zulip_localizations_sk.dart b/lib/generated/l10n/zulip_localizations_sk.dart index 33b4465eb6..e79d81866f 100644 --- a/lib/generated/l10n/zulip_localizations_sk.dart +++ b/lib/generated/l10n/zulip_localizations_sk.dart @@ -90,6 +90,9 @@ class ZulipLocalizationsSk extends ZulipLocalizations { @override String get actionSheetOptionMarkChannelAsRead => 'Mark channel as read'; + @override + String get actionSheetOptionCopyChannelLink => 'Copy link to channel'; + @override String get actionSheetOptionListOfTopics => 'List of topics'; @@ -300,6 +303,9 @@ class ZulipLocalizationsSk extends ZulipLocalizations { @override String get successMessageLinkCopied => 'Message link copied'; + @override + String get successChannelLinkCopied => 'Channel link copied'; + @override String get errorBannerDeactivatedDmLabel => 'You cannot send messages to deactivated users.'; diff --git a/lib/generated/l10n/zulip_localizations_sl.dart b/lib/generated/l10n/zulip_localizations_sl.dart index 8d587b9085..df7ba7377e 100644 --- a/lib/generated/l10n/zulip_localizations_sl.dart +++ b/lib/generated/l10n/zulip_localizations_sl.dart @@ -90,6 +90,9 @@ class ZulipLocalizationsSl extends ZulipLocalizations { @override String get actionSheetOptionMarkChannelAsRead => 'Označi kanal kot prebran'; + @override + String get actionSheetOptionCopyChannelLink => 'Copy link to channel'; + @override String get actionSheetOptionListOfTopics => 'Seznam tem'; @@ -320,6 +323,9 @@ class ZulipLocalizationsSl extends ZulipLocalizations { String get successMessageLinkCopied => 'Povezava do sporočila je bila kopirana'; + @override + String get successChannelLinkCopied => 'Channel link copied'; + @override String get errorBannerDeactivatedDmLabel => 'Deaktiviranim uporabnikom ne morete pošiljati sporočil.'; diff --git a/lib/generated/l10n/zulip_localizations_uk.dart b/lib/generated/l10n/zulip_localizations_uk.dart index 6799942531..169af2fa7e 100644 --- a/lib/generated/l10n/zulip_localizations_uk.dart +++ b/lib/generated/l10n/zulip_localizations_uk.dart @@ -93,6 +93,9 @@ class ZulipLocalizationsUk extends ZulipLocalizations { String get actionSheetOptionMarkChannelAsRead => 'Позначити канал як прочитаний'; + @override + String get actionSheetOptionCopyChannelLink => 'Copy link to channel'; + @override String get actionSheetOptionListOfTopics => 'Список тем'; @@ -311,6 +314,9 @@ class ZulipLocalizationsUk extends ZulipLocalizations { String get successMessageLinkCopied => 'Посилання на повідомлення скопійовано'; + @override + String get successChannelLinkCopied => 'Channel link copied'; + @override String get errorBannerDeactivatedDmLabel => 'Ви не можете надсилати повідомлення деактивованим користувачам.'; diff --git a/lib/generated/l10n/zulip_localizations_zh.dart b/lib/generated/l10n/zulip_localizations_zh.dart index c44852c041..4b9bc3759b 100644 --- a/lib/generated/l10n/zulip_localizations_zh.dart +++ b/lib/generated/l10n/zulip_localizations_zh.dart @@ -90,6 +90,9 @@ class ZulipLocalizationsZh extends ZulipLocalizations { @override String get actionSheetOptionMarkChannelAsRead => 'Mark channel as read'; + @override + String get actionSheetOptionCopyChannelLink => 'Copy link to channel'; + @override String get actionSheetOptionListOfTopics => 'List of topics'; @@ -300,6 +303,9 @@ class ZulipLocalizationsZh extends ZulipLocalizations { @override String get successMessageLinkCopied => 'Message link copied'; + @override + String get successChannelLinkCopied => 'Channel link copied'; + @override String get errorBannerDeactivatedDmLabel => 'You cannot send messages to deactivated users.'; diff --git a/lib/widgets/action_sheet.dart b/lib/widgets/action_sheet.dart index f794e6ae70..b77c73a55d 100644 --- a/lib/widgets/action_sheet.dart +++ b/lib/widgets/action_sheet.dart @@ -205,6 +205,9 @@ void showChannelActionSheet(BuildContext context, { MarkChannelAsReadButton(pageContext: pageContext, channelId: channelId)); } + optionButtons.add( + CopyChannelLinkButton(channelId: channelId, pageContext: pageContext)); + _showActionSheet(pageContext, optionButtons: optionButtons); } @@ -256,6 +259,34 @@ class MarkChannelAsReadButton extends ActionSheetMenuItemButton { } } +class CopyChannelLinkButton extends ActionSheetMenuItemButton { + const CopyChannelLinkButton({ + super.key, + required this.channelId, + required super.pageContext, + }); + + final int channelId; + + @override + IconData get icon => ZulipIcons.link; + + @override + String label(ZulipLocalizations zulipLocalizations) { + return zulipLocalizations.actionSheetOptionCopyChannelLink; + } + + @override + void onPressed() async { + final localizations = ZulipLocalizations.of(pageContext); + final store = PerAccountStoreWidget.of(pageContext); + + PlatformActions.copyWithPopup(context: pageContext, + successContent: Text(localizations.successChannelLinkCopied), + data: ClipboardData(text: narrowLink(store, ChannelNarrow(channelId)).toString())); + } +} + /// Show a sheet of actions you can take on a topic. /// /// Needs a [PageRoot] ancestor. diff --git a/test/widgets/action_sheet_test.dart b/test/widgets/action_sheet_test.dart index 84d92fb587..28faa69ff6 100644 --- a/test/widgets/action_sheet_test.dart +++ b/test/widgets/action_sheet_test.dart @@ -243,6 +243,7 @@ void main() { check(actionSheetFinder).findsOne(); checkButton('List of topics'); checkButton('Mark channel as read'); + checkButton('Copy link to channel'); } testWidgets('show from inbox', (tester) async { @@ -342,6 +343,32 @@ void main() { expectedTitle: "Mark as read failed"); }); }); + + group('CopyChannelLinkButton', () { + setUp(() async { + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler( + SystemChannels.platform, + MockClipboard().handleMethodCall, + ); + }); + + Future tapCopyChannelLinkButton(WidgetTester tester) async { + await tester.ensureVisible(find.byIcon(ZulipIcons.link, skipOffstage: false)); + await tester.tap(find.byIcon(ZulipIcons.link)); + await tester.pump(); // [MenuItemButton.onPressed] called in a post-frame callback: flutter/flutter@e4a39fa2e + } + + testWidgets('copies channel link to clipboard', (tester) async { + await prepare(); + final narrow = ChannelNarrow(someChannel.streamId); + await showFromAppBar(tester, narrow: narrow); + + await tapCopyChannelLinkButton(tester); + await tester.pump(Duration.zero); + final expectedLink = narrowLink(store, narrow).toString(); + check(await Clipboard.getData('text/plain')).isNotNull().text.equals(expectedLink); + }); + }); }); group('topic action sheet', () {