@@ -0,0 +1,197 @@ | |||||
/// GENERATED CODE - DO NOT MODIFY BY HAND | |||||
/// ***************************************************** | |||||
/// FlutterGen | |||||
/// ***************************************************** | |||||
// coverage:ignore-file | |||||
// ignore_for_file: type=lint | |||||
// ignore_for_file: directives_ordering,unnecessary_import,implicit_dynamic_list_literal,deprecated_member_use | |||||
import 'package:flutter/widgets.dart'; | |||||
class $AssetsIconGen { | |||||
const $AssetsIconGen(); | |||||
/// File path: assets/icon/bottom_nav_home_select.png | |||||
AssetGenImage get bottomNavHomeSelect => | |||||
const AssetGenImage('assets/icon/bottom_nav_home_select.png'); | |||||
/// File path: assets/icon/bottom_nav_home_unselect.gif | |||||
AssetGenImage get bottomNavHomeUnselect => | |||||
const AssetGenImage('assets/icon/bottom_nav_home_unselect.gif'); | |||||
/// File path: assets/icon/bottom_nav_profile_select.png | |||||
AssetGenImage get bottomNavProfileSelect => | |||||
const AssetGenImage('assets/icon/bottom_nav_profile_select.png'); | |||||
/// File path: assets/icon/bottom_nav_profile_unselect.png | |||||
AssetGenImage get bottomNavProfileUnselect => | |||||
const AssetGenImage('assets/icon/bottom_nav_profile_unselect.png'); | |||||
/// File path: assets/icon/bottom_nav_sleep_select.png | |||||
AssetGenImage get bottomNavSleepSelect => | |||||
const AssetGenImage('assets/icon/bottom_nav_sleep_select.png'); | |||||
/// File path: assets/icon/bottom_nav_sleep_unselect.png | |||||
AssetGenImage get bottomNavSleepUnselect => | |||||
const AssetGenImage('assets/icon/bottom_nav_sleep_unselect.png'); | |||||
/// File path: assets/icon/home_ai_model.png | |||||
AssetGenImage get homeAiModel => | |||||
const AssetGenImage('assets/icon/home_ai_model.png'); | |||||
/// File path: assets/icon/home_bluetooth.png | |||||
AssetGenImage get homeBluetooth => | |||||
const AssetGenImage('assets/icon/home_bluetooth.png'); | |||||
/// File path: assets/icon/home_icon_fyjl.png | |||||
AssetGenImage get homeIconFyjl => | |||||
const AssetGenImage('assets/icon/home_icon_fyjl.png'); | |||||
/// File path: assets/icon/home_icon_mdmfy.png | |||||
AssetGenImage get homeIconMdmfy => | |||||
const AssetGenImage('assets/icon/home_icon_mdmfy.png'); | |||||
/// File path: assets/icon/home_icon_tcty.png | |||||
AssetGenImage get homeIconTcty => | |||||
const AssetGenImage('assets/icon/home_icon_tcty.png'); | |||||
/// File path: assets/icon/home_icon_thyyfy.png | |||||
AssetGenImage get homeIconThyyfy => | |||||
const AssetGenImage('assets/icon/home_icon_thyyfy.png'); | |||||
/// File path: assets/icon/home_img.png | |||||
AssetGenImage get homeImg => const AssetGenImage('assets/icon/home_img.png'); | |||||
/// File path: assets/icon/home_question.png | |||||
AssetGenImage get homeQuestion => | |||||
const AssetGenImage('assets/icon/home_question.png'); | |||||
/// File path: assets/icon/icon.png | |||||
AssetGenImage get icon => const AssetGenImage('assets/icon/icon.png'); | |||||
/// List of all assets | |||||
List<AssetGenImage> get values => [ | |||||
bottomNavHomeSelect, | |||||
bottomNavHomeUnselect, | |||||
bottomNavProfileSelect, | |||||
bottomNavProfileUnselect, | |||||
bottomNavSleepSelect, | |||||
bottomNavSleepUnselect, | |||||
homeAiModel, | |||||
homeBluetooth, | |||||
homeIconFyjl, | |||||
homeIconMdmfy, | |||||
homeIconTcty, | |||||
homeIconThyyfy, | |||||
homeImg, | |||||
homeQuestion, | |||||
icon | |||||
]; | |||||
} | |||||
class $AssetsImgGen { | |||||
const $AssetsImgGen(); | |||||
/// File path: assets/img/image_ai.png | |||||
AssetGenImage get imageAi => const AssetGenImage('assets/img/image_ai.png'); | |||||
/// File path: assets/img/image_bg_blur.png | |||||
AssetGenImage get imageBgBlur => | |||||
const AssetGenImage('assets/img/image_bg_blur.png'); | |||||
/// File path: assets/img/image_user.png | |||||
AssetGenImage get imageUser => | |||||
const AssetGenImage('assets/img/image_user.png'); | |||||
/// List of all assets | |||||
List<AssetGenImage> get values => [imageAi, imageBgBlur, imageUser]; | |||||
} | |||||
class Assets { | |||||
const Assets._(); | |||||
static const $AssetsIconGen icon = $AssetsIconGen(); | |||||
static const $AssetsImgGen img = $AssetsImgGen(); | |||||
} | |||||
class AssetGenImage { | |||||
const AssetGenImage( | |||||
this._assetName, { | |||||
this.size, | |||||
this.flavors = const {}, | |||||
}); | |||||
final String _assetName; | |||||
final Size? size; | |||||
final Set<String> flavors; | |||||
Image image({ | |||||
Key? key, | |||||
AssetBundle? bundle, | |||||
ImageFrameBuilder? frameBuilder, | |||||
ImageErrorWidgetBuilder? errorBuilder, | |||||
String? semanticLabel, | |||||
bool excludeFromSemantics = false, | |||||
double? scale, | |||||
double? width, | |||||
double? height, | |||||
Color? color, | |||||
Animation<double>? opacity, | |||||
BlendMode? colorBlendMode, | |||||
BoxFit? fit, | |||||
AlignmentGeometry alignment = Alignment.center, | |||||
ImageRepeat repeat = ImageRepeat.noRepeat, | |||||
Rect? centerSlice, | |||||
bool matchTextDirection = false, | |||||
bool gaplessPlayback = true, | |||||
bool isAntiAlias = false, | |||||
String? package, | |||||
FilterQuality filterQuality = FilterQuality.medium, | |||||
int? cacheWidth, | |||||
int? cacheHeight, | |||||
}) { | |||||
return Image.asset( | |||||
_assetName, | |||||
key: key, | |||||
bundle: bundle, | |||||
frameBuilder: frameBuilder, | |||||
errorBuilder: errorBuilder, | |||||
semanticLabel: semanticLabel, | |||||
excludeFromSemantics: excludeFromSemantics, | |||||
scale: scale, | |||||
width: width, | |||||
height: height, | |||||
color: color, | |||||
opacity: opacity, | |||||
colorBlendMode: colorBlendMode, | |||||
fit: fit, | |||||
alignment: alignment, | |||||
repeat: repeat, | |||||
centerSlice: centerSlice, | |||||
matchTextDirection: matchTextDirection, | |||||
gaplessPlayback: gaplessPlayback, | |||||
isAntiAlias: isAntiAlias, | |||||
package: package, | |||||
filterQuality: filterQuality, | |||||
cacheWidth: cacheWidth, | |||||
cacheHeight: cacheHeight, | |||||
); | |||||
} | |||||
ImageProvider provider({ | |||||
AssetBundle? bundle, | |||||
String? package, | |||||
}) { | |||||
return AssetImage( | |||||
_assetName, | |||||
bundle: bundle, | |||||
package: package, | |||||
); | |||||
} | |||||
String get path => _assetName; | |||||
String get keyName => _assetName; | |||||
} |
@@ -0,0 +1,67 @@ | |||||
// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart | |||||
// This is a library that looks up messages for specific locales by | |||||
// delegating to the appropriate library. | |||||
// Ignore issues from commonly used lints in this file. | |||||
// ignore_for_file:implementation_imports, file_names, unnecessary_new | |||||
// ignore_for_file:unnecessary_brace_in_string_interps, directives_ordering | |||||
// ignore_for_file:argument_type_not_assignable, invalid_assignment | |||||
// ignore_for_file:prefer_single_quotes, prefer_generic_function_type_aliases | |||||
// ignore_for_file:comment_references | |||||
import 'dart:async'; | |||||
import 'package:flutter/foundation.dart'; | |||||
import 'package:intl/intl.dart'; | |||||
import 'package:intl/message_lookup_by_library.dart'; | |||||
import 'package:intl/src/intl_helpers.dart'; | |||||
import 'messages_en.dart' as messages_en; | |||||
import 'messages_zh.dart' as messages_zh; | |||||
typedef Future<dynamic> LibraryLoader(); | |||||
Map<String, LibraryLoader> _deferredLibraries = { | |||||
'en': () => new SynchronousFuture(null), | |||||
'zh': () => new SynchronousFuture(null), | |||||
}; | |||||
MessageLookupByLibrary? _findExact(String localeName) { | |||||
switch (localeName) { | |||||
case 'en': | |||||
return messages_en.messages; | |||||
case 'zh': | |||||
return messages_zh.messages; | |||||
default: | |||||
return null; | |||||
} | |||||
} | |||||
/// User programs should call this before using [localeName] for messages. | |||||
Future<bool> initializeMessages(String localeName) { | |||||
var availableLocale = Intl.verifiedLocale( | |||||
localeName, (locale) => _deferredLibraries[locale] != null, | |||||
onFailure: (_) => null); | |||||
if (availableLocale == null) { | |||||
return new SynchronousFuture(false); | |||||
} | |||||
var lib = _deferredLibraries[availableLocale]; | |||||
lib == null ? new SynchronousFuture(false) : lib(); | |||||
initializeInternalMessageLookup(() => new CompositeMessageLookup()); | |||||
messageLookup.addLocale(availableLocale, _findGeneratedMessagesFor); | |||||
return new SynchronousFuture(true); | |||||
} | |||||
bool _messagesExistFor(String locale) { | |||||
try { | |||||
return _findExact(locale) != null; | |||||
} catch (e) { | |||||
return false; | |||||
} | |||||
} | |||||
MessageLookupByLibrary? _findGeneratedMessagesFor(String locale) { | |||||
var actualLocale = | |||||
Intl.verifiedLocale(locale, _messagesExistFor, onFailure: (_) => null); | |||||
if (actualLocale == null) return null; | |||||
return _findExact(actualLocale); | |||||
} |
@@ -0,0 +1,29 @@ | |||||
// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart | |||||
// This is a library that provides messages for a en locale. All the | |||||
// messages from the main program should be duplicated here with the same | |||||
// function name. | |||||
// Ignore issues from commonly used lints in this file. | |||||
// ignore_for_file:unnecessary_brace_in_string_interps, unnecessary_new | |||||
// ignore_for_file:prefer_single_quotes,comment_references, directives_ordering | |||||
// ignore_for_file:annotate_overrides,prefer_generic_function_type_aliases | |||||
// ignore_for_file:unused_import, file_names, avoid_escaping_inner_quotes | |||||
// ignore_for_file:unnecessary_string_interpolations, unnecessary_string_escapes | |||||
import 'package:intl/intl.dart'; | |||||
import 'package:intl/message_lookup_by_library.dart'; | |||||
final messages = new MessageLookup(); | |||||
typedef String MessageIfAbsent(String messageStr, List<dynamic> args); | |||||
class MessageLookup extends MessageLookupByLibrary { | |||||
String get localeName => 'en'; | |||||
final messages = _notInlinedMessages(_notInlinedMessages); | |||||
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{ | |||||
"bottomNavHome": MessageLookupByLibrary.simpleMessage("home"), | |||||
"bottomNavProfile": MessageLookupByLibrary.simpleMessage("profile"), | |||||
"bottomNavSleep": MessageLookupByLibrary.simpleMessage("sleep") | |||||
}; | |||||
} |
@@ -0,0 +1,29 @@ | |||||
// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart | |||||
// This is a library that provides messages for a zh locale. All the | |||||
// messages from the main program should be duplicated here with the same | |||||
// function name. | |||||
// Ignore issues from commonly used lints in this file. | |||||
// ignore_for_file:unnecessary_brace_in_string_interps, unnecessary_new | |||||
// ignore_for_file:prefer_single_quotes,comment_references, directives_ordering | |||||
// ignore_for_file:annotate_overrides,prefer_generic_function_type_aliases | |||||
// ignore_for_file:unused_import, file_names, avoid_escaping_inner_quotes | |||||
// ignore_for_file:unnecessary_string_interpolations, unnecessary_string_escapes | |||||
import 'package:intl/intl.dart'; | |||||
import 'package:intl/message_lookup_by_library.dart'; | |||||
final messages = new MessageLookup(); | |||||
typedef String MessageIfAbsent(String messageStr, List<dynamic> args); | |||||
class MessageLookup extends MessageLookupByLibrary { | |||||
String get localeName => 'zh'; | |||||
final messages = _notInlinedMessages(_notInlinedMessages); | |||||
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{ | |||||
"bottomNavHome": MessageLookupByLibrary.simpleMessage("首页"), | |||||
"bottomNavProfile": MessageLookupByLibrary.simpleMessage("我的"), | |||||
"bottomNavSleep": MessageLookupByLibrary.simpleMessage("睡眠") | |||||
}; | |||||
} |
@@ -0,0 +1,109 @@ | |||||
// GENERATED CODE - DO NOT MODIFY BY HAND | |||||
import 'package:flutter/material.dart'; | |||||
import 'package:intl/intl.dart'; | |||||
import 'intl/messages_all.dart'; | |||||
// ************************************************************************** | |||||
// Generator: Flutter Intl IDE plugin | |||||
// Made by Localizely | |||||
// ************************************************************************** | |||||
// ignore_for_file: non_constant_identifier_names, lines_longer_than_80_chars | |||||
// ignore_for_file: join_return_with_assignment, prefer_final_in_for_each | |||||
// ignore_for_file: avoid_redundant_argument_values, avoid_escaping_inner_quotes | |||||
class S { | |||||
S(); | |||||
static S? _current; | |||||
static S get current { | |||||
assert(_current != null, | |||||
'No instance of S was loaded. Try to initialize the S delegate before accessing S.current.'); | |||||
return _current!; | |||||
} | |||||
static const AppLocalizationDelegate delegate = AppLocalizationDelegate(); | |||||
static Future<S> load(Locale locale) { | |||||
final name = (locale.countryCode?.isEmpty ?? false) | |||||
? locale.languageCode | |||||
: locale.toString(); | |||||
final localeName = Intl.canonicalizedLocale(name); | |||||
return initializeMessages(localeName).then((_) { | |||||
Intl.defaultLocale = localeName; | |||||
final instance = S(); | |||||
S._current = instance; | |||||
return instance; | |||||
}); | |||||
} | |||||
static S of(BuildContext context) { | |||||
final instance = S.maybeOf(context); | |||||
assert(instance != null, | |||||
'No instance of S present in the widget tree. Did you add S.delegate in localizationsDelegates?'); | |||||
return instance!; | |||||
} | |||||
static S? maybeOf(BuildContext context) { | |||||
return Localizations.of<S>(context, S); | |||||
} | |||||
/// `home` | |||||
String get bottomNavHome { | |||||
return Intl.message( | |||||
'home', | |||||
name: 'bottomNavHome', | |||||
desc: '', | |||||
args: [], | |||||
); | |||||
} | |||||
/// `sleep` | |||||
String get bottomNavSleep { | |||||
return Intl.message( | |||||
'sleep', | |||||
name: 'bottomNavSleep', | |||||
desc: '', | |||||
args: [], | |||||
); | |||||
} | |||||
/// `profile` | |||||
String get bottomNavProfile { | |||||
return Intl.message( | |||||
'profile', | |||||
name: 'bottomNavProfile', | |||||
desc: '', | |||||
args: [], | |||||
); | |||||
} | |||||
} | |||||
class AppLocalizationDelegate extends LocalizationsDelegate<S> { | |||||
const AppLocalizationDelegate(); | |||||
List<Locale> get supportedLocales { | |||||
return const <Locale>[ | |||||
Locale.fromSubtags(languageCode: 'en'), | |||||
Locale.fromSubtags(languageCode: 'zh'), | |||||
]; | |||||
} | |||||
@override | |||||
bool isSupported(Locale locale) => _isSupported(locale); | |||||
@override | |||||
Future<S> load(Locale locale) => S.load(locale); | |||||
@override | |||||
bool shouldReload(AppLocalizationDelegate old) => false; | |||||
bool _isSupported(Locale locale) { | |||||
for (var supportedLocale in supportedLocales) { | |||||
if (supportedLocale.languageCode == locale.languageCode) { | |||||
return true; | |||||
} | |||||
} | |||||
return false; | |||||
} | |||||
} |
@@ -0,0 +1,6 @@ | |||||
{ | |||||
"@@locale": "en", | |||||
"bottomNavHome": "home", | |||||
"bottomNavSleep": "sleep", | |||||
"bottomNavProfile": "profile" | |||||
} |
@@ -0,0 +1,6 @@ | |||||
{ | |||||
"@@locale": "zh", | |||||
"bottomNavHome": "首页", | |||||
"bottomNavSleep": "睡眠", | |||||
"bottomNavProfile": "我的" | |||||
} |
@@ -1,8 +1,27 @@ | |||||
import 'package:demo001/gen/assets.gen.dart'; | |||||
import 'package:demo001/generated/l10n.dart'; | |||||
import 'package:demo001/scenes/home/home_view.dart'; | |||||
import 'package:demo001/scenes/translate/TranslateScene.dart'; | import 'package:demo001/scenes/translate/TranslateScene.dart'; | ||||
import 'package:demo001/tools/color_utils.dart'; | |||||
import 'package:flutter/material.dart'; | import 'package:flutter/material.dart'; | ||||
import 'package:flutter_localizations/flutter_localizations.dart'; | |||||
import 'package:get/get.dart'; | |||||
import 'package:flutter_easyloading/flutter_easyloading.dart'; | |||||
import 'scenes/login/login_view.dart'; | |||||
void main() { | void main() { | ||||
WidgetsFlutterBinding.ensureInitialized(); | WidgetsFlutterBinding.ensureInitialized(); | ||||
EasyLoading.instance | |||||
..indicatorType = EasyLoadingIndicatorType.threeBounce | |||||
..maskType = EasyLoadingMaskType.black | |||||
..progressColor = Colors.white | |||||
..backgroundColor = const Color.fromRGBO(38, 38, 38, 1) | |||||
..indicatorColor = Colors.white | |||||
..textColor = Colors.white | |||||
..userInteractions = false | |||||
..displayDuration = 1.5.seconds | |||||
..dismissOnTap = false; | |||||
runApp(const MyApp()); | runApp(const MyApp()); | ||||
} | } | ||||
@@ -12,13 +31,107 @@ class MyApp extends StatelessWidget { | |||||
// This widget is the root of your application. | // This widget is the root of your application. | ||||
@override | @override | ||||
Widget build(BuildContext context) { | Widget build(BuildContext context) { | ||||
return MaterialApp( | |||||
title: 'Flutter Demo', | |||||
return GetMaterialApp( | |||||
locale: Get.deviceLocale, | |||||
fallbackLocale: Get.deviceLocale, | |||||
title: '智能耳机', | |||||
debugShowCheckedModeBanner: false, | |||||
defaultTransition: Transition.rightToLeft, | |||||
theme: ThemeData( | theme: ThemeData( | ||||
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), | |||||
useMaterial3: true, | |||||
), | |||||
home: TranslateScene(), | |||||
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), | |||||
useMaterial3: true, | |||||
appBarTheme: const AppBarTheme( | |||||
centerTitle: true, | |||||
iconTheme: IconThemeData(color: whiteGrey), | |||||
surfaceTintColor: white, | |||||
shape: Border(bottom: BorderSide(color: lightBlue, width: 1)), | |||||
backgroundColor: bgColor, | |||||
), | |||||
scaffoldBackgroundColor: bgColor, | |||||
splashColor: clean, | |||||
highlightColor: clean, | |||||
switchTheme: SwitchThemeData( | |||||
trackOutlineColor: const WidgetStatePropertyAll(blue), | |||||
trackColor: WidgetStateProperty.resolveWith((states) { | |||||
if (states.contains(WidgetState.selected)) { | |||||
return blue; | |||||
} else { | |||||
return silver.withOpacity(0.5); | |||||
} | |||||
}), | |||||
thumbColor: WidgetStateProperty.resolveWith((states) { | |||||
if (states.contains(WidgetState.selected)) { | |||||
return white; | |||||
} else { | |||||
return silver; | |||||
} | |||||
}), | |||||
trackOutlineWidth: const WidgetStatePropertyAll(0)), | |||||
navigationBarTheme: NavigationBarThemeData()), | |||||
localizationsDelegates: { | |||||
S.delegate, | |||||
GlobalMaterialLocalizations.delegate, | |||||
GlobalCupertinoLocalizations.delegate, | |||||
GlobalWidgetsLocalizations.delegate, | |||||
}, | |||||
supportedLocales: S.delegate.supportedLocales, | |||||
home: LoginScene(), | |||||
builder: (context, widget) { | |||||
final easyload = EasyLoading.init(); | |||||
var child = easyload(context, widget); | |||||
return MediaQuery( | |||||
data: | |||||
MediaQuery.of(context).copyWith(textScaler: TextScaler.noScaling), | |||||
child: child, | |||||
); | |||||
}, | |||||
); | ); | ||||
} | } | ||||
} | } | ||||
S? s; | |||||
class IndexWidget extends StatelessWidget { | |||||
final RxInt _currentIndex = 0.obs; | |||||
final List<Widget> _pages = [HomePage()]; | |||||
@override | |||||
Widget build(BuildContext context) { | |||||
return Scaffold( | |||||
body: Obx(() => _pages[_currentIndex.value]), | |||||
bottomNavigationBar: Builder(builder: (context) { | |||||
s = S.of(context); | |||||
return Obx(() => BottomNavigationBar( | |||||
type: BottomNavigationBarType.fixed, | |||||
backgroundColor: bottomNavBg, | |||||
currentIndex: _currentIndex.value, | |||||
selectedItemColor: white, | |||||
unselectedItemColor: grey, | |||||
items: [ | |||||
BottomNavigationBarItem( | |||||
icon: Assets.icon.bottomNavHomeUnselect | |||||
.image(width: 20, height: 20), | |||||
activeIcon: Assets.icon.bottomNavHomeSelect | |||||
.image(width: 20, height: 20), | |||||
label: s?.bottomNavHome), | |||||
BottomNavigationBarItem( | |||||
icon: Assets.icon.bottomNavSleepUnselect | |||||
.image(width: 20, height: 20), | |||||
activeIcon: Assets.icon.bottomNavSleepSelect | |||||
.image(width: 20, height: 20), | |||||
label: s?.bottomNavSleep), | |||||
BottomNavigationBarItem( | |||||
icon: Assets.icon.bottomNavProfileUnselect | |||||
.image(width: 20, height: 20), | |||||
activeIcon: Assets.icon.bottomNavProfileSelect | |||||
.image(width: 20, height: 20), | |||||
label: s?.bottomNavProfile), | |||||
], | |||||
onTap: (index) { | |||||
_currentIndex.value = index; | |||||
}, | |||||
)); | |||||
})); | |||||
} | |||||
} |
@@ -1,129 +0,0 @@ | |||||
/* | |||||
AI测试场景 | |||||
*/ | |||||
import 'package:flutter/material.dart'; | |||||
import 'package:demo001/doubao/DouBao.dart'; | |||||
class ChatItem { | |||||
String msg = ""; | |||||
int state = 0; | |||||
ChatItem({required this.msg}); | |||||
void append(String _msg) { | |||||
msg += _msg; | |||||
} | |||||
void end() { | |||||
state = 1; | |||||
} | |||||
} | |||||
class AIChatScene extends StatefulWidget { | |||||
@override | |||||
_AIChatSceneState createState() => _AIChatSceneState(); | |||||
} | |||||
class _AIChatSceneState extends State<AIChatScene> { | |||||
// final String apiKey = "sk-3adfd188a3134e718bbf704f525aff17"; | |||||
final Doubao doubao = Doubao( | |||||
apiKey: "418ec475-e2dc-4b76-8aca-842d81bc3466", | |||||
modelId: "ep-20250203161136-9lrxg"); | |||||
final List<ChatItem> _chats = [ChatItem(msg: "我是测试代码")]; | |||||
ChatItem? _currchat; | |||||
final TextEditingController _textController = TextEditingController(); | |||||
//发送消息 | |||||
_sendMessage() async { | |||||
_currchat = ChatItem(msg: ""); | |||||
setState(() { | |||||
_chats.add(_currchat!); | |||||
}); | |||||
var stream = doubao.chat(_textController.text); | |||||
_textController.text = ""; | |||||
await for (final content in stream) { | |||||
setState(() { | |||||
_currchat?.append(content); | |||||
}); | |||||
print('实时更新: $content'); | |||||
} | |||||
print('结束恢复'); | |||||
_currchat?.end(); | |||||
} | |||||
@override | |||||
Widget build(BuildContext context) { | |||||
return Scaffold( | |||||
appBar: AppBar(title: Text("AI测试")), | |||||
body: ListView.builder( | |||||
itemCount: _chats.length, | |||||
itemBuilder: (context, index) { | |||||
var item = _chats[index]; | |||||
return _buildAudioMessage(item); | |||||
}, | |||||
), | |||||
bottomNavigationBar: Padding( | |||||
padding: const EdgeInsets.all(20.0), | |||||
child: Container( | |||||
decoration: BoxDecoration( | |||||
color: Colors.grey[200], // 背景颜色 | |||||
borderRadius: BorderRadius.circular(30), // 圆角 | |||||
), | |||||
padding: EdgeInsets.symmetric( | |||||
horizontal: 20, vertical: 10), // 输入框和按钮的内边距 | |||||
child: Row( | |||||
children: [ | |||||
Expanded( | |||||
child: TextField( | |||||
controller: _textController, // 用于获取输入的文本 | |||||
decoration: InputDecoration( | |||||
hintText: '输入消息...', // 提示文本 | |||||
border: InputBorder.none, // 去除默认边框 | |||||
contentPadding: | |||||
EdgeInsets.symmetric(vertical: 12), // 调整内边距 | |||||
), | |||||
), | |||||
), | |||||
IconButton( | |||||
icon: Icon(Icons.send, color: Colors.blue), // 发送按钮图标 | |||||
onPressed: _sendMessage, // 发送按钮点击事件 | |||||
), | |||||
], | |||||
), | |||||
), | |||||
)); | |||||
} | |||||
// 构建语音消息 | |||||
Widget _buildAudioMessage(ChatItem data) { | |||||
return Padding( | |||||
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 15), | |||||
child: Column( | |||||
crossAxisAlignment: CrossAxisAlignment.start, | |||||
children: [ | |||||
// 音频播放按钮 | |||||
GestureDetector( | |||||
onTap: () {}, | |||||
child: Container( | |||||
padding: EdgeInsets.symmetric(vertical: 10, horizontal: 20), | |||||
decoration: BoxDecoration( | |||||
color: Colors.green, | |||||
borderRadius: BorderRadius.circular(30), | |||||
), | |||||
child: Row( | |||||
children: [ | |||||
Text( | |||||
data.msg, | |||||
style: TextStyle(color: Colors.white), | |||||
), | |||||
], | |||||
), | |||||
), | |||||
), | |||||
SizedBox(height: 5), | |||||
], | |||||
), | |||||
); | |||||
} | |||||
} |
@@ -1,114 +0,0 @@ | |||||
// /* | |||||
// 蓝牙测试场景 | |||||
// */ | |||||
// import 'package:flutter/material.dart'; | |||||
// import 'package:flutter_blue/flutter_blue.dart'; | |||||
// class BluetoothScene extends StatefulWidget { | |||||
// @override | |||||
// _BluetoothSceneState createState() => _BluetoothSceneState(); | |||||
// } | |||||
// class _BluetoothSceneState extends State<BluetoothScene> { | |||||
// FlutterBlue flutterBlue = FlutterBlue.instance; | |||||
// List<BluetoothDevice> devicesList = []; | |||||
// BluetoothDevice? connectedDevice; | |||||
// BluetoothState? bluetoothState; | |||||
// @override | |||||
// void initState() { | |||||
// super.initState(); | |||||
// flutterBlue.state.listen((state) { | |||||
// setState(() { | |||||
// bluetoothState = state; | |||||
// }); | |||||
// }); | |||||
// flutterBlue.scanResults.listen((results) { | |||||
// setState(() { | |||||
// devicesList = results | |||||
// .where((result) => result.device.name.isNotEmpty) | |||||
// .map((result) => result.device) | |||||
// .toList(); | |||||
// }); | |||||
// }); | |||||
// } | |||||
// void startScan() { | |||||
// flutterBlue.startScan(timeout: Duration(seconds: 4)); | |||||
// } | |||||
// void stopScan() { | |||||
// flutterBlue.stopScan(); | |||||
// } | |||||
// void connectToDevice(BluetoothDevice device) async { | |||||
// await device.connect(); | |||||
// setState(() { | |||||
// connectedDevice = device; | |||||
// }); | |||||
// listenToDeviceEvents(device); | |||||
// } | |||||
// void listenToDeviceEvents(BluetoothDevice device) { | |||||
// device.state.listen((state) { | |||||
// if (state == BluetoothDeviceState.connected) { | |||||
// print('Device connected'); | |||||
// } else if (state == BluetoothDeviceState.disconnected) { | |||||
// print('Device disconnected'); | |||||
// } | |||||
// }); | |||||
// device.discoverServices().then((services) { | |||||
// services.forEach((service) { | |||||
// service.characteristics.forEach((characteristic) { | |||||
// characteristic.setNotifyValue(true); | |||||
// characteristic.value.listen((value) { | |||||
// print('Received value: $value'); | |||||
// }); | |||||
// }); | |||||
// }); | |||||
// }); | |||||
// } | |||||
// @override | |||||
// Widget build(BuildContext context) { | |||||
// return Scaffold( | |||||
// appBar: AppBar(title: Text('Flutter Bluetooth Demo')), | |||||
// body: Column( | |||||
// children: [ | |||||
// if (bluetoothState == BluetoothState.off) | |||||
// Text('Bluetooth is off, please turn it on'), | |||||
// if (bluetoothState == BluetoothState.on) | |||||
// Column( | |||||
// children: [ | |||||
// ElevatedButton( | |||||
// onPressed: startScan, | |||||
// child: Text('Start Scan'), | |||||
// ), | |||||
// ElevatedButton( | |||||
// onPressed: stopScan, | |||||
// child: Text('Stop Scan'), | |||||
// ), | |||||
// ListView.builder( | |||||
// shrinkWrap: true, | |||||
// itemCount: devicesList.length, | |||||
// itemBuilder: (context, index) { | |||||
// return ListTile( | |||||
// title: Text(devicesList[index].name), | |||||
// subtitle: Text(devicesList[index].id.toString()), | |||||
// onTap: () { | |||||
// connectToDevice(devicesList[index]); | |||||
// }, | |||||
// ); | |||||
// }, | |||||
// ), | |||||
// if (connectedDevice != null) | |||||
// Text('Connected to: ${connectedDevice?.name}') | |||||
// ], | |||||
// ), | |||||
// ], | |||||
// ), | |||||
// ); | |||||
// } | |||||
// } |
@@ -0,0 +1,92 @@ | |||||
import 'dart:convert'; | |||||
import 'package:demo001/scenes/login/login_view.dart'; | |||||
import 'package:demo001/scenes/public.dart'; | |||||
import 'package:demo001/tools/http_utils.dart'; | |||||
import 'package:flutter_easyloading/flutter_easyloading.dart'; | |||||
import 'package:get/get.dart'; | |||||
import 'package:logger/logger.dart'; | |||||
import 'package:permission_handler/permission_handler.dart'; | |||||
import 'home_state.dart'; | |||||
/// @description: | |||||
/// @author | |||||
/// @date: 2025-01-07 15:51:34 | |||||
class HomeLogic extends GetxController { | |||||
final state = HomeState(); | |||||
//1.已连接到的蓝牙,2.失去连接的蓝牙 | |||||
final String _bluetoothConnectStateKey = "bluetoothConnected"; | |||||
final String _bluetoothDisConnectStateKey = "bluetoothDisConnect"; | |||||
//1.已经连接的蓝牙 | |||||
final String _bluetoothAlreadyConnectKey = "bluetoothAlreadyConnected"; | |||||
@override | |||||
void onInit() { | |||||
super.onInit(); | |||||
_getLoginInfo(); | |||||
_checkPermission(); | |||||
_checkConnectDevice(); | |||||
_checkBluetoothStatus(); | |||||
} | |||||
void _checkPermission() { | |||||
Permission.microphone.request(); | |||||
state.methodChannel.invokeMethod("bluetoothPermissionRequest"); | |||||
} | |||||
void _checkBluetoothStatus() {} | |||||
///获取登录信息 | |||||
void _getLoginInfo() async { | |||||
final info = await getSharedLoginInfo(); | |||||
if (info != null) { | |||||
state.loginModel = info; | |||||
_getAliToken(); | |||||
} else { | |||||
EasyLoading.showError('登录过期'); | |||||
Get.offAll(() => LoginScene()); | |||||
} | |||||
} | |||||
void _getAliToken() { | |||||
ApiClient.post( | |||||
url: '/api/home/auth_alitoken', | |||||
token: state.loginModel!.data!.token!, | |||||
param: {}, | |||||
onSuccess: (data) { | |||||
// Logger().i('---------_getAliToken--------token-:${data.runtimeType}'); | |||||
// Logger().i('---------_getAliToken--------token-:${data['data']['token']}'); | |||||
state.loginModel?.ali_appkey = data['data']['appkey']; | |||||
state.loginModel?.ali_token = data['data']['token']; | |||||
}, | |||||
onFailed: (msg) { | |||||
Logger().e('-------_getAliToken-------error-$msg'); | |||||
}); | |||||
} | |||||
///原生传回的数据监听 | |||||
void _phoneCallRecordingListenner() { | |||||
state.eventChannel.receiveBroadcastStream((data) {}); | |||||
} | |||||
//开始监听蓝牙连接设备状态 | |||||
void _checkConnectDevice() { | |||||
state.methodChannel.invokeMethod('checkConnectedBluetooth'); | |||||
} | |||||
///通话翻译按钮点击 | |||||
void startPhoneCallRecording() { | |||||
state.methodChannel.invokeMethod('startPhoneCallRecording'); | |||||
} | |||||
///停止通讯录音 | |||||
void stopPhoneCallRecording() { | |||||
state.methodChannel.invokeMethod('stopPhoneCallRecording'); | |||||
} | |||||
//打开蓝牙扫描界面 | |||||
void toBonding() {} | |||||
///**************通话录音翻译*************** */ | |||||
} |
@@ -0,0 +1,23 @@ | |||||
import 'package:demo001/scenes/login/model/login_model.dart'; | |||||
import 'package:flutter/services.dart'; | |||||
import 'package:get/get.dart'; | |||||
/// @description: | |||||
/// @author | |||||
/// @date: 2025-01-07 15:51:34 | |||||
class HomeState { | |||||
HomeState() { | |||||
///Initialize variables | |||||
} | |||||
EventChannel eventChannel = EventChannel('eventChannel'); | |||||
MethodChannel methodChannel = MethodChannel('methodChannel'); | |||||
RxString originText = 'english'.obs; | |||||
RxString translationText = '英语'.obs; | |||||
RxString phoneNum = ''.obs; | |||||
LoginModel? loginModel; | |||||
} |
@@ -0,0 +1,235 @@ | |||||
import 'dart:io'; | |||||
import 'dart:typed_data'; | |||||
import 'package:audioplayers/audioplayers.dart'; | |||||
import 'package:demo001/gen/assets.gen.dart'; | |||||
import 'package:demo001/generated/l10n.dart'; | |||||
import 'package:demo001/main.dart'; | |||||
import 'package:demo001/tools/color_utils.dart'; | |||||
import 'package:demo001/tools/textStyle.dart'; | |||||
import 'package:demo001/tools/widgets.dart'; | |||||
import 'package:flutter/material.dart'; | |||||
import 'package:gap/gap.dart'; | |||||
import 'package:get/get.dart'; | |||||
import 'package:logger/logger.dart'; | |||||
import 'package:path_provider/path_provider.dart'; | |||||
import 'home_logic.dart'; | |||||
import 'home_state.dart'; | |||||
/// @description: | |||||
/// @author | |||||
/// @date: 2025-01-07 15:51:34 | |||||
class HomePage extends StatelessWidget { | |||||
final HomeLogic logic = Get.put(HomeLogic()); | |||||
final HomeState state = Get.find<HomeLogic>().state; | |||||
double width = 0; | |||||
double height = 0; | |||||
@override | |||||
Widget build(BuildContext context) { | |||||
s = S.of(context); | |||||
width = MediaQuery.of(context).size.width; | |||||
height = MediaQuery.of(context).size.height; | |||||
return Scaffold( | |||||
backgroundColor: bgColor, | |||||
body: SafeArea( | |||||
child: Container( | |||||
padding: EdgeInsets.all(10), | |||||
alignment: Alignment.center, | |||||
child: SingleChildScrollView( | |||||
child: Column( | |||||
crossAxisAlignment: CrossAxisAlignment.start, | |||||
children: [ | |||||
Container( | |||||
alignment: Alignment.center, | |||||
child: Obx(() => Text( | |||||
state.phoneNum.value, | |||||
style: TextStyle( | |||||
fontSize: 18, | |||||
fontWeight: FontWeight.bold, | |||||
color: white), | |||||
)), | |||||
), | |||||
const Gap(12), | |||||
Center( | |||||
child: Image.asset( | |||||
'assets/icon/home_img.png', | |||||
width: width * 0.5, | |||||
fit: BoxFit.fitWidth, | |||||
), | |||||
), | |||||
const Gap(20), | |||||
GestureDetector( | |||||
onTap: logic.toBonding, | |||||
child: Center( | |||||
child: Container( | |||||
height: 40, | |||||
decoration: BoxDecoration( | |||||
color: white, | |||||
borderRadius: BorderRadius.circular(20), | |||||
), | |||||
child: Row( | |||||
mainAxisSize: MainAxisSize.min, | |||||
children: [ | |||||
const Gap(20), | |||||
Container( | |||||
width: 20, | |||||
height: 20, | |||||
alignment: Alignment.center, | |||||
decoration: BoxDecoration( | |||||
color: blue, | |||||
borderRadius: BorderRadius.circular(4)), | |||||
child: Icon( | |||||
Icons.bluetooth, | |||||
color: white, | |||||
size: 15, | |||||
), | |||||
), | |||||
Gap(5), | |||||
Obx(() => Text( | |||||
'请连接耳机', | |||||
style: TextStyle(fontSize: 14, color: black), | |||||
)), | |||||
const Gap(20), | |||||
], | |||||
), | |||||
), | |||||
), | |||||
), | |||||
const Gap(10), | |||||
Text( | |||||
'智能工具', | |||||
style: TextStyle( | |||||
fontSize: 18, fontWeight: FontWeight.bold, color: white), | |||||
), | |||||
const Gap(10), | |||||
GestureDetector( | |||||
onTap: () { | |||||
//Get.to(() => AiChatPage(loginModel: state.loginModel!)); | |||||
}, | |||||
child: Stack( | |||||
children: [ | |||||
Assets.icon.homeAiModel.image(fit: BoxFit.fitHeight), | |||||
Positioned( | |||||
left: 20, | |||||
top: 120 * 0.5 - | |||||
textSize('AI对话模式', Style.homeItemTextStyle).height * | |||||
0.5, | |||||
child: Text( | |||||
'AI对话模式', | |||||
style: Style.homeItemTextStyle, | |||||
), | |||||
) | |||||
], | |||||
), | |||||
), | |||||
const Gap(10), | |||||
Row( | |||||
mainAxisAlignment: MainAxisAlignment.spaceBetween, | |||||
children: [ | |||||
GestureDetector( | |||||
onTap: () { | |||||
//Get.to(() => Tcty_chatPage()); | |||||
}, | |||||
child: Stack( | |||||
children: [ | |||||
Assets.icon.homeIconTcty.image( | |||||
width: (width - 30) * 0.5, fit: BoxFit.fitWidth), | |||||
Positioned( | |||||
left: (width - 30) * 0.25 - | |||||
textSize('同声传译', Style.homeItemTextStyle) | |||||
.width * | |||||
0.5, | |||||
top: (width - 30) * 0.5 - 50, | |||||
child: Text( | |||||
'同声传译', | |||||
style: Style.homeItemTextStyle, | |||||
), | |||||
) | |||||
], | |||||
), | |||||
), | |||||
GestureDetector( | |||||
onTap: () async {}, | |||||
child: Stack( | |||||
children: [ | |||||
Assets.icon.homeIconMdmfy.image( | |||||
width: (width - 30) * 0.5, fit: BoxFit.fitWidth), | |||||
Positioned( | |||||
left: (width - 30) * 0.25 - | |||||
textSize('面对面翻译', Style.homeItemTextStyle) | |||||
.width * | |||||
0.5, | |||||
top: (width - 30) * 0.5 - 50, | |||||
child: Text( | |||||
'面对面翻译', | |||||
style: Style.homeItemTextStyle, | |||||
), | |||||
) | |||||
], | |||||
), | |||||
), | |||||
], | |||||
), | |||||
const Gap(10), | |||||
Row( | |||||
mainAxisAlignment: MainAxisAlignment.spaceBetween, | |||||
children: [ | |||||
GestureDetector( | |||||
onTap: () { | |||||
// Get.to(() => PhonecallAudioTranslatePage()); | |||||
// state.methodChannel | |||||
// .invokeMethod('googleRealTimeSpeechToText'); | |||||
}, | |||||
child: Stack( | |||||
children: [ | |||||
Assets.icon.homeIconThyyfy.image( | |||||
width: (width - 30) * 0.5, fit: BoxFit.fitWidth), | |||||
Positioned( | |||||
left: (width - 30) * 0.25 - | |||||
textSize('通话语音翻译', Style.homeItemTextStyle) | |||||
.width * | |||||
0.5, | |||||
top: (width - 30) * 0.5 - 50, | |||||
child: Text( | |||||
'通话语音翻译', | |||||
style: Style.homeItemTextStyle, | |||||
), | |||||
) | |||||
], | |||||
), | |||||
), | |||||
GestureDetector( | |||||
onTap: () { | |||||
// Get.to(() => TransRecordsPage()); | |||||
}, | |||||
child: Stack( | |||||
children: [ | |||||
Assets.icon.homeIconFyjl.image( | |||||
width: (width - 30) * 0.5, fit: BoxFit.fitWidth), | |||||
Positioned( | |||||
left: (width - 30) * 0.25 - | |||||
textSize('翻译记录', Style.homeItemTextStyle) | |||||
.width * | |||||
0.5, | |||||
top: (width - 30) * 0.5 - 50, | |||||
child: Text( | |||||
'翻译记录', | |||||
style: Style.homeItemTextStyle, | |||||
), | |||||
) | |||||
], | |||||
), | |||||
), | |||||
], | |||||
) | |||||
], | |||||
), | |||||
), | |||||
), | |||||
), | |||||
); | |||||
} | |||||
} |
@@ -0,0 +1,105 @@ | |||||
import 'dart:async'; | |||||
import 'dart:convert'; | |||||
import 'package:demo001/main.dart'; | |||||
import 'package:demo001/scenes/public.dart'; | |||||
import 'package:demo001/tools/http_utils.dart'; | |||||
import 'package:flutter/material.dart'; | |||||
import 'package:flutter_easyloading/flutter_easyloading.dart'; | |||||
import 'package:get/get.dart'; | |||||
import 'package:shared_preferences/shared_preferences.dart'; | |||||
import 'package:demo001/tools/widgets.dart'; | |||||
import 'login_state.dart'; | |||||
/// @description: | |||||
/// @author | |||||
/// @date: 2025-01-11 17:25:10 | |||||
class LoginLogic extends GetxController { | |||||
final state = LoginState(); | |||||
@override | |||||
void onInit() { | |||||
super.onInit(); | |||||
} | |||||
@override | |||||
void onReady() { | |||||
super.onReady(); | |||||
_getLoginInfo(); | |||||
_checkPermission(); | |||||
} | |||||
void _checkPermission() { | |||||
state.methodChannel.invokeMethod("bluetoothPermissionRequest"); | |||||
} | |||||
void _getLoginInfo() async { | |||||
EasyLoading.show(); | |||||
final info = await getSharedLoginInfo(); | |||||
if (info != null) { | |||||
state.emailContr.text = info.data?.user?.mail ?? ''; | |||||
Future.delayed(1.seconds, () { | |||||
EasyLoading.dismiss(); | |||||
Get.offAll(() => IndexWidget()); | |||||
}); | |||||
} else { | |||||
EasyLoading.dismiss(); | |||||
} | |||||
} | |||||
void getEmailPin() { | |||||
if (state.countDown.value != 60) return; | |||||
state.pinCodeTimer = Timer.periodic(1.seconds, (t) { | |||||
if (state.countDown.value <= 0) { | |||||
t.cancel(); | |||||
state.pinCodeTimer?.cancel(); | |||||
state.pinCodeTimer = null; | |||||
state.countDown.value = 60; | |||||
} else { | |||||
state.countDown.value = state.countDown.value - 1; | |||||
} | |||||
}); | |||||
EasyLoading.show(); | |||||
ApiClient.post( | |||||
url: ApiClient.getPin, | |||||
param: {"addr": state.emailContr.text, "vtype": 0}, | |||||
onSuccess: (data) { | |||||
EasyLoading.showInfo('验证码已发送,请到邮箱查看!'); | |||||
}, | |||||
onFailed: (msg) { | |||||
EasyLoading.showError(msg); | |||||
}); | |||||
} | |||||
void login() { | |||||
FocusScope.of(Get.context!).unfocus(); | |||||
if (!isEmail(state.emailContr.text)) { | |||||
EasyLoading.showError('邮箱错误!'); | |||||
return; | |||||
} | |||||
if (state.pinContr.text.length != 4) { | |||||
EasyLoading.showError('验证码错误!'); | |||||
return; | |||||
} | |||||
EasyLoading.show(); | |||||
ApiClient.post( | |||||
url: ApiClient.login, | |||||
param: { | |||||
"mail": state.emailContr.text, | |||||
"openid": "", | |||||
"phone": "", | |||||
"stype": 0, | |||||
"vcode": state.pinContr.text | |||||
}, | |||||
onSuccess: (data) async { | |||||
EasyLoading.dismiss(); | |||||
final instance = await SharedPreferences.getInstance(); | |||||
await instance.setString('loginInfo', jsonEncode(data)); | |||||
Get.offAll(() => IndexWidget()); | |||||
}, | |||||
onFailed: (msg) { | |||||
EasyLoading.showError(msg); | |||||
}); | |||||
} | |||||
} |
@@ -0,0 +1,32 @@ | |||||
import 'dart:async'; | |||||
import 'package:flutter/material.dart'; | |||||
import 'package:flutter/services.dart'; | |||||
import 'package:get/get.dart'; | |||||
/// @description: | |||||
/// @author | |||||
/// @date: 2025-01-11 17:25:10 | |||||
class LoginState { | |||||
TextEditingController phoneContr = TextEditingController(); | |||||
TextEditingController pinContr = TextEditingController(); | |||||
TextEditingController emailContr = TextEditingController(); | |||||
MethodChannel methodChannel = MethodChannel('methodChannel'); | |||||
FocusNode phoneNode = FocusNode(); | |||||
FocusNode emailNode = FocusNode(); | |||||
FocusNode pinNode = FocusNode(); | |||||
// RxString phoneNumber = ''.obs; | |||||
RxString countryCode = '86'.obs; | |||||
RxBool protocolCheck = false.obs; | |||||
Timer? pinCodeTimer; | |||||
RxInt countDown = 60.obs; | |||||
RxBool isWechatLogin = false.obs; | |||||
} |
@@ -0,0 +1,317 @@ | |||||
import 'package:flutter/material.dart'; | |||||
import 'package:flutter/services.dart'; | |||||
import 'package:flutter_easyloading/flutter_easyloading.dart'; | |||||
import 'package:flutter_keyboard_visibility/flutter_keyboard_visibility.dart'; | |||||
import 'package:gap/gap.dart'; | |||||
import 'package:get/get.dart'; | |||||
import 'package:demo001/tools/color_utils.dart'; | |||||
import 'package:demo001/tools/textStyle.dart'; | |||||
import 'package:demo001/tools/widgets.dart'; | |||||
import 'login_logic.dart'; | |||||
import 'login_state.dart'; | |||||
/// @description: | |||||
/// @author | |||||
/// @date: 2025-01-11 17:25:10 | |||||
class LoginScene extends StatelessWidget { | |||||
final LoginLogic logic = Get.put(LoginLogic()); | |||||
final LoginState state = Get.find<LoginLogic>().state; | |||||
LoginScene({Key? key}) : super(key: key); | |||||
double width = 0; | |||||
double height = 0; | |||||
@override | |||||
Widget build(BuildContext context) { | |||||
width = MediaQuery.of(context).size.width; | |||||
height = MediaQuery.of(context).size.height; | |||||
return Scaffold( | |||||
backgroundColor: Color.fromARGB(255, 18, 19, 24), | |||||
body: KeyboardDismissOnTap( | |||||
child: Container( | |||||
height: height, | |||||
padding: EdgeInsets.all(8), | |||||
child: SingleChildScrollView( | |||||
child: Column( | |||||
mainAxisSize: MainAxisSize.max, | |||||
children: [ | |||||
Gap(45), | |||||
topWidget, | |||||
Gap(50), | |||||
logo, | |||||
Gap(15), | |||||
emailInput, | |||||
Gap(15), | |||||
pinInput, | |||||
Gap(30), | |||||
loginBtn, | |||||
Gap(80), | |||||
otherLogin, | |||||
Gap(180), | |||||
protocolWidget, | |||||
Gap(15), | |||||
], | |||||
), | |||||
), | |||||
), | |||||
), | |||||
); | |||||
} | |||||
Widget get topWidget => Row( | |||||
mainAxisAlignment: MainAxisAlignment.spaceBetween, | |||||
children: [ | |||||
Text( | |||||
'联系客服', | |||||
style: Style.clean, | |||||
), | |||||
Text( | |||||
'登录/注册账号', | |||||
style: Style.navTitle, | |||||
), | |||||
Text( | |||||
'联系客服', | |||||
style: Style.normalWhiteGrey, | |||||
) | |||||
], | |||||
); | |||||
Widget get logo => Container( | |||||
child: Icon( | |||||
Icons.logo_dev, | |||||
size: 100, | |||||
color: white, | |||||
), | |||||
); | |||||
Widget get phoneInput => SizedBox( | |||||
width: width * 0.9, | |||||
child: Container( | |||||
height: 45, | |||||
decoration: BoxDecoration( | |||||
color: inputBgColor, borderRadius: BorderRadius.circular(8)), | |||||
child: Obx(() => TextField( | |||||
controller: state.phoneContr, | |||||
focusNode: state.phoneNode, | |||||
textInputAction: TextInputAction.next, | |||||
style: Style.normalBold, | |||||
keyboardType: TextInputType.phone, | |||||
inputFormatters: state.countryCode.value == '86' | |||||
? [ | |||||
FilteringTextInputFormatter.allow(RegExp(r'[0-9]')), | |||||
LengthLimitingTextInputFormatter(11), | |||||
] | |||||
: [ | |||||
FilteringTextInputFormatter.allow(RegExp(r'[0-9]')), | |||||
], | |||||
decoration: InputDecoration( | |||||
border: InputBorder.none, | |||||
hintText: '请输入手机号', | |||||
contentPadding: EdgeInsets.symmetric(horizontal: 8), | |||||
), | |||||
onSubmitted: (value) { | |||||
if (value.length != 11) { | |||||
EasyLoading.showError('手机号码错误!'); | |||||
} else { | |||||
state.emailNode.nextFocus(); | |||||
} | |||||
}, | |||||
)), | |||||
), | |||||
); | |||||
Widget get emailInput => SizedBox( | |||||
width: width * 0.9, | |||||
child: Container( | |||||
height: 45, | |||||
decoration: BoxDecoration( | |||||
color: inputBgColor, borderRadius: BorderRadius.circular(8)), | |||||
child: TextField( | |||||
controller: state.emailContr, | |||||
focusNode: state.emailNode, | |||||
style: Style.normalBold, | |||||
textInputAction: TextInputAction.next, | |||||
keyboardType: TextInputType.emailAddress, | |||||
decoration: InputDecoration( | |||||
border: InputBorder.none, | |||||
hintText: '请输入邮箱', | |||||
contentPadding: EdgeInsets.symmetric(horizontal: 8), | |||||
), | |||||
onSubmitted: (value) { | |||||
final emailRegex = | |||||
RegExp(r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'); | |||||
if (!emailRegex.hasMatch(state.emailContr.text)) { | |||||
EasyLoading.showError('邮箱输入错误,请重新输入!'); | |||||
} else { | |||||
state.pinNode.nextFocus(); | |||||
} | |||||
}, | |||||
), | |||||
), | |||||
); | |||||
Widget get pinInput => SizedBox( | |||||
width: width * 0.9, | |||||
child: Row( | |||||
mainAxisAlignment: MainAxisAlignment.spaceBetween, | |||||
children: [ | |||||
Container( | |||||
height: 45, | |||||
width: width * 0.5, | |||||
decoration: BoxDecoration( | |||||
color: inputBgColor, borderRadius: BorderRadius.circular(8)), | |||||
child: Obx(() => TextField( | |||||
controller: state.pinContr, | |||||
focusNode: state.pinNode, | |||||
style: Style.normalBold, | |||||
textInputAction: TextInputAction.send, | |||||
keyboardType: TextInputType.phone, | |||||
inputFormatters: state.countryCode.value == '86' | |||||
? [ | |||||
FilteringTextInputFormatter.allow(RegExp(r'[0-9]')), | |||||
LengthLimitingTextInputFormatter(4), | |||||
] | |||||
: [ | |||||
FilteringTextInputFormatter.allow(RegExp(r'[0-9]')), | |||||
], | |||||
decoration: InputDecoration( | |||||
border: InputBorder.none, | |||||
hintText: '请输入验证码', | |||||
contentPadding: EdgeInsets.symmetric(horizontal: 8), | |||||
), | |||||
onSubmitted: (value) { | |||||
if (value.length != 4) { | |||||
EasyLoading.showError("验证码错误"); | |||||
} else { | |||||
logic.login(); | |||||
} | |||||
}, | |||||
)), | |||||
), | |||||
GestureDetector( | |||||
onTap: () { | |||||
if (!isEmail(state.emailContr.text)) { | |||||
EasyLoading.showError('邮箱输入错误,请重新输入!'); | |||||
} else { | |||||
logic.getEmailPin(); | |||||
} | |||||
}, | |||||
child: Container( | |||||
height: 45, | |||||
width: width * 0.35, | |||||
alignment: Alignment.center, | |||||
decoration: BoxDecoration( | |||||
color: lightBlue, borderRadius: BorderRadius.circular(8)), | |||||
child: Obx(() => Text( | |||||
state.countDown.value == 60 | |||||
? '获取验证码' | |||||
: '${state.countDown.value}S后重新获取', | |||||
style: Style.normalWhiteGrey, | |||||
)), | |||||
), | |||||
) | |||||
], | |||||
), | |||||
); | |||||
Widget get loginBtn => GestureDetector( | |||||
onTap: logic.login, | |||||
child: Container( | |||||
height: 45, | |||||
width: width * 0.9, | |||||
alignment: Alignment.center, | |||||
decoration: BoxDecoration( | |||||
color: blue, borderRadius: BorderRadius.circular(8)), | |||||
child: Obx(() => Text( | |||||
state.isWechatLogin.value ? '确定绑定' : '登录', | |||||
style: Style.normalBold, | |||||
)), | |||||
), | |||||
); | |||||
Widget get otherLogin => Container( | |||||
child: Column( | |||||
children: [ | |||||
Row( | |||||
mainAxisAlignment: MainAxisAlignment.center, | |||||
children: [ | |||||
Container( | |||||
color: white.withAlpha(30), | |||||
height: 1, | |||||
width: width * 0.25, | |||||
), | |||||
Text( | |||||
' 其他登录方式 ', | |||||
style: Style.normalSmallWhiteGrey, | |||||
), | |||||
Container( | |||||
color: white.withAlpha(30), | |||||
height: 1, | |||||
width: width * 0.25, | |||||
), | |||||
], | |||||
), | |||||
Gap(15), | |||||
GestureDetector( | |||||
onTap: () { | |||||
state.isWechatLogin.value = !state.isWechatLogin.value; | |||||
}, | |||||
child: Icon( | |||||
Icons.wechat, | |||||
size: 60, | |||||
color: green, | |||||
), | |||||
), | |||||
], | |||||
), | |||||
); | |||||
Widget get protocolWidget => Row( | |||||
mainAxisSize: MainAxisSize.max, | |||||
mainAxisAlignment: MainAxisAlignment.center, | |||||
children: [ | |||||
GestureDetector( | |||||
onTap: () { | |||||
state.protocolCheck.value = !state.protocolCheck.value; | |||||
}, | |||||
child: Row( | |||||
children: [ | |||||
Obx(() => Container( | |||||
child: Icon( | |||||
state.protocolCheck.value | |||||
? Icons.check_circle | |||||
: Icons.radio_button_unchecked, | |||||
color: state.protocolCheck.value ? blue : white, | |||||
size: 16, | |||||
), | |||||
)), | |||||
Text( | |||||
' 我已阅读并同意', | |||||
style: Style.normalSmall2, | |||||
), | |||||
], | |||||
), | |||||
), | |||||
GestureDetector( | |||||
onTap: () {}, | |||||
child: Text( | |||||
'《用户服务协议》', | |||||
style: Style.normalBlueSmall2, | |||||
), | |||||
), | |||||
Text( | |||||
'和', | |||||
style: Style.normalSmall2, | |||||
), | |||||
GestureDetector( | |||||
onTap: () {}, | |||||
child: Text( | |||||
'《用户服务协议》', | |||||
style: Style.normalBlueSmall2, | |||||
), | |||||
) | |||||
], | |||||
); | |||||
} |
@@ -0,0 +1,20 @@ | |||||
import 'package:json_annotation/json_annotation.dart'; | |||||
import 'user.dart'; | |||||
part 'data.g.dart'; | |||||
@JsonSerializable() | |||||
class Data { | |||||
String? token; | |||||
User? user; | |||||
Data({this.token, this.user}); | |||||
@override | |||||
String toString() => 'Data(token: $token, user: $user)'; | |||||
factory Data.fromJson(Map<String, dynamic> json) => _$DataFromJson(json); | |||||
Map<String, dynamic> toJson() => _$DataToJson(this); | |||||
} |
@@ -0,0 +1,19 @@ | |||||
// GENERATED CODE - DO NOT MODIFY BY HAND | |||||
part of 'data.dart'; | |||||
// ************************************************************************** | |||||
// JsonSerializableGenerator | |||||
// ************************************************************************** | |||||
Data _$DataFromJson(Map<String, dynamic> json) => Data( | |||||
token: json['token'] as String?, | |||||
user: json['user'] == null | |||||
? null | |||||
: User.fromJson(json['user'] as Map<String, dynamic>), | |||||
); | |||||
Map<String, dynamic> _$DataToJson(Data instance) => <String, dynamic>{ | |||||
'token': instance.token, | |||||
'user': instance.user, | |||||
}; |
@@ -0,0 +1,26 @@ | |||||
import 'package:json_annotation/json_annotation.dart'; | |||||
import 'data.dart'; | |||||
part 'login_model.g.dart'; | |||||
@JsonSerializable() | |||||
class LoginModel { | |||||
int? code; | |||||
Data? data; | |||||
String? msg; | |||||
String? ali_token; | |||||
String? ali_appkey; | |||||
LoginModel({this.code, this.data, this.msg}); | |||||
@override | |||||
String toString() => 'LoginModel(code: $code, data: $data, msg: $msg)'; | |||||
factory LoginModel.fromJson(Map<String, dynamic> json) { | |||||
return _$LoginModelFromJson(json); | |||||
} | |||||
Map<String, dynamic> toJson() => _$LoginModelToJson(this); | |||||
} |
@@ -0,0 +1,22 @@ | |||||
// GENERATED CODE - DO NOT MODIFY BY HAND | |||||
part of 'login_model.dart'; | |||||
// ************************************************************************** | |||||
// JsonSerializableGenerator | |||||
// ************************************************************************** | |||||
LoginModel _$LoginModelFromJson(Map<String, dynamic> json) => LoginModel( | |||||
code: (json['code'] as num?)?.toInt(), | |||||
data: json['data'] == null | |||||
? null | |||||
: Data.fromJson(json['data'] as Map<String, dynamic>), | |||||
msg: json['msg'] as String?, | |||||
); | |||||
Map<String, dynamic> _$LoginModelToJson(LoginModel instance) => | |||||
<String, dynamic>{ | |||||
'code': instance.code, | |||||
'data': instance.data, | |||||
'msg': instance.msg, | |||||
}; |
@@ -0,0 +1,37 @@ | |||||
import 'package:json_annotation/json_annotation.dart'; | |||||
part 'user.g.dart'; | |||||
@JsonSerializable() | |||||
class User { | |||||
String? avatar; | |||||
int? createtime; | |||||
String? language; | |||||
int? lastbettime; | |||||
String? mail; | |||||
String? name; | |||||
String? phone; | |||||
String? uid; | |||||
String? wxopenid; | |||||
User({ | |||||
this.avatar, | |||||
this.createtime, | |||||
this.language, | |||||
this.lastbettime, | |||||
this.mail, | |||||
this.name, | |||||
this.phone, | |||||
this.uid, | |||||
this.wxopenid, | |||||
}); | |||||
@override | |||||
String toString() { | |||||
return 'User(avatar: $avatar, createtime: $createtime, language: $language, lastbettime: $lastbettime, mail: $mail, name: $name, phone: $phone, uid: $uid, wxopenid: $wxopenid)'; | |||||
} | |||||
factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json); | |||||
Map<String, dynamic> toJson() => _$UserToJson(this); | |||||
} |
@@ -0,0 +1,31 @@ | |||||
// GENERATED CODE - DO NOT MODIFY BY HAND | |||||
part of 'user.dart'; | |||||
// ************************************************************************** | |||||
// JsonSerializableGenerator | |||||
// ************************************************************************** | |||||
User _$UserFromJson(Map<String, dynamic> json) => User( | |||||
avatar: json['avatar'] as String?, | |||||
createtime: (json['createtime'] as num?)?.toInt(), | |||||
language: json['language'] as String?, | |||||
lastbettime: (json['lastbettime'] as num?)?.toInt(), | |||||
mail: json['mail'] as String?, | |||||
name: json['name'] as String?, | |||||
phone: json['phone'] as String?, | |||||
uid: json['uid'] as String?, | |||||
wxopenid: json['wxopenid'] as String?, | |||||
); | |||||
Map<String, dynamic> _$UserToJson(User instance) => <String, dynamic>{ | |||||
'avatar': instance.avatar, | |||||
'createtime': instance.createtime, | |||||
'language': instance.language, | |||||
'lastbettime': instance.lastbettime, | |||||
'mail': instance.mail, | |||||
'name': instance.name, | |||||
'phone': instance.phone, | |||||
'uid': instance.uid, | |||||
'wxopenid': instance.wxopenid, | |||||
}; |
@@ -0,0 +1,14 @@ | |||||
import 'dart:convert'; | |||||
import 'package:demo001/scenes/login/model/login_model.dart'; | |||||
import 'package:shared_preferences/shared_preferences.dart'; | |||||
Future<LoginModel?> getSharedLoginInfo() async { | |||||
final instance = await SharedPreferences.getInstance(); | |||||
final loginInfoStr = instance.getString('loginInfo'); | |||||
if (loginInfoStr != null && loginInfoStr != '') { | |||||
return LoginModel.fromJson(jsonDecode(loginInfoStr)); | |||||
} else { | |||||
return null; | |||||
} | |||||
} |
@@ -4,6 +4,7 @@ import 'dart:typed_data'; | |||||
import 'package:demo001/scenes/translate/TranslateState.dart'; | import 'package:demo001/scenes/translate/TranslateState.dart'; | ||||
import 'package:demo001/tools/audio_tool.dart'; | import 'package:demo001/tools/audio_tool.dart'; | ||||
import 'package:demo001/xunfei/phonetic_dictation/phonetic_dictaion_model/w.dart'; | |||||
import 'package:demo001/xunfei/recognition_result/recognition_content/recognition_content.dart'; | import 'package:demo001/xunfei/recognition_result/recognition_content/recognition_content.dart'; | ||||
import 'package:demo001/xunfei/xunfei_translate.dart'; | import 'package:demo001/xunfei/xunfei_translate.dart'; | ||||
import 'package:get/get.dart'; | import 'package:get/get.dart'; | ||||
@@ -103,22 +104,66 @@ class TranslateLogic extends GetxController { | |||||
void _handleRecognaitionData(result) { | void _handleRecognaitionData(result) { | ||||
KDXFSentenceModel model; | KDXFSentenceModel model; | ||||
String text = ""; | |||||
try { | try { | ||||
final index = state.kdxfSentenceList.indexWhere((KDXFSentenceModel e) { | final index = state.kdxfSentenceList.indexWhere((KDXFSentenceModel e) { | ||||
return e.sid == result.header?.sid; | return e.sid == result.header?.sid; | ||||
}); | }); | ||||
if (index != -1) { | if (index != -1) { | ||||
model = state.kdxfSentenceList[index]; | model = state.kdxfSentenceList[index]; | ||||
final recogContextStr = utf8.decode( | |||||
base64Decode(result.payload?.recognitionResults?.text ?? '')); | |||||
final ctx = RecognitionContent.fromJson(jsonDecode(recogContextStr)); | |||||
if (ctx.pgs == 'apd') { | |||||
String text = state.kdxfSentenceList[index].content; | |||||
state.kdxfSentenceList[index].contentList.add(ctx); | |||||
ctx.ws!.forEach((e) { | |||||
e.cw?.forEach((e2) { | |||||
text = text + (e2.w ?? ''); | |||||
}); | |||||
}); | |||||
if (text.trim() == '') return; | |||||
state.kdxfSentenceList[index].content = text; | |||||
state.kdxfSentenceList.refresh(); | |||||
} else if (ctx.pgs == 'rpl') { | |||||
String text = ''; | |||||
for (var i = ctx.rg?[0]; i! <= ctx.rg![1]; i++) { | |||||
state.kdxfSentenceList[index].contentList.removeWhere((ee) { | |||||
return ee.sn == i; | |||||
}); | |||||
} | |||||
state.kdxfSentenceList[index].contentList.add(ctx); | |||||
state.kdxfSentenceList[index].contentList.forEach((e) { | |||||
e.ws?.forEach((ee) { | |||||
ee.cw?.forEach((e2) { | |||||
text = text + (e2.w ?? ''); | |||||
}); | |||||
}); | |||||
}); | |||||
if (text.trim() == '') return; | |||||
state.kdxfSentenceList[index].content = text; | |||||
state.kdxfSentenceList.refresh(); | |||||
// } | |||||
} | |||||
} else { | } else { | ||||
final recogContextStr = utf8.decode( | final recogContextStr = utf8.decode( | ||||
base64Decode(result.payload?.recognitionResults?.text ?? '')); | base64Decode(result.payload?.recognitionResults?.text ?? '')); | ||||
final model = RecognitionContent.fromJson(jsonDecode(recogContextStr)); | |||||
final ctx = RecognitionContent.fromJson(jsonDecode(recogContextStr)); | |||||
ctx.ws!.forEach((e) { | |||||
e.cw?.forEach((e2) { | |||||
text = text + (e2.w ?? ''); | |||||
}); | |||||
}); | |||||
if (text == '') return; | |||||
model = KDXFSentenceModel( | model = KDXFSentenceModel( | ||||
content: "", | |||||
sid: result.header?.sid ?? '', | |||||
transResult: '', | |||||
audioPath: '', | |||||
perviousWs: model.ws ?? []); | |||||
content: text, | |||||
sid: result.header?.sid ?? '', | |||||
transResult: '', | |||||
audioPath: '', | |||||
perviousWs: (ctx.ws ?? []) as List<W>, | |||||
); | |||||
state.kdxfSentenceList.add(model); | |||||
state.kdxfSentenceList.refresh(); | |||||
} | } | ||||
} catch (e) { | } catch (e) { | ||||
print("接收识别结果异常 $e"); | print("接收识别结果异常 $e"); | ||||
@@ -129,6 +174,10 @@ class TranslateLogic extends GetxController { | |||||
void _handleAudioData(AudioModel model) {} | void _handleAudioData(AudioModel model) {} | ||||
void kdxfPlayAudio(KDXFSentenceModel model) async { | |||||
model.playerController.startPlayer(); | |||||
} | |||||
void _log(String msg) { | void _log(String msg) { | ||||
Logger().f("LIWEI---------------:$msg"); | Logger().f("LIWEI---------------:$msg"); | ||||
} | } | ||||
@@ -1,3 +1,8 @@ | |||||
import 'package:audio_waveforms/audio_waveforms.dart'; | |||||
import 'package:demo001/gen/assets.gen.dart'; | |||||
import 'package:demo001/tools/color_utils.dart'; | |||||
import 'package:demo001/tools/textStyle.dart'; | |||||
import 'package:gap/gap.dart'; | |||||
import 'package:get/get.dart'; | import 'package:get/get.dart'; | ||||
import 'package:flutter/material.dart'; | import 'package:flutter/material.dart'; | ||||
import 'TranslateLogic.dart'; | import 'TranslateLogic.dart'; | ||||
@@ -6,21 +11,30 @@ import 'TranslateState.dart'; | |||||
/* | /* | ||||
录音测试场景 | 录音测试场景 | ||||
*/ | */ | ||||
// ignore: must_be_immutable | |||||
class TranslateScene extends StatelessWidget { | class TranslateScene extends StatelessWidget { | ||||
final TranslateLogic logic = Get.put(TranslateLogic()); | final TranslateLogic logic = Get.put(TranslateLogic()); | ||||
final TranslateState state = Get.find<TranslateLogic>().state; | final TranslateState state = Get.find<TranslateLogic>().state; | ||||
TranslateScene({super.key}); | TranslateScene({super.key}); | ||||
double width = 0; | |||||
double height = 0; | |||||
@override | @override | ||||
Widget build(BuildContext context) { | Widget build(BuildContext context) { | ||||
width = MediaQuery.of(context).size.width; | |||||
height = MediaQuery.of(context).size.height; | |||||
return Scaffold( | return Scaffold( | ||||
appBar: AppBar(title: Text("录音测试")), | |||||
// body: ListView.builder( | |||||
// itemCount: _records.length, | |||||
// itemBuilder: (context, index) { | |||||
// var audio = _records[index]; | |||||
// return _buildAudioMessage(audio); | |||||
// }, | |||||
// ), | |||||
appBar: AppBar( | |||||
title: Text( | |||||
"同声传译", | |||||
style: Style.navTitle, | |||||
)), | |||||
body: ListView.builder( | |||||
itemCount: state.kdxfSentenceList.length, | |||||
itemBuilder: (context, index) { | |||||
var audio = state.kdxfSentenceList[index]; | |||||
return _buildAudioMessage(audio); | |||||
}, | |||||
), | |||||
bottomNavigationBar: Padding( | bottomNavigationBar: Padding( | ||||
padding: const EdgeInsets.all(20.0), | padding: const EdgeInsets.all(20.0), | ||||
child: InkWell( | child: InkWell( | ||||
@@ -59,47 +73,80 @@ class TranslateScene extends StatelessWidget { | |||||
} | } | ||||
// 构建语音消息 | // 构建语音消息 | ||||
// Widget _buildAudioMessage(RecordData data) { | |||||
// Color buttColor = data.state == 0 ? Colors.red : Colors.green; | |||||
// return Padding( | |||||
// padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 15), | |||||
// child: Column( | |||||
// crossAxisAlignment: CrossAxisAlignment.start, | |||||
// children: [ | |||||
// // 音频播放按钮 | |||||
// GestureDetector( | |||||
// onTap: () { | |||||
// // _playRecording(data); | |||||
// }, | |||||
// child: Container( | |||||
// padding: EdgeInsets.symmetric(vertical: 10, horizontal: 20), | |||||
// decoration: BoxDecoration( | |||||
// color: buttColor, | |||||
// borderRadius: BorderRadius.circular(30), | |||||
// ), | |||||
// child: Row( | |||||
// children: [ | |||||
// Icon( | |||||
// Icons.play_arrow, | |||||
// color: Colors.white, | |||||
// ), | |||||
// SizedBox(width: 10), | |||||
// Text( | |||||
// '播放音频', | |||||
// style: TextStyle(color: Colors.white), | |||||
// ), | |||||
// ], | |||||
// ), | |||||
// ), | |||||
// ), | |||||
// SizedBox(height: 5), | |||||
// // 文字内容 | |||||
// // Text( | |||||
// // message['text'], | |||||
// // style: TextStyle(fontSize: 16), | |||||
// // ), | |||||
// ], | |||||
// ), | |||||
// ); | |||||
// } | |||||
Widget _buildAudioMessage(KDXFSentenceModel model) { | |||||
return Padding( | |||||
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 15), | |||||
child: Row( | |||||
children: [ | |||||
const Gap(10), | |||||
Assets.img.imageAi.image(width: 25, height: 25), | |||||
const Gap(10), | |||||
Flexible( | |||||
child: ConstrainedBox( | |||||
constraints: BoxConstraints(maxWidth: width * 0.7), | |||||
child: Container( | |||||
padding: EdgeInsets.all(10), | |||||
margin: EdgeInsets.only(top: 10), | |||||
decoration: BoxDecoration( | |||||
borderRadius: BorderRadius.circular(12), | |||||
color: bottomNavBg, | |||||
), | |||||
child: Column( | |||||
crossAxisAlignment: CrossAxisAlignment.start, | |||||
children: [ | |||||
Text( | |||||
textAlign: TextAlign.start, | |||||
model.content, | |||||
style: Style.normal, | |||||
maxLines: 1000, | |||||
), | |||||
Obx(() => Visibility( | |||||
visible: model.transResult != '', child: Gap(5))), | |||||
Obx(() => Visibility( | |||||
visible: model.transResult != '', | |||||
child: Text( | |||||
textAlign: TextAlign.start, | |||||
model.transResult, | |||||
style: Style.normal, | |||||
maxLines: 1000, | |||||
), | |||||
)), | |||||
Obx(() => Visibility( | |||||
visible: model.audioDuration > 0, | |||||
child: const Gap(10), | |||||
)), | |||||
Obx( | |||||
() => Visibility( | |||||
visible: model.audioDuration > 0, | |||||
child: GestureDetector( | |||||
onTap: () { | |||||
logic.kdxfPlayAudio(model); | |||||
}, | |||||
child: ClipRRect( | |||||
borderRadius: BorderRadius.circular(5), | |||||
child: AudioFileWaveforms( | |||||
size: Size(165, 30), | |||||
decoration: BoxDecoration( | |||||
border: Border.all(color: orange, width: 0.5), | |||||
borderRadius: BorderRadius.circular(5), | |||||
), | |||||
enableSeekGesture: false, | |||||
waveformType: WaveformType.fitWidth, | |||||
playerWaveStyle: PlayerWaveStyle( | |||||
waveThickness: 2, scaleFactor: 50), | |||||
playerController: model.playerController, | |||||
), | |||||
), | |||||
), | |||||
), | |||||
) | |||||
], | |||||
), | |||||
), | |||||
), | |||||
), | |||||
], | |||||
), | |||||
); | |||||
} | |||||
} | } |
@@ -0,0 +1,52 @@ | |||||
import 'package:flutter/material.dart'; | |||||
import 'dart:math'; | |||||
MaterialColor createMaterialColor(Color color) { | |||||
List strengths = <double>[.05]; | |||||
Map<int, Color> swatch = <int, Color>{}; | |||||
final int r = color.red, g = color.green, b = color.blue; | |||||
for (int i = 1; i < 10; i++) { | |||||
strengths.add(0.1 * i); | |||||
} | |||||
for (var strength in strengths) { | |||||
final double ds = 0.5 - strength; | |||||
swatch[(strength * 1000).round()] = Color.fromRGBO( | |||||
r + ((ds < 0 ? r : (255 - r)) * ds).round(), | |||||
g + ((ds < 0 ? g : (255 - g)) * ds).round(), | |||||
b + ((ds < 0 ? b : (255 - b)) * ds).round(), | |||||
1, | |||||
); | |||||
} | |||||
return MaterialColor(color.value, swatch); | |||||
} | |||||
Color randomColor() { | |||||
return Color.fromARGB( | |||||
255, Random().nextInt(256), Random().nextInt(256), Random().nextInt(256)); | |||||
} | |||||
const Color bottomNavBg = Color.fromARGB(255, 31, 33, 39); | |||||
const Color silver = Color.fromARGB(255, 194, 195, 197); | |||||
const Color white = Colors.white; | |||||
const Color whiteGrey = Color.fromARGB(255, 224, 224, 224); | |||||
const Color inputBgColor = Color.fromARGB(255, 44, 46, 53); | |||||
const Color chatBlue = Color.fromARGB(255, 80, 158, 171); | |||||
// const Color white = Colors.black; | |||||
const Color bgColor = Color.fromARGB(255, 18, 19, 24); | |||||
// const Color bgColor = Colors.grey; | |||||
const Color blue = Color.fromARGB(255, 72, 90, 255); | |||||
const Color lightBlue = Color.fromARGB(255, 55, 56, 110); | |||||
const Color deepBlue = Color.fromARGB(255, 18, 19, 41); | |||||
const Color black = Color.fromARGB(255, 31, 32, 35); | |||||
const Color red = Color.fromARGB(255, 225, 118, 129); | |||||
const Color lightRed = Color.fromARGB(255, 224, 143, 152); | |||||
const Color orange = Color.fromARGB(255, 235, 176, 41); | |||||
const Color lightOrange = Color.fromARGB(255, 234, 200, 120); | |||||
const Color clean = Colors.transparent; | |||||
const Color grey = Color.fromARGB(255, 129, 129, 130); | |||||
const Color lightGrey = Color.fromARGB(255, 228, 228, 228); | |||||
const Color green = Color.fromARGB(255, 41, 196, 72); | |||||
const Color lightGreen = Color.fromARGB(255, 137, 214, 153); |
@@ -0,0 +1,259 @@ | |||||
import 'dart:convert'; | |||||
import 'dart:io'; | |||||
import 'package:dio/dio.dart'; | |||||
import 'package:logger/logger.dart'; | |||||
import 'package:http/http.dart' as http; | |||||
class ApiClient { | |||||
static final _client = Dio(); | |||||
static String _baseUrl = 'https://www.aitoyyun.com'; | |||||
static get( | |||||
{String baseUrl = 'https://www.aitoyyun.com', | |||||
required String url, | |||||
required Map<String, dynamic> param, | |||||
required Function(Map<String, dynamic>) onSuccess, | |||||
required Function(String) onFailed}) async { | |||||
_baseUrl = baseUrl; | |||||
try { | |||||
Response response = await _client.get( | |||||
_baseUrl + url.trim(), | |||||
options: Options( | |||||
headers: {"Accept": 'application/json'}, | |||||
sendTimeout: const Duration(seconds: 10), | |||||
receiveTimeout: const Duration(seconds: 10)), | |||||
queryParameters: param, | |||||
); | |||||
final jsonData = response.data; | |||||
_progressData( | |||||
jsonData: jsonData, onSuccess: onSuccess, onFailed: onFailed); | |||||
} on DioException catch (e) { | |||||
_progressError(response: e.response, onFailed: onFailed); | |||||
} | |||||
} | |||||
static post( | |||||
{required String url, | |||||
String? token, | |||||
required Map<String, dynamic> param, | |||||
required Function(Map<String, dynamic>) onSuccess, | |||||
required Function(String) onFailed}) async { | |||||
try { | |||||
// Logger().i('---url: ${_baseUrl + url.trim()}---param: $param-----token: $token'); | |||||
Response response = await _client.post( | |||||
_baseUrl + url.trim(), | |||||
options: Options( | |||||
headers: {'Authorization': token ?? ''}, | |||||
sendTimeout: const Duration(seconds: 10), | |||||
receiveTimeout: const Duration(seconds: 10)), | |||||
queryParameters: param, | |||||
data: param, | |||||
); | |||||
final jsonData = response.data; | |||||
_progressData( | |||||
jsonData: jsonData, onSuccess: onSuccess, onFailed: onFailed); | |||||
} on DioException catch (e) { | |||||
_progressError(response: e.response, onFailed: onFailed); | |||||
} | |||||
} | |||||
static put( | |||||
{required String url, | |||||
required Map<String, dynamic> param, | |||||
required Function(Map<String, dynamic>) onSuccess, | |||||
required Function(String) onFailed}) async { | |||||
try { | |||||
Response response = await _client.put( | |||||
_baseUrl + url.trim(), | |||||
options: Options( | |||||
headers: {"Accept": 'application/json'}, | |||||
sendTimeout: const Duration(seconds: 10), | |||||
receiveTimeout: const Duration(seconds: 10)), | |||||
queryParameters: param, | |||||
data: param, | |||||
); | |||||
final jsonData = response.data; | |||||
_progressData( | |||||
jsonData: jsonData, onSuccess: onSuccess, onFailed: onFailed); | |||||
} on DioException catch (e) { | |||||
_progressError(response: e.response, onFailed: onFailed); | |||||
} | |||||
} | |||||
static delete( | |||||
{required String url, | |||||
required Map<String, dynamic> param, | |||||
required Function(Map<String, dynamic>) onSuccess, | |||||
required Function(String) onFailed}) async { | |||||
try { | |||||
Response response = await _client.delete( | |||||
_baseUrl + url.trim(), | |||||
options: Options( | |||||
headers: {"Accept": 'application/json'}, | |||||
sendTimeout: const Duration(seconds: 10), | |||||
receiveTimeout: const Duration(seconds: 10)), | |||||
queryParameters: param, | |||||
data: param, | |||||
); | |||||
final jsonData = response.data; | |||||
_progressData( | |||||
jsonData: jsonData, onSuccess: onSuccess, onFailed: onFailed); | |||||
} on DioException catch (e) { | |||||
_progressError(response: e.response, onFailed: onFailed); | |||||
} | |||||
} | |||||
static void _progressData( | |||||
{required Map<String, dynamic> jsonData, | |||||
required Function(Map<String, dynamic>) onSuccess, | |||||
required Function(String) onFailed}) { | |||||
// Logger().i('--_progressData------${jsonData}'); | |||||
if (jsonData['code'] == 0) { | |||||
onSuccess(jsonData); | |||||
} else { | |||||
onFailed(jsonData['msg'].toString()); | |||||
} | |||||
} | |||||
static void _progressError( | |||||
{Response? response, required Function(String) onFailed}) { | |||||
if (response != null && response.data != null) { | |||||
Logger().i('_progressError---------${response.data}'); | |||||
if (response.data.runtimeType == String) { | |||||
onFailed('返回数据格式错误'); | |||||
} else { | |||||
if (response.data['errCode'] != null) { | |||||
final jsonData = response.data; | |||||
onFailed(jsonData['errMsg'].toString()); | |||||
} else { | |||||
final jsonData = response.data; | |||||
onFailed(jsonData['state']['msg'].toString()); | |||||
} | |||||
} | |||||
} else { | |||||
onFailed('请求失败'); | |||||
} | |||||
} | |||||
static final String login = '/api/home/user_sgin'; | |||||
static final String getPin = '/api/home/user_verification'; | |||||
// static final String aiChat = '/api/home/ai_chat'; | |||||
static final String deepseekApiKey = 'sk-3adfd188a3134e718bbf704f525aff17'; | |||||
static Stream<String> aiChat(String prompt) async* { | |||||
final client = HttpClient(); | |||||
try { | |||||
final request = await client | |||||
.postUrl(Uri.parse('https://api.deepseek.com/chat/completions')); | |||||
// 设置流式请求头 | |||||
request.headers | |||||
..set('Content-Type', 'application/json') | |||||
..set('Authorization', 'Bearer $deepseekApiKey') | |||||
..set('Accept', 'text/event-stream'); | |||||
// 构建请求体 | |||||
final requestBody = jsonEncode({ | |||||
'model': 'deepseek-chat', | |||||
'stream': true, | |||||
'messages': [ | |||||
{'role': 'user', 'content': prompt} | |||||
] | |||||
}); | |||||
// 写入请求体 | |||||
request.add(utf8.encode(requestBody)); | |||||
final response = await request.close(); | |||||
// 检查状态码 | |||||
if (response.statusCode != 200) { | |||||
throw Exception('API请求失败: ${response.statusCode}'); | |||||
} | |||||
// 处理流数据 | |||||
String buffer = ''; | |||||
await for (final chunk in response.transform(utf8.decoder)) { | |||||
buffer += chunk; | |||||
// 分割完整事件(假设使用SSE格式) | |||||
while (buffer.contains('\n\n')) { | |||||
final eventEnd = buffer.indexOf('\n\n'); | |||||
final event = buffer.substring(0, eventEnd); | |||||
buffer = buffer.substring(eventEnd + 2); | |||||
if (event.startsWith('data: ')) { | |||||
final dataContent = event.substring(6); | |||||
// 增加有效性检查 | |||||
if (dataContent == '[DONE]') { | |||||
// print('流式传输结束'); | |||||
continue; // 跳过特殊结束标记 | |||||
} | |||||
final jsonData = jsonDecode(event.substring(6)); | |||||
yield jsonData['choices'][0]['delta']['content']; | |||||
} | |||||
} | |||||
} | |||||
} finally { | |||||
client.close(); | |||||
} | |||||
} | |||||
static final String dbAppkey = '418ec475-e2dc-4b76-8aca-842d81bc3466'; | |||||
static final String dbModelId = 'ep-20250203161136-9lrxg'; | |||||
static Stream<String> dbChat(String userMessage) async* { | |||||
final client = http.Client(); | |||||
final url = | |||||
Uri.parse('https://ark.cn-beijing.volces.com/api/v3/chat/completions'); | |||||
final headers = { | |||||
'Content-Type': 'application/json', | |||||
'Authorization': 'Bearer $dbAppkey', | |||||
}; | |||||
final requestBody = { | |||||
"model": dbModelId, | |||||
"messages": [ | |||||
{"role": "system", "content": "你是豆包,是由字节跳动开发的 AI 人工智能助手."}, | |||||
{"role": "user", "content": userMessage} | |||||
], | |||||
"stream": true, | |||||
}; | |||||
try { | |||||
final request = http.Request('POST', url) | |||||
..headers.addAll(headers) | |||||
..body = jsonEncode(requestBody); | |||||
final response = await client.send(request); | |||||
//流式处理响应数据 | |||||
await for (final chunk in response.stream | |||||
.transform(utf8.decoder) | |||||
.transform(const LineSplitter())) { | |||||
if (chunk.isEmpty) continue; | |||||
// 假设豆包API使用类似OpenAI的流式格式(data: {...}) | |||||
if (chunk.startsWith('data:')) { | |||||
final jsonStr = chunk.substring(5).trim(); | |||||
if (jsonStr == '[DONE]') break; // 流结束标志 | |||||
try { | |||||
final data = jsonDecode(jsonStr); | |||||
final content = data['choices'][0]['delta']['content'] ?? ''; | |||||
if (content.isNotEmpty) { | |||||
yield content; // 逐块返回生成的文本 | |||||
} | |||||
} catch (e) { | |||||
// print('JSON解析错误: $e'); | |||||
} | |||||
// print('请求成功: $jsonStr'); | |||||
} | |||||
} | |||||
} catch (e) { | |||||
print('请求异常: $e'); | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,32 @@ | |||||
import 'package:flutter/material.dart'; | |||||
import 'package:demo001/tools/color_utils.dart'; | |||||
class Style { | |||||
static final greenBold = | |||||
TextStyle(color: green, fontSize: 18, fontWeight: FontWeight.bold); | |||||
static final navTitle = | |||||
TextStyle(color: white, fontSize: 18, fontWeight: FontWeight.bold); | |||||
static final normalWhiteGrey = | |||||
TextStyle(color: whiteGrey, fontSize: 14, fontWeight: FontWeight.bold); | |||||
static final normalBold = | |||||
TextStyle(color: white, fontSize: 16, fontWeight: FontWeight.bold); | |||||
static final normal = TextStyle(color: white, fontSize: 16); | |||||
static final normalGrey = | |||||
TextStyle(color: const Color.fromARGB(255, 92, 92, 92), fontSize: 16); | |||||
static final normalSmall = TextStyle(color: white, fontSize: 14); | |||||
static final normalGreySmall = | |||||
TextStyle(color: const Color.fromARGB(255, 92, 92, 92), fontSize: 14); | |||||
static final normalSmall2 = TextStyle(color: white, fontSize: 12); | |||||
static final normalGreySmall2 = | |||||
TextStyle(color: const Color.fromARGB(255, 92, 92, 92), fontSize: 12); | |||||
static final normalBlueSmall2 = TextStyle(color: blue, fontSize: 12); | |||||
static final normalSmallWhiteGrey = TextStyle(color: whiteGrey, fontSize: 14); | |||||
static final normalSmallBoldWhiteGrey = | |||||
TextStyle(color: whiteGrey, fontSize: 14, fontWeight: FontWeight.bold); | |||||
static final clean = TextStyle( | |||||
color: Colors.transparent, fontSize: 14, fontWeight: FontWeight.bold); | |||||
static final homeItemTextStyle = | |||||
TextStyle(fontSize: 14, color: white, fontWeight: FontWeight.bold); | |||||
static final hintStyle = TextStyle(fontSize: 14, color: grey); | |||||
} |
@@ -0,0 +1,21 @@ | |||||
import 'package:flutter/material.dart'; | |||||
Widget circleCheckBox() { | |||||
return Container( | |||||
child: Icon(Icons.check), | |||||
); | |||||
} | |||||
Size textSize(String text, TextStyle style) { | |||||
final TextPainter textPainter = TextPainter( | |||||
text: TextSpan(text: text, style: style), | |||||
textDirection: TextDirection.ltr, | |||||
)..layout(); // 布局文本 | |||||
return textPainter.size; // 返回文本的尺寸 | |||||
} | |||||
bool isEmail(String email) { | |||||
final emailRegex = | |||||
RegExp(r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'); | |||||
return emailRegex.hasMatch(email); | |||||
} |
@@ -296,8 +296,8 @@ class XunFeiTranslateTask { | |||||
onDone: () { | onDone: () { | ||||
isconnected = false; | isconnected = false; | ||||
print('WebSocket 连接已关闭'); | print('WebSocket 连接已关闭'); | ||||
print('Close code: ${_channel?.closeCode}'); | |||||
print('Close reason: ${_channel?.closeReason}'); | |||||
print('Close code: ${_channel.closeCode}'); | |||||
print('Close reason: ${_channel.closeReason}'); | |||||
}, | }, | ||||
cancelOnError: true, | cancelOnError: true, | ||||
); | ); | ||||
@@ -9,10 +9,12 @@ import audio_session | |||||
import audioplayers_darwin | import audioplayers_darwin | ||||
import path_provider_foundation | import path_provider_foundation | ||||
import record_darwin | import record_darwin | ||||
import shared_preferences_foundation | |||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { | func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { | ||||
AudioSessionPlugin.register(with: registry.registrar(forPlugin: "AudioSessionPlugin")) | AudioSessionPlugin.register(with: registry.registrar(forPlugin: "AudioSessionPlugin")) | ||||
AudioplayersDarwinPlugin.register(with: registry.registrar(forPlugin: "AudioplayersDarwinPlugin")) | AudioplayersDarwinPlugin.register(with: registry.registrar(forPlugin: "AudioplayersDarwinPlugin")) | ||||
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) | PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) | ||||
RecordPlugin.register(with: registry.registrar(forPlugin: "RecordPlugin")) | RecordPlugin.register(with: registry.registrar(forPlugin: "RecordPlugin")) | ||||
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) | |||||
} | } |
@@ -31,7 +31,8 @@ dependencies: | |||||
flutter: | flutter: | ||||
sdk: flutter | sdk: flutter | ||||
flutter_localizations: | |||||
sdk: flutter | |||||
# The following adds the Cupertino Icons font to your application. | # The following adds the Cupertino Icons font to your application. | ||||
# Use with the CupertinoIcons class for iOS style icons. | # Use with the CupertinoIcons class for iOS style icons. | ||||
cupertino_icons: ^1.0.8 | cupertino_icons: ^1.0.8 | ||||
@@ -40,13 +41,20 @@ dependencies: | |||||
logger: ^2.5.0 | logger: ^2.5.0 | ||||
path_provider: ^2.1.5 | path_provider: ^2.1.5 | ||||
record: ^5.2.0 | record: ^5.2.0 | ||||
intl: ^0.20.2 | |||||
intl: ^0.19.0 | |||||
web_socket_channel: ^3.0.2 | web_socket_channel: ^3.0.2 | ||||
http: ^1.3.0 | http: ^1.3.0 | ||||
get: ^4.6.6 | get: ^4.6.6 | ||||
json_annotation: ^4.9.0 | json_annotation: ^4.9.0 | ||||
audioplayers: ^6.1.1 | audioplayers: ^6.1.1 | ||||
audio_waveforms: ^1.2.0 | audio_waveforms: ^1.2.0 | ||||
gap: ^3.0.1 | |||||
flutter_gen_runner: ^5.9.0 | |||||
build_runner: ^2.4.13 | |||||
flutter_easyloading: ^3.0.5 | |||||
flutter_keyboard_visibility: ^6.0.0 | |||||
shared_preferences: ^2.5.1 | |||||
dio: ^5.8.0+1 | |||||
dev_dependencies: | dev_dependencies: | ||||
flutter_test: | flutter_test: | ||||
@@ -58,6 +66,7 @@ dev_dependencies: | |||||
# package. See that file for information about deactivating specific lint | # package. See that file for information about deactivating specific lint | ||||
# rules and activating additional ones. | # rules and activating additional ones. | ||||
flutter_lints: ^5.0.0 | flutter_lints: ^5.0.0 | ||||
intl_utils: ^2.8.5 | |||||
# For information on the generic Dart part of this file, see the | # For information on the generic Dart part of this file, see the | ||||
# following page: https://dart.dev/tools/pub/pubspec | # following page: https://dart.dev/tools/pub/pubspec | ||||
@@ -70,9 +79,9 @@ flutter: | |||||
# the material Icons class. | # the material Icons class. | ||||
uses-material-design: true | uses-material-design: true | ||||
# To add assets to your application, add an assets section, like this: | # To add assets to your application, add an assets section, like this: | ||||
# assets: | |||||
# - images/a_dot_burr.jpeg | |||||
# - images/a_dot_ham.jpeg | |||||
assets: | |||||
- assets/icon/ | |||||
- assets/img/ | |||||
# An image asset can refer to one or more resolution-specific "variants", see | # An image asset can refer to one or more resolution-specific "variants", see | ||||
# https://flutter.dev/to/resolution-aware-images | # https://flutter.dev/to/resolution-aware-images | ||||