fluffychat/lib/pangea/course_chats/course_chats_view.dart
Kelrap 2bb51da97b
What now button takes user to top of course plan page (#4946)
* Add scrollController to course details pages

* Make what now button refresh details tab if needed, remove scrollController
2025-12-29 11:20:17 -05:00

294 lines
10 KiB
Dart

import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:material_symbols_icons/symbols.dart';
import 'package:matrix/matrix.dart' as sdk;
import 'package:matrix/matrix.dart';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/config/themes.dart';
import 'package:fluffychat/l10n/l10n.dart';
import 'package:fluffychat/pages/chat_list/chat_list_item.dart';
import 'package:fluffychat/pangea/analytics_summary/learning_progress_indicators.dart';
import 'package:fluffychat/pangea/chat_settings/widgets/chat_context_menu_action.dart';
import 'package:fluffychat/pangea/course_chats/activity_template_chat_list_item.dart';
import 'package:fluffychat/pangea/course_chats/course_chats_page.dart';
import 'package:fluffychat/pangea/course_chats/course_default_chats_enum.dart';
import 'package:fluffychat/pangea/course_chats/unjoined_chat_list_item.dart';
import 'package:fluffychat/pangea/space_analytics/analytics_request_indicator.dart';
import 'package:fluffychat/pangea/spaces/knocking_users_indicator.dart';
import 'package:fluffychat/utils/stream_extension.dart';
import 'package:fluffychat/widgets/future_loading_dialog.dart';
class CourseChatsView extends StatelessWidget {
final CourseChatsController controller;
const CourseChatsView(
this.controller, {
super.key,
});
@override
Widget build(BuildContext context) {
final room = controller.room;
if (room == null) {
return const Center(
child: Icon(
Icons.search_outlined,
size: 80,
),
);
}
return StreamBuilder(
stream: room.client.onSync.stream
.where((s) => s.hasRoomUpdate)
.rateLimit(const Duration(seconds: 1)),
builder: (context, snapshot) {
final joinedChats = controller.joinedChats;
final joinedSessions = controller.joinedActivities();
final discoveredGroupChats = controller.discoveredGroupChats;
final discoveredSessions =
controller.discoveredActivities().entries.toList();
final isColumnMode = FluffyThemes.isColumnMode(context);
return Padding(
padding: isColumnMode
? const EdgeInsets.only(
top: 12.0,
left: 8.0,
right: 8.0,
)
: const EdgeInsets.all(0.0),
child: ListView.builder(
shrinkWrap: true,
itemCount: joinedChats.length +
joinedSessions.length +
discoveredGroupChats.length +
discoveredSessions.length +
9,
itemBuilder: (context, i) {
// courses chats title
if (i == 0) {
if (isColumnMode) {
return const Padding(
padding: EdgeInsets.symmetric(
vertical: 4.0,
horizontal: 8.0,
),
child: Column(
spacing: 12.0,
mainAxisSize: MainAxisSize.min,
children: [
LearningProgressIndicators(),
Icon(
Icons.chat_bubble_outline,
size: 30.0,
),
SizedBox(height: 12.0),
],
),
);
}
return const SizedBox();
}
i--;
if (i == 0) {
return KnockingUsersIndicator(room: room);
}
i--;
if (i == 0) {
return AnalyticsRequestIndicator(room: room);
}
i--;
if (i == 0) {
return _DefaultChatCreationTile(
type: CourseDefaultChatsEnum.introductions,
controller: controller,
);
}
i--;
if (i == 0) {
return _DefaultChatCreationTile(
type: CourseDefaultChatsEnum.announcements,
controller: controller,
);
}
i--;
// joined group chats
if (i < joinedChats.length) {
final joinedRoom = joinedChats[i];
return ChatListItem(
joinedRoom,
onTap: () => controller.onChatTap(joinedRoom),
onLongPress: (c) => chatContextMenuAction(
joinedRoom,
c,
context,
() => controller.onChatTap(joinedRoom),
),
activeChat: controller.widget.activeChat == joinedRoom.id,
);
}
i -= joinedChats.length;
// unjoined group chats
if (i < discoveredGroupChats.length) {
return UnjoinedChatListItem(
chunk: discoveredGroupChats[i],
onTap: () =>
controller.joinChildRoom(discoveredGroupChats[i]),
);
}
i -= discoveredGroupChats.length;
if (i == 0) {
return joinedSessions.isEmpty && discoveredSessions.isEmpty
? ListTile(
leading: const Icon(Icons.map_outlined),
title: Text(L10n.of(context).whatNow),
subtitle: Text(L10n.of(context).chooseNextActivity),
trailing: const Icon(Icons.arrow_forward),
onTap: () => context.pushReplacement(
"/rooms/spaces/${room.id}/details?tab=course",
),
)
: const SizedBox();
}
i--;
if (i == 0) {
return joinedSessions.isEmpty
? const SizedBox()
: Padding(
padding: const EdgeInsets.only(
top: 20.0,
bottom: 4.0,
),
child: Text(
L10n.of(context).myActivities,
style: const TextStyle(fontSize: 12.0),
textAlign: TextAlign.center,
),
);
}
i--;
// joined activity sessions
if (i < joinedSessions.length) {
final joinedRoom = joinedSessions[i];
return ChatListItem(
joinedRoom,
onTap: () => controller.onChatTap(joinedRoom),
onLongPress: (c) => chatContextMenuAction(
joinedRoom,
c,
context,
() => controller.onChatTap(joinedRoom),
),
activeChat: controller.widget.activeChat == joinedRoom.id,
borderRadius: BorderRadius.circular(
AppConfig.borderRadius / 2,
),
);
}
i -= joinedSessions.length;
if (i == 0) {
return discoveredSessions.isEmpty
? const SizedBox()
: Padding(
padding: const EdgeInsets.only(
top: 20.0,
bottom: 4.0,
),
child: Text(
L10n.of(context).openToJoin,
style: const TextStyle(fontSize: 12.0),
textAlign: TextAlign.center,
),
);
}
i--;
// unjoined activity sessions
if (i < discoveredSessions.length) {
final activity = discoveredSessions[i].key;
final sessions = discoveredSessions[i].value;
return ActivityTemplateChatListItem(
space: room,
sessions: sessions,
joinActivity: (e) => controller.joinActivity(activity, e),
);
}
i -= discoveredSessions.length;
if (controller.noMoreRooms) {
return const SizedBox();
}
return Padding(
padding: const EdgeInsets.symmetric(
horizontal: 12.0,
vertical: 2.0,
),
child: TextButton(
onPressed:
controller.isLoading ? null : controller.loadHierarchy,
child: controller.isLoading
? LinearProgressIndicator(
borderRadius: BorderRadius.circular(
AppConfig.borderRadius,
),
)
: Text(L10n.of(context).loadMore),
),
);
},
),
);
},
);
}
}
class _DefaultChatCreationTile extends StatelessWidget {
final CourseDefaultChatsEnum type;
final CourseChatsController controller;
const _DefaultChatCreationTile({
required this.type,
required this.controller,
});
@override
Widget build(BuildContext context) {
if (!controller.showDefaultChatCreation(type)) {
return const SizedBox();
}
final l10n = L10n.of(context);
return ListTile(
leading: const Icon(Symbols.chat_add_on),
title: Text(type.creationTitle(l10n)),
subtitle: Text(type.creationDesc(l10n)),
trailing: IconButton(
icon: const Icon(Icons.close),
onPressed: () => showFutureLoadingDialog(
context: context,
future: () => controller.dismissDefaultChatCreation(type),
),
),
onTap: () => showFutureLoadingDialog(
context: context,
future: () => controller.createDefaultChat(type),
),
);
}
}