import 'dart:io' show File; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/common/widgets/loading_widget/loading_widget.dart'; import 'package:PiliPlus/http/fav.dart'; import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/http/msg.dart'; import 'package:PiliPlus/utils/extension/file_ext.dart'; import 'package:PiliPlus/utils/extension/theme_ext.dart'; import 'package:PiliPlus/utils/fav_utils.dart'; import 'package:PiliPlus/utils/platform_utils.dart'; import 'package:easy_debounce/easy_throttle.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart' show LengthLimitingTextInputFormatter; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; import 'package:image_cropper/image_cropper.dart'; import 'package:image_picker/image_picker.dart'; class CreateFavPage extends StatefulWidget { const CreateFavPage({super.key}); @override State createState() => _CreateFavPageState(); } class _CreateFavPageState extends State { dynamic _mediaId; late final TextEditingController _titleController; late final TextEditingController _introController; String? _cover; bool _isPublic = true; late final _imagePicker = ImagePicker(); String? _errMsg; int? _attr; @override void initState() { super.initState(); _titleController = TextEditingController(); _introController = TextEditingController(); _mediaId = Get.parameters['mediaId']; if (_mediaId != null) { _getFolderInfo(); } } void _getFolderInfo() { _errMsg = null; FavHttp.favFolderInfo(mediaId: _mediaId).then((res) { if (res case Success(:final response)) { _titleController.text = response.title; _introController.text = response.intro ?? ''; _isPublic = FavUtils.isPublicFav(response.attr); _cover = response.cover; _attr = response.attr; } else { _errMsg = res.toString(); } setState(() {}); }); } @override void dispose() { _titleController.dispose(); _introController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { final theme = Theme.of(context); return Scaffold( appBar: AppBar( title: Text(_mediaId != null ? '编辑' : '创建'), actions: [ TextButton( onPressed: () { if (_titleController.text.isEmpty) { SmartDialog.showToast('名称不能为空'); return; } FavHttp.addOrEditFolder( isAdd: _mediaId == null, mediaId: _mediaId, title: _titleController.text, privacy: _isPublic ? 0 : 1, cover: _cover ?? '', intro: _introController.text, ).then((res) { if (res case Success(:final response)) { Get.back(result: response); SmartDialog.showToast('${_mediaId != null ? '编辑' : '创建'}成功'); } else { res.toast(); } }); }, child: const Text('完成'), ), const SizedBox(width: 16), ], ), body: _mediaId != null ? _titleController.text.isNotEmpty ? _buildBody(theme) : _errMsg?.isNotEmpty == true ? scrollErrorWidget(errMsg: _errMsg, onReload: _getFolderInfo) : const Center(child: CircularProgressIndicator()) : _buildBody(theme), ); } Future _pickImg(BuildContext context, ThemeData theme) async { try { XFile? pickedFile = await _imagePicker.pickImage( source: ImageSource.gallery, imageQuality: 100, ); if (pickedFile != null && mounted) { String imgPath = pickedFile.path; if (PlatformUtils.isMobile) { final croppedFile = await ImageCropper.platform.cropImage( sourcePath: imgPath, uiSettings: [ AndroidUiSettings( toolbarTitle: '裁剪', toolbarColor: theme.colorScheme.secondaryContainer, toolbarWidgetColor: theme.colorScheme.onSecondaryContainer, statusBarLight: theme.colorScheme.isLight, aspectRatioPresets: [CropAspectRatioPreset.ratio16x9], lockAspectRatio: true, hideBottomControls: true, initAspectRatio: CropAspectRatioPreset.ratio16x9, ), IOSUiSettings( title: '裁剪', // aspectRatioPresets: [CropAspectRatioPreset.ratio16x9], // aspectRatioLockEnabled: false, // resetAspectRatioEnabled: false, // aspectRatioPickerButtonHidden: true, ), ], ); if (croppedFile != null) { File(imgPath).tryDel(); imgPath = croppedFile.path; } } MsgHttp.uploadImage( path: imgPath, bucket: 'medialist', dir: 'cover', ).then((res) { if (context.mounted) { if (res case Success(:final response)) { _cover = response['location']; (context as Element).markNeedsBuild(); } else { res.toast(); } } if (PlatformUtils.isMobile) { File(imgPath).tryDel(); } }); } } catch (e) { SmartDialog.showToast(e.toString()); } } final leadingStyle = const TextStyle(fontSize: 14); Widget _buildBody(ThemeData theme) => SingleChildScrollView( padding: .only(bottom: MediaQuery.viewPaddingOf(context).bottom + 25), child: Column( spacing: 12, children: [ if (_attr == null || !FavUtils.isDefaultFav(_attr!)) Builder( builder: (context) { return ListTile( visualDensity: .standard, tileColor: theme.colorScheme.onInverseSurface, onTap: () { EasyThrottle.throttle( 'imagePicker', const Duration(milliseconds: 500), () { if (_cover?.isNotEmpty == true) { showDialog( context: context, builder: (_) => AlertDialog( clipBehavior: Clip.hardEdge, contentPadding: const .symmetric(vertical: 12), content: Column( mainAxisSize: MainAxisSize.min, children: [ ListTile( dense: true, onTap: () { Get.back(); _pickImg(context, theme); }, title: const Text( '替换封面', style: TextStyle(fontSize: 14), ), ), ListTile( dense: true, onTap: () { Get.back(); _cover = null; (context as Element).markNeedsBuild(); }, title: const Text( '移除封面', style: TextStyle(fontSize: 14), ), ), ], ), ), ); } else { _pickImg(context, theme); } }, ); }, leading: Text( '封面', style: leadingStyle, ), trailing: Row( spacing: 10, mainAxisSize: MainAxisSize.min, children: [ if (_cover?.isNotEmpty == true) Padding( padding: const EdgeInsets.symmetric(vertical: 5), child: NetworkImgLayer( src: _cover, height: 55, width: 88, borderRadius: const BorderRadius.all( Radius.circular(6), ), ), ), Icon( Icons.keyboard_arrow_right, color: theme.colorScheme.outline, ), ], ), ); }, ), ListTile( tileColor: theme.colorScheme.onInverseSurface, title: Row( children: [ SizedBox( width: 55, child: Text.rich( TextSpan( children: [ TextSpan( text: '*', style: TextStyle( fontSize: 14, color: theme.colorScheme.error, ), ), const TextSpan( text: '名称', style: TextStyle(fontSize: 14), ), ], ), ), ), Expanded( child: TextField( autofocus: true, readOnly: _attr != null && FavUtils.isDefaultFav(_attr!), controller: _titleController, style: TextStyle( fontSize: 14, color: _attr != null && FavUtils.isDefaultFav(_attr!) ? theme.colorScheme.outline : null, ), inputFormatters: [ LengthLimitingTextInputFormatter(20), ], decoration: InputDecoration( isDense: true, hintText: '名称', hintStyle: TextStyle( fontSize: 14, color: theme.colorScheme.outline, ), border: const OutlineInputBorder( borderSide: BorderSide.none, gapPadding: 0, ), contentPadding: EdgeInsets.zero, ), ), ), ], ), ), if (_attr == null || !FavUtils.isDefaultFav(_attr!)) ListTile( tileColor: theme.colorScheme.onInverseSurface, title: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox( width: 55, child: Text( '简介', style: TextStyle( fontSize: 14, color: theme.colorScheme.onSurfaceVariant, ), ), ), Expanded( child: TextField( minLines: 6, maxLines: 6, controller: _introController, style: const TextStyle(fontSize: 14), inputFormatters: [ LengthLimitingTextInputFormatter(200), ], decoration: InputDecoration( isDense: true, hintText: '可填写简介', hintStyle: TextStyle( fontSize: 14, color: theme.colorScheme.outline, ), border: const OutlineInputBorder( borderSide: BorderSide.none, gapPadding: 0, ), contentPadding: EdgeInsets.zero, ), ), ), ], ), ), Builder( builder: (context) { void onTap() { _isPublic = !_isPublic; (context as Element).markNeedsBuild(); } return ListTile( onTap: onTap, tileColor: theme.colorScheme.onInverseSurface, leading: Text( '公开', style: leadingStyle, ), trailing: Transform.scale( alignment: Alignment.centerRight, scale: 0.8, child: Switch( value: _isPublic, onChanged: (value) => onTap(), ), ), ); }, ), ], ), ); }