3822 begin activity design implementation (#3827)
* file reorganization * added activity summary widget to show in chat view and activity launch view * more updates to activity sessions start page * function to ping course * remove duplicate loading of participants * nav bar visibility changes * add generalized image from url widget * update bottom of screen activity status message and add summaries to chat event list * scroll to summary on click * show invited activity sessions in course chats view
This commit is contained in:
parent
a446229242
commit
677e9ce594
40 changed files with 1383 additions and 980 deletions
|
|
@ -578,13 +578,17 @@ abstract class AppRoutes {
|
|||
pageBuilder: (context, state) => defaultPageBuilder(
|
||||
context,
|
||||
state,
|
||||
ChatDetails(
|
||||
roomId: state.pathParameters['spaceid']!,
|
||||
),
|
||||
const EmptyPage(),
|
||||
),
|
||||
redirect: loggedOutRedirect,
|
||||
redirect: (context, state) {
|
||||
final subroute =
|
||||
state.fullPath?.split(":spaceid/details").last ??
|
||||
"";
|
||||
return "/rooms/spaces/${state.pathParameters['spaceid']}$subroute";
|
||||
},
|
||||
routes: roomDetailsRoutes('spaceid'),
|
||||
),
|
||||
...roomDetailsRoutes('spaceid'),
|
||||
GoRoute(
|
||||
path: ':roomid',
|
||||
pageBuilder: (context, state) {
|
||||
|
|
|
|||
|
|
@ -5243,5 +5243,26 @@
|
|||
"myActivitySessions": "My Activity Sessions",
|
||||
"directMessages": "Direct Messages",
|
||||
"whatNow": "What now?",
|
||||
"chooseNextActivity": "Choose your next activity!"
|
||||
"chooseNextActivity": "Choose your next activity!",
|
||||
"seeInstructions": "See Instructions",
|
||||
"hideInstructions": "Hide Instructions",
|
||||
"letsGo": "Let’s go!",
|
||||
"chooseRole": "Choose a role!",
|
||||
"chooseRoleToParticipate": "Choose a role to participate!",
|
||||
"waitingToFillRole": "Waiting to fill {num} roles...",
|
||||
"@waitingToFillRole": {
|
||||
"type": "int",
|
||||
"placeholders": {
|
||||
"num": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"pingParticipants": "Ping course participants",
|
||||
"playWithBot": "Play with Pangea Bot",
|
||||
"inviteFriends": "Invite friends",
|
||||
"waitNotDone": "Wait I’m not done!",
|
||||
"waitingForOthersToFinish": "Waiting for the rest to finish up...",
|
||||
"saveToCompletedActivities": "Save to completed activities",
|
||||
"generatingSummary": "Analyzing chat and generating results"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,6 +29,8 @@ import 'package:fluffychat/pages/chat/events/audio_player.dart';
|
|||
import 'package:fluffychat/pages/chat/recording_dialog.dart';
|
||||
import 'package:fluffychat/pages/chat_details/chat_details.dart';
|
||||
import 'package:fluffychat/pangea/activity_sessions/activity_role_model.dart';
|
||||
import 'package:fluffychat/pangea/activity_sessions/activity_room_extension.dart';
|
||||
import 'package:fluffychat/pangea/activity_sessions/activity_session_start/activity_session_start_page.dart';
|
||||
import 'package:fluffychat/pangea/analytics_misc/construct_type_enum.dart';
|
||||
import 'package:fluffychat/pangea/analytics_misc/constructs_model.dart';
|
||||
import 'package:fluffychat/pangea/analytics_misc/gain_points_animation.dart';
|
||||
|
|
@ -56,6 +58,7 @@ import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart';
|
|||
import 'package:fluffychat/pangea/instructions/instructions_enum.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/constants/language_constants.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/widgets/p_language_dialog.dart';
|
||||
import 'package:fluffychat/pangea/spaces/utils/load_participants_util.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/enums/message_mode_enum.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/widgets/message_selection_overlay.dart';
|
||||
import 'package:fluffychat/utils/error_reporter.dart';
|
||||
|
|
@ -100,7 +103,6 @@ class ChatPage extends StatelessWidget {
|
|||
Widget build(BuildContext context) {
|
||||
final room = Matrix.of(context).client.getRoomById(roomId);
|
||||
// #Pangea
|
||||
|
||||
if (room?.isSpace == true &&
|
||||
GoRouterState.of(context).fullPath?.endsWith(":roomid") == true) {
|
||||
ErrorHandler.logError(
|
||||
|
|
@ -788,6 +790,7 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
_analyticsSubscription?.cancel();
|
||||
_botAudioSubscription?.cancel();
|
||||
_router.routeInformationProvider.removeListener(_onRouteChanged);
|
||||
carouselController.dispose();
|
||||
//Pangea#
|
||||
super.dispose();
|
||||
}
|
||||
|
|
@ -2204,11 +2207,17 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
);
|
||||
}
|
||||
|
||||
ActivityRoleModel? highlightedRole;
|
||||
final ScrollController carouselController = ScrollController();
|
||||
|
||||
ActivityRoleModel? highlightedRole;
|
||||
void highlightRole(ActivityRoleModel role) {
|
||||
if (mounted) setState(() => highlightedRole = role);
|
||||
}
|
||||
|
||||
bool showInstructions = false;
|
||||
void toggleShowInstructions() {
|
||||
if (mounted) setState(() => showInstructions = !showInstructions);
|
||||
}
|
||||
// Pangea#
|
||||
|
||||
late final ValueNotifier<bool> _displayChatDetailsColumn;
|
||||
|
|
@ -2223,43 +2232,59 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
return Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: ChatView(this),
|
||||
),
|
||||
ValueListenableBuilder(
|
||||
valueListenable: _displayChatDetailsColumn,
|
||||
builder: (context, displayChatDetailsColumn, _) =>
|
||||
!FluffyThemes.isThreeColumnMode(context) ||
|
||||
room.membership != Membership.join ||
|
||||
!displayChatDetailsColumn
|
||||
? const SizedBox(
|
||||
height: double.infinity,
|
||||
width: 0,
|
||||
)
|
||||
: Container(
|
||||
width: FluffyThemes.columnWidth,
|
||||
clipBehavior: Clip.hardEdge,
|
||||
decoration: BoxDecoration(
|
||||
border: Border(
|
||||
left: BorderSide(
|
||||
width: 1,
|
||||
color: theme.dividerColor,
|
||||
// #Pangea
|
||||
return LoadParticipantsBuilder(
|
||||
room: room,
|
||||
builder: (context, participants) {
|
||||
if (!room.participantListComplete && participants.loading) {
|
||||
return const Center(
|
||||
child: CircularProgressIndicator.adaptive(),
|
||||
);
|
||||
}
|
||||
|
||||
if (room.isActivitySession == true && !room.activityHasStarted) {
|
||||
return ActivitySessionStartPage(room: room);
|
||||
}
|
||||
// Pangea#
|
||||
final theme = Theme.of(context);
|
||||
return Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: ChatView(this),
|
||||
),
|
||||
ValueListenableBuilder(
|
||||
valueListenable: _displayChatDetailsColumn,
|
||||
builder: (context, displayChatDetailsColumn, _) =>
|
||||
!FluffyThemes.isThreeColumnMode(context) ||
|
||||
room.membership != Membership.join ||
|
||||
!displayChatDetailsColumn
|
||||
? const SizedBox(
|
||||
height: double.infinity,
|
||||
width: 0,
|
||||
)
|
||||
: Container(
|
||||
width: FluffyThemes.columnWidth,
|
||||
clipBehavior: Clip.hardEdge,
|
||||
decoration: BoxDecoration(
|
||||
border: Border(
|
||||
left: BorderSide(
|
||||
width: 1,
|
||||
color: theme.dividerColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
child: ChatDetails(
|
||||
roomId: roomId,
|
||||
embeddedCloseButton: IconButton(
|
||||
icon: const Icon(Icons.close),
|
||||
onPressed: toggleDisplayChatDetailsColumn,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
child: ChatDetails(
|
||||
roomId: roomId,
|
||||
embeddedCloseButton: IconButton(
|
||||
icon: const Icon(Icons.close),
|
||||
onPressed: toggleDisplayChatDetailsColumn,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,8 @@ import 'package:fluffychat/pages/chat/events/message.dart';
|
|||
import 'package:fluffychat/pages/chat/seen_by_row.dart';
|
||||
import 'package:fluffychat/pages/chat/typing_indicators.dart';
|
||||
import 'package:fluffychat/pangea/activity_planner/activity_plan_message.dart';
|
||||
import 'package:fluffychat/pangea/activity_sessions/activity_finished_status_message.dart';
|
||||
import 'package:fluffychat/pangea/activity_sessions/activity_session_chat/activity_finished_status_message.dart';
|
||||
import 'package:fluffychat/pangea/activity_sessions/activity_user_summaries_widget.dart';
|
||||
import 'package:fluffychat/pangea/events/extensions/pangea_event_extension.dart';
|
||||
import 'package:fluffychat/utils/account_config.dart';
|
||||
import 'package:fluffychat/utils/matrix_sdk_extensions/filtered_timeline_extension.dart';
|
||||
|
|
@ -94,7 +95,7 @@ class ChatEventList extends StatelessWidget {
|
|||
// Request history button or progress indicator:
|
||||
// #Pangea
|
||||
// if (i == events.length + 1) {
|
||||
if (i == events.length + 2) {
|
||||
if (i == events.length + 3) {
|
||||
// Pangea#
|
||||
if (timeline.isRequestingHistory) {
|
||||
return const Center(
|
||||
|
|
@ -127,11 +128,15 @@ class ChatEventList extends StatelessWidget {
|
|||
if (i == 1) {
|
||||
return ActivityFinishedStatusMessage(controller: controller);
|
||||
}
|
||||
|
||||
if (i == 2) {
|
||||
return ActivityUserSummaries(controller: controller);
|
||||
}
|
||||
// Pangea#
|
||||
|
||||
// #Pangea
|
||||
// i--;
|
||||
i = i - 2;
|
||||
i = i - 3;
|
||||
// Pangea#
|
||||
|
||||
// The message at this index:
|
||||
|
|
@ -209,7 +214,7 @@ class ChatEventList extends StatelessWidget {
|
|||
},
|
||||
// #Pangea
|
||||
// childCount: events.length + 2,
|
||||
childCount: events.length + 3,
|
||||
childCount: events.length + 4,
|
||||
// Pangea#
|
||||
findChildIndexCallback: (key) =>
|
||||
controller.findChildIndexCallback(key, thisEventsKeyMap),
|
||||
|
|
|
|||
|
|
@ -13,9 +13,9 @@ import 'package:fluffychat/pages/chat/chat_app_bar_list_tile.dart';
|
|||
import 'package:fluffychat/pages/chat/chat_app_bar_title.dart';
|
||||
import 'package:fluffychat/pages/chat/chat_event_list.dart';
|
||||
import 'package:fluffychat/pages/chat/pinned_events.dart';
|
||||
import 'package:fluffychat/pangea/activity_sessions/activity_pinned_message.dart';
|
||||
import 'package:fluffychat/pangea/activity_sessions/activity_room_extension.dart';
|
||||
import 'package:fluffychat/pangea/activity_sessions/activity_status_message.dart';
|
||||
import 'package:fluffychat/pangea/activity_sessions/activity_session_chat/activity_pinned_message.dart';
|
||||
import 'package:fluffychat/pangea/activity_sessions/activity_session_chat/load_activity_summary_widget.dart';
|
||||
import 'package:fluffychat/pangea/chat/widgets/chat_input_bar.dart';
|
||||
import 'package:fluffychat/pangea/chat/widgets/chat_input_bar_header.dart';
|
||||
import 'package:fluffychat/pangea/chat/widgets/chat_view_background.dart';
|
||||
|
|
@ -435,7 +435,8 @@ class ChatView extends StatelessWidget {
|
|||
height: controller.inputBarHeight,
|
||||
),
|
||||
),
|
||||
ActivityStatusMessage(room: controller.room),
|
||||
if (controller.room.activityIsFinished)
|
||||
LoadActivitySummaryWidget(room: controller.room),
|
||||
// Pangea#
|
||||
],
|
||||
),
|
||||
|
|
|
|||
|
|
@ -10,10 +10,9 @@ import 'package:fluffychat/l10n/l10n.dart';
|
|||
import 'package:fluffychat/pages/chat/chat.dart';
|
||||
import 'package:fluffychat/pages/chat/events/pangea_message_reactions.dart';
|
||||
import 'package:fluffychat/pages/chat/events/room_creation_state_event.dart';
|
||||
import 'package:fluffychat/pangea/activity_sessions/activity_creation_state_event.dart';
|
||||
import 'package:fluffychat/pangea/activity_sessions/activity_roles_event.dart';
|
||||
import 'package:fluffychat/pangea/activity_sessions/activity_room_extension.dart';
|
||||
import 'package:fluffychat/pangea/activity_sessions/activity_state_event.dart';
|
||||
import 'package:fluffychat/pangea/activity_sessions/activity_session_chat/activity_roles_event_widget.dart';
|
||||
import 'package:fluffychat/pangea/activity_sessions/activity_summary_widget.dart';
|
||||
import 'package:fluffychat/pangea/chat/extensions/custom_room_display_extension.dart';
|
||||
import 'package:fluffychat/pangea/common/widgets/pressable_button.dart';
|
||||
import 'package:fluffychat/pangea/events/constants/pangea_event_types.dart';
|
||||
|
|
@ -131,15 +130,22 @@ class Message extends StatelessWidget {
|
|||
if (event.type == EventTypes.RoomCreate) {
|
||||
// #Pangea
|
||||
// return RoomCreationStateEvent(event: event);
|
||||
return event.room.activityPlan != null
|
||||
? ActivityCreationStateEvent(event: event)
|
||||
return event.room.isActivitySession
|
||||
? const SizedBox(height: 60.0)
|
||||
: RoomCreationStateEvent(event: event);
|
||||
// Pangea#
|
||||
}
|
||||
|
||||
// #Pangea
|
||||
if (event.type == PangeaEventTypes.activityPlan) {
|
||||
return ActivityStateEvent(event: event);
|
||||
return ActivitySummary(
|
||||
room: event.room,
|
||||
showInstructions: controller.showInstructions,
|
||||
toggleInstructions: controller.toggleShowInstructions,
|
||||
getParticipantOpacity: (role) =>
|
||||
role == null || role.isFinished ? 0.5 : 1.0,
|
||||
isParticipantSelected: (id) => controller.room.ownRole?.id == id,
|
||||
);
|
||||
}
|
||||
|
||||
if (event.type == PangeaEventTypes.activityRole) {
|
||||
|
|
|
|||
|
|
@ -12,12 +12,12 @@ import 'package:fluffychat/l10n/l10n.dart';
|
|||
import 'package:fluffychat/pangea/activity_planner/activity_planner_builder.dart';
|
||||
import 'package:fluffychat/pangea/activity_suggestions/activity_suggestion_dialog.dart';
|
||||
import 'package:fluffychat/pangea/chat_settings/widgets/language_level_dropdown.dart';
|
||||
import 'package:fluffychat/pangea/common/widgets/url_image_widget.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/enums/language_level_type_enum.dart';
|
||||
import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart';
|
||||
import 'package:fluffychat/widgets/avatar.dart';
|
||||
import 'package:fluffychat/widgets/future_loading_dialog.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
import 'package:fluffychat/widgets/mxc_image.dart';
|
||||
|
||||
class ActivityPlanCard extends StatelessWidget {
|
||||
final VoidCallback regenerate;
|
||||
|
|
@ -177,57 +177,15 @@ class ActivityPlanCard extends StatelessWidget {
|
|||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
controller.updatedActivity.imageURL != null
|
||||
? ClipRRect(
|
||||
borderRadius:
|
||||
BorderRadius.circular(4.0),
|
||||
child: controller
|
||||
.updatedActivity.imageURL!
|
||||
.startsWith("mxc")
|
||||
? MxcImage(
|
||||
uri: Uri.parse(
|
||||
controller.updatedActivity
|
||||
.imageURL!,
|
||||
),
|
||||
width: 24.0,
|
||||
height: 24.0,
|
||||
cacheKey: controller
|
||||
.updatedActivity.activityId,
|
||||
fit: BoxFit.cover,
|
||||
)
|
||||
: CachedNetworkImage(
|
||||
imageRenderMethodForWeb:
|
||||
ImageRenderMethodForWeb
|
||||
.HttpGet,
|
||||
imageUrl: controller
|
||||
.updatedActivity.imageURL!,
|
||||
httpHeaders: {
|
||||
'Authorization':
|
||||
'Bearer ${MatrixState.pangeaController.userController.accessToken}',
|
||||
},
|
||||
fit: BoxFit.cover,
|
||||
width: 24.0,
|
||||
height: 24.0,
|
||||
placeholder: (
|
||||
context,
|
||||
url,
|
||||
) =>
|
||||
const Center(
|
||||
child:
|
||||
CircularProgressIndicator(),
|
||||
),
|
||||
errorWidget: (
|
||||
context,
|
||||
url,
|
||||
error,
|
||||
) =>
|
||||
const SizedBox(),
|
||||
),
|
||||
)
|
||||
: const Icon(
|
||||
Icons.event_note_outlined,
|
||||
size: 24.0,
|
||||
),
|
||||
ImageByUrl(
|
||||
imageUrl: controller.updatedActivity.imageURL,
|
||||
width: 24.0,
|
||||
borderRadius: BorderRadius.circular(4.0),
|
||||
replacement: const Icon(
|
||||
Icons.event_note_outlined,
|
||||
size: 24.0,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: itemPadding),
|
||||
Expanded(
|
||||
child: Text(
|
||||
|
|
|
|||
|
|
@ -10,11 +10,11 @@ import 'package:matrix/matrix.dart';
|
|||
|
||||
import 'package:fluffychat/pangea/activity_planner/activity_plan_model.dart';
|
||||
import 'package:fluffychat/pangea/activity_planner/activity_plan_request.dart';
|
||||
import 'package:fluffychat/pangea/activity_sessions/activity_room_extension.dart';
|
||||
import 'package:fluffychat/pangea/activity_suggestions/activity_plan_repo.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/common/utils/error_handler.dart';
|
||||
import 'package:fluffychat/pangea/events/constants/pangea_event_types.dart';
|
||||
import 'package:fluffychat/pangea/extensions/join_rule_extension.dart';
|
||||
import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/enums/language_level_type_enum.dart';
|
||||
|
|
@ -348,6 +348,10 @@ class ActivityPlannerBuilderState extends State<ActivityPlannerBuilder> {
|
|||
visibility: Visibility.private,
|
||||
name: "${updatedActivity.title} ${index + 1}",
|
||||
initialState: [
|
||||
StateEvent(
|
||||
type: PangeaEventTypes.activityPlan,
|
||||
content: updatedActivity.toJson(),
|
||||
),
|
||||
if (imageURL != null)
|
||||
StateEvent(
|
||||
type: EventTypes.RoomAvatar,
|
||||
|
|
@ -382,12 +386,6 @@ class ActivityPlannerBuilderState extends State<ActivityPlannerBuilder> {
|
|||
await room.client.waitForRoomInSync(activityRoom.id);
|
||||
}
|
||||
|
||||
await activityRoom.sendActivityPlan(
|
||||
updatedActivity,
|
||||
avatar: avatar,
|
||||
filename: filename,
|
||||
);
|
||||
|
||||
return activityRoom.id;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,40 +0,0 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/l10n/l10n.dart';
|
||||
import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart';
|
||||
import 'package:fluffychat/widgets/avatar.dart';
|
||||
|
||||
class ActivityCreationStateEvent extends StatelessWidget {
|
||||
final Event event;
|
||||
|
||||
const ActivityCreationStateEvent({required this.event, super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = L10n.of(context);
|
||||
final matrixLocals = MatrixLocals(l10n);
|
||||
final theme = Theme.of(context);
|
||||
final roomName = event.room.getLocalizedDisplayname(matrixLocals);
|
||||
return Center(
|
||||
child: Container(
|
||||
padding: const EdgeInsets.only(bottom: 32.0, top: 60.0),
|
||||
child: Material(
|
||||
color: theme.colorScheme.surfaceContainer,
|
||||
borderRadius: BorderRadius.circular(AppConfig.borderRadius),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Avatar(
|
||||
mxContent: event.room.avatar,
|
||||
name: roomName,
|
||||
size: Avatar.defaultSize * 5,
|
||||
userId: event.room.directChatMatrixID,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,277 +0,0 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
import 'package:fluffychat/config/themes.dart';
|
||||
import 'package:fluffychat/l10n/l10n.dart';
|
||||
import 'package:fluffychat/pages/chat/chat.dart';
|
||||
import 'package:fluffychat/pangea/activity_planner/activity_plan_model.dart';
|
||||
import 'package:fluffychat/pangea/activity_sessions/activity_analytics_chip.dart';
|
||||
import 'package:fluffychat/pangea/activity_sessions/activity_participant_indicator.dart';
|
||||
import 'package:fluffychat/pangea/activity_sessions/activity_results_carousel.dart';
|
||||
import 'package:fluffychat/pangea/activity_sessions/activity_role_model.dart';
|
||||
import 'package:fluffychat/pangea/activity_sessions/activity_room_extension.dart';
|
||||
import 'package:fluffychat/pangea/analytics_misc/construct_type_enum.dart';
|
||||
import 'package:fluffychat/pangea/analytics_summary/progress_indicators_enum.dart';
|
||||
import 'package:fluffychat/pangea/course_plans/course_plan_room_extension.dart';
|
||||
import 'package:fluffychat/pangea/course_plans/course_plans_repo.dart';
|
||||
import 'package:fluffychat/pangea/spaces/utils/load_participants_util.dart';
|
||||
import 'package:fluffychat/widgets/future_loading_dialog.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
||||
class ActivityFinishedStatusMessage extends StatelessWidget {
|
||||
final ChatController controller;
|
||||
|
||||
const ActivityFinishedStatusMessage({
|
||||
super.key,
|
||||
required this.controller,
|
||||
});
|
||||
|
||||
Map<String, ActivityRole> get _roles =>
|
||||
controller.room.activityPlan?.roles ?? {};
|
||||
|
||||
Future<void> _archiveToAnalytics(BuildContext context) async {
|
||||
await controller.room.archiveActivity();
|
||||
await MatrixState.pangeaController.putAnalytics
|
||||
.sendActivityAnalytics(controller.room.id);
|
||||
|
||||
final courseParent = controller.room.courseParent;
|
||||
if (courseParent?.coursePlan == null) return;
|
||||
final coursePlan = await CoursePlansRepo.get(
|
||||
courseParent!.coursePlan!.uuid,
|
||||
);
|
||||
|
||||
if (coursePlan == null) {
|
||||
throw L10n.of(context).noCourseFound;
|
||||
}
|
||||
|
||||
final activityId = controller.room.activityPlan!.activityId;
|
||||
final topicId = coursePlan.topicID(activityId);
|
||||
if (topicId == null) {
|
||||
throw L10n.of(context).activityNotFoundForCourse;
|
||||
}
|
||||
|
||||
await courseParent.finishCourseActivity(activityId, topicId);
|
||||
}
|
||||
|
||||
List<ActivityRoleModel> get _rolesWithSummaries {
|
||||
if (controller.room.activitySummary?.summary == null) {
|
||||
return <ActivityRoleModel>[];
|
||||
}
|
||||
|
||||
final roles = controller.room.activityRoles;
|
||||
return roles?.roles.values.where((role) {
|
||||
return controller.room.activitySummary!.summary!.participants.any(
|
||||
(p) => p.participantId == role.userId,
|
||||
);
|
||||
}).toList() ??
|
||||
[];
|
||||
}
|
||||
|
||||
ActivityRoleModel? get _highlightedRole {
|
||||
if (controller.highlightedRole != null) {
|
||||
return controller.highlightedRole;
|
||||
}
|
||||
|
||||
return _rolesWithSummaries.firstWhereOrNull(
|
||||
(r) => r.userId == controller.room.client.userID,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return LoadParticipantsBuilder(
|
||||
room: controller.room,
|
||||
builder: (context, participants) {
|
||||
if (!controller.room.showActivityChatUI ||
|
||||
!controller.room.activityIsFinished ||
|
||||
controller.room.ownRole == null) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
final summary = controller.room.activitySummary;
|
||||
final theme = Theme.of(context);
|
||||
|
||||
final user = participants.participants.firstWhereOrNull(
|
||||
(u) => u.id == _highlightedRole?.userId,
|
||||
);
|
||||
|
||||
final userSummary = controller
|
||||
.room.activitySummary?.summary?.participants
|
||||
.firstWhereOrNull(
|
||||
(p) => p.participantId == _highlightedRole?.userId,
|
||||
);
|
||||
|
||||
return AnimatedSize(
|
||||
duration: FluffyThemes.animationDuration,
|
||||
child: Center(
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
constraints: const BoxConstraints(
|
||||
maxWidth: FluffyThemes.columnWidth * 1.5,
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
if (summary?.summary != null) ...[
|
||||
Text(
|
||||
L10n.of(context).activityFinishedMessage,
|
||||
style: const TextStyle(fontSize: 18.0),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 4.0),
|
||||
child: Text(
|
||||
summary!.summary!.summary,
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(
|
||||
fontSize: 14.0,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (summary.analytics != null)
|
||||
Row(
|
||||
spacing: 8.0,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
ActivityAnalyticsChip(
|
||||
ConstructTypeEnum.vocab.indicator.icon,
|
||||
"${summary.analytics!.uniqueConstructCount(ConstructTypeEnum.vocab)}",
|
||||
),
|
||||
ActivityAnalyticsChip(
|
||||
ConstructTypeEnum.morph.indicator.icon,
|
||||
"${summary.analytics!.uniqueConstructCount(ConstructTypeEnum.morph)}",
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16.0),
|
||||
if (_highlightedRole != null && userSummary != null)
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(24.0),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: theme.colorScheme.surfaceContainer,
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
ActivityResultsCarousel(
|
||||
userId: _highlightedRole!.userId,
|
||||
selectedRole: _highlightedRole!,
|
||||
user: user,
|
||||
summary: userSummary,
|
||||
analytics: summary.analytics,
|
||||
),
|
||||
Wrap(
|
||||
alignment: WrapAlignment.center,
|
||||
children: _rolesWithSummaries.map(
|
||||
(role) {
|
||||
final user = participants.participants
|
||||
.firstWhereOrNull(
|
||||
(u) => u.id == role.userId,
|
||||
);
|
||||
|
||||
return IntrinsicWidth(
|
||||
child: ActivityParticipantIndicator(
|
||||
availableRole: _roles[role.id]!,
|
||||
avatarUrl: _roles[role.id]?.avatarUrl ??
|
||||
user?.avatarUrl?.toString(),
|
||||
onTap: _highlightedRole == role
|
||||
? null
|
||||
: () =>
|
||||
controller.highlightRole(role),
|
||||
assignedRole: role,
|
||||
selected: _highlightedRole == role,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 8.0,
|
||||
horizontal: 24.0,
|
||||
),
|
||||
borderRadius: BorderRadius.zero,
|
||||
),
|
||||
);
|
||||
},
|
||||
).toList(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20.0),
|
||||
] else if (summary?.isLoading ?? false)
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
spacing: 8.0,
|
||||
children: [
|
||||
const CircularProgressIndicator.adaptive(),
|
||||
Text(L10n.of(context).loadingActivitySummary),
|
||||
],
|
||||
),
|
||||
)
|
||||
else if (summary?.hasError ?? false)
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
spacing: 8.0,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(
|
||||
Icons.school_outlined,
|
||||
size: 24.0,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Flexible(
|
||||
child: Text(
|
||||
L10n.of(context).activitySummaryError,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () => controller.room.fetchSummaries(),
|
||||
child: Text(L10n.of(context).requestSummaries),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (!controller.room.isHiddenActivityRoom)
|
||||
ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12.0,
|
||||
vertical: 8.0,
|
||||
),
|
||||
foregroundColor: theme.colorScheme.onPrimaryContainer,
|
||||
backgroundColor: theme.colorScheme.primaryContainer,
|
||||
),
|
||||
onPressed: () async {
|
||||
final resp = await showFutureLoadingDialog(
|
||||
context: context,
|
||||
future: () => _archiveToAnalytics(context),
|
||||
);
|
||||
|
||||
if (!resp.isError) {
|
||||
context.go(
|
||||
"/rooms/analytics?mode=activities",
|
||||
);
|
||||
}
|
||||
},
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(L10n.of(context).archiveToAnalytics),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -4,16 +4,13 @@ import 'package:cached_network_image/cached_network_image.dart';
|
|||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import 'package:fluffychat/l10n/l10n.dart';
|
||||
import 'package:fluffychat/pangea/activity_planner/activity_plan_model.dart';
|
||||
import 'package:fluffychat/pangea/activity_sessions/activity_role_model.dart';
|
||||
import 'package:fluffychat/utils/string_color.dart';
|
||||
import 'package:fluffychat/widgets/avatar.dart';
|
||||
import 'package:fluffychat/widgets/hover_builder.dart';
|
||||
|
||||
class ActivityParticipantIndicator extends StatelessWidget {
|
||||
final ActivityRole availableRole;
|
||||
final ActivityRoleModel? assignedRole;
|
||||
|
||||
final String name;
|
||||
final String? userId;
|
||||
final String? avatarUrl;
|
||||
|
||||
final VoidCallback? onTap;
|
||||
|
|
@ -25,9 +22,9 @@ class ActivityParticipantIndicator extends StatelessWidget {
|
|||
|
||||
const ActivityParticipantIndicator({
|
||||
super.key,
|
||||
required this.availableRole,
|
||||
required this.name,
|
||||
this.avatarUrl,
|
||||
this.assignedRole,
|
||||
this.userId,
|
||||
this.selected = false,
|
||||
this.onTap,
|
||||
this.opacity = 1.0,
|
||||
|
|
@ -63,13 +60,13 @@ class ActivityParticipantIndicator extends StatelessWidget {
|
|||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
assignedRole != null
|
||||
userId != null
|
||||
? avatarUrl == null || avatarUrl!.startsWith("mxc")
|
||||
? Avatar(
|
||||
mxContent: avatarUrl != null
|
||||
? Uri.parse(avatarUrl!)
|
||||
: null,
|
||||
name: assignedRole?.userId.localpart,
|
||||
name: userId!.localpart,
|
||||
size: 60.0,
|
||||
)
|
||||
: ClipRRect(
|
||||
|
|
@ -87,23 +84,20 @@ class ActivityParticipantIndicator extends StatelessWidget {
|
|||
theme.colorScheme.primaryContainer,
|
||||
),
|
||||
Text(
|
||||
availableRole.name,
|
||||
name,
|
||||
style: const TextStyle(
|
||||
fontSize: 12.0,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
assignedRole?.userId.localpart ??
|
||||
L10n.of(context).openRoleLabel,
|
||||
userId?.localpart ?? L10n.of(context).openRoleLabel,
|
||||
style: TextStyle(
|
||||
fontSize: 12.0,
|
||||
color:
|
||||
(Theme.of(context).brightness == Brightness.light
|
||||
? assignedRole
|
||||
?.userId.localpart?.lightColorAvatar
|
||||
: assignedRole
|
||||
?.userId.localpart?.lightColorText) ??
|
||||
assignedRole?.role?.lightColorAvatar,
|
||||
? userId?.localpart?.lightColorAvatar
|
||||
: userId?.localpart?.lightColorText) ??
|
||||
name.lightColorAvatar,
|
||||
),
|
||||
),
|
||||
],
|
||||
|
|
|
|||
|
|
@ -56,8 +56,8 @@ class ActivityParticipantList extends StatelessWidget {
|
|||
canSelect != null ? canSelect!(availableRole.id) : true;
|
||||
|
||||
return ActivityParticipantIndicator(
|
||||
availableRole: availableRole,
|
||||
assignedRole: assignedRole,
|
||||
name: availableRole.name,
|
||||
userId: assignedRole?.userId,
|
||||
opacity: getOpacity != null ? getOpacity!(assignedRole) : 1.0,
|
||||
avatarUrl:
|
||||
availableRole.avatarUrl ?? user?.avatarUrl?.toString(),
|
||||
|
|
|
|||
|
|
@ -36,6 +36,9 @@ class ActivityRolesModel {
|
|||
static ActivityRolesModel fromJson(Map<String, dynamic> json) {
|
||||
final roles = (json['roles'] as Map<String, dynamic>)
|
||||
.map((id, value) => MapEntry(id, ActivityRoleModel.fromJson(value)));
|
||||
return ActivityRolesModel(roles);
|
||||
|
||||
return ActivityRolesModel(
|
||||
roles,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,21 +22,6 @@ import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart';
|
|||
import '../activity_summary/activity_summary_repo.dart';
|
||||
|
||||
extension ActivityRoomExtension on Room {
|
||||
Future<void> sendActivityPlan(
|
||||
ActivityPlanModel activity, {
|
||||
Uint8List? avatar,
|
||||
String? filename,
|
||||
}) async {
|
||||
if (canChangeStateEvent(PangeaEventTypes.activityPlan)) {
|
||||
await client.setRoomStateWithKey(
|
||||
id,
|
||||
PangeaEventTypes.activityPlan,
|
||||
"",
|
||||
activity.toJson(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> joinActivity(ActivityRole role) async {
|
||||
final currentRoles = activityRoles ?? ActivityRolesModel.empty;
|
||||
final activityRole = ActivityRoleModel(
|
||||
|
|
@ -279,8 +264,8 @@ extension ActivityRoomExtension on Room {
|
|||
ActivityRoleModel? get ownRole => activityRoles?.role(client.userID!);
|
||||
|
||||
int get remainingRoles {
|
||||
final availableRoles = activityPlan!.roles;
|
||||
return max(0, availableRoles.length - (assignedRoles?.length ?? 0));
|
||||
final availableRoles = activityPlan?.roles;
|
||||
return max(0, (availableRoles?.length ?? 0) - (assignedRoles?.length ?? 0));
|
||||
}
|
||||
|
||||
bool get showActivityChatUI {
|
||||
|
|
@ -289,6 +274,8 @@ extension ActivityRoomExtension on Room {
|
|||
powerForChangingStateEvent(PangeaEventTypes.activitySummary) == 0;
|
||||
}
|
||||
|
||||
bool get activityHasStarted => remainingRoles == 0;
|
||||
|
||||
bool get isActiveInActivity {
|
||||
if (!showActivityChatUI) return false;
|
||||
final role = ownRole;
|
||||
|
|
@ -329,11 +316,8 @@ extension ActivityRoomExtension on Room {
|
|||
(parent) => parent.coursePlan != null,
|
||||
);
|
||||
|
||||
bool get isActivitySession =>
|
||||
getState(EventTypes.RoomCreate)
|
||||
?.content
|
||||
.tryGet<String>('type')
|
||||
?.startsWith(PangeaRoomTypes.activitySession) ==
|
||||
true ||
|
||||
activityPlan != null;
|
||||
bool get isActivityRoomType =>
|
||||
roomType?.startsWith(PangeaRoomTypes.activitySession) == true;
|
||||
|
||||
bool get isActivitySession => isActivityRoomType || activityPlan != null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,190 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
import 'package:fluffychat/config/themes.dart';
|
||||
import 'package:fluffychat/l10n/l10n.dart';
|
||||
import 'package:fluffychat/pages/chat/chat.dart';
|
||||
import 'package:fluffychat/pangea/activity_sessions/activity_room_extension.dart';
|
||||
import 'package:fluffychat/pangea/course_plans/course_plan_room_extension.dart';
|
||||
import 'package:fluffychat/pangea/course_plans/course_plans_repo.dart';
|
||||
import 'package:fluffychat/widgets/future_loading_dialog.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
||||
class ActivityFinishedStatusMessage extends StatelessWidget {
|
||||
final ChatController controller;
|
||||
|
||||
const ActivityFinishedStatusMessage({
|
||||
super.key,
|
||||
required this.controller,
|
||||
});
|
||||
|
||||
Future<void> _archiveToAnalytics(BuildContext context) async {
|
||||
await controller.room.archiveActivity();
|
||||
await MatrixState.pangeaController.putAnalytics
|
||||
.sendActivityAnalytics(controller.room.id);
|
||||
|
||||
final courseParent = controller.room.courseParent;
|
||||
if (courseParent?.coursePlan == null) return;
|
||||
final coursePlan = await CoursePlansRepo.get(
|
||||
courseParent!.coursePlan!.uuid,
|
||||
);
|
||||
|
||||
if (coursePlan == null) {
|
||||
throw L10n.of(context).noCourseFound;
|
||||
}
|
||||
|
||||
final activityId = controller.room.activityPlan!.activityId;
|
||||
final topicId = coursePlan.topicID(activityId);
|
||||
if (topicId == null) {
|
||||
throw L10n.of(context).activityNotFoundForCourse;
|
||||
}
|
||||
|
||||
await courseParent.finishCourseActivity(activityId, topicId);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (!controller.room.showActivityChatUI ||
|
||||
controller.room.ownRole == null ||
|
||||
!controller.room.hasCompletedActivity) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
final theme = Theme.of(context);
|
||||
final summary = controller.room.activitySummary;
|
||||
|
||||
return AnimatedSize(
|
||||
duration: FluffyThemes.animationDuration,
|
||||
child: Container(
|
||||
margin: const EdgeInsets.only(top: 20.0),
|
||||
padding: const EdgeInsets.only(
|
||||
top: 12.0,
|
||||
left: 12.0,
|
||||
right: 12.0,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
border: Border(
|
||||
top: BorderSide(color: theme.dividerColor),
|
||||
),
|
||||
),
|
||||
child: Center(
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 400),
|
||||
child: Column(
|
||||
spacing: 12.0,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: controller.room.activityIsFinished
|
||||
? [
|
||||
if (summary?.isLoading ?? false) ...[
|
||||
Text(
|
||||
L10n.of(context).generatingSummary,
|
||||
style: const TextStyle(
|
||||
fontStyle: FontStyle.italic,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 36.0,
|
||||
width: 36.0,
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
] else if (summary?.hasError ?? false) ...[
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(
|
||||
Icons.school_outlined,
|
||||
size: 24.0,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Flexible(
|
||||
child: Text(
|
||||
L10n.of(context).activitySummaryError,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () => controller.room.fetchSummaries(),
|
||||
child: Text(L10n.of(context).requestSummaries),
|
||||
),
|
||||
],
|
||||
if (!controller.room.isHiddenActivityRoom)
|
||||
ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12.0,
|
||||
vertical: 8.0,
|
||||
),
|
||||
foregroundColor:
|
||||
theme.colorScheme.onPrimaryContainer,
|
||||
backgroundColor: theme.colorScheme.primaryContainer,
|
||||
),
|
||||
onPressed: () async {
|
||||
final resp = await showFutureLoadingDialog(
|
||||
context: context,
|
||||
future: () => _archiveToAnalytics(context),
|
||||
);
|
||||
|
||||
if (!resp.isError) {
|
||||
context.go(
|
||||
"/rooms/analytics?mode=activities",
|
||||
);
|
||||
}
|
||||
},
|
||||
child: Row(
|
||||
spacing: 12.0,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Icon(Icons.radar, size: 20.0),
|
||||
Text(
|
||||
L10n.of(context).saveToCompletedActivities,
|
||||
style: const TextStyle(fontSize: 12.0),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
]
|
||||
: [
|
||||
Text(
|
||||
L10n.of(context).waitingForOthersToFinish,
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
fontStyle: FontStyle.italic,
|
||||
),
|
||||
),
|
||||
ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12.0,
|
||||
vertical: 8.0,
|
||||
),
|
||||
foregroundColor: theme.colorScheme.onSurface,
|
||||
backgroundColor: theme.colorScheme.surface,
|
||||
side: BorderSide(
|
||||
color: theme.colorScheme.primaryContainer,
|
||||
),
|
||||
),
|
||||
onPressed: controller.room.continueActivity,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
L10n.of(context).waitNotDone,
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -3,8 +3,8 @@ import 'package:flutter/material.dart';
|
|||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import 'package:fluffychat/config/themes.dart';
|
||||
import 'package:fluffychat/pangea/activity_sessions/activity_analytics_chip.dart';
|
||||
import 'package:fluffychat/pangea/activity_sessions/activity_role_model.dart';
|
||||
import 'package:fluffychat/pangea/activity_sessions/activity_session_chat/activity_analytics_chip.dart';
|
||||
import 'package:fluffychat/pangea/activity_summary/activity_summary_analytics_model.dart';
|
||||
import 'package:fluffychat/pangea/activity_summary/activity_summary_response_model.dart';
|
||||
import 'package:fluffychat/pangea/analytics_misc/construct_type_enum.dart';
|
||||
|
|
@ -0,0 +1,146 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import 'package:fluffychat/l10n/l10n.dart';
|
||||
import 'package:fluffychat/pangea/activity_sessions/activity_room_extension.dart';
|
||||
import 'package:fluffychat/pangea/activity_sessions/activity_session_start/activity_sessions_start_view.dart';
|
||||
import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart';
|
||||
import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart';
|
||||
import 'package:fluffychat/widgets/future_loading_dialog.dart';
|
||||
|
||||
enum SessionState {
|
||||
notStarted,
|
||||
notSelectedRole,
|
||||
selectedRole,
|
||||
confirmedRole,
|
||||
}
|
||||
|
||||
class ActivitySessionStartPage extends StatefulWidget {
|
||||
final Room room;
|
||||
const ActivitySessionStartPage({
|
||||
super.key,
|
||||
required this.room,
|
||||
});
|
||||
|
||||
@override
|
||||
ActivitySessionStartController createState() =>
|
||||
ActivitySessionStartController();
|
||||
}
|
||||
|
||||
class ActivitySessionStartController extends State<ActivitySessionStartPage> {
|
||||
bool _started = false;
|
||||
|
||||
bool showInstructions = false;
|
||||
String? _selectedRoleId;
|
||||
|
||||
@override
|
||||
void didUpdateWidget(covariant ActivitySessionStartPage oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (oldWidget.room.id != widget.room.id) {
|
||||
setState(() {
|
||||
_started = false;
|
||||
_selectedRoleId = null;
|
||||
showInstructions = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Room get room => widget.room;
|
||||
|
||||
String get displayname => room.getLocalizedDisplayname(
|
||||
MatrixLocals(L10n.of(context)),
|
||||
);
|
||||
|
||||
SessionState get state {
|
||||
if (room.ownRole != null) return SessionState.confirmedRole;
|
||||
if (_selectedRoleId != null) return SessionState.selectedRole;
|
||||
if (room.isRoomAdmin && !_started) return SessionState.notStarted;
|
||||
return SessionState.notSelectedRole;
|
||||
}
|
||||
|
||||
String get descriptionText {
|
||||
switch (state) {
|
||||
case SessionState.confirmedRole:
|
||||
return L10n.of(context).waitingToFillRole(room.remainingRoles);
|
||||
case SessionState.selectedRole:
|
||||
return room.activityPlan!.learningObjective;
|
||||
case SessionState.notStarted:
|
||||
return L10n.of(context).letsGo;
|
||||
case SessionState.notSelectedRole:
|
||||
return room.isRoomAdmin
|
||||
? L10n.of(context).chooseRole
|
||||
: L10n.of(context).chooseRoleToParticipate;
|
||||
}
|
||||
}
|
||||
|
||||
String get buttonText => state == SessionState.notStarted
|
||||
? L10n.of(context).start
|
||||
: L10n.of(context).confirm;
|
||||
|
||||
bool get enableButtons => [
|
||||
SessionState.notStarted,
|
||||
SessionState.selectedRole,
|
||||
].contains(state);
|
||||
|
||||
bool canSelectParticipant(String id) {
|
||||
if (state == SessionState.confirmedRole) return false;
|
||||
|
||||
final availableRoles = room.activityPlan!.roles;
|
||||
final assignedRoles = room.assignedRoles ?? {};
|
||||
final unassignedIds = availableRoles.keys
|
||||
.where((id) => !assignedRoles.containsKey(id))
|
||||
.toList();
|
||||
return unassignedIds.contains(id);
|
||||
}
|
||||
|
||||
bool isParticipantSelected(String id) {
|
||||
if (state == SessionState.confirmedRole) {
|
||||
return room.ownRole?.id == id;
|
||||
}
|
||||
return _selectedRoleId == id;
|
||||
}
|
||||
|
||||
void toggleInstructions() {
|
||||
setState(() {
|
||||
showInstructions = !showInstructions;
|
||||
});
|
||||
}
|
||||
|
||||
void selectRole(String id) {
|
||||
if (state == SessionState.confirmedRole) return;
|
||||
if (_selectedRoleId == id) return;
|
||||
if (mounted) setState(() => _selectedRoleId = id);
|
||||
}
|
||||
|
||||
Future<void> onTap() async {
|
||||
switch (state) {
|
||||
case SessionState.notStarted:
|
||||
if (mounted) setState(() => _started = true);
|
||||
case SessionState.selectedRole:
|
||||
await showFutureLoadingDialog(
|
||||
context: context,
|
||||
future: () => room.joinActivity(
|
||||
room.activityPlan!.roles[_selectedRoleId!]!,
|
||||
),
|
||||
);
|
||||
if (mounted) setState(() {});
|
||||
case SessionState.notSelectedRole:
|
||||
case SessionState.confirmedRole:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> pingCourse() async {
|
||||
if (room.courseParent == null) {
|
||||
throw Exception("Activity is not part of a course");
|
||||
}
|
||||
|
||||
await room.courseParent!.sendTextEvent("");
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => ActivitySessionStartView(this);
|
||||
}
|
||||
|
|
@ -0,0 +1,222 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
import 'package:fluffychat/config/themes.dart';
|
||||
import 'package:fluffychat/l10n/l10n.dart';
|
||||
import 'package:fluffychat/pangea/activity_sessions/activity_room_extension.dart';
|
||||
import 'package:fluffychat/pangea/activity_sessions/activity_session_start/activity_session_start_page.dart';
|
||||
import 'package:fluffychat/pangea/activity_sessions/activity_summary_widget.dart';
|
||||
import 'package:fluffychat/pangea/bot/utils/bot_name.dart';
|
||||
import 'package:fluffychat/pangea/common/widgets/share_room_button.dart';
|
||||
import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart';
|
||||
import 'package:fluffychat/utils/stream_extension.dart';
|
||||
import 'package:fluffychat/widgets/future_loading_dialog.dart';
|
||||
import 'package:fluffychat/widgets/layouts/max_width_body.dart';
|
||||
|
||||
class ActivitySessionStartView extends StatelessWidget {
|
||||
final ActivitySessionStartController controller;
|
||||
const ActivitySessionStartView(
|
||||
this.controller, {
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
return StreamBuilder(
|
||||
stream: controller.room.client.onRoomState.stream
|
||||
.rateLimit(const Duration(seconds: 1)),
|
||||
builder: (context, snapshot) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
leadingWidth: 52.0,
|
||||
title: Text(controller.displayname),
|
||||
leading: Padding(
|
||||
padding: const EdgeInsets.only(left: 12.0),
|
||||
child: Center(
|
||||
child: IconButton(
|
||||
icon: const Icon(Icons.close),
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
),
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 12.0),
|
||||
child: SizedBox(
|
||||
width: 40.0,
|
||||
height: 40.0,
|
||||
child: Center(
|
||||
child: ShareRoomButton(room: controller.room),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
body: MaxWidthBody(
|
||||
showBorder: false,
|
||||
withScrolling: false,
|
||||
child: Stack(
|
||||
children: [
|
||||
Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
child: Column(
|
||||
spacing: 12.0,
|
||||
children: [
|
||||
ActivitySummary(
|
||||
room: controller.room,
|
||||
showInstructions: controller.showInstructions,
|
||||
toggleInstructions:
|
||||
controller.toggleInstructions,
|
||||
onTapParticipant: controller.selectRole,
|
||||
isParticipantSelected:
|
||||
controller.isParticipantSelected,
|
||||
canSelectParticipant:
|
||||
controller.canSelectParticipant,
|
||||
),
|
||||
const SizedBox(height: 250.0),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: AnimatedSize(
|
||||
duration: FluffyThemes.animationDuration,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
border: Border(
|
||||
top: BorderSide(color: theme.dividerColor),
|
||||
),
|
||||
color: theme.colorScheme.surface,
|
||||
),
|
||||
padding: const EdgeInsets.all(24.0),
|
||||
child: Column(
|
||||
spacing: 16.0,
|
||||
children: [
|
||||
Text(
|
||||
controller.descriptionText,
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
if (controller.state ==
|
||||
SessionState.confirmedRole) ...[
|
||||
if (controller.room.courseParent != null)
|
||||
ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor:
|
||||
theme.colorScheme.primaryContainer,
|
||||
foregroundColor:
|
||||
theme.colorScheme.onPrimaryContainer,
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(20.0),
|
||||
),
|
||||
),
|
||||
onPressed: () => showFutureLoadingDialog(
|
||||
context: context,
|
||||
future: controller.pingCourse,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
L10n.of(context).pingParticipants,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (controller.room.isRoomAdmin) ...[
|
||||
ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor:
|
||||
theme.colorScheme.primaryContainer,
|
||||
foregroundColor:
|
||||
theme.colorScheme.onPrimaryContainer,
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(20.0),
|
||||
),
|
||||
),
|
||||
onPressed: () => showFutureLoadingDialog(
|
||||
context: context,
|
||||
future: () => controller.room
|
||||
.invite(BotName.byEnvironment),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(L10n.of(context).playWithBot),
|
||||
],
|
||||
),
|
||||
),
|
||||
ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor:
|
||||
theme.colorScheme.primaryContainer,
|
||||
foregroundColor:
|
||||
theme.colorScheme.onPrimaryContainer,
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(20.0),
|
||||
),
|
||||
),
|
||||
onPressed: () => context.go(
|
||||
"/rooms/${controller.room.id}/invite",
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(L10n.of(context).inviteFriends),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
] else
|
||||
ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor:
|
||||
theme.colorScheme.primaryContainer,
|
||||
foregroundColor:
|
||||
theme.colorScheme.onPrimaryContainer,
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(20.0),
|
||||
),
|
||||
),
|
||||
onPressed: controller.enableButtons
|
||||
? controller.onTap
|
||||
: null,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(controller.buttonText),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,53 +0,0 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import 'package:fluffychat/config/themes.dart';
|
||||
import 'package:fluffychat/pages/chat/events/state_message.dart';
|
||||
import 'package:fluffychat/pangea/activity_planner/activity_plan_model.dart';
|
||||
import 'package:fluffychat/pangea/activity_sessions/activity_participant_list.dart';
|
||||
import 'package:fluffychat/pangea/activity_sessions/activity_room_extension.dart';
|
||||
|
||||
class ActivityStateEvent extends StatelessWidget {
|
||||
final Event event;
|
||||
const ActivityStateEvent({super.key, required this.event});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (event.room.activityPlan == null) {
|
||||
return const SizedBox();
|
||||
}
|
||||
|
||||
try {
|
||||
final activity = ActivityPlanModel.fromJson(event.content);
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 24.0,
|
||||
vertical: 16.0,
|
||||
),
|
||||
constraints: const BoxConstraints(
|
||||
maxWidth: FluffyThemes.maxTimelineWidth,
|
||||
),
|
||||
child: Column(
|
||||
spacing: 12.0,
|
||||
children: [
|
||||
Text(
|
||||
activity.markdown,
|
||||
style: const TextStyle(fontSize: 14.0),
|
||||
),
|
||||
if (event.room.ownRole != null ||
|
||||
event.room.remainingRoles == 0) ...[
|
||||
ActivityParticipantList(
|
||||
room: event.room,
|
||||
getOpacity: (role) =>
|
||||
role == null || role.isFinished ? 0.5 : 1.0,
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
return StateMessage(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,54 +0,0 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import 'package:fluffychat/config/themes.dart';
|
||||
import 'package:fluffychat/pangea/activity_sessions/activity_room_extension.dart';
|
||||
import 'package:fluffychat/pangea/activity_sessions/activity_unfinished_status_message.dart';
|
||||
import 'package:fluffychat/pangea/activity_sessions/load_activity_summary_widget.dart';
|
||||
|
||||
class ActivityStatusMessage extends StatelessWidget {
|
||||
final Room room;
|
||||
|
||||
const ActivityStatusMessage({
|
||||
super.key,
|
||||
required this.room,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (!room.showActivityChatUI) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
if (room.activityIsFinished) {
|
||||
return LoadActivitySummaryWidget(room: room);
|
||||
}
|
||||
|
||||
final role = room.ownRole;
|
||||
if (role != null && !role.isFinished) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
return Material(
|
||||
child: AnimatedSize(
|
||||
duration: FluffyThemes.animationDuration,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(
|
||||
bottom: FluffyThemes.isColumnMode(context) ? 32.0 : 16.0,
|
||||
left: 16.0,
|
||||
right: 16.0,
|
||||
),
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
maxHeight: MediaQuery.of(context).size.height * 0.8,
|
||||
),
|
||||
child: SingleChildScrollView(
|
||||
child: ActivityUnfinishedStatusMessage(room: room),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
189
lib/pangea/activity_sessions/activity_summary_widget.dart
Normal file
189
lib/pangea/activity_sessions/activity_summary_widget.dart
Normal file
|
|
@ -0,0 +1,189 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import 'package:fluffychat/config/themes.dart';
|
||||
import 'package:fluffychat/l10n/l10n.dart';
|
||||
import 'package:fluffychat/pangea/activity_sessions/activity_participant_list.dart';
|
||||
import 'package:fluffychat/pangea/activity_sessions/activity_role_model.dart';
|
||||
import 'package:fluffychat/pangea/activity_sessions/activity_room_extension.dart';
|
||||
import 'package:fluffychat/pangea/activity_suggestions/activity_suggestion_card_row.dart';
|
||||
import 'package:fluffychat/pangea/common/widgets/url_image_widget.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/enums/language_level_type_enum.dart';
|
||||
|
||||
class ActivitySummary extends StatelessWidget {
|
||||
final Room room;
|
||||
|
||||
final bool showInstructions;
|
||||
final VoidCallback toggleInstructions;
|
||||
|
||||
final Function(String)? onTapParticipant;
|
||||
final bool Function(String)? canSelectParticipant;
|
||||
final bool Function(String)? isParticipantSelected;
|
||||
final double Function(ActivityRoleModel?)? getParticipantOpacity;
|
||||
|
||||
const ActivitySummary({
|
||||
super.key,
|
||||
required this.room,
|
||||
required this.showInstructions,
|
||||
required this.toggleInstructions,
|
||||
this.onTapParticipant,
|
||||
this.canSelectParticipant,
|
||||
this.isParticipantSelected,
|
||||
this.getParticipantOpacity,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final activity = room.activityPlan;
|
||||
if (activity == null) {
|
||||
return const SizedBox();
|
||||
}
|
||||
|
||||
final theme = Theme.of(context);
|
||||
return Center(
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
constraints: const BoxConstraints(
|
||||
maxWidth: FluffyThemes.columnWidth * 1.5,
|
||||
),
|
||||
child: Column(
|
||||
spacing: 12.0,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(4.0),
|
||||
child: ImageByUrl(
|
||||
imageUrl: activity.imageURL,
|
||||
width: 80.0,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
),
|
||||
Text(
|
||||
activity.learningObjective,
|
||||
style: const TextStyle(fontSize: 12.0),
|
||||
),
|
||||
ActivityParticipantList(
|
||||
room: room,
|
||||
onTap: onTapParticipant,
|
||||
canSelect: canSelectParticipant,
|
||||
isSelected: isParticipantSelected,
|
||||
getOpacity: getParticipantOpacity,
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 8.0,
|
||||
),
|
||||
child: Column(
|
||||
spacing: 4.0,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 6.0,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Row(
|
||||
spacing: 8.0,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
activity.req.mode,
|
||||
style: const TextStyle(fontSize: 12.0),
|
||||
),
|
||||
Row(
|
||||
spacing: 4.0,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(Icons.school, size: 12.0),
|
||||
Text(
|
||||
activity.req.cefrLevel.string,
|
||||
style: const TextStyle(fontSize: 12.0),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
GestureDetector(
|
||||
onTap: toggleInstructions,
|
||||
child: Row(
|
||||
spacing: 4.0,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
showInstructions
|
||||
? L10n.of(context).hideInstructions
|
||||
: L10n.of(context).seeInstructions,
|
||||
style: const TextStyle(fontSize: 12.0),
|
||||
),
|
||||
Icon(
|
||||
showInstructions
|
||||
? Icons.arrow_drop_up
|
||||
: Icons.arrow_drop_down,
|
||||
size: 12.0,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (showInstructions) ...[
|
||||
ActivitySuggestionCardRow(
|
||||
icon: Symbols.target,
|
||||
iconSize: 16.0,
|
||||
child: Text(
|
||||
activity.learningObjective,
|
||||
style: const TextStyle(fontSize: 12.0),
|
||||
),
|
||||
),
|
||||
ActivitySuggestionCardRow(
|
||||
icon: Symbols.steps,
|
||||
iconSize: 16.0,
|
||||
child: Text(
|
||||
activity.instructions,
|
||||
style: const TextStyle(fontSize: 12.0),
|
||||
),
|
||||
),
|
||||
ActivitySuggestionCardRow(
|
||||
icon: Symbols.dictionary,
|
||||
iconSize: 16.0,
|
||||
child: Wrap(
|
||||
spacing: 4.0,
|
||||
runSpacing: 4.0,
|
||||
children: activity.vocab
|
||||
.map(
|
||||
(vocab) => Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 4.0,
|
||||
horizontal: 8.0,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: theme.colorScheme.primary.withAlpha(
|
||||
20,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(
|
||||
24.0,
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
vocab.lemma,
|
||||
style: const TextStyle(fontSize: 12),
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,112 +0,0 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import 'package:fluffychat/config/themes.dart';
|
||||
import 'package:fluffychat/l10n/l10n.dart';
|
||||
import 'package:fluffychat/pangea/activity_sessions/activity_participant_list.dart';
|
||||
import 'package:fluffychat/pangea/activity_sessions/activity_room_extension.dart';
|
||||
import 'package:fluffychat/widgets/future_loading_dialog.dart';
|
||||
|
||||
class ActivityUnfinishedStatusMessage extends StatefulWidget {
|
||||
final Room room;
|
||||
const ActivityUnfinishedStatusMessage({
|
||||
super.key,
|
||||
required this.room,
|
||||
});
|
||||
|
||||
@override
|
||||
ActivityUnfinishedStatusMessageState createState() =>
|
||||
ActivityUnfinishedStatusMessageState();
|
||||
}
|
||||
|
||||
class ActivityUnfinishedStatusMessageState
|
||||
extends State<ActivityUnfinishedStatusMessage> {
|
||||
String? _selectedRoleId;
|
||||
|
||||
void _selectRole(String id) {
|
||||
if (_selectedRoleId == id) return;
|
||||
if (mounted) setState(() => _selectedRoleId = id);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final isColumnMode = FluffyThemes.isColumnMode(context);
|
||||
final completed = widget.room.hasCompletedActivity;
|
||||
|
||||
final availableRoles = widget.room.activityPlan!.roles;
|
||||
final assignedRoles = widget.room.assignedRoles ?? {};
|
||||
final remainingRoles = availableRoles.length - assignedRoles.length;
|
||||
|
||||
final unassignedIds = availableRoles.keys
|
||||
.where((id) => !assignedRoles.containsKey(id))
|
||||
.toList();
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
if (!completed) ...[
|
||||
if (unassignedIds.isNotEmpty)
|
||||
ActivityParticipantList(
|
||||
room: widget.room,
|
||||
onTap: _selectRole,
|
||||
isSelected: (id) => _selectedRoleId == id,
|
||||
canSelect: (id) => unassignedIds.contains(id),
|
||||
),
|
||||
const SizedBox(height: 16.0),
|
||||
Text(
|
||||
remainingRoles > 0
|
||||
? L10n.of(context).unjoinedActivityMessage
|
||||
: L10n.of(context).fullActivityMessage,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: isColumnMode ? 16.0 : 12.0,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16.0),
|
||||
],
|
||||
if (completed || remainingRoles > 0)
|
||||
ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12.0,
|
||||
vertical: 8.0,
|
||||
),
|
||||
foregroundColor: theme.colorScheme.onPrimaryContainer,
|
||||
backgroundColor: theme.colorScheme.primaryContainer,
|
||||
),
|
||||
onPressed: completed
|
||||
? () {
|
||||
showFutureLoadingDialog(
|
||||
context: context,
|
||||
future: widget.room.continueActivity,
|
||||
);
|
||||
}
|
||||
: _selectedRoleId != null
|
||||
? () {
|
||||
showFutureLoadingDialog(
|
||||
context: context,
|
||||
future: () => widget.room.joinActivity(
|
||||
availableRoles[_selectedRoleId!]!,
|
||||
),
|
||||
);
|
||||
}
|
||||
: null,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
completed
|
||||
? L10n.of(context).continueText
|
||||
: L10n.of(context).confirmRole,
|
||||
style: TextStyle(
|
||||
fontSize: isColumnMode ? 16.0 : 12.0,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
223
lib/pangea/activity_sessions/activity_user_summaries_widget.dart
Normal file
223
lib/pangea/activity_sessions/activity_user_summaries_widget.dart
Normal file
|
|
@ -0,0 +1,223 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/l10n/l10n.dart';
|
||||
import 'package:fluffychat/pages/chat/chat.dart';
|
||||
import 'package:fluffychat/pangea/activity_sessions/activity_participant_indicator.dart';
|
||||
import 'package:fluffychat/pangea/activity_sessions/activity_room_extension.dart';
|
||||
import 'package:fluffychat/pangea/activity_summary/activity_summary_response_model.dart';
|
||||
import 'package:fluffychat/widgets/avatar.dart';
|
||||
|
||||
class ActivityUserSummaries extends StatelessWidget {
|
||||
final ChatController controller;
|
||||
|
||||
const ActivityUserSummaries({
|
||||
super.key,
|
||||
required this.controller,
|
||||
});
|
||||
|
||||
Room get room => controller.room;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final summary = room.activitySummary?.summary;
|
||||
if (summary == null) return const SizedBox();
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
child: Column(
|
||||
spacing: 4.0,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
L10n.of(context).activityFinishedMessage,
|
||||
),
|
||||
Text(
|
||||
summary.summary,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
vertical: 8.0,
|
||||
),
|
||||
),
|
||||
ButtonControlledCarouselView(
|
||||
summary: summary,
|
||||
controller: controller,
|
||||
),
|
||||
// Row(
|
||||
// mainAxisSize: MainAxisSize.min,
|
||||
// children: userSummaries.map((p) {
|
||||
// final user = room.getParticipants().firstWhereOrNull(
|
||||
// (u) => u.id == p.participantId,
|
||||
// );
|
||||
// final userRole = assignedRoles.values.firstWhere(
|
||||
// (role) => role.userId == p.participantId,
|
||||
// );
|
||||
// final userRoleInfo = availableRoles[userRole.id]!;
|
||||
// return ActivityParticipantIndicator(
|
||||
// availableRole: userRoleInfo,
|
||||
// assignedRole: userRole,
|
||||
// avatarUrl:
|
||||
// userRoleInfo.avatarUrl ?? user?.avatarUrl?.toString(),
|
||||
// borderRadius: BorderRadius.circular(4),
|
||||
// selected: controller.highlightedRole?.id == userRole.id,
|
||||
// onTap: () => controller.highlightRole(userRole),
|
||||
// );
|
||||
// }).toList(),
|
||||
// ),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ButtonControlledCarouselView extends StatelessWidget {
|
||||
final ActivitySummaryResponseModel summary;
|
||||
final ChatController controller;
|
||||
const ButtonControlledCarouselView({
|
||||
super.key,
|
||||
required this.summary,
|
||||
required this.controller,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final room = controller.room;
|
||||
|
||||
final availableRoles = room.activityPlan!.roles;
|
||||
final assignedRoles = room.assignedRoles ?? {};
|
||||
|
||||
final userSummaries = summary.participants.where(
|
||||
(p) => assignedRoles.values.any(
|
||||
(role) => role.userId == p.participantId,
|
||||
),
|
||||
);
|
||||
return Column(
|
||||
children: [
|
||||
SizedBox(
|
||||
height: 175.0,
|
||||
child: ListView(
|
||||
controller: controller.carouselController,
|
||||
itemExtent: 250,
|
||||
scrollDirection: Axis.horizontal,
|
||||
children: userSummaries.mapIndexed((i, p) {
|
||||
final user = room.getParticipants().firstWhereOrNull(
|
||||
(u) => u.id == p.participantId,
|
||||
);
|
||||
final userRole = assignedRoles.values.firstWhere(
|
||||
(role) => role.userId == p.participantId,
|
||||
);
|
||||
return Container(
|
||||
width: 250.0,
|
||||
margin: const EdgeInsets.only(right: 5.0),
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
decoration: ShapeDecoration(
|
||||
shape: RoundedRectangleBorder(
|
||||
side: BorderSide(
|
||||
width: 0.10,
|
||||
color: Theme.of(context).colorScheme.outline,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
spacing: 10.0,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Avatar(
|
||||
name: p.participantId.localpart,
|
||||
mxContent: user?.avatarUrl,
|
||||
size: 40,
|
||||
),
|
||||
Flexible(
|
||||
child: Text(
|
||||
"${userRole.role ?? L10n.of(context).participant} | ${user?.calcDisplayname() ?? p.participantId.localpart}",
|
||||
style: const TextStyle(
|
||||
fontSize: 12.0,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Text(
|
||||
p.feedback,
|
||||
style: const TextStyle(fontSize: 8.0),
|
||||
),
|
||||
Row(
|
||||
spacing: 14.0,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(4.0),
|
||||
child: Row(
|
||||
spacing: 4.0,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(
|
||||
Icons.school,
|
||||
size: 12.0,
|
||||
color: AppConfig.yellowDark,
|
||||
),
|
||||
Text(
|
||||
p.cefrLevel,
|
||||
style: const TextStyle(
|
||||
color: AppConfig.yellowDark,
|
||||
fontSize: 12.0,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (p.superlatives.isNotEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(4.0),
|
||||
child: Text(
|
||||
p.superlatives.first,
|
||||
style: const TextStyle(fontSize: 12.0),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: userSummaries.mapIndexed((i, p) {
|
||||
final user = room.getParticipants().firstWhereOrNull(
|
||||
(u) => u.id == p.participantId,
|
||||
);
|
||||
final userRole = assignedRoles.values.firstWhere(
|
||||
(role) => role.userId == p.participantId,
|
||||
);
|
||||
final userRoleInfo = availableRoles[userRole.id]!;
|
||||
return ActivityParticipantIndicator(
|
||||
name: userRoleInfo.name,
|
||||
userId: p.participantId,
|
||||
avatarUrl: userRoleInfo.avatarUrl ?? user?.avatarUrl?.toString(),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
selected: controller.highlightedRole?.id == userRole.id,
|
||||
onTap: () {
|
||||
controller.highlightRole(userRole);
|
||||
controller.carouselController.jumpTo(i * 250.0);
|
||||
},
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -2,13 +2,9 @@
|
|||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:cached_network_image_platform_interface/cached_network_image_platform_interface.dart';
|
||||
|
||||
import 'package:fluffychat/pangea/activity_planner/activity_plan_model.dart';
|
||||
import 'package:fluffychat/pangea/activity_planner/activity_planner_builder.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
import 'package:fluffychat/widgets/mxc_image.dart';
|
||||
import 'package:fluffychat/pangea/common/widgets/url_image_widget.dart';
|
||||
|
||||
class ActivitySuggestionCard extends StatelessWidget {
|
||||
final ActivityPlannerBuilderState controller;
|
||||
|
|
@ -53,36 +49,11 @@ class ActivitySuggestionCard extends StatelessWidget {
|
|||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
SizedBox(
|
||||
height: width,
|
||||
ImageByUrl(
|
||||
imageUrl: activity.imageURL,
|
||||
width: width,
|
||||
child: activity.imageURL != null
|
||||
? activity.imageURL!.startsWith("mxc")
|
||||
? MxcImage(
|
||||
uri: Uri.parse(activity.imageURL!),
|
||||
width: width,
|
||||
height: width,
|
||||
cacheKey: activity.activityId,
|
||||
fit: BoxFit.cover,
|
||||
)
|
||||
: CachedNetworkImage(
|
||||
imageUrl: activity.imageURL!,
|
||||
imageRenderMethodForWeb:
|
||||
ImageRenderMethodForWeb.HttpGet,
|
||||
httpHeaders: {
|
||||
'Authorization':
|
||||
'Bearer ${MatrixState.pangeaController.userController.accessToken}',
|
||||
},
|
||||
placeholder: (context, url) => const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
errorWidget: (context, url, error) =>
|
||||
const SizedBox(),
|
||||
fit: BoxFit.cover,
|
||||
width: width,
|
||||
height: width,
|
||||
)
|
||||
: const SizedBox(),
|
||||
borderRadius: const BorderRadius.all(Radius.zero),
|
||||
replacement: SizedBox(height: width),
|
||||
),
|
||||
Expanded(
|
||||
child: Padding(
|
||||
|
|
|
|||
|
|
@ -4,11 +4,13 @@ class ActivitySuggestionCardRow extends StatelessWidget {
|
|||
final IconData? icon;
|
||||
final Widget? leading;
|
||||
final Widget child;
|
||||
final double? iconSize;
|
||||
|
||||
const ActivitySuggestionCardRow({
|
||||
required this.child,
|
||||
this.icon,
|
||||
this.leading,
|
||||
this.iconSize,
|
||||
super.key,
|
||||
});
|
||||
|
||||
|
|
@ -23,7 +25,7 @@ class ActivitySuggestionCardRow extends StatelessWidget {
|
|||
if (icon != null)
|
||||
Icon(
|
||||
icon,
|
||||
size: 24.0,
|
||||
size: iconSize ?? 24.0,
|
||||
),
|
||||
Expanded(child: child),
|
||||
],
|
||||
|
|
|
|||
|
|
@ -2,8 +2,6 @@
|
|||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:cached_network_image_platform_interface/cached_network_image_platform_interface.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
|
||||
|
|
@ -12,11 +10,10 @@ import 'package:fluffychat/pangea/activity_planner/activity_planner_builder.dart
|
|||
import 'package:fluffychat/pangea/activity_suggestions/activity_suggestion_card_row.dart';
|
||||
import 'package:fluffychat/pangea/activity_suggestions/activity_suggestion_dialog.dart';
|
||||
import 'package:fluffychat/pangea/chat_settings/widgets/language_level_dropdown.dart';
|
||||
import 'package:fluffychat/pangea/common/widgets/url_image_widget.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/enums/language_level_type_enum.dart';
|
||||
import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart';
|
||||
import 'package:fluffychat/widgets/avatar.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
import 'package:fluffychat/widgets/mxc_image.dart';
|
||||
|
||||
class ActivitySuggestionDialogContent extends StatelessWidget {
|
||||
final ActivitySuggestionDialogState controller;
|
||||
|
|
@ -50,52 +47,23 @@ class _ActivitySuggestionDialogImage extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final imageWidth = (width / 2) + 42.0;
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(24.0),
|
||||
width: (width / 2) + 42.0,
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(20.0),
|
||||
child: activityController.avatar != null
|
||||
? Image.memory(
|
||||
width: imageWidth,
|
||||
child: activityController.avatar != null
|
||||
? ClipRRect(
|
||||
borderRadius: BorderRadius.circular(20.0),
|
||||
child: Image.memory(
|
||||
activityController.avatar!,
|
||||
fit: BoxFit.cover,
|
||||
)
|
||||
: activityController.updatedActivity.imageURL != null
|
||||
? activityController.updatedActivity.imageURL!.startsWith("mxc")
|
||||
? MxcImage(
|
||||
uri: Uri.parse(
|
||||
activityController.updatedActivity.imageURL!,
|
||||
),
|
||||
width: width / 2,
|
||||
height: 200,
|
||||
cacheKey: activityController.updatedActivity.activityId,
|
||||
fit: BoxFit.cover,
|
||||
)
|
||||
: CachedNetworkImage(
|
||||
imageRenderMethodForWeb:
|
||||
ImageRenderMethodForWeb.HttpGet,
|
||||
httpHeaders: {
|
||||
'Authorization':
|
||||
'Bearer ${MatrixState.pangeaController.userController.accessToken}',
|
||||
},
|
||||
imageUrl: activityController.updatedActivity.imageURL!,
|
||||
fit: BoxFit.cover,
|
||||
placeholder: (
|
||||
context,
|
||||
url,
|
||||
) =>
|
||||
const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
errorWidget: (
|
||||
context,
|
||||
url,
|
||||
error,
|
||||
) =>
|
||||
const SizedBox(),
|
||||
)
|
||||
: null,
|
||||
),
|
||||
),
|
||||
)
|
||||
: ImageByUrl(
|
||||
imageUrl: activityController.updatedActivity.imageURL,
|
||||
width: imageWidth,
|
||||
borderRadius: BorderRadius.circular(20.0),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -623,50 +591,15 @@ class _ActivitySuggestionLaunchContent extends StatelessWidget {
|
|||
),
|
||||
),
|
||||
ActivitySuggestionCardRow(
|
||||
leading: activityController.updatedActivity.imageURL != null
|
||||
? ClipRRect(
|
||||
borderRadius: BorderRadius.circular(4.0),
|
||||
child: activityController.updatedActivity.imageURL!
|
||||
.startsWith("mxc")
|
||||
? MxcImage(
|
||||
uri: Uri.parse(
|
||||
activityController.updatedActivity.imageURL!,
|
||||
),
|
||||
width: 24.0,
|
||||
height: 24.0,
|
||||
cacheKey: activityController.updatedActivity.activityId,
|
||||
fit: BoxFit.cover,
|
||||
)
|
||||
: CachedNetworkImage(
|
||||
imageRenderMethodForWeb:
|
||||
ImageRenderMethodForWeb.HttpGet,
|
||||
httpHeaders: {
|
||||
'Authorization':
|
||||
'Bearer ${MatrixState.pangeaController.userController.accessToken}',
|
||||
},
|
||||
imageUrl: activityController.updatedActivity.imageURL!,
|
||||
fit: BoxFit.cover,
|
||||
width: 24.0,
|
||||
height: 24.0,
|
||||
placeholder: (
|
||||
context,
|
||||
url,
|
||||
) =>
|
||||
const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
errorWidget: (
|
||||
context,
|
||||
url,
|
||||
error,
|
||||
) =>
|
||||
const SizedBox(),
|
||||
),
|
||||
)
|
||||
: const Icon(
|
||||
Icons.event_note_outlined,
|
||||
size: 24.0,
|
||||
),
|
||||
leading: ImageByUrl(
|
||||
imageUrl: activityController.updatedActivity.imageURL,
|
||||
width: 24.0,
|
||||
borderRadius: BorderRadius.circular(4.0),
|
||||
replacement: const Icon(
|
||||
Icons.event_note_outlined,
|
||||
size: 24.0,
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
activityController.updatedActivity.title,
|
||||
style: const TextStyle(
|
||||
|
|
|
|||
|
|
@ -1,11 +1,8 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
import 'package:universal_html/html.dart' as html;
|
||||
|
||||
import 'package:fluffychat/config/themes.dart';
|
||||
import 'package:fluffychat/l10n/l10n.dart';
|
||||
|
|
@ -14,7 +11,7 @@ import 'package:fluffychat/pangea/chat_settings/pages/room_details_buttons.dart'
|
|||
import 'package:fluffychat/pangea/chat_settings/pages/room_participants_widget.dart';
|
||||
import 'package:fluffychat/pangea/chat_settings/pages/space_details_button_row.dart';
|
||||
import 'package:fluffychat/pangea/chat_settings/widgets/delete_space_dialog.dart';
|
||||
import 'package:fluffychat/pangea/common/config/environment.dart';
|
||||
import 'package:fluffychat/pangea/common/widgets/share_room_button.dart';
|
||||
import 'package:fluffychat/pangea/course_chats/course_chats_page.dart';
|
||||
import 'package:fluffychat/pangea/course_creation/course_info_chip_widget.dart';
|
||||
import 'package:fluffychat/pangea/course_plans/course_plan_builder.dart';
|
||||
|
|
@ -23,7 +20,6 @@ import 'package:fluffychat/pangea/course_settings/course_settings.dart';
|
|||
import 'package:fluffychat/pangea/events/constants/pangea_event_types.dart';
|
||||
import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart';
|
||||
import 'package:fluffychat/pangea/space_analytics/space_analytics.dart';
|
||||
import 'package:fluffychat/pangea/spaces/constants/space_constants.dart';
|
||||
import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart';
|
||||
import 'package:fluffychat/widgets/adaptive_dialogs/show_ok_cancel_alert_dialog.dart';
|
||||
import 'package:fluffychat/widgets/avatar.dart';
|
||||
|
|
@ -278,51 +274,7 @@ class SpaceDetailsContentState extends State<SpaceDetailsContent> {
|
|||
if (widget.room.classCode != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 16.0),
|
||||
child: PopupMenuButton(
|
||||
child: const Icon(Symbols.upload),
|
||||
onSelected: (value) async {
|
||||
final spaceCode = widget.room.classCode!;
|
||||
String toCopy = spaceCode;
|
||||
if (value == 0) {
|
||||
final String initialUrl = kIsWeb
|
||||
? html.window.origin!
|
||||
: Environment.frontendURL;
|
||||
toCopy =
|
||||
"$initialUrl/#/join_with_link?${SpaceConstants.classCode}=${widget.room.classCode}";
|
||||
}
|
||||
|
||||
await Clipboard.setData(ClipboardData(text: toCopy));
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
L10n.of(context).copiedToClipboard,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
itemBuilder: (BuildContext context) =>
|
||||
<PopupMenuEntry<int>>[
|
||||
PopupMenuItem<int>(
|
||||
value: 0,
|
||||
child: ListTile(
|
||||
title: Text(L10n.of(context).shareSpaceLink),
|
||||
contentPadding: const EdgeInsets.all(0),
|
||||
),
|
||||
),
|
||||
PopupMenuItem<int>(
|
||||
value: 1,
|
||||
child: ListTile(
|
||||
title: Text(
|
||||
L10n.of(context)
|
||||
.shareInviteCode(widget.room.classCode!),
|
||||
),
|
||||
contentPadding: const EdgeInsets.all(0),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: ShareRoomButton(room: widget.room),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
|
|||
70
lib/pangea/common/widgets/share_room_button.dart
Normal file
70
lib/pangea/common/widgets/share_room_button.dart
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
import 'package:universal_html/html.dart' as html;
|
||||
|
||||
import 'package:fluffychat/l10n/l10n.dart';
|
||||
import 'package:fluffychat/pangea/common/config/environment.dart';
|
||||
import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart';
|
||||
import 'package:fluffychat/pangea/spaces/constants/space_constants.dart';
|
||||
|
||||
class ShareRoomButton extends StatelessWidget {
|
||||
final Room room;
|
||||
const ShareRoomButton({
|
||||
super.key,
|
||||
required this.room,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (room.classCode == null) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
return PopupMenuButton(
|
||||
child: const Icon(Symbols.upload),
|
||||
onSelected: (value) async {
|
||||
final spaceCode = room.classCode!;
|
||||
String toCopy = spaceCode;
|
||||
if (value == 0) {
|
||||
final String initialUrl =
|
||||
kIsWeb ? html.window.origin! : Environment.frontendURL;
|
||||
toCopy =
|
||||
"$initialUrl/#/join_with_link?${SpaceConstants.classCode}=${room.classCode}";
|
||||
}
|
||||
|
||||
await Clipboard.setData(ClipboardData(text: toCopy));
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
L10n.of(context).copiedToClipboard,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
itemBuilder: (BuildContext context) => <PopupMenuEntry<int>>[
|
||||
PopupMenuItem<int>(
|
||||
value: 0,
|
||||
child: ListTile(
|
||||
title: Text(L10n.of(context).shareSpaceLink),
|
||||
contentPadding: const EdgeInsets.all(0),
|
||||
),
|
||||
),
|
||||
PopupMenuItem<int>(
|
||||
value: 1,
|
||||
child: ListTile(
|
||||
title: Text(
|
||||
L10n.of(context).shareInviteCode(room.classCode!),
|
||||
),
|
||||
contentPadding: const EdgeInsets.all(0),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
71
lib/pangea/common/widgets/url_image_widget.dart
Normal file
71
lib/pangea/common/widgets/url_image_widget.dart
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
// ignore_for_file: depend_on_referenced_packages
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:cached_network_image_platform_interface/cached_network_image_platform_interface.dart';
|
||||
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
import 'package:fluffychat/widgets/mxc_image.dart';
|
||||
|
||||
class ImageByUrl extends StatelessWidget {
|
||||
final String? imageUrl;
|
||||
final double width;
|
||||
final BorderRadius borderRadius;
|
||||
final Widget? replacement;
|
||||
|
||||
const ImageByUrl({
|
||||
super.key,
|
||||
required this.imageUrl,
|
||||
required this.width,
|
||||
this.replacement,
|
||||
this.borderRadius = const BorderRadius.all(Radius.circular(20.0)),
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (imageUrl == null) {
|
||||
return replacement ?? const SizedBox();
|
||||
}
|
||||
|
||||
return SizedBox(
|
||||
width: width,
|
||||
height: width,
|
||||
child: ClipRRect(
|
||||
borderRadius: borderRadius,
|
||||
child: imageUrl!.startsWith("mxc")
|
||||
? MxcImage(
|
||||
uri: Uri.parse(imageUrl!),
|
||||
width: width,
|
||||
height: width,
|
||||
cacheKey: imageUrl,
|
||||
fit: BoxFit.cover,
|
||||
)
|
||||
: CachedNetworkImage(
|
||||
width: width,
|
||||
height: width,
|
||||
fit: BoxFit.cover,
|
||||
imageUrl: imageUrl!,
|
||||
placeholder: (
|
||||
context,
|
||||
url,
|
||||
) =>
|
||||
const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
errorWidget: (
|
||||
context,
|
||||
url,
|
||||
error,
|
||||
) =>
|
||||
replacement ?? const SizedBox(),
|
||||
httpHeaders: {
|
||||
'Authorization':
|
||||
'Bearer ${MatrixState.pangeaController.userController.accessToken}',
|
||||
},
|
||||
imageRenderMethodForWeb: ImageRenderMethodForWeb.HttpGet,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -50,50 +50,56 @@ class CourseChatsView extends StatelessWidget {
|
|||
final Topic? topic = controller.selectedTopic;
|
||||
final List<String> activityIds = topic?.activityIds ?? [];
|
||||
|
||||
final childrenIds =
|
||||
room.spaceChildren.map((c) => c.roomId).whereType<String>().toSet();
|
||||
|
||||
final joinedChats = [];
|
||||
final joinedSessions = [];
|
||||
final joinedRooms = room.client.rooms
|
||||
.where((room) => childrenIds.remove(room.id))
|
||||
.where((room) => !room.isHiddenRoom)
|
||||
.toList();
|
||||
|
||||
for (final joinedRoom in joinedRooms) {
|
||||
if (joinedRoom.isActivitySession) {
|
||||
if (topic == null ||
|
||||
activityIds.contains(joinedRoom.activityPlan?.activityId)) {
|
||||
joinedSessions.add(joinedRoom);
|
||||
}
|
||||
} else {
|
||||
joinedChats.add(joinedRoom);
|
||||
}
|
||||
}
|
||||
|
||||
final discoveredGroupChats = [];
|
||||
final discoveredSessions = [];
|
||||
final discoveredChildren =
|
||||
controller.discoveredChildren ?? <SpaceRoomsChunk>[];
|
||||
|
||||
for (final child in discoveredChildren) {
|
||||
if (child.roomType?.startsWith(PangeaRoomTypes.activitySession) ==
|
||||
true) {
|
||||
if (activityIds.contains(child.roomType!.split(":").last)) {
|
||||
discoveredSessions.add(child);
|
||||
}
|
||||
} else {
|
||||
discoveredGroupChats.add(child);
|
||||
}
|
||||
}
|
||||
|
||||
final isColumnMode = FluffyThemes.isColumnMode(context);
|
||||
|
||||
return StreamBuilder(
|
||||
stream: room.client.onSync.stream
|
||||
.where((s) => s.hasRoomUpdate)
|
||||
.rateLimit(const Duration(seconds: 1)),
|
||||
builder: (context, snapshot) {
|
||||
final childrenIds = room.spaceChildren
|
||||
.map((c) => c.roomId)
|
||||
.whereType<String>()
|
||||
.toSet();
|
||||
|
||||
final joinedChats = [];
|
||||
final joinedSessions = [];
|
||||
final joinedRooms = room.client.rooms
|
||||
.where((room) => childrenIds.remove(room.id))
|
||||
.where((room) => !room.isHiddenRoom)
|
||||
.toList();
|
||||
|
||||
for (final joinedRoom in joinedRooms) {
|
||||
if (joinedRoom.isActivitySession) {
|
||||
String? activityId = joinedRoom.activityPlan?.activityId;
|
||||
if (activityId == null && joinedRoom.isActivityRoomType) {
|
||||
activityId = joinedRoom.roomType!.split(":").last;
|
||||
}
|
||||
|
||||
if (topic == null || activityIds.contains(activityId)) {
|
||||
joinedSessions.add(joinedRoom);
|
||||
}
|
||||
} else {
|
||||
joinedChats.add(joinedRoom);
|
||||
}
|
||||
}
|
||||
|
||||
final discoveredGroupChats = [];
|
||||
final discoveredSessions = [];
|
||||
final discoveredChildren =
|
||||
controller.discoveredChildren ?? <SpaceRoomsChunk>[];
|
||||
|
||||
for (final child in discoveredChildren) {
|
||||
final roomType = child.roomType;
|
||||
if (roomType?.startsWith(PangeaRoomTypes.activitySession) ==
|
||||
true) {
|
||||
if (activityIds.contains(roomType!.split(":").last)) {
|
||||
discoveredSessions.add(child);
|
||||
}
|
||||
} else {
|
||||
discoveredGroupChats.add(child);
|
||||
}
|
||||
}
|
||||
|
||||
final isColumnMode = FluffyThemes.isColumnMode(context);
|
||||
return Padding(
|
||||
padding: isColumnMode
|
||||
? const EdgeInsets.symmetric(
|
||||
|
|
|
|||
|
|
@ -1,51 +0,0 @@
|
|||
// ignore_for_file: depend_on_referenced_packages
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:cached_network_image_platform_interface/cached_network_image_platform_interface.dart';
|
||||
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
||||
class CourseImage extends StatelessWidget {
|
||||
final String? imageUrl;
|
||||
final double width;
|
||||
final Widget? replacement;
|
||||
final BorderRadius borderRadius;
|
||||
|
||||
const CourseImage({
|
||||
super.key,
|
||||
required this.imageUrl,
|
||||
required this.width,
|
||||
this.replacement,
|
||||
this.borderRadius = const BorderRadius.all(Radius.circular(20.0)),
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ClipRRect(
|
||||
borderRadius: borderRadius,
|
||||
child: imageUrl != null
|
||||
? CachedNetworkImage(
|
||||
width: width,
|
||||
height: width,
|
||||
fit: BoxFit.cover,
|
||||
imageUrl: imageUrl!,
|
||||
httpHeaders: {
|
||||
'Authorization':
|
||||
'Bearer ${MatrixState.pangeaController.userController.accessToken}',
|
||||
},
|
||||
imageRenderMethodForWeb: ImageRenderMethodForWeb.HttpGet,
|
||||
placeholder: (context, url) {
|
||||
return const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
);
|
||||
},
|
||||
errorWidget: (context, url, error) {
|
||||
return replacement ?? const SizedBox();
|
||||
},
|
||||
)
|
||||
: replacement ?? const SizedBox(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:fluffychat/pangea/course_creation/course_image_widget.dart';
|
||||
import 'package:fluffychat/pangea/common/widgets/url_image_widget.dart';
|
||||
import 'package:fluffychat/pangea/course_creation/course_info_chip_widget.dart';
|
||||
import 'package:fluffychat/pangea/course_plans/course_plan_model.dart';
|
||||
import 'package:fluffychat/widgets/hover_builder.dart';
|
||||
|
|
@ -41,7 +41,7 @@ class CoursePlanTile extends StatelessWidget {
|
|||
child: Row(
|
||||
spacing: 4.0,
|
||||
children: [
|
||||
CourseImage(
|
||||
ImageByUrl(
|
||||
imageUrl: course.imageUrl,
|
||||
width: 40.0,
|
||||
replacement: Container(
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import 'package:cached_network_image/cached_network_image.dart';
|
|||
import 'package:go_router/go_router.dart';
|
||||
|
||||
import 'package:fluffychat/l10n/l10n.dart';
|
||||
import 'package:fluffychat/pangea/course_creation/course_image_widget.dart';
|
||||
import 'package:fluffychat/pangea/common/widgets/url_image_widget.dart';
|
||||
import 'package:fluffychat/pangea/course_creation/course_info_chip_widget.dart';
|
||||
import 'package:fluffychat/pangea/course_plans/course_plan_builder.dart';
|
||||
import 'package:fluffychat/pangea/course_plans/course_plan_model.dart';
|
||||
|
|
@ -58,7 +58,7 @@ class SelectedCourseView extends StatelessWidget {
|
|||
return Column(
|
||||
spacing: 8.0,
|
||||
children: [
|
||||
CourseImage(
|
||||
ImageByUrl(
|
||||
imageUrl: course.imageUrl,
|
||||
width: 100.0,
|
||||
replacement: Container(
|
||||
|
|
|
|||
|
|
@ -195,6 +195,16 @@ class CoursePlanModel {
|
|||
}
|
||||
}
|
||||
|
||||
final Map<String, ActivityRole> roles = {};
|
||||
for (final role in activity.roles) {
|
||||
roles[role.id] = ActivityRole(
|
||||
id: role.id,
|
||||
name: role.name,
|
||||
avatarUrl: role.avatarUrl,
|
||||
goal: role.goal,
|
||||
);
|
||||
}
|
||||
|
||||
activityPlans ??= [];
|
||||
activityPlans.add(
|
||||
ActivityPlanModel(
|
||||
|
|
@ -216,17 +226,7 @@ class CoursePlanModel {
|
|||
vocab: activity.vocabs
|
||||
.map((v) => Vocab(lemma: v.lemma, pos: v.pos))
|
||||
.toList(),
|
||||
roles: activity.roles.asMap().map(
|
||||
(index, v) => MapEntry(
|
||||
index.toString(),
|
||||
ActivityRole(
|
||||
id: v.id,
|
||||
name: v.name,
|
||||
avatarUrl: v.avatarUrl,
|
||||
goal: v.goal,
|
||||
),
|
||||
),
|
||||
),
|
||||
roles: roles,
|
||||
imageURL: activityMedias != null && activityMedias.isNotEmpty
|
||||
? '${Environment.cmsApi}${activityMedias.first.url}'
|
||||
: null,
|
||||
|
|
|
|||
|
|
@ -28,12 +28,13 @@ extension RoomInformationRoomExtension on Room {
|
|||
return botOptions?.mode == BotMode.directChat && await botIsInRoom;
|
||||
}
|
||||
|
||||
String? get roomType =>
|
||||
getState(EventTypes.RoomCreate)?.content.tryGet<String>('type');
|
||||
|
||||
bool isAnalyticsRoomOfUser(String userId) =>
|
||||
isAnalyticsRoom && isMadeByUser(userId);
|
||||
|
||||
bool get isAnalyticsRoom =>
|
||||
getState(EventTypes.RoomCreate)?.content.tryGet<String>('type') ==
|
||||
PangeaRoomTypes.analytics;
|
||||
bool get isAnalyticsRoom => roomType == PangeaRoomTypes.analytics;
|
||||
|
||||
bool get isHiddenRoom => isAnalyticsRoom || isHiddenActivityRoom;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,13 +30,28 @@ class TwoColumnLayout extends StatelessWidget {
|
|||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
|
||||
// #Pangea
|
||||
bool showNavRail = FluffyThemes.isColumnMode(context);
|
||||
if (!showNavRail) {
|
||||
final roomID = state.pathParameters['roomid'];
|
||||
final spaceID = state.pathParameters['spaceid'];
|
||||
|
||||
if (roomID == null && spaceID == null) {
|
||||
showNavRail = !["newcourse"].any(
|
||||
(p) => state.fullPath?.contains(p) ?? false,
|
||||
);
|
||||
} else if (roomID == null) {
|
||||
showNavRail = state.fullPath?.endsWith(':spaceid') == true;
|
||||
}
|
||||
}
|
||||
// Pangea#
|
||||
|
||||
return ScaffoldMessenger(
|
||||
child: Scaffold(
|
||||
body: Row(
|
||||
children: [
|
||||
// #Pangea
|
||||
if (FluffyThemes.isColumnMode(context) ||
|
||||
!(state.fullPath?.endsWith(":roomid") ?? false)) ...[
|
||||
if (showNavRail) ...[
|
||||
SpacesNavigationRail(
|
||||
activeSpaceId: state.pathParameters['spaceid'],
|
||||
path: state.fullPath,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue