需求1:发帖退出时提示是否保存草稿
需求2:每条草稿中可以保存多张图片(最多9张)或一条视频及三十来个其它参数
需求3:每条草稿都是可以被覆盖的、可以点击删除
需求4:草稿页面可以一键清空
需求5:草稿随app删除一起没掉
看到需求第一时间想到的就是存轻量级SharedPreferences中;
行动:将图片转为base64,然后将base64转json从而实现将图片存入轻量级
dart
/*
* Asset类型图片转base64(其它类型替换能获取字节的对象)
* */
imageToUint8List(List<Asset> imageList) async {
Completer<List<String>> _completer = Completer<List<String>>();
List<String> dataList = [];
if(imageList.length > 0){
for (int i = 0; i < imageList.length; i++) {
Asset asset = imageList[i];
ByteData byteData = await asset.getByteData();
var imageBase64 = base64.encode(Uint8List.view(byteData.buffer));
dataList.add(imageBase64);
}
_completer.complete(dataList);
}
return _completer.future;
}
结果:行不通。转换加储存极易造成内存泄漏且耗时太多(一张图片都有可能10mb以上而需求更是多图)
方法:
1.在app的file目录下新建一个文件夹保存草稿箱时把图片、视频文件复制进去。
2.把普通的字符串、数字等参数和图片、视频的文件路径封装转成json字符串一起存入轻量级
封装工具
dart
import 'dart:convert';
import 'dart:io';
import 'package:test/common/sp_util.dart';
import 'package:test/generated/l10n.dart';
import 'package:test/jade/bean/DraftsBean.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:path_provider/path_provider.dart';
class DraftsUtil{
/*
* 保存草稿箱提示框
* */
draftsSaveDialog(context, {Function callback,Function cancelCallback}) async {
showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return CupertinoAlertDialog(
content: SingleChildScrollView(
child: ListBody(
children: <Widget>[
Padding(
child: Text(
S.current.tuichu,
style: TextStyle(fontSize: 15, color: Color(0xffea2020)),
),
padding: EdgeInsets.only(top: 15),
),
SizedBox(height: 10),
Padding(
child: Text(
'是否保存编辑到草稿箱?',
style: TextStyle(fontSize: 15, color: Color(0xff666666)),
),
padding: EdgeInsets.only(bottom: 15),
)
],
),
),
actions: <Widget>[
CupertinoDialogAction(
child: Text(
S.current.quxiao,
style: TextStyle(fontSize: 14, color: Color(0xff999999)),
),
onPressed: () {
if(cancelCallback != null){
cancelCallback();
}
Navigator.pop(context, false);
},
),
CupertinoDialogAction(
child: Text(
'保存并退出',
style: TextStyle(fontSize: 14, color: Color(0xff4ec6cc)),
),
onPressed: () {
if (callback != null) {
callback();
}
Navigator.pop(context, false);
},
)
],
);
});
}
/*
* 读取草稿列表数据
* */
readDraftsList(var userId){
List<DraftsBean> draftsList = [];
List<Map<dynamic, dynamic>> getDraftsList = SpUtil.getObjectList('${userId}draftsList');
if (getDraftsList != null && getDraftsList.length > 0) {
getDraftsList.forEach((element) {
draftsList.add(DraftsBean(type: element['type'],paramsData: element['paramsData'],time: element['time']));
});
}
return draftsList;
}
/*
* 删除某一条草稿
* */
deleteDrafts(var userId,int index,{List<DraftsBean> draftsList}){
if(draftsList == null){
draftsList = [];
List<Map<dynamic,dynamic>> _getDraftsList = SpUtil.getObjectList('${userId}draftsList');
if (_getDraftsList != null && _getDraftsList.length > 0) {
_getDraftsList.forEach((element) {
draftsList.add(DraftsBean(
type: element['type'],
paramsData: element['paramsData'],
time: element['time']
));
});
}
}
var _paramsData = json.decode(draftsList[index].paramsData);
List <dynamic> imagePathList = _paramsData['selectImageList'];
String videoPath = _paramsData['selectVideoPath'];
if(imagePathList != null && imagePathList.length>0){
imagePathList.forEach((element) {
var imageFile = File(element);
if(imageFile.existsSync()){
imageFile.deleteSync();
}
});
}
if(videoPath != null){
var videoFile = File(videoPath);
if(videoFile.existsSync()){
videoFile.deleteSync();
}
}
draftsList.removeAt(index);
SpUtil.putObjectList('${userId}draftsList',draftsList);
}
//清空草稿箱
clearDrafts(var userId) async {
Directory libDir = await getExternalStorageDirectory();
String draftsPath = libDir.path + '/draftsFiles';
var directory = Directory(draftsPath);
try{
bool exists = await directory.exists();
if(exists){
directory.deleteSync(recursive: true);
}
}catch(e){
print('删除文件夹出错:${e.toString()}');
}
SpUtil.remove('${userId}draftsList');
}
}
草稿箱参数对象
dart
class DraftsBean {
int type; //草稿类型: 0普通发帖 1 A型发帖 2 B型发帖
String paramsData;//保存的发帖参数 json字符串
String time; //日期
DraftsBean({this.type, this.paramsData,this.time});
DraftsBean.fromJson(dynamic json) {
type = json['type'];
paramsData = json['paramsData'];
time = json['time'];
}
Map<String, dynamic> toJson() {
final map = <String, dynamic>{};
map['type'] = type;
map['paramsData'] = paramsData;
map['time'] = time;
return map;
}
}
引用
dart
//草稿箱提示框
_backDrafts(context){
if(logic.canSaveDrafts()){
DraftsUtil().draftsSaveDialog(context,
cancelCallback: (){
Navigator.of(context).pop();
},
callback: () async {
print('========点击了保存草稿');
//当前时间
DateTime nowTime = DateTime.now();
var _dateFormatStr = DateUtil.formatDate(nowTime, format: DateFormats.y_mo_d);
state.selectImagePathList.clear();
state.selectVideoPath = null;
List<DraftsBean> draftsList = DraftsUtil().readDraftsList(logic.userInfo.user.id);
if(state.selectImageList.length>0){
state.selectImagePathList = await Utils().copyFile(state.selectImageList);
}else if(state.videoPath != null){
List<String> videoPathList = await Utils().copyFile([File(state.videoPath)]);
state.selectVideoPath = videoPathList.first;
}
if(widget.draftsIndex != null ){
//从草稿箱进来的,传递了草稿的index,先删除再插入用来覆盖该条
DraftsUtil().deleteDrafts(logic.userInfo.user.id, widget.draftsIndex,draftsList: draftsList);
List<DraftsBean> latestDraftsList = DraftsUtil().readDraftsList(logic.userInfo.user.id);
latestDraftsList.insert(widget.draftsIndex, DraftsBean(
type: logic.state.type == 0? 1:2,
paramsData: json.encode(logic.paramsData()),
time: '$_dateFormatStr',
));
SpUtil.putObjectList('${logic.userInfo.user.id}draftsList',latestDraftsList);
}else{
draftsList.insert(0, DraftsBean(
type: logic.state.type == 0? 1:2,
paramsData: json.encode(logic.paramsData()),
time: '$_dateFormatStr',
));
SpUtil.putObjectList('${logic.userInfo.user.id}draftsList',draftsList);
}
ATuiEventBusTool.shared().fire(CommonConfig.draftsPageRefresh);
Navigator.of(context).pop();
});
}else{
Navigator.of(context).pop();
}
}
草稿箱列表页面
dart
import 'dart:convert';
import 'dart:io';
import 'package:test/common/sp_util.dart';
import 'package:test/jade/bean/DraftsBean.dart';
import 'package:test/jade/configs/CommonConfig.dart';
import 'package:test/jade/configs/PathConfig.dart';
import 'package:test/jade/customWidget/EmptyWidget.dart';
import 'package:test/jade/homePage/wantHave/WantPost.dart';
import 'package:test/jade/homePage/wantHave/wantHaveLogic/WantHaveLogic.dart';
import 'package:test/jade/homePage/wantHave/wantHaveLogic/WantHavePostBinding.dart';
import 'package:test/jade/mine/drafts/DraftsUtil.dart';
import 'package:test/jade/mine/draftsVideoItemView.dart';
import 'package:test/jade/utils/JadeColors.dart';
import 'package:test/jade/utils/Utils.dart';
import 'package:test/model/user/user_info_model.dart';
import 'package:test/pages/user_share/edit/push_share_edit.dart';
import 'package:test/util/eventbustool.dart';
import 'package:test/util/navigator_util.dart';
import 'package:test/widget/custom_appbar.dart';
import 'package:city_pickers/city_pickers.dart';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
import 'package:get/get.dart';
import 'package:provider/provider.dart';
class DraftsPage extends StatefulWidget{
@override
State<StatefulWidget> createState() {
// TODO: implement createState
return _DraftsPage();
}
}
class _DraftsPage extends State<DraftsPage>{
String _userId;
List<DraftsBean> _draftsList = [];
var _eventBus;
@override
void initState() {
// TODO: implement initState
super.initState();
_userId = Provider.of<UserInfoModel>(context, listen: false).user?.id;
_draftsList = DraftsUtil().readDraftsList(_userId);
_eventBus = ATuiEventBusTool.shared().on().listen((event) {
if(event == CommonConfig.draftsPageRefresh){
_draftsList = DraftsUtil().readDraftsList(_userId);
setState(() {});
}
});
}
@override
void dispose() {
// TODO: implement dispose
if(_eventBus != null){
_eventBus.cancel();
_eventBus = null;
}
super.dispose();
}
@override
Widget build(BuildContext context) {
// TODO: implement build
return Scaffold(
backgroundColor: JadeColors.lightGrey,
appBar: CustomAppBar(
elevation: 0.5,
leading: GestureDetector(
onTap: () {
Navigator.of(context).pop();
},
child: Icon(
Icons.arrow_back_ios,
color: Colors.black,
),
),
title: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Container(),
Container(
margin: EdgeInsets.only(right: 34.w),
child: Text(
'草稿箱',
style: TextStyle(color: Colors.black,fontSize: 38.sp,fontWeight: FontWeight.w400),
),
),
GestureDetector(
child: Container(
color: Colors.transparent,
padding: EdgeInsets.only(left: 25.w,top: 6.w,bottom: 6.w),
child: Text('清空',style: TextStyle(color: JadeColors.grey_2,fontSize:32.sp),),
),
onTap: () async {
var bool = await Utils.commonDialog(context,"是否清空草稿?");
if (bool) {
DraftsUtil().clearDrafts(_userId);
setState(() {
_draftsList.clear();
});
}
},
)
],
)
),
body: _draftsList.length>0?_body(): EmptyLayout(description: '没有草稿~',),
);
}
_body(){
return Column(
children: [
Container(
margin: EdgeInsets.only(left: 20.w,right: 20.w,top: 24.w,bottom: 40.w),
alignment: Alignment.centerLeft,
child: Text('草稿在APP卸载后会被删除,请及时发布。',style: TextStyle(color: JadeColors.grey_2,fontSize: 24.sp,),)
),
Expanded(child: Container(
margin: EdgeInsets.symmetric(horizontal: 20.w,),
padding: EdgeInsets.only(bottom: 20.w),
child: MasonryGridView.count(
crossAxisCount: 2,
itemBuilder: (BuildContext context, int index){
var data = _draftsList[index].paramsData;
var decodeData = json.decode(data);
return _itemView(index,decodeData);
},
mainAxisSpacing: 6.0,
crossAxisSpacing: 6.0,
itemCount: _draftsList.length,
shrinkWrap: true,
)
))
],
);
}
_itemView(int index,var paramsData){
return GestureDetector(
child: Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(10)
),
child: Column(
children: [
if(paramsData['selectImageList'] != null && paramsData['selectImageList'].isNotEmpty)
ClipRRect(
borderRadius: BorderRadius.only(topLeft: Radius.circular(10),topRight: Radius.circular(10)),
child: Image.file(
File(paramsData['selectImageList'][0]),
width: double.infinity,
height: index % 2 == 1?500.w : 400.w,
fit: BoxFit.cover,
frameBuilder: (context, child, frame, wasSynchronouslyLoaded){
if(wasSynchronouslyLoaded){
return child;
}
return AnimatedOpacity(
child: child,
opacity: frame == null ? 0 : 1,
duration: const Duration(seconds: 4),
curve: Curves.easeOut,
);
}
),
),
if(paramsData['selectVideoPath'] != null)
DraftsVideoItemView(mediaUrl: paramsData['selectVideoPath'],currentIndex: index,),
Container(
margin: EdgeInsets.only(left: 10.w,right: 10.w,top: 20.w,bottom: 20.w),
alignment: Alignment.centerLeft,
child: Text('${paramsData['articleContent']??''}',
style: TextStyle(fontSize: 26.sp,fontWeight: FontWeight.w600),maxLines: 2,overflow: TextOverflow.ellipsis)
),
Container(
margin: EdgeInsets.only(left: 20.w,right: 20.w,bottom: 20.w),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('${_draftsList[index].time}',style: TextStyle(fontSize: 20.sp,color: JadeColors.grey)),
GestureDetector(
child: Container(
width: 32.w,
height: 32.w,
color: Colors.transparent,
padding: EdgeInsets.all(2),
child: Image.asset(PathConfig.iconDeleteGrey),
),
onTap: () async {
var bool = await Utils.commonDialog(context,"是否删除草稿?");
if (bool) {
DraftsUtil().deleteDrafts(_userId,index,draftsList:_draftsList);
setState(() {});
}
},
)
],
)
)
],
)
),
onTap: (){
switch(_draftsList[index].type){
case 0:
NavigatorUtil.push(PushShareEdit(
draftsJson: _draftsList[index].paramsData,
draftsIndex: index,
));
break;
case 1:
Get.to(() => WantPost(),
binding: WantHavePostBinding(), arguments: {
'type':0,
'draftsParamsJson': _draftsList[index].paramsData, //草稿箱json数据
'draftsIndex': index //草稿index
});
break;
case 2:
Get.to(() => WantPost(),
binding: WantHavePostBinding(), arguments: {
'type':1,
'draftsParamsJson': _draftsList[index].paramsData, //草稿箱json数据
'draftsIndex': index //草稿index
});
break;
}
},
);
}
}