@@ -10,16 +10,21 @@ import '../host/android_intents.dart';
10
10
import '../log.dart' ;
11
11
import '../model/binding.dart' ;
12
12
import '../model/narrow.dart' ;
13
+ import '../model/store.dart' ;
14
+ import 'action_sheet.dart' ;
13
15
import 'app.dart' ;
16
+ import 'button.dart' ;
14
17
import 'color.dart' ;
15
18
import 'compose_box.dart' ;
16
19
import 'dialog.dart' ;
20
+ import 'home.dart' ;
17
21
import 'message_list.dart' ;
18
22
import 'page.dart' ;
19
23
import 'recent_dm_conversations.dart' ;
20
24
import 'store.dart' ;
21
25
import 'subscription_list.dart' ;
22
26
import 'theme.dart' ;
27
+ import 'user.dart' ;
23
28
24
29
// Responds to receiving shared content from other apps.
25
30
class ShareService {
@@ -95,18 +100,10 @@ class ShareService {
95
100
mimeType: mimeType);
96
101
});
97
102
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)));
110
107
}
111
108
}
112
109
@@ -120,16 +117,18 @@ class SharePage extends StatelessWidget {
120
117
final Iterable <FileToUpload >? sharedFiles;
121
118
final String ? sharedText;
122
119
123
- static AccountRoute <void > buildRoute ({
124
- required int accountId,
120
+ static MaterialWidgetRoute <void > buildRoute ({
125
121
required Iterable <FileToUpload >? sharedFiles,
126
122
required String ? sharedText,
127
123
}) {
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))));
133
132
}
134
133
135
134
void _handleNarrowSelect (BuildContext context, Narrow narrow) {
@@ -176,22 +175,27 @@ class SharePage extends StatelessWidget {
176
175
Widget build (BuildContext context) {
177
176
final zulipLocalizations = ZulipLocalizations .of (context);
178
177
final designVariables = DesignVariables .of (context);
178
+ final selectedAccountId = MultiAccountPageProvider .of (context).selectedAccountId;
179
179
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: [
195
199
SubscriptionListPageBody (
196
200
showTopicListButtonInActionSheet: false ,
197
201
hideChannelsIfUserCantPost: true ,
@@ -206,56 +210,139 @@ class SharePage extends StatelessWidget {
206
210
RecentDmConversationsPageBody (
207
211
hideDmsIfUserCantPost: true ,
208
212
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));
210
229
}
211
230
}
212
231
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});
219
234
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
+ }
222
286
223
287
@override
224
288
Widget build (BuildContext context) {
225
- final colorScheme = ColorScheme .of (context);
226
- final zulipLocalizations = ZulipLocalizations .of (context);
227
- assert (! PerAccountStoreWidget .debugExistsOf (context));
228
289
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
+ });
229
302
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 ();
260
347
}
261
348
}
0 commit comments