diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 6984e133e7..5030c533b1 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -21,7 +21,7 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/lib/src/app.dart b/lib/src/app.dart
index 52d6d55b89..bee6d29036 100644
--- a/lib/src/app.dart
+++ b/lib/src/app.dart
@@ -1,4 +1,5 @@
import 'dart:async';
+import 'dart:io';
import 'package:app_links/app_links.dart';
import 'package:flutter/material.dart';
@@ -23,6 +24,8 @@ import 'package:lichess_mobile/src/quick_actions.dart';
import 'package:lichess_mobile/src/tab_scaffold.dart';
import 'package:lichess_mobile/src/theme.dart';
import 'package:lichess_mobile/src/utils/screen.dart';
+import 'package:lichess_mobile/src/view/more/import_pgn_screen.dart';
+import 'package:receive_sharing_intent/receive_sharing_intent.dart';
/// Application initialization and main entry point.
class AppInitializationScreen extends ConsumerWidget {
@@ -65,7 +68,9 @@ class _AppState extends ConsumerState {
bool _firstTimeOnlineCheck = false;
final _appLinks = AppLinks();
final _navigatorKey = GlobalKey();
+
StreamSubscription? _linkSubscription;
+ StreamSubscription>? _intentSub;
@override
void initState() {
@@ -109,11 +114,13 @@ class _AppState extends ConsumerState {
super.initState();
_initAppLinks();
+ _initSharingIntent();
}
@override
void dispose() {
_linkSubscription?.cancel();
+ _intentSub?.cancel();
super.dispose();
}
@@ -149,10 +156,47 @@ class _AppState extends ConsumerState {
Future _initAppLinks() async {
_linkSubscription = _appLinks.uriLinkStream.listen((uri) {
+ // File links are handled by the sharing intent logic, so we can ignore them here.
+ if (uri.scheme == 'file' || uri.scheme == 'content') {
+ return;
+ }
final context = _navigatorKey.currentContext;
if (context != null && context.mounted) {
handleAppLink(context, uri);
}
});
}
+
+ void _initSharingIntent() {
+ // Warm start
+ _intentSub = ReceiveSharingIntent.instance.getMediaStream().listen((
+ List value,
+ ) {
+ _processSharedFiles(value);
+ });
+
+ // Cold start
+ ReceiveSharingIntent.instance.getInitialMedia().then((List value) {
+ _processSharedFiles(value);
+ ReceiveSharingIntent.instance.reset();
+ });
+ }
+
+ Future _processSharedFiles(List files) async {
+ if (files.isEmpty) return;
+ final filePath = files.first.path;
+ try {
+ final context = _navigatorKey.currentContext;
+ if (context == null || !context.mounted) return;
+
+ final file = File(filePath);
+ final pgnText = await file.readAsString();
+
+ if (context.mounted) {
+ ImportPgnScreen.handlePgnText(context, pgnText);
+ }
+ } catch (e) {
+ debugPrint('Failed to process incoming file: $e');
+ }
+ }
}
diff --git a/lib/src/view/more/import_pgn_screen.dart b/lib/src/view/more/import_pgn_screen.dart
index e1c5307f15..52c18cae2f 100644
--- a/lib/src/view/more/import_pgn_screen.dart
+++ b/lib/src/view/more/import_pgn_screen.dart
@@ -22,6 +22,43 @@ class ImportPgnScreen extends StatelessWidget {
return buildScreenRoute(context, screen: const ImportPgnScreen());
}
+ static void handlePgnText(BuildContext context, String text) {
+ try {
+ final games = PgnGame.parseMultiGamePgn(text);
+
+ if (games.isEmpty) {
+ showSnackBar(context, context.l10n.invalidPgn, type: .error);
+ return;
+ }
+
+ if (games.length == 1) {
+ final game = games.first;
+ final rule = Rule.fromPgn(game.headers['Variant']);
+
+ Navigator.of(context, rootNavigator: true).push(
+ AnalysisScreen.buildRoute(
+ context,
+ AnalysisOptions.pgn(
+ id: const StringId('pgn_import_single_game'),
+ orientation: .white,
+ pgn: text,
+ isComputerAnalysisAllowed: true,
+ initialMoveCursor: game.moves.mainline().isEmpty ? 0 : 1,
+ variant: rule != null ? Variant.fromRule(rule) : .standard,
+ ),
+ ),
+ );
+ } else {
+ Navigator.of(
+ context,
+ rootNavigator: true,
+ ).push(PgnGamesListScreen.buildRoute(context, games.lock));
+ }
+ } catch (_) {
+ showSnackBar(context, context.l10n.invalidPgn, type: .error);
+ }
+ }
+
@override
Widget build(BuildContext context) {
return Scaffold(
@@ -73,43 +110,6 @@ class _BodyState extends State<_Body> {
);
}
- void _handlePgnText(String text) {
- try {
- final games = PgnGame.parseMultiGamePgn(text);
-
- if (games.isEmpty) {
- showSnackBar(context, context.l10n.invalidPgn, type: SnackBarType.error);
- return;
- }
-
- if (games.length == 1) {
- final game = games.first;
- final rule = Rule.fromPgn(game.headers['Variant']);
-
- Navigator.of(context, rootNavigator: true).push(
- AnalysisScreen.buildRoute(
- context,
- AnalysisOptions.pgn(
- id: const StringId('pgn_import_single_game'),
- orientation: Side.white,
- pgn: text,
- isComputerAnalysisAllowed: true,
- initialMoveCursor: game.moves.mainline().isEmpty ? 0 : 1,
- variant: rule != null ? Variant.fromRule(rule) : Variant.standard,
- ),
- ),
- );
- } else {
- Navigator.of(
- context,
- rootNavigator: true,
- ).push(PgnGamesListScreen.buildRoute(context, games.lock));
- }
- } catch (_) {
- showSnackBar(context, context.l10n.invalidPgn, type: SnackBarType.error);
- }
- }
-
Future _getClipboardData() async {
final ClipboardData? data = await Clipboard.getData(Clipboard.kTextPlain);
if (data?.text == null) return;
@@ -118,7 +118,7 @@ class _BodyState extends State<_Body> {
final text = data!.text!.trim();
if (text.isEmpty) return;
- _handlePgnText(text);
+ ImportPgnScreen.handlePgnText(context, text);
}
Future _pickPgnFile() async {
@@ -132,7 +132,7 @@ class _BodyState extends State<_Body> {
if (result != null && result.files.single.bytes != null) {
final content = utf8.decode(result.files.single.bytes!);
if (mounted) {
- _handlePgnText(content);
+ ImportPgnScreen.handlePgnText(context, content);
}
}
} catch (e) {
diff --git a/pubspec.lock b/pubspec.lock
index 233a947a24..2a81dc2761 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -1279,6 +1279,15 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.2.2"
+ receive_sharing_intent:
+ dependency: "direct main"
+ description:
+ path: "."
+ ref: "2cea396843cd3ab1b5ec4334be4233864637874e"
+ resolved-ref: "2cea396843cd3ab1b5ec4334be4233864637874e"
+ url: "https://github.com/KasemJaffer/receive_sharing_intent"
+ source: git
+ version: "1.8.1"
result_extensions:
dependency: "direct main"
description:
diff --git a/pubspec.yaml b/pubspec.yaml
index ee558355bb..bd34714eec 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -68,6 +68,10 @@ dependencies:
popover: ^0.4.0
pub_semver: ^2.1.4
quick_actions: ^1.1.0
+ receive_sharing_intent:
+ git:
+ url: https://github.com/KasemJaffer/receive_sharing_intent
+ ref: 2cea396843cd3ab1b5ec4334be4233864637874e
result_extensions: ^0.2.0
share_plus: ^12.0.0
shared_preferences: ^2.1.0