feature: share link , open cover image

This commit is contained in:
kodjomoustapha 2023-05-18 18:16:25 +01:00
parent 9d4a7e8cb1
commit 1403048fc4
12 changed files with 286 additions and 170 deletions

View file

@ -3,7 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:mangayomi/models/chapter.dart';
import 'package:mangayomi/models/manga_type.dart';
import 'package:mangayomi/services/webviews/webview.dart';
import 'package:mangayomi/services/webview/webview.dart';
import 'package:mangayomi/views/browse/browse_screen.dart';
import 'package:mangayomi/views/browse/extension/extension_lang.dart';
import 'package:mangayomi/views/browse/global_search_screen.dart';
@ -251,9 +251,9 @@ class AsyncRouterNotifier extends ChangeNotifier {
builder: (context, state) {
final data = state.extra as Map<String, String>;
return MangaWebView(
url: data["url"]!,
source: data["source"]!,
);
url: data["url"]!,
source: data["source"]!,
title: data['title']!);
},
pageBuilder: (context, state) {
final data = state.extra as Map<String, String>;
@ -262,6 +262,7 @@ class AsyncRouterNotifier extends ChangeNotifier {
child: MangaWebView(
url: data["url"]!,
source: data["source"]!,
title: data['title']!,
),
);
},

View file

@ -0,0 +1,191 @@
import 'package:flutter/material.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:mangayomi/main.dart';
import 'package:mangayomi/models/settings.dart';
import 'package:mangayomi/services/http_service/cloudflare/cookie.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:share_plus/share_plus.dart';
class MangaWebView extends ConsumerStatefulWidget {
final String url;
final String source;
final String title;
const MangaWebView({
super.key,
required this.url,
required this.source,
required this.title,
});
@override
ConsumerState<MangaWebView> createState() => _MangaWebViewState();
}
class _MangaWebViewState extends ConsumerState<MangaWebView> {
final GlobalKey webViewKey = GlobalKey();
double progress = 0;
InAppWebViewController? webViewController;
late String _url = widget.url;
late String _title = widget.title;
bool _canGoback = false;
bool _canGoForward = false;
@override
Widget build(BuildContext context) {
return SafeArea(
child: WillPopScope(
onWillPop: () async {
webViewController?.goBack();
return false;
},
child: Column(
children: [
SizedBox(
height: AppBar().preferredSize.height,
child: Row(
children: [
Expanded(
child: ListTile(
dense: true,
subtitle: Text(
_url,
style: const TextStyle(
fontSize: 10, overflow: TextOverflow.ellipsis),
),
title: Text(
_title,
style: const TextStyle(
overflow: TextOverflow.ellipsis,
fontWeight: FontWeight.bold),
),
leading: IconButton(
onPressed: () {
Navigator.pop(context);
},
icon: const Icon(Icons.close)),
),
),
IconButton(
icon: Icon(Icons.arrow_back,
color: _canGoback ? null : Colors.grey),
onPressed: _canGoback
? () {
webViewController?.goBack();
}
: null,
),
IconButton(
icon: Icon(Icons.arrow_forward,
color: _canGoForward ? null : Colors.grey),
onPressed: _canGoForward
? () {
webViewController?.goForward();
}
: null,
),
PopupMenuButton(itemBuilder: (context) {
return [
const PopupMenuItem<int>(
value: 0, child: Text("Refresh")),
const PopupMenuItem<int>(value: 1, child: Text("Share")),
const PopupMenuItem<int>(
value: 2, child: Text("Open in browser")),
const PopupMenuItem<int>(
value: 3, child: Text("Clear cookie")),
];
}, onSelected: (value) async {
if (value == 0) {
webViewController?.reload();
} else if (value == 1) {
Share.share(_url);
} else if (value == 2) {
await InAppBrowser.openWithSystemBrowser(
url: WebUri.uri(Uri.parse(_url)));
} else if (value == 3) {
CookieManager.instance().getAllCookies();
}
}),
],
),
),
progress < 1.0
? LinearProgressIndicator(value: progress)
: Container(),
Expanded(
child: InAppWebView(
key: webViewKey,
onWebViewCreated: (controller) async {
webViewController = controller;
},
onLoadStart: (controller, url) async {
setState(() {
_url = url.toString();
});
},
onPermissionRequest: (controller, request) async {
return PermissionResponse(
resources: request.resources,
action: PermissionResponseAction.GRANT);
},
shouldOverrideUrlLoading: (controller, navigationAction) async {
var uri = navigationAction.request.url!;
if (![
"http",
"https",
"file",
"chrome",
"data",
"javascript",
"about"
].contains(uri.scheme)) {
if (await canLaunchUrl(uri)) {
// Launch the App
await launchUrl(
uri,
);
// and cancel the request
return NavigationActionPolicy.CANCEL;
}
}
return NavigationActionPolicy.ALLOW;
},
onLoadStop: (controller, url) async {
setState(() {
_url = url.toString();
});
},
onReceivedError: (controller, request, error) {},
onProgressChanged: (controller, progress) async {
setState(() {
this.progress = progress / 100;
});
},
onUpdateVisitedHistory: (controller, url, isReload) async {
await ref.watch(
setCookieProvider(widget.source, url.toString()).future);
final canGoback = await controller.canGoBack();
final canGoForward = await controller.canGoForward();
final title = await controller.getTitle();
setState(() {
_url = url.toString();
_title = title!;
_canGoback = canGoback;
_canGoForward = canGoForward;
});
},
initialSettings: InAppWebViewSettings(
userAgent: isar.settings.getSync(227)!.userAgent!),
initialUrlRequest:
URLRequest(url: WebUri.uri(Uri.parse(widget.url))),
),
),
],
),
),
);
}
}

View file

@ -1,152 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:mangayomi/main.dart';
import 'package:mangayomi/models/settings.dart';
import 'package:mangayomi/services/http_service/cloudflare/cookie.dart';
import 'package:url_launcher/url_launcher.dart';
class MangaWebView extends ConsumerStatefulWidget {
final String url;
final String source;
const MangaWebView({super.key, required this.url, required this.source});
@override
ConsumerState<MangaWebView> createState() => _MangaWebViewState();
}
class _MangaWebViewState extends ConsumerState<MangaWebView> {
final GlobalKey webViewKey = GlobalKey();
double progress = 0;
InAppWebViewController? webViewController;
String _url = "";
@override
void initState() {
setState(() {
_url = widget.url;
});
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: IconButton(
onPressed: () {
Navigator.pop(context);
},
icon: const Icon(Icons.close)),
title: Text(
_url,
style: const TextStyle(fontSize: 10),
),
actions: [
IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: () {
webViewController?.goBack();
},
),
IconButton(
icon: const Icon(Icons.arrow_forward),
onPressed: () {
webViewController?.goForward();
},
),
IconButton(
icon: const Icon(Icons.refresh),
onPressed: () {
webViewController?.reload();
},
),
IconButton(
onPressed: () async {
await InAppBrowser.openWithSystemBrowser(
url: WebUri.uri(Uri.parse(_url)));
},
icon: const Icon(Icons.read_more))
],
),
body: WillPopScope(
onWillPop: () async {
webViewController?.goBack();
return false;
},
child: Column(
children: [
progress < 1.0
? LinearProgressIndicator(value: progress)
: Container(),
Flexible(
child: InAppWebView(
key: webViewKey,
onWebViewCreated: (controller) async {
webViewController = controller;
},
onLoadStart: (controller, url) async {
setState(() {
_url = url.toString();
});
},
onPermissionRequest: (controller, request) async {
return PermissionResponse(
resources: request.resources,
action: PermissionResponseAction.GRANT);
},
shouldOverrideUrlLoading: (controller, navigationAction) async {
var uri = navigationAction.request.url!;
if (![
"http",
"https",
"file",
"chrome",
"data",
"javascript",
"about"
].contains(uri.scheme)) {
if (await canLaunchUrl(uri)) {
// Launch the App
await launchUrl(
uri,
);
// and cancel the request
return NavigationActionPolicy.CANCEL;
}
}
return NavigationActionPolicy.ALLOW;
},
onLoadStop: (controller, url) async {
setState(() {
_url = url.toString();
});
},
onReceivedError: (controller, request, error) {},
onProgressChanged: (controller, progress) async {
setState(() {
this.progress = progress / 100;
});
},
onUpdateVisitedHistory: (controller, url, isReload) async {
await ref.watch(
setCookieProvider(widget.source, url.toString()).future);
setState(() {
_url = url.toString();
});
},
initialSettings: InAppWebViewSettings(
userAgent: isar.settings.getSync(227)!.userAgent!),
initialUrlRequest:
URLRequest(url: WebUri.uri(Uri.parse(widget.url))),
),
),
],
),
),
);
}
}

View file

@ -24,6 +24,10 @@ import 'package:mangayomi/views/manga/detail/widgets/chapter_list_tile_widget.da
import 'package:mangayomi/views/manga/detail/widgets/chapter_sort_list_tile_widget.dart';
import 'package:mangayomi/views/manga/download/providers/download_provider.dart';
import 'package:mangayomi/views/widgets/error_text.dart';
import 'package:mangayomi/views/widgets/progress_center.dart';
import 'package:photo_view/photo_view.dart';
import 'package:photo_view/photo_view_gallery.dart';
import 'package:share_plus/share_plus.dart';
class MangaDetailView extends ConsumerStatefulWidget {
final Function(bool) isExtended;
@ -325,6 +329,14 @@ class _MangaDetailViewState extends ConsumerState<MangaDetailView>
}, onSelected: (value) {
if (value == 0) {
context.push("/categories");
} else if (value == 1) {
} else if (value == 2) {
String url = getMangaAPIUrl(
widget.manga!.source!)
.isEmpty
? widget.manga!.link!
: "${getMangaBaseUrl(widget.manga!.source!)}${widget.manga!.link!}";
Share.share(url);
}
}),
],
@ -874,7 +886,9 @@ class _MangaDetailViewState extends ConsumerState<MangaDetailView>
top: 20,
left: 13,
child: GestureDetector(
onTap: () {},
onTap: () {
_openImage(widget.manga!.imageUrl!);
},
child: SizedBox(
width: 65 * 1.5,
height: 65 * 2.3,
@ -938,6 +952,7 @@ class _MangaDetailViewState extends ConsumerState<MangaDetailView>
Map<String, String> data = {
'url': url,
'source': manga.source!,
'title': manga.name!
};
context.push("/mangawebview", extra: data);
},
@ -964,4 +979,33 @@ class _MangaDetailViewState extends ConsumerState<MangaDetailView>
),
);
}
_openImage(String url) {
showDialog(
context: context,
builder: (context) {
return Scaffold(
backgroundColor: Colors.transparent,
appBar: AppBar(
backgroundColor: Colors.transparent,
),
body: GestureDetector(
onTap: () => Navigator.pop(context),
child: PhotoViewGallery.builder(
itemCount: 1,
builder: (context, index) {
return PhotoViewGalleryPageOptions(
imageProvider: CachedNetworkImageProvider(url),
minScale: PhotoViewComputedScale.contained,
maxScale: PhotoViewComputedScale.covered,
);
},
loadingBuilder: (context, event) {
return const ProgressCenter();
},
),
),
);
});
}
}

View file

@ -204,7 +204,9 @@ class _MangaDetailsViewState extends ConsumerState<MangaDetailsView> {
showDialog(
context: context,
builder: (context) {
return StatefulBuilder(
return
StatefulBuilder(
builder: (context, setState) {
return AlertDialog(
title: const Text(

View file

@ -455,6 +455,7 @@ class _MangaChapterPageGalleryState
Map<String, String> data = {
'url': url,
'source': manga.source!,
'title': widget.chapter.name!
};
context.push("/mangawebview", extra: data);
},

View file

@ -102,8 +102,7 @@ class AppearanceScreen extends ConsumerWidget {
child: Text(
"Cancel",
style: TextStyle(
color: primaryColor(context)
.withOpacity(0.2)),
color: primaryColor(context)),
)),
],
)
@ -168,8 +167,7 @@ class AppearanceScreen extends ConsumerWidget {
child: Text(
"Cancel",
style: TextStyle(
color: primaryColor(context)
.withOpacity(0.2)),
color: primaryColor(context)),
)),
],
)

View file

@ -10,6 +10,7 @@ import flutter_js
import isar_flutter_libs
import package_info_plus
import path_provider_foundation
import share_plus
import sqflite
import url_launcher_macos
@ -19,6 +20,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
IsarFlutterLibsPlugin.register(with: registry.registrar(forPlugin: "IsarFlutterLibsPlugin"))
FLTPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FLTPackageInfoPlusPlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin"))
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
}

