提交 39ff5237 authored 作者: 王鹏飞's avatar 王鹏飞

feat: Update splash screen privacy notice handling and add system navigator pop on decline

refactor: Remove TTS related files and controller for cleaner codebase fix: Improve microphone permission request with explanation context style: Clean up user settings view and improve toast utility chore: Update asset picker to streamline permission checks fix: Update SQL manager for better database handling and query efficiency chore: Update constants for user agreements with versioning refactor: General code cleanup and formatting across multiple files
上级 67ef61b0
...@@ -2,10 +2,8 @@ ...@@ -2,10 +2,8 @@
package="com.zijing.book.flutterBook"> package="com.zijing.book.flutterBook">
<!-- INTERNET 权限在开发过程中是必需的。具体来说,Flutter 需要它与正在运行的应用程序进行通信,以允许设置断点、提供热重载等功能--> <!-- INTERNET 权限在开发过程中是必需的。具体来说,Flutter 需要它与正在运行的应用程序进行通信,以允许设置断点、提供热重载等功能-->
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.INTERNET"/>
<!-- STORAGE 权限是用于 TTS 合成到文件的--> <uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.RECORD_AUDIO" /> <uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<!-- 加入tts服务--> <!-- 加入tts服务-->
<queries> <queries>
<intent> <intent>
......
...@@ -44,8 +44,6 @@ post_install do |installer| ...@@ -44,8 +44,6 @@ post_install do |installer|
'PERMISSION_MICROPHONE=1', 'PERMISSION_MICROPHONE=1',
'PERMISSION_CAMERA=1', 'PERMISSION_CAMERA=1',
'PERMISSION_PHOTOS=1', 'PERMISSION_PHOTOS=1',
'PERMISSION_SPEECH_RECOGNIZER=1',
'PERMISSION_MEDIA_LIBRARY=1',
] ]
end end
end end
......
...@@ -46,16 +46,12 @@ ...@@ -46,16 +46,12 @@
<key>NSAllowsArbitraryLoadsInWebContent</key> <key>NSAllowsArbitraryLoadsInWebContent</key>
<true/> <true/>
</dict> </dict>
<key>NSAppleMusicUsageDescription</key>
<string>清控紫荆数智学堂需要访问媒体</string>
<key>NSCameraUsageDescription</key> <key>NSCameraUsageDescription</key>
<string>清控紫荆数智学堂需要访问相机</string> <string>清控紫荆数智学堂需要访问相机</string>
<key>NSMicrophoneUsageDescription</key> <key>NSMicrophoneUsageDescription</key>
<string>清控紫荆数智学堂需要访问麦克风</string> <string>清控紫荆数智学堂需要访问麦克风</string>
<key>NSPhotoLibraryUsageDescription</key> <key>NSPhotoLibraryUsageDescription</key>
<string>清控紫荆数智学堂需要访问照片</string> <string>清控紫荆数智学堂需要访问照片</string>
<key>NSSpeechRecognitionUsageDescription</key>
<string>清控紫荆数智学堂需要语言识别</string>
<key>UIApplicationSupportsIndirectInputEvents</key> <key>UIApplicationSupportsIndirectInputEvents</key>
<true/> <true/>
<key>UILaunchStoryboardName</key> <key>UILaunchStoryboardName</key>
......
...@@ -20,12 +20,9 @@ import '../../store/index.dart'; ...@@ -20,12 +20,9 @@ import '../../store/index.dart';
import '../../widgets/index.dart'; import '../../widgets/index.dart';
import '../course/index.dart'; import '../course/index.dart';
part 'view.dart'; part 'view.dart';
part 'controller.dart'; part 'controller.dart';
part 'widgets/cell.dart'; part 'widgets/cell.dart';
part 'widgets/content.dart'; part 'widgets/content.dart';
part 'widgets/subject.dart'; part 'widgets/subject.dart';
part 'widgets/filter.dart'; part 'widgets/filter.dart';
part 'test.dart';
part of library;
class TestPage extends StatefulWidget {
const TestPage({Key? key}) : super(key: key);
@override
State<LibraryPage> createState() => _LibraryPageState();
}
class _TestPageState extends State<TestPage> with AutomaticKeepAliveClientMixin {
//AutomaticKeepAliveClientMixin
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('分类'),
bottom: PreferredSize(
preferredSize: Size.fromHeight(48.w),
child: Row(
children: [
// TabBar放在左侧
const Expanded(
child: TabBar(
tabs: [
Tab(text: 'Tab 1'),
Tab(text: 'Tab 2'),
Tab(text: 'Tab 3'),
Tab(text: 'Tab 4'),
Tab(text: 'Tab 5'),
],
),
),
// 筛选按钮放在右侧
IconButton(
icon: const Icon(Icons.filter_list),
onPressed: () {
// 处理筛选按钮点击事件
},
),
],
),
)
),
body: Column(
children: [
Container(
height: 43, // 设置标签栏的高度
color: Colors.cyan, // 设置标签栏的颜色
// child: // 添加你的横向滑动标签组件
),
Expanded(
child: CustomScrollView(
slivers: [
// // 横向滑动的标签
// SliverToBoxAdapter(
// child: Container(
// height: 43, // 设置标签栏的高度
// color: Colors.grey, // 设置标签栏的颜色
// // child: // 添加你的横向滑动标签组件
// ),
// ),
// 广告位
SliverToBoxAdapter(
child: Container(
height: 100, // 设置广告位的高度
color: Colors.grey, // 设置广告位的颜色
)
),
SliverFillRemaining(
child: TabBarView(
children: [
// Tab 1 对应的内容
Container(
color: Colors.red,
),
// Tab 2 对应的内容
Container(
color: Colors.green,
),
// Tab 3 对应的内容
Container(
color: Colors.blue,
),
Container(
color: Colors.blue,
),
Container(
color: Colors.blue,
),
],
),
),
],
),
),
],
),
);
}
@override
bool get wantKeepAlive => true;
}
part of web; part of web;
class ReadController extends FullLifeCycleController with GetSingleTickerProviderStateMixin{ class ReadController extends FullLifeCycleController
with GetSingleTickerProviderStateMixin {
// 图书id // 图书id
final String bookId; final String bookId;
// 章节id // 章节id
...@@ -11,15 +12,35 @@ class ReadController extends FullLifeCycleController with GetSingleTickerProvide ...@@ -11,15 +12,35 @@ class ReadController extends FullLifeCycleController with GetSingleTickerProvide
final BookDetailModel bookDetailModel; final BookDetailModel bookDetailModel;
// 笔记id 用于我的笔记跳转阅读页对应位置 // 笔记id 用于我的笔记跳转阅读页对应位置
final String noteId; final String noteId;
ReadController({required this.bookId, required this.chapterId,required this.chapterName,required this.bookDetailModel,required this.noteId}); ReadController(
{required this.bookId,
required this.chapterId,
required this.chapterName,
required this.bookDetailModel,
required this.noteId});
late InAppWebViewController webViewController; late InAppWebViewController webViewController;
// 目录 // 目录
List <ChapterModel> chapters = []; List<ChapterModel> chapters = [];
// 工具栏数组 // 工具栏数组
List <ToolModel> tools = [ List<ToolModel> tools = [
ToolModel(tag: 0,name: '目录',selected: false,icon: 'assets/images/category_unselect.png',activeIcon: 'assets/images/category_select.png'), ToolModel(
ToolModel(tag: 1,name: '笔记',selected: false,icon: 'assets/images/note_unselect.png',activeIcon:'assets/images/note_select.png'), tag: 0,
ToolModel(tag: 2,name: '讨论',selected: false,icon: 'assets/images/discuss_unselect.png',activeIcon:'assets/images/discuss_select.png'), name: '目录',
selected: false,
icon: 'assets/images/category_unselect.png',
activeIcon: 'assets/images/category_select.png'),
ToolModel(
tag: 1,
name: '笔记',
selected: false,
icon: 'assets/images/note_unselect.png',
activeIcon: 'assets/images/note_select.png'),
ToolModel(
tag: 2,
name: '讨论',
selected: false,
icon: 'assets/images/discuss_unselect.png',
activeIcon: 'assets/images/discuss_select.png'),
]; ];
// 默认工具栏选项 // 默认工具栏选项
late ToolModel toolModel = tools.first; late ToolModel toolModel = tools.first;
...@@ -41,19 +62,20 @@ class ReadController extends FullLifeCycleController with GetSingleTickerProvide ...@@ -41,19 +62,20 @@ class ReadController extends FullLifeCycleController with GetSingleTickerProvide
final TextEditingController contentInput = TextEditingController(); final TextEditingController contentInput = TextEditingController();
// //
// 讨论添加的图片path数组 // 讨论添加的图片path数组
List <String> discussInputImages= []; List<String> discussInputImages = [];
// 讨论添加的语音path数组 // 讨论添加的语音path数组
List <AudioModel> discussInputAudios= []; List<AudioModel> discussInputAudios = [];
// 笔记标题 // 笔记标题
String noteTitle = ''; String noteTitle = '';
bool _show = false; bool _show = false;
bool get show => _show; bool get show => _show;
// 录音 // 录音
final FlutterSoundRecorder _mRecorder = FlutterSoundRecorder(logLevel:Level.error); final FlutterSoundRecorder _mRecorder =
FlutterSoundRecorder(logLevel: Level.error);
// 录音开始 // 录音开始
bool startRecording = false; bool startRecording = false;
// 是否存在离线文件 // 是否存在离线文件
bool existDownFile= false; bool existDownFile = false;
// 网络状态 // 网络状态
bool netStatus = false; bool netStatus = false;
// 当前html名称 0-318.html // 当前html名称 0-318.html
...@@ -82,7 +104,6 @@ class ReadController extends FullLifeCycleController with GetSingleTickerProvide ...@@ -82,7 +104,6 @@ class ReadController extends FullLifeCycleController with GetSingleTickerProvide
// 本地文件地址 // 本地文件地址
String localHtml5Path = ''; String localHtml5Path = '';
///------------------------------------------ 页面 生命周期-------------------------------------------------------- ///------------------------------------------ 页面 生命周期--------------------------------------------------------
@override @override
void onInit() async { void onInit() async {
...@@ -102,7 +123,7 @@ class ReadController extends FullLifeCycleController with GetSingleTickerProvide ...@@ -102,7 +123,7 @@ class ReadController extends FullLifeCycleController with GetSingleTickerProvide
_getChapters(); _getChapters();
netStatus = await Tools.checkCurrentNetStatus(); netStatus = await Tools.checkCurrentNetStatus();
if(existDownFile && !netStatus){ if (existDownFile && !netStatus) {
webViewController.loadFile(assetFilePath: 'assets/html/read_unline.html'); webViewController.loadFile(assetFilePath: 'assets/html/read_unline.html');
} }
...@@ -125,10 +146,11 @@ class ReadController extends FullLifeCycleController with GetSingleTickerProvide ...@@ -125,10 +146,11 @@ class ReadController extends FullLifeCycleController with GetSingleTickerProvide
// await ScreenProtector.preventScreenshotOff(); // await ScreenProtector.preventScreenshotOff();
super.onClose(); super.onClose();
} }
///------------------------------------------ 页面 生命周期-------------------------------------------------------- ///------------------------------------------ 页面 生命周期--------------------------------------------------------
/// 选择了底部工具栏 /// 选择了底部工具栏
void chooseTool(ToolModel selectedModel){ void chooseTool(ToolModel selectedModel) {
for (var model in tools) { for (var model in tools) {
// 如果当前遍历到的工具是选中的,并且不是点击的工具,则取消选中 // 如果当前遍历到的工具是选中的,并且不是点击的工具,则取消选中
if (model.selected && model != selectedModel) { if (model.selected && model != selectedModel) {
...@@ -159,8 +181,8 @@ class ReadController extends FullLifeCycleController with GetSingleTickerProvide ...@@ -159,8 +181,8 @@ class ReadController extends FullLifeCycleController with GetSingleTickerProvide
} }
/// 是否显示搜索组件 /// 是否显示搜索组件
void setShowSearch(bool show){ void setShowSearch(bool show) {
if(show == false){ if (show == false) {
searchInput.text = ''; searchInput.text = '';
} }
showSearch = show; showSearch = show;
...@@ -169,7 +191,7 @@ class ReadController extends FullLifeCycleController with GetSingleTickerProvide ...@@ -169,7 +191,7 @@ class ReadController extends FullLifeCycleController with GetSingleTickerProvide
/// 选择了某个章节 /// 选择了某个章节
void selectChapter(ChapterModel model) { void selectChapter(ChapterModel model) {
chapterName = model.name??''; chapterName = model.name ?? '';
chapterId = model.id.toString(); chapterId = model.id.toString();
writeCurrentReadChapterIdToData(chapters); writeCurrentReadChapterIdToData(chapters);
update(); update();
...@@ -186,7 +208,6 @@ class ReadController extends FullLifeCycleController with GetSingleTickerProvide ...@@ -186,7 +208,6 @@ class ReadController extends FullLifeCycleController with GetSingleTickerProvide
flutterTts.setStartHandler(() { flutterTts.setStartHandler(() {
Console.log('-------------Playing-------------------'); Console.log('-------------Playing-------------------');
}); });
if (Platform.isAndroid) { if (Platform.isAndroid) {
...@@ -214,7 +235,6 @@ class ReadController extends FullLifeCycleController with GetSingleTickerProvide ...@@ -214,7 +235,6 @@ class ReadController extends FullLifeCycleController with GetSingleTickerProvide
flutterTts.setErrorHandler((msg) { flutterTts.setErrorHandler((msg) {
Console.log('-------------TTS error-------------------$msg'); Console.log('-------------TTS error-------------------$msg');
}); });
} }
/// 设置参数 /// 设置参数
...@@ -251,8 +271,11 @@ class ReadController extends FullLifeCycleController with GetSingleTickerProvide ...@@ -251,8 +271,11 @@ class ReadController extends FullLifeCycleController with GetSingleTickerProvide
/// 初始化录音组件 /// 初始化录音组件
Future<void> openTheRecorder() async { Future<void> openTheRecorder() async {
var status = await Access.microphone(
var status = await Access.microphone(); showExplanation: true,
purpose: '用于录制语音笔记,仅用于创建和播放您的录音内容。',
context: Get.context,
);
if (status == false) { if (status == false) {
Toast.show('录音权限没有开启无法使用该功能'); Toast.show('录音权限没有开启无法使用该功能');
return; return;
...@@ -290,7 +313,6 @@ class ReadController extends FullLifeCycleController with GetSingleTickerProvide ...@@ -290,7 +313,6 @@ class ReadController extends FullLifeCycleController with GetSingleTickerProvide
toFile: recordePath, toFile: recordePath,
audioSource: AudioSource.microphone, audioSource: AudioSource.microphone,
codec: Codec.aacMP4, codec: Codec.aacMP4,
); );
// _mRecorder?.setSubscriptionDuration(Duration(milliseconds: 100)); // _mRecorder?.setSubscriptionDuration(Duration(milliseconds: 100));
// _mRecorder?.onProgress?.listen((e) // _mRecorder?.onProgress?.listen((e)
...@@ -304,38 +326,40 @@ class ReadController extends FullLifeCycleController with GetSingleTickerProvide ...@@ -304,38 +326,40 @@ class ReadController extends FullLifeCycleController with GetSingleTickerProvide
} }
/// 停止录音 /// 停止录音
void stopRecorder() async{ void stopRecorder() async {
startRecording = false; startRecording = false;
final path = await _mRecorder.stopRecorder(); final path = await _mRecorder.stopRecorder();
Console.log('stopRecorder-----------path---------------------$path'); Console.log('stopRecorder-----------path---------------------$path');
if(path!=null && path.isNotEmpty){ if (path != null && path.isNotEmpty) {
var duration = await audioPlayer.setFilePath(path); var duration = await audioPlayer.setFilePath(path);
Console.log('-----duration---------------------$duration------'); Console.log('-----duration---------------------$duration------');
AudioModel audioModel = AudioModel(path: path,duration: Tools.formatDuration(duration!),currentDuration: '0:00:00'); AudioModel audioModel = AudioModel(
if(audioModel.duration !='0:00:00'){ path: path,
duration: Tools.formatDuration(duration!),
currentDuration: '0:00:00');
if (audioModel.duration != '0:00:00') {
discussInputAudios.add(audioModel); discussInputAudios.add(audioModel);
} }
} }
update(); update();
} }
/// 语音文件名称 /// 语音文件名称
String generateVoiceFileName(){ String generateVoiceFileName() {
DateTime now = DateTime.now(); DateTime now = DateTime.now();
String formattedDate = DateFormat('yyyyMMddHHmmss').format(now); String formattedDate = DateFormat('yyyyMMddHHmmss').format(now);
return 'voice_$formattedDate.mp4'; return 'voice_$formattedDate.mp4';
} }
/// 重置所有信息 /// 重置所有信息
void reset(){ void reset() {
clearAllDiscussInput(); clearAllDiscussInput();
} }
/// 播放音频 /// 播放音频
void playAudio(AudioModel audioModel){ void playAudio(AudioModel audioModel) {
Console.log('-------------播放开始-------------------'); Console.log('-------------播放开始-------------------');
if(audioPlayer.playerState.playing){ if (audioPlayer.playerState.playing) {
audioPlayer.stop(); audioPlayer.stop();
audioModel.currentDuration = '0:00:00'; audioModel.currentDuration = '0:00:00';
} }
...@@ -343,11 +367,12 @@ class ReadController extends FullLifeCycleController with GetSingleTickerProvide ...@@ -343,11 +367,12 @@ class ReadController extends FullLifeCycleController with GetSingleTickerProvide
audioPlayer.setFilePath(audioModel.path); audioPlayer.setFilePath(audioModel.path);
audioPlayer.play(); audioPlayer.play();
StreamSubscription? positionSubscription; StreamSubscription? positionSubscription;
positionSubscription =audioPlayer.positionStream.listen((position) { positionSubscription = audioPlayer.positionStream.listen((position) {
String temp = Tools.formatDuration(position); String temp = Tools.formatDuration(position);
Console.log('播放时间---------------------$temp------id-------------${audioModel.path}'); Console.log(
'播放时间---------------------$temp------id-------------${audioModel.path}');
audioModel.currentDuration = temp; audioModel.currentDuration = temp;
if(position >= audioPlayer.duration!){ if (position >= audioPlayer.duration!) {
Console.log('---------播放结束-----------'); Console.log('---------播放结束-----------');
positionSubscription?.cancel(); positionSubscription?.cancel();
} }
...@@ -357,35 +382,35 @@ class ReadController extends FullLifeCycleController with GetSingleTickerProvide ...@@ -357,35 +382,35 @@ class ReadController extends FullLifeCycleController with GetSingleTickerProvide
} }
/// 添加讨论图片 /// 添加讨论图片
void addDiscussInputImages(String path){ void addDiscussInputImages(String path) {
discussInputImages.add(path); discussInputImages.add(path);
Console.log('discussInputImages--------------------------------$path'); Console.log('discussInputImages--------------------------------$path');
update(); update();
} }
/// 删除讨论图片 /// 删除讨论图片
void delDiscussInputImages(String path){ void delDiscussInputImages(String path) {
discussInputImages.remove(path); discussInputImages.remove(path);
Console.log('delDiscussInputImages--------------------------------$path'); Console.log('delDiscussInputImages--------------------------------$path');
update(); update();
} }
/// 清空讨论图片 /// 清空讨论图片
void clearDiscussInputImages(){ void clearDiscussInputImages() {
discussInputImages.clear(); discussInputImages.clear();
Console.log('clearDiscussInputImages--------------------------------'); Console.log('clearDiscussInputImages--------------------------------');
update(); update();
} }
/// 情况语音 /// 情况语音
void clearDiscussAudios(){ void clearDiscussAudios() {
discussInputAudios.clear(); discussInputAudios.clear();
Console.log('clearDiscussAudios--------------------------------'); Console.log('clearDiscussAudios--------------------------------');
update(); update();
} }
/// 清空所有已经填写的数据 /// 清空所有已经填写的数据
void clearAllDiscussInput(){ void clearAllDiscussInput() {
clearDiscussInputImages(); clearDiscussInputImages();
clearDiscussAudios(); clearDiscussAudios();
titleInput.text = ''; titleInput.text = '';
...@@ -397,15 +422,13 @@ class ReadController extends FullLifeCycleController with GetSingleTickerProvide ...@@ -397,15 +422,13 @@ class ReadController extends FullLifeCycleController with GetSingleTickerProvide
} }
/// 删除音频 /// 删除音频
void delAudio(AudioModel audioModel){ void delAudio(AudioModel audioModel) {
discussInputAudios.remove(audioModel); discussInputAudios.remove(audioModel);
update(); update();
} }
/// 上传文件 /// 上传文件
Future<String> upload({ Future<String> upload({required String path}) async {
required String path
}) async {
// String result = await CommonAPI.upload(path:path,fileTypes: 'comment'); // String result = await CommonAPI.upload(path:path,fileTypes: 'comment');
// return result; // return result;
OssTool tool = OssTool('zxts-comment-file'); OssTool tool = OssTool('zxts-comment-file');
...@@ -423,58 +446,59 @@ class ReadController extends FullLifeCycleController with GetSingleTickerProvide ...@@ -423,58 +446,59 @@ class ReadController extends FullLifeCycleController with GetSingleTickerProvide
final status = await Tools.checkCurrentNetStatus(); final status = await Tools.checkCurrentNetStatus();
if(chatType == 0){ if (chatType == 0) {
if(contentInput.text.isEmpty && discussInputImages.isEmpty){ if (contentInput.text.isEmpty && discussInputImages.isEmpty) {
Toast.show('话题必须填写内容或选择图片'); Toast.show('话题必须填写内容或选择图片');
return false; return false;
} }
} } else if (chatType == 1) {
else if(chatType == 1){ if (contentInput.text.isEmpty &&
if(contentInput.text.isEmpty && discussInputImages.isEmpty && discussInputAudios.isEmpty){ discussInputImages.isEmpty &&
discussInputAudios.isEmpty) {
Toast.show('笔记必须填写内容或选择图片或音频'); Toast.show('笔记必须填写内容或选择图片或音频');
return false; return false;
} }
} }
// 有网情况下 先直传oss 获取到url // 有网情况下 先直传oss 获取到url
if (status){ if (status) {
CustomToast.loading(); CustomToast.loading();
// 循环上传图片获取地址 // 循环上传图片获取地址
for(String path in discussInputImages){ for (String path in discussInputImages) {
final url = await upload(path: path); final url = await upload(path: path);
images.add(url); images.add(url);
} }
// 循环上传音频获取地址 // 循环上传音频获取地址
for(AudioModel model in discussInputAudios){ for (AudioModel model in discussInputAudios) {
final url = await upload(path: model.path); final url = await upload(path: model.path);
audios.add(url); audios.add(url);
} }
CustomToast.dismiss(); CustomToast.dismiss();
} }
// 没有网的情况下 存储到本地数据库 // 没有网的情况下 存储到本地数据库
else{ else {
for(String path in discussInputImages){ for (String path in discussInputImages) {
images.add(path); images.add(path);
} }
// 循环上传音频获取地址 // 循环上传音频获取地址
for(AudioModel model in discussInputAudios){ for (AudioModel model in discussInputAudios) {
audios.add(model.path); audios.add(model.path);
} }
} }
Map<String,dynamic> contentMap = { Map<String, dynamic> contentMap = {
'text':contentInput.text, 'text': contentInput.text,
'audio':audios, 'audio': audios,
'image':images 'image': images
}; };
// 话题 只有有网的时候才能发起话题 // 话题 只有有网的时候才能发起话题
if (chatType == 0){ if (chatType == 0) {
final result = await addDiscuss(contentMap); final result = await addDiscuss(contentMap);
return result; return result;
} }
// 笔记 有网没有网都能发起笔记 // 笔记 有网没有网都能发起笔记
else if (chatType == 1){ else if (chatType == 1) {
final result = addNote(contentMap); final result = addNote(contentMap);
return result; return result;
} }
...@@ -482,27 +506,26 @@ class ReadController extends FullLifeCycleController with GetSingleTickerProvide ...@@ -482,27 +506,26 @@ class ReadController extends FullLifeCycleController with GetSingleTickerProvide
} }
/// 添加笔记 /// 添加笔记
Future<bool> addNote(Map<String,dynamic> contentMap) async { Future<bool> addNote(Map<String, dynamic> contentMap) async {
final status = await Tools.checkCurrentNetStatus(); final status = await Tools.checkCurrentNetStatus();
bool result ; bool result;
// 有网直接上传接口 // 有网直接上传接口
if (status){ if (status) {
result = await LibraryAPI.addNote( result = await LibraryAPI.addNote(
bookId: bookId, bookId: bookId,
chapterId: chapterId, chapterId: chapterId,
content: noteTitle, content: noteTitle,
isOpen: isPublic?'1':'0', isOpen: isPublic ? '1' : '0',
positioning: notePosition, positioning: notePosition,
noteContent: jsonEncode(contentMap) noteContent: jsonEncode(contentMap));
);
} }
// 没有网存储本地数据库 // 没有网存储本地数据库
else{ else {
Map<String, dynamic> data = { Map<String, dynamic> data = {
'types': 3, 'types': 3,
'book_id': int.parse(bookId), 'book_id': int.parse(bookId),
'chapter_id': int.parse(chapterId), 'chapter_id': int.parse(chapterId),
'is_open': isPublic?1:0, 'is_open': isPublic ? 1 : 0,
'color': '#FF0000', 'color': '#FF0000',
'content': noteTitle, 'content': noteTitle,
'upload': 0, 'upload': 0,
...@@ -513,10 +536,9 @@ class ReadController extends FullLifeCycleController with GetSingleTickerProvide ...@@ -513,10 +536,9 @@ class ReadController extends FullLifeCycleController with GetSingleTickerProvide
result = await SqlManager.insertData(data); result = await SqlManager.insertData(data);
} }
if(result){ if (result) {
Toast.show('笔记发表成功'); Toast.show('笔记发表成功');
} } else {
else{
Toast.show('笔记发表失败'); Toast.show('笔记发表失败');
} }
// 重置所有信息 // 重置所有信息
...@@ -527,28 +549,24 @@ class ReadController extends FullLifeCycleController with GetSingleTickerProvide ...@@ -527,28 +549,24 @@ class ReadController extends FullLifeCycleController with GetSingleTickerProvide
/// 发表评论 /// 发表评论
// {String commentId ='0',String quoteContent ='',String title = ''} // {String commentId ='0',String quoteContent ='',String title = ''}
Future<bool> addDiscuss(Map<String,dynamic> contentMap) async{ Future<bool> addDiscuss(Map<String, dynamic> contentMap) async {
final result = await LibraryAPI.addDiscuss( final result = await LibraryAPI.addDiscuss(
bookId: bookId, bookId: bookId,
chapterId: chapterId, chapterId: chapterId,
commentId: '0', commentId: '0',
quoteContent: noteTitle, quoteContent: noteTitle,
title: titleInput.text, title: titleInput.text,
content: jsonEncode(contentMap) content: jsonEncode(contentMap));
); if (result.isNotEmpty) {
if(result.isNotEmpty){
Toast.show('话题发表成功'); Toast.show('话题发表成功');
} } else {
else{
Toast.show('话题发表失败'); Toast.show('话题发表失败');
} }
// 重置所有信息 // 重置所有信息
reset(); reset();
setShowChat(false); setShowChat(false);
return result.isNotEmpty?true:false; return result.isNotEmpty ? true : false;
} }
/// 焦点变化 /// 焦点变化
...@@ -563,19 +581,19 @@ class ReadController extends FullLifeCycleController with GetSingleTickerProvide ...@@ -563,19 +581,19 @@ class ReadController extends FullLifeCycleController with GetSingleTickerProvide
/// 展示输入框 /// 展示输入框
void setShowChat(bool value) { void setShowChat(bool value) {
showChat = value; showChat = value;
if(value == false){ if (value == false) {
reset(); reset();
} }
update(); update();
} }
/// 显示输入框类型 /// 显示输入框类型
void setChatType(int type){ void setChatType(int type) {
chatType = type; chatType = type;
} }
/// 设置笔记是否公开 /// 设置笔记是否公开
void setIsPublic(){ void setIsPublic() {
isPublic = !isPublic; isPublic = !isPublic;
update(); update();
} }
...@@ -586,7 +604,6 @@ class ReadController extends FullLifeCycleController with GetSingleTickerProvide ...@@ -586,7 +604,6 @@ class ReadController extends FullLifeCycleController with GetSingleTickerProvide
Future<void> extractZipFileFromCache(String url) async { Future<void> extractZipFileFromCache(String url) async {
// 从缓存中获取 ZIP 文件 // 从缓存中获取 ZIP 文件
var file = await DefaultCacheManager().getSingleFile(url); var file = await DefaultCacheManager().getSingleFile(url);
if (file != null) {
// 读取 ZIP 文件内容 // 读取 ZIP 文件内容
Uint8List bytes = await file.readAsBytes(); Uint8List bytes = await file.readAsBytes();
// 解压缩 ZIP 文件 // 解压缩 ZIP 文件
...@@ -608,10 +625,6 @@ class ReadController extends FullLifeCycleController with GetSingleTickerProvide ...@@ -608,10 +625,6 @@ class ReadController extends FullLifeCycleController with GetSingleTickerProvide
Toast.show('离线成功'); Toast.show('离线成功');
await _isExistFile(bookId); await _isExistFile(bookId);
update(); update();
} else {
Console.log('未找到缓存中的文件或文件不存在');
}
} }
/// 判断是否存在离线文件 /// 判断是否存在离线文件
...@@ -622,19 +635,16 @@ class ReadController extends FullLifeCycleController with GetSingleTickerProvide ...@@ -622,19 +635,16 @@ class ReadController extends FullLifeCycleController with GetSingleTickerProvide
if (directoryExists) { if (directoryExists) {
Console.log('存在名为 "$bookId" 的文件夹'); Console.log('存在名为 "$bookId" 的文件夹');
existDownFile = await Directory('${directory.path}/$bookId').exists(); existDownFile = await Directory('${directory.path}/$bookId').exists();
} else {
}
else {
Console.log('不存在名为 "$bookId" 的文件夹'); Console.log('不存在名为 "$bookId" 的文件夹');
existDownFile = false; existDownFile = false;
} }
update(); update();
return existDownFile; return existDownFile;
} }
/// 获取对应chapterId文件路径 /// 获取对应chapterId文件路径
Future<String> getLocalReadHtml(String chapterId) async{ Future<String> getLocalReadHtml(String chapterId) async {
String docPath = await Tools.getDirectory(); String docPath = await Tools.getDirectory();
String filePath = '$docPath/$bookId'; String filePath = '$docPath/$bookId';
Directory directory = Directory(filePath); Directory directory = Directory(filePath);
...@@ -643,7 +653,7 @@ class ReadController extends FullLifeCycleController with GetSingleTickerProvide ...@@ -643,7 +653,7 @@ class ReadController extends FullLifeCycleController with GetSingleTickerProvide
for (var file in files) { for (var file in files) {
if (file is File && file.path.toLowerCase().endsWith('.html')) { if (file is File && file.path.toLowerCase().endsWith('.html')) {
String fileName = path.basenameWithoutExtension(file.path); String fileName = path.basenameWithoutExtension(file.path);
if (fileName.split('-').last == chapterId){ if (fileName.split('-').last == chapterId) {
return file.path; return file.path;
} }
} }
...@@ -653,7 +663,8 @@ class ReadController extends FullLifeCycleController with GetSingleTickerProvide ...@@ -653,7 +663,8 @@ class ReadController extends FullLifeCycleController with GetSingleTickerProvide
/// 获取离线数据 /// 获取离线数据
void getOffLineInfo({String direction = ''}) async { void getOffLineInfo({String direction = ''}) async {
Console.log('-chapterId----------------------$chapterId-----------------------------------------'); Console.log(
'-chapterId----------------------$chapterId-----------------------------------------');
Map<String, dynamic> data = {}; Map<String, dynamic> data = {};
data['chapter_name'] = chapterName; data['chapter_name'] = chapterName;
// 1、根据当前章节id名称 获取内容 // 1、根据当前章节id名称 获取内容
...@@ -667,10 +678,9 @@ class ReadController extends FullLifeCycleController with GetSingleTickerProvide ...@@ -667,10 +678,9 @@ class ReadController extends FullLifeCycleController with GetSingleTickerProvide
// 4、获取上一章节信息 // 4、获取上一章节信息
Map<String, dynamic> upChapter = {}; Map<String, dynamic> upChapter = {};
final upId = await getChapterId(type: 0); final upId = await getChapterId(type: 0);
if(upId.isEmpty) { if (upId.isEmpty) {
data['up_chapter'] = ''; data['up_chapter'] = '';
} } else {
else{
String upName = getChapterName(upId); String upName = getChapterName(upId);
upChapter['name'] = upName; upChapter['name'] = upName;
upChapter['id'] = int.parse(upId); upChapter['id'] = int.parse(upId);
...@@ -679,15 +689,13 @@ class ReadController extends FullLifeCycleController with GetSingleTickerProvide ...@@ -679,15 +689,13 @@ class ReadController extends FullLifeCycleController with GetSingleTickerProvide
String deCodeContent = EncryptUtil.aesDecrypt(enCodeContent!); String deCodeContent = EncryptUtil.aesDecrypt(enCodeContent!);
upChapter['contents'] = deCodeContent; upChapter['contents'] = deCodeContent;
data['up_chapter'] = upChapter; data['up_chapter'] = upChapter;
} }
// 5、获取下一章节信息 // 5、获取下一章节信息
Map<String, dynamic> nextChapter = {}; Map<String, dynamic> nextChapter = {};
final nextId = await getChapterId(type: 1); final nextId = await getChapterId(type: 1);
if(nextId.isEmpty) { if (nextId.isEmpty) {
data['next_chapter'] = ''; data['next_chapter'] = '';
} } else {
else{
String nextName = getChapterName(nextId); String nextName = getChapterName(nextId);
nextChapter['name'] = nextName; nextChapter['name'] = nextName;
nextChapter['id'] = int.parse(nextId); nextChapter['id'] = int.parse(nextId);
...@@ -704,16 +712,19 @@ class ReadController extends FullLifeCycleController with GetSingleTickerProvide ...@@ -704,16 +712,19 @@ class ReadController extends FullLifeCycleController with GetSingleTickerProvide
// 7、 给前端参数 // 7、 给前端参数
String jsonStr = jsonEncode(data); String jsonStr = jsonEncode(data);
Console.log('callbackInFlutterComponent--------------------------------$jsonStr'); Console.log(
webViewController.evaluateJavascript(source: 'callbackInFlutterComponent($jsonStr)'); 'callbackInFlutterComponent--------------------------------$jsonStr');
webViewController.evaluateJavascript(
final result = await SqlManager.updateReadHistoryByBookId(int.parse(bookId), int.parse(chapterId)); source: 'callbackInFlutterComponent($jsonStr)');
Console.log('Sql----readread---存入数据库读到的章节----------------book_id:$bookId-----chapterId:$chapterId---------result:$result--');
final result = await SqlManager.updateReadHistoryByBookId(
int.parse(bookId), int.parse(chapterId));
Console.log(
'Sql----readread---存入数据库读到的章节----------------book_id:$bookId-----chapterId:$chapterId---------result:$result--');
} }
/// 获取上一章节或下一章节 id /// 获取上一章节或下一章节 id
Future<String> getChapterId({required int type}) async{ Future<String> getChapterId({required int type}) async {
String docPath = await Tools.getDirectory(); String docPath = await Tools.getDirectory();
String filePath = '$docPath/$bookId'; String filePath = '$docPath/$bookId';
Directory directory = Directory(filePath); Directory directory = Directory(filePath);
...@@ -721,17 +732,16 @@ class ReadController extends FullLifeCycleController with GetSingleTickerProvide ...@@ -721,17 +732,16 @@ class ReadController extends FullLifeCycleController with GetSingleTickerProvide
List<FileSystemEntity> files = directory.listSync(recursive: true); List<FileSystemEntity> files = directory.listSync(recursive: true);
int findIndex = int.parse(currentHtmlName.split('-').first); int findIndex = int.parse(currentHtmlName.split('-').first);
if(type == 0){ if (type == 0) {
findIndex--; findIndex--;
if(findIndex <0){ if (findIndex < 0) {
// Toast.show('前面已没有章节'); // Toast.show('前面已没有章节');
// 已到最前 // 已到最前
return ''; return '';
} }
} } else {
else{
findIndex++; findIndex++;
if(findIndex >files.length -1){ if (findIndex > files.length - 1) {
// 已到最后 // 已到最后
return ''; return '';
} }
...@@ -740,7 +750,7 @@ class ReadController extends FullLifeCycleController with GetSingleTickerProvide ...@@ -740,7 +750,7 @@ class ReadController extends FullLifeCycleController with GetSingleTickerProvide
for (var file in files) { for (var file in files) {
if (file is File && file.path.toLowerCase().endsWith('.html')) { if (file is File && file.path.toLowerCase().endsWith('.html')) {
String fileName = path.basenameWithoutExtension(file.path); String fileName = path.basenameWithoutExtension(file.path);
if (int.parse(fileName.split('-').first) == findIndex){ if (int.parse(fileName.split('-').first) == findIndex) {
Console.log('HTML File--------------------------------${file.path}'); Console.log('HTML File--------------------------------${file.path}');
String chapterId = fileName.split('-').last; String chapterId = fileName.split('-').last;
return chapterId; return chapterId;
...@@ -757,23 +767,22 @@ class ReadController extends FullLifeCycleController with GetSingleTickerProvide ...@@ -757,23 +767,22 @@ class ReadController extends FullLifeCycleController with GetSingleTickerProvide
File htmlFile = File(filePath); File htmlFile = File(filePath);
String fileContent = await htmlFile.readAsString(); String fileContent = await htmlFile.readAsString();
return fileContent; return fileContent;
} } catch (e) {
catch (e){
Console.log('Error reading file: $e'); Console.log('Error reading file: $e');
return ''; return '';
} }
} }
/// 通过 chapter_id 获取 chapter_name /// 通过 chapter_id 获取 chapter_name
String getChapterName(String chapterId){ String getChapterName(String chapterId) {
for (ChapterModel model in chapters){ for (ChapterModel model in chapters) {
if ('${model.id}' == chapterId){ if ('${model.id}' == chapterId) {
return model.name??''; return model.name ?? '';
} }
if (model.children !=null){ if (model.children != null) {
for (ChapterModel subModel in model.children!){ for (ChapterModel subModel in model.children!) {
if ('${subModel.id}' == chapterId){ if ('${subModel.id}' == chapterId) {
return subModel.name??''; return subModel.name ?? '';
} }
} }
} }
...@@ -782,14 +791,16 @@ class ReadController extends FullLifeCycleController with GetSingleTickerProvide ...@@ -782,14 +791,16 @@ class ReadController extends FullLifeCycleController with GetSingleTickerProvide
} }
/// 查询本地笔记 /// 查询本地笔记
Future<Map<String,dynamic>> queryNewLocalNote() async { Future<Map<String, dynamic>> queryNewLocalNote() async {
final result = await SqlManager.queryLocalNote(bookId: int.parse(bookId), chapterId: int.parse(chapterId)); final result = await SqlManager.queryLocalNote(
bookId: int.parse(bookId), chapterId: int.parse(chapterId));
return result; return result;
} }
/// 查询本地划线高亮笔记 /// 查询本地划线高亮笔记
void queryLocalNote() async { void queryLocalNote() async {
final result = await SqlManager.queryLocalNote(bookId: int.parse(bookId), chapterId: int.parse(chapterId)); final result = await SqlManager.queryLocalNote(
bookId: int.parse(bookId), chapterId: int.parse(chapterId));
Console.log('前端-----------queryLocalNote---------------------$result'); Console.log('前端-----------queryLocalNote---------------------$result');
// webViewController.evaluateJavascript(source: 'querySuccessCallBack($result)'); // webViewController.evaluateJavascript(source: 'querySuccessCallBack($result)');
} }
...@@ -805,15 +816,19 @@ class ReadController extends FullLifeCycleController with GetSingleTickerProvide ...@@ -805,15 +816,19 @@ class ReadController extends FullLifeCycleController with GetSingleTickerProvide
} }
/// 本地删除划线高亮笔记 /// 本地删除划线高亮笔记
void delLocalNote({required int noteId,required int id}) async { void delLocalNote({required int noteId, required int id}) async {
final result = await SqlManager.delLocalNote(noteId: noteId,id: id); final result = await SqlManager.delLocalNote(noteId: noteId, id: id);
Console.log('前端-----------delLocalNote---------------------$result'); Console.log('前端-----------delLocalNote---------------------$result');
// webViewController.evaluateJavascript(source: 'delSuccessCallBack($result)'); // webViewController.evaluateJavascript(source: 'delSuccessCallBack($result)');
} }
/// 修改本地划线高亮笔记 /// 修改本地划线高亮笔记
void updateLocalNote({required int notesId,required int id,required Map<String, dynamic> data}) async { void updateLocalNote(
final result = await SqlManager.updateLocalNote(notesId: notesId,id: id, data: data); {required int notesId,
required int id,
required Map<String, dynamic> data}) async {
final result =
await SqlManager.updateLocalNote(notesId: notesId, id: id, data: data);
Console.log('前端-----------updateLocalNote---------------------$result'); Console.log('前端-----------updateLocalNote---------------------$result');
// webViewController.evaluateJavascript(source: 'updateSuccessCallBack($result)'); // webViewController.evaluateJavascript(source: 'updateSuccessCallBack($result)');
} }
...@@ -833,30 +848,29 @@ class ReadController extends FullLifeCycleController with GetSingleTickerProvide ...@@ -833,30 +848,29 @@ class ReadController extends FullLifeCycleController with GetSingleTickerProvide
/// 写入当前章节 /// 写入当前章节
void writeCurrentReadChapterIdToData(List<ChapterModel> data) { void writeCurrentReadChapterIdToData(List<ChapterModel> data) {
for(ChapterModel cModel in data){ for (ChapterModel cModel in data) {
cModel.currentRead = false; cModel.currentRead = false;
if(cModel.id == int.parse(chapterId)){ if (cModel.id == int.parse(chapterId)) {
cModel.currentRead = true; cModel.currentRead = true;
cModel.selected = true; cModel.selected = true;
} } else {
else{
writeCurrentReadChapterIdToData(cModel.children!); writeCurrentReadChapterIdToData(cModel.children!);
} }
} }
ChapterModel? tModel = findChapterById(chapters, int.parse(chapterId)); ChapterModel? tModel = findChapterById(chapters, int.parse(chapterId));
if(tModel != null){ if (tModel != null) {
updateParentsStatus(chapters, tModel); updateParentsStatus(chapters, tModel);
} }
} }
/// 查找章节 /// 查找章节
ChapterModel? findChapterById(List<ChapterModel> data,num id){ ChapterModel? findChapterById(List<ChapterModel> data, num id) {
for (ChapterModel cModel in data){ for (ChapterModel cModel in data) {
if(cModel.id == id){ if (cModel.id == id) {
return cModel; return cModel;
} }
ChapterModel? tModel = findChapterById(cModel.children!, id); ChapterModel? tModel = findChapterById(cModel.children!, id);
if(tModel !=null){ if (tModel != null) {
return tModel; return tModel;
} }
} }
...@@ -873,13 +887,13 @@ class ReadController extends FullLifeCycleController with GetSingleTickerProvide ...@@ -873,13 +887,13 @@ class ReadController extends FullLifeCycleController with GetSingleTickerProvide
} }
/// 寻找父节点 /// 寻找父节点
ChapterModel? findParentChapter(List<ChapterModel> data, ChapterModel model){ ChapterModel? findParentChapter(List<ChapterModel> data, ChapterModel model) {
for (ChapterModel tModel in data){ for (ChapterModel tModel in data) {
if(tModel.id == model.pid){ if (tModel.id == model.pid) {
return tModel; return tModel;
} }
ChapterModel? cModel = findParentChapter(tModel.children!, model); ChapterModel? cModel = findParentChapter(tModel.children!, model);
if(cModel != null) { if (cModel != null) {
return cModel; return cModel;
} }
} }
...@@ -887,17 +901,18 @@ class ReadController extends FullLifeCycleController with GetSingleTickerProvide ...@@ -887,17 +901,18 @@ class ReadController extends FullLifeCycleController with GetSingleTickerProvide
} }
/// 添加阅读时长 /// 添加阅读时长
void _addReadTime({required type}) async{ void _addReadTime({required type}) async {
final status = await Tools.checkCurrentNetStatus(); final status = await Tools.checkCurrentNetStatus();
if(status){ if (status) {
LibraryAPI.addReadTime(bookId: bookId, readTypes: type); LibraryAPI.addReadTime(bookId: bookId, readTypes: type);
} }
} }
/// 获取离线文件路径 /// 获取离线文件路径
void getBookDown() async{ void getBookDown() async {
final exit = await _isExistFile(bookId); final exit = await _isExistFile(bookId);
// 存在离线文件 // 存在离线文件
if (!exit){ if (!exit) {
CustomToast.loading(); CustomToast.loading();
final result = await LibraryAPI.getbookDownloadParam(bookId: bookId); final result = await LibraryAPI.getbookDownloadParam(bookId: bookId);
Console.log('----------_getBookDown------------------${result.download}'); Console.log('----------_getBookDown------------------${result.download}');
...@@ -913,16 +928,14 @@ class ReadController extends FullLifeCycleController with GetSingleTickerProvide ...@@ -913,16 +928,14 @@ class ReadController extends FullLifeCycleController with GetSingleTickerProvide
page: _searchPage, page: _searchPage,
limit: _searchLimit, limit: _searchLimit,
bookId: bookId.toString(), bookId: bookId.toString(),
key: searchInput.text key: searchInput.text);
);
Console.log('--------------------------------'); Console.log('--------------------------------');
// 如果是刷新 清理数据 // 如果是刷新 清理数据
if (isRefresh) searchALlResults.clear(); if (isRefresh) searchALlResults.clear();
searchALlResults.addAll(result); searchALlResults.addAll(result);
_searchPage ++; _searchPage++;
_searchNoMore = result.length < _searchLimit; _searchNoMore = result.length < _searchLimit;
update(); update();
} }
/// 刷新搜索数据 /// 刷新搜索数据
...@@ -954,7 +967,7 @@ class ReadController extends FullLifeCycleController with GetSingleTickerProvide ...@@ -954,7 +967,7 @@ class ReadController extends FullLifeCycleController with GetSingleTickerProvide
///------------------------------------------ app 生命周期-------------------------------------------------------- ///------------------------------------------ app 生命周期--------------------------------------------------------
/// 当应用程序从后台切换到前台并变为活动状态时调用。这通常在用户从其他应用程序返回到你的应用程序时发生 /// 当应用程序从后台切换到前台并变为活动状态时调用。这通常在用户从其他应用程序返回到你的应用程序时发生
void onResumed(){ void onResumed() {
Console.log('onResumed'); Console.log('onResumed');
webViewController.evaluateJavascript(source: 'activeState("1");'); webViewController.evaluateJavascript(source: 'activeState("1");');
// open // open
...@@ -963,7 +976,7 @@ class ReadController extends FullLifeCycleController with GetSingleTickerProvide ...@@ -963,7 +976,7 @@ class ReadController extends FullLifeCycleController with GetSingleTickerProvide
} }
/// 当应用程序失去焦点并进入非活动状态时调用。这可能是因为用户切换到其他应用程序或将应用程序最小化 /// 当应用程序失去焦点并进入非活动状态时调用。这可能是因为用户切换到其他应用程序或将应用程序最小化
void onPaused(){ void onPaused() {
// close // close
Console.log('onPaused'); Console.log('onPaused');
webViewController.evaluateJavascript(source: 'activeState("0");'); webViewController.evaluateJavascript(source: 'activeState("0");');
...@@ -972,7 +985,7 @@ class ReadController extends FullLifeCycleController with GetSingleTickerProvide ...@@ -972,7 +985,7 @@ class ReadController extends FullLifeCycleController with GetSingleTickerProvide
} }
/// 当应用程序失去焦点但仍然可见时调用。通常,在用户切换到另一个应用程序或显示系统对话框时,应用程序可能会处于非活动状态,但仍然是可见的 /// 当应用程序失去焦点但仍然可见时调用。通常,在用户切换到另一个应用程序或显示系统对话框时,应用程序可能会处于非活动状态,但仍然是可见的
void onInactive(){ void onInactive() {
Console.log('onInactive'); Console.log('onInactive');
webViewController.evaluateJavascript(source: 'activeState("0");'); webViewController.evaluateJavascript(source: 'activeState("0");');
// close // close
...@@ -981,19 +994,24 @@ class ReadController extends FullLifeCycleController with GetSingleTickerProvide ...@@ -981,19 +994,24 @@ class ReadController extends FullLifeCycleController with GetSingleTickerProvide
} }
/// 当应用程序被挂起,可能是由于用户关闭应用程序或系统资源不足时调用。在这个状态下,应用程序的代码将不再运行,并且可能被系统终止 /// 当应用程序被挂起,可能是由于用户关闭应用程序或系统资源不足时调用。在这个状态下,应用程序的代码将不再运行,并且可能被系统终止
void onDetached(){ void onDetached() {
Console.log('onDetached'); Console.log('onDetached');
webViewController.evaluateJavascript(source: 'activeState("0");'); webViewController.evaluateJavascript(source: 'activeState("0");');
// close // close
// 上报阅读结束时间 // 上报阅读结束时间
_addReadTime(type: 'close'); _addReadTime(type: 'close');
} }
///------------------------------------------ app 生命周期--------------------------------------------------------
///------------------------------------------ app 生命周期--------------------------------------------------------
} }
class ToolModel { class ToolModel {
ToolModel({required this.tag, required this.selected, required this.name,required this.icon,required this.activeIcon}); ToolModel(
{required this.tag,
required this.selected,
required this.name,
required this.icon,
required this.activeIcon});
int tag; int tag;
String name; String name;
bool selected; bool selected;
...@@ -1002,7 +1020,10 @@ class ToolModel { ...@@ -1002,7 +1020,10 @@ class ToolModel {
} }
class AudioModel { class AudioModel {
AudioModel({required this.path, required this.duration,required this.currentDuration}); AudioModel(
{required this.path,
required this.duration,
required this.currentDuration});
String path; String path;
String duration; String duration;
String currentDuration; String currentDuration;
......
part of record;
\ No newline at end of file
library record;
import 'dart:io';
import 'package:audio_session/audio_session.dart';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:flutter_sound/flutter_sound.dart';
import 'package:flutter_sound/public/flutter_sound_player.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:path_provider/path_provider.dart';
import 'package:permission_handler/permission_handler.dart';
import '../../utils/index.dart';
part 'view.dart';
part 'controller.dart';
\ No newline at end of file
part of record;
typedef _Fn = void Function();
const theSource = AudioSource.microphone;
class RecordPage extends StatefulWidget {
const RecordPage({Key? key}) : super(key: key);
@override
State<RecordPage> createState() => _RecordPageState();
}
class _RecordPageState extends State<RecordPage> {
final FlutterSoundRecorder? _mRecorder = FlutterSoundRecorder();
final FlutterSoundPlayer? _mPlayer = FlutterSoundPlayer();
final String _mPath = 'tau_file.mp4';
final Codec _codec = Codec.aacMP4;
bool _mRecorderIsInited = false;
@override
void initState() {
openTheRecorder().then((value) {
setState(() {
_mRecorderIsInited = true;
});
});
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.blue,
appBar: AppBar(
title: const Text('Recorder'),
),
body: Column(
children: [
Container(
margin: EdgeInsets.all(3.w),
padding: EdgeInsets.all(3.w),
height: 80.w,
width: double.infinity,
alignment: Alignment.center,
decoration: BoxDecoration(
color: Colors.white,
border: Border.all(
color: Colors.indigo,
width: 3
)
),
child: Row(
children: [
ElevatedButton(
onPressed: getRecorderFn(),
child: Text(_mRecorder!.isRecording?'stop':'Record')
),
SizedBox(
width: 20.w,
),
Text(_mRecorder!.isRecording? 'Recording in progress' : 'Record is stopped')
],
),
),
Container(
margin: EdgeInsets.all(3.w),
padding: EdgeInsets.all(3.w),
height: 80,
width: double.infinity,
alignment: Alignment.center,
decoration: BoxDecoration(
color: const Color(0xFFFAF0E6),
border: Border.all(
color: Colors.indigo,
width: 3,
),
),
child: Row(children: [
ElevatedButton(
onPressed: (){
},
//color: Colors.white,
//disabledColor: Colors.grey,
child: Text(_mPlayer!.isPlaying ? 'Stop' : 'Play'),
),
const SizedBox(
width: 20,
),
Text(_mPlayer!.isPlaying
? 'Playback in progress'
: 'Player is stopped'),
]),
)
],
),
);
}
Future<void> openTheRecorder() async {
Permission permission = Permission.microphone;
var status = await permission.request();
if (status != PermissionStatus.granted){
throw RecordingPermissionException('Microphone permission not granted');
}else if (status == PermissionStatus.denied) {
requestPermission(permission);
} else if(status == PermissionStatus.permanentlyDenied){
requestPermission(permission);
} else if(status == PermissionStatus.restricted){
requestPermission(permission);
}
await _mRecorder!.openRecorder();
final session = await AudioSession.instance;
await session.configure(AudioSessionConfiguration(
avAudioSessionCategory: AVAudioSessionCategory.playAndRecord,
avAudioSessionCategoryOptions:
AVAudioSessionCategoryOptions.allowBluetooth |
AVAudioSessionCategoryOptions.defaultToSpeaker,
avAudioSessionMode: AVAudioSessionMode.spokenAudio,
avAudioSessionRouteSharingPolicy:
AVAudioSessionRouteSharingPolicy.defaultPolicy,
avAudioSessionSetActiveOptions: AVAudioSessionSetActiveOptions.none,
androidAudioAttributes: const AndroidAudioAttributes(
contentType: AndroidAudioContentType.speech,
flags: AndroidAudioFlags.none,
usage: AndroidAudioUsage.voiceCommunication,
),
androidAudioFocusGainType: AndroidAudioFocusGainType.gain,
androidWillPauseWhenDucked: true,
));
_mRecorderIsInited = true;
}
_Fn? getRecorderFn() {
if (!_mRecorderIsInited){
return null;
}
return _mRecorder!.isStopped ? record : stopRecorder;
}
void record() async {
Directory? tempDir = await getExternalStorageDirectory();
var time = DateTime.now().millisecondsSinceEpoch;
const String mPath = 'tau_file.mp4';
String filePath = '${tempDir!.path}/$time$mPath';
_mRecorder!.startRecorder(
toFile: filePath,
codec: _codec,
audioSource: theSource
).then((value) {
setState(() {});
});
}
void stopRecorder() async{
await _mRecorder!.stopRecorder().then((value) async {
Directory? tempDir = await getExternalStorageDirectory();
List<FileSystemEntity> files = tempDir!.listSync();
for (FileSystemEntity file in files) {
Console.log('File: ${file.path}');
}
setState(() {
});
});
}
void requestPermission(Permission permission) async {
PermissionStatus status = await permission.request();
if (status.isPermanentlyDenied){
openAppSettings();
}
}
}
...@@ -2,6 +2,7 @@ library splash_page; ...@@ -2,6 +2,7 @@ library splash_page;
import 'package:flutter/gestures.dart'; import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_book/apis/index.dart'; import 'package:flutter_book/apis/index.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
......
...@@ -19,7 +19,11 @@ class _SplashPageState extends State<SplashPage> { ...@@ -19,7 +19,11 @@ class _SplashPageState extends State<SplashPage> {
} }
Future<void> _startLaunchFlow() async { Future<void> _startLaunchFlow() async {
await _showPrivacyNoticeIfNeeded(); final accepted = await _showPrivacyNoticeIfNeeded();
if (!accepted) {
await SystemNavigator.pop();
return;
}
if (!mounted || _navigated) return; if (!mounted || _navigated) return;
await Future.delayed(const Duration(seconds: 2)); await Future.delayed(const Duration(seconds: 2));
...@@ -42,12 +46,12 @@ class _SplashPageState extends State<SplashPage> { ...@@ -42,12 +46,12 @@ class _SplashPageState extends State<SplashPage> {
context.pushReplacementNamed(Routes.main); context.pushReplacementNamed(Routes.main);
} }
Future<void> _showPrivacyNoticeIfNeeded() async { Future<bool> _showPrivacyNoticeIfNeeded() async {
if (StorageService.to.getBool(kLocalPrivacyNoticeShown)) { if (StorageService.to.getBool(kLocalPrivacyNoticeShown)) {
return; return true;
} }
await showDialog<void>( final agreed = await showDialog<bool>(
context: context, context: context,
barrierDismissible: false, barrierDismissible: false,
builder: (dialogContext) { builder: (dialogContext) {
...@@ -120,23 +124,36 @@ class _SplashPageState extends State<SplashPage> { ...@@ -120,23 +124,36 @@ class _SplashPageState extends State<SplashPage> {
), ),
), ),
actions: [ actions: [
SizedBox( Row(
width: double.infinity, children: [
Expanded(
child: TextButton(
onPressed: () {
Navigator.of(dialogContext).pop(false);
},
child: const Text('不同意'),
),
),
Expanded(
child: TextButton( child: TextButton(
onPressed: () async { onPressed: () async {
await StorageService.to await StorageService.to
.setBool(kLocalPrivacyNoticeShown, true); .setBool(kLocalPrivacyNoticeShown, true);
if (dialogContext.mounted) { if (dialogContext.mounted) {
Navigator.of(dialogContext).pop(); Navigator.of(dialogContext).pop(true);
} }
}, },
child: const Text('我知道了'), child: const Text('同意'),
), ),
), ),
], ],
),
],
); );
}, },
); );
return agreed ?? false;
} }
@override @override
......
part of tts;
enum TtsState{playing, stopped, paused, continued}
class TTSController extends GetxController {
late FlutterTts flutterTts;
TtsState ttsState = TtsState.stopped;
late String textToSpeak;
@override
void onInit() {
initTts();
super.onInit();
}
initTts() {
flutterTts = FlutterTts();
// 设置语言
flutterTts.setLanguage('zh-Hans-CN');
// 设置语速
flutterTts.setSpeechRate(0.5);
// 设置音量
flutterTts.setVolume(1.0);
// 设置音调
flutterTts.setPitch(1.0);
flutterTts.setStartHandler(() {
ttsState = TtsState.playing;
});
flutterTts.setCompletionHandler(() {
ttsState = TtsState.stopped;
});
flutterTts.setPauseHandler(() {
ttsState = TtsState.paused;
});
flutterTts.setContinueHandler(() {
ttsState = TtsState.continued;
Console.log('继续');
});
flutterTts.setErrorHandler((message) {
Console.log('error:$message');
ttsState = TtsState.stopped;
});
flutterTts.setProgressHandler((text, start, end, word) {
});
}
Future speak(String text) async {
textToSpeak = text;
Console.log(text);
flutterTts.speak(text);
Console.log('读文字');
}
Future stop() async {
update();
}
Future pause() async {
if (ttsState == TtsState.playing){
flutterTts.pause();
ttsState = TtsState.paused;
}else if (ttsState == TtsState.paused || ttsState == TtsState.stopped){
flutterTts.continueHandler!();
}
}
@override
void onClose() {
flutterTts.stop();
super.onClose();
}
}
\ No newline at end of file
library tts;
import 'package:flutter/material.dart';
import 'package:flutter_tts/flutter_tts.dart';
import 'package:flutter_book/utils/index.dart';
import 'package:get/get.dart';
part 'view.dart';
part 'controller.dart';
\ No newline at end of file
part of tts;
class TestTTSPage extends StatefulWidget {
const TestTTSPage({Key? key}) : super(key: key);
@override
State<TestTTSPage> createState() => _TestTTSPageState();
}
class _TestTTSPageState extends State<TestTTSPage> {
@override
Widget build(BuildContext context) {
return GetBuilder(
init: TTSController(),
builder: (controller) => Scaffold(
appBar: AppBar(
title: const Text('测试语音'),
centerTitle: true,
),
body: Column(
mainAxisAlignment: MainAxisAlignment.start,
// crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
color: Colors.yellow,
height: 150,
width: double.infinity,
// padding: const EdgeInsets.fromLTRB(10, 10, 10, 10),
child: const Text('你好我是小助手',textAlign: TextAlign.start,style: TextStyle(
color: Colors.black,
fontSize: 18
),),
),
GestureDetector(
onTap: (){
controller.speak("中华人民共和国(the People's Republic of China),简称“中国”,成立于1949年10月1日 [1],位于亚洲东部,太平洋西岸 [2],是工人阶级领导的、以工农联盟为基础的人民民主专政的社会主义国家 [3],以五星红旗为国旗 [4]、《义勇军进行曲》为国歌 [5],国徽中间是五星照耀下的天安门,周围是谷穗和齿轮 [6] [170],通用语言文字是普通话和规范汉字 [7],首都北京 [8],是一个以汉族为主体、56个民族共同组成的统一的多民族国家。中国陆地面积约960万平方千米,东部和南部大陆海岸线1.8万多千米,海域总面积约473万平方千米 [2]。海域分布有大小岛屿7600多个,其中台湾岛最大,面积35798平方千米 [2]。中国同14国接壤,与8国海上相邻。省级行政区划为23个省、5个自治区、4个直辖市、2个特别行政区。 [2]中国是世界上历史最悠久的国家之一,有着光辉灿烂的文化和光荣的革命传统 [3],世界遗产数量全球领先 [9]。1949年新中国成立后,进入社会主义革命和建设时期,1956年实现向社会主义过渡,此后社会主义建设在探索中曲折发展 [10]。“文化大革命”结束后实行改革开放,沿着中国特色社会主义道路,集中力量进行社会主义现代化建设 [3]。经过长期努力,中国特色社会主义进入了新时代。 [11] [136]中国是世界上人口最多的发展中国家,国土面积居世界第三位,是世界第二大经济体,并持续成为世界经济增长最大的贡献者,2020年经济总量突破100万亿元 [12-13] [125]。中国坚持独立自主的和平外交政策,是联合国安全理事会常任理事国,也是许多国际组织的重要成员,被认为是潜在超级大国之一");
},
child: Container(
margin: const EdgeInsets.all(10),
padding: const EdgeInsets.all(20),
color: Colors.red,
child: const Text('播放'),
),
),
GestureDetector(
onTap: (){
controller.pause();
},
child: Container(
margin: const EdgeInsets.all(10),
padding: const EdgeInsets.all(20),
color: Colors.blue,
child: const Text('暂停'),
),
)
],
),
)
);
}
}
...@@ -89,7 +89,11 @@ class UserEditNoteController extends GetxController { ...@@ -89,7 +89,11 @@ class UserEditNoteController extends GetxController {
Future<void> openTheRecorder() async { Future<void> openTheRecorder() async {
if (!initRecorder) { if (!initRecorder) {
// 获取权限 // 获取权限
if (await Access.microphone()) { if (await Access.microphone(
showExplanation: true,
purpose: '用于录制语音笔记,仅用于创建和播放您的录音内容。',
context: Get.context,
)) {
await _mRecorder.openRecorder(); await _mRecorder.openRecorder();
final session = await AudioSession.instance; final session = await AudioSession.instance;
await session.configure(AudioSessionConfiguration( await session.configure(AudioSessionConfiguration(
......
...@@ -105,8 +105,7 @@ class _UserSetPageState extends State<UserSetPage> { ...@@ -105,8 +105,7 @@ class _UserSetPageState extends State<UserSetPage> {
content: Wrap( content: Wrap(
children: [ children: [
Container( Container(
margin: margin: EdgeInsets.only(top: 44.w, bottom: 29.w),
EdgeInsets.only(top: 44.w, bottom: 29.w),
// 调整上下间距 // 调整上下间距
child: Center( child: Center(
child: Text( child: Text(
...@@ -157,10 +156,10 @@ class _UserSetPageState extends State<UserSetPage> { ...@@ -157,10 +156,10 @@ class _UserSetPageState extends State<UserSetPage> {
), ),
GestureDetector( GestureDetector(
onTap: () async { onTap: () async {
final result = await AccountAPI.logout();
if (result) {
// Toast.show('退出成功');
CustomToast.loading(); CustomToast.loading();
try {
await AccountAPI.logout();
} finally {
await UserStore.to.logout(); await UserStore.to.logout();
await Tools.clearData(); await Tools.clearData();
CustomToast.dismiss(); CustomToast.dismiss();
...@@ -241,7 +240,7 @@ class _UserSetPageState extends State<UserSetPage> { ...@@ -241,7 +240,7 @@ class _UserSetPageState extends State<UserSetPage> {
UModel model = await MineAPI.update(); UModel model = await MineAPI.update();
_getModel = model; _getModel = model;
final packageInfo = await PackageInfo.fromPlatform(); final packageInfo = await PackageInfo.fromPlatform();
int update = verifyVersion(model.version!,packageInfo.version); int update = verifyVersion(model.version!, packageInfo.version);
if (update == 1) { if (update == 1) {
_showUpdateDialog(false); _showUpdateDialog(false);
} }
...@@ -422,10 +421,10 @@ class _UserSetPageState extends State<UserSetPage> { ...@@ -422,10 +421,10 @@ class _UserSetPageState extends State<UserSetPage> {
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
if (!forcedUpgrade) if (!forcedUpgrade)
Padding(padding: EdgeInsets.only(right: 28.w), Padding(
padding: EdgeInsets.only(right: 28.w),
child: GestureDetector( child: GestureDetector(
child: child: Container(
Container(
width: 85.w, width: 85.w,
color: Colours.cFF, color: Colours.cFF,
alignment: Alignment.center, alignment: Alignment.center,
...@@ -438,14 +437,15 @@ class _UserSetPageState extends State<UserSetPage> { ...@@ -438,14 +437,15 @@ class _UserSetPageState extends State<UserSetPage> {
), ),
), ),
onTap: () => Navigator.of(context).pop(), onTap: () => Navigator.of(context).pop(),
),), ),
),
Container( Container(
height: 39.5.w, // 设置分割线的高度 height: 39.5.w, // 设置分割线的高度
width: 1, // 设置分割线的宽度 width: 1, // 设置分割线的宽度
color: Colours.cLine, // 设置分割线的颜色 color: Colours.cLine, // 设置分割线的颜色
), ),
Padding(padding: EdgeInsets.only(left: 28.w), Padding(
padding: EdgeInsets.only(left: 28.w),
child: GestureDetector( child: GestureDetector(
child: Container( child: Container(
width: 85.w, width: 85.w,
...@@ -467,7 +467,6 @@ class _UserSetPageState extends State<UserSetPage> { ...@@ -467,7 +467,6 @@ class _UserSetPageState extends State<UserSetPage> {
}, },
), ),
), ),
], ],
), ),
], ],
...@@ -481,8 +480,6 @@ class _UserSetPageState extends State<UserSetPage> { ...@@ -481,8 +480,6 @@ class _UserSetPageState extends State<UserSetPage> {
); );
} }
/// TODO: 苹果市场app地址 /// TODO: 苹果市场app地址
_appUpdate() { _appUpdate() {
UpdateModel model = UpdateModel( UpdateModel model = UpdateModel(
......
...@@ -10,30 +10,15 @@ abstract class Access { ...@@ -10,30 +10,15 @@ abstract class Access {
String purpose = '', String purpose = '',
BuildContext? context, BuildContext? context,
}) async { }) async {
// 如果需要在申请权限前显示说明,且提供了上下文 if (Platform.isAndroid) return true;
if (showExplanation && context != null && purpose.isNotEmpty) { return _requestPermission(
final bool? shouldRequest = await _showPermissionExplanationDialog( permission: Permission.photos,
context: context, permissionName: '相册权限',
permissionName: Platform.isIOS ? '相册权限' : '存储权限', showExplanation: showExplanation,
purpose: purpose, purpose: purpose,
context: context,
allowLimited: true,
); );
// 用户取消,不申请权限
if (shouldRequest != true) {
return false;
}
}
if (Platform.isIOS) {
final result = await [Permission.photos].request();
return result[Permission.photos] == PermissionStatus.granted ||
result[Permission.photos] == PermissionStatus.limited;
}
if (Platform.isAndroid) {
final result = await [Permission.storage].request();
return result[Permission.storage] == PermissionStatus.granted;
}
return false;
} }
/// 相机权限 /// 相机权限
...@@ -45,22 +30,68 @@ abstract class Access { ...@@ -45,22 +30,68 @@ abstract class Access {
String purpose = '', String purpose = '',
BuildContext? context, BuildContext? context,
}) async { }) async {
// 如果需要在申请权限前显示说明,且提供了上下文 return _requestPermission(
permission: Permission.camera,
permissionName: '相机权限',
showExplanation: showExplanation,
purpose: purpose,
context: context,
);
}
/// 获取麦克风权限
static Future<bool> microphone({
bool showExplanation = false,
String purpose = '',
BuildContext? context,
}) async {
return _requestPermission(
permission: Permission.microphone,
permissionName: '麦克风权限',
showExplanation: showExplanation,
purpose: purpose,
context: context,
);
}
static Future<bool> _requestPermission({
required Permission permission,
required String permissionName,
required bool showExplanation,
required String purpose,
required BuildContext? context,
bool allowLimited = false,
}) async {
final status = await permission.status;
if (_isGranted(status, allowLimited: allowLimited)) {
return true;
}
if (showExplanation && context != null && purpose.isNotEmpty) { if (showExplanation && context != null && purpose.isNotEmpty) {
final bool? shouldRequest = await _showPermissionExplanationDialog( final bool? shouldRequest = await _showPermissionExplanationDialog(
context: context, context: context,
permissionName: '相机权限', permissionName: permissionName,
purpose: purpose, purpose: purpose,
); );
// 用户取消,不申请权限
if (shouldRequest != true) { if (shouldRequest != true) {
return false; return false;
} }
} }
final result = await [Permission.camera].request(); final result = await permission.request();
return result[Permission.camera] == PermissionStatus.granted; final granted = _isGranted(result, allowLimited: allowLimited);
if (!granted &&
context != null &&
context.mounted &&
result.isPermanentlyDenied) {
_showSettingDialog(context, '获取$permissionName');
}
return granted;
}
static bool _isGranted(PermissionStatus status, {bool allowLimited = false}) {
return status == PermissionStatus.granted ||
(allowLimited && status == PermissionStatus.limited);
} }
/// 显示权限使用目的说明对话框 /// 显示权限使用目的说明对话框
...@@ -177,18 +208,111 @@ abstract class Access { ...@@ -177,18 +208,111 @@ abstract class Access {
); );
} }
/// 打开设置 static void _showSettingDialog(BuildContext context, String content) {
static Future<void> setting() async => await openAppSettings(); showDialog(
context: context,
/// 存储权限 builder: (BuildContext dialogContext) {
static Future<bool> storage() async { return WillPopScope(
final result = await [Permission.storage].request(); onWillPop: () => Future.value(true),
return result[Permission.storage] == PermissionStatus.granted; child: AlertDialog(
insetPadding: EdgeInsets.zero,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8.0.w),
),
actionsPadding: EdgeInsets.zero,
title: Container(
alignment: Alignment.center,
child: Text(
'权限申请',
style: TextStyle(
fontSize: 20.w,
color: Colours.c3,
fontWeight: Fonts.boldSemi),
),
),
actions: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Padding(
padding: EdgeInsets.only(top: 5.5.w),
child: Text(
content,
style: TextStyle(
fontSize: 15.w,
color: Colours.c9,
fontWeight: Fonts.medium),
),
),
SizedBox(height: 22.w),
Container(
height: 1,
width: 216.5.w,
color: Colours.cLine,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Padding(
padding: EdgeInsets.only(right: 28.w),
child: GestureDetector(
onTap: () => Navigator.of(dialogContext).pop(),
child: Container(
width: 85.w,
color: Colours.cFF,
alignment: Alignment.center,
child: const Text(
'取消',
style: TextStyle(
color: Colours.c6,
fontSize: 15,
fontWeight: Fonts.medium),
),
),
),
),
Container(
height: 39.5.w,
width: 1,
color: Colours.cLine,
),
Padding(
padding: EdgeInsets.only(left: 28.w),
child: GestureDetector(
onTap: () {
Navigator.of(dialogContext).pop();
setting();
},
child: Container(
width: 85.w,
color: Colours.cFF,
alignment: Alignment.center,
child: const Text(
'设置',
style: TextStyle(
color: Colours.cAB1941,
fontSize: 15,
fontWeight: Fonts.boldSemi),
),
),
),
),
],
),
],
),
],
),
],
),
);
},
);
} }
/// 获取麦克风权限 /// 打开设置
static Future<bool> microphone() async { static Future<void> setting() async => await openAppSettings();
final result = await [Permission.microphone].request();
return result[Permission.microphone] == PermissionStatus.granted;
}
} }
...@@ -12,38 +12,14 @@ abstract class AssetsPicker { ...@@ -12,38 +12,14 @@ abstract class AssetsPicker {
double maxHeight = 1024, double maxHeight = 1024,
String purpose = '用于从相册中选择图片,用于设置头像、上传图片等功能。', String purpose = '用于从相册中选择图片,用于设置头像、上传图片等功能。',
}) async { }) async {
// 先检查权限状态
bool hasPermission = false;
if (Platform.isIOS) {
final status = await Permission.photos.status;
hasPermission = status == PermissionStatus.granted || status == PermissionStatus.limited;
} else if (Platform.isAndroid) {
final status = await Permission.storage.status;
hasPermission = status == PermissionStatus.granted;
}
// 如果没有权限,先显示说明对话框,然后申请权限
if (!hasPermission) {
final granted = await Access.photos( final granted = await Access.photos(
showExplanation: true, showExplanation: true,
purpose: purpose, purpose: purpose,
context: context, context: context,
); );
if (!granted) { if (!granted) {
// 用户拒绝或取消,检查是否需要跳转设置
if (context.mounted) {
final status = Platform.isIOS
? await Permission.photos.status
: await Permission.storage.status;
if (status.isPermanentlyDenied) {
_showSettingDialog(context, '获取相册权限');
}
}
return null; return null;
} }
}
return _imagePicker.pickImage( return _imagePicker.pickImage(
source: source, source: source,
...@@ -62,138 +38,25 @@ abstract class AssetsPicker { ...@@ -62,138 +38,25 @@ abstract class AssetsPicker {
double maxHeight = 512, double maxHeight = 512,
String purpose = '用于使用相机拍照,用于设置头像、上传图片等功能。', String purpose = '用于使用相机拍照,用于设置头像、上传图片等功能。',
}) async { }) async {
// 先检查权限状态
final cameraStatus = await Permission.camera.status;
bool hasPermission = cameraStatus == PermissionStatus.granted;
// 如果没有相机权限,先显示说明对话框,然后申请权限
if (!hasPermission) {
final granted = await Access.camera( final granted = await Access.camera(
showExplanation: true, showExplanation: true,
purpose: purpose, purpose: purpose,
context: context, context: context,
); );
if (!granted) { if (!granted) {
// 用户拒绝或取消,检查是否需要跳转设置
if (context.mounted) {
final status = await Permission.camera.status;
if (status.isPermanentlyDenied) {
_showSettingDialog(context, '获取相机权限');
}
}
return null; return null;
} }
}
return _imagePicker.pickImage( try {
return await _imagePicker.pickImage(
source: source, source: source,
// maxWidth: maxWidth, // maxWidth: maxWidth,
// maxHeight: maxHeight, // maxHeight: maxHeight,
); );
} catch (error) {
Console.log('打开相机失败: $error');
Toast.show('打开相机失败,请检查权限后重试');
return null;
} }
/// 展示设置弹窗
static _showSettingDialog(BuildContext context,String content) {
showDialog(
context: context,
builder: (BuildContext context) {
return WillPopScope(
onWillPop: () => Future.value(true),
child: AlertDialog(
insetPadding: EdgeInsets.zero, // 设置水平边距
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8.0.w),
),
// 去除操作按钮区域的内边距
actionsPadding: EdgeInsets.zero,
title: Container(
alignment: Alignment.center,
child: Text('权限申请', style: TextStyle(
fontSize: 20.w,
color: Colours.c3,
fontWeight: Fonts.boldSemi),),
),
actions: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Padding(
padding: EdgeInsets.only(top: 5.5.w),
child: Text(
content,
style: TextStyle(
fontSize: 15.w,
color: Colours.c9,
fontWeight: Fonts.medium),
),
),
SizedBox(
height: 22.w,
),
Container(
height: 1, // 设置分割线的高度
width: 216.5.w, // 设置分割线的宽度
color: Colours.cLine, // 设置分割线的颜色
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Padding(padding: EdgeInsets.only(right: 28.w),
child: GestureDetector(
child:
Container(
width: 85.w,
color: Colours.cFF,
alignment: Alignment.center,
child: const Text(
'取消',
style: TextStyle(
color: Colours.c6,
fontSize: 15,
fontWeight: Fonts.medium),
),
),
onTap: () => Navigator.of(context).pop(),
),),
Container(
height: 39.5.w, // 设置分割线的高度
width: 1, // 设置分割线的宽度
color: Colours.cLine, // 设置分割线的颜色
),
Padding(padding: EdgeInsets.only(left: 28.w),
child: GestureDetector(
child: Container(
width: 85.w,
color: Colours.cFF,
alignment: Alignment.center,
child: const Text(
'设置',
style: TextStyle(
color: Colours.cAB1941,
fontSize: 15,
fontWeight: Fonts.boldSemi),
),
),
onTap: () {
Access.setting();
},
),
),
],
),
],
),
],
),
],
),
);
},
);
} }
} }
...@@ -14,11 +14,11 @@ const String kSearchHistory = 'search_history'; ...@@ -14,11 +14,11 @@ const String kSearchHistory = 'search_history';
const String kFailOrder = 'failOrder'; const String kFailOrder = 'failOrder';
const String kNoteTable = 'members_book_notes'; const String kNoteTable = 'members_book_notes';
const String kReadTable = 'read_history'; const String kReadTable = 'read_history';
const String kUserAgreement = '$kHtmlBaseServer/agreement/ser_agreement.html'; const String kUserAgreement = '$kHtmlBaseServer/agreement/ser_agreement.html?v=1';
const String kUserPriAgreement = const String kUserPriAgreement =
'$kHtmlBaseServer/agreement/pri_agreement.html'; '$kHtmlBaseServer/agreement/pri_agreement.html?v=1';
const String kUserRechargeAgreement = const String kUserRechargeAgreement =
'$kHtmlBaseServer/agreement/rec_agreement.html'; '$kHtmlBaseServer/agreement/rec_agreement.html?v=1';
// 错题详情页 html // 错题详情页 html
const String kUserWrongDes = '$kHtmlBaseServer/evaluating_wrong.html'; const String kUserWrongDes = '$kHtmlBaseServer/evaluating_wrong.html';
......
part of utils; part of utils;
class SqlManager { class SqlManager {
static const _version = 1; static const _version = 1;
static const _name= "zi_jing_app_flutter.db"; static const _name = "zi_jing_app_flutter.db";
static Database? _database; static Database? _database;
///初始化 ///初始化
static init() async { static init() async {
// open the database // open the database
...@@ -17,7 +14,6 @@ class SqlManager { ...@@ -17,7 +14,6 @@ class SqlManager {
// var databasesPath = await Tools.getDirectory(); // var databasesPath = await Tools.getDirectory();
String dbName = _name; String dbName = _name;
Console.log('Sql-----------databasesPath---------$databasesPath'); Console.log('Sql-----------databasesPath---------$databasesPath');
if(databasesPath != null) {
String path = databasesPath + dbName; String path = databasesPath + dbName;
if (Platform.isIOS) { if (Platform.isIOS) {
path = "$databasesPath/$dbName"; path = "$databasesPath/$dbName";
...@@ -39,17 +35,14 @@ class SqlManager { ...@@ -39,17 +35,14 @@ class SqlManager {
"content TEXT, " "content TEXT, "
"upload INTEGER DEFAULT 0, " "upload INTEGER DEFAULT 0, "
"positioning TEXT, " "positioning TEXT, "
"note_content TEXT)" "note_content TEXT)");
);
// // 阅读章节表 // // 阅读章节表
await db.execute("CREATE TABLE $kReadTable (" await db.execute("CREATE TABLE $kReadTable ("
"id INTEGER PRIMARY KEY, " "id INTEGER PRIMARY KEY, "
"book_id INTEGER, " "book_id INTEGER, "
"chapter_id INTEGER)" "chapter_id INTEGER)");
);
}); });
} }
}
static Future<Database?> getCurrentDatabase() async { static Future<Database?> getCurrentDatabase() async {
if (_database == null) { if (_database == null) {
...@@ -63,25 +56,24 @@ class SqlManager { ...@@ -63,25 +56,24 @@ class SqlManager {
await _database?.close(); await _database?.close();
} }
/// 查询划线高亮笔记 /// 查询划线高亮笔记
static Future<Map<String, dynamic>> queryLocalNote({required int bookId,required int chapterId}) async { static Future<Map<String, dynamic>> queryLocalNote(
{required int bookId, required int chapterId}) async {
Database? db = await SqlManager.getCurrentDatabase(); Database? db = await SqlManager.getCurrentDatabase();
Map<String, dynamic> returnMap = {}; Map<String, dynamic> returnMap = {};
// 划线 // 划线
List<Map<String, dynamic>>? lineResult = await db?.query( List<Map<String, dynamic>>? lineResult = await db?.query(
kNoteTable, kNoteTable,
columns: ['types', 'chapter_id','positioning','content','color','id'], columns: ['types', 'chapter_id', 'positioning', 'content', 'color', 'id'],
where: 'book_id = ? and chapter_id = ? and types = ? and del = ?', where: 'book_id = ? and chapter_id = ? and types = ? and del = ?',
whereArgs: [bookId, chapterId,1,0], whereArgs: [bookId, chapterId, 1, 0],
); );
// 高亮 // 高亮
List<Map<String, dynamic>>? colorResult = await db?.query( List<Map<String, dynamic>>? colorResult = await db?.query(
kNoteTable, kNoteTable,
columns: ['types', 'chapter_id','positioning','content','color','id'], columns: ['types', 'chapter_id', 'positioning', 'content', 'color', 'id'],
where: 'book_id = ? and chapter_id = ? and types = ? and del = ?', where: 'book_id = ? and chapter_id = ? and types = ? and del = ?',
whereArgs: [bookId, chapterId,2,0], whereArgs: [bookId, chapterId, 2, 0],
); );
returnMap['line_list'] = lineResult; returnMap['line_list'] = lineResult;
returnMap['color_line'] = colorResult; returnMap['color_line'] = colorResult;
...@@ -97,71 +89,71 @@ class SqlManager { ...@@ -97,71 +89,71 @@ class SqlManager {
data, data,
conflictAlgorithm: ConflictAlgorithm.replace, conflictAlgorithm: ConflictAlgorithm.replace,
); );
return result??0; return result ?? 0;
} }
/// 删除划线高亮笔记 /// 删除划线高亮笔记
static Future<int> delLocalNote({required int noteId,required int id}) async { static Future<int> delLocalNote(
{required int noteId, required int id}) async {
Database? db = await SqlManager.getCurrentDatabase(); Database? db = await SqlManager.getCurrentDatabase();
if(noteId == 0){ if (noteId == 0) {
final result = await db?.update( final result = await db?.update(
kNoteTable, kNoteTable,
{'del': 1}, {'del': 1},
where: 'id = ?', where: 'id = ?',
whereArgs: [id], whereArgs: [id],
); );
return result??0; return result ?? 0;
} } else {
else{
final result = await db?.update( final result = await db?.update(
kNoteTable, kNoteTable,
{'del': 1}, {'del': 1},
where: 'notes_id = ?', where: 'notes_id = ?',
whereArgs: [noteId], whereArgs: [noteId],
); );
return result??0; return result ?? 0;
} }
} }
/// 修改划线高亮笔记 /// 修改划线高亮笔记
static Future<int> updateLocalNote({required int notesId,required int id,required Map<String, dynamic> data}) async { static Future<int> updateLocalNote(
{required int notesId,
required int id,
required Map<String, dynamic> data}) async {
Database? db = await SqlManager.getCurrentDatabase(); Database? db = await SqlManager.getCurrentDatabase();
if(notesId ==0){ if (notesId == 0) {
final result = await db?.update( final result = await db?.update(
kNoteTable, kNoteTable,
data, data,
where: 'id = ?', where: 'id = ?',
whereArgs: [id], whereArgs: [id],
); );
return result??0; return result ?? 0;
} } else {
else{
final result = await db?.update( final result = await db?.update(
kNoteTable, kNoteTable,
data, data,
where: 'notes_id = ?', where: 'notes_id = ?',
whereArgs: [notesId], whereArgs: [notesId],
); );
return result??0; return result ?? 0;
} }
} }
/// 查询所有没有上传的数据 /// 查询所有没有上传的数据
static Future<List<Map<String, dynamic>>> queryNoUploadData() async { static Future<List<Map<String, dynamic>>> queryNoUploadData() async {
Database? db = await SqlManager.getCurrentDatabase(); Database? db = await SqlManager.getCurrentDatabase();
List<Map<String, dynamic>>? results = await db?.query( List<Map<String, dynamic>>? results = await db?.query(
kNoteTable, kNoteTable,
where: 'upload = ? and del = ?', where: 'upload = ? and del = ?',
whereArgs: [0,0], whereArgs: [0, 0],
); );
// List<Map<String, dynamic>>? results = await db?.query( // List<Map<String, dynamic>>? results = await db?.query(
// 'members_book_notes', // 'members_book_notes',
// where: 'del = ?', // where: 'del = ?',
// whereArgs: [0], // whereArgs: [0],
// ); // );
return results ??[]; return results ?? [];
} }
/// 将所有 upload 为 0 的数据的 upload 字段值更新为 1 /// 将所有 upload 为 0 的数据的 upload 字段值更新为 1
...@@ -175,8 +167,9 @@ class SqlManager { ...@@ -175,8 +167,9 @@ class SqlManager {
); );
Console.log('Sql---------------更新数据----------------$result'); Console.log('Sql---------------更新数据----------------$result');
} }
/// 将上传成功的id数据的notes_id 更新为服务器对应数值 并设置upload 为1 /// 将上传成功的id数据的notes_id 更新为服务器对应数值 并设置upload 为1
static Future<void> updateNotesId(List<Map<String,dynamic>> data) async { static Future<void> updateNotesId(List<Map<String, dynamic>> data) async {
Database? db = await SqlManager.getCurrentDatabase(); Database? db = await SqlManager.getCurrentDatabase();
// 构建批量更新的SQL语句 // 构建批量更新的SQL语句
String sql = 'UPDATE members_book_notes SET notes_id = CASE id '; String sql = 'UPDATE members_book_notes SET notes_id = CASE id ';
...@@ -205,57 +198,54 @@ class SqlManager { ...@@ -205,57 +198,54 @@ class SqlManager {
db?.delete(kNoteTable); db?.delete(kNoteTable);
db?.delete(kReadTable); db?.delete(kReadTable);
} }
/// 根据 book_id 查询当前读到的 章节 /// 根据 book_id 查询当前读到的 章节
static Future<String> queryReadHistoryByBookId(int bookId) async { static Future<String> queryReadHistoryByBookId(int bookId) async {
try { try {
Database? db = await SqlManager.getCurrentDatabase(); Database? db = await SqlManager.getCurrentDatabase();
if (!db!.isOpen){ if (!db!.isOpen) {
await init(); await init();
} }
List<Map<String, dynamic>>? results = await db?.query( List<Map<String, dynamic>> results = await db.query(
kReadTable, kReadTable,
where: 'book_id = ?', where: 'book_id = ?',
whereArgs: [bookId], whereArgs: [bookId],
); );
return results?.first['chapter_id'].toString() ?? ''; return results.isNotEmpty ? results.first['chapter_id'].toString() : '';
} } catch (e) {
catch(e){
Console.log('Error querying read history by book id: $e'); Console.log('Error querying read history by book id: $e');
return ''; return '';
} }
} }
/// 根据 book_id 写入当前读到的 章节 /// 根据 book_id 写入当前读到的 章节
static Future<int> updateReadHistoryByBookId(int bookId ,int chapterId) async { static Future<int> updateReadHistoryByBookId(
int bookId, int chapterId) async {
Database? db = await SqlManager.getCurrentDatabase(); Database? db = await SqlManager.getCurrentDatabase();
final queryResult = await queryReadHistoryByBookId(bookId); final queryResult = await queryReadHistoryByBookId(bookId);
if (queryResult.isEmpty){ if (queryResult.isEmpty) {
Console.log('Sql----------没有当前书籍的数据----------------------'); Console.log('Sql----------没有当前书籍的数据----------------------');
final result = await db?.insert( final result = await db?.insert(
'read_history', 'read_history',
{'chapter_id':chapterId, 'book_id':bookId}, {'chapter_id': chapterId, 'book_id': bookId},
conflictAlgorithm: ConflictAlgorithm.replace, conflictAlgorithm: ConflictAlgorithm.replace,
); );
Console.log('Sql----------插入数据结果:$result----------------------'); Console.log('Sql----------插入数据结果:$result----------------------');
return result??0; return result ?? 0;
} } else {
else{
Console.log('Sql----------有当前书籍的数据----------------------'); Console.log('Sql----------有当前书籍的数据----------------------');
final result = await db?.update( final result = await db?.update(
'read_history', 'read_history',
{'chapter_id':chapterId}, {'chapter_id': chapterId},
where: 'book_id = ?', where: 'book_id = ?',
whereArgs: [bookId], whereArgs: [bookId],
); );
Console.log('Sql----------更新数据结果:$result----------------------'); Console.log('Sql----------更新数据结果:$result----------------------');
return result??0; return result ?? 0;
} }
} }
/// 插入数据 /// 插入数据
static Future<bool> insertData(Map<String, dynamic> data) async { static Future<bool> insertData(Map<String, dynamic> data) async {
Database? db = await SqlManager.getCurrentDatabase(); Database? db = await SqlManager.getCurrentDatabase();
...@@ -264,13 +254,12 @@ class SqlManager { ...@@ -264,13 +254,12 @@ class SqlManager {
data, data,
conflictAlgorithm: ConflictAlgorithm.replace, conflictAlgorithm: ConflictAlgorithm.replace,
); );
if (result !=null){ if (result != null) {
return true; return true;
} }
return false; return false;
} }
/// 查询所有数据 /// 查询所有数据
static Future<List<Map<String, dynamic>>?> queryAllData() async { static Future<List<Map<String, dynamic>>?> queryAllData() async {
Database? db = await SqlManager.getCurrentDatabase(); Database? db = await SqlManager.getCurrentDatabase();
...@@ -285,7 +274,7 @@ class SqlManager { ...@@ -285,7 +274,7 @@ class SqlManager {
where: 'id = ?', where: 'id = ?',
whereArgs: [id], whereArgs: [id],
); );
return results?.first??{}; return results?.first ?? {};
} }
/// 更新数据 /// 更新数据
...@@ -308,5 +297,4 @@ class SqlManager { ...@@ -308,5 +297,4 @@ class SqlManager {
whereArgs: [id], whereArgs: [id],
); );
} }
} }
...@@ -4,14 +4,8 @@ part of utils; ...@@ -4,14 +4,8 @@ part of utils;
class Toast { class Toast {
/// 展示toast /// 展示toast
static void show(String msg, {int duration = 2000}) { static void show(String msg, {int duration = 2000}) {
if (msg == null) { showToast(msg,
return; duration: Duration(milliseconds: duration), dismissOtherToast: true);
}
showToast(
msg,
duration: Duration(milliseconds: duration),
dismissOtherToast: true
);
} }
/// 取消toast /// 取消toast
......
part of utils; part of utils;
abstract class Tools { abstract class Tools {
/// 取消焦点 /// 取消焦点
static void unfocus() { static void unfocus() {
...@@ -13,7 +12,7 @@ abstract class Tools { ...@@ -13,7 +12,7 @@ abstract class Tools {
String pattern = 'yyyy-MM-dd', String pattern = 'yyyy-MM-dd',
bool humanize = false, bool humanize = false,
}) { }) {
final dateTime = DateTime.fromMillisecondsSinceEpoch(timestamp*1000); final dateTime = DateTime.fromMillisecondsSinceEpoch(timestamp * 1000);
if (humanize) { if (humanize) {
final now = DateTime.now(); final now = DateTime.now();
final difference = now.difference(dateTime); final difference = now.difference(dateTime);
...@@ -43,11 +42,11 @@ abstract class Tools { ...@@ -43,11 +42,11 @@ abstract class Tools {
static Future<String> getDirectory() async { static Future<String> getDirectory() async {
// getTemporaryDirectory // getTemporaryDirectory
final directory = await getTemporaryDirectory(); final directory = await getTemporaryDirectory();
return directory!.path; return directory.path;
} }
/// 语音文件名称 /// 语音文件名称
static String generateVoiceFileName(){ static String generateVoiceFileName() {
DateTime now = DateTime.now(); DateTime now = DateTime.now();
String formattedDate = DateFormat('yyyyMMddHHmmss').format(now); String formattedDate = DateFormat('yyyyMMddHHmmss').format(now);
return 'voice_$formattedDate.mp4'; return 'voice_$formattedDate.mp4';
...@@ -63,7 +62,7 @@ abstract class Tools { ...@@ -63,7 +62,7 @@ abstract class Tools {
/// 判断当前网络状态 /// 判断当前网络状态
static Future<bool> checkCurrentNetStatus() async { static Future<bool> checkCurrentNetStatus() async {
final connectivityResult = await (Connectivity().checkConnectivity()); final connectivityResult = await (Connectivity().checkConnectivity());
if (connectivityResult == ConnectivityResult.none){ if (connectivityResult == ConnectivityResult.none) {
return false; return false;
} }
return true; return true;
...@@ -108,7 +107,3 @@ abstract class Tools { ...@@ -108,7 +107,3 @@ abstract class Tools {
// } // }
// } // }
} }
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论