diff --git a/lib/pangea/activity_sessions/activity_session_start/activity_session_start_page.dart b/lib/pangea/activity_sessions/activity_session_start/activity_session_start_page.dart index e4caea598..93a7b9609 100644 --- a/lib/pangea/activity_sessions/activity_session_start/activity_session_start_page.dart +++ b/lib/pangea/activity_sessions/activity_session_start/activity_session_start_page.dart @@ -57,6 +57,8 @@ class ActivitySessionStartController extends State { bool showInstructions = false; String? _selectedRoleId; + Timer? _pingCooldown; + @override void initState() { super.initState(); @@ -78,6 +80,12 @@ class ActivitySessionStartController extends State { } } + @override + void dispose() { + _pingCooldown?.cancel(); + super.dispose(); + } + Room? get room => widget.room; Room? get parent => widget.parentId != null @@ -152,6 +160,11 @@ class ActivitySessionStartController extends State { return parent!.numOpenSessions(widget.activityId) > 0; } + bool get canPingParticipants { + if (room == null || room?.courseParent == null) return false; + return _pingCooldown == null || !_pingCooldown!.isActive; + } + void toggleInstructions() { setState(() { showInstructions = !showInstructions; @@ -276,6 +289,16 @@ class ActivitySessionStartController extends State { throw Exception("Activity is not part of a course"); } + if (!canPingParticipants) { + throw Exception("Ping is on cooldown"); + } + + _pingCooldown?.cancel(); + _pingCooldown = Timer(const Duration(minutes: 1), () { + _pingCooldown = null; + if (mounted) setState(() {}); + }); + await room!.courseParent!.sendEvent( { "body": L10n.of(context).pingParticipantsNotification( @@ -286,6 +309,8 @@ class ActivitySessionStartController extends State { "pangea.activity.session_room_id": room!.id, }, ); + + if (mounted) setState(() {}); } Future playWithBot() async { diff --git a/lib/pangea/activity_sessions/activity_session_start/activity_sessions_start_view.dart b/lib/pangea/activity_sessions/activity_session_start/activity_sessions_start_view.dart index f6c0d1be2..59cea6c42 100644 --- a/lib/pangea/activity_sessions/activity_session_start/activity_sessions_start_view.dart +++ b/lib/pangea/activity_sessions/activity_session_start/activity_sessions_start_view.dart @@ -180,11 +180,16 @@ class ActivitySessionStartView extends StatelessWidget { null) ElevatedButton( style: buttonStyle, - onPressed: () => - showFutureLoadingDialog( - context: context, - future: controller.pingCourse, - ), + onPressed: controller + .canPingParticipants + ? () { + showFutureLoadingDialog( + context: context, + future: controller + .pingCourse, + ); + } + : null, child: Row( mainAxisAlignment: MainAxisAlignment.center, diff --git a/lib/pangea/common/widgets/url_image_widget.dart b/lib/pangea/common/widgets/url_image_widget.dart index d78d9a48d..ea3b6eef9 100644 --- a/lib/pangea/common/widgets/url_image_widget.dart +++ b/lib/pangea/common/widgets/url_image_widget.dart @@ -4,6 +4,7 @@ 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:shimmer/shimmer.dart'; import 'package:fluffychat/widgets/matrix.dart'; import 'package:fluffychat/widgets/mxc_image.dart'; @@ -50,8 +51,18 @@ class ImageByUrl extends StatelessWidget { context, url, ) => - const Center( - child: CircularProgressIndicator(), + Shimmer.fromColors( + baseColor: + Theme.of(context).colorScheme.primary.withAlpha(20), + highlightColor: + Theme.of(context).colorScheme.primary.withAlpha(50), + child: Container( + width: width, + height: width, + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.surface, + ), + ), ), errorWidget: ( context, diff --git a/lib/pangea/course_creation/course_plan_tile_widget.dart b/lib/pangea/course_creation/course_plan_tile_widget.dart index 30940ce38..516df058d 100644 --- a/lib/pangea/course_creation/course_plan_tile_widget.dart +++ b/lib/pangea/course_creation/course_plan_tile_widget.dart @@ -5,6 +5,7 @@ import 'package:flutter/material.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/pangea/course_plans/map_clipper.dart'; import 'package:fluffychat/widgets/hover_builder.dart'; class CoursePlanTile extends StatelessWidget { @@ -41,14 +42,18 @@ class CoursePlanTile extends StatelessWidget { child: Row( spacing: 4.0, children: [ - ImageByUrl( - imageUrl: course.imageUrl, - width: 40.0, - replacement: Container( + ClipPath( + clipper: MapClipper(), + child: ImageByUrl( + imageUrl: course.imageUrl, + borderRadius: BorderRadius.circular(0.0), width: 40.0, - height: 40.0, - decoration: BoxDecoration( - color: theme.colorScheme.secondary, + replacement: Container( + width: 40.0, + height: 40.0, + decoration: BoxDecoration( + color: theme.colorScheme.secondary, + ), ), ), ), diff --git a/lib/pangea/course_creation/selected_course_page.dart b/lib/pangea/course_creation/selected_course_page.dart index dba6d780b..a8cb8ab92 100644 --- a/lib/pangea/course_creation/selected_course_page.dart +++ b/lib/pangea/course_creation/selected_course_page.dart @@ -2,7 +2,6 @@ import 'dart:typed_data'; import 'package:flutter/material.dart'; -import 'package:collection/collection.dart'; import 'package:go_router/go_router.dart'; import 'package:http/http.dart' as http; import 'package:http/http.dart'; @@ -28,11 +27,7 @@ class SelectedCourseController extends State { final client = Matrix.of(context).client; Uint8List? avatar; Uri? avatarUrl; - final imageUrl = course.imageUrl ?? - course.loadedTopics - .lastWhereOrNull((topic) => topic.imageUrl != null) - ?.imageUrl; - + final imageUrl = course.imageUrl; if (imageUrl != null) { try { final Response response = await http.get( diff --git a/lib/pangea/course_creation/selected_course_view.dart b/lib/pangea/course_creation/selected_course_view.dart index b14cb1367..d6eadeb7e 100644 --- a/lib/pangea/course_creation/selected_course_view.dart +++ b/lib/pangea/course_creation/selected_course_view.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:cached_network_image/cached_network_image.dart'; import 'package:go_router/go_router.dart'; import 'package:fluffychat/l10n/l10n.dart'; @@ -8,6 +7,8 @@ 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'; +import 'package:fluffychat/pangea/course_plans/map_clipper.dart'; +import 'package:fluffychat/pangea/course_settings/pin_clipper.dart'; import 'package:fluffychat/widgets/future_loading_dialog.dart'; import 'package:fluffychat/widgets/layouts/max_width_body.dart'; @@ -58,14 +59,18 @@ class SelectedCourseView extends StatelessWidget { return Column( spacing: 8.0, children: [ - ImageByUrl( - imageUrl: course.imageUrl, - width: 100.0, - replacement: Container( + ClipPath( + clipper: MapClipper(), + child: ImageByUrl( + imageUrl: course.imageUrl, width: 100.0, - height: 100.0, - decoration: BoxDecoration( - color: theme.colorScheme.secondary, + borderRadius: BorderRadius.circular(0.0), + replacement: Container( + width: 100.0, + height: 100.0, + decoration: BoxDecoration( + color: theme.colorScheme.secondary, + ), ), ), ), @@ -124,39 +129,19 @@ class SelectedCourseView extends StatelessWidget { spacing: 8.0, crossAxisAlignment: CrossAxisAlignment.start, children: [ - ClipRRect( - borderRadius: BorderRadius.circular(80), - child: topic.imageUrl != null - ? CachedNetworkImage( - width: 40.0, - height: 40.0, - fit: BoxFit.cover, - imageUrl: topic.imageUrl!, - placeholder: (context, url) { - return const Center( - child: - CircularProgressIndicator(), - ); - }, - errorWidget: (context, url, error) { - return Container( - width: 40.0, - height: 40.0, - decoration: BoxDecoration( - color: theme - .colorScheme.secondary, - ), - ); - }, - ) - : Container( - width: 40.0, - height: 40.0, - decoration: BoxDecoration( - color: - theme.colorScheme.secondary, - ), - ), + ClipPath( + clipper: PinClipper(), + child: ImageByUrl( + imageUrl: topic.imageUrl, + width: 45.0, + replacement: Container( + width: 45.0, + height: 45.0, + decoration: BoxDecoration( + color: theme.colorScheme.secondary, + ), + ), + ), ), Flexible( child: Column( diff --git a/lib/pangea/course_plans/course_plan_model.dart b/lib/pangea/course_plans/course_plan_model.dart index de47367bf..63f9a4118 100644 --- a/lib/pangea/course_plans/course_plan_model.dart +++ b/lib/pangea/course_plans/course_plan_model.dart @@ -1,3 +1,5 @@ +import 'package:collection/collection.dart'; + import 'package:fluffychat/pangea/activity_planner/activity_plan_model.dart'; import 'package:fluffychat/pangea/common/config/environment.dart'; import 'package:fluffychat/pangea/course_plans/course_media_repo.dart'; @@ -111,7 +113,9 @@ class CoursePlanModel { List get loadedMediaUrls => CourseMediaRepo.getSync(mediaIds); Future> fetchMediaUrls() => CourseMediaRepo.get(uuid, mediaIds); String? get imageUrl => loadedMediaUrls.isEmpty - ? null + ? loadedTopics + .lastWhereOrNull((topic) => topic.imageUrl != null) + ?.imageUrl : "${Environment.cmsApi}${loadedMediaUrls.first}"; Future init() async { diff --git a/lib/pangea/course_settings/pin_clipper.dart b/lib/pangea/course_settings/pin_clipper.dart index 93c879a4f..cab641e2f 100644 --- a/lib/pangea/course_settings/pin_clipper.dart +++ b/lib/pangea/course_settings/pin_clipper.dart @@ -10,7 +10,7 @@ class PinClipper extends CustomClipper { path.moveTo(w * 0.1, h * 0.4); path.arcToPoint( Offset(w * 0.9, h * 0.4), - radius: const Radius.circular(20), + radius: const Radius.circular(15), ); path.quadraticBezierTo(w * 0.9, h * 0.75, w / 2, h); path.quadraticBezierTo(w * 0.1, h * 0.75, w * 0.1, h * 0.4); diff --git a/lib/pangea/space_analytics/space_analytics.dart b/lib/pangea/space_analytics/space_analytics.dart index 3f242bd21..491ef7203 100644 --- a/lib/pangea/space_analytics/space_analytics.dart +++ b/lib/pangea/space_analytics/space_analytics.dart @@ -228,9 +228,10 @@ class SpaceAnalyticsState extends State { ]; await Future.wait(futures); - selectedLanguage = availableLanguages.contains(_userL2) - ? _userL2 - : availableLanguages.firstOrNull; + selectedLanguage = + availableLanguages.contains(_userL2) || availableLanguages.isEmpty + ? _userL2 + : availableLanguages.firstOrNull; await refresh(); if (mounted) {