Browse Source

Merge remote-tracking branch 'onewaygame/tanls' into lxm

newdev_shunjiawei
lxm 2 years ago
parent
commit
140d53e351
  1. 102
      lib/core/translations/app_translations.dart
  2. 155
      lib/core/translations/language/en_us.dart
  3. 150
      lib/core/translations/language/zh_cn.dart
  4. 19
      lib/core/widgets/common_bottom_nav.dart
  5. 26
      lib/main.dart
  6. 207
      lib/modules/chat/views/chat_view.dart
  7. 80
      lib/modules/explore/controllers/explore_controller.dart
  8. 60
      lib/modules/explore/views/explore_view.dart
  9. 115
      lib/modules/home/controllers/home_controller.dart
  10. 414
      lib/modules/home/views/home_view.dart
  11. 299
      lib/modules/login/views/login_view.dart
  12. 118
      lib/modules/pairing/controllers/pairing_controller.dart
  13. 61
      lib/modules/pairing/views/pairing_view.dart
  14. 98
      lib/modules/settings/controllers/settings_controller.dart
  15. 11
      lib/modules/settings/models/language_model.dart
  16. 398
      lib/modules/settings/views/settings_view.dart
  17. 30
      lib/modules/splash/controllers/splash_controller.dart
  18. 20
      lib/modules/splash/views/splash_view.dart
  19. 164
      lib/modules/translation/views/translation_view.dart
  20. 2
      pubspec.yaml

102
lib/core/translations/app_translations.dart

@ -1,81 +1,29 @@
import 'dart:ui';
import 'package:get/get.dart';
import 'package:get_storage/get_storage.dart';
import 'language/en_us.dart';
import 'language/zh_cn.dart';
class AppTranslations extends Translations {
static const fallbackLocale = Locale('zh', 'CN');
static final supportedLocales = [
const Locale('zh', 'CN'),
const Locale('en', 'US'),
];
static Locale? deviceLocale() {
final GetStorage storage = GetStorage();
Locale? locale = Get.deviceLocale;
String? language = storage.read('language');
if (language != null && language != 'system') {
List languageList = language.split('_');
locale = Locale(languageList[0], languageList[1]);
}
return locale;
}
@override
Map<String, Map<String, String>> get keys => {
'zh_CN': {
// 通用
'app_name': 'i深音',
'confirm': '确认',
'cancel': '取消',
'retry': '重试',
'error': '错误',
'success': '成功',
// 主页
'add_device': '添加设备',
'searching': '搜索中',
'searching_hint': '请将设备放在手机附近',
'connected': '已连接',
'device_ready': '设备已就绪',
'connection_failed': '连接失败',
'connection_failed_hint': '未找到设备。请确保蓝牙已开启且设备在5米范围内。尝试重启设备。',
'disconnect': '断开连接',
// 聊天页面
'clear_chat': '清空对话',
'thinking': '正在思考...',
'type_message': '发消息...',
'voice_enabled': '语音已开启',
'voice_disabled': '语音已关闭',
// 设置页面
'settings': '设置',
'language_settings': '语言设置',
'about': '关于',
'check_update': '检查更新',
'clear_cache': '清除缓存',
'home': '首页',
'explore': '发现',
'profile': '我的',
'device': '设备',
},
'en_US': {
// General
'app_name': 'iDeep Voice',
'confirm': 'Confirm',
'cancel': 'Cancel',
'retry': 'Retry',
'error': 'Error',
'success': 'Success',
// Home
'add_device': '+ Add Device',
'searching': 'Searching',
'searching_hint': 'Please place the device near your phone',
'connected': 'Connected!',
'device_ready': 'Your device is ready',
'connection_failed': 'Connection Failed',
'connection_failed_hint': 'Device not found. Please ensure Bluetooth is enabled and the device is within 5 meters. Try turning the device off and on again.',
'disconnect': 'Disconnect',
// Chat
'clear_chat': 'Clear Chat',
'thinking': 'Thinking...',
'type_message': 'Type a message...',
'voice_enabled': 'Voice Enabled',
'voice_disabled': 'Voice Disabled',
// Settings
'settings': 'Settings',
'language_settings': 'Language Settings',
'about': 'About',
'check_update': 'Check for Updates',
'clear_cache': 'Clear Cache',
'home': 'Home',
'explore': 'Explore',
'profile': 'Profile',
'device': 'Device',
},
};
}
Map<String, Map<String, String>> get keys => {'en_US': enUS, 'zh_CN': zhCN};
}

155
lib/core/translations/language/en_us.dart

@ -0,0 +1,155 @@
const Map<String, String> enUS = {
// 通用
'continue': 'Continue',
'confirm': 'Confirm',
'cancel': 'Cancel',
'retry': 'Retry',
'error': 'Error',
'success': 'Success',
'connected': 'Connected',
'connecting': 'Connecting...',
'connectionFailure': 'Connection Failed',
'unconnected': 'Disconnected',
'search': 'Search',
// 启动页
"slogan": "Connect the World, Beyond Voice",
"sloganInfo": "AI Voice Expert · Anytime, Anywhere",
// 底部导航
"home": "Home",
"explore": "Explore",
"settings": "Settings",
// 语言
"followingSystem": "Following system",
"chinese": "Chinese",
"english": "English",
// 登录页
"welcome": "Welcome",
"loginTips": "Please log in or register with your phone number or email",
"accountNumber": "Phone number or email",
"verificationCode": "Verification Code",
"getCode": "Get Code",
"reacquireAfter": "s Get it again after",
"otherWays": "Or login with",
"expressConsent": "By logging in/registering, you agree to the",
"and": " and ",
"userAgreement": "User Agreement",
"privacyPolicy": "Privacy Policy",
// 首页
"deviceStatus": "Device Status",
"reconnect": "Reconnect",
"connectingDevice": "Connecting to device...",
"connectedTo": "Connected to",
"signal": "Signal",
"deviceId": "Device ID",
"firmwareVersion": "Firmware Version",
"disconnectedDevice": "No device connected, click the button below",
"connectDevice": "Connect Device",
"topAiExperiences": "Top AI Experiences",
"aiAssistant": "AI Assistant",
"aiAssistantInfo": "24/7 personal consultant for questions and advice",
"simultaneousInterpretation": "Real-time Interpretation",
"simultaneousInterpretationInfo": "Real-time translation for 80+ languages",
"aiCompanion": "AI Companion",
"aiCompanionInfo": "Understands you and provides warm companionship",
"homeSlogan": "Connect the World, Beyond Sound",
"turnEarphones":
"Please open the earphone case and put the earbuds in your ears",
"currentlyConnected": "Currently connected:",
"unsystemSetup": "Go to System Settings",
"talkLater": "Remind Later",
"initializing": "Initializing...",
"bluetoothInitializationFailure": "Bluetooth service initialization failed",
"checkingPermissions": "Checking permissions...",
"lackPermissions": "Bluetooth permissions required",
"searchingDevice": "Searching for devices...",
"findConnectedDevice": "Found connected device",
"notFound": "No connected Bluetooth devices found",
"initializationFailure": "Initialization failed",
"connectedDevice": "Connected to device:",
"failedRefreshDevice": "Failed to refresh device",
"noDeviceFound": "No connected devices found",
"failedDeviceInformation": "Failed to get device information",
"unknownEquipment": "Unknown Device",
"zeroSignal": "No Signal",
"good": "Good",
"fine": "Great",
"excellent": "Excellent",
"unknown": "Unknown",
// 发现页
"searchAi": "Search AI features...",
"exploreSlogan": "Smart Earbuds, Infinite Possibilities",
"exploreAi": "Explore AI features provided by DeepVoice",
"aiFunction": "AI Features",
"allRoundAssistant": "All-round Assistant",
"allRoundAssistantInfo": "Helps solve various problems",
"simultaneousTranslation": "Real-time Translation",
"simultaneousTranslationInfo": "Instant translation for 80+ languages",
"aiGirlfriend": "AI Girlfriend",
"aiGirlfriendInfo": "Real-time translation for 80+ languages",
"healthConsultant": "Health Consultant",
"healthConsultantInfo": "Provides scientific health advice",
"conferenceAssistant": "Business Meeting Assistant",
"conferenceAssistantInfo":
"Real-time minutes recording and multilingual translation",
"studyAssistant": "Language Learning Assistant",
"studyAssistantInfo": "Pronunciation correction and speaking practice",
"travelTranslator": "Travel Translator",
"travelTranslatorInfo": "Real-time voice translation and attraction guides",
"beSearching": "Searching for:",
"noAssistantFound": "No corresponding AI assistant found",
"scene": "Scenarios",
"youHaveSelected": "You've selected:",
"newFunction": "New Features",
"youChoseTry": "You've chosen to try",
"aiSinger": "AI Singer Mode",
"aiSingerInfo":
"Sing any song in your favorite artist's voice, even your own creations",
"popularFunction": "Popular Features",
"tryNow": "Try Now",
// 设置页
"premiumMember": "Premium Membership",
"joinDevice": "Connected device:",
"accountId": "Account ID:",
"systemSetting": "System Settings",
"languageSetting": "Language Settings",
"darkMode": "Dark Mode",
"darkModeInfo": "Switch between light/dark themes",
"backups": "Backup & Restore",
"backupsInfo": "Backup settings and AI history",
"lastBackup": "Last backup:",
"privacySettings": "Privacy Settings",
"privacySettingsInfo": "Manage data collection and permissions",
"logout": "Log Out",
"logoutTips": "Are you sure you want to log out of the current account?",
"aboutApplication": "About",
"versions": "Version",
"checkUpdates": "Check Updates",
"selectLanguage": "Select Language",
"voiceService": "Voice Service",
"voiceServiceInfo": "Select speech recognition and synthesis service",
"selectVoiceService": "Select Voice Service",
"azureVoiceService": "Azure Speech Service",
"azureVoiceServiceInfo":
"Microsoft cloud-based service supporting high-quality speech synthesis and recognition",
"flutterVoiceService": "Local voice service",
"flutterVoiceServiceInfo":
"Device-local voice service with no internet required",
// 聊天页
"clearChatHistory": "Clear Chat History",
"online": "Online",
"inputMessage": "Type a message...",
"imListening": "Listening...",
"canStartTalking": "You may start speaking",
// 翻译页
"selectSourceLanguage": "Select Source Language",
"selectTargetLanguage": "Select Target Language",
};

150
lib/core/translations/language/zh_cn.dart

@ -0,0 +1,150 @@
const Map<String, String> zhCN = {
// 通用
'continue': '继续',
'confirm': '确认',
'cancel': '取消',
'retry': '重试',
'error': '错误',
'success': '成功',
'connected': '已连接',
'connecting': '连接中...',
'connectionFailure': '连接失败',
'unconnected': '未连接',
'search': '搜索',
// 启动页
"slogan": "连接世界,岂止声音",
"sloganInfo": "AI语音专家 · 随时随地",
// 底部导航
"home": "首页",
"explore": "发现",
"settings": "设置",
// 语言
"followingSystem": "跟随系统",
"chinese": "中文",
"english": "英语",
// 登录页
"welcome": "欢迎使用",
"loginTips": "请使用手机号或邮箱登录/注册",
"accountNumber": "手机号码或邮箱",
"verificationCode": "验证码",
"getCode": "获取验证码",
"reacquireAfter": "s后重新获取",
"otherWays": "或使用以下方式登录",
"expressConsent": "登录/注册即表示你同意",
"and": " 和 ",
"userAgreement": "《用户协议》",
"privacyPolicy": "《隐私政策》",
// 首页
"deviceStatus": "设备状态",
"reconnect": "重新连接",
"connectingDevice": "正在连接设备...",
"connectedTo": "已连接到",
"signal": "信号",
"deviceId": "设备ID",
"firmwareVersion": "固件版本",
"disconnectedDevice": "未连接设备,点击下方按钮连接",
"connectDevice": "连接设备",
"topAiExperiences": "热门AI体验",
"aiAssistant": "AI全能助手",
"aiAssistantInfo": "24小时专属顾问,解答问题、提供建议",
"simultaneousInterpretation": "同声传译",
"simultaneousInterpretationInfo": "80+语言实时翻译,出国无忧",
"aiCompanion": "AI伴侣",
"aiCompanionInfo": "懂你心意,温暖陪伴",
"homeSlogan": "连接世界,超越声音",
"turnEarphones": "请打开耳机充电盒,将耳机放入您的耳朵",
"currentlyConnected": "当前已连接:",
"unsystemSetup": "去系统设置",
"talkLater": "稍后再说",
"initializing": "正在初始化...",
"bluetoothInitializationFailure": "蓝牙服务初始化失败",
"checkingPermissions": "正在检查权限...",
"lackPermissions": "缺少蓝牙权限",
"searchingDevice": "正在搜索设备...",
"findConnectedDevice": "找到已连接的设备",
"notFound": "未找到已连接的蓝牙设备",
"initializationFailure": "初始化失败",
"connectedDevice": "已连接到设备:",
"failedRefreshDevice": "刷新设备失败",
"noDeviceFound": "未找到已连接的设备",
"failedDeviceInformation": "无法获取设备信息",
"unknownEquipment": "未知设备",
"zeroSignal": "无信号",
"good": "良好",
"fine": "很好",
"excellent": "极佳",
"unknown": "未知",
// 发现页
"searchAi": "搜索AI功能...",
"exploreSlogan": "智能耳机,无限可能",
"exploreAi": "探索DeepVoice提供的AI功能",
"aiFunction": "AI功能",
"allRoundAssistant": "全能助理",
"allRoundAssistantInfo": "帮你解决各类问题",
"simultaneousTranslation": "同声翻译",
"simultaneousTranslationInfo": "实时翻译80+种语言",
"aiGirlfriend": "AI女友",
"aiGirlfriendInfo": "实时翻译80+种语言",
"healthConsultant": "健康顾问",
"healthConsultantInfo": "提供科学健康建议",
"conferenceAssistant": "商务会议助手",
"conferenceAssistantInfo": "实时记录会议纪要,多语言翻译",
"studyAssistant": "语言学习助手",
"studyAssistantInfo": "发音纠正,实时练习口语",
"travelTranslator": "旅行翻译助手",
"travelTranslatorInfo": "实时语音翻译,旅游景点介绍",
"beSearching": "正在搜索:",
"noAssistantFound": "未找到对应的AI助手",
"scene": "场景",
"youHaveSelected": "您选择了:",
"newFunction": "新功能",
"youChoseTry": "您选择了尝试",
"aiSinger": "AI歌手模式",
"aiSingerInfo": "用你喜欢的歌手声音演唱任何歌曲,甚至是你自己创作的歌曲",
"popularFunction": "热门功能场景",
"tryNow": "立即尝试",
// 设置页
"premiumMember": "高级会员",
"joinDevice": "已连接设备:",
"accountId": "账号ID:",
"systemSetting": "系统设置",
"languageSetting": "语言设置",
"darkMode": "暗黑模式",
"darkModeInfo": "切换应用的明暗主题",
"backups": "备份与恢复",
"backupsInfo": "备份设置和AI互动历史",
"lastBackup": "上次备份:",
"privacySettings": "隐私设置",
"privacySettingsInfo": "管理数据收集和权限",
"logout": "退出登录",
"logoutTips": "确定要退出当前账号吗?",
"aboutApplication": "关于应用",
"versions": "版本",
"checkUpdates": "检查更新",
"selectLanguage": "选择语言",
"voiceService": "语音服务",
"voiceServiceInfo": "选择语音识别和合成服务",
"selectVoiceService": "选择语音服务",
"azureVoiceService": "Azure 语音服务",
"azureVoiceServiceInfo": "微软云端语音服务,支持高质量语音合成和识别",
"flutterVoiceService": "Flutter 本地语音服务",
"flutterVoiceServiceInfo": "设备本地语音服务,无需联网即可使用",
// 聊天页
"clearChatHistory": "清空聊天记录",
"online": "在线",
"inputMessage": "输入消息...",
"imListening": "正在聆听...",
"canStartTalking": "你可以开始说话",
// 翻译页
"selectSourceLanguage": "选择源语言",
"selectTargetLanguage": "选择目标语言",
};

19
lib/core/widgets/common_bottom_nav.dart

@ -4,7 +4,7 @@ import '../../routes/app_routes.dart';
class CommonBottomNav extends StatelessWidget {
final int currentIndex;
const CommonBottomNav({
Key? key,
required this.currentIndex,
@ -13,7 +13,7 @@ class CommonBottomNav extends StatelessWidget {
@override
Widget build(BuildContext context) {
final isDarkMode = Theme.of(context).brightness == Brightness.dark;
return BottomNavigationBar(
currentIndex: currentIndex,
onTap: (index) {
@ -30,25 +30,28 @@ class CommonBottomNav extends StatelessWidget {
break;
}
},
backgroundColor: isDarkMode ? Colors.black : const Color(0xFFF9FAFB), // 暗黑模式黑色背景,浅色模式浅灰色背景
backgroundColor: isDarkMode
? Colors.black
: const Color(0xFFF9FAFB), // 暗黑模式黑色背景,浅色模式浅灰色背景
selectedItemColor: const Color(0xFF3B82F6), // 蓝色选中项保持不变
unselectedItemColor: isDarkMode ? Colors.grey[500] : const Color(0xFF6B7280), // 暗黑模式灰色调整
unselectedItemColor:
isDarkMode ? Colors.grey[500] : const Color(0xFF6B7280), // 暗黑模式灰色调整
elevation: isDarkMode ? 0 : 8, // 暗黑模式无阴影,浅色模式保持阴影
type: BottomNavigationBarType.fixed,
items: [
BottomNavigationBarItem(
icon: const Icon(Icons.home),
label: 'home'.tr,
label: 'home'.tr, // 首页
),
BottomNavigationBarItem(
icon: const Icon(Icons.explore),
label: 'explore'.tr,
label: 'explore'.tr, // 发现
),
BottomNavigationBarItem(
icon: const Icon(Icons.settings),
label: 'settings'.tr,
label: 'settings'.tr, // 设置
),
],
);
}
}
}

26
lib/main.dart

@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:get/get.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:get_storage/get_storage.dart';
@ -15,22 +16,22 @@ import 'core/themes/theme_service.dart';
void main() async {
// 确保Flutter绑定初始化
WidgetsFlutterBinding.ensureInitialized();
// 初始化存储 - 在创建应用之前初始化
await GetStorage.init();
// 初始化环境变量
await dotenv.load();
// 设置窗口大小(仅在 macOS 下生效)
if (Platform.isMacOS) {
const double width = 375.0;
const double height = 812.0;
setWindowTitle('Deep Voice');
setWindowMinSize(const Size(width, height));
setWindowMaxSize(const Size(width, height));
// 居中显示窗口
final screen = await getCurrentScreen();
if (screen != null) {
@ -43,7 +44,7 @@ void main() async {
setWindowFrame(windowFrame);
}
}
// 运行应用
runApp(const MainApp());
}
@ -55,7 +56,7 @@ class MainApp extends StatelessWidget {
Widget build(BuildContext context) {
// 获取保存的主题设置
final themeMode = ThemeService().getThemeMode();
return ScreenUtilInit(
designSize: const Size(375, 812),
minTextAdapt: true,
@ -71,9 +72,14 @@ class MainApp extends StatelessWidget {
getPages: AppPages.pages,
debugShowCheckedModeBanner: false,
translations: AppTranslations(),
locale: const Locale('zh', 'CN'),
fallbackLocale: const Locale('en', 'US'),
localizationsDelegates: const [
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
locale: AppTranslations.deviceLocale(),
fallbackLocale: AppTranslations.fallbackLocale,
supportedLocales: AppTranslations.supportedLocales,
);
},
);

207
lib/modules/chat/views/chat_view.dart

@ -14,25 +14,22 @@ class ChatView extends GetView<ChatController> {
@override
Widget build(BuildContext context) {
final isDarkMode = Theme.of(context).brightness == Brightness.dark;
return Scaffold(
backgroundColor: isDarkMode ? AppColors.darkBackground : Colors.grey[50],
appBar: _buildAppBar(isDarkMode),
body: _buildBody(isDarkMode),
);
}
// 构建AppBar
PreferredSizeWidget _buildAppBar(bool isDarkMode) {
return AppBar(
elevation: 1,
backgroundColor: isDarkMode ? AppColors.cardBackground : Colors.white,
leading: IconButton(
icon: Icon(
Icons.arrow_back_ios,
color: isDarkMode ? Colors.white : Colors.black87,
size: 20.r
),
icon: Icon(Icons.arrow_back_ios,
color: isDarkMode ? Colors.white : Colors.black87, size: 20.r),
onPressed: () => Get.back(),
),
titleSpacing: 0,
@ -101,7 +98,7 @@ class ChatView extends GetView<ChatController> {
),
SizedBox(width: 4.w),
Text(
'在线',
'online'.tr, // 在线
style: TextStyle(
fontSize: 12.sp,
color: Colors.green,
@ -123,7 +120,9 @@ class ChatView extends GetView<ChatController> {
: Icons.volume_off,
color: controller.isTtsEnabled.value
? AppColors.primary
: isDarkMode ? Colors.grey[400] : Colors.grey,
: isDarkMode
? Colors.grey[400]
: Colors.grey,
),
onPressed: controller.toggleTts,
)),
@ -133,7 +132,8 @@ class ChatView extends GetView<ChatController> {
height: 34.r,
margin: EdgeInsets.only(right: 8.w),
decoration: BoxDecoration(
color: isDarkMode ? Colors.white.withOpacity(0.1) : Colors.grey[100],
color:
isDarkMode ? Colors.white.withOpacity(0.1) : Colors.grey[100],
shape: BoxShape.circle,
boxShadow: [
if (!isDarkMode)
@ -145,11 +145,8 @@ class ChatView extends GetView<ChatController> {
],
),
child: PopupMenuButton(
icon: Icon(
Icons.more_vert,
color: isDarkMode ? Colors.white : Colors.black87,
size: 20.r
),
icon: Icon(Icons.more_vert,
color: isDarkMode ? Colors.white : Colors.black87, size: 20.r),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12.r),
),
@ -162,7 +159,7 @@ class ChatView extends GetView<ChatController> {
Icon(Icons.delete_outline, color: Colors.red),
SizedBox(width: 8.w),
Text(
'清空聊天记录',
'clearChatHistory'.tr, // 清空聊天记录
style: TextStyle(
color: isDarkMode ? Colors.white : Colors.black87,
),
@ -183,7 +180,7 @@ class ChatView extends GetView<ChatController> {
],
);
}
// 构建主体内容
Widget _buildBody(bool isDarkMode) {
return Column(
@ -198,13 +195,13 @@ class ChatView extends GetView<ChatController> {
}
}),
),
// 底部输入区域
_buildInputArea(isDarkMode),
],
);
}
// 构建空状态
Widget _buildEmptyState(bool isDarkMode) {
return Center(
@ -215,13 +212,15 @@ class ChatView extends GetView<ChatController> {
width: 80.w,
height: 80.w,
decoration: BoxDecoration(
color: isDarkMode ? Colors.white.withOpacity(0.1) : Colors.grey[100],
color:
isDarkMode ? Colors.white.withOpacity(0.1) : Colors.grey[100],
shape: BoxShape.circle,
),
child: Icon(
Icons.chat_bubble_outline,
size: 40.w,
color: isDarkMode ? Colors.white.withOpacity(0.5) : Colors.grey[400],
color:
isDarkMode ? Colors.white.withOpacity(0.5) : Colors.grey[400],
),
),
SizedBox(height: 24.h),
@ -250,7 +249,7 @@ class ChatView extends GetView<ChatController> {
),
);
}
// 构建聊天列表
Widget _buildChatList(bool isDarkMode) {
return ListView.builder(
@ -266,32 +265,34 @@ class ChatView extends GetView<ChatController> {
},
);
}
// 构建消息项
Widget _buildMessageItem(Message message, bool isDarkMode) {
final isUser = message.role == 'user';
return Padding(
padding: EdgeInsets.only(bottom: 16.h),
child: Row(
mainAxisAlignment: isUser ? MainAxisAlignment.end : MainAxisAlignment.start,
mainAxisAlignment:
isUser ? MainAxisAlignment.end : MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (!isUser) _buildAvatar(false, isDarkMode),
SizedBox(width: 8.w),
Flexible(
child: Column(
crossAxisAlignment: isUser ? CrossAxisAlignment.end : CrossAxisAlignment.start,
crossAxisAlignment:
isUser ? CrossAxisAlignment.end : CrossAxisAlignment.start,
children: [
// 消息气泡
Container(
padding: EdgeInsets.all(12.r),
decoration: BoxDecoration(
color: isUser
? AppColors.primary
: isDarkMode ? AppColors.cardBackground : Colors.grey[200],
color: isUser
? AppColors.primary
: isDarkMode
? AppColors.cardBackground
: Colors.grey[200],
borderRadius: BorderRadius.circular(16.r),
),
child: Column(
@ -302,14 +303,16 @@ class ChatView extends GetView<ChatController> {
SelectableText(
message.content,
style: TextStyle(
color: isUser
? Colors.white
: isDarkMode ? Colors.white : Colors.black,
color: isUser
? Colors.white
: isDarkMode
? Colors.white
: Colors.black,
fontSize: 16.sp,
height: 1.5,
),
),
// 加载中动画
if (message.isLoading)
Padding(
@ -325,7 +328,7 @@ class ChatView extends GetView<ChatController> {
],
),
),
// 时间戳
Padding(
padding: EdgeInsets.only(top: 4.h, left: 4.w, right: 4.w),
@ -340,9 +343,7 @@ class ChatView extends GetView<ChatController> {
],
),
),
SizedBox(width: 8.w),
if (isUser) _buildAvatar(true, isDarkMode),
],
),
@ -351,8 +352,9 @@ class ChatView extends GetView<ChatController> {
// 构建输入区域
Widget _buildInputArea(bool isDarkMode) {
final textController = TextEditingController(text: controller.inputText.value);
final textController =
TextEditingController(text: controller.inputText.value);
// 检查是否处于语音识别模式
return Obx(() {
if (controller.isVoiceRecognitionActive.value) {
@ -362,9 +364,10 @@ class ChatView extends GetView<ChatController> {
}
});
}
// 构建文本输入面板
Widget _buildTextInputPanel(bool isDarkMode, TextEditingController textController) {
Widget _buildTextInputPanel(
bool isDarkMode, TextEditingController textController) {
return Container(
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 8.h),
decoration: BoxDecoration(
@ -385,7 +388,9 @@ class ChatView extends GetView<ChatController> {
width: 40.w,
height: 40.w,
decoration: BoxDecoration(
color: isDarkMode ? Colors.white.withOpacity(0.1) : Colors.grey[100],
color: isDarkMode
? Colors.white.withOpacity(0.1)
: Colors.grey[100],
shape: BoxShape.circle,
),
child: IconButton(
@ -398,20 +403,21 @@ class ChatView extends GetView<ChatController> {
onPressed: controller.startVoiceRecognition,
),
),
SizedBox(width: 8.w),
// 输入框
Expanded(
child: TextField(
controller: textController,
decoration: InputDecoration(
hintText: '输入消息...',
hintText: 'inputMessage'.tr, // 输入消息...
hintStyle: TextStyle(
color: isDarkMode ? Colors.grey[500] : Colors.grey[500]
),
color: isDarkMode ? Colors.grey[500] : Colors.grey[500]),
filled: true,
fillColor: isDarkMode ? Colors.white.withOpacity(0.1) : Colors.grey[100],
fillColor: isDarkMode
? Colors.white.withOpacity(0.1)
: Colors.grey[100],
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(20.r),
borderSide: BorderSide.none,
@ -435,9 +441,9 @@ class ChatView extends GetView<ChatController> {
},
),
),
SizedBox(width: 8.w),
// 发送按钮
Obx(() {
final hasText = controller.inputText.value.isNotEmpty;
@ -445,17 +451,21 @@ class ChatView extends GetView<ChatController> {
width: 40.w,
height: 40.w,
decoration: BoxDecoration(
color: hasText
? AppColors.primary
: isDarkMode ? Colors.white.withOpacity(0.1) : Colors.grey[200],
color: hasText
? AppColors.primary
: isDarkMode
? Colors.white.withOpacity(0.1)
: Colors.grey[200],
shape: BoxShape.circle,
),
child: IconButton(
icon: Icon(
Icons.send,
color: hasText
? Colors.white
: isDarkMode ? Colors.white.withOpacity(0.5) : Colors.grey[400],
color: hasText
? Colors.white
: isDarkMode
? Colors.white.withOpacity(0.5)
: Colors.grey[400],
size: 18.r,
),
padding: EdgeInsets.zero,
@ -473,7 +483,7 @@ class ChatView extends GetView<ChatController> {
),
);
}
// 构建语音输入面板
Widget _buildVoiceInputPanel(bool isDarkMode) {
return Container(
@ -501,7 +511,9 @@ class ChatView extends GetView<ChatController> {
width: 60.r,
height: 60.r,
decoration: BoxDecoration(
color: isDarkMode ? Colors.white.withOpacity(0.1) : Colors.white,
color: isDarkMode
? Colors.white.withOpacity(0.1)
: Colors.white,
shape: BoxShape.circle,
boxShadow: [
BoxShadow(
@ -513,13 +525,17 @@ class ChatView extends GetView<ChatController> {
),
child: Center(
child: Obx(() => Icon(
controller.isListening.value ? Icons.mic : Icons.mic_none,
color: controller.isListening.value ? AppColors.primary : Colors.grey.shade400,
size: 28.r,
)),
controller.isListening.value
? Icons.mic
: Icons.mic_none,
color: controller.isListening.value
? AppColors.primary
: Colors.grey.shade400,
size: 28.r,
)),
),
),
// 中间音频波形和状态文本
Expanded(
child: Column(
@ -542,10 +558,10 @@ class ChatView extends GetView<ChatController> {
23,
(index) {
// 随机高度模拟波形
double height = isSpeaking
? (Random().nextDouble() * 20.0 + 5.0).h
double height = isSpeaking
? (Random().nextDouble() * 20.0 + 5.0).h
: 2.h;
return SizedBox(
width: 6.w,
child: Center(
@ -553,12 +569,16 @@ class ChatView extends GetView<ChatController> {
duration: Duration(milliseconds: 500),
width: 2.5.w,
height: height,
margin: EdgeInsets.symmetric(horizontal: 1.5.w),
margin: EdgeInsets.symmetric(
horizontal: 1.5.w),
decoration: BoxDecoration(
color: isSpeaking
? AppColors.primary
: isDarkMode ? Colors.grey.shade600 : Colors.grey.shade400,
borderRadius: BorderRadius.circular(2.r),
color: isSpeaking
? AppColors.primary
: isDarkMode
? Colors.grey.shade600
: Colors.grey.shade400,
borderRadius:
BorderRadius.circular(2.r),
),
),
),
@ -571,26 +591,32 @@ class ChatView extends GetView<ChatController> {
}),
),
SizedBox(height: 8.h),
// 语音状态文本
Obx(() => Text(
controller.isSpeechDetected.value ? '正在聆听...' : '你可以开始说话',
style: TextStyle(
fontSize: 14.sp,
color: isDarkMode ? Colors.grey[400] : Colors.grey[700],
fontWeight: FontWeight.w500,
),
)),
controller.isSpeechDetected.value
? 'imListening'.tr // 正在聆听...
: 'canStartTalking'.tr, // 你可以开始说话
style: TextStyle(
fontSize: 14.sp,
color: isDarkMode
? Colors.grey[400]
: Colors.grey[700],
fontWeight: FontWeight.w500,
),
)),
],
),
),
// 关闭按钮
Container(
width: 60.r,
height: 60.r,
decoration: BoxDecoration(
color: isDarkMode ? Colors.white.withOpacity(0.1) : Colors.white,
color: isDarkMode
? Colors.white.withOpacity(0.1)
: Colors.white,
shape: BoxShape.circle,
boxShadow: [
BoxShadow(
@ -619,15 +645,15 @@ class ChatView extends GetView<ChatController> {
),
);
}
// 构建头像
Widget _buildAvatar(bool isUser, bool isDarkMode) {
return Container(
width: 36.r,
height: 36.r,
decoration: BoxDecoration(
color: isUser
? (isDarkMode ? Colors.white.withOpacity(0.1) : Colors.grey[200])
color: isUser
? (isDarkMode ? Colors.white.withOpacity(0.1) : Colors.grey[200])
: AppColors.primary,
shape: BoxShape.circle,
boxShadow: [
@ -668,7 +694,7 @@ class ChatView extends GetView<ChatController> {
),
);
}
// 构建加载动画点
Widget _buildLoadingDot(int index, bool isDarkMode) {
return AnimatedBuilder(
@ -679,21 +705,22 @@ class ChatView extends GetView<ChatController> {
width: 8.r,
height: 8.r,
decoration: BoxDecoration(
color: isDarkMode ? Colors.white.withOpacity(0.5) : Colors.grey[400],
color:
isDarkMode ? Colors.white.withOpacity(0.5) : Colors.grey[400],
shape: BoxShape.circle,
),
);
},
);
}
// 格式化时间
String _formatTime(DateTime time) {
final now = DateTime.now();
final today = DateTime(now.year, now.month, now.day);
final yesterday = today.subtract(const Duration(days: 1));
final messageDate = DateTime(time.year, time.month, time.day);
if (messageDate == today) {
return '今天 ${time.hour.toString().padLeft(2, '0')}:${time.minute.toString().padLeft(2, '0')}';
} else if (messageDate == yesterday) {
@ -702,4 +729,4 @@ class ChatView extends GetView<ChatController> {
return '${time.month}月${time.day}日 ${time.hour.toString().padLeft(2, '0')}:${time.minute.toString().padLeft(2, '0')}';
}
}
}
}

80
lib/modules/explore/controllers/explore_controller.dart

@ -13,7 +13,7 @@ class AIFeature {
final Color iconColor;
final String agentId;
final String targetRoute;
AIFeature({
required this.title,
required this.description,
@ -34,7 +34,7 @@ class FunctionScene {
final Color startColor;
final Color endColor;
final Color iconColor;
FunctionScene({
required this.title,
required this.description,
@ -48,12 +48,12 @@ class FunctionScene {
class ExploreController extends GetxController {
// 搜索关键词
final searchController = TextEditingController();
// AI功能列表
final List<AIFeature> aiFeatures = [
AIFeature(
title: '全能助理',
description: '帮你解决各类问题',
title: 'allRoundAssistant'.tr, // 全能助理
description: 'allRoundAssistantInfo'.tr, // 帮你解决各类问题
icon: Icons.assistant,
startColor: const Color(0xFF0066CC),
endColor: const Color(0xFF5E5CE6),
@ -63,8 +63,8 @@ class ExploreController extends GetxController {
targetRoute: Routes.chat,
),
AIFeature(
title: '同声翻译',
description: '实时翻译80+种语言',
title: 'simultaneousTranslation'.tr, // 同声翻译
description: 'simultaneousTranslationInfo.tr', // 实时翻译80+种语言
icon: Icons.translate,
startColor: const Color(0xFF4CAF50),
endColor: const Color(0xFF009688),
@ -74,8 +74,8 @@ class ExploreController extends GetxController {
targetRoute: Routes.translation,
),
AIFeature(
title: 'AI女友',
description: '智能陪伴,随时聊天',
title: 'aiGirlfriend'.tr, // AI女友
description: 'aiGirlfriendInfo'.tr, // 智能陪伴,随时聊天
icon: Icons.favorite,
startColor: const Color(0xFFE91E63),
endColor: const Color(0xFFFF5252),
@ -85,8 +85,8 @@ class ExploreController extends GetxController {
targetRoute: Routes.chat,
),
AIFeature(
title: '健康顾问',
description: '提供科学健康建议',
title: 'healthConsultant'.tr, // 健康顾问
description: 'healthConsultantInfo'.tr, // 提供科学健康建议
icon: Icons.healing,
startColor: const Color(0xFFFFC107),
endColor: const Color(0xFFFF9800),
@ -96,65 +96,65 @@ class ExploreController extends GetxController {
targetRoute: Routes.chat,
),
];
// 热门功能场景列表
final List<FunctionScene> functionScenes = [
FunctionScene(
title: '商务会议助手',
description: '实时记录会议纪要,多语言翻译',
title: 'conferenceAssistant'.tr, // 商务会议助手
description: 'conferenceAssistantInfo'.tr, // 实时记录会议纪要,多语言翻译
icon: Icons.people,
startColor: const Color(0xFF3F51B5),
endColor: const Color(0xFF673AB7),
iconColor: const Color(0xFF3F51B5),
),
FunctionScene(
title: '语言学习助手',
description: '发音纠正,实时练习口语',
title: 'studyAssistant'.tr, // 语言学习助手
description: 'studyAssistantInfo'.tr, // 发音纠正,实时练习口语
icon: Icons.school,
startColor: const Color(0xFF4CAF50),
endColor: const Color(0xFF009688),
iconColor: const Color(0xFF4CAF50),
),
FunctionScene(
title: '旅行翻译助手',
description: '实时语音翻译,旅游景点介绍',
title: 'travelTranslator'.tr, // 旅行翻译助手
description: 'travelTranslatorInfo'.tr, // 实时语音翻译,旅游景点介绍
icon: Icons.language,
startColor: const Color(0xFFFFC107),
endColor: const Color(0xFFFF9800),
iconColor: const Color(0xFFFFC107),
),
];
// 搜索功能
void search() {
if (searchController.text.isEmpty) return;
Get.snackbar(
'搜索',
'正在搜索: ${searchController.text}',
'search'.tr, // 搜索
'${'beSearching'.tr} ${searchController.text}', // 正在搜索:
snackPosition: SnackPosition.TOP,
);
// 实际应用中,这里应该实现真正的搜索逻辑
searchController.clear();
}
// 点击AI功能
void onAIFeatureTap(int index) {
if (index < 0 || index >= aiFeatures.length) return;
final feature = aiFeatures[index];
final agent = AgentProvider.getAgentById(feature.agentId);
if (agent == null) {
Get.snackbar(
'错误',
'未找到对应的AI助手',
'error'.tr, // 错误
'noAssistantFound'.tr, // 未找到对应的AI助手
snackPosition: SnackPosition.TOP,
);
return;
}
// 根据目标路由进行跳转
if (feature.targetRoute == Routes.chat) {
// 跳转到聊天页面
@ -172,34 +172,34 @@ class ExploreController extends GetxController {
Get.toNamed(feature.targetRoute);
}
}
// 点击功能场景
void onFunctionSceneTap(int index) {
if (index < 0 || index >= functionScenes.length) return;
Get.snackbar(
'场景',
'您选择了: ${functionScenes[index].title}',
'scene'.tr, // 场景
'${'youHaveSelected'.tr} ${functionScenes[index].title}', // 您选择了:
snackPosition: SnackPosition.TOP,
);
// 实际应用中,这里应该跳转到对应的场景页面
}
// 尝试新功能
void tryNewFeature() {
Get.snackbar(
'新功能',
'您选择了尝试AI歌手模式',
'newFunction'.tr, // 新功能
'${'youChoseTry'.tr}${'aiSinger'.tr}', // 您选择了尝试 // AI歌手模式
snackPosition: SnackPosition.TOP,
);
// 实际应用中,这里应该跳转到新功能页面
}
@override
void onClose() {
searchController.dispose();
super.onClose();
}
}
}

60
lib/modules/explore/views/explore_view.dart

@ -11,7 +11,7 @@ class ExploreView extends GetView<ExploreController> {
@override
Widget build(BuildContext context) {
final isDarkMode = Theme.of(context).brightness == Brightness.dark;
return Scaffold(
backgroundColor: isDarkMode ? Colors.black : Colors.grey[50],
body: SafeArea(
@ -19,7 +19,7 @@ class ExploreView extends GetView<ExploreController> {
children: [
// 标题和搜索
_buildHeaderAndSearch(isDarkMode),
// 主要内容(可滚动)
Expanded(
child: SingleChildScrollView(
@ -31,15 +31,15 @@ class ExploreView extends GetView<ExploreController> {
children: [
// 特色功能
_buildFeaturedCard(),
// AI功能列表
SizedBox(height: 24.h),
_buildAIFeaturesSection(isDarkMode),
// 热门功能场景
SizedBox(height: 24.h),
_buildFunctionScenesSection(isDarkMode),
// 新功能推荐
SizedBox(height: 24.h),
_buildNewFeatureSection(isDarkMode),
@ -66,7 +66,7 @@ class ExploreView extends GetView<ExploreController> {
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'发现',
'explore'.tr, // 发现
style: TextStyle(
fontSize: 24.sp,
fontWeight: FontWeight.bold,
@ -77,12 +77,14 @@ class ExploreView extends GetView<ExploreController> {
width: 32.w,
height: 32.w,
decoration: BoxDecoration(
color: isDarkMode ? Colors.white.withOpacity(0.1) : Colors.grey[200],
color: isDarkMode
? Colors.white.withOpacity(0.1)
: Colors.grey[200],
shape: BoxShape.circle,
),
child: IconButton(
icon: Icon(
Icons.more_vert,
Icons.more_vert,
size: 16.sp,
color: isDarkMode ? Colors.white : Colors.grey[700],
),
@ -92,18 +94,19 @@ class ExploreView extends GetView<ExploreController> {
),
],
),
// 搜索框
SizedBox(height: 16.h),
Container(
decoration: BoxDecoration(
color: isDarkMode ? Colors.white.withOpacity(0.1) : Colors.grey[100],
color:
isDarkMode ? Colors.white.withOpacity(0.1) : Colors.grey[100],
borderRadius: BorderRadius.circular(12.r),
),
child: TextField(
controller: controller.searchController,
decoration: InputDecoration(
hintText: '搜索AI功能...',
hintText: 'searchAi'.tr, // 搜索AI功能...
hintStyle: TextStyle(
fontSize: 14.sp,
color: isDarkMode ? Colors.grey[400] : Colors.grey[400],
@ -169,7 +172,7 @@ class ExploreView extends GetView<ExploreController> {
),
),
),
// 渐变遮罩
Container(
decoration: BoxDecoration(
@ -184,7 +187,7 @@ class ExploreView extends GetView<ExploreController> {
),
),
),
// 文本内容
Padding(
padding: EdgeInsets.all(24.w),
@ -193,7 +196,7 @@ class ExploreView extends GetView<ExploreController> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'智能耳机,无限可能',
'exploreSlogan'.tr, // 智能耳机,无限可能
style: TextStyle(
fontSize: 22.sp,
fontWeight: FontWeight.bold,
@ -202,7 +205,7 @@ class ExploreView extends GetView<ExploreController> {
),
SizedBox(height: 4.h),
Text(
'探索DeepVoice提供的AI功能',
'exploreAi'.tr, // 探索DeepVoice提供的AI功能
style: TextStyle(
fontSize: 14.sp,
color: Colors.white.withOpacity(0.8),
@ -222,7 +225,7 @@ class ExploreView extends GetView<ExploreController> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'AI功能',
'aiFunction'.tr, // AI功能
style: TextStyle(
fontSize: 18.sp,
fontWeight: FontWeight.w600,
@ -267,7 +270,7 @@ class ExploreView extends GetView<ExploreController> {
borderRadius: BorderRadius.circular(20.r),
boxShadow: [
BoxShadow(
color: isDarkMode
color: isDarkMode
? Colors.black.withOpacity(0.2)
: Colors.grey.withOpacity(0.1),
blurRadius: 8,
@ -338,7 +341,7 @@ class ExploreView extends GetView<ExploreController> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'热门功能场景',
'popularFunction'.tr, // 热门功能场景
style: TextStyle(
fontSize: 18.sp,
fontWeight: FontWeight.w600,
@ -363,7 +366,8 @@ class ExploreView extends GetView<ExploreController> {
}
// 功能场景项
Widget _buildFunctionSceneItem(FunctionScene scene, int index, bool isDarkMode) {
Widget _buildFunctionSceneItem(
FunctionScene scene, int index, bool isDarkMode) {
return GestureDetector(
onTap: () => controller.onFunctionSceneTap(index),
child: Container(
@ -373,7 +377,7 @@ class ExploreView extends GetView<ExploreController> {
borderRadius: BorderRadius.circular(16.r),
boxShadow: [
BoxShadow(
color: isDarkMode
color: isDarkMode
? Colors.black.withOpacity(0.2)
: Colors.grey.withOpacity(0.1),
blurRadius: 4,
@ -405,7 +409,7 @@ class ExploreView extends GetView<ExploreController> {
),
),
SizedBox(width: 16.w),
// 文本内容
Expanded(
child: Column(
@ -478,10 +482,10 @@ class ExploreView extends GetView<ExploreController> {
),
),
SizedBox(height: 16.h),
// 标题
Text(
'AI歌手模式',
'aiSinger'.tr, // AI歌手模式
style: TextStyle(
fontSize: 20.sp,
fontWeight: FontWeight.bold,
@ -489,17 +493,17 @@ class ExploreView extends GetView<ExploreController> {
),
),
SizedBox(height: 8.h),
// 描述
Text(
'用你喜欢的歌手声音演唱任何歌曲,甚至是你自己创作的歌曲',
'aiSingerInfo'.tr, // 用你喜欢的歌手声音演唱任何歌曲,甚至是你自己创作的歌曲
style: TextStyle(
fontSize: 14.sp,
color: isDarkMode ? Colors.grey[300] : Colors.grey[600],
),
),
SizedBox(height: 16.h),
// 按钮
ElevatedButton(
onPressed: controller.tryNewFeature,
@ -514,7 +518,7 @@ class ExploreView extends GetView<ExploreController> {
),
),
child: Text(
'立即尝试',
'tryNow'.tr, // 立即尝试
style: TextStyle(
fontSize: 14.sp,
fontWeight: FontWeight.w500,
@ -526,4 +530,4 @@ class ExploreView extends GetView<ExploreController> {
),
);
}
}
}

115
lib/modules/home/controllers/home_controller.dart

@ -6,76 +6,77 @@ import 'package:flutter_blue_plus/flutter_blue_plus.dart' hide BluetoothDevice;
import 'package:flutter/material.dart';
import '../../../data/services/voice_interaction_service.dart';
import 'package:permission_handler/permission_handler.dart';
// 连接状态枚举
enum ConnectionStatus {
disconnected, // 未连接
connecting, // 连接中
connected, // 已连接
failed // 连接失败
disconnected, // 未连接
connecting, // 连接中
connected, // 已连接
failed // 连接失败
}
class HomeController extends GetxController {
static HomeController get to => Get.find();
// 经典蓝牙服务
late classic.ClassicBluetoothService _bluetoothService;
// 设备连接状态
final _isConnected = false.obs;
bool get isConnected => _isConnected.value;
// 连接状态
final _connectionStatus = ConnectionStatus.disconnected.obs;
ConnectionStatus get connectionStatus => _connectionStatus.value;
// 电池状态(模拟数据)
final _deviceBattery = Rx<int?>(null);
int? get deviceBattery => _deviceBattery.value;
// 固件版本(模拟数据)
final String firmwareVersion = 'v2.4.1';
// 蓝牙设备信息
final _deviceInfo = <String, dynamic>{}.obs;
Map<String, dynamic> get deviceInfo => _deviceInfo.value;
// 连接错误信息
final _connectionErrorMsg = ''.obs;
String get connectionErrorMsg => _connectionErrorMsg.value;
// 当前连接的经典蓝牙设备
final _connectedClassicDevices = <classic.BluetoothDevice>[].obs;
List<classic.BluetoothDevice> get connectedClassicDevices => _connectedClassicDevices;
List<classic.BluetoothDevice> get connectedClassicDevices =>
_connectedClassicDevices;
@override
void onInit() {
super.onInit();
// 初始化蓝牙服务和状态监控
_initBluetoothService();
// 电池模拟更新(实际应用中可以通过蓝牙获取)
_simulateBatteryUpdate();
}
// 初始化蓝牙服务
void _initBluetoothService() {
try {
// 获取经典蓝牙服务实例
_bluetoothService = Get.find<classic.ClassicBluetoothService>();
// 更新当前连接状态
_updateConnectionStatus();
// 监听蓝牙连接状态变化
_setupBluetoothListeners();
} catch (e) {
Logger.error('初始化蓝牙服务失败: $e');
_connectionStatus.value = ConnectionStatus.failed;
}
}
// 电池电量模拟更新
void _simulateBatteryUpdate() {
// 每5分钟更新一次电池电量(模拟)
@ -86,17 +87,17 @@ class HomeController extends GetxController {
} else {
_deviceBattery.value = null;
}
// 递归调用实现定时更新
_simulateBatteryUpdate();
});
}
// 更新连接状态
void _updateConnectionStatus() {
// 检查当前设备
final currentDevice = _bluetoothService.currentDevice.value;
if (currentDevice != null) {
_isConnected.value = true;
_connectionStatus.value = ConnectionStatus.connected;
@ -104,10 +105,10 @@ class HomeController extends GetxController {
_isConnected.value = false;
_connectionStatus.value = ConnectionStatus.disconnected;
}
update(); // 通知UI刷新
}
// 设置蓝牙监听器
void _setupBluetoothListeners() {
// 监听设备连接
@ -117,7 +118,7 @@ class HomeController extends GetxController {
_connectionStatus.value = ConnectionStatus.connected;
update();
});
// 监听设备断开
_bluetoothService.onDeviceDisconnected((device) {
Logger.info('监听到蓝牙设备断开: ${device.name}');
@ -126,11 +127,11 @@ class HomeController extends GetxController {
_deviceBattery.value = null;
update();
});
// 监听蓝牙状态变化
_bluetoothService.onBluetoothStateChanged((state) {
Logger.info('监听到蓝牙状态变化: $state');
if (state == 'off') {
_isConnected.value = false;
_connectionStatus.value = ConnectionStatus.disconnected;
@ -139,26 +140,26 @@ class HomeController extends GetxController {
}
});
}
// 刷新设备信息
Future<void> refreshDeviceInfo() async {
try {
// 设置状态为连接中
_connectionStatus.value = ConnectionStatus.connecting;
update();
// 刷新设备列表
final hasDevices = await _bluetoothService.refreshConnectedDevices();
// 更新状态
_updateConnectionStatus();
// 根据结果更新状态
if (!hasDevices) {
Logger.info('刷新设备:未找到设备');
// 这里可以设置状态变量而不是使用snackbar
_connectionErrorMsg.value = _bluetoothService.errorMessage.value.isEmpty
? '未找到已连接的设备'
_connectionErrorMsg.value = _bluetoothService.errorMessage.value.isEmpty
? 'noDeviceFound'.tr // 未找到已连接的设备
: _bluetoothService.errorMessage.value;
} else {
_connectionErrorMsg.value = '';
@ -167,32 +168,32 @@ class HomeController extends GetxController {
} catch (e) {
Logger.error('刷新设备信息失败: $e');
_connectionStatus.value = ConnectionStatus.failed;
_connectionErrorMsg.value = '无法获取设备信息';
_connectionErrorMsg.value = 'failedDeviceInformation'.tr; // 无法获取设备信息
update();
}
}
// 重新扫描设备
void rescanDevices() {
Get.toNamed(Routes.pairing);
}
// 打开翻译功能
void openTranslation() {
Get.toNamed(Routes.translation);
}
// 打开AI伴侣功能
void openAIGirlfriend() {
Get.toNamed(Routes.chat);
}
// 展示AI功能
void exploreAIFeatures() {
// 简单实现,直接跳转到AI聊天页面
openAIGirlfriend();
}
// 获取连接状态对应的颜色
Color getConnectionStatusColor() {
switch (_connectionStatus.value) {
@ -207,49 +208,49 @@ class HomeController extends GetxController {
return Colors.grey;
}
}
// 获取连接状态对应的文本
String getConnectionStatusText() {
switch (_connectionStatus.value) {
case ConnectionStatus.connected:
return '已连接';
return 'connected'.tr; // 已连接
case ConnectionStatus.connecting:
return '连接中...';
return 'connecting'.tr; // 连接中...
case ConnectionStatus.failed:
return '连接失败';
return 'connectionFailure'.tr; // 连接失败
case ConnectionStatus.disconnected:
default:
return '未连接';
return 'unconnected'.tr; // 未连接
}
}
// 获取设备名称
String getDeviceName() {
final device = _bluetoothService.currentDevice.value;
return device?.name ?? '未知设备';
return device?.name ?? 'unknownEquipment'.tr; // 未知设备
}
// 获取信号强度(模拟数据)
String getSignalStrength() {
if (!_isConnected.value) {
return '无信号';
return 'zeroSignal'.tr; // 无信号
}
// 简单模拟信号强度
final random = DateTime.now().millisecondsSinceEpoch % 3;
switch (random) {
case 0:
return '良好';
return 'good'.tr; // 良好
case 1:
return '很好';
return 'fine'.tr; // 很好
default:
return '极佳';
return 'excellent'.tr; // 极佳
}
}
// 获取设备ID
String getDeviceId() {
final device = _bluetoothService.currentDevice.value;
return device?.address ?? '未知';
return device?.address ?? 'unknown'.tr; // 未知
}
}
}

414
lib/modules/home/views/home_view.dart

@ -16,7 +16,8 @@ class AnimatedPulsing extends StatefulWidget {
_AnimatedPulsingState createState() => _AnimatedPulsingState();
}
class _AnimatedPulsingState extends State<AnimatedPulsing> with SingleTickerProviderStateMixin {
class _AnimatedPulsingState extends State<AnimatedPulsing>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _animation;
@ -74,7 +75,7 @@ class HomeView extends GetView<HomeController> {
@override
Widget build(BuildContext context) {
final bool isDarkMode = Theme.of(context).brightness == Brightness.dark;
return Scaffold(
backgroundColor: isDarkMode ? Colors.black : Color(0xFFF9FAFC),
body: SafeArea(
@ -85,13 +86,13 @@ class HomeView extends GetView<HomeController> {
children: [
// 顶部标题栏
_buildAppBar(isDarkMode),
// 顶部图片
Center(
child: _buildEarbudsImage(),
),
SizedBox(height: 20.h),
// 设备状态卡片
_buildDeviceStatusCard(isDarkMode),
SizedBox(height: 20.h),
@ -111,43 +112,41 @@ class HomeView extends GetView<HomeController> {
// 顶部标题栏
Widget _buildAppBar(bool isDarkMode) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'DeepVoice',
style: TextStyle(
fontSize: 20.sp,
fontWeight: FontWeight.bold,
style: TextStyle(
fontSize: 20.sp,
fontWeight: FontWeight.bold,
color: isDarkMode ? Colors.white : Colors.grey[800],
),
),
Container(
width: 32.w,
height: 32.w,
decoration: BoxDecoration(
),
),
Container(
width: 32.w,
height: 32.w,
decoration: BoxDecoration(
color: isDarkMode ? Colors.grey[800] : Colors.grey[200],
shape: BoxShape.circle,
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.2),
blurRadius: 4,
offset: const Offset(0, 2),
),
],
),
child: IconButton(
icon: Icon(
Icons.settings_outlined,
size: 16.sp,
color: isDarkMode ? Colors.white70 : Colors.grey[600]
),
padding: EdgeInsets.zero,
onPressed: () {
Get.toNamed(Routes.settings);
},
),
shape: BoxShape.circle,
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.2),
blurRadius: 4,
offset: const Offset(0, 2),
),
],
),
child: IconButton(
icon: Icon(Icons.settings_outlined,
size: 16.sp,
color: isDarkMode ? Colors.white70 : Colors.grey[600]),
padding: EdgeInsets.zero,
onPressed: () {
Get.toNamed(Routes.settings);
},
),
),
],
);
}
@ -158,7 +157,9 @@ class HomeView extends GetView<HomeController> {
height: 220.h,
alignment: Alignment.center,
child: Image.asset(
isDarkMode ? 'assets/images/headphone_dark.png' : 'assets/images/headphone_light.png',
isDarkMode
? 'assets/images/headphone_dark.png'
: 'assets/images/headphone_light.png',
height: 180.h,
fit: BoxFit.contain,
),
@ -187,7 +188,7 @@ class HomeView extends GetView<HomeController> {
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'设备状态',
'deviceStatus'.tr, // 设备状态
style: TextStyle(
fontSize: 14.sp,
fontWeight: FontWeight.w500,
@ -195,52 +196,57 @@ class HomeView extends GetView<HomeController> {
),
),
Obx(() => Row(
children: [
Container(
width: 8.w,
height: 8.w,
decoration: BoxDecoration(
color: controller.getConnectionStatusColor(),
shape: BoxShape.circle,
),
),
SizedBox(width: 4.w),
Text(
controller.getConnectionStatusText(),
style: TextStyle(
fontSize: 12.sp,
color: controller.getConnectionStatusColor(),
),
),
// 添加刷新按钮
SizedBox(width: 8.w),
InkWell(
onTap: controller.refreshDeviceInfo,
child: Icon(
Icons.refresh,
size: 16.sp,
color: isDarkMode ? Colors.grey[400] : Colors.grey[600],
),
),
],
)),
children: [
Container(
width: 8.w,
height: 8.w,
decoration: BoxDecoration(
color: controller.getConnectionStatusColor(),
shape: BoxShape.circle,
),
),
SizedBox(width: 4.w),
Text(
controller.getConnectionStatusText(),
style: TextStyle(
fontSize: 12.sp,
color: controller.getConnectionStatusColor(),
),
),
// 添加刷新按钮
SizedBox(width: 8.w),
InkWell(
onTap: controller.refreshDeviceInfo,
child: Icon(
Icons.refresh,
size: 16.sp,
color:
isDarkMode ? Colors.grey[400] : Colors.grey[600],
),
),
],
)),
],
),
// 设备信息
SizedBox(height: 12.h),
Obx(() {
// 如果连接失败且有错误信息,显示错误提示
if (controller.connectionStatus == ConnectionStatus.failed &&
if (controller.connectionStatus == ConnectionStatus.failed &&
controller.connectionErrorMsg.isNotEmpty) {
return Column(
children: [
Container(
padding: EdgeInsets.all(8.w),
decoration: BoxDecoration(
color: isDarkMode ? Colors.red[900]!.withOpacity(0.2) : Colors.red[50],
color: isDarkMode
? Colors.red[900]!.withOpacity(0.2)
: Colors.red[50],
borderRadius: BorderRadius.circular(8.r),
border: Border.all(color: isDarkMode ? Colors.red[800]! : Colors.red[200]!),
border: Border.all(
color:
isDarkMode ? Colors.red[800]! : Colors.red[200]!),
),
child: Row(
children: [
@ -255,7 +261,9 @@ class HomeView extends GetView<HomeController> {
controller.connectionErrorMsg,
style: TextStyle(
fontSize: 12.sp,
color: isDarkMode ? Colors.red[300] : Colors.red[800],
color: isDarkMode
? Colors.red[300]
: Colors.red[800],
),
),
),
@ -274,12 +282,12 @@ class HomeView extends GetView<HomeController> {
borderRadius: BorderRadius.circular(8.r),
),
),
child: Text('重新连接'),
child: Text('reconnect'.tr), // 重新连接
),
],
);
}
// 如果正在连接中,显示连接中提示
if (controller.connectionStatus == ConnectionStatus.connecting) {
return Container(
@ -292,12 +300,13 @@ class HomeView extends GetView<HomeController> {
height: 24.w,
child: CircularProgressIndicator(
strokeWidth: 2.w,
valueColor: AlwaysStoppedAnimation<Color>(Colors.blue),
valueColor:
AlwaysStoppedAnimation<Color>(Colors.blue),
),
),
SizedBox(height: 8.h),
Text(
'正在连接设备...',
'connectingDevice'.tr, // 正在连接设备...
style: TextStyle(
fontSize: 12.sp,
color: Colors.blue,
@ -308,119 +317,135 @@ class HomeView extends GetView<HomeController> {
),
);
}
return controller.isConnected
? Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Text(
'已连接到:${controller.getDeviceName()}',
style: TextStyle(
fontSize: 12.sp,
fontWeight: FontWeight.w500,
color: isDarkMode ? Colors.grey[300] : Colors.grey[700],
return controller.isConnected
? Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Text(
'${'connectedTo'.tr}:${controller.getDeviceName()}', // 已连接到
style: TextStyle(
fontSize: 12.sp,
fontWeight: FontWeight.w500,
color: isDarkMode
? Colors.grey[300]
: Colors.grey[700],
),
overflow: TextOverflow.ellipsis,
),
overflow: TextOverflow.ellipsis,
),
),
Container(
padding: EdgeInsets.symmetric(horizontal: 6.w, vertical: 2.h),
decoration: BoxDecoration(
color: Colors.blue.withOpacity(0.1),
borderRadius: BorderRadius.circular(4.r),
),
child: Text(
'信号: ${controller.getSignalStrength()}',
style: TextStyle(
fontSize: 10.sp,
color: Colors.blue[700],
Container(
padding: EdgeInsets.symmetric(
horizontal: 6.w, vertical: 2.h),
decoration: BoxDecoration(
color: Colors.blue.withOpacity(0.1),
borderRadius: BorderRadius.circular(4.r),
),
child: Text(
'${'signal'.tr}: ${controller.getSignalStrength()}', // 信号
style: TextStyle(
fontSize: 10.sp,
color: Colors.blue[700],
),
),
),
),
],
),
SizedBox(height: 8.h),
Container(
padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 6.h),
decoration: BoxDecoration(
color: isDarkMode ? Colors.grey[900] : Colors.grey[50],
borderRadius: BorderRadius.circular(8.r),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'设备ID:${controller.getDeviceId()}',
style: TextStyle(
fontSize: 10.sp,
color: isDarkMode ? Colors.grey[400] : Colors.grey[500],
SizedBox(height: 8.h),
Container(
padding: EdgeInsets.symmetric(
horizontal: 8.w, vertical: 6.h),
decoration: BoxDecoration(
color:
isDarkMode ? Colors.grey[900] : Colors.grey[50],
borderRadius: BorderRadius.circular(8.r),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'${'deviceId'.tr}:${controller.getDeviceId()}', // 设备ID
style: TextStyle(
fontSize: 10.sp,
color: isDarkMode
? Colors.grey[400]
: Colors.grey[500],
),
),
),
SizedBox(height: 4.h),
Text(
'固件版本:${controller.firmwareVersion}',
style: TextStyle(
fontSize: 10.sp,
color: isDarkMode ? Colors.grey[400] : Colors.grey[500],
SizedBox(height: 4.h),
Text(
'${'firmwareVersion'.tr}:${controller.firmwareVersion}', // 固件版本
style: TextStyle(
fontSize: 10.sp,
color: isDarkMode
? Colors.grey[400]
: Colors.grey[500],
),
),
),
SizedBox(height: 4.h),
// 显示设备状态(已连接/已配对)
Row(
children: [
Container(
padding: EdgeInsets.symmetric(horizontal: 6.w, vertical: 2.h),
decoration: BoxDecoration(
color: controller.getConnectionStatusColor().withOpacity(0.1),
borderRadius: BorderRadius.circular(4.r),
),
child: Text(
controller.getConnectionStatusText(),
style: TextStyle(
fontSize: 10.sp,
color: controller.getConnectionStatusColor(),
SizedBox(height: 4.h),
// 显示设备状态(已连接/已配对)
Row(
children: [
Container(
padding: EdgeInsets.symmetric(
horizontal: 6.w, vertical: 2.h),
decoration: BoxDecoration(
color: controller
.getConnectionStatusColor()
.withOpacity(0.1),
borderRadius: BorderRadius.circular(4.r),
),
child: Text(
controller.getConnectionStatusText(),
style: TextStyle(
fontSize: 10.sp,
color:
controller.getConnectionStatusColor(),
),
),
),
),
],
),
],
),
),
],
)
: Container(
padding: EdgeInsets.symmetric(vertical: 8.h),
alignment: Alignment.center,
decoration: BoxDecoration(
color: isDarkMode ? Colors.grey[900] : Colors.grey[100],
borderRadius: BorderRadius.circular(8.r),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.bluetooth_disabled,
size: 14.sp,
color: isDarkMode ? Colors.grey[400] : Colors.grey[700],
),
SizedBox(width: 4.w),
Text(
'未连接设备,点击下方按钮连接',
style: TextStyle(
fontSize: 12.sp,
color: isDarkMode ? Colors.grey[400] : Colors.grey[700],
],
),
],
),
),
],
),
);
)
: Container(
padding: EdgeInsets.symmetric(vertical: 8.h),
alignment: Alignment.center,
decoration: BoxDecoration(
color: isDarkMode ? Colors.grey[900] : Colors.grey[100],
borderRadius: BorderRadius.circular(8.r),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.bluetooth_disabled,
size: 14.sp,
color:
isDarkMode ? Colors.grey[400] : Colors.grey[700],
),
SizedBox(width: 4.w),
Text(
'disconnectedDevice'.tr, // 未连接设备,点击下方按钮连接
style: TextStyle(
fontSize: 12.sp,
color: isDarkMode
? Colors.grey[400]
: Colors.grey[700],
),
),
],
),
);
}),
// 连接按钮(仅在未连接时显示)
SizedBox(height: 12.h),
Obx(() {
@ -435,10 +460,10 @@ class HomeView extends GetView<HomeController> {
borderRadius: BorderRadius.circular(8.r),
),
),
child: Text('连接设备'),
child: Text('connectDevice'.tr), // 连接设备
);
} else {
return SizedBox();
return const SizedBox();
}
}),
],
@ -451,7 +476,7 @@ class HomeView extends GetView<HomeController> {
return Row(
children: [
Text(
'热门AI体验',
'topAiExperiences'.tr, // 热门AI体验
style: TextStyle(
fontSize: 18.sp,
fontWeight: FontWeight.bold,
@ -478,8 +503,8 @@ class HomeView extends GetView<HomeController> {
Expanded(
flex: 1,
child: _buildFeatureCard(
title: 'AI全能助手',
description: '24小时专属顾问,解答问题、提供建议',
title: 'aiAssistant'.tr, // AI全能助手
description: 'aiAssistantInfo'.tr, // 24小时专属顾问,解答问题、提供建议
icon: Icons.assistant,
gradient: LinearGradient(
begin: Alignment.topLeft,
@ -496,9 +521,9 @@ class HomeView extends GetView<HomeController> {
iconColor: const Color(0xFF0066CC),
),
),
SizedBox(width: 16.w),
// 右侧两个卡片
Expanded(
flex: 1,
@ -506,8 +531,9 @@ class HomeView extends GetView<HomeController> {
children: [
// 右上卡片 - 同声翻译
_buildFeatureCard(
title: '同声传译',
description: '80+语言实时翻译,出国无忧',
title: 'simultaneousInterpretation'.tr, // 同声传译
description:
'simultaneousInterpretationInfo'.tr, // 80+语言实时翻译,出国无忧
icon: Icons.translate,
gradient: LinearGradient(
begin: Alignment.topLeft,
@ -522,13 +548,13 @@ class HomeView extends GetView<HomeController> {
isDarkMode: isDarkMode,
iconColor: const Color(0xFF4CAF50),
),
SizedBox(height: 5.h), // 减少间距
// 右下卡片 - AI女友
_buildFeatureCard(
title: 'AI伴侣',
description: '懂你心意,温暖陪伴',
title: 'aiCompanion'.tr, // AI伴侣
description: 'aiCompanionInfo'.tr, // 懂你心意,温暖陪伴
icon: Icons.favorite,
gradient: LinearGradient(
begin: Alignment.topLeft,
@ -549,7 +575,7 @@ class HomeView extends GetView<HomeController> {
],
);
}
// 特性卡片构建方法
Widget _buildFeatureCard({
required String title,
@ -572,7 +598,7 @@ class HomeView extends GetView<HomeController> {
borderRadius: BorderRadius.circular(16.r),
boxShadow: [
BoxShadow(
color: isDarkMode
color: isDarkMode
? Colors.black.withOpacity(0.2)
: Colors.grey.withOpacity(0.1),
blurRadius: 8,
@ -583,7 +609,7 @@ class HomeView extends GetView<HomeController> {
child: Stack(
children: [
// 图标
if (iconOnTop)
if (iconOnTop)
// 大卡片布局
Align(
alignment: Alignment.topLeft,
@ -619,7 +645,7 @@ class HomeView extends GetView<HomeController> {
),
),
),
// 文本内容
if (iconOnTop)
// 大卡片布局 - 文本
@ -688,4 +714,4 @@ class HomeView extends GetView<HomeController> {
),
);
}
}
}

299
lib/modules/login/views/login_view.dart

@ -11,7 +11,7 @@ class LoginView extends GetView<LoginController> {
@override
Widget build(BuildContext context) {
final isDarkMode = Theme.of(context).brightness == Brightness.dark;
return Scaffold(
backgroundColor: isDarkMode ? Colors.black : Colors.grey[50],
body: SafeArea(
@ -33,11 +33,11 @@ class LoginView extends GetView<LoginController> {
// 应用标志和标题
SizedBox(height: 32.h),
_buildAppLogo(isDarkMode),
// 欢迎文本
SizedBox(height: 32.h),
_buildWelcomeText(isDarkMode),
// 输入框和继续按钮
SizedBox(height: 24.h),
_buildInputField(isDarkMode),
@ -45,11 +45,11 @@ class LoginView extends GetView<LoginController> {
_buildVerificationInputField(isDarkMode),
SizedBox(height: 16.h),
_buildContinueButton(isDarkMode),
// 社交媒体登录
SizedBox(height: 32.h),
_buildSocialLoginSection(isDarkMode),
// 用户协议
SizedBox(height: 40.h),
_buildTermsAndPrivacy(isDarkMode),
@ -71,23 +71,23 @@ class LoginView extends GetView<LoginController> {
children: [
// 返回按钮
_buildBackButton(isDarkMode),
// 验证码标题
SizedBox(height: 24.h),
_buildVerificationTitle(isDarkMode),
// 验证码输入框
SizedBox(height: 32.h),
_buildVerificationInputs(context, isDarkMode),
// 重新发送验证码
SizedBox(height: 16.h),
_buildResendButton(isDarkMode),
// 登录按钮
SizedBox(height: 32.h),
_buildLoginButton(isDarkMode),
// 使用密码登录
SizedBox(height: 16.h),
_buildUsePasswordButton(isDarkMode),
@ -137,7 +137,7 @@ class LoginView extends GetView<LoginController> {
),
SizedBox(height: 8.h),
Text(
'连接世界,超越声音',
'slogan'.tr, // 连接世界,超越声音
style: TextStyle(
fontSize: 14.sp,
color: isDarkMode ? Colors.grey[400] : Colors.black54,
@ -152,7 +152,7 @@ class LoginView extends GetView<LoginController> {
return Column(
children: [
Text(
'欢迎使用',
'welcome'.tr, // 欢迎使用
style: TextStyle(
fontSize: 20.sp,
fontWeight: FontWeight.w600,
@ -161,7 +161,7 @@ class LoginView extends GetView<LoginController> {
),
SizedBox(height: 4.h),
Text(
'请使用手机号或邮箱登录/注册',
'loginTips'.tr, // 请使用手机号或邮箱登录/注册
style: TextStyle(
fontSize: 14.sp,
color: isDarkMode ? Colors.grey[400] : Colors.black54,
@ -175,7 +175,9 @@ class LoginView extends GetView<LoginController> {
Widget _buildInputField(bool isDarkMode) {
return Container(
decoration: BoxDecoration(
color: isDarkMode ? Colors.grey[900]!.withOpacity(0.4) : Colors.grey.withOpacity(0.1),
color: isDarkMode
? Colors.grey[900]!.withOpacity(0.4)
: Colors.grey.withOpacity(0.1),
borderRadius: BorderRadius.circular(16.r),
),
child: Padding(
@ -192,7 +194,7 @@ class LoginView extends GetView<LoginController> {
child: TextField(
controller: controller.contactController,
decoration: InputDecoration(
hintText: '手机号码或邮箱',
hintText: 'accountNumber'.tr, // 手机号码或邮箱
hintStyle: TextStyle(
fontSize: 14.sp,
color: isDarkMode ? Colors.grey[600] : Colors.black38,
@ -217,7 +219,9 @@ class LoginView extends GetView<LoginController> {
Widget _buildVerificationInputField(bool isDarkMode) {
return Container(
decoration: BoxDecoration(
color: isDarkMode ? Colors.grey[900]!.withOpacity(0.4) : Colors.grey.withOpacity(0.1),
color: isDarkMode
? Colors.grey[900]!.withOpacity(0.4)
: Colors.grey.withOpacity(0.1),
borderRadius: BorderRadius.circular(16.r),
),
child: Padding(
@ -236,7 +240,7 @@ class LoginView extends GetView<LoginController> {
keyboardType: TextInputType.number,
maxLength: 6,
decoration: InputDecoration(
hintText: '验证码',
hintText: 'verificationCode'.tr, // 验证码
hintStyle: TextStyle(
fontSize: 14.sp,
color: isDarkMode ? Colors.grey[600] : Colors.black38,
@ -253,25 +257,26 @@ class LoginView extends GetView<LoginController> {
),
),
Obx(() => TextButton(
onPressed: controller.countdownSeconds > 0
? null
: controller.getVerificationCode,
style: TextButton.styleFrom(
padding: EdgeInsets.symmetric(horizontal: 16.w),
foregroundColor: const Color(0xFF3B82F6),
backgroundColor: Colors.transparent,
disabledForegroundColor: isDarkMode ? Colors.grey[600] : Colors.grey[400],
),
child: Text(
controller.countdownSeconds > 0
? '${controller.countdownSeconds}s后重新获取'
: '获取验证码',
style: TextStyle(
fontSize: 14.sp,
fontWeight: FontWeight.w500,
),
),
)),
onPressed: controller.countdownSeconds > 0
? null
: controller.getVerificationCode,
style: TextButton.styleFrom(
padding: EdgeInsets.symmetric(horizontal: 16.w),
foregroundColor: const Color(0xFF3B82F6),
backgroundColor: Colors.transparent,
disabledForegroundColor:
isDarkMode ? Colors.grey[600] : Colors.grey[400],
),
child: Text(
controller.countdownSeconds > 0
? '${controller.countdownSeconds}${'reacquireAfter'.tr}' // s后重新获取
: 'getCode'.tr, // 获取验证码
style: TextStyle(
fontSize: 14.sp,
fontWeight: FontWeight.w500,
),
),
)),
],
),
),
@ -283,35 +288,36 @@ class LoginView extends GetView<LoginController> {
return SizedBox(
width: double.infinity,
child: Obx(() => ElevatedButton(
onPressed: controller.isLoading ? null : () => controller.login(),
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF0066CC),
foregroundColor: Colors.white,
padding: EdgeInsets.symmetric(vertical: 14.h),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16.r),
),
elevation: 0,
disabledBackgroundColor: const Color(0xFF0066CC).withOpacity(0.7),
disabledForegroundColor: Colors.white,
),
child: controller.isLoading
? SizedBox(
height: 20.h,
width: 20.h,
child: CircularProgressIndicator(
strokeWidth: 2.w,
valueColor: const AlwaysStoppedAnimation<Color>(Colors.white),
),
)
: Text(
'继续',
style: TextStyle(
fontSize: 16.sp,
fontWeight: FontWeight.w600,
),
onPressed: controller.isLoading ? null : () => controller.login(),
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF0066CC),
foregroundColor: Colors.white,
padding: EdgeInsets.symmetric(vertical: 14.h),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16.r),
),
)),
elevation: 0,
disabledBackgroundColor: const Color(0xFF0066CC).withOpacity(0.7),
disabledForegroundColor: Colors.white,
),
child: controller.isLoading
? SizedBox(
height: 20.h,
width: 20.h,
child: CircularProgressIndicator(
strokeWidth: 2.w,
valueColor:
const AlwaysStoppedAnimation<Color>(Colors.white),
),
)
: Text(
'continue'.tr, // 继续
style: TextStyle(
fontSize: 16.sp,
fontWeight: FontWeight.w600,
),
),
)),
);
}
@ -321,18 +327,22 @@ class LoginView extends GetView<LoginController> {
children: [
Row(
children: [
Expanded(child: Divider(color: isDarkMode ? Colors.grey[800] : Colors.grey[300])),
Expanded(
child: Divider(
color: isDarkMode ? Colors.grey[800] : Colors.grey[300])),
Padding(
padding: EdgeInsets.symmetric(horizontal: 16.w),
child: Text(
'或使用以下方式登录',
'otherWays'.tr, // 或使用以下方式登录
style: TextStyle(
fontSize: 12.sp,
color: isDarkMode ? Colors.grey[500] : Colors.grey[600],
),
),
),
Expanded(child: Divider(color: isDarkMode ? Colors.grey[800] : Colors.grey[300])),
Expanded(
child: Divider(
color: isDarkMode ? Colors.grey[800] : Colors.grey[300])),
],
),
SizedBox(height: 24.h),
@ -341,7 +351,8 @@ class LoginView extends GetView<LoginController> {
children: [
_buildSocialButton(FontAwesomeIcons.google, Colors.red, isDarkMode),
SizedBox(width: 24.w),
_buildSocialButton(FontAwesomeIcons.weixin, Colors.green, isDarkMode),
_buildSocialButton(
FontAwesomeIcons.weixin, Colors.green, isDarkMode),
],
),
],
@ -356,13 +367,15 @@ class LoginView extends GetView<LoginController> {
decoration: BoxDecoration(
color: isDarkMode ? Colors.grey[800] : Colors.white,
borderRadius: BorderRadius.circular(24.r),
boxShadow: isDarkMode ? [] : [
BoxShadow(
color: Colors.grey.withOpacity(0.2),
blurRadius: 4,
offset: const Offset(0, 2),
),
],
boxShadow: isDarkMode
? []
: [
BoxShadow(
color: Colors.grey.withOpacity(0.2),
blurRadius: 4,
offset: const Offset(0, 2),
),
],
),
child: IconButton(
icon: FaIcon(
@ -380,7 +393,7 @@ class LoginView extends GetView<LoginController> {
return Column(
children: [
Text(
'登录/注册即表示你同意',
'expressConsent'.tr, // 登录/注册即表示你同意
style: TextStyle(
fontSize: 12.sp,
color: isDarkMode ? Colors.grey[500] : Colors.grey[600],
@ -393,7 +406,7 @@ class LoginView extends GetView<LoginController> {
GestureDetector(
onTap: () {},
child: Text(
'《用户协议》',
'userAgreement'.tr, // 《用户协议》
style: TextStyle(
fontSize: 12.sp,
color: const Color(0xFF3B82F6),
@ -401,7 +414,7 @@ class LoginView extends GetView<LoginController> {
),
),
Text(
' 和 ',
'and'.tr, // 和
style: TextStyle(
fontSize: 12.sp,
color: isDarkMode ? Colors.grey[500] : Colors.grey[600],
@ -410,7 +423,7 @@ class LoginView extends GetView<LoginController> {
GestureDetector(
onTap: () {},
child: Text(
'《隐私政策》',
'privacyPolicy'.tr, // 《隐私政策》
style: TextStyle(
fontSize: 12.sp,
color: const Color(0xFF3B82F6),
@ -458,12 +471,12 @@ class LoginView extends GetView<LoginController> {
),
SizedBox(height: 8.h),
Obx(() => Text(
'验证码已发送至 ${controller.displayContact}',
style: TextStyle(
fontSize: 14.sp,
color: isDarkMode ? Colors.grey[400] : Colors.grey[600],
),
)),
'验证码已发送至 ${controller.displayContact}',
style: TextStyle(
fontSize: 14.sp,
color: isDarkMode ? Colors.grey[400] : Colors.grey[600],
),
)),
],
);
}
@ -472,17 +485,21 @@ class LoginView extends GetView<LoginController> {
Widget _buildVerificationInputs(BuildContext context, bool isDarkMode) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: List.generate(4, (index) => _buildVerificationInput(index, context, isDarkMode)),
children: List.generate(
4, (index) => _buildVerificationInput(index, context, isDarkMode)),
);
}
// 单个验证码输入框
Widget _buildVerificationInput(int index, BuildContext context, bool isDarkMode) {
Widget _buildVerificationInput(
int index, BuildContext context, bool isDarkMode) {
return Container(
width: 60.w,
height: 60.w,
decoration: BoxDecoration(
color: isDarkMode ? Colors.grey[900]!.withOpacity(0.3) : Colors.grey.withOpacity(0.1),
color: isDarkMode
? Colors.grey[900]!.withOpacity(0.3)
: Colors.grey.withOpacity(0.1),
borderRadius: BorderRadius.circular(12.r),
border: Border.all(
color: isDarkMode ? Colors.grey[700]! : Colors.grey[300]!,
@ -520,28 +537,28 @@ class LoginView extends GetView<LoginController> {
Widget _buildResendButton(bool isDarkMode) {
return Center(
child: Obx(() => TextButton.icon(
onPressed: controller.countdownSeconds > 0
? null
: controller.resendVerificationCode,
icon: Icon(
Icons.refresh,
size: 14.sp,
color: controller.countdownSeconds > 0
? (isDarkMode ? Colors.grey[600] : Colors.grey[500])
: const Color(0xFF3B82F6),
),
label: Text(
controller.countdownSeconds > 0
? '重新发送验证码 (${controller.countdownSeconds}s)'
: '重新发送验证码',
style: TextStyle(
fontSize: 14.sp,
color: controller.countdownSeconds > 0
? (isDarkMode ? Colors.grey[600] : Colors.grey[500])
: const Color(0xFF3B82F6),
),
),
)),
onPressed: controller.countdownSeconds > 0
? null
: controller.resendVerificationCode,
icon: Icon(
Icons.refresh,
size: 14.sp,
color: controller.countdownSeconds > 0
? (isDarkMode ? Colors.grey[600] : Colors.grey[500])
: const Color(0xFF3B82F6),
),
label: Text(
controller.countdownSeconds > 0
? '重新发送验证码 (${controller.countdownSeconds}s)'
: '重新发送验证码',
style: TextStyle(
fontSize: 14.sp,
color: controller.countdownSeconds > 0
? (isDarkMode ? Colors.grey[600] : Colors.grey[500])
: const Color(0xFF3B82F6),
),
),
)),
);
}
@ -550,35 +567,36 @@ class LoginView extends GetView<LoginController> {
return SizedBox(
width: double.infinity,
child: Obx(() => ElevatedButton(
onPressed: controller.isLoading ? null : controller.login,
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF3B82F6),
foregroundColor: Colors.white,
padding: EdgeInsets.symmetric(vertical: 14.h),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16.r),
),
elevation: 0,
disabledBackgroundColor: const Color(0xFF3B82F6).withOpacity(0.7),
disabledForegroundColor: Colors.white,
),
child: controller.isLoading
? SizedBox(
height: 20.h,
width: 20.h,
child: CircularProgressIndicator(
strokeWidth: 2.w,
valueColor: const AlwaysStoppedAnimation<Color>(Colors.white),
),
)
: Text(
'登录',
style: TextStyle(
fontSize: 16.sp,
fontWeight: FontWeight.w600,
),
onPressed: controller.isLoading ? null : controller.login,
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF3B82F6),
foregroundColor: Colors.white,
padding: EdgeInsets.symmetric(vertical: 14.h),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16.r),
),
)),
elevation: 0,
disabledBackgroundColor: const Color(0xFF3B82F6).withOpacity(0.7),
disabledForegroundColor: Colors.white,
),
child: controller.isLoading
? SizedBox(
height: 20.h,
width: 20.h,
child: CircularProgressIndicator(
strokeWidth: 2.w,
valueColor:
const AlwaysStoppedAnimation<Color>(Colors.white),
),
)
: Text(
'登录',
style: TextStyle(
fontSize: 16.sp,
fontWeight: FontWeight.w600,
),
),
)),
);
}
@ -591,10 +609,11 @@ class LoginView extends GetView<LoginController> {
'使用密码登录',
style: TextStyle(
fontSize: 14.sp,
color: isDarkMode ? const Color(0xFF3B82F6) : const Color(0xFF3B82F6),
color:
isDarkMode ? const Color(0xFF3B82F6) : const Color(0xFF3B82F6),
),
),
),
);
}
}
}

118
lib/modules/pairing/controllers/pairing_controller.dart

@ -7,127 +7,124 @@ import '../../../core/utils/logger.dart';
import 'package:flutter/material.dart';
import 'package:permission_handler/permission_handler.dart';
/// 简化的配对控制器,仅用于显示已连接经典蓝牙设备和处理基本配对流程
class PairingController extends GetxController {
static PairingController get to => Get.find();
// 经典蓝牙服务
late classic.ClassicBluetoothService _bluetoothService;
// 是否开始搜索
final _isSearching = false.obs;
bool get isSearching => _isSearching.value;
// 设备连接状态
final _isConnecting = false.obs;
bool get isConnecting => _isConnecting.value;
// 蓝牙服务是否可用
final _isBluetoothServiceAvailable = true.obs;
bool get isBluetoothServiceAvailable => _isBluetoothServiceAvailable.value;
// 当前连接设备
Rx<classic.BluetoothDevice?> get currentDevice => _bluetoothService.currentDevice;
Rx<classic.BluetoothDevice?> get currentDevice =>
_bluetoothService.currentDevice;
// 连接设备列表
RxList<classic.BluetoothDevice> get connectedDevices => _bluetoothService.connectedDevices;
RxList<classic.BluetoothDevice> get connectedDevices =>
_bluetoothService.connectedDevices;
// 错误信息
RxString get errorMessage => _bluetoothService.errorMessage;
// 连接设备列表(兼容旧代码)
List<classic.BluetoothDevice> get connectedClassicDevices =>
_bluetoothService.currentDevice.value != null
? [_bluetoothService.currentDevice.value!]
List<classic.BluetoothDevice> get connectedClassicDevices =>
_bluetoothService.currentDevice.value != null
? [_bluetoothService.currentDevice.value!]
: [];
// 状态描述(用于UI显示)
final statusDescription = '正在初始化...'.obs;
final statusDescription = 'initializing'.tr.obs; // 正在初始化...
// 加载状态
final isLoading = false.obs;
@override
void onInit() {
super.onInit();
_initialize();
}
/// 完整的初始化流程
Future<void> _initialize() async {
try {
VoiceInteractionService.to.initialize();
VoiceInteractionService.to.startService();
isLoading.value = true;
statusDescription.value = '正在初始化...';
statusDescription.value = 'initializing'.tr; // 正在初始化...
// 1. 获取蓝牙服务
_bluetoothService = Get.find<classic.ClassicBluetoothService>();
// 确保蓝牙服务已初始化
if (!await _bluetoothService.initialize()) {
Logger.error('蓝牙服务初始化失败');
statusDescription.value = '蓝牙服务初始化失败';
statusDescription.value =
'bluetoothInitializationFailure'.tr; // 蓝牙服务初始化失败
_isBluetoothServiceAvailable.value = false;
isLoading.value = false;
return;
}
// 设置连接监听器,确保在页面完全加载后设置,避免过早监听可能导致的问题
// 设置连接监听器,确保在页面完全加载后设置,避免过早监听可能导致的问题
_setupDeviceListeners();
// 2. 检查权限
statusDescription.value = '正在检查权限...';
final hasPermission = await _bluetoothService.checkAndRequestPermissions();
statusDescription.value = 'checkingPermissions'.tr; // 正在检查权限...
final hasPermission =
await _bluetoothService.checkAndRequestPermissions();
if (!hasPermission) {
statusDescription.value = '缺少蓝牙权限';
statusDescription.value = 'lackPermissions'.tr; // 缺少蓝牙权限
isLoading.value = false;
return;
}
// 3. 刷新设备列表
statusDescription.value = '正在搜索设备...';
statusDescription.value = 'searchingDevice'.tr; // 正在搜索设备...
await refreshConnectedDevices();
// 4. 处理结果
if (_bluetoothService.connectedDevices.isNotEmpty) {
statusDescription.value = '找到已连接的设备';
statusDescription.value = 'findConnectedDevice'.tr; // 找到已连接的设备
// 延迟跳转到主页
_autoNavigateToHome();
} else {
// 没有找到设备
statusDescription.value = '未找到已连接的蓝牙设备';
statusDescription.value = 'notFound'.tr; // 未找到已连接的蓝牙设备
}
} catch (e) {
Logger.error('初始化配对控制器失败: $e');
statusDescription.value = '初始化失败';
statusDescription.value = 'initializationFailure'.tr; // 初始化失败
_isBluetoothServiceAvailable.value = false;
} finally {
isLoading.value = false;
}
}
/// 设置设备监听器
void _setupDeviceListeners() {
try {
if (!_isBluetoothServiceAvailable.value) return;
// 监听设备连接事件
_bluetoothService.onDeviceConnected((device) {
Logger.info('检测到蓝牙设备连接: ${device.name}');
// 更新状态并跳转到主页
statusDescription.value = '已连接到设备: ${device.name}';
statusDescription.value =
'${'connectedDevice'.tr} ${device.name}'; // 已连接到设备:
// 立即跳转到主页
_navigateToHome();
});
@ -135,60 +132,59 @@ class PairingController extends GetxController {
Logger.error('设置设备监听器失败: $e');
}
}
/// 立即跳转到主页
void _navigateToHome() {
Get.offAllNamed(Routes.home);
}
/// 刷新设备
Future<void> refreshConnectedDevices() async {
try {
isLoading.value = true;
statusDescription.value = '正在搜索设备...';
statusDescription.value = 'searchingDevice'.tr; // 正在搜索设备...
final hasDevices = await _bluetoothService.refreshConnectedDevices();
if (hasDevices) {
statusDescription.value = '找到已连接的设备';
statusDescription.value = 'findConnectedDevice'.tr; // 找到已连接的设备
// 延迟跳转到主页
_autoNavigateToHome();
} else {
// 没有找到设备
statusDescription.value = '未找到已连接的蓝牙设备';
statusDescription.value = 'notFound'.tr; // 未找到已连接的蓝牙设备
}
} catch (e) {
Logger.error('刷新设备失败: $e');
statusDescription.value = '刷新设备失败';
statusDescription.value = 'failedRefreshDevice'.tr; // 刷新设备失败
} finally {
isLoading.value = false;
}
}
/// 自动跳转到主页
void _autoNavigateToHome() {
if (_bluetoothService.connectedDevices.isEmpty) return;
Logger.info('已找到连接的设备,自动跳转到主页');
Future.delayed(const Duration(milliseconds: 500), () {
Get.offAllNamed(Routes.home);
});
}
/// 打开系统蓝牙设置
void openBluetoothSettings() {
_bluetoothService.openBluetoothSettings();
}
/// 刷新设备(兼容旧代码)
Future<void> refreshDevices() async {
await refreshConnectedDevices();
}
/// 跳过连接
void skipConnecting() {
Get.offAllNamed(Routes.home);
}
}
}

61
lib/modules/pairing/views/pairing_view.dart

@ -11,7 +11,7 @@ class PairingView extends GetView<PairingController> {
@override
Widget build(BuildContext context) {
final bool isDarkMode = Theme.of(context).brightness == Brightness.dark;
return Scaffold(
backgroundColor: isDarkMode ? Colors.black : Colors.white,
body: SafeArea(
@ -35,32 +35,33 @@ class PairingView extends GetView<PairingController> {
physics: BouncingScrollPhysics(),
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: MediaQuery.of(Get.context!).size.height - 100.h, // 减去底部导航和状态栏高度
minHeight:
MediaQuery.of(Get.context!).size.height - 100.h, // 减去底部导航和状态栏高度
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SizedBox(height: 20.h), // 顶部间距
// 标题部分
_buildHeader(isDarkMode),
SizedBox(height: 20.h),
// 设备动画 - 更大尺寸
_buildDeviceAnimation(),
SizedBox(height: 20.h),
// 状态信息
_buildStatusInfo(isDarkMode),
SizedBox(height: 40.h),
// 底部按钮
_buildActionButtons(isDarkMode),
SizedBox(height: 20.h),
],
),
@ -83,7 +84,7 @@ class PairingView extends GetView<PairingController> {
),
SizedBox(height: 8.h),
Text(
'连接世界,超越声音',
'homeSlogan'.tr, // 连接世界,超越声音
style: TextStyle(
fontSize: 16.sp,
color: isDarkMode ? Colors.white60 : Colors.black54,
@ -127,7 +128,7 @@ class PairingView extends GetView<PairingController> {
// 重新开始动画
},
),
// 旋转边框
SizedBox(
width: 100.w,
@ -140,7 +141,7 @@ class PairingView extends GetView<PairingController> {
backgroundColor: Colors.grey.withOpacity(0.2),
),
),
// 设备图标
Container(
width: 120.w,
@ -166,13 +167,13 @@ class PairingView extends GetView<PairingController> {
),
);
}
// 状态信息
Widget _buildStatusInfo(bool isDarkMode) {
return Obx(() {
final status = controller.statusDescription.value;
final isLoading = controller.isLoading.value;
return Column(
children: [
// 状态文本 - 更大更醒目
@ -185,19 +186,19 @@ class PairingView extends GetView<PairingController> {
),
textAlign: TextAlign.center,
),
SizedBox(height: 10.h),
// 子状态提示文本
Text(
'请打开耳机充电盒,将耳机放入您的耳朵',
'turnEarphones'.tr, // 请打开耳机充电盒,将耳机放入您的耳朵
style: TextStyle(
fontSize: 14.sp,
color: isDarkMode ? Colors.white60 : Colors.grey[600],
),
textAlign: TextAlign.center,
),
// 设备列表(有设备时才显示)
if (controller.connectedClassicDevices.isNotEmpty) ...[
SizedBox(height: 24.h),
@ -207,23 +208,23 @@ class PairingView extends GetView<PairingController> {
);
});
}
// 已连接设备列表
Widget _buildConnectedDevicesList(bool isDarkMode) {
return Obx(() {
final devices = controller.connectedClassicDevices;
if (devices.isEmpty) {
return const SizedBox.shrink();
}
return Container(
width: double.infinity,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'当前已连接:',
'currentlyConnected'.tr, // 当前已连接:
style: TextStyle(
fontSize: 16.sp,
fontWeight: FontWeight.w600,
@ -237,7 +238,7 @@ class PairingView extends GetView<PairingController> {
);
});
}
// 设备项
Widget _buildDeviceItem(device, bool isDarkMode) {
return Container(
@ -287,7 +288,7 @@ class PairingView extends GetView<PairingController> {
),
);
}
// 操作按钮
Widget _buildActionButtons(bool isDarkMode) {
return Container(
@ -308,16 +309,16 @@ class PairingView extends GetView<PairingController> {
minimumSize: Size(double.infinity, 56.h),
),
child: Text(
'去系统设置',
'unsystemSetup'.tr, // 去系统设置
style: TextStyle(
fontSize: 16.sp,
fontWeight: FontWeight.w600,
),
),
),
SizedBox(height: 12.h),
// 次要按钮
TextButton(
onPressed: () => controller.skipConnecting(),
@ -327,7 +328,7 @@ class PairingView extends GetView<PairingController> {
minimumSize: Size(double.infinity, 48.h),
),
child: Text(
'稍后再说',
'talkLater'.tr, // 稍后再说
style: TextStyle(
fontSize: 16.sp,
fontWeight: FontWeight.w500,
@ -338,4 +339,4 @@ class PairingView extends GetView<PairingController> {
),
);
}
}
}

98
lib/modules/settings/controllers/settings_controller.dart

@ -1,46 +1,81 @@
import 'package:get/get.dart';
import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
import 'package:get_storage/get_storage.dart';
import '../../../core/themes/theme_service.dart';
import '../../../data/services/speech_factory.dart';
import 'package:get_storage/get_storage.dart';
import '../models/language_model.dart';
import '../../../data/models/user_model.dart';
class SettingsController extends GetxController {
// 语言设置
final List<LanguageModel> languageList = [
LanguageModel(
name: 'followingSystem', // 跟随系统.tr
alias: 'followingSystem', // 跟随系统.tr
value: 'system'),
LanguageModel(name: 'chinese', alias: '中文', value: 'zh_CN'), // 中文.tr
LanguageModel(name: 'english', alias: 'English', value: 'en_US'), // 英语.tr
];
Rx<LanguageModel> language = LanguageModel().obs;
final GetStorage _storage = GetStorage();
// 主题服务
final ThemeService _themeService = ThemeService();
// 是否是暗黑模式
final RxBool isDarkMode = false.obs;
// 翻译语言对
final RxString sourceLanguage = '中文'.obs;
final RxString targetLanguage = '英文'.obs;
final List<String> availableLanguages = ['中文', '英文', '日文', '韩文', '法文', '德文', '西班牙文', '俄文'];
final List<String> availableLanguages = [
'中文',
'英文',
'日文',
'韩文',
'法文',
'德文',
'西班牙文',
'俄文'
];
// 语音服务类型
final Rx<SpeechServiceType> speechServiceType = SpeechServiceType.azure.obs;
final List<Map<String, dynamic>> availableSpeechServices = [
{'type': SpeechServiceType.azure, 'name': 'Azure 语音服务', 'description': '微软云端语音服务,支持高质量语音合成和识别'},
{'type': SpeechServiceType.flutter, 'name': 'Flutter 本地语音服务', 'description': '设备本地语音服务,无需联网即可使用'},
{
'type': SpeechServiceType.azure,
'name': 'azureVoiceService', // Azure 语音服务.tr
'description': 'azureVoiceServiceInfo' // 微软云端语音服务,支持高质量语音合成和识别.tr
},
{
'type': SpeechServiceType.flutter,
'name': 'flutterVoiceService', // Flutter 本地语音服务.tr
'description': 'flutterVoiceServiceInfo' // 设备本地语音服务,无需联网即可使用.tr
},
];
//用户信息
static const String userKey='user_info';
final _storage=GetStorage();
//final _storage=GetStorage();
final ImagePicker _picker=ImagePicker();
final Rx<UserModel> userInfo=UserModel.defaultUser().obs;
@override
void onInit() {
super.onInit();
// 获取系统语言
_getLanguage();
// 从存储中加载上次保存的主题设置
final savedDarkMode = _themeService.isSavedDarkMode();
// 设置当前主题状态
isDarkMode.value = savedDarkMode;
// 获取当前语音服务类型
_loadCurrentSpeechServiceType();
@ -97,7 +132,28 @@ class SettingsController extends GetxController {
// await saveUserInfo();
// }
// }
void _getLanguage() {
String? languageValue = _storage.read('language');
LanguageModel data = languageList.firstWhere(
(element) => element.value == languageValue,
orElse: () => languageList[0],
);
language.value = data;
}
void setLanguage(LanguageModel data) {
if (data.value != 'system') {
List languageList = data.value.split('_');
Get.updateLocale(Locale(languageList[0], languageList[1]));
} else {
Get.updateLocale(Get.deviceLocale!);
}
language.value = data;
_storage.write('language', data.value);
Get.back();
}
// 加载当前语音服务类型
void _loadCurrentSpeechServiceType() {
try {
@ -107,19 +163,19 @@ class SettingsController extends GetxController {
// 如果找不到SpeechFactory实例,使用默认值
}
}
// 切换主题
Future<void> toggleTheme() async {
// 切换暗黑模式状态
isDarkMode.value = !isDarkMode.value;
// 更新全局主题
Get.changeThemeMode(isDarkMode.value ? ThemeMode.dark : ThemeMode.light);
// 保存用户选择的主题设置 - 确保立即写入存储
await _themeService.saveThemeMode(isDarkMode.value);
}
// 设置语音服务类型
void setSpeechServiceType(SpeechServiceType type) {
try {
@ -130,23 +186,23 @@ class SettingsController extends GetxController {
// 处理错误
}
}
void setSourceLanguage(String language) {
sourceLanguage.value = language;
}
void setTargetLanguage(String language) {
targetLanguage.value = language;
}
void swapLanguages() {
final temp = sourceLanguage.value;
sourceLanguage.value = targetLanguage.value;
targetLanguage.value = temp;
}
void signOut() {
// 在此实现退出登录逻辑
Get.offAllNamed('/login');
}
}
}

11
lib/modules/settings/models/language_model.dart

@ -0,0 +1,11 @@
class LanguageModel {
final String name;
final String alias;
final String value;
LanguageModel({
this.name = '',
this.alias = '',
this.value = '',
});
}

398
lib/modules/settings/views/settings_view.dart

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'dart:ui';
import '../../../core/theme/app_colors.dart';
import '../controllers/settings_controller.dart';
import '../../../core/widgets/common_bottom_nav.dart';
import '../../../routes/app_routes.dart';
@ -14,14 +15,14 @@ class SettingsView extends GetView<SettingsController> {
Widget build(BuildContext context) {
// 直接使用 Theme.of(context) 获取当前主题
final isDarkMode = Theme.of(context).brightness == Brightness.dark;
return Scaffold(
backgroundColor: isDarkMode ? Colors.black : Colors.grey[50],
appBar: AppBar(
backgroundColor: isDarkMode ? Colors.black : Colors.white,
elevation: 0.5,
title: Text(
'设置',
'settings'.tr, // 设置
style: TextStyle(
fontSize: 17.sp,
fontWeight: FontWeight.w600,
@ -29,7 +30,6 @@ class SettingsView extends GetView<SettingsController> {
),
),
centerTitle: true,
),
body: ListView(
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 16.h),
@ -52,22 +52,19 @@ class SettingsView extends GetView<SettingsController> {
padding: EdgeInsets.all(16.w),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: isDarkMode
colors: isDarkMode
? [
const Color(0xFF1A1A1A).withOpacity(0.7),
const Color(0xFF1A1A1A).withOpacity(0.7),
const Color(0xFF2A2A2A).withOpacity(0.7)
]
: [
const Color(0xFFE3F2FD),
const Color(0xFFF3E5F5)
],
: [const Color(0xFFE3F2FD), const Color(0xFFF3E5F5)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(16.r),
boxShadow: [
BoxShadow(
color: isDarkMode
color: isDarkMode
? Colors.black.withOpacity(0.2)
: Colors.black.withOpacity(0.05),
blurRadius: 10,
@ -77,8 +74,10 @@ class SettingsView extends GetView<SettingsController> {
),
child: ClipRRect(//裁剪圆角矩形
borderRadius: BorderRadius.circular(16.r),
child: BackdropFilter(// 背景滤镜(实现毛玻璃效果)
filter: isDarkMode ? ImageFilter.blur(sigmaX: 10, sigmaY: 10) : ImageFilter.blur(sigmaX: 0, sigmaY: 0),
child: BackdropFilter(
filter: isDarkMode
? ImageFilter.blur(sigmaX: 10, sigmaY: 10)
: ImageFilter.blur(sigmaX: 0, sigmaY: 0),
child: Row(
children: [
Obx(()=>GestureDetector(
@ -127,8 +126,9 @@ class SettingsView extends GetView<SettingsController> {
SizedBox(height: 4.h),
Row(
children: [
Container(// 会员等级
padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 2.h),
Container(
padding: EdgeInsets.symmetric(
horizontal: 8.w, vertical: 2.h),
decoration: BoxDecoration(
color: Colors.blue[600],
borderRadius: BorderRadius.circular(10.r),
@ -144,10 +144,11 @@ class SettingsView extends GetView<SettingsController> {
SizedBox(width: 8.w),
Flexible(
child: Text(
'已连接设备: DeepVoice Pro',
'${'joinDevice'.tr} DeepVoice Pro', // 已连接设备:
style: TextStyle(
fontSize: 12.sp,
color: isDarkMode ? Colors.white70 : Colors.black54,
color:
isDarkMode ? Colors.white70 : Colors.black54,
),
overflow: TextOverflow.ellipsis,
),
@ -169,7 +170,7 @@ class SettingsView extends GetView<SettingsController> {
width: 32.w,
height: 32.w,
decoration: BoxDecoration(
color: isDarkMode
color: isDarkMode
? Colors.white.withOpacity(0.1)
: Colors.white.withOpacity(0.7),
borderRadius: BorderRadius.circular(16.r),
@ -206,7 +207,7 @@ class SettingsView extends GetView<SettingsController> {
Padding(
padding: EdgeInsets.only(left: 4.w, bottom: 8.h),
child: Text(
'系统设置',
'systemSetting'.tr, // 系统设置
style: TextStyle(
fontSize: 12.sp,
fontWeight: FontWeight.w500,
@ -221,7 +222,7 @@ class SettingsView extends GetView<SettingsController> {
borderRadius: BorderRadius.circular(16.r),
boxShadow: [
BoxShadow(
color: isDarkMode
color: isDarkMode
? Colors.black.withOpacity(0.2)
: Colors.black.withOpacity(0.05),
blurRadius: 10,
@ -232,52 +233,70 @@ class SettingsView extends GetView<SettingsController> {
child: ClipRRect(
borderRadius: BorderRadius.circular(16.r),
child: BackdropFilter(
filter: isDarkMode ? ImageFilter.blur(sigmaX: 5, sigmaY: 5) : ImageFilter.blur(sigmaX: 0, sigmaY: 0),
filter: isDarkMode
? ImageFilter.blur(sigmaX: 5, sigmaY: 5)
: ImageFilter.blur(sigmaX: 0, sigmaY: 0),
child: Column(
children: [
// 通知设置
_buildSimpleNavigationSetting(
title: '通知设置',
subtitle: '管理应用推送和提醒',
icon: Icons.notifications_none,
iconBgColor: isDarkMode ? Colors.indigo[900]!.withOpacity(0.3) : Colors.indigo[100]!,
iconColor: isDarkMode ? Colors.indigo[300]! : Colors.indigo[600]!,
titleColor: isDarkMode ? Colors.white : null,
subtitleColor: isDarkMode ? Colors.white70 : null,
onTap: () {
// 打开通知设置页面
},
isDarkMode: isDarkMode,
// 语言设置
Obx(
() => _buildSimpleNavigationSetting(
title: 'languageSetting'.tr, // 语言设置
subtitle: controller.language.value.name.tr,
icon: Icons.language,
iconBgColor: isDarkMode
? Colors.indigo[900]!.withOpacity(0.3)
: Colors.indigo[100]!,
iconColor: isDarkMode
? Colors.indigo[300]!
: Colors.indigo[600]!,
titleColor: isDarkMode ? Colors.white : null,
subtitleColor: isDarkMode ? Colors.white70 : null,
onTap: () {
_showLanguageSelectionDialog(isDarkMode);
},
isDarkMode: isDarkMode,
),
),
Divider(height: 1, color: isDarkMode ? Colors.white.withOpacity(0.1) : Colors.grey[200]),
Divider(
height: 1,
color: isDarkMode
? Colors.white.withOpacity(0.1)
: Colors.grey[200]),
// 主题设置
_buildToggleSetting(
title: '暗黑模式',
subtitle: '切换应用的明暗主题',
title: 'darkMode'.tr, // 暗黑模式
subtitle: 'darkModeInfo'.tr, // 切换应用的明暗主题
icon: isDarkMode ? Icons.dark_mode : Icons.light_mode,
iconBgColor: isDarkMode
? Colors.indigo[900]!.withOpacity(0.3)
iconBgColor: isDarkMode
? Colors.indigo[900]!.withOpacity(0.3)
: Colors.amber[100]!,
iconColor: isDarkMode
? Colors.indigo[300]!
: Colors.amber[600]!,
iconColor:
isDarkMode ? Colors.indigo[300]! : Colors.amber[600]!,
value: isDarkMode,
onChanged: (value) {
controller.toggleTheme();
},
isDarkMode: isDarkMode,
),
Divider(height: 1, color: isDarkMode ? Colors.white.withOpacity(0.1) : Colors.grey[200]),
Divider(
height: 1,
color: isDarkMode
? Colors.white.withOpacity(0.1)
: Colors.grey[200]),
// 备份与恢复
_buildNavigationSetting(
title: '备份与恢复',
subtitle: '备份设置和AI互动历史',
title: 'backups'.tr, // 备份与恢复
subtitle: 'backupsInfo'.tr, // 备份设置和AI互动历史
icon: Icons.cloud_upload_outlined,
iconBgColor: isDarkMode ? Colors.cyan[900]!.withOpacity(0.3) : Colors.cyan[100]!,
iconColor: isDarkMode ? Colors.cyan[300]! : Colors.cyan[600]!,
value: '上次备份: 今天',
iconBgColor: isDarkMode
? Colors.cyan[900]!.withOpacity(0.3)
: Colors.cyan[100]!,
iconColor:
isDarkMode ? Colors.cyan[300]! : Colors.cyan[600]!,
value: '${'lastBackup'.tr} 今天', // 上次备份:
valueStyle: TextStyle(
fontSize: 12.sp,
color: isDarkMode ? Colors.white70 : Colors.grey[500],
@ -287,20 +306,31 @@ class SettingsView extends GetView<SettingsController> {
},
isDarkMode: isDarkMode,
),
Divider(height: 1, color: isDarkMode ? Colors.white.withOpacity(0.1) : Colors.grey[200]),
Divider(
height: 1,
color: isDarkMode
? Colors.white.withOpacity(0.1)
: Colors.grey[200]),
// 语音服务选择
_buildSpeechServiceSetting(isDarkMode),
Divider(height: 1, color: isDarkMode ? Colors.white.withOpacity(0.1) : Colors.grey[200]),
Divider(
height: 1,
color: isDarkMode
? Colors.white.withOpacity(0.1)
: Colors.grey[200]),
// 隐私设置
_buildSimpleNavigationSetting(
title: '隐私设置',
subtitle: '管理数据收集和权限',
title: 'privacySettings'.tr, // 隐私设置
subtitle: 'privacySettingsInfo'.tr, // 管理数据收集和权限
icon: Icons.shield_outlined,
iconBgColor: isDarkMode ? Colors.purple[900]!.withOpacity(0.3) : Colors.purple[100]!,
iconColor: isDarkMode ? Colors.purple[300]! : Colors.purple[600]!,
iconBgColor: isDarkMode
? Colors.purple[900]!.withOpacity(0.3)
: Colors.purple[100]!,
iconColor:
isDarkMode ? Colors.purple[300]! : Colors.purple[600]!,
titleColor: isDarkMode ? Colors.white : null,
subtitleColor: isDarkMode ? Colors.white70 : null,
onTap: () {
@ -308,39 +338,50 @@ class SettingsView extends GetView<SettingsController> {
},
isDarkMode: isDarkMode,
),
Divider(height: 1, color: isDarkMode ? Colors.white.withOpacity(0.1) : Colors.grey[200]),
Divider(
height: 1,
color: isDarkMode
? Colors.white.withOpacity(0.1)
: Colors.grey[200]),
// 退出登录
_buildSimpleNavigationSetting(
title: '退出登录',
title: 'logout'.tr, // 退出登录
icon: Icons.logout,
iconBgColor: isDarkMode ? Colors.red[900]!.withOpacity(0.3) : Colors.red[50]!,
iconBgColor: isDarkMode
? Colors.red[900]!.withOpacity(0.3)
: Colors.red[50]!,
iconColor: isDarkMode ? Colors.red[300]! : Colors.red[600]!,
titleColor: isDarkMode ? Colors.red[300]! : Colors.red[600]!,
titleColor:
isDarkMode ? Colors.red[300]! : Colors.red[600]!,
onTap: () {
// 退出登录确认对话框
Get.dialog(
AlertDialog(
backgroundColor: isDarkMode ? Colors.grey[900] : Colors.white,
backgroundColor:
isDarkMode ? Colors.grey[900] : Colors.white,
title: Text(
'退出登录',
'logout'.tr, // 退出登录
style: TextStyle(
color: isDarkMode ? Colors.white : Colors.black87,
),
),
content: Text(
'确定要退出当前账号吗?',
'logoutTips'.tr, // 确定要退出当前账号吗?
style: TextStyle(
color: isDarkMode ? Colors.white70 : Colors.black54,
color:
isDarkMode ? Colors.white70 : Colors.black54,
),
),
actions: [
TextButton(
onPressed: () => Get.back(),
child: Text(
'取消',
'cancel'.tr, // 取消
style: TextStyle(
color: isDarkMode ? Colors.white70 : Colors.black54,
color: isDarkMode
? Colors.white70
: Colors.black54,
),
),
),
@ -350,9 +391,11 @@ class SettingsView extends GetView<SettingsController> {
controller.signOut();
},
child: Text(
'确定',
'confirm'.tr, // 确定
style: TextStyle(
color: isDarkMode ? Colors.red[300] : Colors.red[600],
color: isDarkMode
? Colors.red[300]
: Colors.red[600],
),
),
),
@ -379,7 +422,7 @@ class SettingsView extends GetView<SettingsController> {
Padding(
padding: EdgeInsets.only(left: 4.w, bottom: 8.h),
child: Text(
'关于应用',
'aboutApplication'.tr, // 关于应用
style: TextStyle(
fontSize: 12.sp,
fontWeight: FontWeight.w500,
@ -395,7 +438,7 @@ class SettingsView extends GetView<SettingsController> {
borderRadius: BorderRadius.circular(16.r),
boxShadow: [
BoxShadow(
color: isDarkMode
color: isDarkMode
? Colors.black.withOpacity(0.2)
: Colors.black.withOpacity(0.05),
blurRadius: 10,
@ -406,7 +449,9 @@ class SettingsView extends GetView<SettingsController> {
child: ClipRRect(
borderRadius: BorderRadius.circular(16.r),
child: BackdropFilter(
filter: isDarkMode ? ImageFilter.blur(sigmaX: 5, sigmaY: 5) : ImageFilter.blur(sigmaX: 0, sigmaY: 0),
filter: isDarkMode
? ImageFilter.blur(sigmaX: 5, sigmaY: 5)
: ImageFilter.blur(sigmaX: 0, sigmaY: 0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
@ -418,8 +463,10 @@ class SettingsView extends GetView<SettingsController> {
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
const Color(0xFF2196F3).withOpacity(isDarkMode ? 0.7 : 1.0),
const Color(0xFF9C27B0).withOpacity(isDarkMode ? 0.7 : 1.0)
const Color(0xFF2196F3)
.withOpacity(isDarkMode ? 0.7 : 1.0),
const Color(0xFF9C27B0)
.withOpacity(isDarkMode ? 0.7 : 1.0)
],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
@ -446,10 +493,11 @@ class SettingsView extends GetView<SettingsController> {
),
SizedBox(height: 2.h),
Text(
'版本 2.4.1 (Build 87)',
'${'versions'.tr} 2.4.1 (Build 87)', // 版本
style: TextStyle(
fontSize: 12.sp,
color: isDarkMode ? Colors.white70 : Colors.black54,
color:
isDarkMode ? Colors.white70 : Colors.black54,
),
),
],
@ -457,13 +505,16 @@ class SettingsView extends GetView<SettingsController> {
],
),
Container(
padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 6.h),
padding:
EdgeInsets.symmetric(horizontal: 12.w, vertical: 6.h),
decoration: BoxDecoration(
color: isDarkMode ? Colors.blue[900]!.withOpacity(0.3) : Colors.blue[100],
color: isDarkMode
? Colors.blue[900]!.withOpacity(0.3)
: Colors.blue[100],
borderRadius: BorderRadius.circular(16.r),
),
child: Text(
'检查更新',
'checkUpdates'.tr, // 检查更新
style: TextStyle(
fontSize: 12.sp,
color: isDarkMode ? Colors.blue[300] : Colors.blue[600],
@ -488,7 +539,7 @@ class SettingsView extends GetView<SettingsController> {
),
SizedBox(height: 4.h),
Text(
'连接世界,超越声音',
'homeSlogan'.tr, // 连接世界,超越声音
style: TextStyle(
fontSize: 12.sp,
color: isDarkMode ? Colors.grey[400] : Colors.grey[500],
@ -547,8 +598,7 @@ class SettingsView extends GetView<SettingsController> {
),
overflow: TextOverflow.ellipsis,
),
if (subtitle != null)
SizedBox(height: 2.h),
if (subtitle != null) SizedBox(height: 2.h),
if (subtitle != null)
Text(
subtitle,
@ -623,14 +673,14 @@ class SettingsView extends GetView<SettingsController> {
),
overflow: TextOverflow.ellipsis,
),
if (subtitle != null)
SizedBox(height: 2.h),
if (subtitle != null) SizedBox(height: 2.h),
if (subtitle != null)
Text(
subtitle,
style: TextStyle(
fontSize: 12.sp,
color: isDarkMode ? Colors.white70 : Colors.black54,
color:
isDarkMode ? Colors.white70 : Colors.black54,
),
overflow: TextOverflow.ellipsis,
),
@ -646,10 +696,11 @@ class SettingsView extends GetView<SettingsController> {
Flexible(
child: Text(
value,
style: valueStyle ?? TextStyle(
fontSize: 14.sp,
color: isDarkMode ? Colors.white : Colors.black87,
),
style: valueStyle ??
TextStyle(
fontSize: 14.sp,
color: isDarkMode ? Colors.white : Colors.black87,
),
overflow: TextOverflow.ellipsis,
),
),
@ -658,7 +709,9 @@ class SettingsView extends GetView<SettingsController> {
width: 32.w,
height: 32.w,
decoration: BoxDecoration(
color: isDarkMode ? Colors.white.withOpacity(0.05) : Colors.grey[100],
color: isDarkMode
? Colors.white.withOpacity(0.05)
: Colors.grey[100],
borderRadius: BorderRadius.circular(16.r),
),
child: Icon(
@ -720,18 +773,21 @@ class SettingsView extends GetView<SettingsController> {
style: TextStyle(
fontSize: 16.sp,
fontWeight: FontWeight.w500,
color: titleColor ?? (isDarkMode ? Colors.white : Colors.black87),
color: titleColor ??
(isDarkMode ? Colors.white : Colors.black87),
),
overflow: TextOverflow.ellipsis,
),
if (subtitle != null)
SizedBox(height: 2.h),
if (subtitle != null) SizedBox(height: 2.h),
if (subtitle != null)
Text(
subtitle,
style: TextStyle(
fontSize: 12.sp,
color: subtitleColor ?? (isDarkMode ? Colors.white70 : Colors.black54),
color: subtitleColor ??
(isDarkMode
? Colors.white70
: Colors.black54),
),
overflow: TextOverflow.ellipsis,
),
@ -745,7 +801,9 @@ class SettingsView extends GetView<SettingsController> {
width: 32.w,
height: 32.w,
decoration: BoxDecoration(
color: isDarkMode ? Colors.white.withOpacity(0.05) : Colors.grey[100],
color: isDarkMode
? Colors.white.withOpacity(0.05)
: Colors.grey[100],
borderRadius: BorderRadius.circular(16.r),
),
child: Icon(
@ -778,7 +836,9 @@ class SettingsView extends GetView<SettingsController> {
width: 32.w,
height: 32.w,
decoration: BoxDecoration(
color: isDarkMode ? Colors.teal[900]!.withOpacity(0.3) : Colors.teal[100]!,
color: isDarkMode
? Colors.teal[900]!.withOpacity(0.3)
: Colors.teal[100]!,
borderRadius: BorderRadius.circular(16.r),
),
child: Icon(
@ -793,7 +853,7 @@ class SettingsView extends GetView<SettingsController> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'语音服务',
'voiceService'.tr, // 语音服务
style: TextStyle(
fontSize: 16.sp,
fontWeight: FontWeight.w500,
@ -802,7 +862,7 @@ class SettingsView extends GetView<SettingsController> {
),
SizedBox(height: 2.h),
Text(
'选择语音识别和合成服务',
'voiceServiceInfo'.tr, // 选择语音识别和合成服务
style: TextStyle(
fontSize: 12.sp,
color: isDarkMode ? Colors.white70 : Colors.black54,
@ -821,11 +881,12 @@ class SettingsView extends GetView<SettingsController> {
Obx(() {
final serviceType = controller.speechServiceType.value;
final serviceName = controller.availableSpeechServices
.firstWhere((service) => service['type'] == serviceType)['name'];
.firstWhere(
(service) => service['type'] == serviceType)['name'];
return Flexible(
child: Text(
serviceName,
'$serviceName'.tr,
style: TextStyle(
fontSize: 12.sp,
color: isDarkMode ? Colors.white70 : Colors.grey[600],
@ -839,7 +900,9 @@ class SettingsView extends GetView<SettingsController> {
width: 32.w,
height: 32.w,
decoration: BoxDecoration(
color: isDarkMode ? Colors.white.withOpacity(0.05) : Colors.grey[100],
color: isDarkMode
? Colors.white.withOpacity(0.05)
: Colors.grey[100],
borderRadius: BorderRadius.circular(16.r),
),
child: Icon(
@ -855,7 +918,7 @@ class SettingsView extends GetView<SettingsController> {
),
);
}
// 显示语音服务选择对话框
void _showSpeechServiceSelectionDialog(bool isDarkMode) {
Get.dialog(
@ -871,7 +934,7 @@ class SettingsView extends GetView<SettingsController> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'选择语音服务',
'selectVoiceService'.tr, // 选择语音服务
style: TextStyle(
fontSize: 18.sp,
fontWeight: FontWeight.w600,
@ -881,8 +944,9 @@ class SettingsView extends GetView<SettingsController> {
SizedBox(height: 16.h),
...controller.availableSpeechServices.map((service) {
return Obx(() {
final isSelected = controller.speechServiceType.value == service['type'];
final isSelected =
controller.speechServiceType.value == service['type'];
return InkWell(
onTap: () {
controller.setSpeechServiceType(service['type']);
@ -893,12 +957,18 @@ class SettingsView extends GetView<SettingsController> {
padding: EdgeInsets.all(12.w),
decoration: BoxDecoration(
color: isSelected
? (isDarkMode ? Colors.teal[900]!.withOpacity(0.3) : Colors.teal[50]!)
: (isDarkMode ? Colors.white.withOpacity(0.05) : Colors.grey[100]!),
? (isDarkMode
? Colors.teal[900]!.withOpacity(0.3)
: Colors.teal[50]!)
: (isDarkMode
? Colors.white.withOpacity(0.05)
: Colors.grey[100]!),
borderRadius: BorderRadius.circular(12.r),
border: Border.all(
color: isSelected
? (isDarkMode ? Colors.teal[300]! : Colors.teal[600]!)
? (isDarkMode
? Colors.teal[300]!
: Colors.teal[600]!)
: Colors.transparent,
width: 1,
),
@ -910,8 +980,12 @@ class SettingsView extends GetView<SettingsController> {
height: 24.w,
decoration: BoxDecoration(
color: isSelected
? (isDarkMode ? Colors.teal[300] : Colors.teal[600])
: (isDarkMode ? Colors.white.withOpacity(0.1) : Colors.grey[300]),
? (isDarkMode
? Colors.teal[300]
: Colors.teal[600])
: (isDarkMode
? Colors.white.withOpacity(0.1)
: Colors.grey[300]),
shape: BoxShape.circle,
),
child: isSelected
@ -928,20 +1002,24 @@ class SettingsView extends GetView<SettingsController> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
service['name'],
'${service['name']}'.tr,
style: TextStyle(
fontSize: 16.sp,
fontWeight: FontWeight.w500,
color: isDarkMode ? Colors.white : Colors.black87,
color: isDarkMode
? Colors.white
: Colors.black87,
),
overflow: TextOverflow.ellipsis,
),
SizedBox(height: 4.h),
Text(
service['description'],
'${service['description']}'.tr,
style: TextStyle(
fontSize: 12.sp,
color: isDarkMode ? Colors.white70 : Colors.black54,
color: isDarkMode
? Colors.white70
: Colors.black54,
),
overflow: TextOverflow.ellipsis,
maxLines: 2,
@ -962,7 +1040,7 @@ class SettingsView extends GetView<SettingsController> {
TextButton(
onPressed: () => Get.back(),
child: Text(
'取消',
'cancel'.tr, // 取消
style: TextStyle(
color: isDarkMode ? Colors.white70 : Colors.black54,
),
@ -976,4 +1054,86 @@ class SettingsView extends GetView<SettingsController> {
),
);
}
}
// 显示语言选择对话框
void _showLanguageSelectionDialog(bool isDarkMode) {
Get.dialog(
Dialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(24.r),
),
insetPadding: EdgeInsets.symmetric(horizontal: 20.w),
backgroundColor: isDarkMode ? AppColors.cardBackground : Colors.white,
child: Container(
width: 0.9.sw,
padding: EdgeInsets.only(top: 32.h, bottom: 16.h),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: EdgeInsets.symmetric(horizontal: 24.w),
child: Text(
'selectLanguage'.tr, // 选择语言
style: TextStyle(
fontSize: 20.sp,
fontWeight: FontWeight.bold,
color: isDarkMode ? Colors.white : Colors.black,
),
),
),
SizedBox(height: 16.h),
Container(
constraints: BoxConstraints(maxHeight: 0.5.sh),
child: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
children: controller.languageList.map((language) {
return ListTile(
title: Text(
language.name.tr,
style: TextStyle(
color: isDarkMode ? Colors.white : Colors.black,
),
),
trailing:
controller.language.value.value == language.value
? Icon(
Icons.check_circle,
color: isDarkMode
? AppColors.primary
: Colors.blue,
)
: const SizedBox.shrink(),
onTap: () {
controller.setLanguage(language);
},
);
}).toList(),
),
),
),
SizedBox(height: 16.h),
Align(
alignment: Alignment.centerRight,
child: Padding(
padding: EdgeInsets.only(right: 24.w),
child: TextButton(
onPressed: () => Get.back(),
child: Text(
'cancel'.tr, // 取消
style: TextStyle(
color: isDarkMode ? AppColors.primary : Colors.blue,
fontSize: 16.sp,
),
),
),
),
),
],
),
),
),
);
}
}

30
lib/modules/splash/controllers/splash_controller.dart

@ -8,31 +8,31 @@ class SplashController extends GetxController {
// 进度条动画控制
final _loadingProgress = 0.0.obs;
double get loadingProgress => _loadingProgress.value;
// 版本号
final String version = 'v1.0.0';
// 资源加载状态
final _isResourceLoaded = false.obs;
bool get isResourceLoaded => _isResourceLoaded.value;
// 主题服务
final ThemeService _themeService = ThemeService();
@override
void onInit() {
super.onInit();
// 确保页面先渲染,再执行其他操作
WidgetsBinding.instance.addPostFrameCallback((_) {
// 启动进度条动画
_startLoadingAnimation();
// 异步加载应用资源
_loadAppResources();
});
}
// 进度条动画
void _startLoadingAnimation() {
// 模拟0.5秒的加载进度
@ -48,21 +48,21 @@ class SplashController extends GetxController {
}
});
}
// 异步加载应用资源
Future<void> _loadAppResources() async {
try {
// 在这里可以添加应用初始化需要的资源加载
// 例如:预加载图片、初始化数据库、加载配置等
// 模拟耗时操作
await Future.delayed(const Duration(milliseconds: 100));
// TODO: 在此处添加实际的资源加载代码
// 标记资源加载完成
_isResourceLoaded.value = true;
// 如果进度条已经走完,则跳转到登录页面
if (_loadingProgress.value >= 1.0) {
_navigateToLogin();
@ -71,12 +71,12 @@ class SplashController extends GetxController {
// 处理资源加载错误
// 即使加载失败,也应该允许用户继续使用应用
_isResourceLoaded.value = true;
// 记录错误日志
print('资源加载错误: $e');
}
}
// 跳转到登录页面
void _navigateToLogin() {
// 添加一个小延迟,确保UI完全渲染
@ -84,4 +84,4 @@ class SplashController extends GetxController {
Get.offAllNamed(Routes.login);
});
}
}
}

20
lib/modules/splash/views/splash_view.dart

@ -9,7 +9,7 @@ class SplashView extends GetView<SplashController> {
@override
Widget build(BuildContext context) {
final isDarkMode = Theme.of(context).brightness == Brightness.dark;
return Scaffold(
backgroundColor: isDarkMode ? Colors.black : const Color(0xFFF5F5F7),
body: Stack(
@ -21,24 +21,24 @@ class SplashView extends GetView<SplashController> {
children: [
// Logo动画
_buildAnimatedLogo(isDarkMode),
// 应用名称
_buildAppTitle(isDarkMode),
// 标语
_buildSlogan(isDarkMode),
// 加载进度条
SizedBox(height: 40.h),
_buildLoadingBar(isDarkMode),
// 额外信息
SizedBox(height: 16.h),
_buildExtraInfo(isDarkMode),
],
),
),
// 版本号
_buildVersionInfo(isDarkMode),
],
@ -55,7 +55,7 @@ class SplashView extends GetView<SplashController> {
builder: (context, value, child) {
// 确保opacity值不超过1.0
final opacity = value <= 1.0 ? value : 1.0;
return Transform.scale(
scale: value,
child: AnimatedOpacity(
@ -130,7 +130,7 @@ class SplashView extends GetView<SplashController> {
child: Padding(
padding: EdgeInsets.only(top: 8.h),
child: Text(
'连接世界,岂止声音',
'slogan'.tr, // 连接世界,岂止声音
style: TextStyle(
fontSize: 14.sp,
color: isDarkMode ? Colors.grey[400] : Colors.grey[600],
@ -189,7 +189,7 @@ class SplashView extends GetView<SplashController> {
return Opacity(
opacity: value,
child: Text(
'AI语音专家 · 随时随地',
'sloganInfo'.tr, // AI语音专家 · 随时随地
style: TextStyle(
fontSize: 12.sp,
color: isDarkMode ? Colors.grey[500] : Colors.grey[400],
@ -217,4 +217,4 @@ class SplashView extends GetView<SplashController> {
),
);
}
}
}

164
lib/modules/translation/views/translation_view.dart

@ -13,7 +13,7 @@ class TranslationView extends GetView<TranslationController> {
@override
Widget build(BuildContext context) {
final isDarkMode = Theme.of(context).brightness == Brightness.dark;
return Scaffold(
backgroundColor: isDarkMode ? AppColors.darkBackground : Colors.grey[50],
appBar: _buildAppBar(isDarkMode),
@ -47,22 +47,25 @@ class TranslationView extends GetView<TranslationController> {
child: Container(
padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h),
decoration: BoxDecoration(
color: isDarkMode ? Colors.white.withOpacity(0.1) : Colors.grey[100],
color: isDarkMode
? Colors.white.withOpacity(0.1)
: Colors.grey[100],
borderRadius: BorderRadius.circular(16.r),
),
child: Obx(() => Text(
controller.sourceLanguage.value.split(' ')[0], // Just show the Chinese part
style: TextStyle(
fontSize: 16.sp,
fontWeight: FontWeight.w600,
color: isDarkMode ? Colors.white : Colors.black,
),
overflow: TextOverflow.ellipsis,
)),
controller.sourceLanguage.value
.split(' ')[0], // Just show the Chinese part
style: TextStyle(
fontSize: 16.sp,
fontWeight: FontWeight.w600,
color: isDarkMode ? Colors.white : Colors.black,
),
overflow: TextOverflow.ellipsis,
)),
),
),
),
// 语言互换按钮
GestureDetector(
onTap: controller.swapLanguages,
@ -70,7 +73,9 @@ class TranslationView extends GetView<TranslationController> {
margin: EdgeInsets.symmetric(horizontal: 8.w),
padding: EdgeInsets.all(4.r),
decoration: BoxDecoration(
color: isDarkMode ? AppColors.primary.withOpacity(0.2) : Colors.blue[50],
color: isDarkMode
? AppColors.primary.withOpacity(0.2)
: Colors.blue[50],
shape: BoxShape.circle,
),
child: Icon(
@ -80,7 +85,7 @@ class TranslationView extends GetView<TranslationController> {
),
),
),
// 目标语言选择
Flexible(
child: GestureDetector(
@ -88,18 +93,21 @@ class TranslationView extends GetView<TranslationController> {
child: Container(
padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h),
decoration: BoxDecoration(
color: isDarkMode ? Colors.white.withOpacity(0.1) : Colors.grey[100],
color: isDarkMode
? Colors.white.withOpacity(0.1)
: Colors.grey[100],
borderRadius: BorderRadius.circular(16.r),
),
child: Obx(() => Text(
controller.targetLanguage.value.split(' ')[0], // Just show the English part
style: TextStyle(
fontSize: 16.sp,
fontWeight: FontWeight.w600,
color: isDarkMode ? Colors.white : Colors.black,
),
overflow: TextOverflow.ellipsis,
)),
controller.targetLanguage.value
.split(' ')[0], // Just show the English part
style: TextStyle(
fontSize: 16.sp,
fontWeight: FontWeight.w600,
color: isDarkMode ? Colors.white : Colors.black,
),
overflow: TextOverflow.ellipsis,
)),
),
),
),
@ -109,15 +117,17 @@ class TranslationView extends GetView<TranslationController> {
actions: [
// TTS toggle button
Obx(() => IconButton(
icon: Icon(
controller.isTtsEnabled.value ? Icons.volume_up : Icons.volume_off,
color: isDarkMode ? Colors.white : Colors.black,
size: 24.sp,
),
onPressed: controller.toggleTts,
tooltip: '自动播放翻译结果',
)),
icon: Icon(
controller.isTtsEnabled.value
? Icons.volume_up
: Icons.volume_off,
color: isDarkMode ? Colors.white : Colors.black,
size: 24.sp,
),
onPressed: controller.toggleTts,
tooltip: '自动播放翻译结果',
)),
// 历史记录按钮
IconButton(
icon: Icon(
@ -129,10 +139,10 @@ class TranslationView extends GetView<TranslationController> {
// 停止语音识别和TTS,释放资源
await controller.stopAll();
await Get.to(
() => const TranslationHistoryView(),
() => const TranslationHistoryView(),
binding: TranslationHistoryBinding(),
);
// 返回时不自动开始翻译,只需重新设置滚动监听器
controller.setupScrollListener();
},
@ -156,22 +166,19 @@ class TranslationView extends GetView<TranslationController> {
Widget _buildTranslationContent(bool isDarkMode) {
return Obx(() {
final itemCount = controller.translationHistory.length;
// 使用 currentItem 来判断是否显示当前项
final hasCurrentItem = controller.currentItem.value != null &&
controller.currentItem.value!.sourceText.isNotEmpty; // 只有当有实际内容时才显示当前项
final hasCurrentItem = controller.currentItem.value != null &&
controller
.currentItem.value!.sourceText.isNotEmpty; // 只有当有实际内容时才显示当前项
// 计算底部内边距,确保内容不会被底部框遮住
final bottomPadding = controller.bottomBarHeight + 20.h; // 使用控制栏高度加上额外间距
return ListView.builder(
controller: controller.scrollController,
padding: EdgeInsets.only(
left: 16.w,
right: 16.w,
top: 16.h,
bottom: bottomPadding
),
left: 16.w, right: 16.w, top: 16.h, bottom: bottomPadding),
itemCount: itemCount + (hasCurrentItem ? 1 : 0),
itemBuilder: (context, index) {
// Current item (being translated)
@ -184,7 +191,7 @@ class TranslationView extends GetView<TranslationController> {
isDarkMode: isDarkMode,
);
}
// History items
final historyItem = controller.translationHistory[index];
return _buildTranslationItemWidget(
@ -201,15 +208,13 @@ class TranslationView extends GetView<TranslationController> {
}
Widget _buildTranslationItemWidget(
String sourceText,
String translatedText,
bool isCurrent,
{
TranslationItem? item,
bool isIntermediate = false,
required bool isDarkMode,
}
) {
String sourceText,
String translatedText,
bool isCurrent, {
TranslationItem? item,
bool isIntermediate = false,
required bool isDarkMode,
}) {
// 使用AnimatedContainer实现平滑过渡,但恢复原来的视觉样式
return AnimatedContainer(
duration: const Duration(milliseconds: 300),
@ -231,17 +236,20 @@ class TranslationView extends GetView<TranslationController> {
sourceText.isEmpty && isCurrent ? '等待语音输入...' : sourceText,
style: TextStyle(
fontSize: 16.sp,
color: sourceText.isEmpty && isCurrent
? (isDarkMode ? Colors.grey[400] : Colors.grey)
color: sourceText.isEmpty && isCurrent
? (isDarkMode ? Colors.grey[400] : Colors.grey)
: (isDarkMode ? Colors.white : Colors.black),
fontWeight: FontWeight.normal,
),
),
),
// Divider
Divider(height: 1, thickness: 1, color: isDarkMode ? Colors.grey[800] : Colors.grey[200]),
Divider(
height: 1,
thickness: 1,
color: isDarkMode ? Colors.grey[800] : Colors.grey[200]),
// Translated text
AnimatedContainer(
duration: const Duration(milliseconds: 300),
@ -252,11 +260,13 @@ class TranslationView extends GetView<TranslationController> {
children: [
Expanded(
child: Text(
translatedText.isEmpty && isCurrent ? '翻译结果将显示在这里...' : translatedText,
translatedText.isEmpty && isCurrent
? '翻译结果将显示在这里...'
: translatedText,
style: TextStyle(
fontSize: 16.sp,
color: translatedText.isEmpty && isCurrent
? (isDarkMode ? Colors.grey[400] : Colors.grey)
color: translatedText.isEmpty && isCurrent
? (isDarkMode ? Colors.grey[400] : Colors.grey)
: (isDarkMode ? Colors.white : Colors.black),
fontStyle: FontStyle.normal,
),
@ -288,16 +298,17 @@ class TranslationView extends GetView<TranslationController> {
Widget _buildControlBar(bool isDarkMode) {
// 使用GlobalKey来获取控制栏的高度
final controlBarKey = GlobalKey();
// 在构建完成后获取控制栏的高度并设置到控制器中
WidgetsBinding.instance.addPostFrameCallback((_) {
if (controlBarKey.currentContext != null) {
final RenderBox renderBox = controlBarKey.currentContext!.findRenderObject() as RenderBox;
final RenderBox renderBox =
controlBarKey.currentContext!.findRenderObject() as RenderBox;
final height = renderBox.size.height;
controller.setBottomBarHeight(height);
}
});
return Container(
key: controlBarKey,
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 16.h),
@ -314,8 +325,6 @@ class TranslationView extends GetView<TranslationController> {
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// 主要控制按钮 - 开始/暂停语音识别
Obx(() {
final isRecognizing = controller.isRecognizing.value;
@ -331,8 +340,8 @@ class TranslationView extends GetView<TranslationController> {
width: 60.w,
height: 60.w,
decoration: BoxDecoration(
color: isRecognizing
? (isDarkMode ? Colors.red[700] : Colors.red)
color: isRecognizing
? (isDarkMode ? Colors.red[700] : Colors.red)
: (isDarkMode ? AppColors.primary : Colors.blue),
shape: BoxShape.circle,
),
@ -368,7 +377,9 @@ class TranslationView extends GetView<TranslationController> {
Padding(
padding: EdgeInsets.symmetric(horizontal: 24.w),
child: Text(
isSource ? '选择源语言' : '选择目标语言',
isSource
? 'selectSourceLanguage'.tr // 选择源语言
: 'selectTargetLanguage'.tr, // 选择目标语言
style: TextStyle(
fontSize: 20.sp,
fontWeight: FontWeight.bold,
@ -382,7 +393,8 @@ class TranslationView extends GetView<TranslationController> {
child: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
children: controller.supportedLanguages.keys.map((language) {
children:
controller.supportedLanguages.keys.map((language) {
return ListTile(
title: Text(
language,
@ -391,13 +403,15 @@ class TranslationView extends GetView<TranslationController> {
),
),
trailing: Obx(() {
final currentLanguage = isSource
? controller.sourceLanguage.value
final currentLanguage = isSource
? controller.sourceLanguage.value
: controller.targetLanguage.value;
return currentLanguage == language
? Icon(
Icons.check_circle,
color: isDarkMode ? AppColors.primary : Colors.blue,
color: isDarkMode
? AppColors.primary
: Colors.blue,
)
: const SizedBox.shrink();
}),
@ -422,7 +436,7 @@ class TranslationView extends GetView<TranslationController> {
child: TextButton(
onPressed: () => Get.back(),
child: Text(
'取消',
'cancel'.tr, // 取消
style: TextStyle(
color: isDarkMode ? AppColors.primary : Colors.blue,
fontSize: 16.sp,
@ -437,4 +451,4 @@ class TranslationView extends GetView<TranslationController> {
),
);
}
}
}

2
pubspec.yaml

@ -9,6 +9,8 @@ environment:
dependencies:
flutter:
sdk: flutter
flutter_localizations:
sdk: flutter
cupertino_icons: ^1.0.6
get: ^4.6.6
get_storage: ^2.1.1

Loading…
Cancel
Save