3962 usability test todos (#3973)
* in new course pages, show images * in space analytics, if no available languages, pick user's l2 * chore: add cooldown on ping participants * replace image loading icon with shimmer
This commit is contained in:
parent
c831d6964d
commit
7348e655f4
9 changed files with 97 additions and 66 deletions
|
|
@ -57,6 +57,8 @@ class ActivitySessionStartController extends State<ActivitySessionStartPage> {
|
|||
bool showInstructions = false;
|
||||
String? _selectedRoleId;
|
||||
|
||||
Timer? _pingCooldown;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
|
@ -78,6 +80,12 @@ class ActivitySessionStartController extends State<ActivitySessionStartPage> {
|
|||
}
|
||||
}
|
||||
|
||||
@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<ActivitySessionStartPage> {
|
|||
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<ActivitySessionStartPage> {
|
|||
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<ActivitySessionStartPage> {
|
|||
"pangea.activity.session_room_id": room!.id,
|
||||
},
|
||||
);
|
||||
|
||||
if (mounted) setState(() {});
|
||||
}
|
||||
|
||||
Future<void> playWithBot() async {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -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<SelectedCourse> {
|
|||
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(
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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<String> get loadedMediaUrls => CourseMediaRepo.getSync(mediaIds);
|
||||
Future<List<String>> fetchMediaUrls() => CourseMediaRepo.get(uuid, mediaIds);
|
||||
String? get imageUrl => loadedMediaUrls.isEmpty
|
||||
? null
|
||||
? loadedTopics
|
||||
.lastWhereOrNull((topic) => topic.imageUrl != null)
|
||||
?.imageUrl
|
||||
: "${Environment.cmsApi}${loadedMediaUrls.first}";
|
||||
|
||||
Future<void> init() async {
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ class PinClipper extends CustomClipper<Path> {
|
|||
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);
|
||||
|
|
|
|||
|
|
@ -228,9 +228,10 @@ class SpaceAnalyticsState extends State<SpaceAnalytics> {
|
|||
];
|
||||
await Future.wait(futures);
|
||||
|
||||
selectedLanguage = availableLanguages.contains(_userL2)
|
||||
? _userL2
|
||||
: availableLanguages.firstOrNull;
|
||||
selectedLanguage =
|
||||
availableLanguages.contains(_userL2) || availableLanguages.isEmpty
|
||||
? _userL2
|
||||
: availableLanguages.firstOrNull;
|
||||
|
||||
await refresh();
|
||||
if (mounted) {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue