提交 77bf0eee authored 作者: maodou's avatar maodou

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

# Conflicts: # lib/pages/user_info/view.dart # lib/pages/user_order/view.dart # lib/routes/routes.dart
......@@ -27,6 +27,8 @@ PODS:
- WechatOpenSDK-XCFramework (~> 2.0.2)
- image_picker_ios (0.0.1):
- Flutter
- just_audio (0.0.1):
- Flutter
- OrderedSet (5.0.0)
- package_info_plus (0.4.5):
- Flutter
......@@ -55,6 +57,7 @@ DEPENDENCIES:
- flutter_tts (from `.symlinks/plugins/flutter_tts/ios`)
- fluwx (from `.symlinks/plugins/fluwx/ios`)
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
- just_audio (from `.symlinks/plugins/just_audio/ios`)
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
......@@ -86,6 +89,8 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/fluwx/ios"
image_picker_ios:
:path: ".symlinks/plugins/image_picker_ios/ios"
just_audio:
:path: ".symlinks/plugins/just_audio/ios"
package_info_plus:
:path: ".symlinks/plugins/package_info_plus/ios"
path_provider_foundation:
......@@ -108,6 +113,7 @@ SPEC CHECKSUMS:
flutter_tts: 0f492aab6accf87059b72354fcb4ba934304771d
fluwx: 3c7b6df42f83d444d4538f3eaeae079f12d30c37
image_picker_ios: 99dfe1854b4fa34d0364e74a78448a0151025425
just_audio: baa7252489dbcf47a4c7cc9ca663e9661c99aafa
OrderedSet: aaeb196f7fef5a9edf55d89760da9176ad40b93c
package_info_plus: 115f4ad11e0698c8c1c5d8a689390df880f47e85
path_provider_foundation: 3784922295ac71e43754bd15e0653ccfd36a147c
......
......@@ -165,6 +165,14 @@ abstract class CommonAPI {
return result.data['filesUrl'];
}
/// 11、获取OSS临时token
static Future <OssModel> oss() async {
final result = await HttpService.to.post(
'/v1/common/Alioss/getUploadToken',
);
if (result.data is! Map ) return OssModel();
return OssModel.fromJson(result.data);
}
}
\ No newline at end of file
......@@ -276,7 +276,7 @@ abstract class LibraryAPI {
String types = '3',
required String content,
required String isOpen,
String color= '',
String color= '#FF0000',
required String positioning,
required String noteContent,
}) async {
......@@ -288,7 +288,7 @@ abstract class LibraryAPI {
'types':types,
'content':content,
'is_open':isOpen,
'color':content,
'color':color,
'positioning':positioning,
'note_content':noteContent
......
......@@ -215,7 +215,7 @@ abstract class MineAPI {
});
}
/// 13、笔记详情列表
/// 13、讨论详情列表
///
static Future<List<DiscussModel>> discussList(
{int page = 1,
......@@ -228,7 +228,7 @@ abstract class MineAPI {
'page': page,
'page_size': limit,
'book_id': bookId,
'types': types
'type': types
},
);
if (result.data is! Map && result.data['list'] is! List) return [];
......@@ -413,5 +413,52 @@ abstract class MineAPI {
return false;
}
/// 25、删除笔记、高亮、划线的内容
static Future<bool> delNotes({
required String notesId,
required String bookId,
}) async {
final result = await HttpService.to.post(
'/v1/book/Information/delNotes',
params: {
'notes_id': notesId,
'book_id':bookId
},
);
if (result.data is Map && result.data['is_success'] == 1) {
return true;
}
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;
}
}
......@@ -7,6 +7,7 @@ import 'package:flutter/services.dart';
import 'package:flutter_book/routes/index.dart';
import 'package:flutter_book/store/index.dart';
import 'package:flutter_book/theme.dart';
import 'package:flutter_book/utils/index.dart';
import 'package:flutter_book/widgets/index.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
......@@ -21,6 +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');
runApp(const MyApp());
//FlutterNativeSplash.remove();
});
......
......@@ -2,7 +2,7 @@ library models;
import 'package:just_audio/just_audio.dart' as just_audio;
part 'response.dart';
part 'course.dart';
......
......@@ -494,4 +494,41 @@ class BookDownloadModel {
}
class OssModel {
OssModel({
this.securityToken,
this.accessKeyId,
this.accessKeySecret,
this.expiration,});
OssModel.fromJson(dynamic json) {
securityToken = json['SecurityToken'];
accessKeyId = json['AccessKeyId'];
accessKeySecret = json['AccessKeySecret'];
expiration = json['Expiration'];
}
String? securityToken;
String? accessKeyId;
String? accessKeySecret;
String? expiration;
OssModel copyWith({ String? securityToken,
String? accessKeyId,
String? accessKeySecret,
String? expiration,
}) => OssModel( securityToken: securityToken ?? this.securityToken,
accessKeyId: accessKeyId ?? this.accessKeyId,
accessKeySecret: accessKeySecret ?? this.accessKeySecret,
expiration: expiration ?? this.expiration,
);
Map<String, dynamic> toJson() {
final map = <String, dynamic>{};
map['SecurityToken'] = securityToken;
map['AccessKeyId'] = accessKeyId;
map['AccessKeySecret'] = accessKeySecret;
map['Expiration'] = expiration;
return map;
}
}
......@@ -338,6 +338,7 @@ class UserInfoModel {
/// 笔记详情模型
class NoteModel {
NoteModel({
this.notesId,
this.types,
this.chapterId,
this.content,
......@@ -348,33 +349,38 @@ class NoteModel {
});
NoteModel.fromJson(dynamic json) {
notesId = json['notes_id'];
types = json['types'];
chapterId = json['chapter_id'];
content = json['content'];
positioning = json['positioning'];
noteContent = json['note_content'];
// noteContent = json['note_content'];
noteContent = json['note_content'] != null ? NoteContentModel.fromJson(json['note_content']) : null;
color = json['color'];
chapterName = json['chapter_name'];
}
num? notesId;
num? types;
num? chapterId;
String? content;
String? positioning;
String? noteContent;
// String? noteContent;
NoteContentModel? noteContent;
String? color;
String? chapterName;
NoteModel copyWith({
num? notesId,
num? types,
num? chapterId,
String? content,
String? positioning,
String? noteContent,
NoteContentModel? noteContent,
String? color,
String? chapterName,
}) =>
NoteModel(
notesId: notesId ?? this.notesId,
types: types ?? this.types,
chapterId: chapterId ?? this.chapterId,
content: content ?? this.content,
......@@ -386,17 +392,114 @@ class NoteModel {
Map<String, dynamic> toJson() {
final map = <String, dynamic>{};
map['notes_id'] = notesId;
map['types'] = types;
map['chapter_id'] = chapterId;
map['content'] = content;
map['positioning'] = positioning;
map['note_content'] = noteContent;
if (noteContent != null) {
map['note_content'] = noteContent?.toJson();
}
map['color'] = color;
map['chapter_name'] = chapterName;
return map;
}
}
class MediaModel {
MediaModel({
this.privacyStatus,
this.content,
this.id,
this.duration = '',
this.path = '',
this.currentDuration = '0:00:00'
});
MediaModel.fromJson(dynamic json) {
privacyStatus = json['privacy_status'];
content = json['content'];
id = json['id'];
duration = '';
path = '';
currentDuration = '0:00:00';
}
num? privacyStatus;
String? content;
late String path;
num? id;
late String duration;
late String currentDuration;
MediaModel copyWith({
num? privacyStatus,
String? content,
num? id,
String path = '',
}) => MediaModel(
privacyStatus: privacyStatus ?? this.privacyStatus,
content: content ?? this.content,
id: id ?? this.id,
);
Map<String, dynamic> toJson() {
final map = <String, dynamic>{};
map['privacy_status'] = privacyStatus;
map['content'] = content;
map['id'] = id;
return map;
}
}
class NoteContentModel {
NoteContentModel({
this.text,
this.audio,
this.image,});
NoteContentModel.fromJson(dynamic json) {
text = json['text'] != null ? MediaModel.fromJson(json['text']) : null;
if (json['audio'] != null) {
audio = [];
json['audio'].forEach((v) {
audio?.add(MediaModel.fromJson(v));
});
}
if (json['image'] != null) {
image = [];
json['image'].forEach((v) {
image?.add(MediaModel.fromJson(v));
});
}
}
MediaModel? text;
List<MediaModel>? audio;
List<MediaModel>? image;
NoteContentModel copyWith({ MediaModel? text,
List<MediaModel>? audio,
List<MediaModel>? image,
}) => NoteContentModel( text: text ?? this.text,
audio: audio ?? this.audio,
image: image ?? this.image,
);
Map<String, dynamic> toJson() {
final map = <String, dynamic>{};
if (text != null) {
map['text'] = text?.toJson();
}
if (audio != null) {
map['audio'] = audio?.map((v) => v.toJson()).toList();
}
if (image != null) {
map['image'] = image?.map((v) => v.toJson()).toList();
}
return map;
}
}
/// 讨论模型
class DiscussModel {
DiscussModel({
......@@ -662,3 +765,4 @@ class CoinModel {
}
}
......@@ -4,8 +4,10 @@ import 'package:flutter/material.dart';
import 'package:flutter_book/theme.dart';
import 'package:flutter_book/utils/index.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:go_router/go_router.dart';
import '../../models/index.dart';
import '../../routes/index.dart';
part 'view.dart';
part 'widgets/item.dart';
\ No newline at end of file
......@@ -3,9 +3,13 @@ part of book_category;
class BookCategoryPage extends StatefulWidget {
final List <ChapterModel> chapters;
final String bookId;
final BookDetailModel bookDetails;
const BookCategoryPage({
Key? key,
required this.chapters
required this.chapters,
required this.bookId,
required this.bookDetails
}) : super(key: key);
@override
......@@ -20,7 +24,7 @@ class _BookCategoryPageState extends State<BookCategoryPage> {
Expanded(
child: ListView.builder(
itemBuilder:(BuildContext context, int index){
return BuildItem(model: widget.chapters[index],);
return BuildItem(model: widget.chapters[index],bookId: widget.bookId,bookDetails: widget.bookDetails,);
},
itemCount: widget.chapters.length,
),
......
......@@ -2,9 +2,13 @@ part of book_category;
class BuildItem extends StatefulWidget {
final ChapterModel model;
final String bookId;
final BookDetailModel bookDetails;
const BuildItem({
Key? key,
required this.model
required this.model,
required this.bookId,
required this.bookDetails
}) : super(key: key);
@override
......@@ -19,9 +23,13 @@ class _BuildItemState extends State<BuildItem> {
/// 章节名称容器
GestureDetector(
onTap: (){
setState(() {
widget.model.selected = !widget.model.selected;
});
// 如果章下面没有节 点击才会跳转
if (widget.model.children!.isEmpty){
context.pushNamed(Routes.web,queryParameters: {'book_id': widget.bookDetails.bookId.toString(),'chapter_id': widget.model.id.toString(),'chapter_name':widget.model.name.toString()},extra: widget.bookDetails);
}
// setState(() {
// widget.model.selected = !widget.model.selected;
// });
},
child: Container(
padding: EdgeInsets.symmetric(horizontal: 15.w),
......@@ -49,7 +57,19 @@ class _BuildItemState extends State<BuildItem> {
),
Transform.rotate(
angle: widget.model.selected?0:-90 * (3.141592653589793 / 180),
child: GestureDetector(
onTap: (){
setState(() {
widget.model.selected = !widget.model.selected;
});
},
child: Container(
width: 20,
height: 20,
// color: Colors.red,
child: Image.asset('assets/images/down.png')
),
)
)
],
......@@ -63,7 +83,13 @@ class _BuildItemState extends State<BuildItem> {
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemBuilder: (BuildContext context, int index){
return _buildSection(widget.model.children![index]);
ChapterModel model = widget.model.children![index];
return GestureDetector(
onTap: (){
context.pushNamed(Routes.web,queryParameters: {'book_id': widget.bookDetails.bookId.toString(),'chapter_id':model.id.toString(),'chapter_name':model.name.toString()},extra: widget.bookDetails);
},
child: _buildSection(model)
);
},
itemCount: widget.model.children!.length,
)
......
......@@ -20,7 +20,12 @@ class _BookDetailPageState extends State<BookDetailPage> with SingleTickerProvid
Widget build(BuildContext context) {
return GetBuilder<BookDetailController>(
init:BookDetailController(widget.bookId),
builder: (controller)=> Scaffold(
builder: (controller)=> WillPopScope(
onWillPop: () async {
context.pop(true);
return false;
},
child: Scaffold(
appBar: CustomAppBar(
backgroundColor: const Color(0xFFAB1941).withOpacity(0.02),
title: const Text('详情'),
......@@ -75,7 +80,7 @@ class _BookDetailPageState extends State<BookDetailPage> with SingleTickerProvid
child: TabBarView(
controller: controller.tabController,
children: [
BookCategoryPage(chapters: controller.chapters,),
BookCategoryPage(chapters: controller.chapters,bookId: controller.bookId,bookDetails: controller.bookDetails,),
Container(
padding: EdgeInsets.only(left: 15.w,right: 15.w,top:12.w),
color: Colors.white,
......@@ -128,7 +133,7 @@ class _BookDetailPageState extends State<BookDetailPage> with SingleTickerProvid
// 1免费 0 不免费
if(controller.bookDetails.isFree == 1){
context.pushNamed(Routes.web,queryParameters: {'book_id': controller.bookDetails.bookId.toString(),'chapter_id': controller.bookDetails.chapterId.toString(),'chapter_name':controller.bookDetails.chapterName.toString});
context.pushNamed(Routes.web,queryParameters: {'book_id': controller.bookDetails.bookId.toString(),'chapter_id': controller.bookDetails.chapterId.toString(),'chapter_name':controller.bookDetails.chapterName.toString()},extra: controller.bookDetails);
}
else {
// 没有购买
......@@ -154,11 +159,11 @@ class _BookDetailPageState extends State<BookDetailPage> with SingleTickerProvid
}
}
else{
context.pushNamed(Routes.web,queryParameters: {'book_id': controller.bookDetails.bookId.toString(),'chapter_id': controller.bookDetails.chapterId.toString(),'chapter_name':controller.bookDetails.chapterName.toString()});
context.pushNamed(Routes.web,queryParameters: {'book_id': controller.bookDetails.bookId.toString(),'chapter_id': controller.bookDetails.chapterId.toString(),'chapter_name':controller.bookDetails.chapterName.toString()},extra: controller.bookDetails);
}
}
else{
context.pushNamed(Routes.web,queryParameters: {'book_id': controller.bookDetails.bookId.toString(),'chapter_id': controller.bookDetails.chapterId.toString(),'chapter_name':controller.bookDetails.chapterName.toString()});
context.pushNamed(Routes.web,queryParameters: {'book_id': controller.bookDetails.bookId.toString(),'chapter_id': controller.bookDetails.chapterId.toString(),'chapter_name':controller.bookDetails.chapterName.toString()},extra: controller.bookDetails);
}
}
},
......@@ -179,6 +184,7 @@ class _BookDetailPageState extends State<BookDetailPage> with SingleTickerProvid
),
),
),
),
);
}
......
......@@ -45,6 +45,7 @@ class BookInfoPage extends StatelessWidget {
Container(
margin: EdgeInsets.symmetric(vertical: 10.w),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Column(
mainAxisAlignment: MainAxisAlignment.center,
......@@ -71,17 +72,24 @@ class BookInfoPage extends StatelessWidget {
height: 90,
// color: Colors.green,
child: Column(
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.end,
children: List.generate(model.ratingList!.length, (index){
return _buildProgrss(5- index.toDouble(), model.ratingList![index].toDouble()/model.ratingCount!);
}).toList()
),
Text('${model.ratingCount}个评分',style: TextStyle(fontSize: 9.w,height: 1.4,color: Colours.c9),)
],
),
),
),
)
],
),
)
),
],
),
Container(height: 1,width: double.infinity,color: Colours.cF2,),
......@@ -90,19 +98,26 @@ class BookInfoPage extends StatelessWidget {
Column(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.start,
children: ['书名','作者','分类','出品方','出版社','上架时间'].map((item){
children: [
{'name':'书名','value':model.bookName},
{'name':'作者','value':model.authors},
{'name':'分类','value':model.categoryName},
{'name':'出品方','value':model.producersName},
{'name':'出版社','value':model.pressName},
{'name':'上架时间','value':Tools.dateFromMS(model.onsaleTime!.toInt(),pattern:'yyyy年MM月dd日' )},
].map((item){
return Row(
children: [
Container(
// color: Colors.cyan,
alignment: Alignment.centerRight,
width: 50,
child: Text(item,style: const TextStyle(fontSize: 11,height: 2.1,color: Colours.c3),),
child: Text(item['name'].toString(),style: const TextStyle(fontSize: 11,height: 2.1,color: Colours.c3),),
),
Gaps.hGaps20,
Container(
alignment: Alignment.centerLeft,
child: Text(item,style: const TextStyle(fontSize: 11,height: 2.1,color: Colours.c9),textAlign: TextAlign.end,),
child: Text(item['value'].toString(),style: const TextStyle(fontSize: 11,height: 2.1,color: Colours.c9),textAlign: TextAlign.end,),
),
],
);
......
......@@ -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(){
......
......@@ -113,7 +113,7 @@ class _ChangePwdPageState extends State<ChangePwdPage> {
),
Container(
margin: EdgeInsets.only(left: 5.w,top: 5.w),
child: Text('密码必须是6-20个英文字母、数字或符号',style: TextStyle(fontSize: 10.w,color: Colours.c9),),
child: Text('密码必须是8-20个英文字母、数字或符号(除空格)',style: TextStyle(fontSize: 10.w,color: Colours.c9),),
),
Gaps.vGaps40,
CustomGradientButton(
......
......@@ -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),
......
......@@ -38,10 +38,10 @@ class BuildBanner extends StatelessWidget {
// ),
),
//CustomImage.network(item.pic??'',)
child: ClipRRect(
borderRadius: BorderRadius.circular(8.w),
child: Image.network(item.pic??'',)
child: CustomImage.network(url: item.pic??'')
),
// child: CustomImage.asset(
// url: 'assets/images/banner.png',
......
part of web;
class DiscussController extends GetxController {
final BookDetailModel bookDetailModel;
// 当前的章节id
final String chapterId;
DiscussController({required this.bookDetailModel,required this.chapterId});
List<DiscussModel> discuss = [];
final EasyRefreshController refreshController = EasyRefreshController(
controlFinishLoad: true,
......@@ -32,8 +36,8 @@ class DiscussController extends GetxController {
final result = await LibraryAPI.discussList(
page: _page,
limit: _limit,
bookId: '110',
chapterId: '1'
bookId: bookDetailModel.bookId.toString(),
chapterId: chapterId
);
// 如果是刷新 清理数据
if (isRefresh) discuss.clear();
......
......@@ -2,17 +2,29 @@ library web;
import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';
import 'package:archive/archive.dart';
import 'package:audio_session/audio_session.dart';
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:easy_refresh/easy_refresh.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
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_screenutil/flutter_screenutil.dart';
import 'package:flutter_sound/public/flutter_sound_recorder.dart';
import 'package:flutter_sound_platform_interface/flutter_sound_platform_interface.dart';
import 'package:flutter_sound_platform_interface/flutter_sound_recorder_platform_interface.dart';
import 'package:get/get.dart';
import 'package:go_router/go_router.dart';
import 'package:intl/intl.dart';
import 'package:just_audio/just_audio.dart' as just_audio;
import 'package:path_provider/path_provider.dart';
import 'dart:async';
import 'package:plugin_platform_interface/plugin_platform_interface.dart';
import '../../apis/index.dart';
import '../../models/index.dart';
......@@ -32,3 +44,4 @@ part 'widgets/book.dart';
part 'note_controller.dart';
part 'discuss_controller.dart';
part 'widgets/input_discuss.dart';
part 'widgets/item.dart';
\ No newline at end of file
part of web;
class NoteController extends GetxController {
final BookDetailModel bookDetailModel;
// 当前的章节id
final String chapterId;
NoteController({required this.bookDetailModel,required this.chapterId});
List<NoteModel> notes = [];
final EasyRefreshController refreshController = EasyRefreshController(
......@@ -34,8 +37,8 @@ class NoteController extends GetxController {
final result = await LibraryAPI.noteList(
page: _page,
limit: _limit,
bookId: '110',
chapterId: '1'
bookId: bookDetailModel.bookId.toString(),
chapterId: chapterId
);
// 如果是刷新 清理数据
if (isRefresh) notes.clear();
......
......@@ -4,11 +4,13 @@ class ReadPage extends StatefulWidget {
final String bookId;
final String chapterId;
final String chapterName;
final BookDetailModel bookDetailModel;
const ReadPage({
Key? key,
required this.bookId,
required this.chapterId,
required this.chapterName,
required this.bookDetailModel
}) : super(key: key);
@override
......@@ -24,10 +26,10 @@ class _ReadPageState extends State<ReadPage> {
@override
Widget build(BuildContext context) {
return GetBuilder<ReadController>(
init: ReadController(bookId: widget.bookId, chapterId: widget.chapterId,chapterName: widget.chapterName),
init: ReadController(bookId: widget.bookId, chapterId: widget.chapterId,chapterName: widget.chapterName,bookDetailModel: widget.bookDetailModel),
builder: (readController) => Scaffold(
appBar: CustomAppBar(
title: Text(widget.chapterName),
title: Text(readController.chapterName),
centerTitle: false,
actions: [
GestureDetector(
......@@ -42,7 +44,7 @@ class _ReadPageState extends State<ReadPage> {
],
),
resizeToAvoidBottomInset: false,
floatingActionButton: readController.show?GestureDetector(
floatingActionButton: readController.show&& !readController.toolModel.selected?GestureDetector(
onTap: (){
readController.setShowChat(true);
readController.noteTitle = '你好你问你你等您第五年对哦in我ID呢哦win地哦为内地那打卡你打困哪';
......@@ -56,25 +58,33 @@ class _ReadPageState extends State<ReadPage> {
color: Colors.white,
child: Stack(
children: [
Container(
height: 40,
width: double.infinity,
color: Colors.lightBlue,
),
// Container(
// height: 40,
// width: double.infinity,
// color: Colors.lightBlue,
// ),
InAppWebView(
initialUrlRequest: URLRequest(
url: Uri.parse('http://192.168.11.46:9200/read.html'),
url: Uri.parse('http://150.158.138.40:9200/read.html'),
),
contextMenu: ContextMenu(
options: ContextMenuOptions(hideDefaultSystemContextMenuItems: true),
),
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 = {
'book_id': 110,
'chapter_id': 1,
'book_id': readController.bookId,
'chapter_id': readController.chapterId,
'token':UserStore.to.token
};
Console.log('param--------------------------------$param');
controller.evaluateJavascript(source: 'callbackInFlutterComponent("$param");');
// 添加单击事件
......@@ -232,57 +242,56 @@ class _ReadPageState extends State<ReadPage> {
);
}
/// 目录、评论、笔记
Widget _showContent(ReadController controller,ToolModel model) {
Console.log('++++++++++++++++++++++++${model.tag}');
if (controller.show){
if (model.tag == 0){
return model.selected? Container(
color: const Color(0xFF000000).withOpacity(0.5),
padding: EdgeInsets.only(top: MediaQuery.of(context).size.height * 0.2),
child: ClipRRect(
borderRadius: BorderRadius.only(topRight: Radius.circular(8.w),topLeft: Radius.circular(8.w)),
child: Container(
color: Colors.white,
child: ReadCategoryPage(controller: controller,onTap: (){
Widget detail(ReadController controller,ToolModel model){
if(model.tag == 0){
return ReadCategoryPage(controller: controller,
onTap: (){
controller.chooseTool(model);
},),
),
),
// child: ReadCategoryPage(),
):const SizedBox();
},
onTapChapter: (ChapterModel chapterModel){
print('-----------选择的章节-------------${chapterModel.name}--------');
// 配置选择的章节
controller.selectChapter(chapterModel);
// 取消选中 tool
controller.chooseTool(model);
// 选择了新的章节 刷新 webview
controller.webViewController.reload();
},
);
}
else if (model.tag == 1){
return model.selected? Container(
color: const Color(0xFF000000).withOpacity(0.5),
padding: EdgeInsets.only(top: MediaQuery.of(context).size.height * 0.2),
child: ClipRRect(
borderRadius: BorderRadius.only(topRight: Radius.circular(8.w),topLeft: Radius.circular(8.w)),
child: Container(
color: Colors.white,
child: ReadNotePage(onTap: (){
else if(model.tag == 1){
return ReadNotePage(onTap: (){
controller.chooseTool(model);
},),
),
),
// child: ReadCategoryPage(),
):const SizedBox();
},bookDetailModel: controller.bookDetailModel,chapterId: controller.chapterId,);
}
else if (model.tag == 2){
return model.selected? Container(
else if(model.tag == 2){
return ReadDiscussPage(onTap: (){
controller.chooseTool(model);
},bookDetailModel: controller.bookDetailModel,chapterId:controller.chapterId,);
}
return const SizedBox();
}
/// 目录、评论、笔记 背景
Widget _showContent(ReadController controller,ToolModel model) {
Console.log('++++++++++++++++++++++++${model.tag}');
if (controller.show){
if(model.selected){
return Container(
color: const Color(0xFF000000).withOpacity(0.5),
padding: EdgeInsets.only(top: MediaQuery.of(context).size.height * 0.2),
child: ClipRRect(
borderRadius: BorderRadius.only(topRight: Radius.circular(8.w),topLeft: Radius.circular(8.w)),
child: Container(
color: Colors.white,
child: ReadDiscussPage(onTap: (){
controller.chooseTool(model);
},),
child: detail(controller, model)
),
),
// child: ReadCategoryPage(),
):const SizedBox();
);
}
else{
return const SizedBox();
}
}
return const SizedBox();
......
part of web;
class BuildBook extends StatelessWidget {
const BuildBook({Key? key}) : super(key: key);
final BookDetailModel bookDetailModel;
const BuildBook({
Key? key,
required this.bookDetailModel
}) : super(key: key);
@override
Widget build(BuildContext context) {
......@@ -18,7 +22,7 @@ class BuildBook extends StatelessWidget {
CustomCard(
width: 72.w,
height: 86.w,
url: '',
url: bookDetailModel.img??'',
),
Container(
height: 87.w,
......@@ -31,8 +35,8 @@ class BuildBook extends StatelessWidget {
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('书名',style: TextStyle(fontSize: 14.w,height: 1.5,fontWeight: Fonts.medium,color: Colours.c3),),
Text('作者',style: TextStyle(fontSize: 12.w,height: 1.5,color: Colours.c6),),
Text(bookDetailModel.bookName??'',style: TextStyle(fontSize: 14.w,height: 1.5,fontWeight: Fonts.medium,color: Colours.c3),),
Text(bookDetailModel.authors??'',style: TextStyle(fontSize: 12.w,height: 1.5,color: Colours.c6),),
],
),
],
......
......@@ -3,10 +3,12 @@ part of web;
class ReadCategoryPage extends StatefulWidget {
final ReadController controller;
final void Function()? onTap;
final Function(ChapterModel chapterModel) onTapChapter;
const ReadCategoryPage({
Key? key,
required this.controller,
required this.onTap,
required this.onTapChapter
}) : super(key: key);
@override
......@@ -60,15 +62,18 @@ class _ReadCategoryPageState extends State<ReadCategoryPage> {
),
),
),
BuildBook(),
// Expanded(
// child: ListView.builder(
// itemBuilder:(BuildContext context, int index){
// return BuildItem(model: widget.controller.chapters[index],);
// },
// itemCount: widget.controller.chapters.length,
// ),
// ),
BuildBook(bookDetailModel: widget.controller.bookDetailModel,),
Expanded(
child: ListView.builder(
itemBuilder:(BuildContext context, int index){
return BuildItem(model: widget.controller.chapters[index],onTapChapter:(ChapterModel chapterModel){
widget.onTapChapter(chapterModel);
widget.onTap;
},);
},
itemCount: widget.controller.chapters.length,
),
),
],
),
);
......
......@@ -2,9 +2,14 @@ part of web;
class ReadDiscussPage extends StatefulWidget {
final void Function()? onTap;
final BookDetailModel bookDetailModel;
// 当前的章节id
final String chapterId;
const ReadDiscussPage({
Key? key,
required this.onTap,
required this.bookDetailModel,
required this.chapterId
}) : super(key: key);
@override
......@@ -16,7 +21,7 @@ class _ReadDiscussPageState extends State<ReadDiscussPage> {
@override
Widget build(BuildContext context) {
return GetBuilder<DiscussController>(
init: DiscussController(),
init: DiscussController(bookDetailModel: widget.bookDetailModel,chapterId: widget.chapterId),
builder:(controller) => Scaffold(
resizeToAvoidBottomInset: false,
body: Column(
......@@ -58,7 +63,7 @@ class _ReadDiscussPageState extends State<ReadDiscussPage> {
),
),
),
BuildBook(),
BuildBook(bookDetailModel: widget.bookDetailModel,),
Expanded(
child: ListView.builder(
itemBuilder: (BuildContext context,int index){
......
......@@ -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,)
)
)
],
......@@ -145,7 +145,7 @@ class _ReadInputDiscussState extends State<ReadInputDiscuss> {
),
);
},
itemCount: 0,
itemCount: widget.controller.discussInputAudios.length,
),
),
)
......@@ -174,9 +174,15 @@ class _ReadInputDiscussState extends State<ReadInputDiscuss> {
Gaps.hGaps10,
GestureDetector(
onTap: () async {
if(widget.controller.startRecording){
widget.controller.stopRecorder();
}
else {
widget.controller.record();
}
},
child: Image.asset('assets/images/read_add_audio.png')
child: Image.asset(widget.controller.startRecording?'assets/images/stop.png':'assets/images/read_add_audio.png')
),
widget.controller.chatType ==0?const SizedBox():GestureDetector(
onTap: (){
......
part of web;
class BuildItem extends StatefulWidget {
final ChapterModel model;
final Function(ChapterModel chapterModel) onTapChapter;
const BuildItem({
Key? key,
required this.model,
required this.onTapChapter
}) : super(key: key);
@override
State<BuildItem> createState() => _BuildItemState();
}
class _BuildItemState extends State<BuildItem> {
@override
Widget build(BuildContext context) {
return Column(
children: [
/// 章节名称容器
GestureDetector(
onTap: (){
if(widget.model.children!.isEmpty){
widget.onTapChapter(widget.model);
}
},
child: Container(
padding: EdgeInsets.symmetric(horizontal: 15.w),
height: 30.w,
color: Colors.white,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(widget.model.name??'',style: TextStyle(fontSize: 14.w,color: widget.model.seen ==0? Colours.c3:Colours.c9,fontWeight: Fonts.medium,height: 2),),
Gaps.hGaps5,
widget.model.isReading == 1? Container(
height: 17,
width: 17,
alignment: Alignment.center,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8.5.w),
border: Border.all(width:1,color: AppTheme.primary)
),
child: Text('试',style: TextStyle(fontSize: 12.w,color: AppTheme.primary),),
):const SizedBox(),
],
),
Transform.rotate(
angle: widget.model.selected?0:-90 * (3.141592653589793 / 180),
child: GestureDetector(
onTap: (){
setState(() {
widget.model.selected = !widget.model.selected;
});
},
child: SizedBox(
width: 20,
height: 20,
child: Image.asset('assets/images/down.png')
),
)
)
],
)
),
),
/// 节的名称容器
Visibility(
visible: widget.model.selected,
child: ListView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemBuilder: (BuildContext context, int index){
ChapterModel model = widget.model.children![index];
return GestureDetector(
onTap: (){
widget.onTapChapter(model);
},
child: _buildSection(model)
);
},
itemCount: widget.model.children!.length,
)
)
],
);
}
Widget _buildSection(ChapterModel model){
return Container(
color: Colors.white,
padding: const EdgeInsets.only(left: 60),
child: Text(model.name??'',style:TextStyle(fontSize: 12,color: model.seen ==0? Colours.c3:Colours.c9,height: 2),),
);
}
}
part of web;
class ReadNotePage extends StatefulWidget {
final BookDetailModel bookDetailModel;
// 当前的章节id
final String chapterId;
final void Function()? onTap;
const ReadNotePage({
Key? key,
required this.onTap,
required this.bookDetailModel,
required this.chapterId
}) : super(key: key);
@override
......@@ -16,7 +21,7 @@ class _ReadNotePageState extends State<ReadNotePage> {
@override
Widget build(BuildContext context) {
return GetBuilder<NoteController>(
init: NoteController(),
init: NoteController(bookDetailModel: widget.bookDetailModel,chapterId: widget.chapterId),
builder:(controller) =>Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
......@@ -56,7 +61,7 @@ class _ReadNotePageState extends State<ReadNotePage> {
),
),
),
BuildBook(),
BuildBook(bookDetailModel: widget.bookDetailModel,),
Expanded(
child: ListView.builder(
itemBuilder: (BuildContext context,int index){
......
......@@ -33,28 +33,29 @@ class _SplashPageState extends State<SplashPage> {
return Scaffold(
extendBodyBehindAppBar: true,
appBar: AppBar(),
body: const Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Expanded(
flex: 5,
child: Center(
child: CustomEmpty(
icon: CustomImage.asset(
url: 'assets/images/logo.png',
fit: BoxFit.contain,
),
title: Text('紫荆数智学堂'),
),
)
),
body: Image.asset('assets/images/splash.png',fit: BoxFit.cover,)
// const Column(
// crossAxisAlignment: CrossAxisAlignment.stretch,
// mainAxisAlignment: MainAxisAlignment.center,
// children: [
// Expanded(
// flex: 2,
// child: Center(child: CupertinoActivityIndicator(),),
// flex: 5,
// child: Center(
// child: CustomEmpty(
// icon: CustomImage.asset(
// url: 'assets/images/logo.png',
// fit: BoxFit.contain,
// ),
// title: Text('紫荆数智学堂'),
// ),
// )
],
),
// ),
// // Expanded(
// // flex: 2,
// // child: Center(child: CupertinoActivityIndicator(),),
// // )
// ],
// ),
);
}
}
......@@ -40,8 +40,8 @@ class _AboutPageState extends State<AboutPage> {
margin: EdgeInsets.only(top: 55.w),
height: 60.w,
width: 60.w,
color: Colors.cyan,
child: const CustomImage.asset(url: 'assets/images/banner.png'),
// color: Colors.cyan,
child: const CustomImage.asset(url: 'assets/images/icon.png'),
),
Gaps.vGaps15,
Text('紫荆数智学堂',style: TextStyle(fontSize: 17.w,fontWeight: Fonts.medium,color: Colours.c3),),
......
......@@ -16,6 +16,7 @@ import 'package:get/get_state_manager/src/simple/get_controllers.dart';
import 'package:go_router/go_router.dart';
import 'package:tobias/tobias.dart';
import '../../routes/index.dart';
import '../../services/index.dart';
import '../../utils/index.dart';
import '../../widgets/index.dart';
......
......@@ -68,12 +68,17 @@ class _CoinRechargePageState extends State<CoinRechargePage> with AutomaticKeepA
),
),
Gaps.vGaps15,
RichText(text: TextSpan(
GestureDetector(
onTap: (){
context.pushNamed(Routes.terms);
},
child: RichText(text: TextSpan(
children: [
TextSpan(text: '充值即代表同意',style: TextStyle(fontSize: 13.w,height: 1.5,color: Colours.c9)),
TextSpan(text: '《用户充值协议》',style: TextStyle(fontSize: 13.w,height: 1.5,color: Color(0xFF2A82D9))),
]
)),
),
Gaps.vGaps15
],
),
......
......@@ -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();
}
......
......@@ -54,6 +54,7 @@ class UserDiscussDesController extends GetxController {
refreshController.finishRefresh(IndicatorResult.success);
refreshController.resetFooter();
} catch (error) {
Console.log('------------onRefresh-----------------$error');
refreshController.finishRefresh(IndicatorResult.fail);
}
}
......
......@@ -22,6 +22,7 @@ class _BuildListPageState extends State<BuildListPage> with AutomaticKeepAliveCl
init: UserDiscussDesController(widget.tag,widget.model),
builder: (controller) =>CustomPullScrollView(
controller: controller.refreshController,
// onRefresh: controller.onRefresh,
onLoading: controller.onLoading,
child: ListView.builder(
itemBuilder: (BuildContext context,int index){
......
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 {
// 循环上传图片获取地址
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';
part 'view.dart';
part 'controller.dart';
\ No newline at end of file
差异被折叠。
......@@ -9,8 +9,12 @@ class UserInfoController extends GetxController {
void upload({
required String path
}) async {
String result = await CommonAPI.upload(path:path,fileTypes: 'user');
_changeInfo(result);
// String result = await CommonAPI.upload(path:path,fileTypes: 'user');
OssTool tool = OssTool('zxts-user-file');
print('------path--------------------------$path');
final response = await tool.putObjectFile(path);
print('------response--------------------------${response.realUri}');
_changeInfo(response.realUri.toString());
}
void show(){
......
......@@ -217,6 +217,124 @@ class _UserInfoPageState extends State<UserInfoPage> {
),
],
);
return WillPopScope(
onWillPop: () async {
context.pop(true);
return false;
},
child: GetBuilder<UserInfoController>(
init: UserInfoController(widget.userInfo),
builder: (controller) =>
Scaffold(
appBar: AppBar(
title: const Text('个人信息'),
centerTitle: true,
),
body: Container(
margin: EdgeInsets.symmetric(
horizontal: AppTheme.margin, vertical: AppTheme.margin),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8.w),
color: Colors.white,
boxShadow: [
BoxShadow(
color: const Color(0xFFC7C7C7).withOpacity(0.5),
offset: Offset(3.w, 0),
blurRadius: 10.w,
spreadRadius: 0.w,
),
],
),
child: ClipRRect(
borderRadius: BorderRadius.circular(8.w),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
/// 头像
Container(
height: 52.w,
padding: EdgeInsets.only(left: 18.w, right: 15.w),
child: GestureDetector(
onTap: () async {
final assets = await AssetsPicker.image(
context: context,
);
controller.upload(path: assets!.path);
},
child: Container(
color: Colors.transparent,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'头像',
style: TextStyle(
color: Colours.c3,
fontSize: 14.w,
height: 1.6),
),
Row(
children: [
Container(
width: 33.w,
height: 33.w,
decoration: const BoxDecoration(
shape: BoxShape.circle,
color: Colors.cyan,
),
child: CustomImage.network(
url: controller.userInfo.headImg ?? '',
radius: 16.5,
),
),
Gaps.hGaps10,
SizedBox(
width: 5.w,
height: 8.w,
child: Image.asset(
'assets/images/right_arrow.png'),
)
],
)
],
),
),
),
),
Container(
color: Colours.cLine,
margin: EdgeInsets.symmetric(horizontal: 15.w),
height: 1.w,
),
GestureDetector(
onTap: () {
context.pushNamed(Routes.nike, extra: controller.userInfo);
},
child: _buildItem(
'昵称',
widget.userInfo.name ?? '',
)),
Container(
color: Colours.cLine,
margin: EdgeInsets.symmetric(horizontal: 15.w),
height: 1.w,
),
GestureDetector(
onTap: () {
context.pushNamed(
Routes.gender, extra: controller.userInfo);
},
child: _buildItem(
'性别',
getGender(widget.userInfo.sex)
// widget.userInfo.sex == 1 ? '男' : '女',
)),
],
),
),
)),),
);
}
getGender(num? sex) {
......
......@@ -35,12 +35,24 @@ class MsgPage extends StatelessWidget {
if(model.type == 1){
// 1订单支付快要超时(跳转订单详情)
final result = await context.pushNamed(Routes.order);
if (result == true){
controller.onRefresh();
}
///TODO:
}else if (model.type == 2){
// 2 购买完成三天未评价(跳转订单列表--已完成)
final result = await context.pushNamed(Routes.order);
if (result == true){
controller.onRefresh();
}
///TODO:
}else if (model.type == 3){
// 3 讨论有人回复的时候 (跳转对应书籍的讨论页面)
final result = await context.pushNamed(Routes.bookDetail,queryParameters: {'book_id':model.urlId?.bookId.toString()});
if (result == true){
controller.onRefresh();
}
}
else if (model.type == 4){
// 4 订单完成后有新的积分增加(跳转用户积分记录页)
......
......@@ -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,9 +27,44 @@ 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();
}
}
/// 获取笔记列表
Future<void> _getNotes([bool isRefresh = false]) async {
......@@ -39,6 +76,15 @@ class UserNotesDesController extends GetxController {
bookId: model.bookId.toString(),
types: tag
);
for(NoteModel noteModel in result){
if(noteModel.noteContent!.audio!.isNotEmpty){
for(MediaModel mediaModel in noteModel.noteContent!.audio!){
Duration? duration = await just_audio.AudioPlayer().setUrl(mediaModel.content??'');
mediaModel.duration = Tools.formatDuration(duration!);
}
}
}
// 如果是刷新 清理数据
if (isRefresh) notes.clear();
notes.addAll(result);
......@@ -54,6 +100,7 @@ class UserNotesDesController extends GetxController {
refreshController.finishRefresh(IndicatorResult.success);
refreshController.resetFooter();
} catch (error) {
Console.log('----error--------------------------------------$error--------------');
refreshController.finishRefresh(IndicatorResult.fail);
}
}
......
......@@ -4,12 +4,16 @@ import 'package:easy_refresh/easy_refresh.dart';
import 'package:flutter/material.dart';
import 'package:flutter_book/widgets/index.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:flutter_slidable/flutter_slidable.dart';
import 'package:get/get.dart';
import 'package:go_router/go_router.dart';
import '../../apis/index.dart';
import '../../models/index.dart';
import '../../routes/index.dart';
import '../../theme.dart';
import '../../utils/index.dart';
import 'package:just_audio/just_audio.dart' as just_audio;
part 'view.dart';
......
......@@ -2,16 +2,17 @@ part of user_notes_des;
class BuildHigh extends StatelessWidget {
final NoteModel model;
final void Function()? onTapDel;
const BuildHigh({
Key? key,
required this.model
required this.model,
this.onTapDel
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
margin: EdgeInsets.only(left: 10.w,right: 10.w,top: 10.w),
padding: EdgeInsets.all(10.w),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(4.w),
color: Colors.white,
......@@ -24,6 +25,24 @@ class BuildHigh extends StatelessWidget {
),
],
),
child: Slidable(
endActionPane: ActionPane(
motion: const ScrollMotion(),
children: [
SlidableAction(
// An action can be bigger than the others.
onPressed: (BuildContext context){
onTapDel;
},
backgroundColor: const Color(0xFFAE1414),
foregroundColor: Colors.white,
// icon: Icons.archive,
label: '删除',
),
],
),
child: Container(
padding: EdgeInsets.all(10.w),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
......@@ -42,6 +61,8 @@ class BuildHigh extends StatelessWidget {
)
],
),
),
),
);
}
}
......@@ -2,16 +2,17 @@ part of user_notes_des;
class BuildLine extends StatelessWidget {
final NoteModel model;
final void Function()? onTapDel;
const BuildLine({
Key? key,
required this.model
required this.model,
this.onTapDel
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
margin: EdgeInsets.only(left: 10.w,right: 10.w,top: 10.w),
padding: EdgeInsets.all(10.w),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(4.w),
color: Colors.white,
......@@ -24,6 +25,24 @@ class BuildLine extends StatelessWidget {
),
],
),
child: Slidable(
endActionPane: ActionPane(
motion: const ScrollMotion(),
children: [
SlidableAction(
// An action can be bigger than the others.
onPressed: (BuildContext context){
onTapDel;
},
backgroundColor: const Color(0xFFAE1414),
foregroundColor: Colors.white,
// icon: Icons.archive,
label: '删除',
),
],
),
child: Container(
padding: EdgeInsets.all(10.w),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
......@@ -45,6 +64,8 @@ class BuildLine extends StatelessWidget {
)
],
),
),
),
);
}
}
......@@ -28,16 +28,35 @@ class _BuildListPageState extends State<BuildListPage> with AutomaticKeepAliveCl
NoteModel model = controller.notes[index];
// 划线
if(model.types == 1){
return BuildLine(model: model,);
return BuildLine(model: model,
onTapDel: (){
controller.delNotes(notesId: model.notesId.toString(), bookId:widget.model.bookId.toString());
},
);
}
// 高亮
else if(model.types == 2){
return BuildHigh(model: model,);
return BuildHigh(model: model,
onTapDel: (){
controller.delNotes(notesId: model.notesId.toString(), bookId:widget.model.bookId.toString());
},
);
}
// 笔记
else if(model.types == 3){
return BuildNote(model: model,);
return BuildNote(model: model,
onTapDel: (){
controller.delNotes(notesId: model.notesId.toString(), bookId:widget.model.bookId.toString());
},
onTapEdit: (){
context.pushNamed(Routes.editNote,extra: model,queryParameters: {'book_id':widget.model.bookId.toString()});
},
onTapAudio: (mediaModel){
controller.playAudio(mediaModel);
},
);
}
return null;
},
itemCount: controller.notes.length,
),
......
......@@ -2,16 +2,22 @@ part of user_notes_des;
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
required this.model,
this.onTapDel,
this.onTapEdit,
this.onTapAudio
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
margin: EdgeInsets.only(left: 10.w,right: 10.w,top: 10.w),
padding: EdgeInsets.all(10.w),
// padding: EdgeInsets.all(10.w),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(4.w),
color: Colors.white,
......@@ -24,10 +30,39 @@ class BuildNote extends StatelessWidget {
),
],
),
child: Slidable(
endActionPane: ActionPane(
motion: const ScrollMotion(),
children: [
SlidableAction(
// An action can be bigger than the others.
onPressed: (BuildContext context){
if (onTapEdit !=null) onTapEdit!();
},
backgroundColor: const Color(0xFFEB914A),
foregroundColor: Colors.white,
// icon: Icons.archive,
label: '编辑',
),
SlidableAction(
// An action can be bigger than the others.
onPressed: (BuildContext context){
if (onTapDel !=null) onTapDel!();
},
backgroundColor: const Color(0xFFAE1414),
foregroundColor: Colors.white,
// icon: Icons.archive,
label: '删除',
),
],
),
child: Container(
padding: EdgeInsets.all(10.w),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('看来谁都不记得自己为何出现在此',style: TextStyle(
Text(model.noteContent?.text?.content??'',style: TextStyle(
fontSize: 14.w,
height: 1.5,
// color: Colors.red,
......@@ -52,6 +87,8 @@ class BuildNote extends StatelessWidget {
)
],
),
),
),
);
}
......@@ -59,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,
......@@ -68,12 +105,9 @@ class BuildNote extends StatelessWidget {
childAspectRatio: 1
),
itemBuilder: (BuildContext context, int index) {
return Container(
color: Colors.red,
child: Center(child: Text('图片')),
);
return CustomImage.network(url: model.noteContent!.image![index].content??'',fit: BoxFit.cover,);
},
itemCount: 3,
itemCount: model.noteContent?.image?.length,
);
}
......@@ -82,6 +116,7 @@ class BuildNote extends StatelessWidget {
physics: const NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemBuilder: (BuildContext context, int index) {
MediaModel mediaModel = model.noteContent!.audio![index];
return Container(
height: 20.w,
margin: EdgeInsets.only(right: 130.w),
......@@ -96,34 +131,19 @@ class BuildNote extends StatelessWidget {
// mainAxisSize: MainAxisSize.min,
mainAxisAlignment:MainAxisAlignment.spaceBetween,
children: [
Image.asset('assets/images/audio.png'),
Text('0:00/1:52',style: TextStyle(fontSize: 10.w,height: 1.4,color: Colours.c9),)
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),)
],
),
),
);
},
itemCount: 3,
itemCount: model.noteContent?.audio?.length,
);
}
// Widget _buildAudioGridView(){
// return GridView.builder(
// physics: const NeverScrollableScrollPhysics(),
// shrinkWrap: true,
// gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
// crossAxisCount: 6,
// crossAxisSpacing: 2.w,
// mainAxisSpacing: 2.w,
// childAspectRatio: 1
// ),
// itemBuilder: (BuildContext context, int index) {
// return Container(
// color: Colors.red,
// child: Center(child: Text('音频')),
// );
// },
// itemCount: 3,
// );
// }
}
......@@ -45,6 +45,7 @@ import 'package:go_router/go_router.dart';
import '../models/index.dart';
import '../pages/bai_ke/index.dart';
import '../pages/read_web/index.dart';
import '../pages/user_edit_note/index.dart';
import '../pages/user_order/index.dart';
import '../pages/user_order_evaluate/index.dart';
import '../pages/pay_coupon/index.dart';
......
......@@ -79,6 +79,8 @@ abstract class Routes {
static const note = 'note';
// 笔记详情
static const noteDes = 'note_des';
// 编辑笔记
static const editNote = 'edit_note';
// 错题
static const wrong = 'wrong';
// 讨论
......@@ -189,6 +191,7 @@ abstract class Routes {
bookId: state.uri.queryParameters['book_id'].toString(),
chapterId: state.uri.queryParameters['chapter_id'].toString(),
chapterName: state.uri.queryParameters['chapter_name'].toString(),
bookDetailModel: state.extra as BookDetailModel,
)
)
),
......@@ -553,6 +556,15 @@ abstract class Routes {
)
)
),
GoRoute(
path: '/$editNote',
name: editNote,
pageBuilder: (context, state) =>CupertinoPage(
name: state.uri.toString(),
key: state.pageKey,
child: UserEditNotePage(model: state.extra as NoteModel,bookId: state.uri.queryParameters['book_id'].toString(),)
)
),
GoRoute( // 订单搜索
path: '/$orderSearch',
name: orderSearch,
......
......@@ -19,13 +19,14 @@ class HttpService extends GetxService {
_dio = Dio(options);
_dio.interceptors.add(_RequestInterceptor());
_dio.interceptors.add(_CacheInterceptor());
}
/// 组织 headers 参数
Map<String,dynamic>? _getHeaders({bool excludeToken = false,Map<String,dynamic>? params,String? url}) {
final headers = <String, dynamic>{};
headers['appId'] = AppConfig.AppID;
headers['appSecret'] = AppConfig.AppSecret;
headers['appId'] = AppConfig.appID;
headers['appSecret'] = AppConfig.appSecret;
headers['timestamp'] = (DateTime.now().millisecondsSinceEpoch~/1000).toString();
headers['url'] = kServerUrl + url.toString();
......@@ -56,11 +57,18 @@ class HttpService extends GetxService {
CancelToken? cancelToken,
bool excludeToken = false,
bool showLoading = false,
bool cacheEnabled = false,
}) async {
if (showLoading) CustomToast.loading();
try {
final requestOptions = options ?? Options();
requestOptions.headers = _getHeaders(excludeToken: excludeToken,params: params,url: url);
// 如果启用缓存,将cacheEnabled参数添加到请求选项中
if(cacheEnabled){
requestOptions.extra ??= {};
requestOptions.extra!['cacheEnabled'] = true;
}
final response = await _dio.get(
url,
queryParameters: params,
......@@ -83,6 +91,7 @@ class HttpService extends GetxService {
CancelToken? cancelToken,
bool excludeToken = false,
bool showLoading = false,
bool cacheEnabled = true,
}) async{
if (showLoading) CustomToast.loading();
try {
......@@ -90,6 +99,12 @@ class HttpService extends GetxService {
requestOptions.headers = _getHeaders(excludeToken: excludeToken,params: params,url: url);
Console.log('----headers------${requestOptions.headers}');
Console.log('----params------$params');
// 如果启用缓存,将cacheEnabled参数添加到请求选项中
if(cacheEnabled){
requestOptions.extra ??= {};
requestOptions.extra!['cacheEnabled'] = false;
}
final response = await _dio.post(
url,
data: params,
......@@ -169,25 +184,10 @@ class HttpService extends GetxService {
class _RequestInterceptor extends Interceptor {
@override
void onResponse(Response response, ResponseInterceptorHandler handler) async {
final responseData = response.data;
if (responseData is Map && responseData.containsKey('code')) {
final code = responseData['code'];
if (code != 200) {
handler.reject(
DioException(
requestOptions: response.requestOptions,
response: response,
type: DioExceptionType.badResponse,
),
true,
);
return;
}
} else if (response.requestOptions.responseType == ResponseType.bytes) {
}
super.onResponse(response, handler);
// if (response.data['code'] != 200) {
// final responseData = response.data;
// if (responseData is Map && responseData.containsKey('code')) {
// final code = responseData['code'];
// if (code != 200) {
// handler.reject(
// DioException(
// requestOptions: response.requestOptions,
......@@ -196,9 +196,24 @@ class _RequestInterceptor extends Interceptor {
// ),
// true,
// );
// }else {
// super.onResponse(response, handler);
// return;
// }
// } else if (response.requestOptions.responseType == ResponseType.bytes) {
//
// }
// super.onResponse(response, handler);
if (response.data['code'] != 200) {
handler.reject(
DioException(
requestOptions: response.requestOptions,
response: response,
type: DioExceptionType.badResponse,
),
true,
);
}else {
super.onResponse(response, handler);
}
}
@override
......@@ -226,7 +241,7 @@ class _RequestInterceptor extends Interceptor {
var msg = '服务器错误';
switch (statusCode) {
case 403:
print('-------------------access_token-------------------------${UserStore.to.accessToken}--------------------');
print('----------403---------access_token-------------------------${UserStore.to.accessToken}--------------------');
msg = '$statusCode - Unauthorized';
final newToken = await refreshToken();
if (newToken != null) {
......@@ -260,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();
......@@ -296,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) {
......@@ -313,4 +328,61 @@ class _RequestInterceptor extends Interceptor {
}
// 缓存拦截器
class _CacheInterceptor extends Interceptor {
@override
void onRequest(RequestOptions options, RequestInterceptorHandler handler) async {
final cacheEnabled = options.extra?['cacheEnabled'] ?? false;
if (cacheEnabled) {
// 在发起请求之前,检查缓存是否存在有效数据
final cachedFile = await DefaultCacheManager().getFileFromCache(options.uri.toString());
if (cachedFile != null && cachedFile.validTill.isAfter(DateTime.now())) {
// 如果缓存有效,直接返回缓存数据
final cachedData = await cachedFile.file.readAsBytes();
final decodedData = utf8.decode(cachedData); // 将字节列表解码为字符串
final jsonData = jsonDecode(decodedData);
handler.resolve(Response(
requestOptions: options,
statusCode: 200,
data: jsonData,
));
return;
}
}
// 如果缓存不存在或已过期,或未启用缓存,继续发起网络请求
handler.next(options);
}
@override
void onResponse(Response response, ResponseInterceptorHandler handler) async {
// 只缓存状态码为200的响应
if (response.statusCode == 200) {
try {
// 获取请求的 URL
final url = response.requestOptions.uri.toString();
// 获取请求体参数(如果是 POST 请求)
final requestBody = response.requestOptions.data.toString();
// 将 GET 请求的参数和请求体参数拼接成缓存的键
final cacheKey = requestBody.isEmpty ? url : '$url?$requestBody';
// Console.log('----------cacheKey-----------------------$cacheKey');
// 将响应数据转换为字符串,并将其编码为字节列表
List<int> bytes = utf8.encode(jsonEncode(response.data));
Uint8List uint8List = Uint8List.fromList(bytes);
// 创建一个缓存文件并将数据写入其中
DefaultCacheManager().putFile(
cacheKey,
uint8List,
fileExtension: '.json', // 可以根据需求修改文件扩展名
maxAge: const Duration(hours: 1)
);
} catch (e) {
Console.log('Error caching response: $e');
}
}
super.onResponse(response, handler);
}
}
......@@ -2,11 +2,13 @@ library services;
import 'dart:async';
import 'dart:convert';
import 'dart:typed_data';
import 'package:dio/dio.dart';
import 'package:flutter_book/store/index.dart';
import 'package:flutter_book/utils/index.dart';
import 'package:flutter_book/widgets/index.dart';
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
import 'package:get/get.dart' hide Response, FormData, MultipartFile;
import 'package:shared_preferences/shared_preferences.dart';
......
......@@ -31,4 +31,10 @@ abstract class Access {
return result[Permission.storage] == PermissionStatus.granted;
}
/// 获取麦克风权限
static Future<bool> microphone() async {
final result = await [Permission.microphone].request();
return result[Permission.microphone] == PermissionStatus.granted;
}
}
......@@ -2,10 +2,14 @@ part of utils;
class AppConfig{
//app在服务端注册的id,由后端直接提供
static const String AppID = 'dkFv6OwWyQhSeaZG';
static const String appID = 'dkFv6OwWyQhSeaZG';
//app在服务端的秘钥串,由后端直接提供
static const String AppSecret = 'd34cea92316d1da113e95158bed9ca97';
static const String appSecret = 'd34cea92316d1da113e95158bed9ca97';
//签名时使用的加密串,由后端直接提供
static const String AppEncodeKey = '&4F6g4Y6b5L4R9';
static const String appEncodeKey = '&4F6g4Y6b5L4R9';
// 下载需要的key
static const String aesKey = 'e8a83e801cf85d90b69556b0cb1e0c8a';
static const String aesIV = '13e801c8901b6955';
}
......@@ -14,6 +14,7 @@ const String kFailOrder = 'failOrder';
abstract class C {
static const String localAccount = 'storage_account';
static const String localThemeMode = 'storage_theme_mode';
......
......@@ -7,4 +7,25 @@ class EncryptUtil {
var digest = md5.convert(content);
return hex.encode(digest.bytes);
}
/// AES 加密
static aesEncrypt(String content,[String aesKey = AppConfig.aesKey,]) {
final key = encrypt.Key.fromUtf8(aesKey);
final iv = encrypt.IV.fromBase64(base64Encode(utf8.encode(AppConfig.aesIV)));
final encryptor = encrypt.Encrypter(encrypt.AES(key, mode: encrypt.AESMode.cbc),);
final encrypted = encryptor.encrypt(content, iv: iv);
return base64Encode(utf8.encode(encrypted.base64));
}
/// AES 解密
static aesDecrypt(String content,[String aesKey = AppConfig.aesKey,]) {
final key = encrypt.Key.fromUtf8(aesKey);
final iv = encrypt.IV.fromBase64(base64Encode(utf8.encode(AppConfig.aesIV)));
final encryptor = encrypt.Encrypter(encrypt.AES(key, mode: encrypt.AESMode.cbc));
final encrypted = encrypt.Encrypted.fromBase64(utf8.decode(base64.decode(content)));
final decrypted = encryptor.decrypt(encrypted, iv: iv);
return decrypted.trimRight();
}
}
\ No newline at end of file
library utils;
import 'dart:convert';
import 'dart:convert';
import 'dart:convert';
import 'dart:io';
import 'dart:ui';
// import 'package:encrypt/encrypt.dart';
import 'package:dio/dio.dart';
import 'package:encrypt/encrypt.dart' as encrypt; // 使用别名 'encrypt'
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_book/apis/index.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:image_picker/image_picker.dart';
import 'package:intl/intl.dart';
......@@ -17,6 +23,8 @@ import 'package:oktoast/oktoast.dart';
import 'package:path_provider/path_provider.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:flutter_oss_aliyun/flutter_oss_aliyun.dart';
import '../models/index.dart';
import '../widgets/index.dart';
......@@ -33,3 +41,4 @@ part 'toast_utils.dart';
part 'clear_cache_util.dart';
part 'access.dart';
part 'assets_picker.dart';
part 'oss.dart';
\ No newline at end of file
part of utils;
//'bucket_comment' => 'zxts-comment-file', 'bucket_user' => 'zxts-user-file', 'endpoint' => 'zijingebook.com',
class OssTool {
late OssModel ossModel;
final String bucketName;
OssTool(this.bucketName){
initOss(bucketName);
}
void initOss(String bucketName) async {
Client.init(ossEndpoint: 'oss-cn-beijing.aliyuncs.com', bucketName:bucketName,authGetter: _authGetter);
}
// 获取临时凭证
Future<Auth> _authGetter() async {
final result = await CommonAPI.oss();
return Auth(
accessKey: result.accessKeyId!,
accessSecret: result.accessKeySecret!,
expire: result.expiration!,
secureToken: result.securityToken!,
);
}
// 本地文件上传
Future<Response<dynamic>> putObjectFile(String filePath) async {
print('------------------222222222222222222222--------------$filePath');
print('------------------222222222222222222222--------------');
List<String> parts = filePath.split('/');
String fileName = parts.last;
final String path = '${DateTime.now().year}/${DateTime.now().month.toString().padLeft(2,'0')}/${DateTime.now().day.toString().padLeft(2,'0')}';
final Response<dynamic> resp = await Client().putObjectFile(
filePath,
fileKey: '$path/$fileName'
);
print('99999999999-------------${resp.realUri}---${resp.extra}--${resp.data}');
return resp;
}
// 批量本地文件上传
Future<List<Response>> putObjectFiles(List<String> filePaths){
return Client().putObjectFiles(
filePaths.map((e){
return AssetFileEntity(
filepath: e
);
}).toList()
);
}
}
\ No newline at end of file
......@@ -8,7 +8,7 @@ class SignTool {
tempArr.add("$key=${tempParams[key]}");
}
String tempStr = tempArr.join("&");
String finalString = tempStr + AppConfig.AppEncodeKey;
String finalString = tempStr + AppConfig.appEncodeKey;
String finalEncodeStr = EncryptUtil.encodeMd5(EncryptUtil.encodeMd5(finalString));
return finalEncodeStr;
}
......
......@@ -5,4 +5,51 @@ abstract class Tools {
static void unfocus() {
WidgetsBinding.instance.focusManager.primaryFocus?.unfocus();
}
static String dateFromMS(
int timestamp, {
String pattern = 'yyyy-MM-dd',
bool humanize = false,
}) {
final dateTime = DateTime.fromMillisecondsSinceEpoch(timestamp*1000);
if (humanize) {
final now = DateTime.now();
final difference = now.difference(dateTime);
if (difference.inMinutes < 60) {
if (difference.inMinutes < 1) return '刚刚';
return '${difference.inMinutes}分钟前';
} else if (difference.inHours < 24) {
return '${difference.inHours}小时前';
} else if (difference.inDays < 30) {
return '${difference.inDays}天前';
} else if (now.year == dateTime.year) {
return DateFormat('MM-dd').format(dateTime);
}
}
return DateFormat(pattern).format(dateTime);
}
static String formatDuration(Duration duration) {
String twoDigits(int n) => n.toString().padLeft(2, '0');
String twoDigitMinutes = twoDigits(duration.inMinutes.remainder(60));
String twoDigitSeconds = twoDigits(duration.inSeconds.remainder(60));
return "${duration.inHours}:$twoDigitMinutes:$twoDigitSeconds";
}
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';
}
}
差异被折叠。
......@@ -103,6 +103,17 @@ dependencies:
tobias: ^3.3.0
# 内购
flutter_inapp_purchase: ^5.6.1
encrypt: ^5.0.3
# 阿里oss直传
flutter_oss_aliyun: ^6.4.1
just_audio: ^0.9.36
# 缓存
flutter_cache_manager: ^3.3.1
# 解压
archive: ^3.1.2
# 判断当前网络情况
connectivity_plus: ^5.0.2
dev_dependencies:
flutter_test:
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论