✨ Online indicator in chat
This commit is contained in:
@@ -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"
|
||||||
}
|
}
|
||||||
|
@@ -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,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@@ -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()),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
Reference in New Issue
Block a user