view.dart 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297
  1. import 'dart:convert';
  2. import 'dart:io';
  3. import 'package:flutter/material.dart';
  4. import 'package:get/get.dart';
  5. import 'package:image_picker/image_picker.dart';
  6. import 'package:uuid/uuid.dart';
  7. import 'package:vnoteapp/architecture/storage/storage.dart';
  8. import 'package:vnoteapp/components/appbar.dart';
  9. import 'package:fis_common/logger/logger.dart';
  10. import 'package:vnoteapp/rpc.dart';
  11. import 'dart:ui' as ui;
  12. import 'controller.dart';
  13. /// 签字板
  14. class SignatureBoardPage extends GetView<SignatureBoardController> {
  15. SignatureBoardPage({super.key});
  16. final canvasController = _SignatureBoardController();
  17. @override
  18. Widget build(BuildContext context) {
  19. final params = Get.parameters;
  20. final String title = params["title"] ?? "设置签名";
  21. return WillPopScope(
  22. onWillPop: () async {
  23. return false;
  24. },
  25. child: Scaffold(
  26. appBar: VAppBar(
  27. context: context,
  28. titleText: title,
  29. ),
  30. body: Stack(
  31. children: [
  32. _SignatureBoard(controller: canvasController),
  33. Positioned(
  34. right: 32,
  35. bottom: 0,
  36. top: 0,
  37. child: _buildFloatActions(context),
  38. ),
  39. ],
  40. ),
  41. ),
  42. );
  43. }
  44. Widget _buildFloatActions(BuildContext context) {
  45. return Column(
  46. mainAxisSize: MainAxisSize.min,
  47. mainAxisAlignment: MainAxisAlignment.center,
  48. crossAxisAlignment: CrossAxisAlignment.center,
  49. children: [
  50. SizedBox(
  51. width: 90,
  52. height: 90,
  53. child: ElevatedButton(
  54. style: ButtonStyle(
  55. foregroundColor: const MaterialStatePropertyAll(Colors.white),
  56. backgroundColor: const MaterialStatePropertyAll(Colors.grey),
  57. shape: MaterialStatePropertyAll(
  58. RoundedRectangleBorder(
  59. borderRadius: BorderRadius.circular(45),
  60. side: BorderSide.none,
  61. ),
  62. ),
  63. ),
  64. onPressed: () {
  65. canvasController.revocation();
  66. },
  67. child: const Text(
  68. "撤销",
  69. style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
  70. ),
  71. ),
  72. ),
  73. const SizedBox(height: 32),
  74. SizedBox(
  75. width: 90,
  76. height: 90,
  77. child: ElevatedButton(
  78. style: ButtonStyle(
  79. foregroundColor: const MaterialStatePropertyAll(Colors.white),
  80. backgroundColor: const MaterialStatePropertyAll(Colors.red),
  81. shape: MaterialStatePropertyAll(
  82. RoundedRectangleBorder(
  83. borderRadius: BorderRadius.circular(45),
  84. side: BorderSide.none,
  85. ),
  86. ),
  87. ),
  88. onPressed: () {
  89. canvasController.clear();
  90. },
  91. child: const Text(
  92. "清除",
  93. style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
  94. ),
  95. ),
  96. ),
  97. const SizedBox(height: 32),
  98. SizedBox(
  99. width: 90,
  100. height: 90,
  101. child: ElevatedButton(
  102. style: ButtonStyle(
  103. foregroundColor: const MaterialStatePropertyAll(Colors.white),
  104. backgroundColor:
  105. MaterialStatePropertyAll(Theme.of(context).primaryColor),
  106. shape: MaterialStatePropertyAll(
  107. RoundedRectangleBorder(
  108. borderRadius: BorderRadius.circular(45),
  109. side: BorderSide.none,
  110. ),
  111. ),
  112. ),
  113. onPressed: () async {
  114. final result = await canvasController.getImageUrl();
  115. Get.back(result: result);
  116. },
  117. child: const Text(
  118. "保存",
  119. style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
  120. ),
  121. ),
  122. ),
  123. ],
  124. );
  125. }
  126. }
  127. class _SignatureBoardController extends ChangeNotifier {
  128. List<Offset?> points = [];
  129. SignaturePainter? painter;
  130. Size? size;
  131. /// 清除
  132. void clear() {
  133. points = [];
  134. notifyListeners();
  135. }
  136. /// 撤销
  137. void revocation() {
  138. int index = points.lastIndexOf(null);
  139. if (index == points.length - 1) {
  140. points = points.take(index).toList();
  141. index = points.lastIndexOf(null);
  142. }
  143. if (index < 0) {
  144. points = [];
  145. } else {
  146. points = points.take(index).toList();
  147. }
  148. notifyListeners();
  149. }
  150. XFile? convertBase64ToXFile(String base64Image) {
  151. try {
  152. final bytes = base64Decode(base64Image);
  153. final tempDir = Directory.systemTemp;
  154. final tempPath = tempDir.path;
  155. final imageId = const Uuid().v4().replaceAll('-', '');
  156. final filePath = '$tempPath/$imageId';
  157. File(filePath).writeAsBytesSync(bytes);
  158. return XFile(filePath);
  159. } catch (e) {
  160. print('Error converting base64 to XFile: $e');
  161. return null;
  162. }
  163. }
  164. Future<String?> getImageUrl() async {
  165. final imageBase64 = await getImageBase64();
  166. final xFile = convertBase64ToXFile(imageBase64!);
  167. String? url = await rpc.storage.upload(xFile!);
  168. return url;
  169. }
  170. /// 获取图片base64字符串
  171. Future<String?> getImageBase64() async {
  172. try {
  173. final recorder = ui.PictureRecorder();
  174. final canvas = Canvas(recorder);
  175. painter!.paint(canvas, size!);
  176. // Paint paint = Paint()
  177. // ..color = Colors.black
  178. // ..strokeCap = StrokeCap.round
  179. // ..strokeWidth = 8.0;
  180. // for (int i = 0; i < points.length - 1; i++) {
  181. // if (points[i] != null && points[i + 1] != null) {
  182. // canvas.drawLine(points[i]!, points[i + 1]!, paint);
  183. // }
  184. // }
  185. // 将绘制的内容转换为图像
  186. final picture = recorder.endRecording();
  187. final image =
  188. await picture.toImage(size!.width.toInt(), size!.height.toInt());
  189. // 将图像保存到文件
  190. final byteData = await image.toByteData(format: ui.ImageByteFormat.png);
  191. if (byteData != null) {
  192. String base64Image = base64Encode(byteData.buffer.asUint8List());
  193. return base64Image;
  194. }
  195. } catch (e) {
  196. logger.e("_SignatureBoardController getImageBase64 error.", e);
  197. }
  198. return null;
  199. }
  200. }
  201. class _SignatureBoard extends StatefulWidget {
  202. final _SignatureBoardController controller;
  203. const _SignatureBoard({required this.controller});
  204. @override
  205. _SignatureBoardState createState() => _SignatureBoardState();
  206. }
  207. class _SignatureBoardState extends State<_SignatureBoard> {
  208. List<Offset?> get _points => widget.controller.points;
  209. @override
  210. void initState() {
  211. widget.controller.addListener(_onUpdate);
  212. super.initState();
  213. }
  214. @override
  215. void dispose() {
  216. widget.controller.removeListener(_onUpdate);
  217. super.dispose();
  218. }
  219. void _onUpdate() {
  220. setState(() {});
  221. }
  222. @override
  223. Widget build(BuildContext context) {
  224. return GestureDetector(
  225. onPanUpdate: (DragUpdateDetails details) {
  226. setState(() {
  227. RenderBox renderBox = context.findRenderObject() as RenderBox;
  228. Offset localPosition =
  229. renderBox.globalToLocal(details.globalPosition);
  230. widget.controller.points = List.from(_points)..add(localPosition);
  231. });
  232. },
  233. onPanEnd: (DragEndDetails details) {
  234. widget.controller.points.add(null);
  235. },
  236. child: LayoutBuilder(builder: (context, c) {
  237. final size = Size(c.maxWidth, c.maxHeight);
  238. final painter = SignaturePainter(points: _points);
  239. widget.controller.size = size;
  240. widget.controller.painter = painter;
  241. return CustomPaint(
  242. painter: painter,
  243. size: size,
  244. );
  245. }),
  246. );
  247. }
  248. }
  249. class SignaturePainter extends CustomPainter {
  250. List<Offset?> points;
  251. SignaturePainter({required this.points});
  252. @override
  253. void paint(Canvas canvas, Size size) {
  254. Paint paint = Paint()
  255. ..color = Colors.black
  256. ..strokeCap = StrokeCap.round
  257. ..strokeWidth = 8.0;
  258. for (int i = 0; i < points.length - 1; i++) {
  259. if (points[i] != null && points[i + 1] != null) {
  260. canvas.drawLine(points[i]!, points[i + 1]!, paint);
  261. }
  262. }
  263. }
  264. @override
  265. bool shouldRepaint(SignaturePainter oldDelegate) => true;
  266. }