update multi chat,fix provider

This commit is contained in:
csf 2022-03-25 16:34:27 +08:00
parent 4d64fee76d
commit 2ea9d80be6
7 changed files with 190 additions and 119 deletions

View File

@ -21,23 +21,13 @@ class App extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final analytics = FirebaseAnalytics(); final analytics = FirebaseAnalytics();
final providers = [
ChangeNotifierProvider.value(value: FFI.ffiModel),
ChangeNotifierProvider.value(value: FFI.imageModel),
ChangeNotifierProvider.value(value: FFI.cursorModel),
ChangeNotifierProvider.value(value: FFI.canvasModel),
];
if (!isWeb) {
providers.addAll([
ChangeNotifierProvider.value(value: FFI.chatModel),
ChangeNotifierProvider.value(value: FFI.fileModel),
]);
if (isAndroid) {
providers.add(ChangeNotifierProvider.value(value: FFI.serverModel));
}
}
return MultiProvider( return MultiProvider(
providers: providers, providers: [
ChangeNotifierProvider.value(value: FFI.ffiModel),
ChangeNotifierProvider.value(value: FFI.imageModel),
ChangeNotifierProvider.value(value: FFI.cursorModel),
ChangeNotifierProvider.value(value: FFI.canvasModel),
],
child: MaterialApp( child: MaterialApp(
navigatorKey: globalKey, navigatorKey: globalKey,
debugShowCheckedModeBanner: false, debugShowCheckedModeBanner: false,

View File

@ -17,14 +17,24 @@ class ChatModel with ChangeNotifier {
final ChatUser me = ChatUser( final ChatUser me = ChatUser(
uid:"", uid:"",
name: "me", name: "me",
customProperties: Map()..["id"] = clientModeID
); );
final _scroller = ScrollController();
var _currentID = clientModeID; var _currentID = clientModeID;
get messages => _messages; ScrollController get scroller => _scroller;
get currentID => _currentID; Map<int, List<ChatMessage>> get messages => _messages;
int get currentID => _currentID;
changeCurrentID(int id){
if(_messages.containsKey(id)){
_currentID = id;
notifyListeners();
}
}
receive(int id, String text) { receive(int id, String text) {
if (text.isEmpty) return; if (text.isEmpty) return;
@ -32,18 +42,36 @@ class ChatModel with ChangeNotifier {
if (iconOverlayEntry == null) { if (iconOverlayEntry == null) {
showChatIconOverlay(); showChatIconOverlay();
} }
late final chatUser;
if(id == clientModeID){
chatUser = ChatUser(
name: FFI.ffiModel.pi.username,
uid: FFI.getId(),
);
}else{
chatUser = FFI.serverModel.clients[id]?.chatUser;
}
if(chatUser == null){
return debugPrint("Failed to receive msg,user doesn't exist");
}
if(!_messages.containsKey(id)){ if(!_messages.containsKey(id)){
_messages[id] = []; _messages[id] = [];
} }
// TODO peer info _messages[id]!.add(ChatMessage(
_messages[id]?.add(ChatMessage(
text: text, text: text,
user: ChatUser( user: chatUser));
name: FFI.ffiModel.pi.username,
uid: FFI.getId(),
)));
_currentID = id; _currentID = id;
notifyListeners(); notifyListeners();
scrollToBottom();
}
scrollToBottom(){
Future.delayed(Duration(milliseconds: 500), () {
_scroller.animateTo(
_scroller.position.maxScrollExtent,
duration: Duration(milliseconds: 200),
curve: Curves.fastLinearToSlowEaseIn);
});
} }
send(ChatMessage message) { send(ChatMessage message) {
@ -59,13 +87,12 @@ class ChatModel with ChangeNotifier {
} }
} }
notifyListeners(); notifyListeners();
scrollToBottom();
} }
release() { close() {
hideChatIconOverlay(); hideChatIconOverlay();
hideChatWindowOverlay(); hideChatWindowOverlay();
_messages.forEach((key, value) => value.clear());
_messages.clear();
notifyListeners(); notifyListeners();
} }
} }

