flutter简单的本地草稿箱功能

news2025/5/24 20:52:03

请添加图片描述

需求1:发帖退出时提示是否保存草稿
需求2:每条草稿中可以保存多张图片(最多9张)或一条视频及三十来个其它参数
需求3:每条草稿都是可以被覆盖的、可以点击删除
需求4:草稿页面可以一键清空
需求5:草稿随app删除一起没掉

看到需求第一时间想到的就是存轻量级SharedPreferences中;
行动:将图片转为base64,然后将base64转json从而实现将图片存入轻量级

/*
  * 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字符串一起存入轻量级
封装工具

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');
  }
}

草稿箱参数对象

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;
  }
}

引用

//草稿箱提示框
  _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();
    }
  }

草稿箱列表页面

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{
  
  State<StatefulWidget> createState() {
    // TODO: implement createState
    return _DraftsPage();
  }
}

class _DraftsPage extends State<DraftsPage>{

  String _userId;
  List<DraftsBean> _draftsList = [];
  var _eventBus;
  
  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(() {});
      }
    });
  }

  
  void dispose() {
    // TODO: implement dispose
    if(_eventBus != null){
      _eventBus.cancel();
      _eventBus = null;
    }
    super.dispose();
  }

  
  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;
        }
      },
    );
  }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1010636.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

简单2D几何求交点

2D图形SVG工具添加了通过选项属性显示交点的功能。 PaladinDu&#xff1a;2D图形SVG化工具0 赞同 0 评论文章 在这个工具中&#xff0c;已经定义的数据结构有点&#xff0c;线段&#xff0c;有向线段&#xff0c;射线&#xff0c;直线&#xff0c;多边形&#xff0c;和圆。 …

【技巧】如何保护PDF文件不被随意修改?

做好的PDF文件&#xff0c;不想被他人随意修改&#xff0c;只要给PDF设置“限制保护”就可以了&#xff0c;设置保护后需要输入密码才能进行编辑。 设置“限制保护”我们需要用到PDF编辑器&#xff0c;以小编使用的编辑器为例&#xff0c;首先通过编辑器打开PDF文件后&#xf…

医学访问学者申请四点规划建议

医学领域一直以来都是人类社会的重要组成部分&#xff0c;而作为一名有志于成为一名医学领域的访问学者&#xff0c;您需要明确自己的目标并做好充分准备。知识人网小编将为您提供四点规划建议&#xff0c;以帮助您成功申请医学访问学者的机会。 第一点&#xff1a;明确研究方向…

input 的 placeholder 样式

::placeholder 伪元素 这个伪元素可以改变 input、textarea 占位文本的样式。 input::placeholder {color: green; }完整的兼容性写法&#xff1a; input {&::-webkit-input-placeholder, /* WebKit browsers*/ &:-moz-input-placeholder, /* Mozilla Firefox 4 to …

2个小时的腾讯面试经历(C++),来看看它终究考察了些什么?

今天分享一位同学实习面试鹅厂 c 岗的面试&#xff0c;全程都是问 C 和计算机基础&#xff08;系统、网络、算法&#xff09;的内容了。难度中规中矩吧&#xff0c;基本都是追问式的问法&#xff0c;一层一层往下问。 C相关 对面向对象的理解 C面向对象编程就是把一切事物都…

Kubernetes 部署应用(nginx)的两种方式,你更喜欢哪一种?

k8s发布应用的两种方式&#xff1a; kubernetes-dashboardkubectl命令行 一、Dashboard方式 配置部署&#xff1a;包含应用名称、容器镒、pod数量、Service非常的方便&#xff0c;不想设置配置yaml的可以很方便的部署。 点击部署就成功了k8s应用的部署了。部署后可以看到相应…

python爬虫经典案例(一)

爬虫&#xff08;Web Scraping&#xff09;是一种自动获取互联网信息的技术&#xff0c;广泛用于数据采集、分析和应用开发。无论你是数据科学家、市场营销专家还是应用程序开发者&#xff0c;都可以通过编写爬虫来获取所需的信息。在本文中&#xff0c;我们将介绍五个实用的爬…

从0开始实现简易版vue2

文章目录 前言原理思路分析实现Observer实现Watcher实现Compile 前言 Vue.js的双向绑定原理是其最重要的特性之一&#xff0c;它使得数据模型和DOM之间的同步变得非常简单和高效。 先上个成果图来吸引各位&#xff1a; new SimpleVue({el: #app,data: {title: SimpleVue,name…

re学习(37)DASCTF 2023_7 controlflow

IDA载入一下 发现flag的长度为40&#xff0c;然后将input[i]的值赋值给str[i]&#xff0c;str[i]又执行一个异或操作&#xff0c;但是上面又有一些函数&#xff0c;导致这个程序的控制流有点奇怪..... 输入40只&#xff0c;检验是否是flag。 F5直接罢工了&#xff0c;暂时先忽…

Java入坑之代理

一、代理模式和静态代理 1.1代理模式 代理模式 - Proxy Pattern, 23个经典模式的一种&#xff0c;又称委托模式 -为目标对象提供(包装)了一个代理&#xff0c;这个代理可以控制对目标对象的访问 外界不用直接访问目标对象&#xff0c;而是访问代理对象&#xff0c;由代理对象再…

软件外包开发bug管理工具

国内有一些常用的Bug管理工具&#xff0c;这些工具旨在帮助团队有效地发现、跟踪和解决软件开发中的缺陷和问题。以下是一些国内的Bug管理工具&#xff0c;希望对大家有所帮助。北京木奇移动技术有限公司&#xff0c;专业的软件外包开发公司&#xff0c;欢迎交流合作。 1.禅道&…

在openSUSE上开启护眼模式

色温 色温是用来衡量光源色彩时所用到的一个概念&#xff0c;单位为开尔文。热黑体辐射体与光源温度相同时的温度&#xff0c;就是该光源的色温。 显而易见&#xff0c;色温越低时&#xff0c;光源看起来越黄&#xff1b;色温越高时&#xff0c;光源看起来越蓝。下面是一些常见…

【LangChain系列 9】Prompt模版——MessagePromptTemplate

原文地址&#xff1a;【LangChain系列 9】Prompt模版——MessagePromptTemplate 本文速读&#xff1a; MessagePromptTemplate MessagesPlaceholder 在对话模型(chat model) 中&#xff0c; prompt主要是封装在Message中&#xff0c;LangChain提供了一些MessagePromptTemplat…

入门必读:Python try except异常处理详解

Python 中&#xff0c;用try except语句块捕获并处理异常&#xff0c;其基本语法结构如下所示&#xff1a; try: 可能产生异常的代码块 except [ (Error1, Error2, ... ) [as e] ]: 处理异常的代码块1 except [ (Error3, Error4, ... ) [as e] ]: 处理异常的代码块…

CSAPP Bomb Lab

CSAPP Bomb Lab 芜湖&#xff0c;完成实验来记录一下啦~ 这个lab看的我真是眼皮发麻&#xff0c;框框就是反汇编->c语言形式->优化c语言 最后我才能写出来&#xff0c;整的我睡觉都是 mov… lea… 难道适中&#xff0c;感觉只要耐心一点就没问题&#xff0c;好了现在开…

【鸿蒙(HarmonyOS)】List列表、ArkUI资源组数据类型

一、引言 描述&#xff1a;List列表在移动端设备中最为常见。比如&#xff1a;通讯录、短信、聊天软件等都&#xff0c;都拥有他的身影。难度&#xff1a;简单知识点&#xff1a;1、列表组件的使用2、认识ArkUI资源组数据类型 二、列表List 1、发现问题&#xff08;Bug&…

2023-9-14 石子合并

题目链接&#xff1a;石子合并 #include <iostream> #include <algorithm>using namespace std;const int N 310, INF 1e9;int n; // 前缀和 int s[N]; int f[N][N];int main() {cin >> n;for(int i 1; i < n; i ) cin >> s[i];for(int i 1; i …

模式分类与“组件协作模式”

1. GOF-23 模式分类 从目的来看&#xff1a; 创建型&#xff08;Creational&#xff09;模式&#xff1a;将对象的部分创建工作延迟到子类或者其他对象&#xff0c;从而应对需求变化为对象创建时具体类型实现引来的冲击。结构型&#xff08;Structural&#xff09;模式&#…

CS5366 typec转HDMI 4k60拓展坞方案芯片,带PD快充功能

CS5366是一个高性能的USB类型-c/显示端口到HDMI2.0转换器方案芯片&#xff0c;CS5366集成了一个DP1.4兼容的接收器&#xff0c;一个HDMI2.0兼容的发射器。此外&#xff0c;还包括两个CC控 制器&#xff0c;用于CC通信&#xff0c;以实现DP Alt模式和电源传输功能&#xff0c;一…

[设计模式] 浅谈SOLID设计原则

目录 单一职责原则开闭原则里氏替换原则接口隔离原则依赖倒转原则 SOLID是一个缩写词&#xff0c;代表以下五种设计原则 单一职责原则 Single Responsibility Principle, SRP开闭原则 Open-Closed Principle, OCP里氏替换原则 Liskov Substitution Principle, LSP接口隔离原则 …