Skip to content

Commit e38794e

Browse files
committed
NOMERGE rough draft for alternative choose-account-for-share approach
1 parent e883035 commit e38794e

File tree

1 file changed

+163
-76
lines changed

1 file changed

+163
-76
lines changed

lib/widgets/share.dart

Lines changed: 163 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,21 @@ import '../host/android_intents.dart';
1010
import '../log.dart';
1111
import '../model/binding.dart';
1212
import '../model/narrow.dart';
13+
import '../model/store.dart';
14+
import 'action_sheet.dart';
1315
import 'app.dart';
16+
import 'button.dart';
1417
import 'color.dart';
1518
import 'compose_box.dart';
1619
import 'dialog.dart';
20+
import 'home.dart';
1721
import 'message_list.dart';
1822
import 'page.dart';
1923
import 'recent_dm_conversations.dart';
2024
import 'store.dart';
2125
import 'subscription_list.dart';
2226
import 'theme.dart';
27+
import 'user.dart';
2328

2429
// Responds to receiving shared content from other apps.
2530
class 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

Comments
 (0)