diff --git a/lib/modules/voice_replication/controllers/voice_replication_controller.dart b/lib/modules/voice_replication/controllers/voice_replication_controller.dart index 021ba01f0..3f2f94c35 100644 --- a/lib/modules/voice_replication/controllers/voice_replication_controller.dart +++ b/lib/modules/voice_replication/controllers/voice_replication_controller.dart @@ -1,10 +1,8 @@ import 'package:get/get.dart'; import 'package:get_storage/get_storage.dart'; -import 'package:record/record.dart'; class VoiceReplicationController extends GetxController { final GetStorage _storage = GetStorage(); - final _record = AudioRecorder(); RxBool isReplicating = false.obs; @@ -16,12 +14,6 @@ class VoiceReplicationController extends GetxController { _getDefaultData(); } - @override - void onClose() { - _record.dispose(); - super.onClose(); - } - void _getDefaultData() { isReplicating.value = _storage.read('open_voice_replication') ?? false; speakerProfileId.value = _storage.read('speakerProfileId') ?? ''; diff --git a/lib/modules/voice_replication/views/recorder_bottom_sheet.dart b/lib/modules/voice_replication/views/recorder_bottom_sheet.dart new file mode 100644 index 000000000..018bd8842 --- /dev/null +++ b/lib/modules/voice_replication/views/recorder_bottom_sheet.dart @@ -0,0 +1,176 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:get/get.dart'; +import 'package:record/record.dart'; + +import '../controllers/voice_replication_controller.dart'; + +class RecorderBottomSheet extends StatefulWidget { + const RecorderBottomSheet({super.key}); + + @override + State createState() => _RecorderBottomSheetState(); +} + +class _RecorderBottomSheetState extends State { + final _controller = Get.find(); + + final _record = AudioRecorder(); + + bool _isRecording = false; + + @override + void initState() { + super.initState(); + } + + @override + void dispose() { + _record.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Padding( + padding: EdgeInsets.symmetric(horizontal: 12.w), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + padding: EdgeInsets.symmetric(vertical: 10.w), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + '同意并朗读使用个人语音', + style: TextStyle( + fontSize: 16.sp, + color: Colors.black87, + fontWeight: FontWeight.bold, + ), + ), + GestureDetector( + onTap: () => Get.back(), + child: Container( + width: 26.w, + height: 26.w, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(26.r), + ), + child: Icon( + Icons.close, + size: 18.w, + color: Colors.grey[400], + ), + ), + ) + ], + ), + ), + Divider( + height: 1, + color: Colors.grey[300], + ), + Container( + padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 20.w), + margin: EdgeInsets.symmetric(vertical: 20.w), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(12.w), + boxShadow: [ + BoxShadow( + color: Colors.black.withValues(alpha: 0.05), + blurRadius: 10, + offset: const Offset(0, 2), + ), + ], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + '请朗读:', + style: TextStyle( + fontSize: 14.sp, + color: Colors.black87, + ), + ), + Padding( + padding: EdgeInsets.only(top: 20.w, bottom: 50.w), + child: Text( + '我(你的姓名)确认我的声音将会被(深圳市云衍智能科技有限公司)使用于创建合成版本语音。', + style: TextStyle( + fontSize: 16.sp, + color: Colors.red, + ), + ), + ), + Text( + '例:我(李伟)确认我的声音将会被(深圳市云衍智能科技有限公司)使用于创建合成版本语音。', + style: TextStyle( + fontSize: 12.sp, + color: Colors.grey, + ), + ), + ], + ), + ), + Padding( + padding: EdgeInsets.only(bottom: 20.w), + child: Row( + children: [ + Expanded( + child: GestureDetector( + onTap: () => Get.back(), + child: Container( + height: 48.w, + alignment: Alignment.center, + decoration: BoxDecoration( + color: Colors.grey[300], + borderRadius: BorderRadius.circular(12.r), + ), + child: Text( + 'cancel'.tr, // 取消 + style: TextStyle( + fontSize: 16.sp, + color: Colors.black, + ), + ), + ), + ), + ), + 10.horizontalSpace, + Expanded( + child: GestureDetector( + onTap: () { + setState(() { + _isRecording = !_isRecording; + }); + }, + child: Container( + height: 48.w, + alignment: Alignment.center, + decoration: BoxDecoration( + color: _isRecording ? Colors.blue : Colors.red, + borderRadius: BorderRadius.circular(12.r), + ), + child: Text( + _isRecording ? 'confirm'.tr : 'startRecording'.tr, // 确认 + style: TextStyle( + fontSize: 16.sp, + color: Colors.white, + ), + ), + ), + ), + ), + ], + ), + ), + ], + ), + ); + } +} diff --git a/lib/modules/voice_replication/views/voice_replication_view.dart b/lib/modules/voice_replication/views/voice_replication_view.dart index f861c73be..24ec9af96 100644 --- a/lib/modules/voice_replication/views/voice_replication_view.dart +++ b/lib/modules/voice_replication/views/voice_replication_view.dart @@ -3,6 +3,7 @@ import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:get/get.dart'; import '../controllers/voice_replication_controller.dart'; +import 'recorder_bottom_sheet.dart'; class VoiceReplicationView extends GetView { const VoiceReplicationView({Key? key}) : super(key: key); @@ -117,26 +118,86 @@ class VoiceReplicationView extends GetView { ), ], ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - '开启声音复刻', - style: TextStyle( - color: isDarkMode ? Colors.white : Colors.black87, - fontSize: 16.sp, - ), - ), - Obx( - () => Switch( - value: controller.isReplicating.value, - onChanged: controller.switchChange, + child: Obx( + () => Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + '开启声音复刻', + style: TextStyle( + color: isDarkMode ? Colors.white : Colors.black87, + fontSize: 16.sp, + ), + ), + Transform.scale( + scale: 0.9, + alignment: Alignment.centerRight, + child: Switch( + value: controller.isReplicating.value, + onChanged: controller.switchChange, + ), + ), + ], ), - ), - ], + if (controller.isReplicating.value) _buildOpen(isDarkMode), + ], + ), ), ), ], ); } + + Widget _buildOpen(bool isDarkMode) { + return Container( + padding: EdgeInsets.symmetric(vertical: 10.w), + child: controller.speakerProfileId.value.isNotEmpty + ? Text( + '个人语音ID:${controller.speakerProfileId.value}', + style: TextStyle( + color: isDarkMode ? Colors.white : Colors.grey, + fontSize: 14.sp, + ), + ) + : GestureDetector( + onTap: () { + Get.bottomSheet( + const RecorderBottomSheet(), + isScrollControlled: true, + backgroundColor: + isDarkMode ? Colors.grey[800] : Colors.grey[100], + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.vertical( + top: Radius.circular(12.r), + ), + ), + ); + }, + child: Container( + padding: EdgeInsets.symmetric( + horizontal: 12.w, + vertical: 6.w, + ), + decoration: BoxDecoration( + color: Colors.blue.withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(6.r), + border: Border.all( + color: Colors.blue.withValues(alpha: 0.3), + ), + ), + child: Text( + '创建个人语音', + style: TextStyle( + fontSize: 14.sp, + color: Colors.blue, + fontWeight: FontWeight.w500, + ), + ), + ), + ), + ); + } }