✨ Maintain basic messaging websocket
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:solian/providers/auth.dart';
|
||||
import 'package:solian/providers/chat.dart';
|
||||
import 'package:solian/providers/navigation.dart';
|
||||
import 'package:solian/router.dart';
|
||||
import 'package:solian/utils/timeago.dart';
|
||||
@@ -37,6 +38,7 @@ class SolianApp extends StatelessWidget {
|
||||
providers: [
|
||||
Provider(create: (_) => NavigationProvider()),
|
||||
Provider(create: (_) => AuthProvider()),
|
||||
Provider(create: (_) => ChatProvider()),
|
||||
],
|
||||
child: child,
|
||||
);
|
||||
|
23
lib/models/packet.dart
Normal file
23
lib/models/packet.dart
Normal file
@@ -0,0 +1,23 @@
|
||||
class NetworkPackage {
|
||||
String method;
|
||||
String? message;
|
||||
Map<String, dynamic>? payload;
|
||||
|
||||
NetworkPackage({
|
||||
required this.method,
|
||||
this.message,
|
||||
this.payload,
|
||||
});
|
||||
|
||||
factory NetworkPackage.fromJson(Map<String, dynamic> json) => NetworkPackage(
|
||||
method: json["w"],
|
||||
message: json["m"],
|
||||
payload: json["p"],
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
"w": method,
|
||||
"m": message,
|
||||
"p": payload,
|
||||
};
|
||||
}
|
29
lib/providers/chat.dart
Normal file
29
lib/providers/chat.dart
Normal file
@@ -0,0 +1,29 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:solian/providers/auth.dart';
|
||||
import 'package:solian/utils/service_url.dart';
|
||||
import 'package:web_socket_channel/web_socket_channel.dart';
|
||||
|
||||
class ChatProvider {
|
||||
bool isOpened = false;
|
||||
|
||||
Future<WebSocketChannel?> connect(AuthProvider auth) async {
|
||||
if (auth.client == null) await auth.pickClient();
|
||||
if (!await auth.isAuthorized()) return null;
|
||||
|
||||
await auth.refreshToken();
|
||||
|
||||
var ori = getRequestUri('messaging', '/api/unified');
|
||||
var uri = Uri(
|
||||
scheme: ori.scheme.replaceFirst('http', 'ws'),
|
||||
host: ori.host,
|
||||
path: ori.path,
|
||||
queryParameters: {'tk': Uri.encodeComponent(auth.client!.credentials.accessToken)},
|
||||
);
|
||||
|
||||
final channel = WebSocketChannel.connect(uri);
|
||||
await channel.ready;
|
||||
|
||||
return channel;
|
||||
}
|
||||
}
|
@@ -1,6 +1,5 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
@@ -9,6 +8,7 @@ import 'package:solian/models/message.dart';
|
||||
import 'package:solian/models/pagination.dart';
|
||||
import 'package:solian/providers/auth.dart';
|
||||
import 'package:solian/utils/service_url.dart';
|
||||
import 'package:solian/widgets/chat/maintainer.dart';
|
||||
import 'package:solian/widgets/chat/message.dart';
|
||||
import 'package:solian/widgets/chat/message_editor.dart';
|
||||
import 'package:solian/widgets/indent_wrapper.dart';
|
||||
@@ -78,6 +78,14 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
return a.createdAt.difference(b.createdAt).inMinutes <= 5;
|
||||
}
|
||||
|
||||
void addMessage(Message item) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
setState(() {
|
||||
_pagingController.itemList?.insert(0, item);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
Future.delayed(Duration.zero, () {
|
||||
@@ -94,35 +102,43 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
return IndentWrapper(
|
||||
hideDrawer: true,
|
||||
title: _channelMeta?.name ?? "Loading...",
|
||||
child: Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: PagedListView<int, Message>(
|
||||
pagingController: _pagingController,
|
||||
builderDelegate: PagedChildBuilderDelegate<Message>(
|
||||
itemBuilder: (context, item, index) {
|
||||
bool isMerged = false, hasMerged = false;
|
||||
if (index > 0) {
|
||||
isMerged = getMessageMergeable(_pagingController.itemList?[index - 1], item);
|
||||
}
|
||||
if (index + 1 < (_pagingController.itemList?.length ?? 0)) {
|
||||
hasMerged = getMessageMergeable(item, _pagingController.itemList?[index + 1]);
|
||||
}
|
||||
return Container(
|
||||
padding: EdgeInsets.only(
|
||||
top: !isMerged ? 8 : 0,
|
||||
bottom: !hasMerged ? 8 : 0,
|
||||
left: 12,
|
||||
right: 12,
|
||||
),
|
||||
child: ChatMessage(item: item, underMerged: isMerged),
|
||||
);
|
||||
},
|
||||
child: ChatMaintainer(
|
||||
child: Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: PagedListView<int, Message>(
|
||||
reverse: true,
|
||||
pagingController: _pagingController,
|
||||
builderDelegate: PagedChildBuilderDelegate<Message>(
|
||||
itemBuilder: (context, item, index) {
|
||||
bool isMerged = false, hasMerged = false;
|
||||
if (index > 0) {
|
||||
hasMerged = getMessageMergeable(_pagingController.itemList?[index - 1], item);
|
||||
}
|
||||
if (index + 1 < (_pagingController.itemList?.length ?? 0)) {
|
||||
isMerged = getMessageMergeable(item, _pagingController.itemList?[index + 1]);
|
||||
}
|
||||
return Container(
|
||||
padding: EdgeInsets.only(
|
||||
top: !isMerged ? 8 : 0,
|
||||
bottom: !hasMerged ? 8 : 0,
|
||||
left: 12,
|
||||
right: 12,
|
||||
),
|
||||
child: ChatMessage(
|
||||
key: Key('m${item.id}'),
|
||||
item: item,
|
||||
underMerged: isMerged,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
ChatMessageEditor(channel: widget.alias),
|
||||
],
|
||||
ChatMessageEditor(channel: widget.alias),
|
||||
],
|
||||
),
|
||||
onNewMessage: (message) => addMessage(message),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
45
lib/widgets/chat/maintainer.dart
Normal file
45
lib/widgets/chat/maintainer.dart
Normal file
@@ -0,0 +1,45 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:solian/models/message.dart';
|
||||
import 'package:solian/models/packet.dart';
|
||||
import 'package:solian/providers/auth.dart';
|
||||
import 'package:solian/providers/chat.dart';
|
||||
|
||||
class ChatMaintainer extends StatefulWidget {
|
||||
final Widget child;
|
||||
final Function(Message val) onNewMessage;
|
||||
|
||||
const ChatMaintainer({super.key, required this.child, required this.onNewMessage});
|
||||
|
||||
@override
|
||||
State<ChatMaintainer> createState() => _ChatMaintainerState();
|
||||
}
|
||||
|
||||
class _ChatMaintainerState extends State<ChatMaintainer> {
|
||||
@override
|
||||
void initState() {
|
||||
Future.delayed(Duration.zero, () {
|
||||
final auth = context.read<AuthProvider>();
|
||||
final chat = context.read<ChatProvider>();
|
||||
|
||||
chat.connect(auth).then((snapshot) {
|
||||
snapshot!.stream.listen((event) {
|
||||
final result = NetworkPackage.fromJson(jsonDecode(event));
|
||||
switch (result.method) {
|
||||
case 'messages.new':
|
||||
widget.onNewMessage(Message.fromJson(result.payload!));
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return widget.child;
|
||||
}
|
||||
}
|
@@ -46,7 +46,11 @@ class ChatMessage extends StatelessWidget {
|
||||
return Row(
|
||||
children: [
|
||||
const SizedBox(width: 40),
|
||||
Expanded(child: contentPart),
|
||||
Expanded(
|
||||
child: Column(
|
||||
children: [contentPart, renderAttachment()],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
} else {
|
||||
|
Reference in New Issue
Block a user