diff --git a/mobile/ios/Runner/Info.plist b/mobile/ios/Runner/Info.plist
index 81227c202..52370f667 100644
--- a/mobile/ios/Runner/Info.plist
+++ b/mobile/ios/Runner/Info.plist
@@ -84,5 +84,12 @@
UIInterfaceOrientationLandscapeLeft
UIInterfaceOrientationLandscapeRight
+ UISupportedInterfaceOrientations~ipad
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationPortraitUpsideDown
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
diff --git a/mobile/lib/features/home/home_page/wide_navigation.dart b/mobile/lib/features/home/home_page/wide_navigation.dart
index 0c8e4027e..aa96ca80c 100644
--- a/mobile/lib/features/home/home_page/wide_navigation.dart
+++ b/mobile/lib/features/home/home_page/wide_navigation.dart
@@ -7,7 +7,7 @@ class _WideNavigationSidebar extends HookConsumerWidget {
final String? selectedChannelId;
final VoidCallback onProfileSelected;
final bool isCommunitySwitching;
- final ValueChanged onCommunitySwitchStart;
+ final ValueChanged onCommunitySwitchStart;
final List<_HomeDestination> destinations;
const _WideNavigationSidebar({
@@ -285,7 +285,7 @@ class _WideNavigationSectionLabel extends StatelessWidget {
class _WideNavigationCommunitySwitcher extends HookConsumerWidget {
final VoidCallback onTap;
- final ValueChanged onCommunitySwitchStart;
+ final ValueChanged onCommunitySwitchStart;
const _WideNavigationCommunitySwitcher({
required this.onTap,
@@ -350,9 +350,19 @@ class _WideNavigationCommunitySwitcher extends HookConsumerWidget {
if (!context.mounted) return;
onCommunitySwitchStart(nextCommunity.id);
unawaited(HapticFeedback.selectionClick());
- await ref
- .read(communityListProvider.notifier)
- .switchCommunity(nextCommunity.id);
+ try {
+ await ref
+ .read(communityListProvider.notifier)
+ .switchCommunity(nextCommunity.id);
+ } catch (error) {
+ debugPrint('[CommunitySwitcher] failed to switch community: $error');
+ if (!context.mounted) return;
+ onCommunitySwitchStart(null);
+ swipeProgress.value = 0;
+ switchTarget.value = null;
+ isSwitching.value = false;
+ return;
+ }
if (!context.mounted) return;
swipeProgress.value = 0;
switchTarget.value = null;
diff --git a/mobile/test/features/home/home_page_test.dart b/mobile/test/features/home/home_page_test.dart
index e24384004..fc8cf847c 100644
--- a/mobile/test/features/home/home_page_test.dart
+++ b/mobile/test/features/home/home_page_test.dart
@@ -658,6 +658,84 @@ void main() {
);
});
+ testWidgets('a failed community swipe restores the workspace', (
+ tester,
+ ) async {
+ tester.view.physicalSize = const Size(1180, 820);
+ tester.view.devicePixelRatio = 1;
+ addTearDown(() {
+ tester.view.resetPhysicalSize();
+ tester.view.resetDevicePixelRatio();
+ });
+ final communityListNotifier = _FailingCommunityListNotifier([
+ Community(
+ id: 'alpha',
+ name: 'Alpha',
+ relayUrl: 'wss://alpha.example.com',
+ addedAt: DateTime(2026),
+ ),
+ Community(
+ id: 'bravo',
+ name: 'Bravo',
+ relayUrl: 'wss://bravo.example.com',
+ addedAt: DateTime(2026),
+ ),
+ ]);
+
+ await tester.pumpWidget(
+ await buildHome(
+ communityListNotifier: communityListNotifier,
+ activeCommunity: communityListNotifier.communities.first,
+ ),
+ );
+ await tester.pump();
+ await tester.pump(const Duration(milliseconds: 1));
+
+ Future swipe() async {
+ final gesture = tester.widget(
+ find.descendant(
+ of: find.byKey(const Key('wide-navigation-community-icon')),
+ matching: find.byType(GestureDetector),
+ ),
+ );
+ gesture.onVerticalDragStart!(
+ DragStartDetails(globalPosition: Offset.zero),
+ );
+ gesture.onVerticalDragUpdate!(
+ DragUpdateDetails(
+ globalPosition: const Offset(0, 40),
+ delta: const Offset(0, 40),
+ ),
+ );
+ await tester.pump();
+ gesture.onVerticalDragEnd!(DragEndDetails(velocity: Velocity.zero));
+ await tester.pump();
+ await tester.pump(const Duration(milliseconds: 120));
+ await tester.pump();
+ await tester.pump(const Duration(milliseconds: 120));
+ await tester.pump();
+ }
+
+ await swipe();
+ expect(communityListNotifier.switchAttempts, 1);
+ expect(
+ tester
+ .widget(
+ find.ancestor(
+ of: find.byKey(
+ const Key('wide-navigation-community-switch-skeleton'),
+ ),
+ matching: find.byType(SkeletonShimmer),
+ ),
+ )
+ .enabled,
+ isFalse,
+ );
+
+ await swipe();
+ expect(communityListNotifier.switchAttempts, 2);
+ });
+
testWidgets('gives selection haptics only when the tab changes', (
tester,
) async {
@@ -783,3 +861,19 @@ class _FakeCommunityListNotifier extends CommunityListNotifier {
switchedToId = id;
}
}
+
+class _FailingCommunityListNotifier extends CommunityListNotifier {
+ _FailingCommunityListNotifier(this.communities);
+
+ final List communities;
+ int switchAttempts = 0;
+
+ @override
+ Future> build() async => communities;
+
+ @override
+ Future switchCommunity(String id) async {
+ switchAttempts++;
+ throw Exception('Unable to save active community');
+ }
+}