View File

@ -722,7 +722,7 @@ class FFI {
} }
static void close() { static void close() {
chatModel.release(); chatModel.close();
if (FFI.imageModel.image != null && !isDesktop) { if (FFI.imageModel.image != null && !isDesktop) {
savePreference(id, cursorModel.x, cursorModel.y, canvasModel.x, savePreference(id, cursorModel.x, cursorModel.y, canvasModel.x,
canvasModel.y, canvasModel.scale, ffiModel.pi.currentDisplay); canvasModel.y, canvasModel.scale, ffiModel.pi.currentDisplay);

View File

@ -1,5 +1,6 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'package:dash_chat/dash_chat.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../common.dart'; import '../common.dart';
import '../pages/server_page.dart'; import '../pages/server_page.dart';
@ -17,7 +18,7 @@ class ServerModel with ChangeNotifier {
final _serverId = TextEditingController(text: _emptyIdShow); final _serverId = TextEditingController(text: _emptyIdShow);
final _serverPasswd = TextEditingController(text: ""); final _serverPasswd = TextEditingController(text: "");
List<Client> _clients = []; Map<int,Client> _clients = {};
bool get isStart => _isStart; bool get isStart => _isStart;
@ -33,7 +34,7 @@ class ServerModel with ChangeNotifier {
TextEditingController get serverPasswd => _serverPasswd; TextEditingController get serverPasswd => _serverPasswd;
List<Client> get clients => _clients; Map<int,Client> get clients => _clients;
ServerModel() { ServerModel() {
()async{ ()async{
@ -191,9 +192,11 @@ class ServerModel with ChangeNotifier {
var res = FFI.getByName("clients_state"); var res = FFI.getByName("clients_state");
try { try {
final List clientsJson = jsonDecode(res); final List clientsJson = jsonDecode(res);
_clients = clientsJson for (var clientJson in clientsJson){
.map((clientJson) => Client.fromJson(jsonDecode(res))) final client = Client.fromJson(jsonDecode(clientJson));
.toList(); _clients[client.id] = client;
}
notifyListeners(); notifyListeners();
} catch (e) {} } catch (e) {}
} }
@ -204,7 +207,7 @@ class ServerModel with ChangeNotifier {
final Map<String, dynamic> response = Map(); final Map<String, dynamic> response = Map();
response["id"] = client.id; response["id"] = client.id;
DialogManager.show((setState, close) => CustomAlertDialog( DialogManager.show((setState, close) => CustomAlertDialog(
title: Text(client.isFileTransfer?"File":"Screen" + "Control Request"), title: Text(translate(client.isFileTransfer?"File Connection":"Screen Connection")),
content: Column( content: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
@ -234,7 +237,7 @@ class ServerModel with ChangeNotifier {
"start_capture"); // to Android service "start_capture"); // to Android service
debugPrint("_toAndroidStartCapture:$res"); debugPrint("_toAndroidStartCapture:$res");
} }
_clients.add(client); _clients[client.id] = client;
notifyListeners(); notifyListeners();
close(); close();
}), }),
@ -246,7 +249,8 @@ class ServerModel with ChangeNotifier {
void onClientAuthorized(Map<String, dynamic> evt) { void onClientAuthorized(Map<String, dynamic> evt) {
try{ try{
_clients.add(Client.fromJson(jsonDecode(evt['client']))); final client = Client.fromJson(jsonDecode(evt['client']));
_clients[client.id] = client;
notifyListeners(); notifyListeners();
}catch(e){ }catch(e){
@ -256,8 +260,7 @@ class ServerModel with ChangeNotifier {
void onClientRemove(Map<String, dynamic> evt) { void onClientRemove(Map<String, dynamic> evt) {
try { try {
final id = int.parse(evt['id'] as String); final id = int.parse(evt['id'] as String);
Client client = _clients.singleWhere((c) => c.id == id); _clients.remove(id);
_clients.remove(client);
notifyListeners(); notifyListeners();
} catch (e) { } catch (e) {
// singleWhere fail ,reset the login dialog // singleWhere fail ,reset the login dialog
@ -267,10 +270,10 @@ class ServerModel with ChangeNotifier {
} }
closeAll() { closeAll() {
_clients.forEach((client) { _clients.forEach((id,client) {
FFI.setByName("close_conn", client.id.toString()); FFI.setByName("close_conn", id.toString());
}); });
_clients = []; _clients.clear();
} }
} }
@ -283,6 +286,7 @@ class Client {
bool keyboard = false; bool keyboard = false;
bool clipboard = false; bool clipboard = false;
bool audio = false; bool audio = false;
late ChatUser chatUser;
Client(this.authorized, this.isFileTransfer, this.name, this.peerId,this.keyboard,this.clipboard,this.audio); Client(this.authorized, this.isFileTransfer, this.name, this.peerId,this.keyboard,this.clipboard,this.audio);
@ -295,6 +299,10 @@ class Client {
keyboard= json['keyboard']; keyboard= json['keyboard'];
clipboard= json['clipboard']; clipboard= json['clipboard'];
audio= json['audio']; audio= json['audio'];
chatUser = ChatUser(
uid:peerId,
name: name,
);
} }
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {

View File

@ -20,23 +20,47 @@ class ChatPage extends StatelessWidget implements PageShape {
final icon = Icon(Icons.chat); final icon = Icon(Icons.chat);
@override @override
final appBarActions = []; final appBarActions = [
PopupMenuButton<int>(
icon: Icon(Icons.list_alt),
itemBuilder: (context) {
final chatModel = FFI.chatModel;
final serverModel = FFI.serverModel;
return chatModel.messages.entries.map((entry) {
final id = entry.key;
final user = serverModel.clients[id]?.chatUser ?? chatModel.me;
return PopupMenuItem<int>(
child: Text("${user.name} - ${user.uid}"),
value: id,
);
}).toList();
},
onSelected: (id) {
FFI.chatModel.changeCurrentID(id);
})
];
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Container( return ChangeNotifierProvider.value(
color: MyTheme.grayBg, value: FFI.chatModel,
child: Consumer<ChatModel>(builder: (context, chatModel, child) { child: Container(
return DashChat( color: MyTheme.grayBg,
inputContainerStyle: BoxDecoration(color: Colors.white70), child: Consumer<ChatModel>(builder: (context, chatModel, child) {
sendOnEnter: false, // if true,reload keyboard everytime,need fix return DashChat(
onSend: (chatMsg) { inputContainerStyle: BoxDecoration(color: Colors.white70),
chatModel.send(chatMsg); sendOnEnter: false,
}, // if true,reload keyboard everytime,need fix
user: chatModel.me, onSend: (chatMsg) {
messages: chatModel.messages[chatModel.currentID] ?? [], chatModel.send(chatMsg);
); },
})); user: chatModel.me,
messages: chatModel.messages[chatModel.currentID] ?? [],
// default scrollToBottom has bug https://github.com/fayeed/dash_chat/issues/53
scrollToBottom: false,
scrollController: chatModel.scroller,
);
})));
} }
} }
@ -209,24 +233,30 @@ class _ChatWindowOverlayState extends State<ChatWindowOverlay> {
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Padding(padding: EdgeInsets.symmetric(horizontal: 15),child: Text( Padding(
translate("Chat"), padding: EdgeInsets.symmetric(horizontal: 15),
style: TextStyle( child: Text(
color: Colors.white, translate("Chat"),
fontFamily: 'WorkSans', style: TextStyle(
fontWeight: FontWeight.bold, color: Colors.white,
fontSize: 20), fontFamily: 'WorkSans',
)), fontWeight: FontWeight.bold,
fontSize: 20),
)),
Row( Row(
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
IconButton(onPressed: () { IconButton(
hideChatWindowOverlay(); onPressed: () {
}, icon: Icon(Icons.keyboard_arrow_down)), hideChatWindowOverlay();
IconButton(onPressed: () { },
hideChatWindowOverlay(); icon: Icon(Icons.keyboard_arrow_down)),
hideChatIconOverlay(); IconButton(
}, icon: Icon(Icons.close)) onPressed: () {
hideChatWindowOverlay();
hideChatIconOverlay();
},
icon: Icon(Icons.close))
], ],
) )
], ],

View File

@ -45,8 +45,9 @@ class _FileManagerPageState extends State<FileManagerPage> {
} }
@override @override
Widget build(BuildContext context) => Widget build(BuildContext context) => ChangeNotifierProvider.value(
Consumer<FileModel>(builder: (_context, _model, _child) { value: FFI.fileModel,
child: Consumer<FileModel>(builder: (_context, _model, _child) {
return WillPopScope( return WillPopScope(
onWillPop: () async { onWillPop: () async {
if (model.selectMode) { if (model.selectMode) {
@ -76,7 +77,7 @@ class _FileManagerPageState extends State<FileManagerPage> {
body: body(), body: body(),
bottomSheet: bottomSheet(), bottomSheet: bottomSheet(),
)); ));
}); }));
bool needShowCheckBox() { bool needShowCheckBox() {
if (!model.selectMode) { if (!model.selectMode) {
@ -260,8 +261,8 @@ class _FileManagerPageState extends State<FileManagerPage> {
itemBuilder: (context) { itemBuilder: (context) {
return SortBy.values return SortBy.values
.map((e) => PopupMenuItem( .map((e) => PopupMenuItem(
child: Text( child:
translate(e.toString().split(".").last)), Text(translate(e.toString().split(".").last)),
value: e, value: e,
)) ))
.toList(); .toList();
@ -380,7 +381,6 @@ class _FileManagerPageState extends State<FileManagerPage> {
children: [ children: [
Padding( Padding(
padding: EdgeInsets.all(2), padding: EdgeInsets.all(2),
// TODO
child: Text( child: Text(
"${translate("Total")}: ${model.currentDir.entries.length}${translate("items")}", "${translate("Total")}: ${model.currentDir.entries.length}${translate("items")}",
style: TextStyle(color: MyTheme.darkGray), style: TextStyle(color: MyTheme.darkGray),
@ -394,7 +394,7 @@ class _FileManagerPageState extends State<FileManagerPage> {
Widget? bottomSheet() { Widget? bottomSheet() {
final state = model.jobState; final state = model.jobState;
final isOtherPage = _selectedItems.isOtherPage(model.isLocal); final isOtherPage = _selectedItems.isOtherPage(model.isLocal);
final selectedItemsLength = "${_selectedItems.length} ${translate("items")}"; // TODO t final selectedItemsLen = "${_selectedItems.length} ${translate("items")}";
final local = _selectedItems.isLocal == null final local = _selectedItems.isLocal == null
? "" ? ""
: " [${_selectedItems.isLocal! ? translate("Local") : translate("Remote")}]"; : " [${_selectedItems.isLocal! ? translate("Local") : translate("Remote")}]";
@ -405,7 +405,7 @@ class _FileManagerPageState extends State<FileManagerPage> {
return BottomSheetBody( return BottomSheetBody(
leading: Icon(Icons.check), leading: Icon(Icons.check),
title: translate("Selected"), title: translate("Selected"),
text: selectedItemsLength + local, text: selectedItemsLen + local,
onCanceled: () => model.toggleSelectMode(), onCanceled: () => model.toggleSelectMode(),
actions: [ actions: [
IconButton( IconButton(
@ -422,7 +422,7 @@ class _FileManagerPageState extends State<FileManagerPage> {
return BottomSheetBody( return BottomSheetBody(
leading: Icon(Icons.input), leading: Icon(Icons.input),
title: translate("Paste here?"), title: translate("Paste here?"),
text: selectedItemsLength + local, text: selectedItemsLen + local,
onCanceled: () => model.toggleSelectMode(), onCanceled: () => model.toggleSelectMode(),
actions: [ actions: [
IconButton( IconButton(
@ -441,7 +441,8 @@ class _FileManagerPageState extends State<FileManagerPage> {
return BottomSheetBody( return BottomSheetBody(
leading: CircularProgressIndicator(), leading: CircularProgressIndicator(),
title: translate("Waiting"), title: translate("Waiting"),
text: "${translate("Speed")}: ${readableFileSize(model.jobProgress.speed)}/s", text:
"${translate("Speed")}: ${readableFileSize(model.jobProgress.speed)}/s",
onCanceled: null, onCanceled: null,
); );
case JobState.done: case JobState.done:

View File

@ -37,20 +37,22 @@ class ServerPage extends StatelessWidget implements PageShape {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
checkService(); checkService();
return Consumer<ServerModel>( return ChangeNotifierProvider.value(
builder: (context, serverModel, child) => SingleChildScrollView( value: FFI.serverModel,
child: Center( child: Consumer<ServerModel>(
child: Column( builder: (context, serverModel, child) => SingleChildScrollView(
mainAxisAlignment: MainAxisAlignment.start, child: Center(
children: [ child: Column(
ServerInfo(), mainAxisAlignment: MainAxisAlignment.start,
PermissionChecker(), children: [
ConnectionManager(), ServerInfo(),
SizedBox.fromSize(size: Size(0, 15.0)), // Bottom padding PermissionChecker(),
], ConnectionManager(),
), SizedBox.fromSize(size: Size(0, 15.0)),
), ],
)); ),
),
)));
} }
} }
@ -155,7 +157,7 @@ class _PermissionCheckerState extends State<PermissionChecker> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final serverModel = Provider.of<ServerModel>(context); final serverModel = Provider.of<ServerModel>(context);
final hasAudioPermission = androidVersion>=30; final hasAudioPermission = androidVersion >= 30;
return PaddingCard( return PaddingCard(
title: translate("Configuration Permissions"), title: translate("Configuration Permissions"),
child: Column( child: Column(
@ -167,8 +169,13 @@ class _PermissionCheckerState extends State<PermissionChecker> {
serverModel.toggleInput), serverModel.toggleInput),
PermissionRow(translate("File Transfer"), serverModel.fileOk, PermissionRow(translate("File Transfer"), serverModel.fileOk,
serverModel.toggleFile), serverModel.toggleFile),
hasAudioPermission?PermissionRow(translate("Audio Capture"), serverModel.audioOk, hasAudioPermission
serverModel.toggleAudio):Text("* ${translate("android_version_audio_tip")}",style: TextStyle(color: MyTheme.darkGray),), ? PermissionRow(translate("Audio Capture"), serverModel.audioOk,
serverModel.toggleAudio)
: Text(
"* ${translate("android_version_audio_tip")}",
style: TextStyle(color: MyTheme.darkGray),
),
SizedBox(height: 8), SizedBox(height: 8),
serverModel.mediaOk serverModel.mediaOk
? ElevatedButton.icon( ? ElevatedButton.icon(
@ -216,7 +223,7 @@ class PermissionRow extends StatelessWidget {
TextButton( TextButton(
onPressed: onPressed, onPressed: onPressed,
child: Text( child: Text(
translate(isOk ?"CLOSE":"OPEN"), translate(isOk ? "CLOSE" : "OPEN"),
style: TextStyle(fontWeight: FontWeight.bold), style: TextStyle(fontWeight: FontWeight.bold),
)), )),
const Divider(height: 0) const Divider(height: 0)
@ -230,16 +237,20 @@ class ConnectionManager extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final serverModel = Provider.of<ServerModel>(context); final serverModel = Provider.of<ServerModel>(context);
return Column( return Column(
children: serverModel.clients children: serverModel.clients.entries
.map((client) => PaddingCard( .map((entry) => PaddingCard(
title: translate(client.isFileTransfer?"File Connection":"Screen Connection"), title: translate(entry.value.isFileTransfer
titleIcon: client.isFileTransfer?Icons.folder_outlined:Icons.mobile_screen_share, ? "File Connection"
: "Screen Connection"),
titleIcon: entry.value.isFileTransfer
? Icons.folder_outlined
: Icons.mobile_screen_share,
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Padding( Padding(
padding: EdgeInsets.symmetric(vertical: 5.0), padding: EdgeInsets.symmetric(vertical: 5.0),
child: clientInfo(client), child: clientInfo(entry.value),
), ),
ElevatedButton.icon( ElevatedButton.icon(
style: ButtonStyle( style: ButtonStyle(
@ -247,7 +258,7 @@ class ConnectionManager extends StatelessWidget {
MaterialStateProperty.all(Colors.red)), MaterialStateProperty.all(Colors.red)),
icon: Icon(Icons.close), icon: Icon(Icons.close),
onPressed: () { onPressed: () {
FFI.setByName("close_conn", client.id.toString()); FFI.setByName("close_conn", entry.key.toString());
}, },
label: Text(translate("Close"))) label: Text(translate("Close")))
], ],
@ -257,7 +268,7 @@ class ConnectionManager extends StatelessWidget {
} }
class PaddingCard extends StatelessWidget { class PaddingCard extends StatelessWidget {
PaddingCard({required this.child, this.title,this.titleIcon}); PaddingCard({required this.child, this.title, this.titleIcon});
final String? title; final String? title;
final IconData? titleIcon; final IconData? titleIcon;
@ -273,7 +284,12 @@ class PaddingCard extends StatelessWidget {
padding: EdgeInsets.symmetric(vertical: 5.0), padding: EdgeInsets.symmetric(vertical: 5.0),
child: Row( child: Row(
children: [ children: [
titleIcon !=null?Padding(padding: EdgeInsets.only(right: 10),child:Icon(titleIcon,color: MyTheme.accent80,size: 30)):SizedBox.shrink(), titleIcon != null
? Padding(
padding: EdgeInsets.only(right: 10),
child: Icon(titleIcon,
color: MyTheme.accent80, size: 30))
: SizedBox.shrink(),
Text( Text(
title!, title!,
style: TextStyle( style: TextStyle(
@ -284,7 +300,7 @@ class PaddingCard extends StatelessWidget {
), ),
) )
], ],
) )); )));
} }
return Container( return Container(
width: double.maxFinite, width: double.maxFinite,
@ -302,28 +318,27 @@ class PaddingCard extends StatelessWidget {
} }
Widget clientInfo(Client client) { Widget clientInfo(Client client) {
return Column( return Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
crossAxisAlignment: CrossAxisAlignment.start
,children: [
Row( Row(
children: [ children: [
CircleAvatar( CircleAvatar(
child: Text(client.name[0]), backgroundColor: MyTheme.border), child: Text(client.name[0]), backgroundColor: MyTheme.border),
SizedBox(width: 12), SizedBox(width: 12),
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center mainAxisAlignment: MainAxisAlignment.center,
,children: [ children: [
Text(client.name, style: TextStyle(color: MyTheme.idColor,fontSize: 20)), Text(client.name,
SizedBox(width: 8), style: TextStyle(color: MyTheme.idColor, fontSize: 20)),
Text(client.peerId, style: TextStyle(color: MyTheme.idColor,fontSize: 10)) SizedBox(width: 8),
]) Text(client.peerId,
style: TextStyle(color: MyTheme.idColor, fontSize: 10))
])
], ],
), ),
]); ]);
} }
void toAndroidChannelInit() { void toAndroidChannelInit() {
FFI.setMethodCallHandler((method, arguments) { FFI.setMethodCallHandler((method, arguments) {
debugPrint("flutter got android msg,$method,$arguments"); debugPrint("flutter got android msg,$method,$arguments");