add client chat page & chat overlay window
This commit is contained in:
parent
b106ed5717
commit
e9f8fd1175
@ -1,5 +1,44 @@
|
||||
import 'package:dash_chat/dash_chat.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hbb/pages/chat_page.dart';
|
||||
|
||||
import 'model.dart';
|
||||
import 'native_model.dart';
|
||||
|
||||
class ChatModel with ChangeNotifier {
|
||||
final List<ChatMessage> _messages = [];
|
||||
|
||||
final ChatUser me = ChatUser(
|
||||
name:"me",
|
||||
);
|
||||
|
||||
get messages => _messages;
|
||||
|
||||
receive(String text){
|
||||
if (text.isEmpty) return;
|
||||
// first message show overlay icon
|
||||
if (iconOverlayEntry == null){
|
||||
showChatIconOverlay();
|
||||
}
|
||||
_messages.add(ChatMessage(text: text, user: ChatUser(
|
||||
name:FFI.ffiModel.pi.username,
|
||||
uid: FFI.getId(),
|
||||
)));
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
send(ChatMessage message){
|
||||
_messages.add(message);
|
||||
if(message.text != null && message.text!.isNotEmpty){
|
||||
PlatformFFI.setByName("chat",message.text!);
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
release(){
|
||||
hideChatIconOverlay();
|
||||
hideChatWindowOverlay();
|
||||
_messages.clear();
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_easyloading/flutter_easyloading.dart';
|
||||
import 'package:flutter_hbb/models/chat_model.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'dart:math';
|
||||
import 'dart:convert';
|
||||
@ -128,6 +129,9 @@ class FfiModel with ChangeNotifier {
|
||||
Clipboard.setData(ClipboardData(text: evt['content']));
|
||||
} else if (name == 'permission') {
|
||||
FFI.ffiModel.updatePermission(evt);
|
||||
} else if (name == 'chat'){
|
||||
// FFI.setByName("chat",msg);
|
||||
FFI.chatModel.receive(evt['text']??"");
|
||||
}
|
||||
}
|
||||
if (pos != null) FFI.cursorModel.updateCursorPosition(pos);
|
||||
@ -703,6 +707,7 @@ class FFI {
|
||||
static final cursorModel = CursorModel();
|
||||
static final canvasModel = CanvasModel();
|
||||
static final serverModel = ServerModel();
|
||||
static final chatModel = ChatModel();
|
||||
|
||||
static String getId() {
|
||||
return getByName('remote_id');
|
||||
@ -797,6 +802,7 @@ class FFI {
|
||||
}
|
||||
|
||||
static void close() {
|
||||
chatModel.release();
|
||||
if (FFI.imageModel.image != null && !isDesktop) {
|
||||
savePreference(
|
||||
id,
|
||||
|
@ -1,13 +1,21 @@
|
||||
import 'package:dash_chat/dash_chat.dart';
|
||||
import 'package:draggable_float_widget/draggable_float_widget.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hbb/common.dart';
|
||||
import 'package:flutter_hbb/models/chat_model.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import '../main.dart';
|
||||
import '../models/model.dart';
|
||||
import '../models/native_model.dart';
|
||||
import 'home_page.dart';
|
||||
|
||||
OverlayEntry? iconOverlayEntry;
|
||||
OverlayEntry? windowOverlayEntry;
|
||||
|
||||
ChatPage chatPage = ChatPage();
|
||||
|
||||
class ChatPage extends StatelessWidget implements PageShape {
|
||||
|
||||
final FocusNode _focusNode = FocusNode();
|
||||
|
||||
@override
|
||||
final title = "Chat";
|
||||
|
||||
@ -19,31 +27,208 @@ class ChatPage extends StatelessWidget implements PageShape {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
color: MyTheme.darkGray,
|
||||
child: DashChat(
|
||||
focusNode: _focusNode,
|
||||
onSend: (ChatMessage) {},
|
||||
user: ChatUser(uid: "111", name: "Bob"),
|
||||
messages: [
|
||||
ChatMessage(
|
||||
text: "hello", user: ChatUser(uid: "111", name: "Bob")),
|
||||
ChatMessage(
|
||||
text: "hi", user: ChatUser(uid: "222", name: "Alice")),
|
||||
ChatMessage(
|
||||
text: "hello", user: ChatUser(uid: "111", name: "Bob")),
|
||||
ChatMessage(
|
||||
text: "hi", user: ChatUser(uid: "222", name: "Alice")),
|
||||
ChatMessage(
|
||||
text: "hello", user: ChatUser(uid: "111", name: "Bob")),
|
||||
ChatMessage(
|
||||
text: "hi", user: ChatUser(uid: "222", name: "Alice")),
|
||||
ChatMessage(
|
||||
text: "hello", user: ChatUser(uid: "111", name: "Bob")),
|
||||
ChatMessage(
|
||||
text: "hi", user: ChatUser(uid: "222", name: "Alice")),
|
||||
],
|
||||
),
|
||||
);
|
||||
return ChangeNotifierProvider.value(
|
||||
value: FFI.chatModel,
|
||||
child: Container(
|
||||
color: MyTheme.grayBg,
|
||||
child: Consumer<ChatModel>(builder: (context, chatModel, child) {
|
||||
return DashChat(
|
||||
sendOnEnter: false, // if true,reload keyboard everytime,need fix
|
||||
onSend: (chatMsg) {
|
||||
chatModel.send(chatMsg);
|
||||
},
|
||||
user: chatModel.me,
|
||||
messages: chatModel.messages,
|
||||
);
|
||||
})));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
showChatIconOverlay({Offset offset = const Offset(200, 50)}) {
|
||||
if (iconOverlayEntry != null) {
|
||||
iconOverlayEntry!.remove();
|
||||
}
|
||||
if (globalKey.currentState == null || globalKey.currentState!.overlay == null)
|
||||
return;
|
||||
final globalOverlayState = globalKey.currentState!.overlay!;
|
||||
|
||||
final overlay = OverlayEntry(builder: (context) {
|
||||
return DraggableFloatWidget(
|
||||
config: DraggableFloatWidgetBaseConfig(
|
||||
initPositionYInTop: false,
|
||||
initPositionYMarginBorder: 100,
|
||||
borderTopContainTopBar: true,
|
||||
),
|
||||
child: FloatingActionButton(
|
||||
onPressed: () {
|
||||
if (windowOverlayEntry == null) {
|
||||
showChatWindowOverlay();
|
||||
} else {
|
||||
hideChatWindowOverlay();
|
||||
}
|
||||
},
|
||||
child: Icon(Icons.message)));
|
||||
});
|
||||
globalOverlayState.insert(overlay);
|
||||
iconOverlayEntry = overlay;
|
||||
debugPrint("created");
|
||||
}
|
||||
|
||||
hideChatIconOverlay() {
|
||||
if (iconOverlayEntry != null) {
|
||||
iconOverlayEntry!.remove();
|
||||
iconOverlayEntry = null;
|
||||
}
|
||||
}
|
||||
|
||||
final FocusNode _focusNode = FocusNode();
|
||||
|
||||
showChatWindowOverlay() {
|
||||
if (windowOverlayEntry != null) return;
|
||||
if (globalKey.currentState == null || globalKey.currentState!.overlay == null)
|
||||
return;
|
||||
final globalOverlayState = globalKey.currentState!.overlay!;
|
||||
|
||||
final overlay = OverlayEntry(builder: (context) {
|
||||
return ChatWindowOverlay();
|
||||
});
|
||||
_focusNode.requestFocus();
|
||||
globalOverlayState.insert(overlay);
|
||||
windowOverlayEntry = overlay;
|
||||
debugPrint("chatEntry created");
|
||||
}
|
||||
|
||||
hideChatWindowOverlay() {
|
||||
if (windowOverlayEntry != null) {
|
||||
windowOverlayEntry!.remove();
|
||||
windowOverlayEntry = null;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
toggleChatOverlay() {
|
||||
if (iconOverlayEntry == null || windowOverlayEntry == null) {
|
||||
showChatIconOverlay();
|
||||
showChatWindowOverlay();
|
||||
} else {
|
||||
hideChatIconOverlay();
|
||||
hideChatWindowOverlay();
|
||||
}
|
||||
}
|
||||
|
||||
class ChatWindowOverlay extends StatefulWidget {
|
||||
final double windowWidth = 250;
|
||||
final double windowHeight = 300;
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => _ChatWindowOverlayState();
|
||||
}
|
||||
|
||||
class _ChatWindowOverlayState extends State<ChatWindowOverlay> {
|
||||
Offset _o = Offset(20, 20);
|
||||
bool _keyboardVisible = false;
|
||||
double _saveHeight = 0;
|
||||
double _lastBottomHeight = 0;
|
||||
|
||||
changeOffset(Offset offset) {
|
||||
final size = MediaQuery.of(context).size;
|
||||
debugPrint("parent size:$size");
|
||||
double x = 0;
|
||||
double y = 0;
|
||||
|
||||
if (_o.dx + offset.dx + widget.windowWidth > size.width) {
|
||||
x = size.width - widget.windowWidth;
|
||||
} else if (_o.dx + offset.dx < 0) {
|
||||
x = 0;
|
||||
} else {
|
||||
x = _o.dx + offset.dx;
|
||||
}
|
||||
|
||||
if (_o.dy + offset.dy + widget.windowHeight > size.height) {
|
||||
y = size.height - widget.windowHeight;
|
||||
} else if (_o.dy + offset.dy < 0) {
|
||||
y = 0;
|
||||
} else {
|
||||
y = _o.dy + offset.dy;
|
||||
}
|
||||
setState(() {
|
||||
_o = Offset(x, y);
|
||||
});
|
||||
}
|
||||
|
||||
checkScreenSize(){
|
||||
// TODO 横屏处理
|
||||
}
|
||||
|
||||
checkKeyBoard(){
|
||||
final bottomHeight = MediaQuery.of(context).viewInsets.bottom;
|
||||
final currentVisible = bottomHeight != 0;
|
||||
|
||||
debugPrint(bottomHeight.toString() + currentVisible.toString());
|
||||
// save
|
||||
if (!_keyboardVisible && currentVisible){
|
||||
_saveHeight = _o.dy;
|
||||
debugPrint("on save $_saveHeight");
|
||||
}
|
||||
|
||||
// reset
|
||||
if (_lastBottomHeight>0 && bottomHeight == 0){
|
||||
debugPrint("on reset");
|
||||
_o = Offset(_o.dx,_saveHeight);
|
||||
}
|
||||
|
||||
// onKeyboardVisible
|
||||
if (_keyboardVisible && currentVisible){
|
||||
|
||||
|
||||
final sumHeight = bottomHeight + widget.windowHeight;
|
||||
final contextHeight = MediaQuery.of(context).size.height;
|
||||
debugPrint("prepare update sumHeight:$sumHeight,contextHeight:$contextHeight");
|
||||
if(sumHeight + _o.dy > contextHeight){
|
||||
final y = contextHeight - sumHeight;
|
||||
debugPrint("on update");
|
||||
_o = Offset(_o.dx,y);
|
||||
}
|
||||
}
|
||||
|
||||
_keyboardVisible = currentVisible;
|
||||
_lastBottomHeight = bottomHeight;
|
||||
|
||||
}
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
checkKeyBoard();
|
||||
checkScreenSize();
|
||||
return Positioned(
|
||||
top: _o.dy,
|
||||
left: _o.dx,
|
||||
width: widget.windowWidth,
|
||||
height: widget.windowHeight,
|
||||
child: Scaffold(
|
||||
resizeToAvoidBottomInset: false,
|
||||
appBar: CustomAppBar(
|
||||
onPanUpdate: (d) => changeOffset(d.delta),
|
||||
appBar: AppBar(
|
||||
title: Text("Chat")),
|
||||
),
|
||||
body: chatPage,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
class CustomAppBar extends StatelessWidget implements PreferredSizeWidget {
|
||||
final GestureDragUpdateCallback onPanUpdate;
|
||||
final AppBar appBar;
|
||||
|
||||
const CustomAppBar(
|
||||
{Key? key, required this.onPanUpdate, required this.appBar})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(onPanUpdate: onPanUpdate, child: appBar);
|
||||
}
|
||||
|
||||
@override
|
||||
Size get preferredSize => new Size.fromHeight(kToolbarHeight);
|
||||
}
|
||||
|
@ -1,4 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hbb/pages/chat_page.dart';
|
||||
import 'package:flutter_hbb/pages/file_manager_page.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'dart:async';
|
||||
@ -53,6 +55,21 @@ class _ConnectionPageState extends State<ConnectionPage> {
|
||||
getSearchBarUI(),
|
||||
Container(height: 12),
|
||||
getPeers(),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (BuildContext context) => FileManagerPage(),
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Text("File")),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
toggleChatOverlay();
|
||||
},
|
||||
child: Text("Chat"))
|
||||
]),
|
||||
);
|
||||
}
|
||||
@ -65,15 +82,12 @@ class _ConnectionPageState extends State<ConnectionPage> {
|
||||
void connect(String id) {
|
||||
if (id == '') return;
|
||||
id = id.replaceAll(' ', '');
|
||||
() async {
|
||||
await Navigator.push<dynamic>(
|
||||
context,
|
||||
MaterialPageRoute<dynamic>(
|
||||
builder: (BuildContext context) => RemotePage(id: id),
|
||||
),
|
||||
);
|
||||
setState(() {});
|
||||
}();
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (BuildContext context) => RemotePage(id: id),
|
||||
),
|
||||
);
|
||||
FocusScopeNode currentFocus = FocusScope.of(context);
|
||||
if (!currentFocus.hasPrimaryFocus) {
|
||||
currentFocus.unfocus();
|
||||
@ -84,20 +98,20 @@ class _ConnectionPageState extends State<ConnectionPage> {
|
||||
return _updateUrl.isEmpty
|
||||
? SizedBox(height: 0)
|
||||
: InkWell(
|
||||
onTap: () async {
|
||||
final url = _updateUrl + '.apk';
|
||||
if (await canLaunch(url)) {
|
||||
await launch(url);
|
||||
}
|
||||
},
|
||||
child: Container(
|
||||
alignment: AlignmentDirectional.center,
|
||||
width: double.infinity,
|
||||
color: Colors.pinkAccent,
|
||||
padding: EdgeInsets.symmetric(vertical: 12),
|
||||
child: Text(translate('Download new version'),
|
||||
style: TextStyle(
|
||||
color: Colors.white, fontWeight: FontWeight.bold))));
|
||||
onTap: () async {
|
||||
final url = _updateUrl + '.apk';
|
||||
if (await canLaunch(url)) {
|
||||
await launch(url);
|
||||
}
|
||||
},
|
||||
child: Container(
|
||||
alignment: AlignmentDirectional.center,
|
||||
width: double.infinity,
|
||||
color: Colors.pinkAccent,
|
||||
padding: EdgeInsets.symmetric(vertical: 12),
|
||||
child: Text(translate('Download new version'),
|
||||
style: TextStyle(
|
||||
color: Colors.white, fontWeight: FontWeight.bold))));
|
||||
}
|
||||
|
||||
Widget getSearchBarUI() {
|
||||
@ -113,12 +127,7 @@ class _ConnectionPageState extends State<ConnectionPage> {
|
||||
child: Ink(
|
||||
decoration: BoxDecoration(
|
||||
color: MyTheme.white,
|
||||
borderRadius: const BorderRadius.only(
|
||||
bottomRight: Radius.circular(13.0),
|
||||
bottomLeft: Radius.circular(13.0),
|
||||
topLeft: Radius.circular(13.0),
|
||||
topRight: Radius.circular(13.0),
|
||||
),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(13)),
|
||||
),
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
@ -164,7 +173,7 @@ class _ConnectionPageState extends State<ConnectionPage> {
|
||||
color: MyTheme.darkGray, size: 45),
|
||||
onPressed: onConnect,
|
||||
),
|
||||
)
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
@ -209,11 +218,11 @@ class _ConnectionPageState extends State<ConnectionPage> {
|
||||
child: Card(
|
||||
child: GestureDetector(
|
||||
onTap: () => {
|
||||
if (!isDesktop) {connect('${p.id}')}
|
||||
},
|
||||
if (!isDesktop) {connect('${p.id}')}
|
||||
},
|
||||
onDoubleTap: () => {
|
||||
if (isDesktop) {connect('${p.id}')}
|
||||
},
|
||||
if (isDesktop) {connect('${p.id}')}
|
||||
},
|
||||
onLongPressStart: (details) {
|
||||
var x = details.globalPosition.dx;
|
||||
var y = details.globalPosition.dy;
|
||||
@ -258,9 +267,9 @@ class _ConnectionPageState extends State<ConnectionPage> {
|
||||
);
|
||||
if (value == 'remove') {
|
||||
setState(() => FFI.setByName('remove', '$id'));
|
||||
() async {
|
||||
() async {
|
||||
removePreference(id);
|
||||
}();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
38
lib/pages/file_manager_page.dart
Normal file
38
lib/pages/file_manager_page.dart
Normal file
@ -0,0 +1,38 @@
|
||||
import 'dart:io';
|
||||
import 'package:file_manager/file_manager.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
final FileManagerController controller = FileManagerController();
|
||||
|
||||
class FileManagerPage extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: FileManager(
|
||||
controller: controller,
|
||||
builder: (context, snapshot) {
|
||||
final List<FileSystemEntity> entities = snapshot;
|
||||
return ListView.builder(
|
||||
itemCount: entities.length,
|
||||
itemBuilder: (context, index) {
|
||||
return Card(
|
||||
child: ListTile(
|
||||
leading: FileManager.isFile(entities[index])
|
||||
? Icon(Icons.feed_outlined)
|
||||
: Icon(Icons.folder),
|
||||
title: Text(FileManager.basename(entities[index])),
|
||||
onTap: () {
|
||||
if (FileManager.isDirectory(entities[index])) {
|
||||
controller.openDirectory(entities[index]); // open directory
|
||||
} else {
|
||||
// Perform file-related tasks.
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
));
|
||||
}
|
||||
}
|
@ -22,7 +22,7 @@ class _HomePageState extends State<HomePage> {
|
||||
var _selectedIndex = 0;
|
||||
final List<PageShape> _pages = [
|
||||
ConnectionPage(),
|
||||
ChatPage(),
|
||||
chatPage,
|
||||
ServerPage(),
|
||||
SettingsPage()
|
||||
];
|
||||
|
@ -11,6 +11,7 @@ import 'package:wakelock/wakelock.dart';
|
||||
import '../common.dart';
|
||||
import '../gestures.dart';
|
||||
import '../models/model.dart';
|
||||
import 'chat_page.dart';
|
||||
|
||||
final initText = '\1' * 1024;
|
||||
|
||||
@ -307,6 +308,11 @@ class _RemotePageState extends State<RemotePage> {
|
||||
)
|
||||
]) +
|
||||
<Widget>[
|
||||
IconButton(
|
||||
color: Colors.white,
|
||||
icon: Icon(Icons.message),
|
||||
onPressed:toggleChatOverlay,
|
||||
),
|
||||
IconButton(
|
||||
color: Colors.white,
|
||||
icon: Icon(Icons.more_vert),
|
||||
|
@ -46,7 +46,9 @@ dependencies:
|
||||
shared_preferences: ^2.0.6
|
||||
toggle_switch: ^1.4.0
|
||||
dash_chat: ^1.1.16
|
||||
draggable_float_widget: ^0.0.2
|
||||
settings_ui: ^2.0.2
|
||||
file_manager: ^1.0.0
|
||||
|
||||
dev_dependencies:
|
||||
flutter_launcher_icons: ^0.9.1
|
||||
|
Loading…
x
Reference in New Issue
Block a user