From f16e1126abb49bb8e317dd8783ccb3a60cf30810 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Thu, 5 Jun 2025 12:50:40 -0400 Subject: [PATCH 1/2] feat: add space view participant list --- lib/pages/chat_list/space_view.dart | 9 + lib/pages/chat_list/status_msg_list.dart | 25 ++- .../pages/pangea_chat_details.dart | 25 +-- .../widgets/leaderboard_participant_list.dart | 174 ++++++++++++++++++ 4 files changed, 211 insertions(+), 22 deletions(-) create mode 100644 lib/pangea/spaces/widgets/leaderboard_participant_list.dart diff --git a/lib/pages/chat_list/space_view.dart b/lib/pages/chat_list/space_view.dart index 1bde3fb21..7c1481c88 100644 --- a/lib/pages/chat_list/space_view.dart +++ b/lib/pages/chat_list/space_view.dart @@ -20,6 +20,7 @@ import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart'; import 'package:fluffychat/pangea/public_spaces/public_room_bottom_sheet.dart'; import 'package:fluffychat/pangea/spaces/constants/space_constants.dart'; import 'package:fluffychat/pangea/spaces/widgets/knocking_users_indicator.dart'; +import 'package:fluffychat/pangea/spaces/widgets/leaderboard_participant_list.dart'; import 'package:fluffychat/utils/localized_exception_extension.dart'; import 'package:fluffychat/utils/stream_extension.dart'; import 'package:fluffychat/widgets/adaptive_dialogs/show_ok_cancel_alert_dialog.dart'; @@ -802,6 +803,14 @@ class _SpaceViewState extends State { // }, // ), KnockingUsersIndicator(room: room), + SliverList.builder( + itemCount: 1, + itemBuilder: (context, i) { + return LeaderboardParticipantList( + space: room, + ); + }, + ), // Pangea# SliverList.builder( itemCount: joinedRooms.length, diff --git a/lib/pages/chat_list/status_msg_list.dart b/lib/pages/chat_list/status_msg_list.dart index 9a7972f32..15402c99a 100644 --- a/lib/pages/chat_list/status_msg_list.dart +++ b/lib/pages/chat_list/status_msg_list.dart @@ -98,11 +98,21 @@ class PresenceAvatar extends StatelessWidget { final CachedPresence presence; final double height; final void Function(Profile) onTap; + // #Pangea + final LinearGradient? gradient; + final Widget? floatingIndicator; + final bool showPresence; + // Pangea# const PresenceAvatar({ required this.presence, required this.height, required this.onTap, + // #Pangea + this.gradient, + this.showPresence = true, + this.floatingIndicator, + // Pangea# super.key, }); @@ -146,7 +156,11 @@ class PresenceAvatar extends StatelessWidget { Container( padding: const EdgeInsets.all(3), decoration: BoxDecoration( - gradient: presence.gradient, + // #Pangea + // gradient: presence.gradient, + gradient: gradient ?? + (showPresence ? presence.gradient : null), + // Pangea# borderRadius: BorderRadius.circular(avatarSize), ), @@ -159,7 +173,11 @@ class PresenceAvatar extends StatelessWidget { size: avatarSize - 6, ), ), - if (presence.userid == client.userID) + // #Pangea + // if (presence.userid == client.userID) + if (floatingIndicator == null && + presence.userid == client.userID) + // Pangea# Positioned( right: 0, bottom: 0, @@ -182,6 +200,9 @@ class PresenceAvatar extends StatelessWidget { ), ), ), + // #Pangea + if (floatingIndicator != null) floatingIndicator!, + // Pangea# if (statusMsg != null) ...[ Positioned( left: 0, diff --git a/lib/pangea/chat_settings/pages/pangea_chat_details.dart b/lib/pangea/chat_settings/pages/pangea_chat_details.dart index ece634751..de2cb01b5 100644 --- a/lib/pangea/chat_settings/pages/pangea_chat_details.dart +++ b/lib/pangea/chat_settings/pages/pangea_chat_details.dart @@ -8,7 +8,6 @@ import 'package:flutter_linkify/flutter_linkify.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/pages/chat_details/chat_details.dart'; import 'package:fluffychat/pages/chat_details/participant_list_item.dart'; @@ -23,6 +22,7 @@ import 'package:fluffychat/pangea/events/constants/pangea_event_types.dart'; import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart'; import 'package:fluffychat/pangea/spaces/utils/load_participants_util.dart'; import 'package:fluffychat/pangea/spaces/widgets/download_space_analytics_dialog.dart'; +import 'package:fluffychat/pangea/spaces/widgets/leaderboard_participant_list.dart'; import 'package:fluffychat/utils/fluffy_share.dart'; import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart'; import 'package:fluffychat/utils/url_launcher.dart'; @@ -721,22 +721,15 @@ class RoomParticipantsSection extends StatelessWidget { runAlignment: WrapAlignment.center, children: [ ...filteredParticipants.mapIndexed((index, user) { - Color? color = index == 0 - ? AppConfig.gold - : index == 1 - ? Colors.grey[400]! - : index == 2 - ? Colors.brown[400]! - : null; - final publicProfile = participantsLoader.getPublicProfile( user.id, ); + LinearGradient? gradient = index.leaderboardGradient; if (user.id == BotName.byEnvironment || publicProfile == null || publicProfile.level == null) { - color = null; + gradient = null; } return Padding( @@ -748,21 +741,13 @@ class RoomParticipantsSection extends StatelessWidget { Stack( alignment: Alignment.center, children: [ - if (color != null) + if (gradient != null) CircleAvatar( radius: _width / 2, child: Container( decoration: BoxDecoration( shape: BoxShape.circle, - gradient: LinearGradient( - begin: const Alignment(0.5, -0.5), - end: const Alignment(-0.5, 0.5), - colors: [ - color, - Colors.white, - color, - ], - ), + gradient: gradient, ), ), ) diff --git a/lib/pangea/spaces/widgets/leaderboard_participant_list.dart b/lib/pangea/spaces/widgets/leaderboard_participant_list.dart new file mode 100644 index 000000000..347d12cd2 --- /dev/null +++ b/lib/pangea/spaces/widgets/leaderboard_participant_list.dart @@ -0,0 +1,174 @@ +import 'package:flutter/material.dart'; + +import 'package:matrix/matrix.dart'; + +import 'package:fluffychat/config/app_config.dart'; +import 'package:fluffychat/config/themes.dart'; +import 'package:fluffychat/pages/chat_list/status_msg_list.dart'; +import 'package:fluffychat/pangea/bot/utils/bot_name.dart'; +import 'package:fluffychat/pangea/spaces/utils/load_participants_util.dart'; +import 'package:fluffychat/utils/stream_extension.dart'; +import 'package:fluffychat/widgets/adaptive_dialogs/user_dialog.dart'; +import 'package:fluffychat/widgets/matrix.dart'; +import 'package:fluffychat/widgets/presence_builder.dart'; + +class LeaderboardParticipantList extends StatefulWidget { + final Room space; + + const LeaderboardParticipantList({ + required this.space, + super.key, + }); + + static const double height = 116; + + @override + State createState() => + LeaderboardParticipantListState(); +} + +class LeaderboardParticipantListState + extends State { + final _scrollController = ScrollController(); + + @override + void dispose() { + _scrollController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final client = Matrix.of(context).client; + final theme = Theme.of(context); + + return StreamBuilder( + stream: client.onSync.stream.rateLimit(const Duration(seconds: 3)), + builder: (context, snapshot) { + return LoadParticipantsUtil( + space: widget.space, + builder: (participantsLoader) { + final participants = participantsLoader.filteredParticipants(""); + + return AnimatedSize( + duration: FluffyThemes.animationDuration, + curve: Curves.easeInOut, + child: SizedBox( + height: 130.0, + child: Scrollbar( + controller: _scrollController, + child: ListView.builder( + controller: _scrollController, + padding: const EdgeInsets.fromLTRB( + 8.0, + 8.0, + 8.0, + 16.0, + ), + scrollDirection: Axis.horizontal, + itemCount: participants.length, + itemBuilder: (context, i) { + final user = participants[i]; + final publicProfile = participantsLoader.getPublicProfile( + user.id, + ); + + LinearGradient? gradient = i.leaderboardGradient; + + if (user.id == BotName.byEnvironment || + publicProfile == null || + publicProfile.level == null) { + gradient = null; + } + + return PresenceBuilder( + userId: user.id, + builder: (context, presence) { + Color? dotColor; + if (presence != null) { + dotColor = presence.presence.isOnline + ? Colors.green + : presence.presence.isUnavailable + ? Colors.orange + : Colors.grey; + } + + return PresenceAvatar( + presence: presence ?? + CachedPresence( + PresenceType.unavailable, + null, + null, + null, + user.id, + ), + height: StatusMessageList.height, + onTap: (profile) => UserDialog.show( + context: context, + profile: profile, + ), + gradient: gradient, + showPresence: false, + floatingIndicator: Positioned( + bottom: 0, + right: 0, + child: Container( + width: 16, + height: 16, + decoration: BoxDecoration( + color: theme.colorScheme.surface, + borderRadius: BorderRadius.circular(32), + ), + alignment: Alignment.center, + child: Container( + width: 10, + height: 10, + decoration: BoxDecoration( + color: dotColor, + borderRadius: BorderRadius.circular(16), + border: Border.all( + width: 1, + color: theme.colorScheme.surface, + ), + ), + ), + ), + ), + ); + }, + ); + }, + ), + ), + ), + ); + }, + ); + }, + ); + } +} + +extension LeaderboardGradient on int { + LinearGradient? get leaderboardGradient { + final Color? color = this == 0 + ? AppConfig.gold + : this == 1 + ? Colors.grey[400]! + : this == 2 + ? Colors.brown[400]! + : null; + + if (color == null) return null; + + return LinearGradient( + colors: [ + color, + Colors.white, + color, + ], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ); + } +} From c180dc25ffaaca84a3282a6faba41ee7cac077b1 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Thu, 5 Jun 2025 13:03:39 -0400 Subject: [PATCH 2/2] chore: add ability to set status in settings --- lib/pages/settings/settings.dart | 30 +++++++++++++++++++++++++++ lib/pages/settings/settings_view.dart | 19 +++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/lib/pages/settings/settings.dart b/lib/pages/settings/settings.dart index 00acd3f38..6ea6f417d 100644 --- a/lib/pages/settings/settings.dart +++ b/lib/pages/settings/settings.dart @@ -207,6 +207,36 @@ class SettingsController extends State { // Pangea# } + // #Pangea + void setStatus() async { + final client = Matrix.of(context).client; + final currentPresence = await client.fetchCurrentPresence(client.userID!); + final input = await showTextInputDialog( + useRootNavigator: false, + context: context, + title: L10n.of(context).setStatus, + message: L10n.of(context).leaveEmptyToClearStatus, + okLabel: L10n.of(context).ok, + cancelLabel: L10n.of(context).cancel, + hintText: L10n.of(context).statusExampleMessage, + maxLines: 6, + minLines: 1, + maxLength: 255, + initialText: currentPresence.statusMsg, + ); + if (input == null) return; + if (!mounted) return; + await showFutureLoadingDialog( + context: context, + future: () => client.setPresence( + client.userID!, + PresenceType.online, + statusMsg: input, + ), + ); + } + // Pangea# + @override Widget build(BuildContext context) { final client = Matrix.of(context).client; diff --git a/lib/pages/settings/settings_view.dart b/lib/pages/settings/settings_view.dart index 4c51ea86d..ee6a74e51 100644 --- a/lib/pages/settings/settings_view.dart +++ b/lib/pages/settings/settings_view.dart @@ -165,6 +165,25 @@ class SettingsView extends StatelessWidget { // style: const TextStyle(fontSize: 12), ), ), + // #Pangea + TextButton.icon( + onPressed: controller.setStatus, + icon: const Icon( + Icons.add, + size: 14, + ), + style: TextButton.styleFrom( + foregroundColor: + theme.colorScheme.secondary, + iconColor: theme.colorScheme.secondary, + ), + label: Text( + L10n.of(context).setStatus, + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), + // Pangea# ], ), ),