@@ -8,6 +8,8 @@ import 'package:flutter_gen/gen_l10n/zulip_localizations.dart';
88import '../log.dart' ;
99import '../model/localizations.dart' ;
1010import '../model/narrow.dart' ;
11+ import '../model/store.dart' ;
12+ import '../notifications/receive.dart' ;
1113import 'about_zulip.dart' ;
1214import 'app_bar.dart' ;
1315import 'dialog.dart' ;
@@ -221,15 +223,63 @@ class ChooseAccountPage extends StatelessWidget {
221223 required Widget title,
222224 Widget ? subtitle,
223225 }) {
226+ final designVariables = DesignVariables .of (context);
227+ final zulipLocalizations = ZulipLocalizations .of (context);
224228 return Card (
225229 clipBehavior: Clip .hardEdge,
226230 child: ListTile (
227231 title: title,
228232 subtitle: subtitle,
233+ trailing: PopupMenuButton <AccountItemOverflowMenuItem >(
234+ iconColor: designVariables.icon,
235+ itemBuilder: (context) => [
236+ PopupMenuItem (
237+ value: AccountItemOverflowMenuItem .logOut,
238+ child: Text (zulipLocalizations.chooseAccountPageLogOutButton)),
239+ ],
240+ onSelected: (item) async {
241+ switch (item) {
242+ case AccountItemOverflowMenuItem .logOut: {
243+ unawaited (_logOutAccount (context, accountId));
244+ }
245+ }
246+ }),
247+ // The default trailing padding with M3 is 24px. Decrease by 12 because
248+ // PopupMenuButton adds 12px padding on all sides of the "…" icon.
249+ contentPadding: const EdgeInsetsDirectional .only (start: 16 , end: 12 ),
229250 onTap: () => Navigator .push (context,
230251 HomePage .buildRoute (accountId: accountId))));
231252 }
232253
254+ Future <void > _logOutAccount (BuildContext context, int accountId) async {
255+ final globalStore = GlobalStoreWidget .of (context);
256+
257+ final account = globalStore.getAccount (accountId);
258+ if (account == null ) return ; // TODO(log)
259+
260+ // Async IIFE to not block removing the account on the unregister-token request.
261+ unawaited (() async {
262+ // TODO(#322) use actual acked push token; until #322, this is just null.
263+ final token = account.ackedPushToken
264+ // Try the current token as a fallback; maybe the server has registered
265+ // it and we just haven't recorded that fact in the client.
266+ ?? NotificationService .instance.token.value;
267+ if (token == null ) return ;
268+
269+ final connection = globalStore.apiConnectionFromAccount (account);
270+ try {
271+ await NotificationService .unregisterToken (connection, token: token);
272+ } catch (e) {
273+ // TODO retry? handle failures?
274+ } finally {
275+ connection.close ();
276+ }
277+ }());
278+
279+ // TODO error handling? disable logout button during this?
280+ await globalStore.removeAccount (accountId);
281+ }
282+
233283 @override
234284 Widget build (BuildContext context) {
235285 final zulipLocalizations = ZulipLocalizations .of (context);
@@ -286,6 +336,8 @@ class ChooseAccountPageOverflowButton extends StatelessWidget {
286336 }
287337}
288338
339+ enum AccountItemOverflowMenuItem { logOut }
340+
289341class HomePage extends StatelessWidget {
290342 const HomePage ({super .key});
291343
0 commit comments