feat: notification in space view for knocking users (#2068)
This commit is contained in:
parent
50af914f86
commit
6914d9d0d7
5 changed files with 215 additions and 47 deletions
|
|
@ -4871,5 +4871,6 @@
|
|||
"type": "String"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"knockSpaceSuccess": "You have requested to join this space! An admin will respond to your request when they receive it 😀"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import 'package:fluffychat/pages/chat_list/search_title.dart';
|
|||
import 'package:fluffychat/pangea/chat/constants/default_power_level.dart';
|
||||
import 'package:fluffychat/pangea/chat_settings/constants/pangea_room_types.dart';
|
||||
import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart';
|
||||
import 'package:fluffychat/pangea/spaces/widgets/knocking_users_indicator.dart';
|
||||
import 'package:fluffychat/utils/adaptive_bottom_sheet.dart';
|
||||
import 'package:fluffychat/utils/localized_exception_extension.dart';
|
||||
import 'package:fluffychat/utils/stream_extension.dart';
|
||||
|
|
@ -717,6 +718,9 @@ class _SpaceViewState extends State<SpaceView> {
|
|||
);
|
||||
},
|
||||
),
|
||||
// #Pangea
|
||||
KnockingUsersIndicator(room: room),
|
||||
// Pangea#
|
||||
SliverList.builder(
|
||||
itemCount: joinedRooms.length,
|
||||
itemBuilder: (context, i) {
|
||||
|
|
|
|||
109
lib/pangea/spaces/widgets/knocking_users_indicator.dart
Normal file
109
lib/pangea/spaces/widgets/knocking_users_indicator.dart
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/config/themes.dart';
|
||||
import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart';
|
||||
import 'package:fluffychat/utils/stream_extension.dart';
|
||||
|
||||
class KnockingUsersIndicator extends StatefulWidget {
|
||||
final Room room;
|
||||
const KnockingUsersIndicator({
|
||||
super.key,
|
||||
required this.room,
|
||||
});
|
||||
|
||||
@override
|
||||
KnockingUsersIndicatorState createState() => KnockingUsersIndicatorState();
|
||||
}
|
||||
|
||||
class KnockingUsersIndicatorState extends State<KnockingUsersIndicator> {
|
||||
List<User> _knockingUsers = [];
|
||||
StreamSubscription? _memberSubscription;
|
||||
|
||||
KnockingUsersIndicatorState();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_memberSubscription ??= widget.room.client.onRoomState.stream
|
||||
.where(_isMemberUpdate)
|
||||
.rateLimit(const Duration(seconds: 1))
|
||||
.listen((_) => _setKnockingUsers());
|
||||
_setKnockingUsers();
|
||||
}
|
||||
|
||||
bool _isMemberUpdate(({String roomId, StrippedStateEvent state}) event) =>
|
||||
event.roomId == widget.room.id &&
|
||||
event.state.type == EventTypes.RoomMember;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_memberSubscription?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> _setKnockingUsers({bool loadParticipants = false}) async {
|
||||
_knockingUsers = loadParticipants
|
||||
? await widget.room.requestParticipants([Membership.knock])
|
||||
: widget.room.getParticipants([Membership.knock]);
|
||||
if (mounted) setState(() {});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SliverList.builder(
|
||||
itemCount: 1,
|
||||
itemBuilder: (context, i) {
|
||||
return AnimatedSize(
|
||||
duration: FluffyThemes.animationDuration,
|
||||
child: _knockingUsers.isEmpty || !widget.room.isRoomAdmin
|
||||
? const SizedBox()
|
||||
: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 4,
|
||||
vertical: 1,
|
||||
),
|
||||
child: Material(
|
||||
borderRadius: BorderRadius.circular(
|
||||
AppConfig.borderRadius,
|
||||
),
|
||||
clipBehavior: Clip.hardEdge,
|
||||
child: ListTile(
|
||||
minVerticalPadding: 0,
|
||||
trailing: Icon(
|
||||
Icons.adaptive.arrow_forward_outlined,
|
||||
size: 16,
|
||||
),
|
||||
title: Row(
|
||||
spacing: 8.0,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.notifications_outlined,
|
||||
color: Theme.of(context).colorScheme.error,
|
||||
),
|
||||
Expanded(
|
||||
child: Text(
|
||||
_knockingUsers.length == 1
|
||||
? "1 user is requesting to join your space"
|
||||
: "${_knockingUsers.length} users are requesting to join your space",
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
onTap: () => context.push(
|
||||
"/rooms/${widget.room.id}/details/members",
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -22,6 +22,7 @@ Future<Result<T>> showFutureLoadingDialog<T>({
|
|||
ExceptionContext? exceptionContext,
|
||||
// #Pangea
|
||||
String? Function(Object, StackTrace?)? onError,
|
||||
String? Function()? onSuccess,
|
||||
VoidCallback? onDismiss,
|
||||
// Pangea#
|
||||
}) async {
|
||||
|
|
@ -55,6 +56,7 @@ Future<Result<T>> showFutureLoadingDialog<T>({
|
|||
// #Pangea
|
||||
onError: onError,
|
||||
onDismiss: onDismiss,
|
||||
onSuccess: onSuccess,
|
||||
// Pangea#
|
||||
),
|
||||
);
|
||||
|
|
@ -80,6 +82,7 @@ class LoadingDialog<T> extends StatefulWidget {
|
|||
final ExceptionContext? exceptionContext;
|
||||
// #Pangea
|
||||
final String? Function(Object, StackTrace?)? onError;
|
||||
final String? Function()? onSuccess;
|
||||
final VoidCallback? onDismiss;
|
||||
// Pangea#
|
||||
|
||||
|
|
@ -91,6 +94,7 @@ class LoadingDialog<T> extends StatefulWidget {
|
|||
this.exceptionContext,
|
||||
// #Pangea
|
||||
this.onError,
|
||||
this.onSuccess,
|
||||
this.onDismiss,
|
||||
// Pangea#
|
||||
});
|
||||
|
|
@ -101,40 +105,57 @@ class LoadingDialog<T> extends StatefulWidget {
|
|||
class LoadingDialogState<T> extends State<LoadingDialog> {
|
||||
Object? exception;
|
||||
StackTrace? stackTrace;
|
||||
// #Pangea
|
||||
Object? _result;
|
||||
String? _successMessage;
|
||||
// Pangea#
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
widget.future.then(
|
||||
// #Pangea
|
||||
// (result) => Navigator.of(context).pop<Result<T>>(Result.value(result)),
|
||||
// onError: (e, s) => setState(() {
|
||||
// exception = e;
|
||||
// stackTrace = s;
|
||||
// }),
|
||||
(result) {
|
||||
if (mounted && Navigator.of(context).canPop()) {
|
||||
Navigator.of(context).pop<Result<T>>(Result.value(result));
|
||||
}
|
||||
},
|
||||
onError: (e, s) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
exception = widget.onError?.call(e, s) ?? e;
|
||||
stackTrace = s;
|
||||
});
|
||||
}
|
||||
},
|
||||
// Pangea#
|
||||
// #Pangea
|
||||
// widget.future.then(
|
||||
// (result) => Navigator.of(context).pop<Result<T>>(Result.value(result)),
|
||||
// onError: (e, s) => setState(() {
|
||||
// exception = e;
|
||||
// stackTrace = s;
|
||||
// }),
|
||||
// );
|
||||
WidgetsBinding.instance.addPostFrameCallback(
|
||||
(_) => widget.future.then(
|
||||
(result) {
|
||||
if (mounted && widget.onSuccess != null) {
|
||||
_successMessage = widget.onSuccess!();
|
||||
_result = result;
|
||||
setState(() {});
|
||||
} else if (mounted && Navigator.of(context).canPop()) {
|
||||
Navigator.of(context).pop<Result<T>>(Result.value(result));
|
||||
}
|
||||
},
|
||||
onError: (e, s) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
exception = widget.onError?.call(e, s) ?? e;
|
||||
stackTrace = s;
|
||||
});
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
// Pangea#
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final exception = this.exception;
|
||||
// #Pangea
|
||||
// final titleLabel = exception != null
|
||||
// ? exception.toLocalizedString(context, widget.exceptionContext)
|
||||
// : widget.title ?? L10n.of(context).loadingPleaseWait;
|
||||
final titleLabel = exception != null
|
||||
? exception.toLocalizedString(context, widget.exceptionContext)
|
||||
: widget.title ?? L10n.of(context).loadingPleaseWait;
|
||||
: _successMessage ?? widget.title ?? L10n.of(context).loadingPleaseWait;
|
||||
// Pangea#
|
||||
|
||||
return AlertDialog.adaptive(
|
||||
title: exception == null
|
||||
|
|
@ -149,7 +170,10 @@ class LoadingDialogState<T> extends State<LoadingDialog> {
|
|||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
if (exception == null) ...[
|
||||
// #Pangea
|
||||
// if (exception == null) ...[
|
||||
if (exception == null && _successMessage == null) ...[
|
||||
// Pangea#
|
||||
const CircularProgressIndicator.adaptive(),
|
||||
const SizedBox(width: 20),
|
||||
],
|
||||
|
|
@ -157,39 +181,65 @@ class LoadingDialogState<T> extends State<LoadingDialog> {
|
|||
child: Text(
|
||||
titleLabel,
|
||||
maxLines: 4,
|
||||
textAlign: exception == null ? TextAlign.left : null,
|
||||
// #Pangea
|
||||
// textAlign: exception == null ? TextAlign.left : null,
|
||||
textAlign: exception == null && _successMessage == null
|
||||
? TextAlign.left
|
||||
: null,
|
||||
// Pangea#
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
actions: exception == null
|
||||
// #Pangea
|
||||
// ? null
|
||||
? widget.onDismiss != null
|
||||
? [
|
||||
AdaptiveDialogAction(
|
||||
onPressed: () {
|
||||
widget.onDismiss!();
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: Text(L10n.of(context).cancel),
|
||||
),
|
||||
]
|
||||
: null
|
||||
// Pangea#
|
||||
: [
|
||||
// #Pangea
|
||||
// actions: exception == null
|
||||
// ? null
|
||||
// : [
|
||||
// AdaptiveDialogAction(
|
||||
// onPressed: () => Navigator.of(context).pop<Result<T>>(
|
||||
// Result.error(
|
||||
// exception,
|
||||
// stackTrace,
|
||||
// ),
|
||||
// ),
|
||||
// child: Text(widget.backLabel ?? L10n.of(context).close),
|
||||
// ),
|
||||
// ],
|
||||
actions: _successMessage != null
|
||||
? [
|
||||
AdaptiveDialogAction(
|
||||
onPressed: () => Navigator.of(context).pop<Result<T>>(
|
||||
Result.error(
|
||||
exception,
|
||||
stackTrace,
|
||||
),
|
||||
Result.value(_result as T),
|
||||
),
|
||||
child: Text(widget.backLabel ?? L10n.of(context).close),
|
||||
child: Text(L10n.of(context).close),
|
||||
),
|
||||
],
|
||||
]
|
||||
: exception == null
|
||||
? widget.onDismiss != null
|
||||
? [
|
||||
AdaptiveDialogAction(
|
||||
onPressed: () {
|
||||
widget.onDismiss!();
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: Text(L10n.of(context).cancel),
|
||||
),
|
||||
]
|
||||
: null
|
||||
: [
|
||||
AdaptiveDialogAction(
|
||||
onPressed: () => Navigator.of(context).pop<Result<T>>(
|
||||
Result.error(
|
||||
exception,
|
||||
stackTrace,
|
||||
),
|
||||
),
|
||||
child: Text(widget.backLabel ?? L10n.of(context).close),
|
||||
),
|
||||
],
|
||||
// Pangea#
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -90,6 +90,10 @@ class PublicRoomBottomSheetState extends State<PublicRoomBottomSheet> {
|
|||
}
|
||||
return roomId;
|
||||
},
|
||||
// #Pangea
|
||||
onSuccess: wasInRoom ? null : () => L10n.of(context).knockSpaceSuccess,
|
||||
delay: false,
|
||||
// Pangea#
|
||||
);
|
||||
// #Pangea
|
||||
// if (knock) {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue