Skip to content

Commit

Permalink
feat: deep links
Browse files Browse the repository at this point in the history
  • Loading branch information
tom-anders committed Jan 27, 2025
1 parent fcc09d4 commit 856686c
Show file tree
Hide file tree
Showing 5 changed files with 120 additions and 37 deletions.
16 changes: 16 additions & 0 deletions android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,22 @@
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>

<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />

<data android:scheme="https" android:host="lichess.org" />

<data android:pathPattern="/training/....." />
<data android:pathPattern="/study/........" />

<!-- Either game or challenge -->
<data android:pathPattern="/........" />
<data android:pathPattern="/......../black" />
<data android:pathPattern="/......../white" />
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
Expand Down
15 changes: 14 additions & 1 deletion lib/src/app.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import 'package:lichess_mobile/src/network/connectivity.dart';
import 'package:lichess_mobile/src/network/http.dart';
import 'package:lichess_mobile/src/network/socket.dart';
import 'package:lichess_mobile/src/styles/styles.dart';
import 'package:lichess_mobile/src/utils/app_links.dart';
import 'package:lichess_mobile/src/utils/navigation.dart';
import 'package:lichess_mobile/src/utils/screen.dart';

/// Application initialization and main entry point.
Expand Down Expand Up @@ -214,8 +216,19 @@ class _AppState extends ConsumerState<Application> {
);
}
: null,
home: const BottomNavScaffold(),
navigatorObservers: [rootNavPageRouteObserver],
onGenerateRoute:
(settings) =>
settings.name != null
? resolveAppLinkUri(context, Uri.parse(settings.name!))
: null,
onGenerateInitialRoutes: (initialRoute) {
final homeRoute = createPlatformRoute(context, screen: const BottomNavScaffold());
return <Route<dynamic>?>[
homeRoute,
resolveAppLinkUri(context, Uri.parse(initialRoute)),
].nonNulls.toList(growable: false);
},
);
},
);
Expand Down
2 changes: 2 additions & 0 deletions lib/src/navigation.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:lichess_mobile/l10n/l10n.dart';
import 'package:lichess_mobile/src/network/connectivity.dart';
import 'package:lichess_mobile/src/utils/app_links.dart';
import 'package:lichess_mobile/src/utils/l10n_context.dart';
import 'package:lichess_mobile/src/utils/navigation.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/settings/settings_tab_screen.dart';
Expand Down
45 changes: 45 additions & 0 deletions lib/src/utils/app_links.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import 'package:dartchess/dartchess.dart';
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
import 'package:flutter/widgets.dart';
import 'package:lichess_mobile/src/model/common/id.dart';
import 'package:lichess_mobile/src/model/puzzle/puzzle_angle.dart';
import 'package:lichess_mobile/src/utils/navigation.dart';
import 'package:lichess_mobile/src/view/game/archived_game_screen.dart';
import 'package:lichess_mobile/src/view/puzzle/puzzle_screen.dart';
import 'package:lichess_mobile/src/view/study/study_screen.dart';

Route<dynamic>? resolveAppLinkUri(BuildContext context, Uri appLinkUri) {
if (appLinkUri.pathSegments.length < 2 || appLinkUri.pathSegments[1].isEmpty) {
return null;
}

final id = appLinkUri.pathSegments[1];

switch (appLinkUri.pathSegments[0]) {
case 'study':
return createPlatformRoute(context, screen: StudyScreen(id: StudyId(id)));
case 'training':
return createPlatformRoute(
context,
screen: PuzzleScreen(angle: PuzzleAngle.fromKey('mix'), puzzleId: PuzzleId(id)),
);
case _:
{
final gameId = GameId(appLinkUri.pathSegments[0]);
final orientation = appLinkUri.pathSegments.getOrNull(2);
if (gameId.isValid) {
return createPlatformRoute(
context,
screen: ArchivedGameScreen(
gameId: gameId,
orientation: orientation == 'black' ? Side.black : Side.white,
),
);
} else {
// TODO if it's not a game, it's a challenge.
// So we should show a accept/decline screen here.
return null;
}
}
}
}
79 changes: 43 additions & 36 deletions lib/src/utils/navigation.dart
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,7 @@ class CupertinoScreenRoute<T extends Object?> extends CupertinoPageRoute<T>

/// Push a new route using Navigator.
///
/// Either [builder] or [screen] must be provided.
///
/// If [builder] if provided, it will return a [MaterialPageRoute] on Android and
/// a [CupertinoPageRoute] on iOS.
///
/// If [screen] is provided, it will return a [MaterialScreenRoute] on Android and
/// a [CupertinoScreenRoute] on iOS.
/// {@macro lichess.navigation.builder_or_screen}
Future<void> pushPlatformRoute(
BuildContext context, {
Widget? screen,
Expand All @@ -63,29 +57,19 @@ Future<void> pushPlatformRoute(
assert(screen != null || builder != null, 'Either screen or builder must be provided.');

return Navigator.of(context, rootNavigator: rootNavigator).push<void>(
Theme.of(context).platform == TargetPlatform.iOS
? builder != null
? CupertinoPageRoute(builder: builder, title: title, fullscreenDialog: fullscreenDialog)
: CupertinoScreenRoute(
screen: screen!,
title: title,
fullscreenDialog: fullscreenDialog,
)
: builder != null
? MaterialPageRoute(builder: builder, fullscreenDialog: fullscreenDialog)
: MaterialScreenRoute(screen: screen!, fullscreenDialog: fullscreenDialog),
createPlatformRoute(
context,
builder: builder,
screen: screen,
fullscreenDialog: fullscreenDialog,
title: title,
),
);
}

/// Push a new route using Navigator and replace the current route.
///
/// Either [builder] or [screen] must be provided.
///
/// If [builder] if provided, it will return a [MaterialPageRoute] on Android and
/// a [CupertinoPageRoute] on iOS.
///
/// If [screen] is provided, it will return a [MaterialScreenRoute] on Android and
/// a [CupertinoScreenRoute] on iOS.
/// {@macro lichess.navigation.builder_or_screen}
Future<void> pushReplacementPlatformRoute(
BuildContext context, {
WidgetBuilder? builder,
Expand All @@ -95,16 +79,39 @@ Future<void> pushReplacementPlatformRoute(
String? title,
}) {
return Navigator.of(context, rootNavigator: rootNavigator).pushReplacement<void, void>(
Theme.of(context).platform == TargetPlatform.iOS
? builder != null
? CupertinoPageRoute(builder: builder, title: title, fullscreenDialog: fullscreenDialog)
: CupertinoScreenRoute(
screen: screen!,
title: title,
fullscreenDialog: fullscreenDialog,
)
: builder != null
? MaterialPageRoute(builder: builder, fullscreenDialog: fullscreenDialog)
: MaterialScreenRoute(screen: screen!, fullscreenDialog: fullscreenDialog),
createPlatformRoute(
context,
builder: builder,
screen: screen,
fullscreenDialog: fullscreenDialog,
title: title,
),
);
}

/// Create a route from either [builder] or [screen].
///
/// {@template lichess.navigation.builder_or_screen}
/// If [builder] is provided, it will return a [MaterialPageRoute] on Android and
/// a [CupertinoPageRoute] on iOS.
///
/// If [screen] is provided, it will return a [MaterialScreenRoute] on Android and
/// a [CupertinoScreenRoute] on iOS.
/// {@endtemplate}
Route<dynamic> createPlatformRoute(
BuildContext context, {
Widget? screen,
WidgetBuilder? builder,
bool fullscreenDialog = false,
String? title,
}) {
assert(screen != null || builder != null, 'Either screen or builder must be provided.');

return Theme.of(context).platform == TargetPlatform.iOS
? builder != null
? CupertinoPageRoute(builder: builder, title: title, fullscreenDialog: fullscreenDialog)
: CupertinoScreenRoute(screen: screen!, title: title, fullscreenDialog: fullscreenDialog)
: builder != null
? MaterialPageRoute(builder: builder, fullscreenDialog: fullscreenDialog)
: MaterialScreenRoute(screen: screen!, fullscreenDialog: fullscreenDialog);
}

0 comments on commit 856686c

Please sign in to comment.