Skip to content

Comments

Open PGN files from file explorer, messages,... in the App on Android#2671

Open
HaonRekcef wants to merge 3 commits intolichess-org:mainfrom
HaonRekcef:open-pgn-in-app
Open

Open PGN files from file explorer, messages,... in the App on Android#2671
HaonRekcef wants to merge 3 commits intolichess-org:mainfrom
HaonRekcef:open-pgn-in-app

Conversation

@HaonRekcef
Copy link
Collaborator

@HaonRekcef HaonRekcef commented Feb 22, 2026

Allows to open .pgn files from outside the app on android. Preview:

open_pgn_files.mp4

Successfully tested on Android 8 and Android 15

I used the package receive_sharing_intent which seems to only be sporadically maintained, but I couldnt find a better option.
I needed to give a direct link to github for the package because of a fix that is not released yet: KasemJaffer/receive_sharing_intent#333

Needs to be implemented on the iOS side as well, but I don't have the devices to do it.

I changed the launchmode to single task to prevent multiple instances of the app getting created.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds the ability to open .pgn files from outside the Lichess mobile app on Android, allowing users to share or open PGN files from file explorers, messaging apps, and other sources directly into the app.

Changes:

  • Added the receive_sharing_intent package (via git dependency) to handle incoming file shares on Android
  • Refactored PGN handling logic in ImportPgnScreen to be reusable from the app entry point
  • Updated Android manifest to register intent filters for PGN files and changed launch mode to singleTask

Reviewed changes

Copilot reviewed 4 out of 5 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
pubspec.yaml Added receive_sharing_intent dependency from a specific GitHub commit
pubspec.lock Lock file update for the new dependency
lib/src/view/more/import_pgn_screen.dart Refactored PGN handling logic into a public static method for reuse
lib/src/app.dart Added sharing intent initialization and file processing logic
android/app/src/main/AndroidManifest.xml Added intent filters for PGN files and changed launch mode to singleTask
Comments suppressed due to low confidence (10)

lib/src/view/more/import_pgn_screen.dart:30

  • The shorthand enum syntax .error is inconsistent with the rest of the codebase. The codebase consistently uses the fully-qualified SnackBarType.error format. Please change type: .error to type: SnackBarType.error.
        showSnackBar(context, context.l10n.invalidPgn, type: .error);

lib/src/view/more/import_pgn_screen.dart:43

  • The shorthand enum syntax .white is inconsistent with the rest of the codebase. The codebase consistently uses the fully-qualified Side.white format. Please change orientation: .white to orientation: Side.white.
              orientation: .white,

android/app/src/main/AndroidManifest.xml:54

  • The second intent-filter for PGN files (lines 46-54) lacks MIME type specification and only uses pathSuffix. This is problematic because:
  1. The pathSuffix filter alone may not work reliably across all Android versions and file providers
  2. It could potentially match unintended files with .pgn in their names
  3. It creates duplicate intent handling with the first filter

Consider either removing this filter (if the MIME type filter at lines 36-44 is sufficient) or adding a MIME type to this filter as well for better reliability.

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

                <data android:scheme="content" />

                <data android:pathSuffix=".pgn" />
            </intent-filter>

lib/src/app.dart:200

  • Error handling in _processSharedFiles silently fails with only a debug print. If file reading fails due to permissions, encoding issues, or the file being moved/deleted, the user receives no feedback. Consider showing a user-visible error message using showSnackBar similar to how errors are handled in ImportPgnScreen.handlePgnText. This would provide better user experience and consistency with the existing error handling patterns in the codebase.
    } catch (e) {
      debugPrint('Failed to process incoming file: $e');
    }

pubspec.yaml:74

  • The dependency uses a specific commit SHA (2cea396843cd3ab1b5ec4334be4233864637874e) rather than a branch or tag. While this ensures reproducibility, it's important to verify:
  1. That this commit exists and contains the necessary fix mentioned in the PR description
  2. That this is a reasonable long-term approach, as the package maintainer may force-push or delete commits
  3. Consider documenting in a comment why this specific commit is needed and what fix it includes

This helps future maintainers understand why a git dependency is used instead of a published version.

  receive_sharing_intent:
    git:
      url: https://github.com/KasemJaffer/receive_sharing_intent
      ref: 2cea396843cd3ab1b5ec4334be4233864637874e

