Open links on system browser

This commit is contained in:
Juan Gilsanz Polo
2024-06-11 15:14:21 +02:00
parent 3ee194c50a
commit edf49edfa8
19 changed files with 243 additions and 53 deletions

View File

@@ -13,3 +13,14 @@ enum ReadStatus { all, unread, read }
enum SortingType { date, title }
enum SortingWay { descendant, ascendant }
enum OpenLinksBrowser { integrated, browserCustomTab, systemBrowser }
extension OpenLinksBrowserExtension on OpenLinksBrowser {
static OpenLinksBrowser? fromString(String? value) {
if (value == null || !OpenLinksBrowser.values.map((v) => v.toString()).contains(value)) {
return null;
}
return OpenLinksBrowser.values.firstWhere((e) => e.toString() == value);
}
}

View File

@@ -5,4 +5,5 @@ class SharedPreferencesKeys {
static const selectedColor = "selectedColor";
static const useInAppBrowser = "useInAppBrowser";
static const showFavicon = "showFavicon";
static const openLinksBrowser = "openLinksBrowser";
}

View File

@@ -4,9 +4,9 @@
/// To regenerate, run: `dart run slang`
///
/// Locales: 3
/// Strings: 570 (190 per locale)
/// Strings: 582 (194 per locale)
///
/// Built on 2024-04-14 at 18:40 UTC
/// Built on 2024-06-11 at 12:16 UTC
// coverage:ignore-file
// ignore_for_file: type=lint
@@ -498,6 +498,10 @@ class _StringsSettingsGeneralSettingsEn {
late final _StringsSettingsGeneralSettingsDisconnectModalEn disconnectModal = _StringsSettingsGeneralSettingsDisconnectModalEn._(_root);
String get useInAppBrowser => 'Use in app browser';
String get useInAppBrowserDescription => 'Opens the bookmarks with the integrated browser instead of with the system browser. On tablet mode the internal browser will always be used.';
String get openLinksWith => 'Open links with...';
String get internalBrowser => 'App internal browser';
String get browserCustomTab => 'Browser custom tab';
String get systemBrowser => 'System browser';
}
// Path: settings.generalSettings.disconnectModal
@@ -884,6 +888,10 @@ class _StringsSettingsGeneralSettingsEs implements _StringsSettingsGeneralSettin
@override late final _StringsSettingsGeneralSettingsDisconnectModalEs disconnectModal = _StringsSettingsGeneralSettingsDisconnectModalEs._(_root);
@override String get useInAppBrowser => 'Usar navegador de la aplicación';
@override String get useInAppBrowserDescription => 'Abre los marcadores con el navegador integrado en vez de con el navegador del sietema. En modo tablet siempre se usará el navegador integrado.';
@override String get openLinksWith => 'Abrir enlaces con...';
@override String get internalBrowser => 'Navegador interno de la app';
@override String get browserCustomTab => 'Pestaña personalizada del navegador';
@override String get systemBrowser => 'Navegador del sistema';
}
// Path: settings.generalSettings.disconnectModal
@@ -1270,6 +1278,10 @@ class _StringsSettingsGeneralSettingsTr implements _StringsSettingsGeneralSettin
@override late final _StringsSettingsGeneralSettingsDisconnectModalTr disconnectModal = _StringsSettingsGeneralSettingsDisconnectModalTr._(_root);
@override String get useInAppBrowser => 'Uygulama tarayıcısında kullanın';
@override String get useInAppBrowserDescription => 'Yer işaretlerini sistem tarayıcısı yerine entegre tarayıcı ile açar. Tablet modunda dahili tarayıcı her zaman kullanılacaktır.';
@override String get openLinksWith => 'Open links with...';
@override String get internalBrowser => 'App internal browser';
@override String get browserCustomTab => 'Browser custom tab';
@override String get systemBrowser => 'System browser';
}
// Path: settings.generalSettings.disconnectModal
@@ -1461,6 +1473,10 @@ extension on Translations {
case 'settings.generalSettings.disconnectModal.description': return 'Are you sure you want to disconnect from the server?\nYou will be redirected to the initial setup.';
case 'settings.generalSettings.useInAppBrowser': return 'Use in app browser';
case 'settings.generalSettings.useInAppBrowserDescription': return 'Opens the bookmarks with the integrated browser instead of with the system browser. On tablet mode the internal browser will always be used.';
case 'settings.generalSettings.openLinksWith': return 'Open links with...';
case 'settings.generalSettings.internalBrowser': return 'App internal browser';
case 'settings.generalSettings.browserCustomTab': return 'Browser custom tab';
case 'settings.generalSettings.systemBrowser': return 'System browser';
case 'webview.goBack': return 'Go back';
case 'webview.goForward': return 'Go forward';
case 'webview.reload': return 'Reload';
@@ -1659,6 +1675,10 @@ extension on _StringsEs {
case 'settings.generalSettings.disconnectModal.description': return '¿Estás seguro que deseas desconectarte del servidor?\nSerás redirigido a la configuración inicial.';
case 'settings.generalSettings.useInAppBrowser': return 'Usar navegador de la aplicación';
case 'settings.generalSettings.useInAppBrowserDescription': return 'Abre los marcadores con el navegador integrado en vez de con el navegador del sietema. En modo tablet siempre se usará el navegador integrado.';
case 'settings.generalSettings.openLinksWith': return 'Abrir enlaces con...';
case 'settings.generalSettings.internalBrowser': return 'Navegador interno de la app';
case 'settings.generalSettings.browserCustomTab': return 'Pestaña personalizada del navegador';
case 'settings.generalSettings.systemBrowser': return 'Navegador del sistema';
case 'webview.goBack': return 'Ir atrás';
case 'webview.goForward': return 'Ir adelante';
case 'webview.reload': return 'Recargar';
@@ -1857,6 +1877,10 @@ extension on _StringsTr {
case 'settings.generalSettings.disconnectModal.description': return 'Sunucu bağlantısını kesmek istediğinizden emin misiniz?\nİlk kuruluma yönlendirileceksiniz.';
case 'settings.generalSettings.useInAppBrowser': return 'Uygulama tarayıcısında kullanın';
case 'settings.generalSettings.useInAppBrowserDescription': return 'Yer işaretlerini sistem tarayıcısı yerine entegre tarayıcı ile açar. Tablet modunda dahili tarayıcı her zaman kullanılacaktır.';
case 'settings.generalSettings.openLinksWith': return 'Open links with...';
case 'settings.generalSettings.internalBrowser': return 'App internal browser';
case 'settings.generalSettings.browserCustomTab': return 'Browser custom tab';
case 'settings.generalSettings.systemBrowser': return 'System browser';
case 'webview.goBack': return 'Geri dön';
case 'webview.goForward': return 'İleri git';
case 'webview.reload': return 'Yenile';

View File

@@ -198,7 +198,11 @@
"description": "Are you sure you want to disconnect from the server?\nYou will be redirected to the initial setup."
},
"useInAppBrowser": "Use in app browser",
"useInAppBrowserDescription": "Opens the bookmarks with the integrated browser instead of with the system browser. On tablet mode the internal browser will always be used."
"useInAppBrowserDescription": "Opens the bookmarks with the integrated browser instead of with the system browser. On tablet mode the internal browser will always be used.",
"openLinksWith": "Open links with...",
"internalBrowser": "App internal browser",
"browserCustomTab": "Browser custom tab",
"systemBrowser": "System browser"
}
},
"webview": {

View File

@@ -198,7 +198,11 @@
"description": "¿Estás seguro que deseas desconectarte del servidor?\nSerás redirigido a la configuración inicial."
},
"useInAppBrowser": "Usar navegador de la aplicación",
"useInAppBrowserDescription": "Abre los marcadores con el navegador integrado en vez de con el navegador del sietema. En modo tablet siempre se usará el navegador integrado."
"useInAppBrowserDescription": "Abre los marcadores con el navegador integrado en vez de con el navegador del sietema. En modo tablet siempre se usará el navegador integrado.",
"openLinksWith": "Abrir enlaces con...",
"internalBrowser": "Navegador interno de la app",
"browserCustomTab": "Pestaña personalizada del navegador",
"systemBrowser": "Navegador del sistema"
}
},
"webview": {

View File

@@ -198,7 +198,11 @@
"description": "Sunucu bağlantısını kesmek istediğinizden emin misiniz?\nİlk kuruluma yönlendirileceksiniz."
},
"useInAppBrowser": "Uygulama tarayıcısında kullanın",
"useInAppBrowserDescription": "Yer işaretlerini sistem tarayıcısı yerine entegre tarayıcı ile açar. Tablet modunda dahili tarayıcı her zaman kullanılacaktır."
"useInAppBrowserDescription": "Yer işaretlerini sistem tarayıcısı yerine entegre tarayıcı ile açar. Tablet modunda dahili tarayıcı her zaman kullanılacaktır.",
"openLinksWith": "Open links with...",
"internalBrowser": "App internal browser",
"browserCustomTab": "Browser custom tab",
"systemBrowser": "System browser"
}
},
"webview": {

View File

@@ -5,7 +5,7 @@ class AppStatusModel {
bool supportsDynamicTheme;
bool useDynamicTheme;
int selectedColor;
bool useInAppBrowser;
OpenLinksBrowser openLinksBrowser;
bool showFavicon;
AppStatusModel({
@@ -13,7 +13,7 @@ class AppStatusModel {
this.supportsDynamicTheme = false,
this.useDynamicTheme = true,
this.selectedColor = 0,
this.useInAppBrowser = true,
this.openLinksBrowser = OpenLinksBrowser.integrated,
this.showFavicon = true,
});
}

View File

@@ -1,9 +1,9 @@
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:linkdy/constants/shared_preferences_keys.dart';
import 'package:linkdy/screens/bookmarks/provider/favicon_loader.provider.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:linkdy/screens/bookmarks/provider/favicon_loader.provider.dart';
import 'package:linkdy/constants/shared_preferences_keys.dart';
import 'package:linkdy/providers/shared_preferences.provider.dart';
import 'package:linkdy/constants/enums.dart';
import 'package:linkdy/models/providers_models/app_status.model.dart';
@@ -19,7 +19,9 @@ class AppStatus extends _$AppStatus {
selectedColor: sharedPreferences.getInt(SharedPreferencesKeys.selectedColor) ?? 0,
selectedTheme: SelectedTheme.values[sharedPreferences.getInt(SharedPreferencesKeys.selectedTheme) ?? 0],
useDynamicTheme: sharedPreferences.getBool(SharedPreferencesKeys.useDynamicTheme) ?? true,
useInAppBrowser: sharedPreferences.getBool(SharedPreferencesKeys.useInAppBrowser) ?? true,
openLinksBrowser:
OpenLinksBrowserExtension.fromString(sharedPreferences.getString(SharedPreferencesKeys.openLinksBrowser)) ??
OpenLinksBrowser.integrated,
showFavicon: sharedPreferences.getBool(SharedPreferencesKeys.showFavicon) ?? true,
);
}
@@ -46,9 +48,10 @@ class AppStatus extends _$AppStatus {
ref.notifyListeners();
}
void setUseInAppBrowser(bool value) {
state.useInAppBrowser = value;
ref.read(sharedPreferencesProvider).setBool(SharedPreferencesKeys.useInAppBrowser, value);
void updateSelectedBrowser(OpenLinksBrowser? value) {
if (value == null) return;
state.openLinksBrowser = value;
ref.read(sharedPreferencesProvider).setString(SharedPreferencesKeys.openLinksBrowser, value.toString());
ref.notifyListeners();
}

View File

@@ -160,15 +160,19 @@ class Bookmarks extends _$Bookmarks {
}
void selectBookmark(Bookmark bookmark, double width) {
if (width <= Sizes.tabletBreakpoint && ref.watch(appStatusProvider).useInAppBrowser == true) {
ref.watch(routerProvider).push(RoutesPaths.webview, extra: bookmark);
} else if (width <= Sizes.tabletBreakpoint && ref.watch(appStatusProvider).useInAppBrowser == false) {
openUrl(bookmark.url!);
} else if (bookmark != state.selectedBookmark) {
state.webViewRouter.go(_webViewRoutePath, extra: bookmark);
if (ref.watch(appStatusProvider).openLinksBrowser == OpenLinksBrowser.browserCustomTab) {
openUrlCustomTab(bookmark.url!);
} else if (ref.watch(appStatusProvider).openLinksBrowser == OpenLinksBrowser.systemBrowser) {
openUrlSystemBrowser(bookmark.url!);
} else {
if (width <= Sizes.tabletBreakpoint) {
ref.watch(routerProvider).push(RoutesPaths.webview, extra: bookmark);
} else if (bookmark != state.selectedBookmark) {
state.webViewRouter.go(_webViewRoutePath, extra: bookmark);
}
state.selectedBookmark = bookmark;
ref.notifyListeners();
}
state.selectedBookmark = bookmark;
ref.notifyListeners();
}
Future<void> refresh() async {

View File

@@ -132,15 +132,19 @@ class SearchBookmarks extends _$SearchBookmarks {
}
void selectBookmark(Bookmark bookmark, double width) {
if (width <= Sizes.tabletBreakpoint && ref.watch(appStatusProvider).useInAppBrowser == true) {
ref.watch(routerProvider).push(RoutesPaths.webview, extra: bookmark);
} else if (width <= Sizes.tabletBreakpoint && ref.watch(appStatusProvider).useInAppBrowser == false) {
openUrl(bookmark.url!);
} else if (bookmark != state.selectedBookmark) {
state.webViewRouter.go(_webViewRoutePath, extra: bookmark);
if (ref.watch(appStatusProvider).openLinksBrowser == OpenLinksBrowser.browserCustomTab) {
openUrlCustomTab(bookmark.url!);
} else if (ref.watch(appStatusProvider).openLinksBrowser == OpenLinksBrowser.systemBrowser) {
openUrlSystemBrowser(bookmark.url!);
} else {
if (width <= Sizes.tabletBreakpoint) {
ref.watch(routerProvider).push(RoutesPaths.webview, extra: bookmark);
} else if (bookmark != state.selectedBookmark) {
state.webViewRouter.go(_webViewRoutePath, extra: bookmark);
}
state.selectedBookmark = bookmark;
ref.notifyListeners();
}
state.selectedBookmark = bookmark;
ref.notifyListeners();
}
void deleteBookmark(Bookmark bookmark) async {

View File

@@ -183,15 +183,19 @@ class FilteredBookmarks extends _$FilteredBookmarks {
}
void selectBookmark(Bookmark bookmark, double width) {
if (width <= Sizes.tabletBreakpoint && ref.watch(appStatusProvider).useInAppBrowser == true) {
ref.watch(routerProvider).push(RoutesPaths.webview, extra: bookmark);
} else if (width <= Sizes.tabletBreakpoint && ref.watch(appStatusProvider).useInAppBrowser == false) {
openUrl(bookmark.url!);
} else if (bookmark != state.selectedBookmark) {
state.webViewRouter.go(_webViewRoutePath, extra: bookmark);
if (ref.watch(appStatusProvider).openLinksBrowser == OpenLinksBrowser.browserCustomTab) {
openUrlCustomTab(bookmark.url!);
} else if (ref.watch(appStatusProvider).openLinksBrowser == OpenLinksBrowser.systemBrowser) {
openUrlSystemBrowser(bookmark.url!);
} else {
if (width <= Sizes.tabletBreakpoint) {
ref.watch(routerProvider).push(RoutesPaths.webview, extra: bookmark);
} else if (bookmark != state.selectedBookmark) {
state.webViewRouter.go(_webViewRoutePath, extra: bookmark);
}
state.selectedBookmark = bookmark;
ref.notifyListeners();
}
state.selectedBookmark = bookmark;
ref.notifyListeners();
}
Future<void> refresh() async {

View File

@@ -10,7 +10,7 @@ import 'package:linkdy/i18n/strings.g.dart';
import 'package:linkdy/utils/open_url.dart';
class OnboardingServerRequired extends ConsumerWidget {
const OnboardingServerRequired({Key? key}) : super(key: key);
const OnboardingServerRequired({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
@@ -42,7 +42,7 @@ class OnboardingServerRequired extends ConsumerWidget {
const SizedBox(height: 16),
Card(
child: InkWell(
onTap: () => openUrl(Urls.linkdingInstallationInstructions),
onTap: () => openUrlCustomTab(Urls.linkdingInstallationInstructions),
borderRadius: BorderRadius.circular(10),
child: Padding(
padding: const EdgeInsets.all(24),

View File

@@ -0,0 +1,88 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:linkdy/constants/enums.dart';
import 'package:linkdy/i18n/strings.g.dart';
import 'package:linkdy/providers/app_status.provider.dart';
class BrowserModeSelectionModal extends ConsumerWidget {
const BrowserModeSelectionModal({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
return Container(
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surface,
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(28),
topRight: Radius.circular(28),
),
),
child: SafeArea(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Flexible(
child: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: const EdgeInsets.only(top: 24, left: 24, right: 24, bottom: 8),
child: Column(
children: [
Icon(
Icons.public_rounded,
color: Theme.of(context).colorScheme.secondary,
size: 24,
),
const SizedBox(height: 16),
Text(
t.settings.generalSettings.openLinksWith,
style: const TextStyle(
fontSize: 24,
),
),
],
),
),
RadioListTile(
value: OpenLinksBrowser.integrated,
groupValue: ref.watch(appStatusProvider).openLinksBrowser,
onChanged: ref.read(appStatusProvider.notifier).updateSelectedBrowser,
title: Text(t.settings.generalSettings.internalBrowser),
),
RadioListTile(
value: OpenLinksBrowser.browserCustomTab,
groupValue: ref.watch(appStatusProvider).openLinksBrowser,
onChanged: ref.read(appStatusProvider.notifier).updateSelectedBrowser,
title: Text(t.settings.generalSettings.browserCustomTab),
),
RadioListTile(
value: OpenLinksBrowser.systemBrowser,
groupValue: ref.watch(appStatusProvider).openLinksBrowser,
onChanged: ref.read(appStatusProvider.notifier).updateSelectedBrowser,
title: Text(t.settings.generalSettings.systemBrowser),
),
],
),
),
),
Padding(
padding: const EdgeInsets.all(24),
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton(
onPressed: () => Navigator.pop(context),
child: Text(t.generic.close),
),
],
),
),
],
),
),
);
}
}

View File

@@ -1,17 +1,37 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:linkdy/constants/enums.dart';
import 'package:linkdy/i18n/strings.g.dart';
import 'package:linkdy/screens/settings/ui/general_settings/browser_mode_selection_modal.dart';
import 'package:linkdy/screens/settings/ui/general_settings/disconnect_modal.dart';
import 'package:linkdy/providers/app_status.provider.dart';
import 'package:linkdy/widgets/section_label.dart';
class GeneralSettings extends ConsumerWidget {
const GeneralSettings({Key? key}) : super(key: key);
const GeneralSettings({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
void openBrowserSelectionSheet() {
showModalBottomSheet(
context: context,
builder: (context) => const BrowserModeSelectionModal(),
);
}
String browserLabel() {
switch (ref.watch(appStatusProvider).openLinksBrowser) {
case OpenLinksBrowser.integrated:
return t.settings.generalSettings.internalBrowser;
case OpenLinksBrowser.browserCustomTab:
return t.settings.generalSettings.browserCustomTab;
case OpenLinksBrowser.systemBrowser:
return t.settings.generalSettings.systemBrowser;
}
}
return Scaffold(
appBar: AppBar(
title: Text(t.settings.generalSettings.generalSettings),
@@ -25,11 +45,10 @@ class GeneralSettings extends ConsumerWidget {
value: ref.watch(appStatusProvider).showFavicon,
onChanged: ref.read(appStatusProvider.notifier).setShowFavicon,
),
SwitchListTile(
title: Text(t.settings.generalSettings.useInAppBrowser),
subtitle: Text(t.settings.generalSettings.useInAppBrowserDescription),
value: ref.watch(appStatusProvider).useInAppBrowser,
onChanged: ref.read(appStatusProvider.notifier).setUseInAppBrowser,
ListTile(
title: Text(t.settings.generalSettings.openLinksWith),
subtitle: Text(browserLabel()),
onTap: openBrowserSelectionSheet,
),
const Padding(
padding: EdgeInsets.symmetric(vertical: 16),

View File

@@ -82,7 +82,7 @@ class _List extends ConsumerWidget {
Icons.open_in_new_rounded,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
onTap: () => openUrl(Urls.linkdingRepo),
onTap: () => openUrlCustomTab(Urls.linkdingRepo),
),
CustomListTile(
title: t.settings.appVersion,
@@ -99,7 +99,7 @@ class _List extends ConsumerWidget {
children: [
if (Platform.isAndroid)
IconButton(
onPressed: () => openUrl(Urls.playStoreLink),
onPressed: () => openUrlCustomTab(Urls.playStoreLink),
icon: SvgPicture.asset(
'assets/resources/google-play.svg',
color: Theme.of(context).colorScheme.onSurfaceVariant,
@@ -109,7 +109,7 @@ class _List extends ConsumerWidget {
tooltip: t.settings.visitGooglePlay,
),
IconButton(
onPressed: () => openUrl(Urls.gitHubRepo),
onPressed: () => openUrlCustomTab(Urls.gitHubRepo),
icon: SvgPicture.asset(
'assets/resources/github.svg',
color: Theme.of(context).colorScheme.onSurfaceVariant,

View File

@@ -143,7 +143,7 @@ class WebViewScreen extends HookConsumerWidget {
),
),
PopupMenuItem(
onTap: () => openUrl(bookmark.url!),
onTap: () => openUrlCustomTab(bookmark.url!),
child: Row(
children: [
const Icon(Icons.open_in_browser_rounded),

View File

@@ -4,7 +4,7 @@ import 'package:flutter_custom_tabs/flutter_custom_tabs.dart' as flutter_custom_
import 'package:sentry_flutter/sentry_flutter.dart';
import 'package:url_launcher/url_launcher.dart' as url_launcher;
void openUrl(String url) async {
void openUrlCustomTab(String url) async {
if (Platform.isAndroid || Platform.isIOS) {
try {
await flutter_custom_tabs.launchUrl(
@@ -30,3 +30,23 @@ void openUrl(String url) async {
}
}
}
void openUrlSystemBrowser(String url) async {
if (Platform.isAndroid || Platform.isIOS) {
try {
await flutter_custom_tabs.launchUrl(
Uri.parse(url),
customTabsOptions: null,
safariVCOptions: null,
);
} catch (e, stackTrace) {
Sentry.captureException(e, stackTrace: stackTrace);
}
} else {
try {
url_launcher.launchUrl(Uri.parse(url));
} catch (e, stackTrace) {
Sentry.captureException(e, stackTrace: stackTrace);
}
}
}

View File

@@ -1183,10 +1183,10 @@ packages:
dependency: "direct main"
description:
name: url_launcher
sha256: "6ce1e04375be4eed30548f10a315826fd933c1e493206eab82eed01f438c8d2e"
sha256: "21b704ce5fa560ea9f3b525b43601c678728ba46725bab9b01187b4831377ed3"
url: "https://pub.dev"
source: hosted
version: "6.2.6"
version: "6.3.0"
url_launcher_android:
dependency: transitive
description:

View File

@@ -38,7 +38,7 @@ dependencies:
riverpod_annotation: ^2.3.5
dynamic_color: ^1.7.0
shared_preferences: ^2.2.2
url_launcher: ^6.2.5
url_launcher: ^6.3.0
animations: ^2.0.11
segmented_button_slide: ^1.0.4
dio: ^5.4.1