diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index a083df8..9794b22 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -3,7 +3,7 @@ name: Build Release
on:
push:
tags:
- - '*'
+ - "*"
workflow_dispatch:
jobs:
@@ -59,6 +59,7 @@ jobs:
sudo apt-get install -y libnotify-dev
sudo apt-get install -y libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev
sudo apt-get install -y gstreamer-1.0
+ sudo apt-get install -y libsecret-1
- run: flutter pub get
- run: flutter build linux
- name: Archive production artifacts
@@ -80,4 +81,4 @@ jobs:
uses: actions/upload-artifact@v4
with:
name: build-output-linux-appimage
- path: './*.AppImage*'
+ path: "./*.AppImage*"
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index a29f020..7ddd838 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -98,7 +98,7 @@
+ android:exported="true" />
+ if (call.method == "initialLink") {
+ val roomId = intent.getStringExtra("room_id")
+ if (roomId != null) {
+ result.success("/rooms/$roomId")
+ } else {
+ result.success(null)
+ }
+ } else {
+ result.notImplemented()
+ }
+ }
+ }
+
+ override fun onNewIntent(intent: Intent) {
+ super.onNewIntent(intent)
+ val roomId = intent.getStringExtra("room_id")
+ if (roomId != null) {
+ MethodChannel(flutterEngine!!.dartExecutor.binaryMessenger, CHANNEL).invokeMethod("newLink", "/rooms/$roomId")
+ }
}
}
diff --git a/android/app/src/main/kotlin/dev/solsynth/solian/network/ApiClient.kt b/android/app/src/main/kotlin/dev/solsynth/solian/network/ApiClient.kt
index aa8af87..ada2897 100644
--- a/android/app/src/main/kotlin/dev/solsynth/solian/network/ApiClient.kt
+++ b/android/app/src/main/kotlin/dev/solsynth/solian/network/ApiClient.kt
@@ -1,48 +1,47 @@
package dev.solsynth.solian.network
import android.content.Context
-import okhttp3.*
+import android.content.SharedPreferences
+import okhttp3.Call
+import okhttp3.Callback
import okhttp3.MediaType.Companion.toMediaType
+import okhttp3.OkHttpClient
+import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
+import okhttp3.Response
import org.json.JSONObject
import java.io.IOException
class ApiClient(private val context: Context) {
private val client = OkHttpClient()
+ private val sharedPreferences: SharedPreferences = context.getSharedPreferences("FlutterSharedPreferences", Context.MODE_PRIVATE)
- fun sendMessage(roomId: String, content: String, repliedMessageId: String, callback: () -> Unit) {
- val prefs = context.getSharedPreferences("FlutterSharedPreferences", Context.MODE_PRIVATE)
- val token = prefs.getString("flutter.token", null)
- val serverUrl = prefs.getString("flutter.serverUrl", null)
-
- if (token == null || serverUrl == null) {
+ fun sendMessage(roomId: String, message: String, replyTo: String, callback: (Boolean) -> Unit) {
+ val token = sharedPreferences.getString("flutter.token", null)
+ if (token == null) {
+ callback(false)
return
}
- val url = "$serverUrl/chat/$roomId/messages"
-
- val json = JSONObject()
- json.put("content", content)
- json.put("replied_message_id", repliedMessageId)
-
- val requestBody = json.toString().toRequestBody("application/json; charset=utf-8".toMediaType())
-
+ val json = JSONObject().apply {
+ put("content", message)
+ put("reply_to", replyTo)
+ }
+ val body = json.toString().toRequestBody("application/json; charset=utf-8".toMediaType())
val request = Request.Builder()
- .url(url)
- .post(requestBody)
- .addHeader("Authorization", "AtField $token")
+ .url("https://solian.dev/api/rooms/$roomId/messages")
+ .header("Authorization", "Bearer $token")
+ .post(body)
.build()
client.newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
- // Handle failure
- callback()
+ callback(false)
}
override fun onResponse(call: Call, response: Response) {
- // Handle success
- callback()
+ callback(response.isSuccessful)
}
})
}
-}
+}
\ No newline at end of file
diff --git a/android/app/src/main/kotlin/dev/solsynth/solian/service/MessagingService.kt b/android/app/src/main/kotlin/dev/solsynth/solian/service/MessagingService.kt
index e4a2d7d..595408d 100644
--- a/android/app/src/main/kotlin/dev/solsynth/solian/service/MessagingService.kt
+++ b/android/app/src/main/kotlin/dev/solsynth/solian/service/MessagingService.kt
@@ -72,6 +72,7 @@ class MessagingService: FirebaseMessagingService() {
val intent = Intent(this, MainActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
+ intent.putExtra("room_id", roomId)
val pendingIntent = PendingIntent.getActivity(this, 0, intent, pendingIntentFlags)
val notificationBuilder = NotificationCompat.Builder(this, "messages")
diff --git a/assets/i18n/en-US.json b/assets/i18n/en-US.json
index cfd0c6d..f17ba28 100644
--- a/assets/i18n/en-US.json
+++ b/assets/i18n/en-US.json
@@ -89,14 +89,32 @@
"authFactorInAppNotifyDescription": "A one-time code sent via in-app notification.",
"authFactorPin": "Pin Code",
"authFactorPinDescription": "It consists of 6 digits. It cannot be used to log in. When performing some dangerous operations, the system will ask you to enter this PIN for confirmation.",
+ "realms": "Realms",
+ "createRealm": "Create a Realm",
+ "createRealmHint": "Meet friends with same interests, build communities, and more.",
+ "editRealm": "Edit Realm",
+ "deleteRealm": "Delete Realm",
+ "deleteRealmHint": "Are you sure to delete this realm? This will also deleted all the channels, publishers, and posts under this realm.",
"explore": "Explore",
"exploreFilterSubscriptions": "Subscriptions",
"exploreFilterFriends": "Friends",
"discover": "Discover",
+ "joinRealm": "Join Realm",
"account": "Account",
"name": "Name",
"slug": "Slug",
"slugHint": "The slug will be used in the URL to access this resource, it should be unique and URL safe.",
+ "createChatRoom": "Create a Room",
+ "editChatRoom": "Edit Room",
+ "deleteChatRoom": "Delete Room",
+ "deleteChatRoomHint": "Are you sure to delete this room? This action cannot be undone.",
+ "chat": "Chat",
+ "chatTabAll": "All",
+ "chatTabDirect": "Direct Messages",
+ "chatTabGroup": "Group Chats",
+ "chatMessageHint": "Message in {}",
+ "chatDirectMessageHint": "Message to {}",
+ "directMessage": "Direct Message",
"loading": "Loading...",
"descriptionNone": "No description yet.",
"invites": "Invites",
@@ -231,6 +249,7 @@
"uploadingProgress": "Uploading {} of {}",
"uploadAll": "Upload All",
"stickerCopyPlaceholder": "Copy Placeholder",
+ "realmSelection": "Select a Realm",
"individual": "Individual",
"firstPostBadgeName": "First Post",
"firstPostBadgeDescription": "Created your first post on Solar Network",
@@ -286,6 +305,10 @@
"levelingProgressExperience": "{} EXP",
"levelingProgressLevel": "Level {}",
"fileUploadingProgress": "Uploading file #{}: {}%",
+ "removeChatMember": "Remove Chat Room Member",
+ "removeChatMemberHint": "Are you sure to remove this member from the room?",
+ "removeRealmMember": "Remove Realm Member",
+ "removeRealmMemberHint": "Are you sure to remove this member from the realm?",
"memberRole": "Member Role",
"memberRoleHint": "Greater number has higher permission.",
"memberRoleEdit": "Edit role for @{}",
@@ -293,6 +316,10 @@
"openLinkConfirmDescription": "You're going to leave the Solar Network and open the link ({}) in your browser. It is not related to Solar Network. Beware of phishing and scams.",
"brokenLink": "Unable open link {}... It might be broken or missing uri parts...",
"copyToClipboard": "Copy to clipboard",
+ "leaveChatRoom": "Leave Chat Room",
+ "leaveChatRoomHint": "Are you sure to leave this chat room?",
+ "leaveRealm": "Leave Realm",
+ "leaveRealmHint": "Are you sure to leave this realm?",
"walletNotFound": "Wallet not found",
"walletCreateHint": "You don't have a wallet yet. Create one to start using the Solar Network eWallet.",
"walletCreate": "Create a Wallet",
@@ -304,6 +331,12 @@
"settingsBackgroundImageClear": "Clear Background Image",
"settingsBackgroundGenerateColor": "Generate color scheme from Bacground Image",
"messageNone": "No content to display",
+ "unreadMessages": {
+ "one": "{} unread message",
+ "other": "{} unread messages"
+ },
+ "chatBreakNone": "None",
+ "settingsRealmCompactView": "Compact Realm View",
"settingsMixedFeed": "Mixed Feed",
"settingsAutoTranslate": "Auto Translate",
"settingsHideBottomNav": "Hide Bottom Navigation",
@@ -346,6 +379,7 @@
"postVisibilityUnlisted": "Unlisted",
"postVisibilityPrivate": "Private",
"postTruncated": "Content truncated, tap to view full post",
+ "copyMessage": "Copy Message",
"authFactor": "Authentication Factor",
"authFactorDelete": "Delete the Factor",
"authFactorDeleteHint": "Are you sure you want to delete this authentication factor? This action cannot be undone.",
@@ -373,6 +407,10 @@
"lastActiveAt": "Last active at {}",
"authDeviceLogout": "Logout",
"authDeviceLogoutHint": "Are you sure you want to logout this device? This will also disable the push notification to this device.",
+ "typingHint": {
+ "one": "{} is typing...",
+ "other": "{} are typing..."
+ },
"authDeviceEditLabel": "Edit Label",
"authDeviceLabelTitle": "Edit Device Label",
"authDeviceLabelHint": "Enter a name for this device",
@@ -439,6 +477,21 @@
"contactMethodSetPrimary": "Set as Primary",
"contactMethodSetPrimaryHint": "Set this contact method as your primary contact method for account recovery and notifications",
"contactMethodDeleteHint": "Are you sure to delete this contact method? This action cannot be undone.",
+ "chatNotifyLevel": "Notify Level",
+ "chatNotifyLevelDescription": "Decide how many notifications you will receive.",
+ "chatNotifyLevelAll": "All",
+ "chatNotifyLevelMention": "Mentions",
+ "chatNotifyLevelNone": "None",
+ "chatNotifyLevelUpdated": "The notify level has been updated to {}.",
+ "chatBreak": "Take a Break",
+ "chatBreakDescription": "Set a time, before that time, your notification level will be metions only, to take a break of the current topic they're talking about.",
+ "chatBreakClear": "Clear the break time",
+ "chatBreakHour": "{} break",
+ "chatBreakDay": "{} day break",
+ "chatBreakSet": "Break set for {}",
+ "chatBreakCleared": "Chat break has been cleared.",
+ "chatBreakCustom": "Custom duration",
+ "chatBreakEnterMinutes": "Enter minutes",
"firstName": "First Name",
"middleName": "Middle Name",
"lastName": "Last Name",
@@ -520,17 +573,29 @@
"quickActions": "Quick Actions",
"post": "Post",
"copy": "Copy",
+ "sendToChat": "Send to Chat",
"failedToShareToPost": "Failed to share to post: {}",
"shareToChatComingSoon": "Share to chat functionality coming soon",
+ "failedToShareToChat": "Failed to share to chat: {}",
+ "shareToSpecificChatComingSoon": "Share to {} coming soon",
+ "directChat": "Direct Chat",
"systemShareComingSoon": "System share functionality coming soon",
"failedToShareToSystem": "Failed to share to system: {}",
"failedToCopy": "Failed to copy: {}",
+ "noChatRoomsAvailable": "No chat rooms available",
+ "failedToLoadChats": "Failed to load chats",
"contentToShare": "Content to share:",
+ "unknownChat": "Unknown Chat",
+ "addAdditionalMessage": "Add additional message...",
"uploadingFiles": "Uploading files...",
+ "sharedSuccessfully": "Shared successfully!",
"shareSuccess": "Shared successfully!",
+ "shareToSpecificChatSuccess": "Shared to {} successfully!",
"wouldYouLikeToGoToChat": "Would you like to go to the chat?",
"no": "No",
"yes": "Yes",
+ "navigateToChat": "Navigate to Chat",
+ "wouldYouLikeToNavigateToChat": "Would you like to navigate to the chat?",
"abuseReport": "Report",
"abuseReportTitle": "Report Content",
"abuseReportDescription": "Help us keep the community safe by reporting inappropriate content or behavior.",
diff --git a/lib/main.dart b/lib/main.dart
index 3a76cfb..3ccba49 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -7,6 +7,7 @@ import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:image_picker_android/image_picker_android.dart';
@@ -158,6 +159,28 @@ class IslandApp extends HookConsumerWidget {
}
useEffect(() {
+ const channel = MethodChannel('dev.solsynth.solian/notifications');
+
+ Future handleInitialLink() async {
+ final String? link = await channel.invokeMethod('initialLink');
+ if (link != null) {
+ final router = ref.read(routerProvider);
+ router.go(link);
+ }
+ }
+
+ if (!kIsWeb && (Platform.isAndroid || Platform.isIOS)) {
+ handleInitialLink();
+ }
+
+ channel.setMethodCallHandler((call) async {
+ if (call.method == 'newLink') {
+ final String link = call.arguments;
+ final router = ref.read(routerProvider);
+ router.go(link);
+ }
+ });
+
// When the app is opened from a terminated state.
FirebaseMessaging.instance.getInitialMessage().then((message) {
if (message != null) {
diff --git a/lib/route.dart b/lib/route.dart
index 3144e47..c3b0234 100644
--- a/lib/route.dart
+++ b/lib/route.dart
@@ -79,33 +79,37 @@ final routerProvider = Provider((ref) {
return EventCalanderScreen(name: name);
},
),
- GoRoute(
- path: '/creators',
- builder: (context, state) => const CreatorHubScreen(),
+ ShellRoute(
+ builder:
+ (context, state, child) => CreatorHubShellScreen(child: child),
routes: [
GoRoute(
- path: ':name/posts',
+ path: '/creators',
+ builder: (context, state) => const CreatorHubScreen(),
+ ),
+ GoRoute(
+ path: '/creators/:name/posts',
builder: (context, state) {
final name = state.pathParameters['name']!;
return CreatorPostListScreen(pubName: name);
},
),
GoRoute(
- path: ':name/stickers',
+ path: '/creators/:name/stickers',
builder: (context, state) {
final name = state.pathParameters['name']!;
return StickersScreen(pubName: name);
},
),
GoRoute(
- path: ':name/stickers/new',
+ path: '/creators/:name/stickers/new',
builder: (context, state) {
final name = state.pathParameters['name']!;
return NewStickerPacksScreen(pubName: name);
},
),
GoRoute(
- path: ':name/stickers/:packId/edit',
+ path: '/creators/:name/stickers/:packId/edit',
builder: (context, state) {
final name = state.pathParameters['name']!;
final packId = state.pathParameters['packId']!;
@@ -113,7 +117,7 @@ final routerProvider = Provider((ref) {
},
),
GoRoute(
- path: ':name/stickers/:packId',
+ path: '/creators/:name/stickers/:packId',
builder: (context, state) {
final name = state.pathParameters['name']!;
final packId = state.pathParameters['packId']!;
@@ -121,14 +125,14 @@ final routerProvider = Provider((ref) {
},
),
GoRoute(
- path: ':name/stickers/:packId/new',
+ path: '/creators/:name/stickers/:packId/new',
builder: (context, state) {
final packId = state.pathParameters['packId']!;
return NewStickersScreen(packId: packId);
},
),
GoRoute(
- path: ':name/stickers/:packId/:id/edit',
+ path: '/creators/:name/stickers/:packId/:id/edit',
builder: (context, state) {
final packId = state.pathParameters['packId']!;
final id = state.pathParameters['id']!;
@@ -136,11 +140,11 @@ final routerProvider = Provider((ref) {
},
),
GoRoute(
- path: 'new',
+ path: '/creators/new',
builder: (context, state) => const NewPublisherScreen(),
),
GoRoute(
- path: ':name/edit',
+ path: '/creators/:name/edit',
builder: (context, state) {
final name = state.pathParameters['name']!;
return EditPublisherScreen(name: name);
@@ -173,56 +177,64 @@ final routerProvider = Provider((ref) {
},
routes: [
// Explore tab
- GoRoute(
- path: '/',
- builder: (context, state) => const ExploreScreen(),
+ ShellRoute(
+ builder:
+ (context, state, child) => ExploreShellScreen(child: child),
routes: [
GoRoute(
- path: 'posts/:id',
+ path: '/',
+ builder: (context, state) => const ExploreScreen(),
+ ),
+ GoRoute(
+ path: '/posts/:id',
builder: (context, state) {
final id = state.pathParameters['id']!;
return PostDetailScreen(id: id);
},
),
GoRoute(
- path: 'publishers/:name',
+ path: '/publishers/:name',
builder: (context, state) {
final name = state.pathParameters['name']!;
return PublisherProfileScreen(name: name);
},
),
GoRoute(
- path: 'discovery/realms',
+ path: '/discovery/realms',
builder: (context, state) => const DiscoveryRealmsScreen(),
),
],
),
// Chat tab
- GoRoute(
- path: '/chat',
- builder: (context, state) => const ChatListScreen(),
+ ShellRoute(
+ builder:
+ (context, state, child) => ChatShellScreen(child: child),
routes: [
GoRoute(
- path: 'new',
+ path: '/chat',
+ builder: (context, state) => const ChatListScreen(),
+ ),
+ GoRoute(
+ path: '/chat/new',
builder: (context, state) => const NewChatScreen(),
),
GoRoute(
- path: ':id',
+ path: '/chat/:id',
builder: (context, state) {
final id = state.pathParameters['id']!;
return ChatRoomScreen(id: id);
},
),
GoRoute(
- path: ':id/edit',
+ path: '/chat/:id/edit',
builder: (context, state) {
final id = state.pathParameters['id']!;
return EditChatScreen(id: id);
},
),
GoRoute(
- path: ':id/detail',
+ path: '/chat/:id/detail',
builder: (context, state) {
final id = state.pathParameters['id']!;
return ChatDetailScreen(id: id);
@@ -258,39 +270,43 @@ final routerProvider = Provider((ref) {
),
// Account tab
- GoRoute(
- path: '/account',
- builder: (context, state) => const AccountScreen(),
+ ShellRoute(
+ builder:
+ (context, state, child) => AccountShellScreen(child: child),
routes: [
GoRoute(
- path: 'notifications',
+ path: '/account',
+ builder: (context, state) => const AccountScreen(),
+ ),
+ GoRoute(
+ path: '/account/notifications',
builder: (context, state) => const NotificationScreen(),
),
GoRoute(
- path: 'wallet',
+ path: '/account/wallet',
builder: (context, state) => const WalletScreen(),
),
GoRoute(
- path: 'relationships',
+ path: '/account/relationships',
builder: (context, state) => const RelationshipScreen(),
),
GoRoute(
- path: ':name',
+ path: '/account/:name',
builder: (context, state) {
final name = state.pathParameters['name']!;
return AccountProfileScreen(name: name);
},
),
GoRoute(
- path: 'me/update',
+ path: '/account/me/update',
builder: (context, state) => const UpdateProfileScreen(),
),
GoRoute(
- path: 'me/leveling',
+ path: '/account/me/leveling',
builder: (context, state) => const LevelingScreen(),
),
GoRoute(
- path: 'settings',
+ path: '/account/settings',
builder: (context, state) => const AccountSettingsScreen(),
),
],
diff --git a/lib/screens/account.dart b/lib/screens/account.dart
index 16a7d25..2704d00 100644
--- a/lib/screens/account.dart
+++ b/lib/screens/account.dart
@@ -143,7 +143,7 @@ class AccountScreen extends HookConsumerWidget {
progress: user.value!.profile.levelingProgress,
),
onTap: () {
- context.push('/account/leveling');
+ context.push('/account/me/leveling');
},
).padding(horizontal: 12),
Row(
@@ -210,7 +210,7 @@ class AccountScreen extends HookConsumerWidget {
contentPadding: EdgeInsets.symmetric(horizontal: 24),
title: Text('wallet').tr(),
onTap: () {
- context.push('/wallet');
+ context.push('/account/wallet');
},
),
ListTile(
diff --git a/lib/screens/account/profile.dart b/lib/screens/account/profile.dart
index 6e71165..956ec7d 100644
--- a/lib/screens/account/profile.dart
+++ b/lib/screens/account/profile.dart
@@ -53,17 +53,21 @@ Future> accountBadges(Ref ref, String uname) async {
@riverpod
Future accountAppbarForcegroundColor(Ref ref, String uname) async {
- final account = await ref.watch(accountProvider(uname).future);
- if (account.profile.background == null) return null;
- final palette = await PaletteGenerator.fromImageProvider(
- CloudImageWidget.provider(
- fileId: account.profile.background!.id,
- serverUrl: ref.watch(serverUrlProvider),
- ),
- );
- final dominantColor = palette.dominantColor?.color;
- if (dominantColor == null) return null;
- return dominantColor.computeLuminance() > 0.5 ? Colors.black : Colors.white;
+ try {
+ final account = await ref.watch(accountProvider(uname).future);
+ if (account.profile.background == null) return null;
+ final palette = await PaletteGenerator.fromImageProvider(
+ CloudImageWidget.provider(
+ fileId: account.profile.background!.id,
+ serverUrl: ref.watch(serverUrlProvider),
+ ),
+ );
+ final dominantColor = palette.dominantColor?.color;
+ if (dominantColor == null) return null;
+ return dominantColor.computeLuminance() > 0.5 ? Colors.black : Colors.white;
+ } catch (_) {
+ return null;
+ }
}
@riverpod
diff --git a/lib/screens/notification.dart b/lib/screens/notification.dart
index e7a6828..6210c23 100644
--- a/lib/screens/notification.dart
+++ b/lib/screens/notification.dart
@@ -4,19 +4,18 @@ import 'dart:math' as math;
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
-import 'package:flutter/services.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:island/models/user.dart';
import 'package:island/pods/network.dart';
import 'package:island/pods/websocket.dart';
-import 'package:island/widgets/alert.dart';
+import 'package:island/route.dart';
import 'package:island/widgets/app_scaffold.dart';
import 'package:island/widgets/content/markdown.dart';
import 'package:relative_time/relative_time.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:riverpod_paging_utils/riverpod_paging_utils.dart';
import 'package:styled_widget/styled_widget.dart';
-import 'package:url_launcher/url_launcher.dart';
+import 'package:url_launcher/url_launcher_string.dart';
part 'notification.g.dart';
@@ -180,36 +179,17 @@ class NotificationScreen extends HookConsumerWidget {
),
),
onTap: () {
- if (notification.meta['link'] is String) {
- final href = notification.meta['link'];
- final uri = Uri.tryParse(href);
- if (uri == null) {
- showSnackBar(
- 'brokenLink'.tr(args: []),
- action: SnackBarAction(
- label: 'copyToClipboard'.tr(),
- onPressed: () {
- Clipboard.setData(ClipboardData(text: href));
- clearSnackBar(context);
- },
- ),
+ if (notification.meta['action_uri'] != null) {
+ var uri = notification.meta['action_uri'] as String;
+ if (uri.startsWith('/')) {
+ // In-app routes
+ rootNavigatorKey.currentContext?.push(
+ notification.meta['action_uri'],
);
- return;
+ } else {
+ // External URLs
+ launchUrlString(uri);
}
- if (uri.scheme == 'solian') {
- context.push(
- ['', uri.host, ...uri.pathSegments].join('/'),
- );
- return;
- }
- showConfirmAlert(
- 'openLinkConfirmDescription'.tr(args: [href]),
- 'openLinkConfirm'.tr(),
- ).then((value) {
- if (value) {
- launchUrl(uri, mode: LaunchMode.externalApplication);
- }
- });
}
},
);