Maintain basic messaging websocket

This commit is contained in:
LittleSheep 2024-04-18 22:33:55 +08:00
parent 8943f089f2
commit 94f9076abb
52 changed files with 288 additions and 101 deletions

View File

@ -2,7 +2,7 @@
<application <application
android:label="solian" android:label="solian"
android:name="${applicationName}" android:name="${applicationName}"
android:icon="@mipmap/ic_launcher"> android:icon="@mipmap/launcher_icon">
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"
android:exported="true" android:exported="true"

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

BIN
assets/icon.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

View File

@ -541,7 +541,7 @@
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO; ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = AppIcon;
CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++"; CLANG_CXX_LIBRARY = "libc++";
@ -598,7 +598,7 @@
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO; ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = AppIcon;
CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++"; CLANG_CXX_LIBRARY = "libc++";

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 295 B

After

Width:  |  Height:  |  Size: 625 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 406 B

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 450 B

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 282 B

After

Width:  |  Height:  |  Size: 958 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 462 B

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 704 B

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 406 B

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 586 B

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 862 B

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 862 B

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 762 B

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 8.0 KiB

View File

@ -1,6 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:solian/providers/auth.dart'; import 'package:solian/providers/auth.dart';
import 'package:solian/providers/chat.dart';
import 'package:solian/providers/navigation.dart'; import 'package:solian/providers/navigation.dart';
import 'package:solian/router.dart'; import 'package:solian/router.dart';
import 'package:solian/utils/timeago.dart'; import 'package:solian/utils/timeago.dart';
@ -37,6 +38,7 @@ class SolianApp extends StatelessWidget {
providers: [ providers: [
Provider(create: (_) => NavigationProvider()), Provider(create: (_) => NavigationProvider()),
Provider(create: (_) => AuthProvider()), Provider(create: (_) => AuthProvider()),
Provider(create: (_) => ChatProvider()),
], ],
child: child, child: child,
); );

23
lib/models/packet.dart Normal file
View 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
View 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;
}
}

View File

