v1.0.1
This commit is contained in:
398
lib/modules/news/detail/news_detail_controller.dart
Normal file
398
lib/modules/news/detail/news_detail_controller.dart
Normal file
@@ -0,0 +1,398 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_dmzj/app/app_color.dart';
|
||||
import 'package:flutter_dmzj/app/app_constant.dart';
|
||||
import 'package:flutter_dmzj/app/app_style.dart';
|
||||
import 'package:flutter_dmzj/app/controller/base_controller.dart';
|
||||
import 'package:flutter_dmzj/app/dialog_utils.dart';
|
||||
import 'package:flutter_dmzj/app/log.dart';
|
||||
import 'package:flutter_dmzj/app/utils.dart';
|
||||
import 'package:flutter_dmzj/requests/news_request.dart';
|
||||
import 'package:flutter_dmzj/routes/app_navigator.dart';
|
||||
import 'package:flutter_dmzj/services/app_settings_service.dart';
|
||||
import 'package:flutter_dmzj/services/db_service.dart';
|
||||
import 'package:flutter_dmzj/services/user_service.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:universal_html/html.dart' as html;
|
||||
import 'package:universal_html/parsing.dart';
|
||||
import 'package:webview_flutter/webview_flutter.dart';
|
||||
|
||||
class NewsDetailController extends BaseController {
|
||||
final String newsUrl;
|
||||
final String title;
|
||||
final int id;
|
||||
final NewsRequest request = NewsRequest();
|
||||
AppSettingsService get settings => AppSettingsService.instance;
|
||||
NewsDetailController(
|
||||
{required this.newsUrl, this.title = "资讯详情", required this.id}) {
|
||||
newsTitle.value = title;
|
||||
if (id == 0) {
|
||||
newsId = int.tryParse(
|
||||
RegExp(r"/(\d+).html").firstMatch(newsUrl)?.group(1) ?? "0") ??
|
||||
0;
|
||||
} else {
|
||||
newsId = id;
|
||||
}
|
||||
}
|
||||
WebViewController? webViewController =
|
||||
(Platform.isAndroid || Platform.isIOS) ? WebViewController() : null;
|
||||
|
||||
/// 评论数
|
||||
var commentAmount = 0.obs;
|
||||
|
||||
/// 点赞数
|
||||
var moodAmount = 0.obs;
|
||||
|
||||
/// 是否点过赞
|
||||
var liked = false.obs;
|
||||
|
||||
/// 是否已经收藏
|
||||
var collected = false.obs;
|
||||
|
||||
var newsId = 0;
|
||||
|
||||
var newsTitle = "资讯详情".obs;
|
||||
|
||||
var htmlContent = "".obs;
|
||||
var author = "".obs;
|
||||
var photo = "".obs;
|
||||
var src = "".obs;
|
||||
var time = "".obs;
|
||||
|
||||
var images = <String>[];
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
liked.value = DBService.instance.newsLikeBox.containsKey(newsId);
|
||||
if (Platform.isAndroid || Platform.isIOS) {
|
||||
initWebView();
|
||||
} else {
|
||||
loadHtml();
|
||||
}
|
||||
// loadStat();
|
||||
// checkCollected();
|
||||
super.onInit();
|
||||
}
|
||||
|
||||
var currentUrl = "";
|
||||
void initWebView() {
|
||||
webViewController!.setJavaScriptMode(JavaScriptMode.unrestricted);
|
||||
webViewController!.setBackgroundColor(
|
||||
Get.isDarkMode ? Colors.black : AppColor.backgroundColor);
|
||||
webViewController!.setNavigationDelegate(
|
||||
NavigationDelegate(
|
||||
onPageStarted: (String url) {
|
||||
pageLoadding.value = true;
|
||||
},
|
||||
onPageFinished: (String url) async {
|
||||
try {
|
||||
await setFontSize();
|
||||
//防止亮瞎24K钛合金狗眼
|
||||
if (Get.isDarkMode) {
|
||||
await webViewController!.runJavaScript("""
|
||||
document.body.style.background="#000000";
|
||||
document.getElementsByClassName("min_box")[0].style.background="#000000";
|
||||
document.getElementsByClassName("news_box")[0].style.color="#f1f2f6";
|
||||
document.getElementsByClassName("min_box_tit")[0].style.color="#fff";
|
||||
""");
|
||||
}
|
||||
//加载前5张图片
|
||||
//当Web没有滚动条时,图片不会加载,这里手动给他加载出来
|
||||
await webViewController!.runJavaScript("""
|
||||
\$('.news_box img:lt(5)').each(function () {
|
||||
\$(this).lazyload({
|
||||
effect: "fadeIn"
|
||||
});
|
||||
});""");
|
||||
//读取全部的图片
|
||||
|
||||
var imagesResult =
|
||||
await webViewController?.runJavaScriptReturningResult('''
|
||||
function getImgLinks(){
|
||||
var imgLinks = [];
|
||||
\$('img').each(function() {
|
||||
var src = \$(this).attr('data-original');
|
||||
if (src && src.startsWith('https://images')) {
|
||||
imgLinks.push(src);
|
||||
}
|
||||
});
|
||||
console.log(imgLinks);
|
||||
return ${Platform.isIOS ? "JSON.stringify(imgLinks)" : "imgLinks"};
|
||||
}
|
||||
getImgLinks();
|
||||
''');
|
||||
if (imagesResult != null && imagesResult != "null") {
|
||||
List list = json.decode(imagesResult.toString());
|
||||
images = list.map((e) => e.toString()).toList();
|
||||
}
|
||||
} finally {
|
||||
pageLoadding.value = false;
|
||||
}
|
||||
},
|
||||
onWebResourceError: (WebResourceError error) {},
|
||||
onNavigationRequest: (NavigationRequest request) async {
|
||||
var result = await onTapUrl(request.url);
|
||||
return result
|
||||
? NavigationDecision.prevent
|
||||
: NavigationDecision.navigate;
|
||||
},
|
||||
),
|
||||
);
|
||||
Log.d(newsUrl);
|
||||
currentUrl = "https://v3api.zaimanhua.com/v3/article/show/$newsId.html";
|
||||
|
||||
webViewController!.loadRequest(Uri.parse(currentUrl));
|
||||
}
|
||||
|
||||
void loadHtml() async {
|
||||
try {
|
||||
pageError.value = false;
|
||||
pageLoadding.value = true;
|
||||
var result = await Dio().get(
|
||||
newsUrl,
|
||||
options: Options(
|
||||
responseType: ResponseType.plain,
|
||||
),
|
||||
);
|
||||
final htmlDocument = parseHtmlDocument(result.data);
|
||||
var news = htmlDocument.documentElement!.querySelector('.news_box');
|
||||
|
||||
htmlContent.value = news!.innerHtml ?? "";
|
||||
|
||||
author.value =
|
||||
htmlDocument.documentElement?.querySelector('.txt1')?.innerText ?? "";
|
||||
src.value =
|
||||
htmlDocument.documentElement?.querySelector('.txt2')?.innerText ?? "";
|
||||
time.value =
|
||||
htmlDocument.documentElement?.querySelector('.txt3')?.innerText ?? "";
|
||||
|
||||
var imgList = htmlDocument.documentElement?.querySelectorAll('img');
|
||||
var imagesList = <String>[];
|
||||
for (html.Element img in imgList ?? []) {
|
||||
var imgSrc = img.getAttribute("data-original");
|
||||
if (imgSrc != null) {
|
||||
imagesList.add(imgSrc);
|
||||
}
|
||||
}
|
||||
images = imagesList;
|
||||
} catch (e) {
|
||||
pageError.value = true;
|
||||
errorMsg.value = e.toString();
|
||||
} finally {
|
||||
pageLoadding.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
void loadStat() async {
|
||||
try {
|
||||
var result = await request.stat(newsId);
|
||||
commentAmount.value = result.commentAmount;
|
||||
moodAmount.value = result.moodAmount;
|
||||
newsTitle.value = result.title;
|
||||
} catch (e) {
|
||||
SmartDialog.showToast(e.toString());
|
||||
SmartDialog.showToast("读取新闻数据失败:$e");
|
||||
}
|
||||
}
|
||||
|
||||
void checkCollected() async {
|
||||
if (!UserService.instance.logined.value) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
collected.value = await request.checkCollect(newsId);
|
||||
} catch (e) {
|
||||
Log.logPrint(e);
|
||||
SmartDialog.showToast("检查用户收藏状态失败:$e");
|
||||
}
|
||||
}
|
||||
|
||||
void refershContent() {
|
||||
webViewController!.reload();
|
||||
}
|
||||
|
||||
void collect() async {
|
||||
if (!await UserService.instance.login()) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
SmartDialog.showLoading();
|
||||
await (collected.value
|
||||
? request.delCollect(newsId)
|
||||
: request.collect(newsId));
|
||||
collected.value = !collected.value;
|
||||
} catch (e) {
|
||||
Log.logPrint(e);
|
||||
SmartDialog.showToast(e.toString());
|
||||
} finally {
|
||||
SmartDialog.dismiss(status: SmartStatus.loading);
|
||||
}
|
||||
}
|
||||
|
||||
void like() async {
|
||||
if (liked.value) {
|
||||
SmartDialog.showToast("已经点过赞了");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
SmartDialog.showLoading();
|
||||
await request.like(newsId);
|
||||
liked.value = true;
|
||||
moodAmount.value += 1;
|
||||
DBService.instance.newsLikeBox.put(newsId, true);
|
||||
} catch (e) {
|
||||
SmartDialog.showToast(e.toString());
|
||||
} finally {
|
||||
SmartDialog.dismiss(status: SmartStatus.loading);
|
||||
}
|
||||
}
|
||||
|
||||
void share() {
|
||||
Utils.share(newsUrl, content: title);
|
||||
}
|
||||
|
||||
void comment() async {
|
||||
AppNavigator.toComment(objId: newsId, type: AppConstant.kTypeNews);
|
||||
}
|
||||
|
||||
void photoView() {
|
||||
DialogUtils.showImageViewer(0, images);
|
||||
}
|
||||
|
||||
void showImageView(String imgSrc) {
|
||||
if (imgSrc.isEmpty) {
|
||||
return;
|
||||
}
|
||||
if (images.contains(imgSrc)) {
|
||||
DialogUtils.showImageViewer(
|
||||
images.indexOf(imgSrc),
|
||||
images,
|
||||
);
|
||||
} else {
|
||||
DialogUtils.showImageViewer(0, [imgSrc]);
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> onTapUrl(url) async {
|
||||
//iOS处理
|
||||
if (url == currentUrl) {
|
||||
return false;
|
||||
}
|
||||
var uri = Uri.parse(url);
|
||||
Log.d(url);
|
||||
if (uri.scheme == "dmzjimage") {
|
||||
//打开图片
|
||||
showImageView(uri.queryParameters['src'].toString());
|
||||
return true;
|
||||
} else if (uri.scheme == "dmzjandroid") {
|
||||
var id = int.tryParse(uri.queryParameters["id"].toString()) ?? 0;
|
||||
if (uri.path == "/cartoon_description") {
|
||||
AppNavigator.toComicDetail(id);
|
||||
} else {
|
||||
AppNavigator.toNovelDetail(id);
|
||||
}
|
||||
return true;
|
||||
} else if (uri.scheme == "https" || uri.scheme == "http") {
|
||||
if (uri.path.contains("article/")) {
|
||||
AppNavigator.toNewsDetail(url: url);
|
||||
} else {
|
||||
AppNavigator.toWebView(url);
|
||||
}
|
||||
|
||||
return true;
|
||||
} else {
|
||||
SmartDialog.showToast("无法打开链接:$url");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
void showSettings() {
|
||||
AppNavigator.showBottomSheet(
|
||||
SizedBox(
|
||||
height: 400,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const ListTile(
|
||||
title: Text("设置"),
|
||||
trailing: IconButton(
|
||||
onPressed: AppNavigator.closePage,
|
||||
icon: Icon(Icons.close),
|
||||
),
|
||||
contentPadding: AppStyle.edgeInsetsL12,
|
||||
),
|
||||
Divider(
|
||||
height: 1.0,
|
||||
color: Colors.grey.withOpacity(.2),
|
||||
),
|
||||
Obx(
|
||||
() => ListTile(
|
||||
title: const Text("字体大小"),
|
||||
leading: const Icon(Icons.text_fields_rounded),
|
||||
trailing: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
OutlinedButton(
|
||||
onPressed: () {
|
||||
settings.setNewsFontSize(
|
||||
settings.newsFontSize.value + 1,
|
||||
);
|
||||
setFontSize();
|
||||
},
|
||||
child: const Icon(
|
||||
Icons.add,
|
||||
color: Colors.grey,
|
||||
),
|
||||
),
|
||||
AppStyle.hGap12,
|
||||
Text("${settings.newsFontSize.value}"),
|
||||
AppStyle.hGap12,
|
||||
OutlinedButton(
|
||||
onPressed: () {
|
||||
settings.setNewsFontSize(
|
||||
settings.newsFontSize.value - 1,
|
||||
);
|
||||
setFontSize();
|
||||
},
|
||||
child: const Icon(
|
||||
Icons.remove,
|
||||
color: Colors.grey,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.photo),
|
||||
title: const Text("进入看图模式"),
|
||||
onTap: () {
|
||||
AppNavigator.closePage();
|
||||
photoView();
|
||||
},
|
||||
trailing: const Icon(Icons.chevron_right),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future setFontSize() async {
|
||||
try {
|
||||
if (webViewController == null) {
|
||||
return;
|
||||
}
|
||||
await webViewController!.runJavaScript(
|
||||
'''document.getElementsByClassName("news_box")[0].style.fontSize="${settings.newsFontSize}px";
|
||||
document.getElementsByClassName("news_box")[0].style.lineHeight="1.6em";
|
||||
''');
|
||||
} catch (e) {
|
||||
Log.logPrint(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
189
lib/modules/news/detail/news_detail_page.dart
Normal file
189
lib/modules/news/detail/news_detail_page.dart
Normal file
@@ -0,0 +1,189 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_dmzj/app/app_style.dart';
|
||||
import 'package:flutter_dmzj/modules/news/detail/news_detail_controller.dart';
|
||||
import 'package:flutter_dmzj/widgets/net_image.dart';
|
||||
import 'package:flutter_dmzj/widgets/status/app_error_widget.dart';
|
||||
import 'package:flutter_dmzj/widgets/status/app_loadding_widget.dart';
|
||||
import 'package:flutter_widget_from_html_core/flutter_widget_from_html_core.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:remixicon/remixicon.dart';
|
||||
import 'package:webview_flutter/webview_flutter.dart';
|
||||
|
||||
class NewsDetailPage extends StatelessWidget {
|
||||
final String newsUrl;
|
||||
final int newsId;
|
||||
final String title;
|
||||
final NewsDetailController controller;
|
||||
NewsDetailPage({
|
||||
required this.newsUrl,
|
||||
this.title = "资讯详情",
|
||||
required this.newsId,
|
||||
Key? key,
|
||||
}) : controller = Get.put(
|
||||
NewsDetailController(id: newsId, newsUrl: newsUrl, title: title),
|
||||
tag: DateTime.now().millisecondsSinceEpoch.toString(),
|
||||
),
|
||||
super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Obx(() => Text(controller.newsTitle.value)),
|
||||
actions: [
|
||||
IconButton(
|
||||
onPressed: controller.share,
|
||||
icon: const Icon(Icons.share),
|
||||
),
|
||||
],
|
||||
),
|
||||
body: Stack(
|
||||
children: [
|
||||
(Platform.isAndroid || Platform.isIOS)
|
||||
? Obx(
|
||||
() => Offstage(
|
||||
offstage: controller.pageLoadding.value,
|
||||
child: WebViewWidget(
|
||||
controller: controller.webViewController!,
|
||||
),
|
||||
),
|
||||
)
|
||||
: buildHtml(),
|
||||
Obx(
|
||||
() => Offstage(
|
||||
offstage: !controller.pageLoadding.value,
|
||||
child: const AppLoaddingWidget(),
|
||||
),
|
||||
),
|
||||
Obx(
|
||||
() => Offstage(
|
||||
offstage: !controller.pageError.value,
|
||||
child: AppErrorWidget(
|
||||
errorMsg: controller.errorMsg.value,
|
||||
onRefresh: controller.refershContent,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
bottomNavigationBar: BottomAppBar(
|
||||
child: SizedBox(
|
||||
height: 48,
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Obx(
|
||||
() => TextButton.icon(
|
||||
onPressed: controller.like,
|
||||
icon: Icon(
|
||||
controller.liked.value
|
||||
? Remix.thumb_up_fill
|
||||
: Remix.thumb_up_line,
|
||||
size: 20,
|
||||
),
|
||||
label: Text(controller.moodAmount > 0
|
||||
? "${controller.moodAmount}"
|
||||
: "点赞"),
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Obx(
|
||||
() => TextButton.icon(
|
||||
style: TextButton.styleFrom(
|
||||
textStyle: const TextStyle(fontSize: 14),
|
||||
),
|
||||
onPressed: controller.comment,
|
||||
icon: const Icon(
|
||||
Remix.chat_2_line,
|
||||
size: 20,
|
||||
),
|
||||
label: Text(controller.commentAmount > 0
|
||||
? "${controller.commentAmount}"
|
||||
: "评论"),
|
||||
),
|
||||
),
|
||||
),
|
||||
// Expanded(
|
||||
// child: Obx(
|
||||
// () => TextButton.icon(
|
||||
// style: TextButton.styleFrom(
|
||||
// textStyle: const TextStyle(fontSize: 14),
|
||||
// ),
|
||||
// onPressed: controller.collect,
|
||||
// icon: Icon(
|
||||
// controller.collected.value
|
||||
// ? Remix.star_fill
|
||||
// : Remix.star_line,
|
||||
// size: 20,
|
||||
// ),
|
||||
// label: Text(controller.collected.value ? "已收藏" : "收藏"),
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
Expanded(
|
||||
child: TextButton.icon(
|
||||
style: TextButton.styleFrom(
|
||||
textStyle: const TextStyle(fontSize: 14),
|
||||
),
|
||||
onPressed: controller.showSettings,
|
||||
icon: const Icon(
|
||||
Remix.settings_line,
|
||||
size: 20,
|
||||
),
|
||||
label: const Text("设置"),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildHtml() {
|
||||
return Obx(
|
||||
() => ListView(
|
||||
padding: AppStyle.edgeInsetsA12,
|
||||
children: [
|
||||
Text(
|
||||
controller.title,
|
||||
style: Get.textTheme.titleLarge,
|
||||
),
|
||||
AppStyle.vGap4,
|
||||
Text(
|
||||
"${controller.author.value} ${controller.src.value} ${controller.time.value}",
|
||||
style: const TextStyle(fontSize: 12, color: Colors.grey),
|
||||
),
|
||||
AppStyle.vGap12,
|
||||
HtmlWidget(
|
||||
controller.htmlContent.value,
|
||||
textStyle: TextStyle(
|
||||
fontSize: controller.settings.newsFontSize.value.toDouble(),
|
||||
),
|
||||
customWidgetBuilder: (e) {
|
||||
if (e.localName == "img") {
|
||||
var imgSrc = e.attributes["src"];
|
||||
imgSrc ??= e.attributes["data-original"];
|
||||
return GestureDetector(
|
||||
child: NetImage(
|
||||
imgSrc!,
|
||||
borderRadius: 4,
|
||||
),
|
||||
onTap: () {
|
||||
controller.showImageView(imgSrc ?? "");
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
onTapUrl: controller.onTapUrl,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user