提交 ce4a0087 authored 作者: maodou's avatar maodou

Merge remote-tracking branch 'origin/test' into test

......@@ -302,5 +302,20 @@ abstract class LibraryAPI {
return false;
}
/// 15、批量上传离线笔记、高亮、划线的内容
static Future <bool> uploadOffline({
required String node,
}) async {
final result = await HttpService.to.post(
'/v1/book/Information/addOfflineNotes',
params: {
'nodes_list':node,
},
);
if (result.data is Map && result.data['is_success'] == 1){
return true;
}
return false;
}
}
\ No newline at end of file
......@@ -18,8 +18,6 @@ class Global {
// print('本地服务器已成功启动,根目录为: $documentRoot');
SqlManager.init();
WidgetsBinding.instance?.addObserver(AppLifecycleObserver());
await Future.wait([
......
library bai_dict;
import 'package:flutter/material.dart';
import 'package:flutter_book/utils/index.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
part 'view.dart';
\ No newline at end of file
part of bai_dict;
class BaiDictPage extends StatefulWidget {
final String keyword;
const BaiDictPage({
Key? key,
required this.keyword
}) : super(key: key);
@override
State<BaiDictPage> createState() => _BaiKePageState();
}
class _BaiKePageState extends State<BaiDictPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
centerTitle: false,
title: Text('$widget.keyword',style: TextStyle(fontSize: 14.w,fontWeight: Fonts.medium,color: Colours.c3),),
),
body: InAppWebView(
initialUrlRequest: URLRequest(
url: Uri.parse('https://hanyu.baidu.com/hanyu-page/zici/s?from=aladdin&query=${widget.keyword}&srcid=51368&wd=${widget.keyword}&ptype=zici'),
),
),
);
}
}
\ No newline at end of file
......@@ -56,6 +56,8 @@ class BookDetailController extends GetxController with GetSingleTickerProviderSt
/// 获取图书详细信息
void getBookDetails() async {
bookDetails = await LibraryAPI.details(bookId:bookId);
// 将阅读最后章节写入到数据库
SqlManager.updateReadHistoryByBookId(int.parse(bookId), bookDetails.chapterId!.toInt());
update();
}
/// 收藏 与 取消收藏
......
......@@ -73,16 +73,38 @@ class LibraryController extends GetxController with GetTickerProviderStateMixin{
vsync: this,
duration: const Duration(milliseconds: 100),
);
super.onInit();
}
@override
void onReady() {
void onReady() async {
// 获取标签数据
_getLabels();
_getAds();
onRefresh();
Map<String, dynamic> data = {
'types': 1,
'book_id': 175,
'chapter_id': 334,
'is_open': 1,
'color': 'blue',
'content': 'This is a note content',
'upload': 0,
'positioning': 'top',
'note': 'This is a note',
'node_id': 0,
};
await SqlManager.insertData(data);
final result = await Tools.checkCurrentNetStatus();
// 有网络的时候上传 笔记
if (result){
upload();
}
super.onReady();
}
......@@ -176,20 +198,6 @@ class LibraryController extends GetxController with GetTickerProviderStateMixin{
/// 获取tab分类数据
void _getTabs() async {
Map<String, dynamic> data = {
'types': 1,
'book_id': 123,
'chapter_id': 456,
// 'is_open': 1,
// 'color': 'blue',
// 'content': 'This is a note content',
// 'del': 0,
// 'positioning': 'top',
// 'note': 'This is a note',
};
await SqlManager.insertData(data);
categories = await LibraryAPI.categories();
tabController.dispose();
tabController = TabController(length:categories.length, vsync: this);
......@@ -232,6 +240,18 @@ class LibraryController extends GetxController with GetTickerProviderStateMixin{
}
}
/// 批量上传离线笔记、高亮、划线的内容
void upload() async{
List<Map<String, dynamic>> data = await SqlManager.queryNoUploadData();
Console.log('查询到的数据----------------------------------------------------$data');
final result = await LibraryAPI.uploadOffline(node: jsonEncode(data));
// 上传成功后 更新数据库中的字段
if (result) {
SqlManager.updateUploadStatus();
}
}
/// 获取图书列表数据
Future<void> _getBooks([bool isRefresh = false]) async {
......
library library;
import 'dart:convert';
import 'package:azlistview/azlistview.dart';
import 'package:easy_refresh/easy_refresh.dart';
import 'package:flutter/material.dart';
......
......@@ -60,13 +60,15 @@ class ReadController extends FullLifeCycleController with GetSingleTickerProvide
// 当前html名称 0-318.html
late String currentHtmlName = '';
// 朗读组件
late FlutterTts flutterTts;
///------------------------------------------ 页面 生命周期--------------------------------------------------------
@override
void onInit() {
initTts();
discussTitleFocusNode.addListener(_onCommentFocusChanged);
openTheRecorder();
super.onInit();
}
......@@ -75,10 +77,25 @@ class ReadController extends FullLifeCycleController with GetSingleTickerProvide
// 上报开始阅读时间
_addReadTime(type: 'open');
_getChapters();
netStatus = await Tools.checkCurrentNetStatus();
// // 判断是否有离线文件 如果有使用离线阅读
readLocalHtml('');
readChapter(type: 1);
final exist = await _isExistFile(bookId);
if (netStatus && exist){
// 1、从数据库中获取读到那个章节
chapterId = await SqlManager.queryReadHistoryByBookId(int.parse(bookId));
chapterName = getChapterName(chapterId);
// 2、通过 chapterId 获取 对应离线的 html路径
String toReadHtmlPath = await getLocalReadHtml(chapterId);
// 3、开始读书
read(toReadHtmlPath);
// 获取当前 文件名称
currentHtmlName = path.basename(toReadHtmlPath);
update();
}
super.onReady();
}
......@@ -91,6 +108,7 @@ class ReadController extends FullLifeCycleController with GetSingleTickerProvide
discussTitleFocusNode.dispose();
titleInput.dispose();
contentInput.dispose();
flutterTts.stop();
super.onClose();
}
///------------------------------------------ 页面 生命周期--------------------------------------------------------
......@@ -141,6 +159,76 @@ class ReadController extends FullLifeCycleController with GetSingleTickerProvide
}
// 初始化朗读组件
initTts() {
flutterTts = FlutterTts();
_setAwaitOptions();
if (Platform.isAndroid) {
_getDefaultEngine();
_getDefaultVoice();
}
flutterTts.setStartHandler(() {
Console.log('-------------Playing-------------------');
});
if (Platform.isAndroid) {
flutterTts.setInitHandler(() {
Console.log('-------------TTS Initialized-------------------');
});
}
flutterTts.setCompletionHandler(() {
Console.log('-------------TTS Complete-------------------');
});
flutterTts.setCancelHandler(() {
Console.log('-------------TTS Cancel-------------------');
});
flutterTts.setPauseHandler(() {
Console.log('-------------TTS Paused-------------------');
});
flutterTts.setContinueHandler(() {
Console.log('-------------TTS Continued-------------------');
});
flutterTts.setErrorHandler((msg) {
Console.log('-------------TTS error-------------------$msg');
});
}
Future _setAwaitOptions() async {
await flutterTts.awaitSpeakCompletion(true);
}
Future _getDefaultEngine() async {
var engine = await flutterTts.getDefaultEngine;
if (engine != null) {
Console.log('engine--------------------------------$engine');
}
}
Future _getDefaultVoice() async {
var voice = await flutterTts.getDefaultVoice;
if (voice != null) {
Console.log('voice--------------------------------$voice');
}
}
Future speak(String text) async {
await flutterTts.setVolume(0.5);
await flutterTts.setSpeechRate(0.5);
await flutterTts.setPitch(1.0);
if (text != null) {
if (text!.isNotEmpty) {
await flutterTts.speak(text!);
}
}
}
// 初始化录音组件
Future<void> openTheRecorder() async {
// 获取权限
......@@ -172,6 +260,9 @@ class ReadController extends FullLifeCycleController with GetSingleTickerProvide
}
// 开启录音
void record() async {
openTheRecorder();
update();
startRecording = true;
String filePath = await Tools.getDirectory();
String fileName = Tools.generateVoiceFileName();
......@@ -198,7 +289,7 @@ class ReadController extends FullLifeCycleController with GetSingleTickerProvide
startRecording = false;
final path = await _mRecorder.stopRecorder();
var duration = await audioPlayer.setFilePath(path!);
print('-----duration---------------------$duration------');
Console.log('-----duration---------------------$duration------');
AudioModel audioModel = AudioModel(path: path,duration: Tools.formatDuration(duration!));
discussInputAudios.add(audioModel);
update();
......@@ -209,7 +300,7 @@ class ReadController extends FullLifeCycleController with GetSingleTickerProvide
String twoDigits(int n) => n.toString().padLeft(2, '0');
String twoDigitMinutes = twoDigits(duration.inMinutes.remainder(60));
String twoDigitSeconds = twoDigits(duration.inSeconds.remainder(60));
return '${duration.inHours}:$twoDigitMinutes:${twoDigitSeconds}';
return '${duration.inHours}:$twoDigitMinutes:$twoDigitSeconds';
}
......@@ -276,7 +367,7 @@ class ReadController extends FullLifeCycleController with GetSingleTickerProvide
// return result;
OssTool tool = OssTool('zxts-comment-file');
final response = await tool.putObjectFile(path);
print('------response--------------------------${response.realUri}');
Console.log('------response--------------------------${response.realUri}');
return response.realUri.toString();
}
......@@ -426,12 +517,12 @@ class ReadController extends FullLifeCycleController with GetSingleTickerProvide
File(filePath)
..createSync(recursive: true)
..writeAsBytesSync(file.content as List<int>);
print('解压缩文件:$fileName,保存路径:$filePath');
('解压缩文件:$fileName,保存路径:$filePath');
}
}
} else {
print('未找到缓存中的文件或文件不存在');
Console.log('未找到缓存中的文件或文件不存在');
}
}
......@@ -441,12 +532,12 @@ class ReadController extends FullLifeCycleController with GetSingleTickerProvide
Directory directory = Directory(directoryPath);
bool directoryExists = await directory.exists();
if (directoryExists) {
print('存在名为 "$bookId" 的文件夹');
Console.log('存在名为 "$bookId" 的文件夹');
existDownFile = await Directory('${directory.path}/$bookId').exists();
}
else {
print('不存在名为 "$bookId" 的文件夹');
Console.log('不存在名为 "$bookId" 的文件夹');
existDownFile = false;
}
update();
......@@ -454,10 +545,46 @@ class ReadController extends FullLifeCycleController with GetSingleTickerProvide
}
Future<String> getLocalReadHtml(String chapterId) async{
String docPath = await _getDirectory();
String filePath = '$docPath/$bookId';
Directory directory = Directory(filePath);
// 获取目录下的所有文件
List<FileSystemEntity> files = directory.listSync(recursive: true);
for (var file in files) {
if (file is File && file.path.toLowerCase().endsWith('.html')) {
String fileName = path.basenameWithoutExtension(file.path);
if (fileName.split('-').last == chapterId){
return file.path;
}
}
}
return '';
}
// 开始阅读离线
void read(String toReadHtmlPath) async {
// 3、获取 离线文件的内容
final content = await readHtmlFileContent(toReadHtmlPath);
// 4、解密离线的内容
String htmlStr = EncryptUtil.aesDecrypt(content!);
// 5、将离线内容写入临时文件
String directoryPath = path.dirname(toReadHtmlPath);
String writeFilePath = '$directoryPath/read.html';
// 6、创建临时文件
File file = File(writeFilePath);
// 7、将HTML内容写入文件
await file.writeAsString(htmlStr);
webViewController.loadUrl(urlRequest: URLRequest(
url: Uri.parse(writeFilePath),
));
}
// 本地阅读 读取上一章节 或 下一章节
// 0 上一章节
// 1 下一章节
Future<String> readChapter({required int type}) async {
Future<void> readChapter({required int type}) async {
Console.log('---------------获取所有html---------------------------------');
String docPath = await _getDirectory();
......@@ -467,33 +594,45 @@ class ReadController extends FullLifeCycleController with GetSingleTickerProvide
List<FileSystemEntity> files = directory.listSync(recursive: true);
int findIndex = int.parse(currentHtmlName.split('-').first);
currentHtmlName;
if(type == 0){
findIndex--;
if(findIndex <0){
Toast.show('前面已没有章节');
// 已到最前
return '-1';
return ;
}
}
else{
findIndex++;
if(findIndex >files.length -1){
// 已到最后
return '1';
return ;
}
}
String toReadHtmlPath = '';
// 打印所有 HTML 文件路径
for (var file in files) {
if (file is File && file.path.toLowerCase().endsWith('.html')) {
String fileName = path.basename(file.path);
if (int.parse(fileName.split('-').first) == 2){
String fileName = path.basenameWithoutExtension(file.path);
if (int.parse(fileName.split('-').first) == findIndex){
print('HTML File--------------------------------${file.path}');
return file.path;
toReadHtmlPath = file.path;
chapterId = fileName.split('-').last;
chapterName = getChapterName(chapterId);
break;
}
}
}
return '0';
// 开始读书
read(toReadHtmlPath);
update();
}
// 读取html内容
......@@ -504,12 +643,30 @@ class ReadController extends FullLifeCycleController with GetSingleTickerProvide
return fileContent;
}
catch (e){
print('Error reading file: $e');
Console.log('Error reading file: $e');
return '';
}
}
// 通过 chapter_id 获取 chapter_name
String getChapterName(String chapterId){
for (ChapterModel model in chapters){
if ('${model.id}' == chapterId){
return model.name??'';
}
if (model.children !=null){
for (ChapterModel subModel in model.children!){
if ('${subModel.id}' == chapterId){
return subModel.name??'';
}
}
}
}
return '';
}
//获取存储目录
Future<String> _getDirectory() async {
// getTemporaryDirectory
......@@ -517,6 +674,8 @@ class ReadController extends FullLifeCycleController with GetSingleTickerProvide
return directory!.path;
}
/// 获取目录信息
void _getChapters() async {
chapters = await LibraryAPI.chapters(bookId: bookId);
......@@ -526,6 +685,7 @@ class ReadController extends FullLifeCycleController with GetSingleTickerProvide
/// 添加阅读时长
void _addReadTime({required type}) async{
final result = await LibraryAPI.addReadTime(bookId: bookId, readTypes: type);
Console.log('-------------$result-------------------');
}
/// 获取离线文件路径
void getBookDown() async{
......
......@@ -18,6 +18,7 @@ import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:flutter_sound/public/flutter_sound_recorder.dart';
import 'package:flutter_sound_platform_interface/flutter_sound_platform_interface.dart';
import 'package:flutter_sound_platform_interface/flutter_sound_recorder_platform_interface.dart';
import 'package:flutter_tts/flutter_tts.dart';
import 'package:get/get.dart';
import 'package:go_router/go_router.dart';
import 'package:intl/intl.dart';
......
......@@ -102,6 +102,7 @@ class _ReadPageState extends State<ReadPage> {
// 监听笔记回调
controller.addJavaScriptHandler(handlerName: 'noteCallBack', callback: (args){
Console.log('监听笔记回调------------------------------------------------$args');
readController.noteTitle = args.first;
readController.setShowChat(true);
readController.setChatType(1);
......@@ -110,16 +111,41 @@ class _ReadPageState extends State<ReadPage> {
// 监听百科回调
controller.addJavaScriptHandler(handlerName: 'baikeCallBack', callback: (args){
Console.log('监听百科回调------------------------------------------------$args');
context.pushNamed(Routes.baiKe,queryParameters: {'keyword':args.first});
});
// 监听字典回调
controller.addJavaScriptHandler(handlerName: 'dicCallBack', callback: (args){
Console.log('监听百科回调------------------------------------------------$args');
context.pushNamed(Routes.baiDict,queryParameters: {'keyword':args.first});
});
// 监听朗读回调
controller.addJavaScriptHandler(handlerName: 'readCallBack', callback: (args){
Console.log('监听朗读回调------------------------------------------------$args');
readController.speak(args.first.toString());
// readController.speak('中国共产党(英文名:the Communist Party of China,简写CPC),'
// '创建于1921年7月23日,1921年中国共产党成立后,确立了新民主主义革命的正确道路,让灾难深重的中'
// '国人民看到了新的希望、有了新的依靠。我们党探索出农村包围城市、武装夺取政权的正确革命道路,“唤起工'
// '农千百万”“夺过鞭子揍敌人”,经过土地革命战争、抗日战争、解放战争,推翻了压在中国人民头上的帝国主义、'
// '封建主义、官僚资本主义“三座大山”,建立了人民当家作主的中华人民共和国,彻底结束了近代以来中国内忧外患、积贫积弱的悲惨境地,开启了中华民族发展进步的新纪元'
// );
});
// 监听讨论回调
controller.addJavaScriptHandler(handlerName: 'discussCallBack', callback: (args){
Console.log('监听讨论回调------------------------------------------------$args');
readController.setShowChat(true);
readController.setChatType(0);
readController.titleInput.text = args.first.toString();
});
// 监听 上一节 下一节
controller.addJavaScriptHandler(handlerName: 'readCallBack', callback: (args){
Console.log('监听 上一节 下一节------------------------------------------------$args');
});
},
),
// AnimatedPositioned(
......
......@@ -45,6 +45,7 @@ import 'package:go_router/go_router.dart';
import '../models/index.dart';
import '../pages/ad_detail/index.dart';
import '../pages/bai_dict/index.dart';
import '../pages/bai_ke/index.dart';
import '../pages/read_web/index.dart';
import '../pages/user_edit_note/index.dart';
......
......@@ -40,7 +40,8 @@ abstract class Routes {
/// 图书馆模块
// 百科
static const baiKe = 'bai_ke';
// 字典
static const baiDict = 'bai_dict';
/// 书架模块
static const creditPoints = 'credit_points';
......@@ -595,6 +596,17 @@ abstract class Routes {
child: AdDetailPage(adModel: state.extra as AdModel,),
)
),
GoRoute( // 字典
path: '/$baiDict',
name: baiDict,
pageBuilder: (context, state) =>CupertinoPage(
name: state.uri.toString(),
key: state.pageKey,
child: BaiDictPage(
keyword: state.uri.queryParameters['keyword'].toString(),
)
)
),
]
);
......
......@@ -32,6 +32,7 @@ class SqlManager {
"types INTEGER, "
"book_id INTEGER, "
"chapter_id INTEGER, "
"node_id INTEGER, "
"is_open INTEGER, "
"color TEXT, "
"content TEXT, "
......@@ -65,15 +66,16 @@ class SqlManager {
}
// 将所有 upload 为 0 的数据的 upload 字段值更新为 1
static Future<void> updateUploadStatus() async {
await _database!.update(
final result = await _database!.update(
'members_book_notes',
{'upload': 1},
where: 'upload = ?',
whereArgs: [0],
);
Console.log('更新数据-------------------------------$result');
}
// 根据 book_id 查询当前读到的 章节
static Future<int> queryDataByBookId(int bookId) async {
static Future<String> queryReadHistoryByBookId(int bookId) async {
List<Map<String, dynamic>> results = await _database!.query(
'read_history',
where: 'book_id = ?',
......@@ -83,9 +85,9 @@ class SqlManager {
}
// 根据 book_id 写入当前读到的 章节
static Future<int?> updateReadHistoryBookId(int bookId ,int chapterId) async {
static Future<int> updateReadHistoryByBookId(int bookId ,int chapterId) async {
final result = await _database?.update(
final result = await _database!.update(
'members_book_notes',
{'chapter_id':chapterId},
where: 'book_id = ?',
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论