@ -1,6 +1,5 @@
import 'dart:convert'; import 'dart:convert';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart'; import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
import 'package:provider/provider.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/models/pagination.dart';
import 'package:solian/providers/auth.dart'; import 'package:solian/providers/auth.dart';
import 'package:solian/utils/service_url.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.dart';
import 'package:solian/widgets/chat/message_editor.dart'; import 'package:solian/widgets/chat/message_editor.dart';
import 'package:solian/widgets/indent_wrapper.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; return a.createdAt.difference(b.createdAt).inMinutes <= 5;
} }
void addMessage(Message item) {
WidgetsBinding.instance.addPostFrameCallback((_) {
setState(() {
_pagingController.itemList?.insert(0, item);
});
});
}
@override @override
void initState() { void initState() {
Future.delayed(Duration.zero, () { Future.delayed(Duration.zero, () {
@ -94,19 +102,21 @@ class _ChatScreenState extends State<ChatScreen> {
return IndentWrapper( return IndentWrapper(
hideDrawer: true, hideDrawer: true,
title: _channelMeta?.name ?? "Loading...", title: _channelMeta?.name ?? "Loading...",
child: ChatMaintainer(
child: Column( child: Column(
children: [ children: [
Expanded( Expanded(
child: PagedListView<int, Message>( child: PagedListView<int, Message>(
reverse: true,
pagingController: _pagingController, pagingController: _pagingController,
builderDelegate: PagedChildBuilderDelegate<Message>( builderDelegate: PagedChildBuilderDelegate<Message>(
itemBuilder: (context, item, index) { itemBuilder: (context, item, index) {
bool isMerged = false, hasMerged = false; bool isMerged = false, hasMerged = false;
if (index > 0) { if (index > 0) {
isMerged = getMessageMergeable(_pagingController.itemList?[index - 1], item); hasMerged = getMessageMergeable(_pagingController.itemList?[index - 1], item);
} }
if (index + 1 < (_pagingController.itemList?.length ?? 0)) { if (index + 1 < (_pagingController.itemList?.length ?? 0)) {
hasMerged = getMessageMergeable(item, _pagingController.itemList?[index + 1]); isMerged = getMessageMergeable(item, _pagingController.itemList?[index + 1]);
} }
return Container( return Container(
padding: EdgeInsets.only( padding: EdgeInsets.only(
@ -115,7 +125,11 @@ class _ChatScreenState extends State<ChatScreen> {
left: 12, left: 12,
right: 12, right: 12,
), ),
child: ChatMessage(item: item, underMerged: isMerged), child: ChatMessage(
key: Key('m${item.id}'),
item: item,
underMerged: isMerged,
),
); );
}, },
), ),
@ -124,6 +138,8 @@ class _ChatScreenState extends State<ChatScreen> {
ChatMessageEditor(channel: widget.alias), ChatMessageEditor(channel: widget.alias),
], ],
), ),
onNewMessage: (message) => addMessage(message),
),
); );
} }
} }

View 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;
}
}

View File

@ -46,7 +46,11 @@ class ChatMessage extends StatelessWidget {
return Row( return Row(
children: [ children: [
const SizedBox(width: 40), const SizedBox(width: 40),
Expanded(child: contentPart), Expanded(
child: Column(
children: [contentPart, renderAttachment()],
),
),
], ],
); );
} else { } else {

View File

@ -1,68 +1,68 @@
{ {
"images" : [ "info": {
"version": 1,
"author": "xcode"
},
"images": [
{ {
"size" : "16x16", "size": "16x16",
"idiom" : "mac", "idiom": "mac",
"filename" : "app_icon_16.png", "filename": "app_icon_16.png",
"scale" : "1x" "scale": "1x"
}, },
{ {
"size" : "16x16", "size": "16x16",
"idiom" : "mac", "idiom": "mac",
"filename" : "app_icon_32.png", "filename": "app_icon_32.png",
"scale" : "2x" "scale": "2x"
}, },
{ {
"size" : "32x32", "size": "32x32",
"idiom" : "mac", "idiom": "mac",
"filename" : "app_icon_32.png", "filename": "app_icon_32.png",
"scale" : "1x" "scale": "1x"
}, },
{ {
"size" : "32x32", "size": "32x32",
"idiom" : "mac", "idiom": "mac",
"filename" : "app_icon_64.png", "filename": "app_icon_64.png",
"scale" : "2x" "scale": "2x"
}, },
{ {
"size" : "128x128", "size": "128x128",
"idiom" : "mac", "idiom": "mac",
"filename" : "app_icon_128.png", "filename": "app_icon_128.png",
"scale" : "1x" "scale": "1x"
}, },
{ {
"size" : "128x128", "size": "128x128",
"idiom" : "mac", "idiom": "mac",
"filename" : "app_icon_256.png", "filename": "app_icon_256.png",
"scale" : "2x" "scale": "2x"
}, },
{ {
"size" : "256x256", "size": "256x256",
"idiom" : "mac", "idiom": "mac",
"filename" : "app_icon_256.png", "filename": "app_icon_256.png",
"scale" : "1x" "scale": "1x"
}, },
{ {
"size" : "256x256", "size": "256x256",
"idiom" : "mac", "idiom": "mac",
"filename" : "app_icon_512.png", "filename": "app_icon_512.png",
"scale" : "2x" "scale": "2x"
}, },
{ {
"size" : "512x512", "size": "512x512",
"idiom" : "mac", "idiom": "mac",
"filename" : "app_icon_512.png", "filename": "app_icon_512.png",
"scale" : "1x" "scale": "1x"
}, },
{ {
"size" : "512x512", "size": "512x512",
"idiom" : "mac", "idiom": "mac",
"filename" : "app_icon_1024.png", "filename": "app_icon_1024.png",
"scale" : "2x" "scale": "2x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
} }
]
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 101 KiB

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 520 B

After

Width:  |  Height:  |  Size: 480 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@ -41,6 +41,22 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.3.0" version: "1.3.0"
checked_yaml:
dependency: transitive
description:
name: checked_yaml
sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff
url: "https://pub.dev"
source: hosted
version: "2.0.3"
cli_util:
dependency: transitive
description:
name: cli_util
sha256: c05b7406fdabc7a49a3929d4af76bcaccbbffcbcdcf185b082e1ae07da323d19
url: "https://pub.dev"
source: hosted
version: "0.4.1"
clock: clock:
dependency: transitive dependency: transitive
description: description:
@ -166,6 +182,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.0" version: "2.2.0"
flutter_launcher_icons:
dependency: "direct main"
description:
name: flutter_launcher_icons
sha256: "526faf84284b86a4cb36d20a5e45147747b7563d921373d4ee0559c54fcdbcea"
url: "https://pub.dev"
source: hosted
version: "0.13.1"
flutter_lints: flutter_lints:
dependency: "direct dev" dependency: "direct dev"
description: description:
@ -397,6 +421,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.6.7" version: "0.6.7"
json_annotation:
dependency: transitive
description:
name: json_annotation
sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467
url: "https://pub.dev"
source: hosted
version: "4.8.1"
leak_tracker: leak_tracker:
dependency: transitive dependency: transitive
description: description:
@ -962,6 +994,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.5.1" version: "0.5.1"
web_socket_channel:
dependency: "direct main"
description:
name: web_socket_channel
sha256: "58c6666b342a38816b2e7e50ed0f1e261959630becd4c879c4f26bfa14aa5a42"
url: "https://pub.dev"
source: hosted
version: "2.4.5"
webview_flutter: webview_flutter:
dependency: "direct main" dependency: "direct main"
description: description:
@ -1018,6 +1058,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.5.0" version: "6.5.0"
yaml:
dependency: transitive
description:
name: yaml
sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5"
url: "https://pub.dev"
source: hosted
version: "3.1.2"
sdks: sdks:
dart: ">=3.3.3 <4.0.0" dart: ">=3.3.3 <4.0.0"
flutter: ">=3.19.0" flutter: ">=3.19.0"

View File

@ -57,6 +57,8 @@ dependencies:
media_kit: ^1.1.10+1 media_kit: ^1.1.10+1
media_kit_libs_video: ^1.0.4 media_kit_libs_video: ^1.0.4
hive_flutter: ^1.1.0 hive_flutter: ^1.1.0
flutter_launcher_icons: ^0.13.1
web_socket_channel: ^2.4.5
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
@ -111,3 +113,21 @@ flutter:
# #
# For details regarding fonts from package dependencies, # For details regarding fonts from package dependencies,
# see https://flutter.dev/custom-fonts/#from-packages # see https://flutter.dev/custom-fonts/#from-packages
flutter_launcher_icons:
android: "launcher_icon"
ios: true
image_path: "assets/icon.png"
min_sdk_android: 21
web:
generate: true
image_path: "assets/icon.png"
background_color: "#ffffff"
theme_color: "#4b5094"
windows:
generate: true
image_path: "assets/icon.png"
icon_size: 256
macos:
generate: true
image_path: "assets/icon.png"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 917 B

After

Width:  |  Height:  |  Size: 480 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.1 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 29 KiB

View File

@ -3,8 +3,8 @@
"short_name": "solian", "short_name": "solian",
"start_url": ".", "start_url": ".",
"display": "standalone", "display": "standalone",
"background_color": "#0175C2", "background_color": "#ffffff",
"theme_color": "#0175C2", "theme_color": "#4b5094",
"description": "A new Flutter project.", "description": "A new Flutter project.",
"orientation": "portrait-primary", "orientation": "portrait-primary",
"prefer_related_applications": false, "prefer_related_applications": false,

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 13 KiB