diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index d48e3f5..b265cfe 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -8,7 +8,7 @@
+
+
+
+
+
+
+
diff --git a/lib/pages/add_app.dart b/lib/pages/add_app.dart
index 0ff0cda..f514a32 100644
--- a/lib/pages/add_app.dart
+++ b/lib/pages/add_app.dart
@@ -1,3 +1,5 @@
+import 'dart:math';
+
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
@@ -21,10 +23,10 @@ class AddAppPage extends StatefulWidget {
const AddAppPage({super.key});
@override
- State createState() => _AddAppPageState();
+ State createState() => AddAppPageState();
}
-class _AddAppPageState extends State {
+class AddAppPageState extends State {
bool gettingAppInfo = false;
bool searching = false;
@@ -36,9 +38,57 @@ class _AddAppPageState extends State {
bool additionalSettingsValid = true;
bool inferAppIdIfOptional = true;
List pickedCategories = [];
- int searchnum = 0;
SourceProvider sourceProvider = SourceProvider();
+ linkFn(String input) {
+ try {
+ if (input.isEmpty) {
+ throw UnsupportedURLError();
+ }
+ sourceProvider.getSource(input);
+ changeUserInput(input, true, false);
+ } catch (e) {
+ showError(e, context);
+ }
+ }
+
+ changeUserInput(String input, bool valid, bool isBuilding) {
+ userInput = input;
+ if (!isBuilding) {
+ setState(() {
+ var prevHost = pickedSource?.host;
+ try {
+ var naturalSource =
+ valid ? sourceProvider.getSource(userInput) : null;
+ if (naturalSource != null &&
+ naturalSource.runtimeType.toString() !=
+ HTML().runtimeType.toString()) {
+ // If input has changed to match a regular source, reset the override
+ pickedSourceOverride = null;
+ }
+ } catch (e) {
+ // ignore
+ }
+ var source = valid
+ ? sourceProvider.getSource(userInput,
+ overrideSource: pickedSourceOverride)
+ : null;
+ if (pickedSource.runtimeType != source.runtimeType ||
+ (prevHost != null && prevHost != source?.host)) {
+ pickedSource = source;
+ additionalSettings = source != null
+ ? getDefaultValuesFromFormItems(
+ source.combinedAppSpecificSettingFormItems)
+ : {};
+ additionalSettingsValid = source != null
+ ? !sourceProvider.ifRequiredAppSpecificSettingsExist(source)
+ : true;
+ inferAppIdIfOptional = true;
+ }
+ });
+ }
+ }
+
@override
Widget build(BuildContext context) {
AppsProvider appsProvider = context.read();
@@ -48,47 +98,6 @@ class _AddAppPageState extends State {
bool doingSomething = gettingAppInfo || searching;
- changeUserInput(String input, bool valid, bool isBuilding,
- {bool isSearch = false}) {
- userInput = input;
- if (!isBuilding) {
- setState(() {
- if (isSearch) {
- searchnum++;
- }
- var prevHost = pickedSource?.host;
- try {
- var naturalSource =
- valid ? sourceProvider.getSource(userInput) : null;
- if (naturalSource != null &&
- naturalSource.runtimeType.toString() !=
- HTML().runtimeType.toString()) {
- // If input has changed to match a regular source, reset the override
- pickedSourceOverride = null;
- }
- } catch (e) {
- // ignore
- }
- var source = valid
- ? sourceProvider.getSource(userInput,
- overrideSource: pickedSourceOverride)
- : null;
- if (pickedSource.runtimeType != source.runtimeType ||
- (prevHost != null && prevHost != source?.host)) {
- pickedSource = source;
- additionalSettings = source != null
- ? getDefaultValuesFromFormItems(
- source.combinedAppSpecificSettingFormItems)
- : {};
- additionalSettingsValid = source != null
- ? !sourceProvider.ifRequiredAppSpecificSettingsExist(source)
- : true;
- inferAppIdIfOptional = true;
- }
- });
- }
- }
-
Future getTrackOnlyConfirmationIfNeeded(bool userPickedTrackOnly,
{bool ignoreHideSetting = false}) async {
var useTrackOnly = userPickedTrackOnly || pickedSource!.enforceTrackOnly;
@@ -205,7 +214,7 @@ class _AddAppPageState extends State {
children: [
Expanded(
child: GeneratedForm(
- key: Key(searchnum.toString()),
+ key: Key(Random().nextInt(10000).toString()),
items: [
[
GeneratedFormTextField('appSourceURL',
@@ -325,7 +334,7 @@ class _AddAppPageState extends State {
);
});
if (selectedUrls != null && selectedUrls.isNotEmpty) {
- changeUserInput(selectedUrls[0], true, false, isSearch: true);
+ changeUserInput(selectedUrls[0], true, false);
}
}
} catch (e) {
diff --git a/lib/pages/home.dart b/lib/pages/home.dart
index c14e83f..c2dd91d 100644
--- a/lib/pages/home.dart
+++ b/lib/pages/home.dart
@@ -1,4 +1,7 @@
+import 'dart:async';
+
import 'package:animations/animations.dart';
+import 'package:app_links/app_links.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
@@ -30,53 +33,95 @@ class _HomePageState extends State {
bool isReversing = false;
int prevAppCount = -1;
bool prevIsLoading = true;
+ late AppLinks _appLinks;
+ StreamSubscription? _linkSubscription;
List pages = [
NavigationPageItem(tr('appsString'), Icons.apps,
AppsPage(key: GlobalKey())),
- NavigationPageItem(tr('addApp'), Icons.add, const AddAppPage()),
+ NavigationPageItem(
+ tr('addApp'), Icons.add, AddAppPage(key: GlobalKey())),
NavigationPageItem(
tr('importExport'), Icons.import_export, const ImportExportPage()),
NavigationPageItem(tr('settings'), Icons.settings, const SettingsPage())
];
+ @override
+ void initState() {
+ super.initState();
+ initDeepLinks();
+ }
+
+ Future initDeepLinks() async {
+ _appLinks = AppLinks();
+
+ goToAddApp(Uri uri) async {
+ switchToPage(1);
+ while (
+ (pages[1].widget.key as GlobalKey?)?.currentState ==
+ null) {
+ await Future.delayed(const Duration(microseconds: 1));
+ }
+ (pages[1].widget.key as GlobalKey?)
+ ?.currentState
+ ?.linkFn(uri.path.length > 1 ? uri.path.substring(1) : "");
+ }
+
+ // Check initial link if app was in cold state (terminated)
+ final appLink = await _appLinks.getInitialAppLink();
+ if (appLink != null) {
+ await goToAddApp(appLink);
+ }
+
+ // Handle link when app is in warm state (front or background)
+ _linkSubscription = _appLinks.uriLinkStream.listen((uri) async {
+ await goToAddApp(uri);
+ });
+ }
+
+ setIsReversing(int targetIndex) {
+ bool reversing = selectedIndexHistory.isNotEmpty &&
+ selectedIndexHistory.last > targetIndex;
+ setState(() {
+ isReversing = reversing;
+ });
+ }
+
+ switchToPage(int index) async {
+ setIsReversing(index);
+ if (index == 0) {
+ while ((pages[0].widget.key as GlobalKey).currentState !=
+ null) {
+ // Avoid duplicate GlobalKey error
+ await Future.delayed(const Duration(microseconds: 1));
+ }
+ setState(() {
+ selectedIndexHistory.clear();
+ });
+ } else if (selectedIndexHistory.isEmpty ||
+ (selectedIndexHistory.isNotEmpty &&
+ selectedIndexHistory.last != index)) {
+ // while (index == 1 &&
+ // (pages[0].widget.key as GlobalKey).currentState !=
+ // null) {
+ // // Avoid duplicate GlobalKey error
+ // await Future.delayed(const Duration(microseconds: 1));
+ // }
+ setState(() {
+ int existingInd = selectedIndexHistory.indexOf(index);
+ if (existingInd >= 0) {
+ selectedIndexHistory.removeAt(existingInd);
+ }
+ selectedIndexHistory.add(index);
+ });
+ }
+ }
+
@override
Widget build(BuildContext context) {
AppsProvider appsProvider = context.watch();
SettingsProvider settingsProvider = context.watch();
- setIsReversing(int targetIndex) {
- bool reversing = selectedIndexHistory.isNotEmpty &&
- selectedIndexHistory.last > targetIndex;
- setState(() {
- isReversing = reversing;
- });
- }
-
- switchToPage(int index) async {
- setIsReversing(index);
- if (index == 0) {
- while ((pages[0].widget.key as GlobalKey).currentState !=
- null) {
- // Avoid duplicate GlobalKey error
- await Future.delayed(const Duration(microseconds: 1));
- }
- setState(() {
- selectedIndexHistory.clear();
- });
- } else if (selectedIndexHistory.isEmpty ||
- (selectedIndexHistory.isNotEmpty &&
- selectedIndexHistory.last != index)) {
- setState(() {
- int existingInd = selectedIndexHistory.indexOf(index);
- if (existingInd >= 0) {
- selectedIndexHistory.removeAt(existingInd);
- }
- selectedIndexHistory.add(index);
- });
- }
- }
-
if (!prevIsLoading &&
prevAppCount >= 0 &&
appsProvider.apps.length > prevAppCount &&
@@ -143,4 +188,10 @@ class _HomePageState extends State {
?.clearSelected();
});
}
+
+ @override
+ void dispose() {
+ super.dispose();
+ _linkSubscription?.cancel();
+ }
}
diff --git a/pubspec.lock b/pubspec.lock
index 88905ae..cf74778 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -42,6 +42,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.0.10"
+ app_links:
+ dependency: "direct main"
+ description:
+ name: app_links
+ sha256: "4e392b5eba997df356ca6021f28431ce1cfeb16758699553a94b13add874a3bb"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.5.0"
archive:
dependency: transitive
description:
@@ -350,6 +358,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "8.2.4"
+ gtk:
+ dependency: transitive
+ description:
+ name: gtk
+ sha256: e8ce9ca4b1df106e4d72dad201d345ea1a036cc12c360f1a7d5a758f78ffa42c
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.1.0"
hsluv:
dependency: "direct main"
description:
diff --git a/pubspec.yaml b/pubspec.yaml
index 0b9b272..8a9ffeb 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -67,6 +67,7 @@ dependencies:
connectivity_plus: ^5.0.0
shared_storage: ^0.8.0
crypto: ^3.0.3
+ app_links: ^3.5.0
dev_dependencies:
flutter_test: