diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 2e8824fe..76bdee2c 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -24,6 +24,1111 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb
index 55633f45..19feb1cd 100644
--- a/lib/l10n/app_en.arb
+++ b/lib/l10n/app_en.arb
@@ -387,6 +387,7 @@
"settings_debug_clearFeedCache": "Clear feed cache",
"settings_debug_clearTags": "Clear user tags",
"settings_debug_clearOauth": "Clear Oauth identifiers",
+ "settings_debug_clearInstancesCache": "Clear federated instances cache",
"settings_debug_log": "Log",
"profiles": "Profiles",
"profile_switch": "Switch profiles",
diff --git a/lib/src/api/search.dart b/lib/src/api/search.dart
index 39d8eda6..2ed6651f 100644
--- a/lib/src/api/search.dart
+++ b/lib/src/api/search.dart
@@ -1,7 +1,14 @@
+import 'package:extended_image/extended_image.dart';
import 'package:interstellar/src/api/client.dart';
import 'package:interstellar/src/controller/server.dart';
+import 'package:interstellar/src/models/comment.dart';
+import 'package:interstellar/src/models/community.dart';
+import 'package:interstellar/src/models/feed.dart';
+import 'package:interstellar/src/models/post.dart';
import 'package:interstellar/src/models/search.dart';
+import 'package:interstellar/src/models/user.dart';
import 'package:interstellar/src/screens/explore/explore_screen.dart';
+import 'package:interstellar/src/utils/utils.dart';
class APISearch {
APISearch(this.client);
@@ -17,7 +24,7 @@ class APISearch {
}) async {
switch (client.software) {
case ServerSoftware.mbin:
- const path = '/search';
+ const path = '/search/v2';
final response = await client.get(
path,
@@ -98,4 +105,79 @@ class APISearch {
);
}
}
+
+ Future resolveObject(String search) async {
+ try {
+ switch (client.software) {
+ case ServerSoftware.mbin:
+ const path = '/search/v2';
+
+ final response = await client.get(
+ path,
+ queryParams: {'q': search, 'onlyAP': 'true'},
+ );
+
+ // get first of the ap objects found.
+ return SearchListModel.fromMbin(response.bodyJson).items.firstOrNull;
+
+ case ServerSoftware.lemmy:
+ const path = '/resolve_object';
+
+ final response = await client.get(path, queryParams: {'q': search});
+
+ final json = response.bodyJson;
+ if (json['comment'] != null) {
+ return CommentModel.fromLemmy(
+ json['comment']! as JsonMap,
+ langCodeIdPairs: await client.languageCodeIdPairs(),
+ );
+ } else if (json['post'] != null) {
+ return PostModel.fromLemmy(
+ json['post']! as JsonMap,
+ langCodeIdPairs: await client.languageCodeIdPairs(),
+ );
+ } else if (json['community'] != null) {
+ return DetailedCommunityModel.fromLemmy(
+ json['community']! as JsonMap,
+ );
+ } else if (json['person'] != null) {
+ return DetailedUserModel.fromLemmy(json['person']! as JsonMap);
+ }
+ return null;
+
+ case ServerSoftware.piefed:
+ const path = '/resolve_object';
+
+ final response = await client.get(path, queryParams: {'q': search});
+
+ final json = response.bodyJson;
+ if (json['comment'] != null) {
+ return CommentModel.fromPiefed(
+ json['comment']! as JsonMap,
+ langCodeIdPairs: await client.languageCodeIdPairs(),
+ );
+ } else if (json['post'] != null) {
+ return PostModel.fromPiefed(
+ json['post']! as JsonMap,
+ langCodeIdPairs: await client.languageCodeIdPairs(),
+ );
+ } else if (json['community'] != null) {
+ return DetailedCommunityModel.fromPiefed(
+ json['community']! as JsonMap,
+ );
+ } else if (json['person'] != null) {
+ return DetailedUserModel.fromPiefed(json['person']! as JsonMap);
+ } else if (json['feed'] != null) {
+ return FeedModel.fromPiefed(json['feed']! as JsonMap);
+ }
+ return null;
+ }
+ } on ClientException catch (e) {
+ if (e.message.contains('No object found') ||
+ e.message.contains('couldnt_find_object')) {
+ return null;
+ }
+ rethrow;
+ }
+ }
}
diff --git a/lib/src/app.dart b/lib/src/app.dart
index 6188c714..40ed97fc 100644
--- a/lib/src/app.dart
+++ b/lib/src/app.dart
@@ -1,12 +1,18 @@
import 'package:dynamic_color/dynamic_color.dart';
import 'package:flex_color_scheme/flex_color_scheme.dart';
+import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_localized_locales/flutter_localized_locales.dart';
import 'package:interstellar/l10n/app_localizations.dart';
import 'package:interstellar/src/controller/controller.dart';
import 'package:interstellar/src/controller/router.dart';
+import 'package:interstellar/src/models/comment.dart';
+import 'package:interstellar/src/models/community.dart';
+import 'package:interstellar/src/models/post.dart';
+import 'package:interstellar/src/models/user.dart';
import 'package:interstellar/src/screens/account/notification/notification_count_controller.dart';
import 'package:interstellar/src/utils/globals.dart';
+import 'package:interstellar/src/utils/instances.dart';
import 'package:interstellar/src/utils/utils.dart';
import 'package:intl/locale.dart' as intl_locale;
import 'package:provider/provider.dart';
@@ -45,7 +51,46 @@ class App extends StatelessWidget {
textScaler: TextScaler.linear(ac.profile.globalTextScale),
),
child: MaterialApp.router(
- routerConfig: router.config(),
+ routerConfig: router.config(
+ deepLinkTransformer: (link) async {
+ final knownInstance = knownInstances[link.host];
+ if (knownInstance == null) return SynchronousFuture(link);
+
+ // Ensure item is federated to current instance.
+ final item = await ac.api.search.resolveObject(
+ link.toString(),
+ );
+
+ if (item == null) {
+ scaffoldMessengerKey.currentState?.showSnackBar(
+ const SnackBar(
+ content: Text('Failed to open link.'),
+ showCloseIcon: true,
+ ),
+ );
+ }
+
+ // Construct interstellar compatible uri.
+ final newUri = switch (item) {
+ final PostModel p => Uri.parse(
+ '/${ac.instanceHost}/c/${p.community.name}/${p.type == PostType.thread ? 'thread' : 'microblog'}/${p.id}',
+ ),
+ final CommentModel c => Uri.parse(
+ '/${ac.instanceHost}/comment/${c.id}',
+ ),
+ final DetailedUserModel u => Uri.parse(
+ '/${ac.instanceHost}/u/${u.name}',
+ ),
+ final DetailedCommunityModel c => Uri.parse(
+ '/${ac.instanceHost}/c/${c.name}',
+ ),
+ Object() => link,
+ null => link,
+ };
+
+ return SynchronousFuture(newUri);
+ },
+ ),
restorationScopeId: 'app',
localizationsDelegates: const [
...AppLocalizations.localizationsDelegates,
diff --git a/lib/src/controller/controller.dart b/lib/src/controller/controller.dart
index 8defa1e1..5a143334 100644
--- a/lib/src/controller/controller.dart
+++ b/lib/src/controller/controller.dart
@@ -287,6 +287,7 @@ class AppController with ChangeNotifier {
_defaultDownloadsDir = await getDefaultDownloadDir();
await _updateAPI();
+
logger.i('Finished init');
}
@@ -595,7 +596,7 @@ class AppController with ChangeNotifier {
await switchAccounts(key, force: forceSwitch);
} else {
// The following is already done when switchAccounts is run, so only needed without switchAccounts.
- _updateAPI();
+ await _updateAPI();
notifyListeners();
}
diff --git a/lib/src/models/post.dart b/lib/src/models/post.dart
index 3048884d..1f61c7e5 100644
--- a/lib/src/models/post.dart
+++ b/lib/src/models/post.dart
@@ -210,7 +210,7 @@ abstract class PostModel with _$PostModel {
JsonMap json, {
required List<(String, int)> langCodeIdPairs,
}) {
- final postView = json['post_view']! as JsonMap;
+ final postView = json['post_view'] as JsonMap? ?? json;
final lemmyPost = postView['post']! as JsonMap;
final lemmyCounts = postView['counts'] as JsonMap?;
diff --git a/lib/src/models/search.dart b/lib/src/models/search.dart
index d5a57947..a53f483e 100644
--- a/lib/src/models/search.dart
+++ b/lib/src/models/search.dart
@@ -18,32 +18,55 @@ abstract class SearchListModel with _$SearchListModel {
factory SearchListModel.fromMbin(Map json) {
final items =