提交 1277c36a authored 作者: yueweilu's avatar yueweilu

修改笔记 以及语音播放

上级 f5f637c7
......@@ -431,5 +431,34 @@ abstract class MineAPI {
return false;
}
/// 25、修改笔记、高亮、划线的内容
static Future<bool> editNotes({
required String content,
required String notesId,
required String bookId,
required String noteContent,
String isOpen = '0',
String positioning = '',
String color= '#FF0000',
}) async {
final result = await HttpService.to.post(
'/v1/book/Information/editNotes',
params: {
'notes_id': notesId,
'book_id':bookId,
'note_content':noteContent,
'positioning':positioning,
'is_open':isOpen,
'color': color,
'content':content
},
showLoading: true
);
if (result.data is Map && result.data['is_success'] == 1) {
return true;
}
return false;
}
}
......@@ -22,10 +22,10 @@ void main() {
Future.wait([
UserStore.to.profile(),
]).whenComplete(() {
String name = EncryptUtil.aesEncrypt('我是谁');
print('2222222---------$name');
final result = EncryptUtil.aesDecrypt(name);
Console.log('解密--------------------------$result');
// String name = EncryptUtil.aesEncrypt('我是谁');
// print('2222222---------$name');
// final result = EncryptUtil.aesDecrypt(name);
// Console.log('解密--------------------------$result');
runApp(const MyApp());
//FlutterNativeSplash.remove();
});
......
......@@ -349,7 +349,7 @@ class NoteModel {
});
NoteModel.fromJson(dynamic json) {
types = json['notes_id'];
notesId = json['notes_id'];
types = json['types'];
chapterId = json['chapter_id'];
content = json['content'];
......@@ -412,6 +412,8 @@ class MediaModel {
this.content,
this.id,
this.duration = '',
this.path = '',
this.currentDuration = '0:00:00'
});
MediaModel.fromJson(dynamic json) {
......@@ -419,15 +421,22 @@ class MediaModel {
content = json['content'];
id = json['id'];
duration = '';
path = '';
currentDuration = '0:00:00';
}
num? privacyStatus;
String? content;
late String path;
num? id;
late String duration;
MediaModel copyWith({ num? privacyStatus,
late String currentDuration;
MediaModel copyWith({
num? privacyStatus,
String? content,
num? id,
}) => MediaModel( privacyStatus: privacyStatus ?? this.privacyStatus,
String path = '',
}) => MediaModel(
privacyStatus: privacyStatus ?? this.privacyStatus,
content: content ?? this.content,
id: id ?? this.id,
);
......
......@@ -60,7 +60,10 @@ class BookPayController extends GetxController {
/// 支付方式 默认第一个
late PayModel _payModel = pays.first;
PayModel get payModel => _payModel;
PayModel get payModel {
_payModel.selected = true;
return _payModel;
}
/// 选择积分状态
void show(){
......
......@@ -86,15 +86,15 @@ class CourseController extends GetxController {
}
}
void logout(BuildContext context) async {
final result = await AccountAPI.logout();
if (result){
CustomToast.success('退出成功');
await UserStore.to.logout();
if(context.mounted){
context.goNamed(Routes.main);
}
}
}
// void logout(BuildContext context) async {
// final result = await AccountAPI.logout();
// if (result){
// CustomToast.success('退出成功');
// await UserStore.to.logout();
// if(context.mounted){
// context.goNamed(Routes.main);
// }
// }
// }
}
\ No newline at end of file
......@@ -42,7 +42,6 @@ class _CoursePageState extends State<CoursePage> {
if (result == true) {
controller.getNums();
}
// controller.logout(context);
},
child: badges.Badge(
position: badges.BadgePosition.topEnd(top: -5.w, end: 0),
......
......@@ -142,8 +142,8 @@ class ReadController extends FullLifeCycleController with GetSingleTickerProvide
// 开启录音
void record() async {
startRecording = true;
String filePath = await _getDirectory();
String fileName = generateVoiceFileName();
String filePath = await Tools.getDirectory();
String fileName = Tools.generateVoiceFileName();
_mRecorder.startRecorder(
toFile: '$filePath/$fileName',
......
......@@ -14,10 +14,8 @@ import 'package:flutter_book/theme.dart';
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
import 'package:flutter_book/utils/index.dart';
import 'package:flutter_oss_aliyun/flutter_oss_aliyun.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:flutter_sound/public/flutter_sound_recorder.dart';
import 'package:flutter_sound/public/util/flutter_sound_helper.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:get/get.dart';
......
......@@ -73,6 +73,10 @@ class _ReadPageState extends State<ReadPage> {
onWebViewCreated: (InAppWebViewController controller){
readController.webViewController = controller;
},
onConsoleMessage: (controller, consoleMessage) {
// 接收从 WebView 发送的消息
print("Received message from WebView: ${consoleMessage.message}");
},
onLoadStop: (controller, url) {
// flutter 主动给 js 传参数
Map<String, dynamic> param = {
......@@ -80,6 +84,7 @@ class _ReadPageState extends State<ReadPage> {
'chapter_id': readController.chapterId,
'token':UserStore.to.token
};
Console.log('param--------------------------------$param');
controller.evaluateJavascript(source: 'callbackInFlutterComponent("$param");');
// 添加单击事件
......
......@@ -106,7 +106,7 @@ class _ReadInputDiscussState extends State<ReadInputDiscuss> {
onTap: (){
widget.controller.delDiscussInputImages(widget.controller.discussInputImages[index]);
},
child: Image.asset('assets/images/del_close.png',width: 12.w,height: 12.w,)
child: Image.asset('assets/images/media_del.png',width: 12.w,height: 12.w,)
)
)
],
......
......@@ -10,7 +10,7 @@ class UserCouponController extends GetxController {
// 优惠券
List <CouponModel> coupons = [];
late int type = 1;
late int type = 2;
final int _limit = 10;
int _page = 1;
......@@ -27,7 +27,7 @@ class UserCouponController extends GetxController {
void getOverCoupons() {
_noMore = true;
_page = 1;
type = 2;
type = 1;
_getCoupon();
}
......
......@@ -2,21 +2,227 @@ part of user_edit_note;
class UserEditNoteController extends GetxController {
final NoteModel model;
final String bookId;
late TextEditingController contentInput;
UserEditNoteController(this.model,this.bookId){
_setDuration();
contentInput = TextEditingController(text: model.noteContent?.text?.content);
}
// 录音
final FlutterSoundRecorder _mRecorder = FlutterSoundRecorder();
just_audio.AudioPlayer audioPlayer = just_audio.AudioPlayer();
// 录音开始
bool startRecording = false;
// 笔记是否公开
bool isPublic = false;
bool initRecorder = false;
String currentDuration = '';
late MediaModel currentPlayMediaModel = MediaModel();
@override
void onInit() {
super.onInit();
}
@override
void onClose() {
contentInput.dispose();
audioPlayer.dispose();
super.onClose();
}
// 设置笔记是否公开
void setIsPublic(){
isPublic = !isPublic;
update();
}
// 配置音频时长
void _setDuration() async {
for(MediaModel mediaModel in model.noteContent!.audio!){
Duration? duration = await just_audio.AudioPlayer().setUrl(mediaModel.content??'');
mediaModel.duration = Tools.formatDuration(duration!);
}
update();
}
// 删除图片
void delImage(MediaModel mediaModel){
model.noteContent!.image!.remove(mediaModel);
update();
}
// 删除音频
void delAudio(MediaModel mediaModel){
model.noteContent!.audio!.remove(mediaModel);
// 新加的录音 如果删除要删除源文件
if(mediaModel.id == 0){
}
update();
}
// 添加音频
void addAudio(String path,String duration){
MediaModel mediaModel = MediaModel(path: path,id: 0,duration: duration);
model.noteContent!.audio!.add(mediaModel);
update();
}
// 添加图片
void addImage(String path){
MediaModel mediaModel = MediaModel(path: path,id: 0);
model.noteContent!.image!.add(mediaModel);
update();
}
// 初始化录音组件
Future<void> openTheRecorder() async {
if(!initRecorder){
// 获取权限
if(await Access.microphone()){
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,
));
}
// 没有权限
else {
}
}
initRecorder = true;
}
// 开启录音
void record() async {
openTheRecorder();
update();
startRecording = true;
String filePath = await Tools.getDirectory();
String fileName = Tools.generateVoiceFileName();
_mRecorder.startRecorder(
toFile: '$filePath/$fileName',
audioSource: sound_recorder_platform.AudioSource.microphone,
codec: Codec.aacMP4,
);
}
// 停止录音
void stopRecorder() async{
startRecording = false;
final path = await _mRecorder.stopRecorder();
var duration = await audioPlayer.setFilePath(path!);
print('-----duration---------------------$duration------');
// 添加到数组
addAudio(path, Tools.formatDuration(duration!));
}
// 播放音频
void playAudio(MediaModel mediaModel){
if(audioPlayer.playerState.playing){
audioPlayer.stop();
mediaModel.currentDuration = '0:00:00';
// if(currentPlayMediaModel.id == mediaModel.id){
// return;
// }
}
// 本地音频
if (mediaModel.id == 0){
audioPlayer.setFilePath(mediaModel.path);
}
// 远程音频
else {
audioPlayer.setUrl(mediaModel.content??'');
}
audioPlayer.play();
audioPlayer.positionStream.listen((position) {
String temp = Tools.formatDuration(position);
Console.log('播放时间---------------------$temp');
mediaModel.currentDuration = temp;
update();
});
// currentPlayMediaModel = mediaModel;
}
// 上传文件
Future<String> upload({
required String path
}) async {
// String result = await CommonAPI.upload(path:path,fileTypes: 'comment');
// return result;
OssTool tool = OssTool('zxts-comment-file');
final response = await tool.putObjectFile(path);
print('------response--------------------------${response.realUri}');
return response.realUri.toString();
}
// 提交
Future<bool> submit() async {
return true;
// 循环上传图片获取地址
for(MediaModel mediaModel in model.noteContent!.image!){
if (mediaModel.id == 0){
final url = await upload(path: mediaModel.path);
mediaModel.content = url;
}
}
// 循环上传音频获取地址
for(MediaModel mediaModel in model.noteContent!.audio!){
if (mediaModel.id == 0){
final url = await upload(path: mediaModel.path);
mediaModel.content = url;
}
}
// 组织图片
List<Map> images = [];
for(MediaModel mediaModel in model.noteContent!.image!){
images.add( mediaModel.toJson());
}
// 组织音频
List<Map> audios = [];
for(MediaModel mediaModel in model.noteContent!.audio!){
audios.add( mediaModel.toJson());
}
model.noteContent!.text!.content = contentInput.text;
Map<String,dynamic> contentMap = {
'text':model.noteContent!.text!.toJson(),
'audio':audios,
'image':images
};
final result = MineAPI.editNotes(
content: model.content??'',
notesId: model.notesId.toString(),
bookId: bookId,
noteContent: jsonEncode(contentMap)
);
return result;
}
}
\ No newline at end of file
library user_edit_note;
import 'dart:convert';
import 'package:audio_session/audio_session.dart';
import 'package:flutter/material.dart';
import 'package:flutter_book/apis/index.dart';
import 'package:flutter_book/theme.dart';
import 'package:flutter_book/utils/index.dart';
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' as sound_recorder_platform;
import 'package:get/get.dart';
import 'package:go_router/go_router.dart';
import 'package:just_audio/just_audio.dart' as just_audio;
import 'package:just_audio/just_audio.dart';
import 'dart:async';
import '../../models/index.dart';
import '../../theme.dart';
import '../../utils/index.dart';
import '../../widgets/index.dart';
......
......@@ -11,6 +11,8 @@ class UserNotesDesController extends GetxController {
controlFinishRefresh: true,
);
just_audio.AudioPlayer audioPlayer = just_audio.AudioPlayer();
final int _limit = 10;
int _page = 1;
bool _noMore = false;
......@@ -25,12 +27,41 @@ class UserNotesDesController extends GetxController {
@override
void onClose() {
refreshController.dispose();
audioPlayer.dispose();
super.onClose();
}
// 播放音频
void playAudio(MediaModel mediaModel){
if(audioPlayer.playerState.playing){
audioPlayer.stop();
mediaModel.currentDuration = '0:00:00';
// if(currentPlayMediaModel.id == mediaModel.id){
// return;
// }
}
// 本地音频
if (mediaModel.id == 0){
audioPlayer.setFilePath(mediaModel.path);
}
// 远程音频
else {
audioPlayer.setUrl(mediaModel.content??'');
}
audioPlayer.play();
audioPlayer.positionStream.listen((position) {
String temp = Tools.formatDuration(position);
Console.log('播放时间---------------------$temp');
mediaModel.currentDuration = temp;
update();
});
// currentPlayMediaModel = mediaModel;
}
void delNotes({required String notesId,required String bookId}) async {
final result = await MineAPI.delNotes(notesId: notesId, bookId: bookId);
if (result){
Toast.show('删除笔记成功');
onRefresh();
}
}
......
......@@ -46,15 +46,17 @@ class _BuildListPageState extends State<BuildListPage> with AutomaticKeepAliveCl
else if(model.types == 3){
return BuildNote(model: model,
onTapDel: (){
print('---------删除--------');
controller.delNotes(notesId: model.notesId.toString(), bookId:widget.model.bookId.toString());
},
onTapEdit: (){
print('---------编辑--------');
context.pushNamed(Routes.editNote);
context.pushNamed(Routes.editNote,extra: model,queryParameters: {'book_id':widget.model.bookId.toString()});
},
onTapAudio: (mediaModel){
controller.playAudio(mediaModel);
},
);
}
return null;
},
itemCount: controller.notes.length,
),
......
......@@ -4,11 +4,13 @@ class BuildNote extends StatelessWidget {
final NoteModel model;
final void Function()? onTapDel;
final void Function()? onTapEdit;
final void Function(MediaModel mediaModel)? onTapAudio;
const BuildNote({
Key? key,
required this.model,
this.onTapDel,
this.onTapEdit
this.onTapEdit,
this.onTapAudio
}) : super(key: key);
@override
......@@ -35,7 +37,6 @@ class BuildNote extends StatelessWidget {
SlidableAction(
// An action can be bigger than the others.
onPressed: (BuildContext context){
print('---------编辑1--------');
if (onTapEdit !=null) onTapEdit!();
},
......@@ -47,7 +48,6 @@ class BuildNote extends StatelessWidget {
SlidableAction(
// An action can be bigger than the others.
onPressed: (BuildContext context){
print('---------删除1--------');
if (onTapDel !=null) onTapDel!();
},
backgroundColor: const Color(0xFFAE1414),
......@@ -96,7 +96,7 @@ class BuildNote extends StatelessWidget {
Widget _buildImageGridView(){
return GridView.builder(
// padding: const EdgeInsets.only(left: 13,top: 10),
physics: NeverScrollableScrollPhysics(),
physics: const NeverScrollableScrollPhysics(),
shrinkWrap: true,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 6,
......@@ -105,10 +105,7 @@ class BuildNote extends StatelessWidget {
childAspectRatio: 1
),
itemBuilder: (BuildContext context, int index) {
return Container(
// color: Colors.red,
child: CustomImage.network(url: model.noteContent!.image![index].content??'',fit: BoxFit.cover,),
);
return CustomImage.network(url: model.noteContent!.image![index].content??'',fit: BoxFit.cover,);
},
itemCount: model.noteContent?.image?.length,
);
......@@ -134,7 +131,12 @@ class BuildNote extends StatelessWidget {
// mainAxisSize: MainAxisSize.min,
mainAxisAlignment:MainAxisAlignment.spaceBetween,
children: [
Image.asset('assets/images/audio.png'),
GestureDetector(
onTap: (){
if (onTapAudio !=null) onTapAudio!(mediaModel);
},
child: Image.asset('assets/images/audio.png')
),
Text('0:00/${mediaModel.duration}',style: TextStyle(fontSize: 10.w,height: 1.4,color: Colours.c9),)
],
),
......
......@@ -560,7 +560,7 @@ abstract class Routes {
pageBuilder: (context, state) =>CupertinoPage(
name: state.uri.toString(),
key: state.pageKey,
child: const UserEditNotePage()
child: UserEditNotePage(model: state.extra as NoteModel,bookId: state.uri.queryParameters['book_id'].toString(),)
)
),
]
......
......@@ -103,7 +103,7 @@ class HttpService extends GetxService {
// 如果启用缓存,将cacheEnabled参数添加到请求选项中
if(cacheEnabled){
requestOptions.extra ??= {};
requestOptions.extra!['cacheEnabled'] = true;
requestOptions.extra!['cacheEnabled'] = false;
}
final response = await _dio.post(
url,
......@@ -275,15 +275,15 @@ class _RequestInterceptor extends Interceptor {
break;
case 404:
msg = '$statusCode - Server not found';
CustomToast.fail(msg);
// CustomToast.fail(msg);
break;
case 500:
msg = '$statusCode - Server error';
CustomToast.fail(msg);
// CustomToast.fail(msg);
break;
case 502:
msg = '$statusCode - Bad gateway';
CustomToast.fail(msg);
// CustomToast.fail(msg);
break;
default:
// if (code == 901) UserStore.to.logout();
......@@ -311,7 +311,7 @@ class _RequestInterceptor extends Interceptor {
final result = await HttpService.to.post(
'/v1/members/login/getToken',
params: {
'access_token':UserStore.to.accessToken
'access_token':StorageService.to.getString(kLocalAccessToken)
}
);
if (result.data is Map) {
......@@ -366,7 +366,7 @@ class _CacheInterceptor extends Interceptor {
final requestBody = response.requestOptions.data.toString();
// 将 GET 请求的参数和请求体参数拼接成缓存的键
final cacheKey = requestBody.isEmpty ? url : '$url?$requestBody';
Console.log('----------cacheKey-----------------------$cacheKey');
// Console.log('----------cacheKey-----------------------$cacheKey');
// 将响应数据转换为字符串,并将其编码为字节列表
List<int> bytes = utf8.encode(jsonEncode(response.data));
Uint8List uint8List = Uint8List.fromList(bytes);
......
......@@ -35,6 +35,19 @@ abstract class Tools {
String twoDigitSeconds = twoDigits(duration.inSeconds.remainder(60));
return "${duration.inHours}:$twoDigitMinutes:$twoDigitSeconds";
}
static Future<String> getDirectory() async {
// getTemporaryDirectory
final directory = await getExternalStorageDirectory();
return directory!.path;
}
// 语音文件名称
static String generateVoiceFileName(){
DateTime now = DateTime.now();
String formattedDate = DateFormat('yyyyMMddHHmmss').format(now);
return 'voice_$formattedDate.mp4';
}
}
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论