From cea09bb2d617d3b35bfd0804e6c713a0b57e0449 Mon Sep 17 00:00:00 2001 From: Frank Stack <294698533+FrankBStack@users.noreply.github.com> Date: Sun, 6 Sep 2026 01:56:25 -0400 Subject: [PATCH] Don't hide Find My / Shared Albums / Passwords in the menu before state loads The overflow menu decides whether to show the Find My, Shared Albums and Passwords entries by looking at pushService.state, which is null for the first few seconds after launch. Opening the menu in that window gave a shorter menu than usual, and it stayed that way until the menu was closed and reopened. Remember the last known answer for each service in prefs whenever the push state is assigned, and fall back to that while state is still null. The cache is cleared when state is cleared on logout, so it can't outlive the account it was recorded for. Both the Material and Cupertino menus now go through the backend for these checks instead of poking at the push state directly. --- .../widgets/header/header_widgets.dart | 8 +-- lib/services/network/backend_service.dart | 2 + lib/services/network/http_service.dart | 10 ++++ lib/services/rustpush/rustpush_service.dart | 52 ++++++++++++++++++- 4 files changed, 66 insertions(+), 6 deletions(-) diff --git a/lib/app/layouts/conversation_list/widgets/header/header_widgets.dart b/lib/app/layouts/conversation_list/widgets/header/header_widgets.dart index 11832f706b..83917c0083 100644 --- a/lib/app/layouts/conversation_list/widgets/header/header_widgets.dart +++ b/lib/app/layouts/conversation_list/widgets/header/header_widgets.dart @@ -173,7 +173,7 @@ class MaterialOverflowMenu extends StatelessWidget { style: context.textTheme.bodyLarge!.apply(color: context.theme.colorScheme.properOnSurface), ), ), - if (pushService.state?.icloudServices?.sharedstreams != null) + if (backend.supportsSharedStreams()) PopupMenuItem( value: 9, child: Text( @@ -188,7 +188,7 @@ class MaterialOverflowMenu extends StatelessWidget { style: context.textTheme.bodyLarge!.apply(color: context.theme.colorScheme.properOnSurface), ), ), - if (pushService.state?.icloudServices?.keychain != null) + if (backend.supportsKeychain()) PopupMenuItem( value: 11, child: Text( @@ -323,7 +323,7 @@ class CupertinoOverflowMenu extends StatelessWidget { icon: CupertinoIcons.location, onTap: () => goToFindMy(context), ), - if (pushService.state?.icloudServices?.sharedstreams != null) + if (backend.supportsSharedStreams()) PullDownMenuItem( title: 'Shared Albums', icon: CupertinoIcons.photo, @@ -334,7 +334,7 @@ class CupertinoOverflowMenu extends StatelessWidget { icon: CupertinoIcons.video_camera, onTap: () => goToFaceTime(context), ), - if (pushService.state?.icloudServices?.keychain != null) + if (backend.supportsKeychain()) PullDownMenuItem( title: 'Passwords', icon: Icons.key, diff --git a/lib/services/network/backend_service.dart b/lib/services/network/backend_service.dart index 3429736805..7f06179c80 100644 --- a/lib/services/network/backend_service.dart +++ b/lib/services/network/backend_service.dart @@ -45,6 +45,8 @@ abstract class BackendService { {void Function(int, int)? onReceiveProgress, CancelToken? cancelToken}); bool canSchedule(); bool supportsFindMy(); + bool supportsSharedStreams(); + bool supportsKeychain(); bool canCreateGroupChats(); bool supportsSmsForwarding(); void startedTyping(Chat c, [iMessageAppData? appdata]); diff --git a/lib/services/network/http_service.dart b/lib/services/network/http_service.dart index 5912e460dc..724c039851 100644 --- a/lib/services/network/http_service.dart +++ b/lib/services/network/http_service.dart @@ -276,6 +276,16 @@ class HttpBackend implements BackendService { return ss.isMinCatalinaSync; } + @override + bool supportsSharedStreams() { + return false; + } + + @override + bool supportsKeychain() { + return false; + } + @override bool canCreateGroupChats() { return ss.canCreateGroupChatSync(); diff --git a/lib/services/rustpush/rustpush_service.dart b/lib/services/rustpush/rustpush_service.dart index 6deb3c1e42..5c238fcc26 100644 --- a/lib/services/rustpush/rustpush_service.dart +++ b/lib/services/rustpush/rustpush_service.dart @@ -1265,7 +1265,17 @@ class RustPushBackend implements BackendService { @override bool supportsFindMy() { - return pushService.state?.icloudServices?.fmfd != null; + return pushService.hasFindMy; + } + + @override + bool supportsSharedStreams() { + return pushService.hasSharedStreams; + } + + @override + bool supportsKeychain() { + return pushService.hasKeychain; } @override @@ -1307,7 +1317,45 @@ class RustPushBackend implements BackendService { } class RustPushService extends GetxService { - api.SharedPushState? state; + api.SharedPushState? _state; + api.SharedPushState? get state => _state; + set state(api.SharedPushState? value) { + _state = value; + _cacheCapabilities(value); + } + + // Which iCloud services the account has is only known once the push state has + // loaded, which happens a few seconds after launch. Remember the last known + // answer so UI built before that (the overflow menu, for example) doesn't + // briefly hide Find My / Shared Streams / Passwords. + static const _capFindMy = "capFindMy"; + static const _capSharedStreams = "capSharedStreams"; + static const _capKeychain = "capKeychain"; + + void _cacheCapabilities(api.SharedPushState? value) { + if (value == null) { + ss.prefs.remove(_capFindMy); + ss.prefs.remove(_capSharedStreams); + ss.prefs.remove(_capKeychain); + return; + } + final services = value.icloudServices; + ss.prefs.setBool(_capFindMy, services?.fmfd != null); + ss.prefs.setBool(_capSharedStreams, services?.sharedstreams != null); + ss.prefs.setBool(_capKeychain, services?.keychain != null); + } + + bool get hasFindMy => _state != null + ? _state!.icloudServices?.fmfd != null + : ss.prefs.getBool(_capFindMy) ?? false; + + bool get hasSharedStreams => _state != null + ? _state!.icloudServices?.sharedstreams != null + : ss.prefs.getBool(_capSharedStreams) ?? false; + + bool get hasKeychain => _state != null + ? _state!.icloudServices?.keychain != null + : ss.prefs.getBool(_capKeychain) ?? false; Mixpanel? mixpanel;