@@ -10,16 +10,21 @@ import '../host/android_intents.dart';
1010import '../log.dart' ;
1111import '../model/binding.dart' ;
1212import '../model/narrow.dart' ;
13+ import '../model/store.dart' ;
14+ import 'action_sheet.dart' ;
1315import 'app.dart' ;
16+ import 'button.dart' ;
1417import 'color.dart' ;
1518import 'compose_box.dart' ;
1619import 'dialog.dart' ;
20+ import 'home.dart' ;
1721import 'message_list.dart' ;
1822import 'page.dart' ;
1923import 'recent_dm_conversations.dart' ;
2024import 'store.dart' ;
2125import 'subscription_list.dart' ;
2226import 'theme.dart' ;
27+ import 'user.dart' ;
2328
2429// Responds to receiving shared content from other apps.
2530class ShareService {
@@ -95,18 +100,10 @@ class ShareService {
95100 mimeType: mimeType);
96101 });
97102
98- if (globalStore.accounts.length == 1 ) {
99- unawaited (navigator.push (
100- SharePage .buildRoute (
101- accountId: globalStore.accounts.first.id,
102- sharedFiles: sharedFiles,
103- sharedText: intentSendEvent.extraText)));
104- } else {
105- unawaited (navigator.push (MaterialWidgetRoute (
106- page: ShareChooseAccountPage (
107- sharedFiles: sharedFiles,
108- sharedText: intentSendEvent.extraText))));
109- }
103+ unawaited (navigator.push (
104+ SharePage .buildRoute (
105+ sharedFiles: sharedFiles,
106+ sharedText: intentSendEvent.extraText)));
110107 }
111108}
112109
@@ -120,16 +117,18 @@ class SharePage extends StatelessWidget {
120117 final Iterable <FileToUpload >? sharedFiles;
121118 final String ? sharedText;
122119
123- static AccountRoute <void > buildRoute ({
124- required int accountId,
120+ static MaterialWidgetRoute <void > buildRoute ({
125121 required Iterable <FileToUpload >? sharedFiles,
126122 required String ? sharedText,
127123 }) {
128- return MaterialAccountWidgetRoute (
129- accountId: accountId,
130- page: SharePage (
131- sharedFiles: sharedFiles,
132- sharedText: sharedText));
124+ return MaterialWidgetRoute (
125+ // TODO either call [ChooseAccountForShareDialog.show] every time this
126+ // page initializes, or else have the [MultiAccountPageController]
127+ // default to the last-visited account
128+ page: MultiAccountPageProvider (
129+ // So that PageRoot.contextOf can be used for MultiAccountPageProvider.of
130+ child: PageRoot (
131+ child: SharePage (sharedFiles: sharedFiles, sharedText: sharedText))));
133132 }
134133
135134 void _handleNarrowSelect (BuildContext context, Narrow narrow) {
@@ -176,22 +175,27 @@ class SharePage extends StatelessWidget {
176175 Widget build (BuildContext context) {
177176 final zulipLocalizations = ZulipLocalizations .of (context);
178177 final designVariables = DesignVariables .of (context);
178+ final selectedAccountId = MultiAccountPageProvider .of (context).selectedAccountId;
179179
180- return DefaultTabController (
181- length: 2 ,
182- child: Scaffold (
183- appBar: AppBar (
184- title: Text (zulipLocalizations.sharePageTitle),
185- bottom: TabBar (
186- indicatorColor: designVariables.icon,
187- labelColor: designVariables.foreground,
188- unselectedLabelColor: designVariables.foreground.withFadedAlpha (0.7 ),
189- splashFactory: NoSplash .splashFactory,
190- tabs: [
191- Tab (text: zulipLocalizations.channelsPageTitle),
192- Tab (text: zulipLocalizations.recentDmConversationsPageTitle),
193- ])),
194- body: TabBarView (children: [
180+ PreferredSizeWidget ? bottom;
181+ if (selectedAccountId != null ) {
182+ bottom = TabBar (
183+ indicatorColor: designVariables.icon,
184+ labelColor: designVariables.foreground,
185+ unselectedLabelColor: designVariables.foreground.withFadedAlpha (0.7 ),
186+ splashFactory: NoSplash .splashFactory,
187+ tabs: [
188+ Tab (text: zulipLocalizations.channelsPageTitle),
189+ Tab (text: zulipLocalizations.recentDmConversationsPageTitle),
190+ ]);
191+ }
192+
193+ final Widget ? body;
194+ if (selectedAccountId != null ) {
195+ body = PerAccountStoreWidget (
196+ accountId: selectedAccountId,
197+ placeholder: PageBodyEmptyContentPlaceholder (loading: true ),
198+ child: TabBarView (children: [
195199 SubscriptionListPageBody (
196200 showTopicListButtonInActionSheet: false ,
197201 hideChannelsIfUserCantPost: true ,
@@ -206,56 +210,139 @@ class SharePage extends StatelessWidget {
206210 RecentDmConversationsPageBody (
207211 hideDmsIfUserCantPost: true ,
208212 onDmSelect: (narrow) => _handleNarrowSelect (context, narrow)),
209- ])));
213+ ]));
214+ } else {
215+ body = PageBodyEmptyContentPlaceholder (
216+ // TODO i18n, choose the right wording
217+ message: 'No account is selected. Please use the button above to choose one.' );
218+ }
219+
220+ return DefaultTabController (
221+ length: 2 ,
222+ child: Scaffold (
223+ appBar: AppBar (
224+ title: Text (zulipLocalizations.sharePageTitle),
225+ actionsPadding: EdgeInsetsDirectional .only (end: 8 ),
226+ actions: [AccountSelectorButton ()],
227+ bottom: bottom),
228+ body: body));
210229 }
211230}
212231
213- class ShareChooseAccountPage extends StatelessWidget {
214- const ShareChooseAccountPage ({
215- super .key,
216- required this .sharedFiles,
217- required this .sharedText,
218- });
232+ class AccountSelectorButton extends StatelessWidget {
233+ const AccountSelectorButton ({super .key});
219234
220- final Iterable <FileToUpload >? sharedFiles;
221- final String ? sharedText;
235+ @override
236+ Widget build (BuildContext context) {
237+ final pageContext = PageRoot .contextOf (context);
238+ final controller = MultiAccountPageProvider .of (context);
239+ final selectedAccountId = controller.selectedAccountId;
240+
241+ if (selectedAccountId == null ) {
242+ return ZulipWebUiKitButton (
243+ attention: ZulipWebUiKitButtonAttention .high, // TODO medium looks better?
244+ label: 'Choose account' , // TODO i18n, choose the right text
245+ onPressed: () => ChooseAccountForShareDialog .show (pageContext));
246+ } else {
247+ return ZulipWebUiKitButton (
248+ attention: ZulipWebUiKitButtonAttention .medium, // TODO low looks better?
249+ label: 'Change account' , // TODO i18n, choose the right text
250+ buildIcon: (size) => PerAccountStoreWidget (
251+ accountId: selectedAccountId,
252+ placeholder: SizedBox .square (dimension: size), // TODO(#1036) realm logo
253+ child: Builder (builder: (context) {
254+ final store = PerAccountStoreWidget .of (context);
255+ return AvatarShape (size: size, borderRadius: 3 ,
256+ // TODO get realm logo from `store`
257+ child: ColoredBox (color: Colors .pink));
258+ })),
259+ onPressed: () => ChooseAccountForShareDialog .show (pageContext));
260+ }
261+ }
262+ }
263+
264+ /// A dialog offering the list of accounts,
265+ /// for one to be chosen to share to.
266+ class ChooseAccountForShareDialog extends StatelessWidget {
267+ const ChooseAccountForShareDialog ._(this .pageContext);
268+
269+ final BuildContext pageContext;
270+
271+ static void show (BuildContext pageContext) async {
272+ unawaited (showModalBottomSheet <void >(
273+ context: pageContext,
274+ // Clip.hardEdge looks bad; Clip.antiAliasWithSaveLayer looks pixel-perfect
275+ // on my iPhone 13 Pro but is marked as "much slower":
276+ // https://api.flutter.dev/flutter/dart-ui/Clip.html
277+ clipBehavior: Clip .antiAlias,
278+ useSafeArea: true ,
279+ isScrollControlled: true ,
280+ builder: (_) {
281+ return SafeArea (
282+ minimum: const EdgeInsets .only (bottom: 16 ),
283+ child: ChooseAccountForShareDialog ._(pageContext));
284+ }));
285+ }
222286
223287 @override
224288 Widget build (BuildContext context) {
225- final colorScheme = ColorScheme .of (context);
226- final zulipLocalizations = ZulipLocalizations .of (context);
227- assert (! PerAccountStoreWidget .debugExistsOf (context));
228289 final globalStore = GlobalStoreWidget .of (context);
290+ final accountIds = globalStore.accountIds.toList ();
291+ final controller = MultiAccountPageProvider .of (pageContext);
292+ final content = SliverList .builder (
293+ itemCount: accountIds.length,
294+ itemBuilder: (_, index) {
295+ final accountId = accountIds[index];
296+ final account = globalStore.getAccount (accountId);
297+ return _AccountButton (
298+ account! ,
299+ handlePressed: () => controller.selectAccount (accountId),
300+ selected: accountId == controller.selectedAccountId);
301+ });
229302
230- return MenuButtonTheme (
231- data: MenuButtonThemeData (style: MenuItemButton .styleFrom (
232- backgroundColor: colorScheme.secondaryContainer,
233- foregroundColor: colorScheme.onSecondaryContainer)),
234- child: Scaffold (
235- appBar: AppBar (title: Text (zulipLocalizations.sharePageTitle)),
236- body: SafeArea (
237- minimum: const EdgeInsets .fromLTRB (8 , 0 , 8 , 8 ),
238- child: Center (
239- child: ConstrainedBox (
240- constraints: const BoxConstraints (maxWidth: 400 ),
241- child: Column (mainAxisSize: MainAxisSize .min, children: [
242- Text (zulipLocalizations.shareChooseAccountLabel,
243- textAlign: TextAlign .center,
244- style: TextStyle (fontSize: 18 , height: 22 / 18 )),
245- Flexible (child: SingleChildScrollView (
246- padding: const EdgeInsets .only (top: 8 ),
247- child: Column (mainAxisSize: MainAxisSize .min, children: [
248- for (final (: accountId, : account) in globalStore.accountEntries)
249- ChooseAccountListItem (
250- accountId: accountId,
251- title: Text (account.realmUrl.toString ()),
252- subtitle: Text (account.email),
253- showLogoutMenu: false ,
254- onTap: () => Navigator .of (context).push (SharePage .buildRoute (
255- accountId: accountId,
256- sharedFiles: sharedFiles,
257- sharedText: sharedText))),
258- ]))),
259- ]))))));
303+ return DraggableScrollableModalBottomSheet (
304+ header: Padding (
305+ padding: const EdgeInsets .only (top: 8 ),
306+ child: BottomSheetHeader (title: 'Choose an account:' )),
307+ contentSliver: SliverPadding (
308+ padding: EdgeInsets .symmetric (horizontal: 8 ),
309+ sliver: content));
310+ }
311+ }
312+
313+ class _AccountButton extends MenuButton {
314+ const _AccountButton (this .account, {
315+ required this .handlePressed,
316+ required bool selected,
317+ }) : _selected = selected;
318+
319+ final Account account;
320+ final VoidCallback handlePressed;
321+
322+ @override
323+ bool get selected => _selected;
324+ final bool _selected;
325+
326+ @override
327+ IconData ? get icon => null ;
328+
329+ @override
330+ Widget buildLeading (BuildContext context) {
331+ return AvatarShape (
332+ size: MenuButton .iconSize,
333+ borderRadius: 4 ,
334+ // TODO(#1036) realm logo
335+ child: ColoredBox (color: Colors .pink));
336+ }
337+
338+ @override
339+ String label (ZulipLocalizations zulipLocalizations) {
340+ // TODO(#1036) realm name (and email?)
341+ return account.email;
342+ }
343+
344+ @override
345+ void onPressed (BuildContext context) {
346+ handlePressed ();
260347 }
261348}
0 commit comments