import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:esse/utils/adaptive.dart'; import 'package:esse/utils/toast.dart'; import 'package:esse/utils/pick_image.dart'; import 'package:esse/utils/pick_file.dart'; import 'package:esse/l10n/localizations.dart'; import 'package:esse/widgets/emoji.dart'; import 'package:esse/widgets/shadow_dialog.dart'; import 'package:esse/widgets/audio_recorder.dart'; import 'package:esse/widgets/user_info.dart'; import 'package:esse/global.dart'; import 'package:esse/options.dart'; import 'package:esse/apps/chat/provider.dart'; import 'package:esse/apps/assistant/models.dart'; import 'package:esse/apps/assistant/provider.dart'; import 'package:esse/apps/assistant/message.dart'; import 'package:esse/apps/assistant/answer.dart'; class AssistantPage extends StatelessWidget { const AssistantPage({Key key}) : super(key: key); @override Widget build(BuildContext context) { return Scaffold( body: SafeArea( child: AssistantDetail(), )); } } class AssistantDetail extends StatefulWidget { const AssistantDetail({Key key}) : super(key: key); @override _AssistantDetailState createState() => _AssistantDetailState(); } class _AssistantDetailState extends State { TextEditingController textController = TextEditingController(); FocusNode textFocus = FocusNode(); bool emojiShow = false; bool sendShow = false; bool menuShow = false; bool recordShow = false; String _recordName; List answers; @override initState() { super.initState(); Future.delayed(Duration.zero, () async { Provider.of(context, listen: false).actived(); final options = context.read(); this.answers = await loadAnswers(options.locale); setState(() {}); }); textFocus.addListener(() { if (textFocus.hasFocus) { setState(() { emojiShow = false; menuShow = false; recordShow = false; }); } }); } @override void deactivate() { Provider.of(context, listen: false).inactived(); super.deactivate(); } _generateRecordPath() { this._recordName = DateTime.now().millisecondsSinceEpoch.toString() + '_assistant.m4a'; } void _sendMessage() async { if (textController.text.length < 1) { return; } final value = textController.text.trim(); final a_type = (value.endsWith('?') || value.endsWith('?')) ? MessageType.Answer : MessageType.String; context.read().create(a_type, textController.text); setState(() { textController.text = ''; textFocus.requestFocus(); emojiShow = false; sendShow = false; menuShow = false; recordShow = false; }); } void _selectEmoji(value) { textController.text += value; } void _sendImage() async { final image = await pickImage(); if (image != null) { context.read().create(MessageType.Image, image); } setState(() { textFocus.requestFocus(); emojiShow = false; sendShow = false; menuShow = false; recordShow = false; }); } void _sendFile() async { final file = await pickFile(); if (file != null) { context.read().create(MessageType.File, file); } setState(() { textFocus.requestFocus(); emojiShow = false; sendShow = false; menuShow = false; recordShow = false; }); } void _sendRecord(int time) async { final raw = Message.rawRecordName(time, _recordName); context.read().create(MessageType.Record, raw); setState(() { textFocus.requestFocus(); emojiShow = false; sendShow = false; menuShow = false; recordShow = false; }); } void _sendContact(ColorScheme color, AppLocalizations lang, friends) { showShadowDialog( context, Icons.person_rounded, 'Contact', Column(children: [ Container( height: 40.0, decoration: BoxDecoration( color: color.surface, borderRadius: BorderRadius.circular(15.0)), child: TextField( autofocus: false, textInputAction: TextInputAction.search, textAlignVertical: TextAlignVertical.center, style: TextStyle(fontSize: 14.0), onSubmitted: (value) { toast(context, 'WIP...'); }, decoration: InputDecoration( hintText: lang.search, hintStyle: TextStyle(color: color.onPrimary.withOpacity(0.5)), border: InputBorder.none, contentPadding: EdgeInsets.only(left: 15.0, right: 15.0, bottom: 15.0), ), ), ), SizedBox(height: 15.0), Column( children: friends.map((contact) { return GestureDetector( behavior: HitTestBehavior.opaque, onTap: () async { context.read().create(MessageType.Contact, "${contact.id}"); Navigator.of(context).pop(); setState(() { textFocus.requestFocus(); emojiShow = false; sendShow = false; menuShow = false; recordShow = false; }); }, child: Padding( padding: const EdgeInsets.symmetric( horizontal: 20.0, vertical: 14.0), child: Row( children: [ contact.showAvatar(), SizedBox(width: 15.0), Text(contact.name, style: TextStyle(fontSize: 16.0)), ], ), ), ); }).toList()) ])); } @override Widget build(BuildContext context) { final color = Theme.of(context).colorScheme; final lang = AppLocalizations.of(context); final isDesktop = isDisplayDesktop(context); final recentMessages = context.watch().messages; final recentMessageKeys = recentMessages.keys.toList().reversed.toList(); return Column( children: [ Container( padding: EdgeInsets.only(left: 20.0, right: 20.0, top: 10.0, bottom: 10.0), child: Row( children: [ if (!isDesktop) GestureDetector( onTap: () { Navigator.pop(context); }, child: Container( width: 20.0, child: Icon(Icons.arrow_back, color: color.primary)), ), SizedBox(width: 15.0), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('Jarvis', style: TextStyle(fontWeight: FontWeight.bold), ), SizedBox(height: 6.0), Text(lang.onlineActive, style: TextStyle(color: color.onPrimary.withOpacity(0.5), fontSize: 14.0)) ], ), ), SizedBox(width: 20.0), GestureDetector( onTap: () {}, child: Container( width: 20.0, child: Icon(Icons.phone_rounded, color: Color(0x26ADB0BB))), ), SizedBox(width: 20.0), GestureDetector( onTap: () {}, child: Container( width: 20.0, child: Icon(Icons.videocam_rounded, color: Color(0x26ADB0BB))), ), SizedBox(width: 20.0), // PopupMenuButton( // shape: RoundedRectangleBorder( // borderRadius: BorderRadius.circular(15) // ), // color: const Color(0xFFEDEDED), // child: Icon(Icons.more_vert_rounded, color: color.primary), // onSelected: (int value) { // if (value == 1) { // // TODO set top // } // }, // itemBuilder: (context) { // return >[ // _menuItem(Color(0xFF6174FF), 1, Icons.vertical_align_top_rounded, lang.cancelTop), // ]; // }, // ) ] ), ), const Divider(height: 1.0, color: Color(0x40ADB0BB)), Expanded( child: ListView.builder( padding: EdgeInsets.symmetric(horizontal: 20.0), itemCount: recentMessageKeys.length, reverse: true, itemBuilder: (BuildContext context, index) => AssistantMessage( name: 'Jarvis', message: recentMessages[recentMessageKeys[index]], answers: this.answers, ) )), Container( padding: const EdgeInsets.symmetric(horizontal: 20.0, vertical: 10.0), child: Row( children: [ GestureDetector( onTap: () async { if (recordShow) { recordShow = false; textFocus.requestFocus(); } else { _generateRecordPath(); setState(() { menuShow = false; emojiShow = false; recordShow = true; textFocus.unfocus(); }); } }, child: Container(width: 20.0, child: Icon(Icons.mic_rounded, color: color.primary)), ), SizedBox(width: 10.0), Expanded( child: Container( height: 40, decoration: BoxDecoration( color: color.surface, borderRadius: BorderRadius.circular(15.0), ), child: TextField( style: TextStyle(fontSize: 14.0), textInputAction: TextInputAction.send, onChanged: (value) { if (value.length == 0 && sendShow) { setState(() { sendShow = false; }); } else { if (!sendShow) { setState(() { sendShow = true; }); } } }, onSubmitted: (_v) => _sendMessage(), decoration: InputDecoration( hintText: 'Aa', border: InputBorder.none, contentPadding: EdgeInsets.only( left: 15.0, right: 15.0, bottom: 7.0), ), controller: textController, focusNode: textFocus, ), ), ), SizedBox(width: 10.0), GestureDetector( onTap: () { if (emojiShow) { textFocus.requestFocus(); } else { setState(() { menuShow = false; recordShow = false; emojiShow = true; textFocus.unfocus(); }); } }, child: Container( width: 20.0, child: Icon( emojiShow ? Icons.keyboard_rounded : Icons.emoji_emotions_rounded, color: color.primary)), ), SizedBox(width: 10.0), sendShow ? GestureDetector( onTap: _sendMessage, child: Container( width: 50.0, height: 30.0, decoration: BoxDecoration( color: Color(0xFF6174FF), borderRadius: BorderRadius.circular(10.0), ), child: Center( child: Icon(Icons.send, color: Colors.white, size: 20.0))), ) : GestureDetector( onTap: () { if (menuShow) { textFocus.requestFocus(); } else { setState(() { emojiShow = false; recordShow = false; menuShow = true; textFocus.unfocus(); }); } }, child: Container( width: 20.0, child: Icon(Icons.add_circle_rounded, color: color.primary)), ), ], ), ), if (emojiShow) Emoji(action: _selectEmoji), if (recordShow) Container( height: 100.0, child: AudioRecorder( path: Global.recordPath + _recordName, onStop: _sendRecord), ), if (menuShow) Container( height: 100.0, child: Wrap( spacing: 20.0, runSpacing: 20.0, alignment: WrapAlignment.center, children: [ ExtensionButton( icon: Icons.image_rounded, text: lang.album, action: _sendImage, bgColor: color.surface, iconColor: color.primary), ExtensionButton( icon: Icons.folder_rounded, text: lang.file, action: _sendFile, bgColor: color.surface, iconColor: color.primary), ExtensionButton( icon: Icons.person_rounded, text: lang.contact, action: () => _sendContact(color, lang, context.read().friends.values), bgColor: color.surface, iconColor: color.primary), ], ), ) ], ); } } class ExtensionButton extends StatelessWidget { final String text; final IconData icon; final Function action; final Color bgColor; final Color iconColor; const ExtensionButton({ Key key, this.icon, this.text, this.action, this.bgColor, this.iconColor, }) : super(key: key); @override Widget build(BuildContext context) { return GestureDetector( onTap: action, child: Column( crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center, children: [ Container( padding: const EdgeInsets.all(10.0), decoration: BoxDecoration( color: bgColor, borderRadius: BorderRadius.circular(15.0), ), child: Icon(icon, color: iconColor, size: 36.0)), SizedBox(height: 5.0), Text(text, style: TextStyle(fontSize: 14.0)), ], )); } } Widget _menuItem(Color color, int value, IconData icon, String text) { return PopupMenuItem( value: value, child: Row( children: [ Icon(icon, color: color), Padding( padding: const EdgeInsets.only(left: 20.0, right: 10.0), child: Text(text, style: TextStyle(color: Colors.black, fontSize: 16.0)), ) ] ), ); }