View file

@ -218,6 +218,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.1.1"
cross_file:
dependency: transitive
description:
name: cross_file
sha256: "0b0036e8cccbfbe0555fd83c1d31a6f30b77a96b598b35a5d36dd41f718695e9"
url: "https://pub.dev"
source: hosted
version: "0.3.3+4"
crypto:
dependency: transitive
description:
@ -746,10 +754,10 @@ packages:
dependency: transitive
description:
name: path_provider_windows
sha256: f53720498d5a543f9607db4b0e997c4b5438884de25b0f73098cc2671a51b130
sha256: d3f80b32e83ec208ac95253e0cd4d298e104fbc63cb29c5c69edaed43b0c69d6
url: "https://pub.dev"
source: hosted
version: "2.1.5"
version: "2.1.6"
pedantic:
dependency: transitive
description:
@ -918,6 +926,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.3.5"
share_plus:
dependency: "direct main"
description:
name: share_plus
sha256: "322a1ec9d9fe07e2e2252c098ce93d12dbd06133cc4c00ffe6a4ef505c295c17"
url: "https://pub.dev"
source: hosted
version: "7.0.0"
share_plus_platform_interface:
dependency: transitive
description:
name: share_plus_platform_interface
sha256: "0c6e61471bd71b04a138b8b588fa388e66d8b005e6f2deda63371c5c505a0981"
url: "https://pub.dev"
source: hosted
version: "3.2.1"
shelf:
dependency: transitive
description:
@ -1095,10 +1119,10 @@ packages:
dependency: transitive
description:
name: url_launcher_linux
sha256: "206fb8334a700ef7754d6a9ed119e7349bc830448098f21a69bf1b4ed038cabc"
sha256: "207f4ddda99b95b4d4868320a352d374b0b7e05eefad95a4a26f57da413443f5"
url: "https://pub.dev"
source: hosted
version: "3.0.4"
version: "3.0.5"
url_launcher_macos:
dependency: transitive
description:
@ -1127,10 +1151,10 @@ packages:
dependency: transitive
description:
name: url_launcher_windows
sha256: a83ba3607a507758669cfafb03f9de09bf6e6280c14d9b9cb18f013e406dcacd
sha256: "254708f17f7c20a9c8c471f67d86d76d4a3f9c1591aad1e15292008aceb82771"
url: "https://pub.dev"
source: hosted
version: "3.0.5"
version: "3.0.6"
uuid:
dependency: transitive
description:
@ -1167,10 +1191,10 @@ packages:
dependency: transitive
description:
name: win32
sha256: c9ebe7ee4ab0c2194e65d3a07d8c54c5d00bb001b76081c4a04cdb8448b59e46
sha256: "5a751eddf9db89b3e5f9d50c20ab8612296e4e8db69009788d6c8b060a84191c"
url: "https://pub.dev"
source: hosted
version: "3.1.3"
version: "4.1.4"
xdg_directories:
dependency: transitive
description:

View file

@ -59,6 +59,7 @@ dependencies:
draggable_menu: ^2.0.2
isar: 3.1.0+1
isar_flutter_libs: 3.1.0+1
share_plus: ^7.0.0
# The following adds the Cupertino Icons font to your application.

View file

@ -9,6 +9,7 @@
#include <flutter_js/flutter_js_plugin.h>
#include <isar_flutter_libs/isar_flutter_libs_plugin.h>
#include <permission_handler_windows/permission_handler_windows_plugin.h>
#include <share_plus/share_plus_windows_plugin_c_api.h>
#include <url_launcher_windows/url_launcher_windows.h>
void RegisterPlugins(flutter::PluginRegistry* registry) {
@ -18,6 +19,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
registry->GetRegistrarForPlugin("IsarFlutterLibsPlugin"));
PermissionHandlerWindowsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin"));
SharePlusWindowsPluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi"));
UrlLauncherWindowsRegisterWithRegistrar(
registry->GetRegistrarForPlugin("UrlLauncherWindows"));
}

View file

@ -6,6 +6,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
flutter_js
isar_flutter_libs
permission_handler_windows
share_plus
url_launcher_windows
)