android/app/src/main/AndroidManifest.xml:24

  • Changing launchMode from singleTop to singleTask is a significant behavioral change that affects how the app handles multiple instances and task management. With singleTask:
  • Only one instance of the activity exists in the system
  • New intents will be delivered to the existing instance via onNewIntent()
  • The activity becomes the root of its own task

While this is likely necessary for the sharing intent to work properly, be aware that:

  1. It changes how back navigation works - users may not return to the previous app after pressing back
  2. It affects how the app interacts with the recent apps list
  3. It may impact existing deep link handling behavior

Consider testing edge cases like: opening the app from a notification while it's already open, deep links when the app is in the background, and back navigation from various entry points.

            android:launchMode="singleTask"

lib/src/view/more/import_pgn_screen.dart:47

  • The shorthand enum syntax .standard is inconsistent with the rest of the codebase. The codebase consistently uses fully-qualified enum values. Please change .standard to Variant.standard.
              variant: rule != null ? Variant.fromRule(rule) : .standard,

lib/src/view/more/import_pgn_screen.dart:58

  • The shorthand enum syntax .error is inconsistent with the rest of the codebase. The codebase consistently uses the fully-qualified SnackBarType.error format. Please change type: .error to type: SnackBarType.error.
      showSnackBar(context, context.l10n.invalidPgn, type: .error);

lib/src/app.dart:187

  • The code only processes the first file from the shared files list (files.first.path). If a user shares multiple PGN files at once, only the first one will be processed and the rest will be silently ignored. Consider either:
  1. Processing all shared files sequentially
  2. Showing an error message if multiple files are shared
  3. Documenting this limitation

The current behavior could confuse users who attempt to share multiple PGN files at once.

  Future<void> _processSharedFiles(List<SharedMediaFile> files) async {
    if (files.isEmpty) return;
    final filePath = files.first.path;

lib/src/app.dart:183

  • The sharing intent functionality is initialized unconditionally but appears to be Android-only based on the PR description. While the receive_sharing_intent package may handle platform checks internally, it would be more explicit and maintainable to wrap the initialization in a platform check (e.g., if (Platform.isAndroid)) to prevent unnecessary initialization on iOS and other platforms. This also makes it clearer that this feature is currently Android-only until iOS implementation is added.
  void _initSharingIntent() {
    // Warm start
    _intentSub = ReceiveSharingIntent.instance.getMediaStream().listen((
      List<SharedMediaFile> value,
    ) {
      _processSharedFiles(value);
    });

    // Cold start
    ReceiveSharingIntent.instance.getInitialMedia().then((List<SharedMediaFile> value) {
      _processSharedFiles(value);
      ReceiveSharingIntent.instance.reset();
    });
  }

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +185 to +191
Future<void> _processSharedFiles(List<SharedMediaFile> files) async {
if (files.isEmpty) return;
final filePath = files.first.path;
try {
final context = _navigatorKey.currentContext;
if (context == null || !context.mounted) return;

Copy link

Copilot AI Feb 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a potential race condition in the cold start handling. The getInitialMedia() call is made in initState(), but at that point the navigator context might not be ready yet, causing the file processing to silently fail when _navigatorKey.currentContext is null. Consider adding a retry mechanism or deferring the cold start processing until after the first frame is built using WidgetsBinding.instance.addPostFrameCallback(). This would ensure the navigator is ready to handle the navigation.

Suggested change
Future<void> _processSharedFiles(List<SharedMediaFile> files) async {
if (files.isEmpty) return;
final filePath = files.first.path;
try {
final context = _navigatorKey.currentContext;
if (context == null || !context.mounted) return;
Future<void> _processSharedFiles(
List<SharedMediaFile> files, {
int attemptsLeft = 3,
}) async {
if (files.isEmpty) return;
final context = _navigatorKey.currentContext;
if (context == null || !context.mounted) {
if (attemptsLeft <= 0) {
return;
}
WidgetsBinding.instance.addPostFrameCallback((_) {
_processSharedFiles(
files,
attemptsLeft: attemptsLeft - 1,
);
});
return;
}
final filePath = files.first.path;
try {

Copilot uses AI. Check for mistakes.
@r3econ
Copy link
Contributor

r3econ commented Feb 22, 2026

Cool stuff! 😎 Once this gets merged I'll work on the iOS support for this 🍏

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants