diff --git a/Cargo.toml b/Cargo.toml index e41986b..f9462f4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,6 +37,7 @@ sysinfo = "0.16" tdn = { git = "https://github.com/cypherlink/TDN", branch="main", default-features = false, features = ["full"] } tdn-did = { git = "https://github.com/cypherlink/tdn-did", branch="main" } tdn-storage = { git = "https://github.com/cypherlink/tdn-storage", branch="main" } +group-chat-types = { git = "https://github.com/cympletech/group-chat", branch="main" } [target.'cfg(target_os="android")'.dependencies] jni = { version = "0.19", default-features = false } \ No newline at end of file diff --git a/lib/apps/chat/list.dart b/lib/apps/chat/list.dart index 07c523a..955d814 100644 --- a/lib/apps/chat/list.dart +++ b/lib/apps/chat/list.dart @@ -8,6 +8,7 @@ import 'package:esse/provider.dart'; import 'package:esse/apps/chat/provider.dart'; import 'package:esse/apps/chat/models.dart'; import 'package:esse/apps/chat/detail.dart'; +import 'package:esse/apps/chat/add.dart'; class ChatList extends StatefulWidget { const ChatList({Key key}) : super(key: key); @@ -20,12 +21,27 @@ class _ChatListState extends State { @override Widget build(BuildContext context) { final provider = context.watch(); + final isDesktop = isDisplayDesktop(context); final friends = provider.friends; final chatKeys = provider.orderKeys; - return ListView.builder( - itemCount: chatKeys.length, - itemBuilder: (BuildContext ctx, int index) => ListChat(friend: friends[chatKeys[index]]), + return Scaffold( + body: ListView.builder( + itemCount: chatKeys.length, + itemBuilder: (BuildContext ctx, int index) => ListChat(friend: friends[chatKeys[index]]), + ), + floatingActionButton: FloatingActionButton( + onPressed: () { + final widget = ChatAddPage(); + if (isDesktop) { + Provider.of(context, listen: false).updateActivedApp(widget); + } else { + Navigator.push(context, MaterialPageRoute(builder: (_) => widget)); + } + }, + child: const Icon(Icons.add, color: Colors.white), + backgroundColor: Color(0xFF6174FF), + ), ); } } diff --git a/lib/apps/group_chat/add.dart b/lib/apps/group_chat/add.dart new file mode 100644 index 0000000..fd54ad4 --- /dev/null +++ b/lib/apps/group_chat/add.dart @@ -0,0 +1,669 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:provider/provider.dart'; + +import 'package:esse/l10n/localizations.dart'; +import 'package:esse/utils/adaptive.dart'; +import 'package:esse/utils/better_print.dart'; +import 'package:esse/widgets/button_text.dart'; +import 'package:esse/widgets/input_text.dart'; +import 'package:esse/widgets/user_info.dart'; +import 'package:esse/widgets/shadow_button.dart'; +import 'package:esse/widgets/shadow_dialog.dart'; +import 'package:esse/widgets/qr_scan.dart'; +import 'package:esse/global.dart'; +import 'package:esse/provider.dart'; + +import 'package:esse/apps/chat/models.dart'; +import 'package:esse/apps/chat/provider.dart'; + +class GroupAddPage extends StatefulWidget { + final String id; + final String addr; + final String name; + + GroupAddPage({Key key, this.id = '', this.addr = '', this.name = ''}) : super(key: key); + + @override + _GroupAddPageState createState() => _GroupAddPageState(); +} + +class _GroupAddPageState extends State { + TextEditingController _joinIdController = TextEditingController(); + TextEditingController _joinAddrController = TextEditingController(); + TextEditingController _joinRemarkController = TextEditingController(); + TextEditingController _joinNameController = TextEditingController(); + FocusNode _joinIdFocus = FocusNode(); + FocusNode _joinAddrFocus = FocusNode(); + FocusNode _joinRemarkFocus = FocusNode(); + + TextEditingController _createAddrController = TextEditingController(); + TextEditingController _createNameController = TextEditingController(); + TextEditingController _createBioController = TextEditingController(); + TextEditingController _createKeyController = TextEditingController(); + FocusNode _createAddrFocus = FocusNode(); + FocusNode _createNameFocus = FocusNode(); + FocusNode _createBioFocus = FocusNode(); + FocusNode _createKeyFocus = FocusNode(); + int _groupType = 0; + bool _groupNeedAgree = false; + bool _groupHasKey = true; + bool _groupHasNeedAgree = true; + + // 0 => encrypted, 1 => common, 2 => open. + Widget _groupTypeWidget(String text, int value, ColorScheme color) { + return Row( + children: [ + Radio( + value: value, + groupValue: _groupType, + onChanged: (n) => setState(() { + _groupType = n; + if (n == 0) { + _groupHasKey = true; + } else { + _groupHasKey = false; + } + + if (n == 2) { + _groupHasNeedAgree = false; + } else { + _groupHasNeedAgree = true; + } + }), + ), + _groupType == value + ? Text(text, style: TextStyle(color: color.primary)) + : Text(text), + ] + ); + } + + _checkAddrPermission() { + // + } + + _scanCallback(bool isOk, String app, List params) { + Navigator.of(context).pop(); + if (isOk && app == 'add-group' && params.length == 3) { + this._joinIdController.text = params[0]; + this._joinAddrController.text = params[1]; + this._joinNameController.text = params[2]; + setState(() {}); + } + } + + _join() { + var id = _joinIdController.text; + if (id == '' || id == null) { + return; + } + + if (id.substring(0, 2) == 'EH') { + id = id.substring(2); + } + + var addr = _joinAddrController.text; + // if has 0x, need remove + if (addr.substring(0, 2) == '0x') { + addr = addr.substring(2); + } + var name = _joinNameController.text; + var remark = _joinRemarkController.text; + + context.read().requestCreate(Request(id, addr, name, remark)); + setState(() { + _joinIdController.text = ''; + _joinAddrController.text = ''; + _joinNameController.text = ''; + _joinRemarkController.text = ''; + }); + } + + _create() { + // + } + + @override + void initState() { + super.initState(); + _joinIdController.text = widget.id; + _joinAddrController.text = widget.addr; + _joinNameController.text = widget.name; + + _joinIdFocus.addListener(() { + setState(() {}); + }); + _joinAddrFocus.addListener(() { + setState(() {}); + }); + _joinRemarkFocus.addListener(() { + setState(() {}); + }); + _createAddrFocus.addListener(() { + setState(() {}); + }); + _createNameFocus.addListener(() { + setState(() {}); + }); + _createBioFocus.addListener(() { + setState(() {}); + }); + _createKeyFocus.addListener(() { + setState(() {}); + }); + new Future.delayed(Duration.zero, () { + //context.read().requestList(); + }); + } + + @override + Widget build(BuildContext context) { + final isDesktop = isDisplayDesktop(context); + final color = Theme.of(context).colorScheme; + final lang = AppLocalizations.of(context); + final provider = context.watch(); + final requests = provider.requests; + + final account = context.read().activedAccount; + + final requestKeys = requests.keys.toList().reversed.toList(); // it had sorted. + + return SafeArea( + child: DefaultTabController( + initialIndex: 0, + length: 2, + child: Scaffold( + appBar: AppBar( + title: Row( + children: [ + if (!isDesktop) + GestureDetector( + onTap: () { + context.read().requestClear(); + Navigator.pop(context); + }, + child: Container( + width: 20.0, + child: Icon(Icons.arrow_back, color: color.primary)), + ), + SizedBox(width: 15.0), + Expanded( + child: Text('Add Group Chat', + style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20.0)), + ), + TextButton( + onPressed: () => Navigator.push( + context, + MaterialPageRoute(builder: (context) => QRScan(callback: _scanCallback)) + ), + child: Text(lang.scanQr, style: TextStyle(fontSize: 16.0)), + ), + ], + ), + bottom: TabBar( + tabs: [ + Tab( + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.add_box_rounded, color: color.primary), + const SizedBox(width: 8.0), + Text('Join A Group', style: TextStyle(color: color.primary)) + ]) + ), + Tab( + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.create_rounded, color: color.primary), + const SizedBox(width: 8.0), + Text('Create A Group', style: TextStyle(color: color.primary)) + ]) + ), + ], + ), + ), + body: TabBarView( + children: [ + Container( + padding: const EdgeInsets.all(20), + child: SingleChildScrollView( + child: Form( + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + InputText( + icon: Icons.person, + text: 'Group ID', + controller: _joinIdController, + focus: _joinIdFocus), + + const SizedBox(height: 20.0), + InputText( + icon: Icons.location_on, + text: lang.address, + controller: _joinAddrController, + focus: _joinAddrFocus), + const SizedBox(height: 20.0), + InputText( + icon: Icons.turned_in, + text: lang.remark, + controller: _joinRemarkController, + focus: _joinRemarkFocus), + const SizedBox(height: 20.0), + ButtonText(action: _join, text: lang.send, width: 600.0), + const SizedBox(height: 20.0), + const Divider(height: 1.0, color: Color(0x40ADB0BB)), + const SizedBox(height: 10.0), + if (requests.isNotEmpty) + Container( + width: 600.0, + child: ListView.builder( + itemCount: requestKeys.length, + shrinkWrap: true, + physics: ClampingScrollPhysics(), + scrollDirection: Axis.vertical, + itemBuilder: (BuildContext context, int index) => + _RequestItem(request: requests[requestKeys[index]]), + ), + ) + ], + ), + ), + ), + ), + Container( + padding: const EdgeInsets.all(20), + child: SingleChildScrollView( + child: Form( + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + height: 50.0, + width: 600.0, + child: Row( + children: [ + Expanded( + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 20.0), + decoration: BoxDecoration( + color: color.surface, + border: Border.all(color: _createAddrFocus.hasFocus + ? color.primary : color.surface), + borderRadius: BorderRadius.circular(15.0), + ), + child: TextField( + style: TextStyle(fontSize: 16.0), + decoration: InputDecoration( + border: InputBorder.none, + hintText: lang.address), + controller: _createAddrController, + focusNode: _createAddrFocus, + onSubmitted: (_v) => _checkAddrPermission(), + onChanged: (v) { + if (v.length > 0) { + setState(() { + //this._addrChecked = true; + }); + } + }), + ), + ), + //if (this._addrOnline) + Container( + padding: const EdgeInsets.only(left: 8.0), + child: Icon(Icons.cloud_done_rounded, + color: Colors.green), + ), + const SizedBox(width: 8.0), + Container( + width: 100.0, + child: InkWell( + //onTap: this._addrChecked ? _checkAddrOnline : null, + child: Container( + height: 45.0, + decoration: BoxDecoration( + color: Color(0xFF6174FF), + borderRadius: BorderRadius.circular(15.0)), + child: Center( + child: Text(lang.search, + style: TextStyle(fontSize: 16.0, color: Colors.white))), + ))), + ])), + const SizedBox(height: 8.0), + Text('Error Message here', style: TextStyle(fontSize: 14.0, color: Colors.red)), + Container( + width: 600.0, + padding: const EdgeInsets.all(10.0), + alignment: Alignment.centerLeft, + child: Text('Group Info', textAlign: TextAlign.left, style: TextStyle(fontSize: 20.0, fontWeight: FontWeight.bold)), + ), + Container( + width: 100.0, + height: 100.0, + margin: const EdgeInsets.symmetric(vertical: 10.0), + decoration: BoxDecoration( + color: color.surface, + borderRadius: BorderRadius.circular(15.0)), + child: Stack( + alignment: Alignment.center, + children: [ + Icon(Icons.camera_alt, + size: 47.0, color: Color(0xFFADB0BB)), + Positioned( + bottom: -1.0, + right: -1.0, + child: InkWell( + child: Icon(Icons.add_circle, + size: 32.0, color: color.primary), + onTap: null, //() => _getImage(context, account.name, color, lang), + ) + ), + ], + ), + ), + Container( + padding: EdgeInsets.symmetric(vertical: 10.0), + width: 600.0, + child: Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + _groupTypeWidget('Encrypted', 0, color), + _groupTypeWidget('Common', 1, color), + _groupTypeWidget('Open', 2, color), + ] + ) + ), + Container( + padding: EdgeInsets.symmetric(vertical: 10.0), + child: InputText( + icon: Icons.person, + text: 'Group Name', + controller: _createNameController, + focus: _createNameFocus), + ), + Container( + padding: EdgeInsets.symmetric(vertical: 10.0), + child: InputText( + icon: Icons.location_on, + text: 'Group Bio', + controller: _createBioController, + focus: _createBioFocus), + ), + if (_groupHasKey) + Container( + padding: EdgeInsets.symmetric(vertical: 10.0), + child: InputText( + icon: Icons.turned_in, + text: 'Encrypted Key', + controller: _createKeyController, + focus: _createKeyFocus), + ), + if (_groupHasNeedAgree) + Container( + height: 50.0, + width: 600.0, + child: Row( + children: [ + Switch( + value: _groupNeedAgree, + onChanged: (value) { + setState(() { + _groupNeedAgree = value; + }); + }, + ), + Text('Need Group Manager Agree.') + ] + ), + ), + const SizedBox(height: 20.0), + ButtonText(action: _create, text: lang.create, width: 600.0), + const SizedBox(height: 20.0), + const Divider(height: 1.0, color: Color(0x40ADB0BB)), + const SizedBox(height: 10.0), + if (requests.isNotEmpty) + Container( + width: 600.0, + child: ListView.builder( + itemCount: requestKeys.length, + shrinkWrap: true, + physics: ClampingScrollPhysics(), + scrollDirection: Axis.vertical, + itemBuilder: (BuildContext context, int index) => + _RequestItem(request: requests[requestKeys[index]]), + ), + ) + ], + ), + ), + ), + ), + ], + ))) + ); + } +} + +class _RequestItem extends StatelessWidget { + final Request request; + + const _RequestItem({Key key, this.request}) : super(key: key); + + Widget _infoList(icon, color, text) { + return Container( + width: 300.0, + padding: const EdgeInsets.symmetric(vertical: 10.0), + child: Row( + children: [ + Icon(icon, size: 20.0, color: color), + const SizedBox(width: 20.0), + Expanded(child: Text(text)), + ] + ), + ); + } + + Widget _infoListTooltip(icon, color, text) { + return Container( + width: 300.0, + padding: const EdgeInsets.symmetric(vertical: 10.0), + child: Row( + children: [ + Icon(icon, size: 20.0, color: color), + const SizedBox(width: 20.0), + Expanded( + child: Tooltip( + message: text, + child: Text(betterPrint(text)), + ) + ) + ] + ), + ); + } + + Widget _info(color, lang, context) { + return Column( + mainAxisSize: MainAxisSize.max, + children: [ + request.showAvatar(100.0), + const SizedBox(height: 10.0), + Text(request.name), + const SizedBox(height: 10.0), + const Divider(height: 1.0, color: Color(0x40ADB0BB)), + const SizedBox(height: 10.0), + _infoListTooltip(Icons.person, color.primary, 'EH' + request.gid.toUpperCase()), + _infoListTooltip(Icons.location_on, color.primary, "0x" + request.addr), + _infoList(Icons.turned_in, color.primary, request.remark), + _infoList(Icons.access_time_rounded, color.primary, request.time.toString()), + const SizedBox(height: 10.0), + if (request.over) + InkWell( + onTap: () { + Navigator.pop(context); + Provider.of(context, listen: false).requestDelete(request.id); + }, + hoverColor: Colors.transparent, + child: Container( + width: 300.0, + padding: const EdgeInsets.symmetric(vertical: 10.0), + decoration: BoxDecoration( + border: Border.all(color: color.primary), + borderRadius: BorderRadius.circular(10.0)), + child: Center(child: Text(lang.ignore, + style: TextStyle(fontSize: 14.0))), + ) + ), + if (!request.over && !request.isMe) + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + InkWell( + onTap: () { + Navigator.pop(context); + Provider.of(context, listen: false).requestReject(request.id); + }, + hoverColor: Colors.transparent, + child: Container( + width: 100.0, + padding: const EdgeInsets.symmetric(vertical: 10.0), + decoration: BoxDecoration( + border: Border.all(), + borderRadius: BorderRadius.circular(10.0)), + child: Center(child: Text(lang.reject, + style: TextStyle(fontSize: 14.0))), + ) + ), + InkWell( + onTap: () { + Navigator.pop(context); + Provider.of(context, listen: false).requestAgree(request.id); + }, + hoverColor: Colors.transparent, + child: Container( + width: 100.0, + padding: const EdgeInsets.symmetric(vertical: 10.0), + decoration: BoxDecoration( + border: Border.all(color: color.primary), + borderRadius: BorderRadius.circular(10.0)), + child: Center(child: Text(lang.agree, + style: TextStyle(fontSize: 14.0, color: color.primary))), + ) + ), + ] + ), + if (!request.over && request.isMe) + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + InkWell( + onTap: () { + Navigator.pop(context); + Provider.of(context, listen: false).requestDelete(request.id); + }, + hoverColor: Colors.transparent, + child: Container( + width: 100.0, + padding: const EdgeInsets.symmetric(vertical: 10.0), + decoration: BoxDecoration( + border: Border.all(), + borderRadius: BorderRadius.circular(10.0)), + child: Center(child: Text(lang.ignore, + style: TextStyle(fontSize: 14.0))), + ) + ), + InkWell( + onTap: () { + Navigator.pop(context); + Provider.of(context, listen: false).requestCreate(request); + }, + hoverColor: Colors.transparent, + child: Container( + width: 100.0, + padding: const EdgeInsets.symmetric(vertical: 10.0), + decoration: BoxDecoration( + border: Border.all(color: color.primary), + borderRadius: BorderRadius.circular(10.0)), + child: Center(child: Text(lang.resend, + style: TextStyle(fontSize: 14.0, color: color.primary))), + ) + ), + ] + ) + ] + ); + } + + @override + Widget build(BuildContext context) { + final color = Theme.of(context).colorScheme; + final lang = AppLocalizations.of(context); + + return GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () => showShadowDialog(context, Icons.info, lang.info, _info(color, lang, context)), + child: SizedBox( + height: 55.0, + child: Row( + children: [ + Container( + width: 45.0, + height: 45.0, + margin: const EdgeInsets.only(right: 15.0), + child: request.showAvatar(), + ), + Expanded( + child: Container( + height: 55.0, + child: Row( + children: [ + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(request.name, maxLines: 1, overflow: TextOverflow.ellipsis, + style: TextStyle(fontSize: 16.0)), + Text(request.remark, maxLines: 1, overflow: TextOverflow.ellipsis, + style: TextStyle(color: Color(0xFFADB0BB), + fontSize: 12.0)), + ], + ), + ), + SizedBox(width: 10.0), + if (request.over || request.isMe) + Container( + child: Text( + request.ok ? lang.added : (request.over ? lang.rejected : lang.sended), + style: TextStyle(color: Color(0xFFADB0BB), fontSize: 14.0), + )), + if (!request.over && !request.isMe) + InkWell( + onTap: () => context.read().requestAgree(request.id), + hoverColor: Colors.transparent, + child: Container( + height: 35.0, + padding: const EdgeInsets.symmetric(horizontal: 10.0), + decoration: BoxDecoration( + border: Border.all(color: color.primary), + borderRadius: BorderRadius.circular(10.0)), + child: Center(child: Text(lang.agree, + style: TextStyle(fontSize: 14.0, color: color.primary))), + ) + ), + ] + ) + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/apps/group_chat/models.dart b/lib/apps/group_chat/models.dart new file mode 100644 index 0000000..e69de29 diff --git a/lib/apps/group_chat/page.dart b/lib/apps/group_chat/page.dart new file mode 100644 index 0000000..0b5c350 --- /dev/null +++ b/lib/apps/group_chat/page.dart @@ -0,0 +1,38 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +import 'package:esse/utils/adaptive.dart'; +import 'package:esse/utils/file_image.dart'; +import 'package:esse/l10n/localizations.dart'; +import 'package:esse/provider.dart'; + +import 'package:esse/apps/group_chat/add.dart'; + +class GroupChatList extends StatefulWidget { + @override + _GroupChatListState createState() => _GroupChatListState(); +} + +class _GroupChatListState extends State { + @override + Widget build(BuildContext context) { + final color = Theme.of(context).colorScheme; + final isDesktop = isDisplayDesktop(context); + + return Scaffold( + body: const Center(child: Text('TODO group list!')), + floatingActionButton: FloatingActionButton( + onPressed: () { + final widget = GroupAddPage(); + if (isDesktop) { + Provider.of(context, listen: false).updateActivedApp(widget); + } else { + Navigator.push(context, MaterialPageRoute(builder: (_) => widget)); + } + }, + child: const Icon(Icons.add, color: Colors.white), + backgroundColor: Color(0xFF6174FF), + ), + ); + } +} diff --git a/lib/apps/group_chat/provider.dart b/lib/apps/group_chat/provider.dart new file mode 100644 index 0000000..e69de29 diff --git a/lib/apps/service/list.dart b/lib/apps/service/list.dart index 4b7db4f..6f92bca 100644 --- a/lib/apps/service/list.dart +++ b/lib/apps/service/list.dart @@ -14,6 +14,7 @@ import 'package:esse/apps/file/page.dart'; const List INNER_SERVICES = [ InnerService.Files, InnerService.Assistant, + InnerService.GroupChat, ]; class ServiceList extends StatefulWidget { diff --git a/lib/apps/service/models.dart b/lib/apps/service/models.dart index e945e87..509e009 100644 --- a/lib/apps/service/models.dart +++ b/lib/apps/service/models.dart @@ -6,10 +6,13 @@ import 'package:esse/provider.dart'; import 'package:esse/apps/assistant/page.dart'; import 'package:esse/apps/file/page.dart'; +import 'package:esse/apps/group_chat/page.dart'; + enum InnerService { Files, Assistant, + GroupChat, } extension InnerServiceExtension on InnerService { @@ -19,6 +22,8 @@ extension InnerServiceExtension on InnerService { return [lang.files, lang.filesBio, 'assets/logo/logo_files.png']; case InnerService.Assistant: return [lang.assistant, lang.assistantBio, 'assets/logo/logo_assistant.png']; + case InnerService.GroupChat: + return [lang.groupChat, lang.groupChatBio, 'assets/logo/logo_assistant.png']; } } @@ -36,6 +41,10 @@ extension InnerServiceExtension on InnerService { case InnerService.Assistant: coreWidget = AssistantDetail(); break; + case InnerService.GroupChat: + listTitle = lang.groupChat; + listHome = GroupChatList(); + break; } Provider.of(context, listen: false).updateActivedApp(coreWidget, listTitle, listHome); } else { @@ -46,6 +55,9 @@ extension InnerServiceExtension on InnerService { case InnerService.Assistant: Navigator.push(context, MaterialPageRoute(builder: (_) => AssistantPage())); break; + case InnerService.GroupChat: + Provider.of(context, listen: false).updateActivedApp(null, lang.groupChat, GroupChatList()); + break; } } } diff --git a/lib/l10n/localizations.dart b/lib/l10n/localizations.dart index 933f15a..febad78 100644 --- a/lib/l10n/localizations.dart +++ b/lib/l10n/localizations.dart @@ -77,6 +77,7 @@ abstract class AppLocalizations { String get delete; String get open; String get unknown; + String get create; // theme String get themeDark; @@ -159,6 +160,8 @@ abstract class AppLocalizations { String get filesBio; String get assistant; String get assistantBio; + String get groupChat; + String get groupChatBio; } class _AppLocalizationsDelegate diff --git a/lib/l10n/localizations_en.dart b/lib/l10n/localizations_en.dart index a8500a8..faf8fd7 100644 --- a/lib/l10n/localizations_en.dart +++ b/lib/l10n/localizations_en.dart @@ -78,6 +78,8 @@ class AppLocalizationsEn extends AppLocalizations { String get open => 'Open'; @override String get unknown => 'Unknown'; + @override + String get create => 'Create'; // theme @override @@ -228,4 +230,8 @@ class AppLocalizationsEn extends AppLocalizations { String get assistant => 'Jarvis'; @override String get assistantBio => 'Jarvis is a robot, only belongs to you.'; + @override + String get groupChat => 'Group Chats'; + @override + String get groupChatBio => 'Multiple group chats'; } diff --git a/lib/l10n/localizations_zh.dart b/lib/l10n/localizations_zh.dart index 6770547..3a004fb 100644 --- a/lib/l10n/localizations_zh.dart +++ b/lib/l10n/localizations_zh.dart @@ -78,6 +78,8 @@ class AppLocalizationsZh extends AppLocalizations { String get open => '打开'; @override String get unknown => '未知'; + @override + String get create => '创建'; // theme @override @@ -228,4 +230,8 @@ class AppLocalizationsZh extends AppLocalizations { String get assistant => 'Jarvis'; @override String get assistantBio => 'Jarvis 是个机器人,只属于你。'; + @override + String get groupChat => '群聊'; + @override + String get groupChatBio => '各种各样的群聊'; } diff --git a/lib/widgets/input_text.dart b/lib/widgets/input_text.dart index fd3b79a..b79a55b 100644 --- a/lib/widgets/input_text.dart +++ b/lib/widgets/input_text.dart @@ -16,6 +16,7 @@ class InputText extends StatelessWidget { return Container( padding: const EdgeInsets.symmetric(horizontal: 20.0), height: 50.0, + width: 600.0, decoration: BoxDecoration( color: color.surface, border: Border.all(color: focus.hasFocus ? color.primary : color.surface), diff --git a/src/apps/group_chat/mod.rs b/src/apps/group_chat/mod.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/apps/group_chat/models.rs b/src/apps/group_chat/models.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/apps/group_chat/rpc.rs b/src/apps/group_chat/rpc.rs new file mode 100644 index 0000000..e69de29