Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Offline mode behavior #645

Merged
merged 2 commits into from
Apr 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -257,4 +257,4 @@ SPEC CHECKSUMS:

PODFILE CHECKSUM: 76a583f8d75b3a8c6e4bdc97ae8783ef36cc7984

COCOAPODS: 1.15.0
COCOAPODS: 1.15.2
185 changes: 112 additions & 73 deletions lib/src/navigation.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@ import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:lichess_mobile/l10n/l10n.dart';
import 'package:lichess_mobile/src/utils/connectivity.dart';
import 'package:lichess_mobile/src/utils/l10n_context.dart';
import 'package:lichess_mobile/src/view/home/home_tab_screen.dart';
import 'package:lichess_mobile/src/view/puzzle/puzzle_tab_screen.dart';
import 'package:lichess_mobile/src/view/tools/tools_tab_screen.dart';
import 'package:lichess_mobile/src/view/watch/watch_tab_screen.dart';
import 'package:lichess_mobile/src/widgets/feedback.dart';

enum BottomTab {
home,
Expand Down Expand Up @@ -98,6 +100,11 @@ final watchScrollController = ScrollController(debugLabel: 'WatchScroll');
final RouteObserver<PageRoute<void>> rootNavPageRouteObserver =
RouteObserver<PageRoute<void>>();

final _cupertinoTabControllerProvider =
StateProvider<CupertinoTabController>((ref) {
return CupertinoTabController();
});

/// Implements a tabbed (iOS style) root layout and behavior structure.
///
/// This widget is intended to be used as the root of the app, and it provides
Expand All @@ -118,21 +125,32 @@ class BottomNavScaffold extends ConsumerWidget {
currentTab: currentTab,
tabBuilder: _androidTabBuilder,
),
bottomNavigationBar: NavigationBar(
selectedIndex: currentTab.index,
destinations: [
for (final tab in BottomTab.values)
NavigationDestination(
icon: Icon(tab == currentTab ? tab.activeIcon : tab.icon),
label: tab.label(context.l10n),
),
],
onDestinationSelected: (i) => _onItemTapped(ref, i),
bottomNavigationBar: Consumer(
builder: (context, ref, _) {
final connectivity = ref.watch(connectivityChangesProvider);
final isOnline = connectivity.value?.isOnline ?? true;
return NavigationBar(
selectedIndex: currentTab.index,
destinations: [
for (final tab in BottomTab.values)
NavigationDestination(
icon: Icon(tab == currentTab ? tab.activeIcon : tab.icon),
label: tab.label(context.l10n),
),
],
onDestinationSelected: (i) => _onItemTapped(ref, i, isOnline),
);
},
),
);
case TargetPlatform.iOS:
final tabController = ref.watch(_cupertinoTabControllerProvider);
final connectivity = ref.watch(connectivityChangesProvider);
final isOnline = connectivity.value?.isOnline ?? true;

return CupertinoTabScaffold(
tabBuilder: _iOSTabBuilder,
controller: tabController,
tabBar: CupertinoTabBar(
currentIndex: currentTab.index,
items: [
Expand All @@ -142,7 +160,18 @@ class BottomNavScaffold extends ConsumerWidget {
label: tab.label(context.l10n),
),
],
onTap: (i) => _onItemTapped(ref, i),
onTap: (i) {
if (i == BottomTab.watch.index && !isOnline) {
showPlatformSnackbar(
ref.context,
'Not available in offline mode',
type: SnackBarType.error,
);
tabController.index = currentTab.index;
return;
}
_onItemTapped(ref, i, isOnline);
},
),
);
default:
Expand All @@ -156,9 +185,10 @@ class BottomNavScaffold extends ConsumerWidget {
/// If the route is already at the first route, scroll the tab's root
/// scrollable to the top.
/// Otherwise, switch to the tapped tab.
void _onItemTapped(WidgetRef ref, int index) {
void _onItemTapped(WidgetRef ref, int index, bool isOnline) {
final curTab = ref.read(currentBottomTabProvider);
final tappedTab = BottomTab.values[index];

if (tappedTab == curTab) {
final navState = ref.read(currentNavigatorKeyProvider).currentState;
if (navState?.canPop() == true) {
Expand All @@ -174,72 +204,81 @@ class BottomNavScaffold extends ConsumerWidget {
}
}
} else {
ref.read(currentBottomTabProvider.notifier).state = tappedTab;
if (tappedTab == BottomTab.watch && !isOnline) {
showPlatformSnackbar(
ref.context,
'Not available in offline mode',
type: SnackBarType.error,
);
ref.read(currentBottomTabProvider.notifier).state = curTab;
return;
}
}
ref.read(currentBottomTabProvider.notifier).state = tappedTab;
}
}

Widget _androidTabBuilder(BuildContext context, int index) {
switch (index) {
case 0:
return _MaterialTabView(
navigatorKey: homeNavigatorKey,
tab: BottomTab.home,
builder: (context) => const HomeTabScreen(),
);
case 1:
return _MaterialTabView(
navigatorKey: puzzlesNavigatorKey,
tab: BottomTab.puzzles,
builder: (context) => const PuzzleTabScreen(),
);
case 2:
return _MaterialTabView(
navigatorKey: toolsNavigatorKey,
tab: BottomTab.tools,
builder: (context) => const ToolsTabScreen(),
);
case 3:
return _MaterialTabView(
navigatorKey: watchNavigatorKey,
tab: BottomTab.watch,
builder: (context) => const WatchTabScreen(),
);
default:
assert(false, 'Unexpected tab');
return const SizedBox.shrink();
}
Widget _androidTabBuilder(BuildContext context, int index) {
switch (index) {
case 0:
return _MaterialTabView(
navigatorKey: homeNavigatorKey,
tab: BottomTab.home,
builder: (context) => const HomeTabScreen(),
);
case 1:
return _MaterialTabView(
navigatorKey: puzzlesNavigatorKey,
tab: BottomTab.puzzles,
builder: (context) => const PuzzleTabScreen(),
);
case 2:
return _MaterialTabView(
navigatorKey: toolsNavigatorKey,
tab: BottomTab.tools,
builder: (context) => const ToolsTabScreen(),
);
case 3:
return _MaterialTabView(
navigatorKey: watchNavigatorKey,
tab: BottomTab.watch,
builder: (context) => const WatchTabScreen(),
);
default:
assert(false, 'Unexpected tab');
return const SizedBox.shrink();
}
}

Widget _iOSTabBuilder(BuildContext context, int index) {
switch (index) {
case 0:
return CupertinoTabView(
defaultTitle: context.l10n.play,
navigatorKey: homeNavigatorKey,
builder: (context) => const HomeTabScreen(),
);
case 1:
return CupertinoTabView(
defaultTitle: context.l10n.puzzles,
navigatorKey: puzzlesNavigatorKey,
builder: (context) => const PuzzleTabScreen(),
);
case 2:
return CupertinoTabView(
defaultTitle: context.l10n.tools,
navigatorKey: toolsNavigatorKey,
builder: (context) => const ToolsTabScreen(),
);
case 3:
return CupertinoTabView(
defaultTitle: context.l10n.watch,
navigatorKey: watchNavigatorKey,
builder: (context) => const WatchTabScreen(),
);
default:
assert(false, 'Unexpected tab');
return const SizedBox.shrink();
}
Widget _iOSTabBuilder(BuildContext context, int index) {
switch (index) {
case 0:
return CupertinoTabView(
defaultTitle: context.l10n.play,
navigatorKey: homeNavigatorKey,
builder: (context) => const HomeTabScreen(),
);
case 1:
return CupertinoTabView(
defaultTitle: context.l10n.puzzles,
navigatorKey: puzzlesNavigatorKey,
builder: (context) => const PuzzleTabScreen(),
);
case 2:
return CupertinoTabView(
defaultTitle: context.l10n.tools,
navigatorKey: toolsNavigatorKey,
builder: (context) => const ToolsTabScreen(),
);
case 3:
return CupertinoTabView(
defaultTitle: context.l10n.watch,
navigatorKey: watchNavigatorKey,
builder: (context) => const WatchTabScreen(),
);
default:
assert(false, 'Unexpected tab');
return const SizedBox.shrink();
}
}

Expand Down
36 changes: 26 additions & 10 deletions lib/src/view/home/home_tab_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -602,16 +602,32 @@ class _PlayerScreenButton extends ConsumerWidget {

@override
Widget build(BuildContext context, WidgetRef ref) {
return AppBarIconButton(
icon: const Icon(Icons.group),
semanticsLabel: context.l10n.players,
onPressed: () {
pushPlatformRoute(
context,
title: context.l10n.players,
builder: (_) => const PlayerScreen(),
);
},
final connectivity = ref.watch(connectivityChangesProvider);

return connectivity.when(
data: (connectivity) => AppBarIconButton(
icon: const Icon(Icons.group),
semanticsLabel: context.l10n.players,
onPressed: !connectivity.isOnline
? null
: () {
pushPlatformRoute(
context,
title: context.l10n.players,
builder: (_) => const PlayerScreen(),
);
},
),
error: (_, __) => AppBarIconButton(
icon: const Icon(Icons.group),
semanticsLabel: context.l10n.players,
onPressed: null,
),
loading: () => AppBarIconButton(
icon: const Icon(Icons.group),
semanticsLabel: context.l10n.players,
onPressed: null,
),
);
}
}
4 changes: 2 additions & 2 deletions pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1456,10 +1456,10 @@ packages:
dependency: transitive
description:
name: vm_service
sha256: a75f83f14ad81d5fe4b3319710b90dec37da0e22612326b696c9e1b8f34bbf48
sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec"
url: "https://pub.dev"
source: hosted
version: "14.2.0"
version: "14.2.1"
wakelock_plus:
dependency: "direct main"
description:
Expand Down