diff --git a/lib/page/admin/models_add.dart b/lib/page/admin/models_add.dart index d27a730b..57e4489e 100644 --- a/lib/page/admin/models_add.dart +++ b/lib/page/admin/models_add.dart @@ -1,5 +1,8 @@ import 'dart:io'; +import 'dart:ui'; +import 'package:animated_list_plus/animated_list_plus.dart'; +import 'package:animated_list_plus/transitions.dart'; import 'package:askaide/bloc/model_bloc.dart'; import 'package:askaide/helper/upload.dart'; import 'package:askaide/lang/lang.dart'; @@ -21,6 +24,7 @@ import 'package:askaide/repo/api/admin/channels.dart'; import 'package:askaide/repo/api/admin/models.dart'; import 'package:askaide/repo/api_server.dart'; import 'package:askaide/repo/settings_repo.dart'; +import 'package:auto_size_text/auto_size_text.dart'; import 'package:bot_toast/bot_toast.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; @@ -155,8 +159,7 @@ class _AdminModelCreatePageState extends State { } }, child: Container( - padding: const EdgeInsets.only( - left: 10, right: 10, top: 10, bottom: 20), + padding: const EdgeInsets.only(left: 10, right: 10, top: 10, bottom: 20), child: Column( children: [ ColumnBlock( @@ -210,10 +213,8 @@ class _AdminModelCreatePageState extends State { ? null : DecorationImage( image: (avatarUrl!.startsWith('http') - ? CachedNetworkImageProviderEnhanced( - avatarUrl!) - : FileImage(File( - avatarUrl!))) as ImageProvider, + ? CachedNetworkImageProviderEnhanced(avatarUrl!) + : FileImage(File(avatarUrl!))) as ImageProvider, fit: BoxFit.cover, ), ), @@ -273,18 +274,14 @@ class _AdminModelCreatePageState extends State { hintText: 'Optional', showCounter: false, keyboardType: TextInputType.number, - inputFormatters: [ - FilteringTextInputFormatter.digitsOnly - ], + inputFormatters: [FilteringTextInputFormatter.digitsOnly], textDirection: TextDirection.rtl, suffixIcon: Container( width: 110, alignment: Alignment.center, child: Text( 'Credits/1K Token', - style: TextStyle( - color: customColors.weakTextColor, - fontSize: 12), + style: TextStyle(color: customColors.weakTextColor, fontSize: 12), ), ), ), @@ -297,18 +294,14 @@ class _AdminModelCreatePageState extends State { hintText: 'Optional', showCounter: false, keyboardType: TextInputType.number, - inputFormatters: [ - FilteringTextInputFormatter.digitsOnly - ], + inputFormatters: [FilteringTextInputFormatter.digitsOnly], textDirection: TextDirection.rtl, suffixIcon: Container( width: 110, alignment: Alignment.center, child: Text( 'Credits/1K Token', - style: TextStyle( - color: customColors.weakTextColor, - fontSize: 12), + style: TextStyle(color: customColors.weakTextColor, fontSize: 12), ), ), ), @@ -321,150 +314,224 @@ class _AdminModelCreatePageState extends State { hintText: 'Optional', showCounter: false, keyboardType: TextInputType.number, - inputFormatters: [ - FilteringTextInputFormatter.digitsOnly - ], + inputFormatters: [FilteringTextInputFormatter.digitsOnly], textDirection: TextDirection.rtl, suffixIcon: Container( width: 110, alignment: Alignment.center, child: Text( 'Credits/Request', - style: TextStyle( - color: customColors.weakTextColor, - fontSize: 12), + style: TextStyle(color: customColors.weakTextColor, fontSize: 12), ), ), ), EnhancedTextField( labelWidth: 120, - labelText: 'Context Length', + labelText: 'Context Len', customColors: customColors, controller: maxContextController, textAlignVertical: TextAlignVertical.top, - hintText: - 'Subtract the expected output length from the maximum context.', + hintText: 'Subtract the expected output length from the maximum context.', showCounter: false, keyboardType: TextInputType.number, - inputFormatters: [ - FilteringTextInputFormatter.digitsOnly - ], + inputFormatters: [FilteringTextInputFormatter.digitsOnly], textDirection: TextDirection.rtl, suffixIcon: Container( width: 50, alignment: Alignment.center, child: Text( 'Token', - style: TextStyle( - color: customColors.weakTextColor, - fontSize: 12), + style: TextStyle(color: customColors.weakTextColor, fontSize: 12), ), ), ), ], ), - ...providers.map((e) { - return Container( - margin: - const EdgeInsets.only(bottom: 10, left: 5, right: 5), - child: Slidable( - endActionPane: ActionPane( - motion: const ScrollMotion(), - children: [ - const SizedBox(width: 10), - SlidableAction( - label: AppLocale.delete.getString(context), - borderRadius: CustomSize.borderRadiusAll, - backgroundColor: Colors.red, - icon: Icons.delete, - onPressed: (_) { - if (providers.length == 1) { - showErrorMessage( - 'At least one channel is needed'); - return; - } - - openConfirmDialog( - context, - AppLocale.confirmToDeleteRoom - .getString(context), - () { - setState(() { - providers - .removeWhere((item) => item == e); - }); - }, - danger: true, - ); - }, - ), - ], - ), - child: ColumnBlock( - margin: const EdgeInsets.all(0), - children: [ - EnhancedInput( - title: Text( - 'Channel', - style: TextStyle( - color: customColors.textfieldLabelColor, - fontSize: 16, + ImplicitlyAnimatedReorderableList( + items: providers, + shrinkWrap: true, + itemBuilder: (context, itemAnimation, item, index) { + return Reorderable( + key: ValueKey(item), + builder: (context, dragAnimation, inDrag) { + final t = dragAnimation.value; + final elevation = lerpDouble(0, 8, t); + final color = Color.lerp(Colors.white, Colors.white.withOpacity(0.8), t); + + return SizeFadeTransition( + sizeFraction: 0.7, + curve: Curves.easeInOut, + animation: itemAnimation, + child: Material( + color: color, + elevation: elevation ?? 0, + type: MaterialType.transparency, + child: Slidable( + startActionPane: ActionPane( + motion: const ScrollMotion(), + children: [ + const SizedBox(width: 10), + SlidableAction( + label: AppLocale.delete.getString(context), + borderRadius: CustomSize.borderRadiusAll, + backgroundColor: Colors.red, + icon: Icons.delete, + onPressed: (_) { + if (providers.length == 1) { + showErrorMessage('At least one channel is needed'); + return; + } + + openConfirmDialog( + context, + AppLocale.confirmToDeleteRoom.getString(context), + () { + setState(() { + providers.removeAt(index); + }); + }, + danger: true, + ); + }, + ), + ], ), - ), - value: Text( - buildChannelName(e), - style: TextStyle( - color: customColors.textfieldValueColor, - fontSize: 16, + child: ListTile( + contentPadding: const EdgeInsets.all(5), + title: ColumnBlock( + margin: const EdgeInsets.all(0), + children: [ + EnhancedInput( + title: Text( + 'Channel', + style: TextStyle( + color: customColors.textfieldLabelColor, + fontSize: 16, + ), + ), + value: AutoSizeText( + buildChannelName(item), + maxLines: 1, + style: TextStyle( + color: customColors.textfieldValueColor, + fontSize: 14, + ), + ), + onPressed: () { + openListSelectDialog( + context, + >[ + ...modelChannels + .map((e) => SelectorItem( + Text('${e.id == null ? '【System】' : ''}${e.name}'), e)) + .toList(), + ], + (value) { + setState(() { + providers[index].id = value.value.id; + if (value.value.id == null) { + providers[index].name = value.value.type; + } + }); + return true; + }, + heightFactor: 0.5, + value: item, + ); + }, + ), + EnhancedTextField( + labelWidth: 90, + labelText: 'Rewrite', + customColors: customColors, + textAlignVertical: TextAlignVertical.top, + hintText: 'Optional', + maxLength: 100, + showCounter: false, + initValue: item.modelRewrite, + onChanged: (value) { + setState(() { + providers[index].modelRewrite = value; + }); + }, + labelHelpWidget: InkWell( + onTap: () { + showBeautyDialog( + context, + type: QuickAlertType.info, + text: + 'When the model identifier corresponding to the channel does not match the ID here, calling the channel interface will automatically replace the model with the value configured here.', + confirmBtnText: AppLocale.gotIt.getString(context), + showCancelBtn: false, + ); + }, + child: Icon( + Icons.help_outline, + size: 16, + color: customColors.weakLinkColor?.withAlpha(150), + ), + ), + ), + ], + ), + trailing: Handle( + delay: const Duration(milliseconds: 100), + child: Column( + children: [ + Container( + width: 15, + height: 15, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Colors.blue.withOpacity(0.1), + border: Border.all( + color: Colors.blue.withOpacity(0.3), + width: 1, + ), + boxShadow: [ + BoxShadow( + color: Colors.blue.withOpacity(0.1), + blurRadius: 2, + spreadRadius: 1, + ), + ], + ), + child: Center( + child: Text( + '${index + 1}', + style: TextStyle( + fontSize: 9, + color: Colors.blue.shade700, + fontWeight: FontWeight.w500, + ), + ), + ), + ), + const SizedBox(height: 10), + const Icon( + Icons.drag_indicator, + size: 20, + color: Colors.grey, + ), + ], + ), + ), ), ), - onPressed: () { - openListSelectDialog( - context, - >[ - ...modelChannels - .map( - (e) => SelectorItem( - Text( - '${e.id == null ? '【System】' : ''}${e.name}'), - e, - ), - ) - .toList(), - ], - (value) { - setState(() { - e.id = value.value.id; - if (value.value.id == null) { - e.name = value.value.type; - } - }); - return true; - }, - heightFactor: 0.5, - value: e, - ); - }, - ), - EnhancedTextField( - labelWidth: 120, - labelText: 'Model Rewrite', - customColors: customColors, - textAlignVertical: TextAlignVertical.top, - hintText: 'Optional', - maxLength: 100, - showCounter: false, - initValue: e.modelRewrite, - onChanged: (value) { - e.modelRewrite = value; - }, ), - ], - ), - ), - ); - }).toList(), - const SizedBox(width: 10), + ); + }, + ); + }, + areItemsTheSame: (AdminModelProvider oldItem, AdminModelProvider newItem) { + return oldItem.id == newItem.id; + }, + onReorderFinished: (AdminModelProvider item, int from, int to, List newItems) { + setState(() { + providers = newItems; + }); + }, + ), WeakTextButton( title: 'Add Channel', icon: Icons.add, @@ -474,6 +541,7 @@ class _AdminModelCreatePageState extends State { }); }, ), + const SizedBox(height: 10), // 高级选项 if (showAdvancedOptions) ColumnBlock( @@ -512,18 +580,15 @@ class _AdminModelCreatePageState extends State { showBeautyDialog( context, type: QuickAlertType.info, - text: - 'Whether the current model supports visual capabilities.', - confirmBtnText: - AppLocale.gotIt.getString(context), + text: 'Whether the current model supports visual capabilities.', + confirmBtnText: AppLocale.gotIt.getString(context), showCancelBtn: false, ); }, child: Icon( Icons.help_outline, size: 16, - color: customColors.weakLinkColor - ?.withAlpha(150), + color: customColors.weakLinkColor?.withAlpha(150), ), ), ], @@ -556,16 +621,14 @@ class _AdminModelCreatePageState extends State { type: QuickAlertType.info, text: 'Whether to display a "New" icon next to the model to inform users that this is a new model.', - confirmBtnText: - AppLocale.gotIt.getString(context), + confirmBtnText: AppLocale.gotIt.getString(context), showCancelBtn: false, ); }, child: Icon( Icons.help_outline, size: 16, - color: customColors.weakLinkColor - ?.withAlpha(150), + color: customColors.weakLinkColor?.withAlpha(150), ), ), ], @@ -598,16 +661,14 @@ class _AdminModelCreatePageState extends State { type: QuickAlertType.info, text: 'Whether to display a "Recommended" icon next to the model to inform users that this is a recommended model.', - confirmBtnText: - AppLocale.gotIt.getString(context), + confirmBtnText: AppLocale.gotIt.getString(context), showCancelBtn: false, ); }, child: Icon( Icons.help_outline, size: 16, - color: customColors.weakLinkColor - ?.withAlpha(150), + color: customColors.weakLinkColor?.withAlpha(150), ), ), ], @@ -640,16 +701,14 @@ class _AdminModelCreatePageState extends State { type: QuickAlertType.info, text: 'Restricted models refer to models that cannot be used in Chinese Mainland due to policy factors.', - confirmBtnText: - AppLocale.gotIt.getString(context), + confirmBtnText: AppLocale.gotIt.getString(context), showCancelBtn: false, ); }, child: Icon( Icons.help_outline, size: 16, - color: customColors.weakLinkColor - ?.withAlpha(150), + color: customColors.weakLinkColor?.withAlpha(150), ), ), ], @@ -707,9 +766,7 @@ class _AdminModelCreatePageState extends State { color: customColors.weakLinkColor, fontSize: 15, icon: Icon( - showAdvancedOptions - ? Icons.unfold_less - : Icons.unfold_more, + showAdvancedOptions ? Icons.unfold_less : Icons.unfold_more, color: customColors.weakLinkColor, size: 15, ), @@ -756,9 +813,7 @@ class _AdminModelCreatePageState extends State { return; } - if (avatarUrl != null && - (!avatarUrl!.startsWith('http://') && - !avatarUrl!.startsWith('https://'))) { + if (avatarUrl != null && (!avatarUrl!.startsWith('http://') && !avatarUrl!.startsWith('https://'))) { final cancel = BotToast.showCustomLoading( toastBuilder: (cancel) { return const LoadingIndicator( @@ -769,8 +824,7 @@ class _AdminModelCreatePageState extends State { ); try { - final res = await ImageUploader(widget.setting) - .upload(avatarUrl!, usage: 'avatar'); + final res = await ImageUploader(widget.setting).upload(avatarUrl!, usage: 'avatar'); avatarUrl = res.url; } catch (e) { showErrorMessage('Failed to upload avatar'); diff --git a/lib/page/admin/models_edit.dart b/lib/page/admin/models_edit.dart index 602dbac2..64c881ac 100644 --- a/lib/page/admin/models_edit.dart +++ b/lib/page/admin/models_edit.dart @@ -1,5 +1,8 @@ import 'dart:io'; +import 'dart:ui'; +import 'package:animated_list_plus/animated_list_plus.dart'; +import 'package:animated_list_plus/transitions.dart'; import 'package:askaide/bloc/model_bloc.dart'; import 'package:askaide/helper/upload.dart'; import 'package:askaide/lang/lang.dart'; @@ -21,6 +24,7 @@ import 'package:askaide/repo/api/admin/channels.dart'; import 'package:askaide/repo/api/admin/models.dart'; import 'package:askaide/repo/api_server.dart'; import 'package:askaide/repo/settings_repo.dart'; +import 'package:auto_size_text/auto_size_text.dart'; import 'package:bot_toast/bot_toast.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; @@ -52,8 +56,7 @@ class _AdminModelEditPageState extends State { final TextEditingController maxContextController = TextEditingController(); final TextEditingController inputPriceController = TextEditingController(); final TextEditingController outputPriceController = TextEditingController(); - final TextEditingController perRequestPriceController = - TextEditingController(); + final TextEditingController perRequestPriceController = TextEditingController(); final TextEditingController promptController = TextEditingController(); final TextEditingController categoryController = TextEditingController(); @@ -153,8 +156,7 @@ class _AdminModelEditPageState extends State { enabled: false, child: SingleChildScrollView( child: BlocListener( - listenWhen: (previous, current) => - current is ModelOperationResult || current is ModelLoaded, + listenWhen: (previous, current) => current is ModelOperationResult || current is ModelLoaded, listener: (context, state) { if (state is ModelOperationResult) { if (state.success) { @@ -166,12 +168,10 @@ class _AdminModelEditPageState extends State { } if (state is ModelLoaded) { - modelIdController.value = - TextEditingValue(text: state.model.modelId); + modelIdController.value = TextEditingValue(text: state.model.modelId); nameController.value = TextEditingValue(text: state.model.name); if (state.model.description != null) { - descriptionController.value = - TextEditingValue(text: state.model.description!); + descriptionController.value = TextEditingValue(text: state.model.description!); } if (state.model.avatarUrl != null) { @@ -186,39 +186,31 @@ class _AdminModelEditPageState extends State { if (state.model.meta != null) { if (state.model.meta!.maxContext != null) { - maxContextController.value = TextEditingValue( - text: state.model.meta!.maxContext.toString()); + maxContextController.value = TextEditingValue(text: state.model.meta!.maxContext.toString()); } if (state.model.meta!.inputPrice != null) { - inputPriceController.value = TextEditingValue( - text: state.model.meta!.inputPrice.toString()); + inputPriceController.value = TextEditingValue(text: state.model.meta!.inputPrice.toString()); } if (state.model.meta!.outputPrice != null) { - outputPriceController.value = TextEditingValue( - text: state.model.meta!.outputPrice.toString()); + outputPriceController.value = TextEditingValue(text: state.model.meta!.outputPrice.toString()); } if (state.model.meta!.perReqPrice != null) { - perRequestPriceController.value = TextEditingValue( - text: state.model.meta!.perReqPrice.toString()); + perRequestPriceController.value = TextEditingValue(text: state.model.meta!.perReqPrice.toString()); } - shortNameController.value = - TextEditingValue(text: state.model.shortName ?? ''); - promptController.value = - TextEditingValue(text: state.model.meta!.prompt ?? ''); + shortNameController.value = TextEditingValue(text: state.model.shortName ?? ''); + promptController.value = TextEditingValue(text: state.model.meta!.prompt ?? ''); supportVision = state.model.meta!.vision ?? false; restricted = state.model.meta!.restricted ?? false; - tagController.value = - TextEditingValue(text: state.model.meta!.tag ?? ''); + tagController.value = TextEditingValue(text: state.model.meta!.tag ?? ''); tagTextColor = state.model.meta!.tagTextColor; tagBgColor = state.model.meta!.tagBgColor; isNew = state.model.meta!.isNew ?? false; isRecommended = state.model.meta!.isRecommend ?? false; - categoryController.value = - TextEditingValue(text: state.model.meta!.category ?? ''); + categoryController.value = TextEditingValue(text: state.model.meta!.category ?? ''); setState(() {}); } @@ -229,8 +221,7 @@ class _AdminModelEditPageState extends State { }); }, child: Container( - padding: const EdgeInsets.only( - left: 10, right: 10, top: 10, bottom: 20), + padding: const EdgeInsets.only(left: 10, right: 10, top: 10, bottom: 20), child: Column( children: [ ColumnBlock( @@ -285,10 +276,8 @@ class _AdminModelEditPageState extends State { ? null : DecorationImage( image: (avatarUrl!.startsWith('http') - ? CachedNetworkImageProviderEnhanced( - avatarUrl!) - : FileImage(File( - avatarUrl!))) as ImageProvider, + ? CachedNetworkImageProviderEnhanced(avatarUrl!) + : FileImage(File(avatarUrl!))) as ImageProvider, fit: BoxFit.cover, ), ), @@ -348,18 +337,14 @@ class _AdminModelEditPageState extends State { hintText: 'Optional', showCounter: false, keyboardType: TextInputType.number, - inputFormatters: [ - FilteringTextInputFormatter.digitsOnly - ], + inputFormatters: [FilteringTextInputFormatter.digitsOnly], textDirection: TextDirection.rtl, suffixIcon: Container( width: 110, alignment: Alignment.center, child: Text( 'Credits/1K Token', - style: TextStyle( - color: customColors.weakTextColor, - fontSize: 12), + style: TextStyle(color: customColors.weakTextColor, fontSize: 12), ), ), ), @@ -372,18 +357,14 @@ class _AdminModelEditPageState extends State { hintText: 'Optional', showCounter: false, keyboardType: TextInputType.number, - inputFormatters: [ - FilteringTextInputFormatter.digitsOnly - ], + inputFormatters: [FilteringTextInputFormatter.digitsOnly], textDirection: TextDirection.rtl, suffixIcon: Container( width: 110, alignment: Alignment.center, child: Text( 'Credits/1K Token', - style: TextStyle( - color: customColors.weakTextColor, - fontSize: 12), + style: TextStyle(color: customColors.weakTextColor, fontSize: 12), ), ), ), @@ -396,168 +377,229 @@ class _AdminModelEditPageState extends State { hintText: 'Optional', showCounter: false, keyboardType: TextInputType.number, - inputFormatters: [ - FilteringTextInputFormatter.digitsOnly - ], + inputFormatters: [FilteringTextInputFormatter.digitsOnly], textDirection: TextDirection.rtl, suffixIcon: Container( width: 110, alignment: Alignment.center, child: Text( 'Credits/Request', - style: TextStyle( - color: customColors.weakTextColor, - fontSize: 12), + style: TextStyle(color: customColors.weakTextColor, fontSize: 12), ), ), ), EnhancedTextField( - labelText: 'Context Length', + labelWidth: 120, + labelText: 'Context Len', customColors: customColors, controller: maxContextController, textAlignVertical: TextAlignVertical.top, - hintText: - 'Subtract the expected output length from the maximum context.', + hintText: 'Subtract the expected output length from the maximum context.', showCounter: false, keyboardType: TextInputType.number, - inputFormatters: [ - FilteringTextInputFormatter.digitsOnly - ], + inputFormatters: [FilteringTextInputFormatter.digitsOnly], textDirection: TextDirection.rtl, suffixIcon: Container( width: 50, alignment: Alignment.center, child: Text( 'Token', - style: TextStyle( - color: customColors.weakTextColor, - fontSize: 12), + style: TextStyle(color: customColors.weakTextColor, fontSize: 12), ), ), ), ], ), - for (var i = 0; i < providers.length; i++) - Container( - margin: - const EdgeInsets.only(bottom: 10, left: 5, right: 5), - child: Slidable( - endActionPane: ActionPane( - motion: const ScrollMotion(), - children: [ - const SizedBox(width: 10), - SlidableAction( - label: AppLocale.delete.getString(context), - borderRadius: CustomSize.borderRadiusAll, - backgroundColor: Colors.red, - icon: Icons.delete, - onPressed: (_) { - if (providers.length == 1) { - showErrorMessage( - 'At least one channel is needed'); - return; - } - - openConfirmDialog( - context, - AppLocale.confirmToDeleteRoom - .getString(context), - () { - setState(() { - providers.removeAt(i); - }); - }, - danger: true, - ); - }, - ), - ], - ), - child: ColumnBlock( - margin: const EdgeInsets.all(0), - children: [ - EnhancedInput( - title: Text( - 'Channel', - style: TextStyle( - color: customColors.textfieldLabelColor, - fontSize: 16, - ), - ), - value: Text( - buildChannelName(providers[i]), - style: TextStyle( - color: customColors.textfieldValueColor, - fontSize: 16, + ImplicitlyAnimatedReorderableList( + items: providers, + shrinkWrap: true, + itemBuilder: (context, itemAnimation, item, index) { + return Reorderable( + key: ValueKey(item), + builder: (context, dragAnimation, inDrag) { + final t = dragAnimation.value; + final elevation = lerpDouble(0, 8, t); + final color = Color.lerp(Colors.white, Colors.white.withOpacity(0.8), t); + + return SizeFadeTransition( + sizeFraction: 0.7, + curve: Curves.easeInOut, + animation: itemAnimation, + child: Material( + color: color, + elevation: elevation ?? 0, + type: MaterialType.transparency, + child: Slidable( + startActionPane: ActionPane( + motion: const ScrollMotion(), + children: [ + const SizedBox(width: 10), + SlidableAction( + label: AppLocale.delete.getString(context), + borderRadius: CustomSize.borderRadiusAll, + backgroundColor: Colors.red, + icon: Icons.delete, + onPressed: (_) { + if (providers.length == 1) { + showErrorMessage('At least one channel is needed'); + return; + } + + openConfirmDialog( + context, + AppLocale.confirmToDeleteRoom.getString(context), + () { + setState(() { + providers.removeAt(index); + }); + }, + danger: true, + ); + }, + ), + ], ), - ), - onPressed: () { - openListSelectDialog( - context, - >[ - ...modelChannels - .map( - (e) => SelectorItem( - Text( - '${e.id == null ? '【System】' : ''}${e.name}'), - e, + child: ListTile( + contentPadding: const EdgeInsets.all(5), + title: ColumnBlock( + margin: const EdgeInsets.all(0), + children: [ + EnhancedInput( + title: Text( + 'Channel', + style: TextStyle( + color: customColors.textfieldLabelColor, + fontSize: 16, ), - ) - .toList(), - ], - (value) { - setState(() { - providers[i].id = value.value.id; - if (value.value.id == null) { - providers[i].name = value.value.type; - } - }); - return true; - }, - heightFactor: 0.5, - value: providers[i], - ); - }, - ), - EnhancedTextField( - labelWidth: 120, - labelText: 'Model Rewrite', - labelFontSize: 12, - customColors: customColors, - textAlignVertical: TextAlignVertical.top, - hintText: 'Optional', - maxLength: 100, - showCounter: false, - initValue: providers[i].modelRewrite, - onChanged: (value) { - setState(() { - providers[i].modelRewrite = value; - }); - }, - labelHelpWidget: InkWell( - onTap: () { - showBeautyDialog( - context, - type: QuickAlertType.info, - text: - 'When the model identifier corresponding to the channel does not match the ID here, calling the channel interface will automatically replace the model with the value configured here.', - confirmBtnText: - AppLocale.gotIt.getString(context), - showCancelBtn: false, - ); - }, - child: Icon( - Icons.help_outline, - size: 16, - color: customColors.weakLinkColor - ?.withAlpha(150), + ), + value: AutoSizeText( + buildChannelName(item), + maxLines: 1, + style: TextStyle( + color: customColors.textfieldValueColor, + fontSize: 14, + ), + ), + onPressed: () { + openListSelectDialog( + context, + >[ + ...modelChannels + .map( + (e) => SelectorItem( + Text('${e.id == null ? '【System】' : ''}${e.name}'), + e, + ), + ) + .toList(), + ], + (value) { + setState(() { + providers[index].id = value.value.id; + if (value.value.id == null) { + providers[index].name = value.value.type; + } + }); + return true; + }, + heightFactor: 0.5, + value: item, + ); + }, + ), + EnhancedTextField( + labelWidth: 90, + labelText: 'Rewrite', + customColors: customColors, + textAlignVertical: TextAlignVertical.top, + hintText: 'Optional', + maxLength: 100, + showCounter: false, + initValue: item.modelRewrite, + onChanged: (value) { + setState(() { + providers[index].modelRewrite = value; + }); + }, + labelHelpWidget: InkWell( + onTap: () { + showBeautyDialog( + context, + type: QuickAlertType.info, + text: + 'When the model identifier corresponding to the channel does not match the ID here, calling the channel interface will automatically replace the model with the value configured here.', + confirmBtnText: AppLocale.gotIt.getString(context), + showCancelBtn: false, + ); + }, + child: Icon( + Icons.help_outline, + size: 16, + color: customColors.weakLinkColor?.withAlpha(150), + ), + ), + ), + ], + ), + trailing: Handle( + delay: const Duration(milliseconds: 100), + child: Column( + children: [ + Container( + width: 15, + height: 15, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Colors.blue.withOpacity(0.1), + border: Border.all( + color: Colors.blue.withOpacity(0.3), + width: 1, + ), + boxShadow: [ + BoxShadow( + color: Colors.blue.withOpacity(0.1), + blurRadius: 2, + spreadRadius: 1, + ), + ], + ), + child: Center( + child: Text( + '${index + 1}', + style: TextStyle( + fontSize: 9, + color: Colors.blue.shade700, + fontWeight: FontWeight.w500, + ), + ), + ), + ), + const SizedBox(height: 10), + const Icon( + Icons.drag_indicator, + size: 20, + color: Colors.grey, + ), + ], + ), + ), ), ), ), - ], - ), - ), - ), + ); + }, + ); + }, + areItemsTheSame: (AdminModelProvider oldItem, AdminModelProvider newItem) { + return oldItem.id == newItem.id; + }, + onReorderFinished: (AdminModelProvider item, int from, int to, List newItems) { + setState(() { + providers = newItems; + }); + }, + ), + const SizedBox(width: 10), WeakTextButton( title: 'Add Channel', @@ -606,18 +648,15 @@ class _AdminModelEditPageState extends State { showBeautyDialog( context, type: QuickAlertType.info, - text: - 'Whether the current model supports visual capabilities.', - confirmBtnText: - AppLocale.gotIt.getString(context), + text: 'Whether the current model supports visual capabilities.', + confirmBtnText: AppLocale.gotIt.getString(context), showCancelBtn: false, ); }, child: Icon( Icons.help_outline, size: 16, - color: customColors.weakLinkColor - ?.withAlpha(150), + color: customColors.weakLinkColor?.withAlpha(150), ), ), ], @@ -650,16 +689,14 @@ class _AdminModelEditPageState extends State { type: QuickAlertType.info, text: 'Whether to display a "New" icon next to the model to inform users that this is a new model.', - confirmBtnText: - AppLocale.gotIt.getString(context), + confirmBtnText: AppLocale.gotIt.getString(context), showCancelBtn: false, ); }, child: Icon( Icons.help_outline, size: 16, - color: customColors.weakLinkColor - ?.withAlpha(150), + color: customColors.weakLinkColor?.withAlpha(150), ), ), ], @@ -692,16 +729,14 @@ class _AdminModelEditPageState extends State { type: QuickAlertType.info, text: 'Whether to display a "Recommended" icon next to the model to inform users that this is a recommended model.', - confirmBtnText: - AppLocale.gotIt.getString(context), + confirmBtnText: AppLocale.gotIt.getString(context), showCancelBtn: false, ); }, child: Icon( Icons.help_outline, size: 16, - color: customColors.weakLinkColor - ?.withAlpha(150), + color: customColors.weakLinkColor?.withAlpha(150), ), ), ], @@ -734,16 +769,14 @@ class _AdminModelEditPageState extends State { type: QuickAlertType.info, text: 'Restricted models refer to models that cannot be used in Chinese Mainland due to policy factors.', - confirmBtnText: - AppLocale.gotIt.getString(context), + confirmBtnText: AppLocale.gotIt.getString(context), showCancelBtn: false, ); }, child: Icon( Icons.help_outline, size: 16, - color: customColors.weakLinkColor - ?.withAlpha(150), + color: customColors.weakLinkColor?.withAlpha(150), ), ), ], @@ -801,9 +834,7 @@ class _AdminModelEditPageState extends State { color: customColors.weakLinkColor, fontSize: 15, icon: Icon( - showAdvancedOptions - ? Icons.unfold_less - : Icons.unfold_more, + showAdvancedOptions ? Icons.unfold_less : Icons.unfold_more, color: customColors.weakLinkColor, size: 15, ), @@ -820,10 +851,8 @@ class _AdminModelEditPageState extends State { title: AppLocale.save.getString(context), onPressed: onSubmit, icon: editLocked - ? const Icon(Icons.lock, - color: Colors.white, size: 16) - : const Icon(Icons.lock_open, - color: Colors.white, size: 16), + ? const Icon(Icons.lock, color: Colors.white, size: 16) + : const Icon(Icons.lock_open, color: Colors.white, size: 16), ), ), ], @@ -854,9 +883,7 @@ class _AdminModelEditPageState extends State { return; } - if (avatarUrl != null && - (!avatarUrl!.startsWith('http://') && - !avatarUrl!.startsWith('https://'))) { + if (avatarUrl != null && (!avatarUrl!.startsWith('http://') && !avatarUrl!.startsWith('https://'))) { final cancel = BotToast.showCustomLoading( toastBuilder: (cancel) { return const LoadingIndicator( @@ -867,8 +894,7 @@ class _AdminModelEditPageState extends State { ); try { - final res = await ImageUploader(widget.setting) - .upload(avatarUrl!, usage: 'avatar'); + final res = await ImageUploader(widget.setting).upload(avatarUrl!, usage: 'avatar'); avatarUrl = res.url; } catch (e) { showErrorMessage('Failed to upload avatar'); diff --git a/lib/page/component/enhanced_input.dart b/lib/page/component/enhanced_input.dart index 811c525e..403781c7 100644 --- a/lib/page/component/enhanced_input.dart +++ b/lib/page/component/enhanced_input.dart @@ -89,13 +89,8 @@ class EnhancedInput extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.end, mainAxisSize: MainAxisSize.min, children: [ - ConstrainedBox( - constraints: BoxConstraints( - maxWidth: MediaQuery.of(context).size.width - 160, - ), - child: value ?? Container(), - ), - const SizedBox(width: 10), + Flexible(child: value ?? Container()), + const SizedBox(width: 5), icon ?? const Icon( CupertinoIcons.chevron_forward, diff --git a/lib/page/component/enhanced_textfield.dart b/lib/page/component/enhanced_textfield.dart index 455dd420..9e8d873d 100644 --- a/lib/page/component/enhanced_textfield.dart +++ b/lib/page/component/enhanced_textfield.dart @@ -144,7 +144,8 @@ class _EnhancedTextFieldState extends State { @override Widget build(BuildContext context) { - if ((widget.labelText != null || widget.labelWidget != null) && widget.labelPosition != LabelPosition.inner) { + if ((widget.labelText != null || widget.labelWidget != null) && + widget.labelPosition != LabelPosition.inner) { // 上下结构 if (widget.labelPosition == LabelPosition.top) { return Column( @@ -167,7 +168,8 @@ class _EnhancedTextFieldState extends State { ), ), const SizedBox(width: 5), - if (widget.labelHelpWidget != null) widget.labelHelpWidget!, + if (widget.labelHelpWidget != null) + widget.labelHelpWidget!, ], ), if (widget.inputSelector != null) widget.inputSelector!, @@ -253,8 +255,10 @@ class _EnhancedTextFieldState extends State { fillColor: widget.customColors.textfieldBackgroundColor, hintText: widget.hintText, hintStyle: TextStyle( - fontSize: widget.hintTextSize ?? CustomSize.defaultHintTextSize, - color: widget.hintColor ?? widget.customColors.textfieldHintColor, + fontSize: widget.hintTextSize ?? + CustomSize.defaultHintTextSize, + color: widget.hintColor ?? + widget.customColors.textfieldHintColor, ), hintTextDirection: widget.textDirection, counterText: "", @@ -266,16 +270,22 @@ class _EnhancedTextFieldState extends State { top: widget.labelPosition == LabelPosition.top ? 0 : 10, left: widget.enableBackground ? 15 : 0, right: widget.enableBackground ? 15 : 0, - bottom: (widget.showCounter || widget.bottomButton != null) && widget.middleWidget == null + bottom: (widget.showCounter || + widget.bottomButton != null) && + widget.middleWidget == null ? 30 : 10, ), - labelText: widget.labelPosition == LabelPosition.inner ? widget.labelText : null, + labelText: widget.labelPosition == LabelPosition.inner + ? widget.labelText + : null, labelStyle: TextStyle( color: widget.customColors.textfieldLabelColor, ), suffixIcon: widget.suffixIcon ?? - (widget.labelPosition == LabelPosition.left ? widget.inputSelector : null), + (widget.labelPosition == LabelPosition.left + ? widget.inputSelector + : null), ), cursorRadius: CustomSize.radius, keyboardType: widget.keyboardType, @@ -327,7 +337,8 @@ class _EnhancedTextFieldState extends State { highlightColor: Colors.transparent, padding: const EdgeInsets.all(0), minWidth: 60, - shape: RoundedRectangleBorder(borderRadius: CustomSize.borderRadius), + shape: RoundedRectangleBorder( + borderRadius: CustomSize.borderRadius), onPressed: widget.bottomButtonOnPressed, child: widget.bottomButton!, ), @@ -340,7 +351,9 @@ class _EnhancedTextFieldState extends State { InputBorder resolveInputBorder() { if (widget.enableBackground) { - return const OutlineInputBorder(borderRadius: CustomSize.borderRadiusAll, borderSide: BorderSide.none); + return const OutlineInputBorder( + borderRadius: CustomSize.borderRadiusAll, + borderSide: BorderSide.none); } return InputBorder.none; diff --git a/pubspec.lock b/pubspec.lock index 7be64cd1..71b7746a 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -25,6 +25,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.0" + animated_list_plus: + dependency: "direct main" + description: + name: animated_list_plus + sha256: fb3d7f1fbaf5af84907f3c739236bacda8bf32cbe1f118dd51510752883ff50c + url: "https://pub.dev" + source: hosted + version: "0.5.2" animated_text_kit: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index d368f3f9..421ca4df 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -134,6 +134,7 @@ dependencies: camerawesome: ^2.1.0 flutter_markdown_latex: ^0.3.4 auto_size_text: ^3.0.0 + animated_list_plus: ^0.5.2 dev_dependencies: flutter_test: