Online indicator in chat

This commit is contained in:
2025-09-27 22:17:29 +08:00
parent fe33931304
commit 214d5c4a53
3 changed files with 57 additions and 42 deletions

View File

@@ -1080,5 +1080,10 @@
"deleteRecycledFiles": "Delete Recycled Files", "deleteRecycledFiles": "Delete Recycled Files",
"recycledFilesDeleted": "Recycled files deleted successfully", "recycledFilesDeleted": "Recycled files deleted successfully",
"failedToDeleteRecycledFiles": "Failed to delete recycled files", "failedToDeleteRecycledFiles": "Failed to delete recycled files",
"upload": "Upload" "upload": "Upload",
"updateAvailable": "Update available",
"noChangelogProvided": "No changelog provided.",
"useSecondarySourceForDownload": "Use secondary source for download",
"installUpdate": "Install update",
"openReleasePage": "Open release page"
} }

View File

@@ -9,6 +9,7 @@ import "package:hooks_riverpod/hooks_riverpod.dart";
import "package:island/database/message.dart"; import "package:island/database/message.dart";
import "package:island/models/chat.dart"; import "package:island/models/chat.dart";
import "package:island/models/file.dart"; import "package:island/models/file.dart";
import "package:island/pods/chat/chat_rooms.dart";
import "package:island/pods/chat/chat_subscribe.dart"; import "package:island/pods/chat/chat_subscribe.dart";
import "package:island/pods/config.dart"; import "package:island/pods/config.dart";
import "package:island/pods/chat/messages_notifier.dart"; import "package:island/pods/chat/messages_notifier.dart";
@@ -33,8 +34,6 @@ import "package:island/widgets/chat/call_button.dart";
import "package:island/widgets/chat/chat_input.dart"; import "package:island/widgets/chat/chat_input.dart";
import "package:island/widgets/chat/public_room_preview.dart"; import "package:island/widgets/chat/public_room_preview.dart";
final isSyncingProvider = StateProvider.autoDispose<bool>((ref) => false);
final flashingMessagesProvider = StateProvider<Set<String>>((ref) => {}); final flashingMessagesProvider = StateProvider<Set<String>>((ref) => {});
class ChatRoomScreen extends HookConsumerWidget { class ChatRoomScreen extends HookConsumerWidget {
@@ -48,6 +47,8 @@ class ChatRoomScreen extends HookConsumerWidget {
final isSyncing = ref.watch(isSyncingProvider); final isSyncing = ref.watch(isSyncingProvider);
final onlineCount = ref.watch(chatOnlineCountNotifierProvider(id)); final onlineCount = ref.watch(chatOnlineCountNotifierProvider(id));
final hasOnlineCount = onlineCount.hasValue;
if (chatIdentity.isLoading || chatRoom.isLoading) { if (chatIdentity.isLoading || chatRoom.isLoading) {
return AppScaffold( return AppScaffold(
appBar: AppBar(leading: const PageBackButton()), appBar: AppBar(leading: const PageBackButton()),
@@ -231,6 +232,32 @@ class ChatRoomScreen extends HookConsumerWidget {
final compactHeader = isWideScreen(context); final compactHeader = isWideScreen(context);
Widget onlineIndicator() => Row(
spacing: 8,
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Container(
width: 8,
height: 8,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: (onlineCount as AsyncData).value > 1 ? Colors.green : null,
border:
(onlineCount as AsyncData).value <= 1
? Border.all(color: Colors.grey)
: null,
),
),
Text(
'${(onlineCount as AsyncData).value} online',
style: Theme.of(context).textTheme.bodySmall!.copyWith(
color: Theme.of(context).appBarTheme.foregroundColor!,
),
),
],
);
Widget comfortHeaderWidget(SnChatRoom? room) => Column( Widget comfortHeaderWidget(SnChatRoom? room) => Column(
spacing: 4, spacing: 4,
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
@@ -264,16 +291,18 @@ class ChatRoomScreen extends HookConsumerWidget {
? room.members!.map((e) => e.account.nick).join(', ') ? room.members!.map((e) => e.account.nick).join(', ')
: room.name!, : room.name!,
).fontSize(15), ).fontSize(15),
if (hasOnlineCount) onlineIndicator(),
], ],
); );
Widget compactHeaderWidget(SnChatRoom? room) => Row( Widget compactHeaderWidget(SnChatRoom? room) => Row(
spacing: 8, spacing: 8,
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.start,
children: [ children: [
SizedBox( SizedBox(
height: 26, height: 28,
width: 26, width: 28,
child: child:
(room!.type == 1 && room.picture?.id == null) (room!.type == 1 && room.picture?.id == null)
? SplitAvatarWidget( ? SplitAvatarWidget(
@@ -299,6 +328,8 @@ class ChatRoomScreen extends HookConsumerWidget {
? room.members!.map((e) => e.account.nick).join(', ') ? room.members!.map((e) => e.account.nick).join(', ')
: room.name!, : room.name!,
).fontSize(19), ).fontSize(19),
if (hasOnlineCount)
onlineIndicator().padding(left: 6),
], ],
); );
@@ -488,7 +519,7 @@ class ChatRoomScreen extends HookConsumerWidget {
appBar: AppBar( appBar: AppBar(
leading: !compactHeader ? const Center(child: PageBackButton()) : null, leading: !compactHeader ? const Center(child: PageBackButton()) : null,
automaticallyImplyLeading: false, automaticallyImplyLeading: false,
toolbarHeight: compactHeader ? null : 64, toolbarHeight: compactHeader ? null : 80,
title: chatRoom.when( title: chatRoom.when(
data: data:
(room) => (room) =>
@@ -556,33 +587,6 @@ class ChatRoomScreen extends HookConsumerWidget {
), ),
const Gap(8), const Gap(8),
], ],
bottom: () {
final hasProgress = isSyncing;
final hasOnlineCount = onlineCount.hasValue;
if (!hasProgress && !hasOnlineCount) return null;
return PreferredSize(
preferredSize: Size.fromHeight(
(hasProgress ? 2 : 0) + (hasOnlineCount ? 24 : 0),
),
child: Column(
children: [
if (hasProgress)
const LinearProgressIndicator(
borderRadius: BorderRadius.zero,
),
if (hasOnlineCount)
Container(
height: 24,
alignment: Alignment.center,
child: Text(
'${(onlineCount as AsyncData).value} online',
style: Theme.of(context).textTheme.bodySmall,
),
),
],
),
);
}(),
), ),
body: Stack( body: Stack(
children: [ children: [
@@ -678,12 +682,14 @@ class ChatRoomScreen extends HookConsumerWidget {
), ),
if (isSyncing) if (isSyncing)
Positioned( Positioned(
top: 16, top: 8,
right: 16, right: 16,
child: Container( child: Container(
padding: const EdgeInsets.all(8), padding: const EdgeInsets.all(8),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Theme.of(context).scaffoldBackgroundColor.withOpacity(0.8), color: Theme.of(
context,
).scaffoldBackgroundColor.withOpacity(0.8),
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
), ),
child: Row( child: Row(
@@ -695,7 +701,10 @@ class ChatRoomScreen extends HookConsumerWidget {
child: CircularProgressIndicator(strokeWidth: 2), child: CircularProgressIndicator(strokeWidth: 2),
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
Text('Syncing...', style: Theme.of(context).textTheme.bodySmall), Text(
'Syncing...',
style: Theme.of(context).textTheme.bodySmall,
),
], ],
), ),
), ),

View File

@@ -4,6 +4,7 @@ import 'dart:io';
import 'package:archive/archive.dart'; import 'package:archive/archive.dart';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_app_update/azhon_app_update.dart'; import 'package:flutter_app_update/azhon_app_update.dart';
@@ -596,7 +597,7 @@ class _UpdateSheetState extends State<_UpdateSheet> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final theme = Theme.of(context); final theme = Theme.of(context);
return SheetScaffold( return SheetScaffold(
titleText: 'Update available', titleText: 'updateAvailable'.tr(),
child: Padding( child: Padding(
padding: EdgeInsets.only( padding: EdgeInsets.only(
bottom: 16 + MediaQuery.of(context).padding.bottom, bottom: 16 + MediaQuery.of(context).padding.bottom,
@@ -624,14 +625,14 @@ class _UpdateSheetState extends State<_UpdateSheet> {
child: MarkdownTextContent( child: MarkdownTextContent(
content: content:
widget.release.body.isEmpty widget.release.body.isEmpty
? 'No changelog provided.' ? 'noChangelogProvided'.tr()
: widget.release.body, : widget.release.body,
), ),
), ),
), ),
if (!kIsWeb && Platform.isAndroid) if (!kIsWeb && Platform.isAndroid)
SwitchListTile( SwitchListTile(
title: const Text('Use secondary source for download'), title: Text('useSecondarySourceForDownload'.tr()),
value: _useProxy, value: _useProxy,
onChanged: (value) { onChanged: (value) {
setState(() { setState(() {
@@ -654,7 +655,7 @@ class _UpdateSheetState extends State<_UpdateSheet> {
_installUpdate(widget.androidUpdateUrl!); _installUpdate(widget.androidUpdateUrl!);
}, },
icon: const Icon(Symbols.update), icon: const Icon(Symbols.update),
label: const Text('Install update'), label: Text('installUpdate'.tr()),
), ),
), ),
if (!kIsWeb && if (!kIsWeb &&
@@ -673,14 +674,14 @@ class _UpdateSheetState extends State<_UpdateSheet> {
); );
}, },
icon: const Icon(Symbols.update), icon: const Icon(Symbols.update),
label: const Text('Install update'), label: Text('installUpdate'.tr()),
), ),
), ),
Expanded( Expanded(
child: FilledButton.icon( child: FilledButton.icon(
onPressed: widget.onOpen, onPressed: widget.onOpen,
icon: const Icon(Icons.open_in_new), icon: const Icon(Icons.open_in_new),
label: const Text('Open release page'), label: Text('openReleasePage'.tr()),
), ),
), ),
], ],