3517 non local storage of bookmarked activities (#3761)
This commit is contained in:
parent
bae5765a97
commit
7c03c70105
27 changed files with 624 additions and 465 deletions
|
|
@ -8,8 +8,6 @@ import 'package:matrix/matrix.dart';
|
|||
|
||||
import 'package:fluffychat/l10n/l10n.dart';
|
||||
import 'package:fluffychat/pages/new_group/new_group_view.dart';
|
||||
import 'package:fluffychat/pangea/activity_planner/activity_plan_model.dart';
|
||||
import 'package:fluffychat/pangea/activity_sessions/activity_room_extension.dart';
|
||||
import 'package:fluffychat/pangea/chat/constants/default_power_level.dart';
|
||||
import 'package:fluffychat/pangea/common/utils/error_handler.dart';
|
||||
import 'package:fluffychat/pangea/common/utils/firebase_analytics.dart';
|
||||
|
|
@ -42,10 +40,6 @@ class NewGroupController extends State<NewGroup> {
|
|||
// #Pangea
|
||||
// bool publicGroup = false;
|
||||
// bool groupCanBeFound = false;
|
||||
ActivityPlanModel? selectedActivity;
|
||||
Uint8List? selectedActivityImage;
|
||||
String? selectedActivityImageFilename;
|
||||
|
||||
final GlobalKey<FormState> formKey = GlobalKey<FormState>();
|
||||
final FocusNode focusNode = FocusNode();
|
||||
|
||||
|
|
@ -72,22 +66,6 @@ class NewGroupController extends State<NewGroup> {
|
|||
// void setPublicGroup(bool b) =>
|
||||
// setState(() => publicGroup = groupCanBeFound = b);
|
||||
|
||||
void setSelectedActivity(
|
||||
ActivityPlanModel? activity,
|
||||
Uint8List? image,
|
||||
String? imageFilename,
|
||||
) {
|
||||
setState(() {
|
||||
selectedActivity = activity;
|
||||
selectedActivityImage = image;
|
||||
selectedActivityImageFilename = imageFilename;
|
||||
if (avatar == null) {
|
||||
avatar = image;
|
||||
avatarUrl = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
|
@ -189,21 +167,6 @@ class NewGroupController extends State<NewGroup> {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (selectedActivity != null) {
|
||||
try {
|
||||
await room.sendActivityPlan(
|
||||
selectedActivity!,
|
||||
avatar: selectedActivityImage,
|
||||
filename: selectedActivityImageFilename,
|
||||
);
|
||||
} catch (err) {
|
||||
ErrorHandler.logError(
|
||||
e: "Failed to send activity plan",
|
||||
data: {"roomId": roomId, "error": err},
|
||||
);
|
||||
}
|
||||
}
|
||||
context.go('/rooms/$roomId/invite');
|
||||
// Pangea#
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,3 @@
|
|||
import 'dart:developer';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
|
|
@ -9,19 +6,16 @@ import 'package:material_symbols_icons/symbols.dart';
|
|||
|
||||
import 'package:fluffychat/config/themes.dart';
|
||||
import 'package:fluffychat/l10n/l10n.dart';
|
||||
import 'package:fluffychat/pangea/activity_planner/activity_plan_model.dart';
|
||||
import 'package:fluffychat/pangea/activity_planner/activity_planner_builder.dart';
|
||||
import 'package:fluffychat/pangea/activity_planner/bookmarked_activities_repo.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/utils/error_handler.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/mxc_image.dart';
|
||||
|
||||
class ActivityPlanCard extends StatefulWidget {
|
||||
class ActivityPlanCard extends StatelessWidget {
|
||||
final VoidCallback regenerate;
|
||||
final ActivityPlannerBuilderState controller;
|
||||
|
||||
|
|
@ -31,73 +25,31 @@ class ActivityPlanCard extends StatefulWidget {
|
|||
required this.controller,
|
||||
});
|
||||
|
||||
@override
|
||||
ActivityPlanCardState createState() => ActivityPlanCardState();
|
||||
}
|
||||
|
||||
class ActivityPlanCardState extends State<ActivityPlanCard> {
|
||||
static const double itemPadding = 12;
|
||||
|
||||
Future<ActivityPlanModel> _addBookmark(ActivityPlanModel activity) async {
|
||||
try {
|
||||
return BookmarkedActivitiesRepo.save(activity);
|
||||
} catch (e, stack) {
|
||||
debugger(when: kDebugMode);
|
||||
ErrorHandler.logError(e: e, s: stack, data: activity.toJson());
|
||||
return activity; // Return the original activity in case of error
|
||||
} finally {
|
||||
if (mounted) {
|
||||
setState(() {});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _removeBookmark() async {
|
||||
try {
|
||||
BookmarkedActivitiesRepo.remove(
|
||||
widget.controller.updatedActivity.bookmarkId,
|
||||
);
|
||||
} catch (e, stack) {
|
||||
debugger(when: kDebugMode);
|
||||
ErrorHandler.logError(
|
||||
e: e,
|
||||
s: stack,
|
||||
data: widget.controller.updatedActivity.toJson(),
|
||||
);
|
||||
} finally {
|
||||
if (mounted) {
|
||||
setState(() {});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onLaunch() async {
|
||||
Future<void> _onLaunch(BuildContext context) async {
|
||||
final resp = await showFutureLoadingDialog(
|
||||
context: context,
|
||||
future: () async {
|
||||
if (!widget.controller.room.isSpace) {
|
||||
if (!controller.room.isSpace) {
|
||||
throw Exception(
|
||||
"Cannot launch activity in a non-space room",
|
||||
);
|
||||
}
|
||||
|
||||
final ids = await widget.controller.launchToSpace();
|
||||
final ids = await controller.launchToSpace();
|
||||
ids.length == 1
|
||||
? context.go("/rooms/${ids.first}")
|
||||
: context.go("/rooms?spaceId=${widget.controller.room.id}");
|
||||
: context.go("/rooms?spaceId=${controller.room.id}");
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
);
|
||||
|
||||
if (!resp.isError) {
|
||||
context.go("/rooms?spaceId=${widget.controller.room.id}");
|
||||
context.go("/rooms?spaceId=${controller.room.id}");
|
||||
}
|
||||
}
|
||||
|
||||
bool get _isBookmarked => BookmarkedActivitiesRepo.isBookmarked(
|
||||
widget.controller.updatedActivity,
|
||||
);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
|
|
@ -108,7 +60,7 @@ class ActivityPlanCardState extends State<ActivityPlanCard> {
|
|||
child: Card(
|
||||
margin: const EdgeInsets.symmetric(vertical: itemPadding),
|
||||
child: Form(
|
||||
key: widget.controller.formKey,
|
||||
key: controller.formKey,
|
||||
child: Column(
|
||||
children: [
|
||||
AnimatedSize(
|
||||
|
|
@ -124,11 +76,10 @@ class ActivityPlanCardState extends State<ActivityPlanCard> {
|
|||
),
|
||||
clipBehavior: Clip.hardEdge,
|
||||
alignment: Alignment.center,
|
||||
child: widget.controller.isLaunching
|
||||
child: controller.isLaunching
|
||||
? Avatar(
|
||||
mxContent: widget.controller.room.avatar,
|
||||
name: widget.controller.room
|
||||
.getLocalizedDisplayname(
|
||||
mxContent: controller.room.avatar,
|
||||
name: controller.room.getLocalizedDisplayname(
|
||||
MatrixLocals(
|
||||
L10n.of(context),
|
||||
),
|
||||
|
|
@ -136,15 +87,14 @@ class ActivityPlanCardState extends State<ActivityPlanCard> {
|
|||
borderRadius: BorderRadius.circular(12.0),
|
||||
size: 200.0,
|
||||
)
|
||||
: widget.controller.imageURL != null ||
|
||||
widget.controller.avatar != null
|
||||
: controller.imageURL != null ||
|
||||
controller.avatar != null
|
||||
? ClipRRect(
|
||||
borderRadius: BorderRadius.circular(20.0),
|
||||
child: widget.controller.avatar == null
|
||||
child: controller.avatar == null
|
||||
? CachedNetworkImage(
|
||||
fit: BoxFit.cover,
|
||||
imageUrl:
|
||||
widget.controller.imageURL!,
|
||||
imageUrl: controller.imageURL!,
|
||||
placeholder: (context, url) {
|
||||
return const Center(
|
||||
child:
|
||||
|
|
@ -158,7 +108,7 @@ class ActivityPlanCardState extends State<ActivityPlanCard> {
|
|||
},
|
||||
)
|
||||
: Image.memory(
|
||||
widget.controller.avatar!,
|
||||
controller.avatar!,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
)
|
||||
|
|
@ -166,10 +116,10 @@ class ActivityPlanCardState extends State<ActivityPlanCard> {
|
|||
padding: EdgeInsets.all(28.0),
|
||||
),
|
||||
),
|
||||
if (widget.controller.isEditing)
|
||||
if (controller.isEditing)
|
||||
InkWell(
|
||||
borderRadius: BorderRadius.circular(90),
|
||||
onTap: widget.controller.selectAvatar,
|
||||
onTap: controller.selectAvatar,
|
||||
child: CircleAvatar(
|
||||
backgroundColor:
|
||||
Theme.of(context).colorScheme.secondary,
|
||||
|
|
@ -188,15 +138,14 @@ class ActivityPlanCardState extends State<ActivityPlanCard> {
|
|||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: widget.controller.isLaunching
|
||||
children: controller.isLaunching
|
||||
? [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Avatar(
|
||||
mxContent: widget.controller.room.avatar,
|
||||
name: widget.controller.room
|
||||
.getLocalizedDisplayname(
|
||||
mxContent: controller.room.avatar,
|
||||
name: controller.room.getLocalizedDisplayname(
|
||||
MatrixLocals(L10n.of(context)),
|
||||
),
|
||||
size: 24.0,
|
||||
|
|
@ -205,8 +154,7 @@ class ActivityPlanCardState extends State<ActivityPlanCard> {
|
|||
const SizedBox(width: itemPadding),
|
||||
Expanded(
|
||||
child: Text(
|
||||
widget.controller.room
|
||||
.getLocalizedDisplayname(
|
||||
controller.room.getLocalizedDisplayname(
|
||||
MatrixLocals(L10n.of(context)),
|
||||
),
|
||||
style:
|
||||
|
|
@ -219,29 +167,26 @@ class ActivityPlanCardState extends State<ActivityPlanCard> {
|
|||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
widget.controller.updatedActivity.imageURL !=
|
||||
null
|
||||
controller.updatedActivity.imageURL != null
|
||||
? ClipRRect(
|
||||
borderRadius:
|
||||
BorderRadius.circular(4.0),
|
||||
child: widget.controller.updatedActivity
|
||||
.imageURL!
|
||||
child: controller
|
||||
.updatedActivity.imageURL!
|
||||
.startsWith("mxc")
|
||||
? MxcImage(
|
||||
uri: Uri.parse(
|
||||
widget
|
||||
.controller
|
||||
.updatedActivity
|
||||
controller.updatedActivity
|
||||
.imageURL!,
|
||||
),
|
||||
width: 24.0,
|
||||
height: 24.0,
|
||||
cacheKey: widget.controller
|
||||
cacheKey: controller
|
||||
.updatedActivity.bookmarkId,
|
||||
fit: BoxFit.cover,
|
||||
)
|
||||
: CachedNetworkImage(
|
||||
imageUrl: widget.controller
|
||||
imageUrl: controller
|
||||
.updatedActivity.imageURL!,
|
||||
fit: BoxFit.cover,
|
||||
width: 24.0,
|
||||
|
|
@ -269,7 +214,7 @@ class ActivityPlanCardState extends State<ActivityPlanCard> {
|
|||
const SizedBox(width: itemPadding),
|
||||
Expanded(
|
||||
child: Text(
|
||||
widget.controller.updatedActivity.title,
|
||||
controller.updatedActivity.title,
|
||||
style:
|
||||
Theme.of(context).textTheme.bodyLarge,
|
||||
),
|
||||
|
|
@ -286,7 +231,7 @@ class ActivityPlanCardState extends State<ActivityPlanCard> {
|
|||
child: Text(
|
||||
L10n.of(context)
|
||||
.maximumActivityParticipants(
|
||||
widget.controller.updatedActivity.req
|
||||
controller.updatedActivity.req
|
||||
.numberOfParticipants,
|
||||
),
|
||||
style:
|
||||
|
|
@ -315,9 +260,8 @@ class ActivityPlanCardState extends State<ActivityPlanCard> {
|
|||
.bodyLarge,
|
||||
),
|
||||
NumberCounter(
|
||||
count: widget.controller.numActivities,
|
||||
update:
|
||||
widget.controller.setNumActivities,
|
||||
count: controller.numActivities,
|
||||
update: controller.setNumActivities,
|
||||
min: 1,
|
||||
max: 5,
|
||||
),
|
||||
|
|
@ -337,7 +281,7 @@ class ActivityPlanCardState extends State<ActivityPlanCard> {
|
|||
horizontal: 12.0,
|
||||
),
|
||||
),
|
||||
onPressed: _onLaunch,
|
||||
onPressed: () => _onLaunch(context),
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(Icons.send_outlined),
|
||||
|
|
@ -358,10 +302,10 @@ class ActivityPlanCardState extends State<ActivityPlanCard> {
|
|||
const Icon(Icons.event_note_outlined),
|
||||
const SizedBox(width: itemPadding),
|
||||
Expanded(
|
||||
child: widget.controller.isEditing
|
||||
child: controller.isEditing
|
||||
? TextField(
|
||||
controller:
|
||||
widget.controller.titleController,
|
||||
controller.titleController,
|
||||
decoration: InputDecoration(
|
||||
labelText:
|
||||
L10n.of(context).activityTitle,
|
||||
|
|
@ -369,22 +313,18 @@ class ActivityPlanCardState extends State<ActivityPlanCard> {
|
|||
maxLines: null,
|
||||
)
|
||||
: Text(
|
||||
widget
|
||||
.controller.updatedActivity.title,
|
||||
controller.updatedActivity.title,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyLarge,
|
||||
),
|
||||
),
|
||||
if (!widget.controller.isEditing)
|
||||
if (!controller.isEditing)
|
||||
IconButton(
|
||||
onPressed: _isBookmarked
|
||||
? () => _removeBookmark()
|
||||
: () => _addBookmark(
|
||||
widget.controller.updatedActivity,
|
||||
),
|
||||
onPressed:
|
||||
controller.toggleBookmarkedActivity,
|
||||
icon: Icon(
|
||||
_isBookmarked
|
||||
controller.isBookmarked
|
||||
? Icons.save
|
||||
: Icons.save_outlined,
|
||||
),
|
||||
|
|
@ -401,9 +341,9 @@ class ActivityPlanCardState extends State<ActivityPlanCard> {
|
|||
),
|
||||
const SizedBox(width: itemPadding),
|
||||
Expanded(
|
||||
child: widget.controller.isEditing
|
||||
child: controller.isEditing
|
||||
? TextField(
|
||||
controller: widget.controller
|
||||
controller: controller
|
||||
.learningObjectivesController,
|
||||
decoration: InputDecoration(
|
||||
labelText:
|
||||
|
|
@ -412,7 +352,7 @@ class ActivityPlanCardState extends State<ActivityPlanCard> {
|
|||
maxLines: null,
|
||||
)
|
||||
: Text(
|
||||
widget.controller.updatedActivity
|
||||
controller.updatedActivity
|
||||
.learningObjective,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
|
|
@ -431,18 +371,18 @@ class ActivityPlanCardState extends State<ActivityPlanCard> {
|
|||
),
|
||||
const SizedBox(width: itemPadding),
|
||||
Expanded(
|
||||
child: widget.controller.isEditing
|
||||
child: controller.isEditing
|
||||
? TextField(
|
||||
controller: widget.controller
|
||||
.instructionsController,
|
||||
controller:
|
||||
controller.instructionsController,
|
||||
decoration: InputDecoration(
|
||||
labelText: l10n.instructions,
|
||||
),
|
||||
maxLines: null,
|
||||
)
|
||||
: Text(
|
||||
widget.controller.updatedActivity
|
||||
.instructions,
|
||||
controller
|
||||
.updatedActivity.instructions,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyMedium,
|
||||
|
|
@ -460,16 +400,16 @@ class ActivityPlanCardState extends State<ActivityPlanCard> {
|
|||
),
|
||||
const SizedBox(width: itemPadding),
|
||||
Expanded(
|
||||
child: widget.controller.isEditing
|
||||
child: controller.isEditing
|
||||
? LanguageLevelDropdown(
|
||||
initialLevel:
|
||||
widget.controller.languageLevel,
|
||||
onChanged: widget
|
||||
.controller.setLanguageLevel,
|
||||
controller.languageLevel,
|
||||
onChanged:
|
||||
controller.setLanguageLevel,
|
||||
)
|
||||
: Text(
|
||||
widget.controller.updatedActivity.req
|
||||
.cefrLevel
|
||||
controller
|
||||
.updatedActivity.req.cefrLevel
|
||||
.title(context),
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
|
|
@ -479,7 +419,7 @@ class ActivityPlanCardState extends State<ActivityPlanCard> {
|
|||
],
|
||||
),
|
||||
const SizedBox(height: itemPadding),
|
||||
if (widget.controller.vocab.isNotEmpty) ...[
|
||||
if (controller.vocab.isNotEmpty) ...[
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
|
|
@ -493,16 +433,13 @@ class ActivityPlanCardState extends State<ActivityPlanCard> {
|
|||
spacing: 4.0,
|
||||
runSpacing: 4.0,
|
||||
children: List<Widget>.generate(
|
||||
widget.controller.vocab.length,
|
||||
(int index) {
|
||||
return widget.controller.isEditing
|
||||
controller.vocab.length, (int index) {
|
||||
return controller.isEditing
|
||||
? Chip(
|
||||
label: Text(
|
||||
widget.controller.vocab[index]
|
||||
.lemma,
|
||||
controller.vocab[index].lemma,
|
||||
),
|
||||
onDeleted: () => widget
|
||||
.controller
|
||||
onDeleted: () => controller
|
||||
.removeVocab(index),
|
||||
backgroundColor:
|
||||
Colors.transparent,
|
||||
|
|
@ -516,8 +453,7 @@ class ActivityPlanCardState extends State<ActivityPlanCard> {
|
|||
)
|
||||
: Chip(
|
||||
label: Text(
|
||||
widget.controller.vocab[index]
|
||||
.lemma,
|
||||
controller.vocab[index].lemma,
|
||||
),
|
||||
backgroundColor:
|
||||
Colors.transparent,
|
||||
|
|
@ -535,7 +471,7 @@ class ActivityPlanCardState extends State<ActivityPlanCard> {
|
|||
],
|
||||
),
|
||||
],
|
||||
if (widget.controller.isEditing) ...[
|
||||
if (controller.isEditing) ...[
|
||||
const SizedBox(height: itemPadding),
|
||||
Padding(
|
||||
padding:
|
||||
|
|
@ -544,19 +480,18 @@ class ActivityPlanCardState extends State<ActivityPlanCard> {
|
|||
children: [
|
||||
Expanded(
|
||||
child: TextField(
|
||||
controller:
|
||||
widget.controller.vocabController,
|
||||
controller: controller.vocabController,
|
||||
decoration: InputDecoration(
|
||||
labelText: l10n.addVocabulary,
|
||||
),
|
||||
onSubmitted: (value) {
|
||||
widget.controller.addVocab();
|
||||
controller.addVocab();
|
||||
},
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.add),
|
||||
onPressed: widget.controller.addVocab,
|
||||
onPressed: controller.addVocab,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
@ -576,7 +511,7 @@ class ActivityPlanCardState extends State<ActivityPlanCard> {
|
|||
horizontal: 12.0,
|
||||
),
|
||||
),
|
||||
onPressed: widget.controller.saveEdits,
|
||||
onPressed: controller.saveEdits,
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(Icons.save),
|
||||
|
|
@ -601,7 +536,7 @@ class ActivityPlanCardState extends State<ActivityPlanCard> {
|
|||
horizontal: 12.0,
|
||||
),
|
||||
),
|
||||
onPressed: widget.controller.clearEdits,
|
||||
onPressed: controller.clearEdits,
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(Icons.cancel),
|
||||
|
|
@ -636,8 +571,7 @@ class ActivityPlanCardState extends State<ActivityPlanCard> {
|
|||
horizontal: 12.0,
|
||||
),
|
||||
),
|
||||
onPressed:
|
||||
widget.controller.startEditing,
|
||||
onPressed: controller.startEditing,
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(Icons.edit),
|
||||
|
|
@ -662,7 +596,7 @@ class ActivityPlanCardState extends State<ActivityPlanCard> {
|
|||
horizontal: 12.0,
|
||||
),
|
||||
),
|
||||
onPressed: widget.regenerate,
|
||||
onPressed: regenerate,
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(
|
||||
|
|
@ -694,7 +628,7 @@ class ActivityPlanCardState extends State<ActivityPlanCard> {
|
|||
),
|
||||
),
|
||||
onPressed: () {
|
||||
widget.controller.setLaunchState(
|
||||
controller.setLaunchState(
|
||||
ActivityLaunchState.launching,
|
||||
);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart' hide Visibility;
|
||||
|
||||
|
|
@ -8,13 +10,14 @@ 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_planner/bookmarked_activities_repo.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/common/utils/error_handler.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';
|
||||
import 'package:fluffychat/pangea/user/controllers/user_controller.dart';
|
||||
import 'package:fluffychat/utils/client_download_content_extension.dart';
|
||||
import 'package:fluffychat/utils/file_selector.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
|
@ -64,6 +67,8 @@ class ActivityPlannerBuilderState extends State<ActivityPlannerBuilder> {
|
|||
|
||||
final GlobalKey<FormState> formKey = GlobalKey<FormState>();
|
||||
|
||||
final StreamController stateStream = StreamController.broadcast();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
|
@ -77,9 +82,17 @@ class ActivityPlannerBuilderState extends State<ActivityPlannerBuilder> {
|
|||
instructionsController.dispose();
|
||||
vocabController.dispose();
|
||||
participantsController.dispose();
|
||||
stateStream.close();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void update() {
|
||||
if (mounted) setState(() {});
|
||||
if (!stateStream.isClosed) {
|
||||
stateStream.add(null);
|
||||
}
|
||||
}
|
||||
|
||||
Room get room => widget.room;
|
||||
|
||||
bool get isEditing => launchState == ActivityLaunchState.editing;
|
||||
|
|
@ -129,7 +142,8 @@ class ActivityPlannerBuilderState extends State<ActivityPlannerBuilder> {
|
|||
if (widget.initialActivity.imageURL != null) {
|
||||
await _setAvatarByURL(widget.initialActivity.imageURL!);
|
||||
}
|
||||
if (mounted) setState(() {});
|
||||
|
||||
update();
|
||||
}
|
||||
|
||||
Future<void> overrideActivity(ActivityPlanModel override) async {
|
||||
|
|
@ -148,18 +162,21 @@ class ActivityPlannerBuilderState extends State<ActivityPlannerBuilder> {
|
|||
if (override.imageURL != null) {
|
||||
await _setAvatarByURL(override.imageURL!);
|
||||
}
|
||||
if (mounted) setState(() {});
|
||||
|
||||
update();
|
||||
}
|
||||
|
||||
void startEditing() => setLaunchState(ActivityLaunchState.editing);
|
||||
void startEditing() {
|
||||
setLaunchState(ActivityLaunchState.editing);
|
||||
}
|
||||
|
||||
void setLaunchState(ActivityLaunchState state) {
|
||||
if (state == ActivityLaunchState.launching) {
|
||||
BookmarkedActivitiesRepo.save(updatedActivity);
|
||||
_addBookmarkedActivity();
|
||||
}
|
||||
|
||||
launchState = state;
|
||||
if (mounted) setState(() {});
|
||||
update();
|
||||
}
|
||||
|
||||
void addVocab() {
|
||||
|
|
@ -171,37 +188,35 @@ class ActivityPlannerBuilderState extends State<ActivityPlannerBuilder> {
|
|||
),
|
||||
);
|
||||
vocabController.clear();
|
||||
if (mounted) setState(() {});
|
||||
update();
|
||||
}
|
||||
|
||||
void removeVocab(int index) {
|
||||
vocab.removeAt(index);
|
||||
if (mounted) setState(() {});
|
||||
update();
|
||||
}
|
||||
|
||||
void setLanguageLevel(LanguageLevelTypeEnum level) {
|
||||
languageLevel = level;
|
||||
if (mounted) setState(() {});
|
||||
update();
|
||||
}
|
||||
|
||||
void selectAvatar() async {
|
||||
Future<void> selectAvatar() async {
|
||||
final photo = await selectFiles(
|
||||
context,
|
||||
type: FileSelectorType.images,
|
||||
allowMultiple: false,
|
||||
);
|
||||
final bytes = await photo.singleOrNull?.readAsBytes();
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
avatar = bytes;
|
||||
imageURL = null;
|
||||
filename = photo.singleOrNull?.name;
|
||||
});
|
||||
}
|
||||
avatar = bytes;
|
||||
imageURL = null;
|
||||
filename = photo.singleOrNull?.name;
|
||||
update();
|
||||
}
|
||||
|
||||
void setNumActivities(int count) {
|
||||
if (mounted) setState(() => numActivities = count);
|
||||
numActivities = count;
|
||||
update();
|
||||
}
|
||||
|
||||
Future<void> _setAvatarByURL(String url) async {
|
||||
|
|
@ -240,10 +255,8 @@ class ActivityPlannerBuilderState extends State<ActivityPlannerBuilder> {
|
|||
avatar!,
|
||||
filename: filename,
|
||||
);
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
imageURL = url.toString();
|
||||
});
|
||||
imageURL = url.toString();
|
||||
update();
|
||||
}
|
||||
|
||||
Future<void> saveEdits() async {
|
||||
|
|
@ -251,9 +264,8 @@ class ActivityPlannerBuilderState extends State<ActivityPlannerBuilder> {
|
|||
await updateImageURL();
|
||||
setLaunchState(ActivityLaunchState.base);
|
||||
|
||||
await BookmarkedActivitiesRepo.remove(widget.initialActivity.bookmarkId);
|
||||
await BookmarkedActivitiesRepo.save(updatedActivity);
|
||||
if (mounted) setState(() {});
|
||||
await _updateBookmarkedActivity();
|
||||
update();
|
||||
}
|
||||
|
||||
Future<void> clearEdits() async {
|
||||
|
|
@ -261,6 +273,49 @@ class ActivityPlannerBuilderState extends State<ActivityPlannerBuilder> {
|
|||
setLaunchState(ActivityLaunchState.base);
|
||||
}
|
||||
|
||||
UserController get _userController =>
|
||||
MatrixState.pangeaController.userController;
|
||||
|
||||
bool get isBookmarked =>
|
||||
_userController.isBookmarked(updatedActivity.bookmarkId);
|
||||
|
||||
Future<void> toggleBookmarkedActivity() async {
|
||||
isBookmarked
|
||||
? await _removeBookmarkedActivity()
|
||||
: await _addBookmarkedActivity();
|
||||
update();
|
||||
}
|
||||
|
||||
Future<void> _addBookmarkedActivity() async {
|
||||
await _userController.addBookmarkedActivity(
|
||||
activityId: updatedActivity.bookmarkId,
|
||||
);
|
||||
await ActivityPlanRepo.set(updatedActivity);
|
||||
}
|
||||
|
||||
Future<void> _updateBookmarkedActivity() async {
|
||||
// save updates locally, in case choreo results in error
|
||||
await ActivityPlanRepo.set(updatedActivity);
|
||||
|
||||
// prevent an error or delay from the choreo endpoint bubbling up
|
||||
// in the UI, since the changes are still stored locally
|
||||
ActivityPlanRepo.update(
|
||||
updatedActivity,
|
||||
).then((resp) {
|
||||
_userController.updateBookmarkedActivity(
|
||||
activityId: widget.initialActivity.bookmarkId,
|
||||
newActivityId: resp.bookmarkId,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _removeBookmarkedActivity() async {
|
||||
await _userController.removeBookmarkedActivity(
|
||||
activityId: updatedActivity.bookmarkId,
|
||||
);
|
||||
await ActivityPlanRepo.remove(updatedActivity.bookmarkId);
|
||||
}
|
||||
|
||||
Future<List<String>> launchToSpace() async {
|
||||
final List<String> activityRoomIDs = [];
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -43,10 +43,7 @@ class ActivityPlannerPageState extends State<ActivityPlannerPage> {
|
|||
switch (pageMode) {
|
||||
case PageMode.savedActivities:
|
||||
if (room != null) {
|
||||
body = BookmarkedActivitiesList(
|
||||
room: room!,
|
||||
controller: this,
|
||||
);
|
||||
body = BookmarkedActivitiesList(room: room!);
|
||||
}
|
||||
break;
|
||||
case PageMode.featuredActivities:
|
||||
|
|
|
|||
|
|
@ -1,57 +0,0 @@
|
|||
// ignore_for_file: depend_on_referenced_packages
|
||||
|
||||
import 'package:get_storage/get_storage.dart';
|
||||
|
||||
import 'package:fluffychat/pangea/activity_planner/activity_plan_model.dart';
|
||||
|
||||
class BookmarkedActivitiesRepo {
|
||||
static final GetStorage _bookStorage = GetStorage('bookmarked_activities');
|
||||
|
||||
/// save an activity to the list of bookmarked activities
|
||||
/// returns the activity with a bookmarkId
|
||||
static Future<ActivityPlanModel> save(
|
||||
ActivityPlanModel activity,
|
||||
) async {
|
||||
await _bookStorage.write(
|
||||
activity.bookmarkId,
|
||||
activity.toJson(),
|
||||
);
|
||||
|
||||
//now it has a bookmarkId
|
||||
return activity;
|
||||
}
|
||||
|
||||
static Future<void> remove(String bookmarkId) =>
|
||||
_bookStorage.remove(bookmarkId);
|
||||
|
||||
static bool isBookmarked(ActivityPlanModel activity) {
|
||||
return _bookStorage.read(activity.bookmarkId) != null;
|
||||
}
|
||||
|
||||
static List<ActivityPlanModel> get() {
|
||||
final List<String> keys = List<String>.from(_bookStorage.getKeys());
|
||||
if (keys.isEmpty) return [];
|
||||
|
||||
final List<ActivityPlanModel> activities = [];
|
||||
for (final key in keys) {
|
||||
final json = _bookStorage.read(key);
|
||||
if (json == null) continue;
|
||||
|
||||
ActivityPlanModel? activity;
|
||||
try {
|
||||
activity = ActivityPlanModel.fromJson(json);
|
||||
} catch (e) {
|
||||
_bookStorage.remove(key);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (key != activity.bookmarkId) {
|
||||
_bookStorage.remove(key);
|
||||
_bookStorage.write(activity.bookmarkId, activity.toJson());
|
||||
}
|
||||
activities.add(activity);
|
||||
}
|
||||
|
||||
return activities;
|
||||
}
|
||||
}
|
||||
|
|
@ -6,20 +6,17 @@ import 'package:fluffychat/config/themes.dart';
|
|||
import 'package:fluffychat/l10n/l10n.dart';
|
||||
import 'package:fluffychat/pangea/activity_planner/activity_plan_model.dart';
|
||||
import 'package:fluffychat/pangea/activity_planner/activity_planner_builder.dart';
|
||||
import 'package:fluffychat/pangea/activity_planner/activity_planner_page.dart';
|
||||
import 'package:fluffychat/pangea/activity_planner/bookmarked_activities_repo.dart';
|
||||
import 'package:fluffychat/pangea/activity_suggestions/activity_suggestion_card.dart';
|
||||
import 'package:fluffychat/pangea/activity_suggestions/activity_suggestion_dialog.dart';
|
||||
import 'package:fluffychat/pangea/common/utils/error_handler.dart';
|
||||
import 'package:fluffychat/pangea/user/controllers/user_controller.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
||||
class BookmarkedActivitiesList extends StatefulWidget {
|
||||
final Room room;
|
||||
|
||||
final ActivityPlannerPageState controller;
|
||||
|
||||
const BookmarkedActivitiesList({
|
||||
super.key,
|
||||
required this.room,
|
||||
required this.controller,
|
||||
});
|
||||
|
||||
@override
|
||||
|
|
@ -28,17 +25,51 @@ class BookmarkedActivitiesList extends StatefulWidget {
|
|||
}
|
||||
|
||||
class BookmarkedActivitiesListState extends State<BookmarkedActivitiesList> {
|
||||
bool _loading = true;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_loadBookmarkedActivities();
|
||||
}
|
||||
|
||||
List<ActivityPlanModel> get _bookmarkedActivities =>
|
||||
BookmarkedActivitiesRepo.get();
|
||||
_userController.getBookmarkedActivitiesSync();
|
||||
|
||||
bool get _isColumnMode => FluffyThemes.isColumnMode(context);
|
||||
|
||||
double get cardHeight => _isColumnMode ? 325.0 : 250.0;
|
||||
double get cardWidth => _isColumnMode ? 225.0 : 150.0;
|
||||
|
||||
UserController get _userController =>
|
||||
MatrixState.pangeaController.userController;
|
||||
|
||||
Future<void> _loadBookmarkedActivities() async {
|
||||
try {
|
||||
setState(() => _loading = true);
|
||||
await _userController.getBookmarkedActivities();
|
||||
} catch (e, s) {
|
||||
ErrorHandler.logError(
|
||||
e: e,
|
||||
s: s,
|
||||
data: {
|
||||
'roomId': widget.room.id,
|
||||
},
|
||||
);
|
||||
} finally {
|
||||
if (mounted) setState(() => _loading = false);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = L10n.of(context);
|
||||
if (_loading) {
|
||||
return const Center(
|
||||
child: CircularProgressIndicator.adaptive(),
|
||||
);
|
||||
}
|
||||
|
||||
if (_bookmarkedActivities.isEmpty) {
|
||||
return Center(
|
||||
child: Container(
|
||||
|
|
@ -60,16 +91,16 @@ class BookmarkedActivitiesListState extends State<BookmarkedActivitiesList> {
|
|||
runSpacing: 16.0,
|
||||
spacing: 4.0,
|
||||
children: _bookmarkedActivities.map((activity) {
|
||||
return ActivitySuggestionCard(
|
||||
activity: activity,
|
||||
onPressed: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return ActivityPlannerBuilder(
|
||||
initialActivity: activity,
|
||||
room: widget.room,
|
||||
builder: (controller) {
|
||||
return ActivityPlannerBuilder(
|
||||
initialActivity: activity,
|
||||
room: widget.room,
|
||||
builder: (controller) {
|
||||
return ActivitySuggestionCard(
|
||||
controller: controller,
|
||||
onPressed: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return ActivitySuggestionDialog(
|
||||
controller: controller,
|
||||
buttonText: l10n.launchActivityButton,
|
||||
|
|
@ -77,11 +108,10 @@ class BookmarkedActivitiesListState extends State<BookmarkedActivitiesList> {
|
|||
},
|
||||
);
|
||||
},
|
||||
width: cardWidth,
|
||||
height: cardHeight,
|
||||
);
|
||||
},
|
||||
width: cardWidth,
|
||||
height: cardHeight,
|
||||
onChange: () => setState(() {}),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import 'package:collection/collection.dart';
|
|||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import 'package:fluffychat/pangea/activity_planner/activity_plan_model.dart';
|
||||
import 'package:fluffychat/pangea/activity_planner/bookmarked_activities_repo.dart';
|
||||
import 'package:fluffychat/pangea/activity_sessions/activity_role_model.dart';
|
||||
import 'package:fluffychat/pangea/activity_sessions/activity_roles_model.dart';
|
||||
import 'package:fluffychat/pangea/activity_summary/activity_summary_model.dart';
|
||||
|
|
@ -25,8 +24,6 @@ extension ActivityRoomExtension on Room {
|
|||
Uint8List? avatar,
|
||||
String? filename,
|
||||
}) async {
|
||||
BookmarkedActivitiesRepo.save(activity);
|
||||
|
||||
if (canChangeStateEvent(PangeaEventTypes.activityPlan)) {
|
||||
await client.setRoomStateWithKey(
|
||||
id,
|
||||
|
|
|
|||
79
lib/pangea/activity_suggestions/activity_plan_repo.dart
Normal file
79
lib/pangea/activity_suggestions/activity_plan_repo.dart
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:get_storage/get_storage.dart';
|
||||
import 'package:http/http.dart';
|
||||
|
||||
import 'package:fluffychat/pangea/activity_planner/activity_plan_model.dart';
|
||||
import 'package:fluffychat/pangea/common/config/environment.dart';
|
||||
import 'package:fluffychat/pangea/common/network/requests.dart';
|
||||
import 'package:fluffychat/pangea/common/network/urls.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
||||
class ActivityPlanRepo {
|
||||
static final GetStorage _activityPlanStorage =
|
||||
GetStorage('activity_plan_by_id_storage');
|
||||
|
||||
static ActivityPlanModel? getCached(String id) {
|
||||
final cachedJson = _activityPlanStorage.read(id);
|
||||
if (cachedJson == null) return null;
|
||||
|
||||
try {
|
||||
return ActivityPlanModel.fromJson(cachedJson);
|
||||
} catch (e) {
|
||||
_removeCached(id);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static Future<void> _setCached(ActivityPlanModel response) =>
|
||||
_activityPlanStorage.write(response.bookmarkId, response.toJson());
|
||||
|
||||
static Future<void> _removeCached(String id) =>
|
||||
_activityPlanStorage.remove(id);
|
||||
|
||||
static Future<void> set(ActivityPlanModel activity) => _setCached(activity);
|
||||
|
||||
static Future<ActivityPlanModel> get(String id) async {
|
||||
final cached = getCached(id);
|
||||
if (cached != null) return cached;
|
||||
|
||||
final Requests req = Requests(
|
||||
choreoApiKey: Environment.choreoApiKey,
|
||||
accessToken: MatrixState.pangeaController.userController.accessToken,
|
||||
);
|
||||
|
||||
final Response res = await req.get(
|
||||
url: "${PApiUrls.activityPlan}/$id",
|
||||
);
|
||||
|
||||
final decodedBody = jsonDecode(utf8.decode(res.bodyBytes));
|
||||
final response = ActivityPlanModel.fromJson(decodedBody["plan"]);
|
||||
|
||||
_setCached(response);
|
||||
return response;
|
||||
}
|
||||
|
||||
static Future<ActivityPlanModel> update(
|
||||
ActivityPlanModel update,
|
||||
) async {
|
||||
final Requests req = Requests(
|
||||
choreoApiKey: Environment.choreoApiKey,
|
||||
accessToken: MatrixState.pangeaController.userController.accessToken,
|
||||
);
|
||||
|
||||
final Response res = await req.patch(
|
||||
url: "${PApiUrls.activityPlan}/${update.bookmarkId}",
|
||||
body: update.toJson(),
|
||||
);
|
||||
|
||||
final decodedBody = jsonDecode(utf8.decode(res.bodyBytes));
|
||||
final response = ActivityPlanModel.fromJson(decodedBody["plan"]);
|
||||
|
||||
_removeCached(update.bookmarkId);
|
||||
_setCached(response);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
static Future<void> remove(String id) => _removeCached(id);
|
||||
}
|
||||
|
|
@ -1,43 +1,32 @@
|
|||
import 'dart:typed_data';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
|
||||
import 'package:fluffychat/pangea/activity_planner/activity_plan_model.dart';
|
||||
import 'package:fluffychat/pangea/activity_planner/bookmarked_activities_repo.dart';
|
||||
import 'package:fluffychat/pangea/activity_planner/activity_planner_builder.dart';
|
||||
import 'package:fluffychat/pangea/common/widgets/pressable_button.dart';
|
||||
import 'package:fluffychat/widgets/mxc_image.dart';
|
||||
|
||||
class ActivitySuggestionCard extends StatelessWidget {
|
||||
final ActivityPlanModel activity;
|
||||
final Uint8List? image;
|
||||
final VoidCallback? onPressed;
|
||||
|
||||
final ActivityPlannerBuilderState controller;
|
||||
final VoidCallback onPressed;
|
||||
final double width;
|
||||
final double height;
|
||||
final bool selected;
|
||||
|
||||
final VoidCallback onChange;
|
||||
|
||||
const ActivitySuggestionCard({
|
||||
super.key,
|
||||
required this.activity,
|
||||
required this.controller,
|
||||
required this.onPressed,
|
||||
required this.width,
|
||||
required this.height,
|
||||
required this.onChange,
|
||||
this.selected = false,
|
||||
this.image,
|
||||
});
|
||||
|
||||
ActivityPlanModel get activity => controller.updatedActivity;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final isBookmarked = BookmarkedActivitiesRepo.isBookmarked(activity);
|
||||
|
||||
return PressableButton(
|
||||
depressed: selected || onPressed == null,
|
||||
onPressed: onPressed,
|
||||
borderRadius: BorderRadius.circular(24.0),
|
||||
color: theme.brightness == Brightness.dark
|
||||
|
|
@ -46,11 +35,6 @@ class ActivitySuggestionCard extends StatelessWidget {
|
|||
colorFactor: theme.brightness == Brightness.dark ? 0.6 : 0.2,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
border: selected
|
||||
? Border.all(
|
||||
color: theme.colorScheme.primary,
|
||||
)
|
||||
: null,
|
||||
borderRadius: BorderRadius.circular(24.0),
|
||||
),
|
||||
height: height,
|
||||
|
|
@ -76,27 +60,25 @@ class ActivitySuggestionCard extends StatelessWidget {
|
|||
),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(24.0),
|
||||
child: image != null
|
||||
? Image.memory(image!, fit: BoxFit.cover)
|
||||
: activity.imageURL != null
|
||||
? activity.imageURL!.startsWith("mxc")
|
||||
? MxcImage(
|
||||
uri: Uri.parse(activity.imageURL!),
|
||||
width: width,
|
||||
height: width,
|
||||
cacheKey: activity.bookmarkId,
|
||||
fit: BoxFit.cover,
|
||||
)
|
||||
: CachedNetworkImage(
|
||||
imageUrl: activity.imageURL!,
|
||||
placeholder: (context, url) => const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
errorWidget: (context, url, error) =>
|
||||
const SizedBox(),
|
||||
fit: BoxFit.cover,
|
||||
)
|
||||
: null,
|
||||
child: activity.imageURL != null
|
||||
? activity.imageURL!.startsWith("mxc")
|
||||
? MxcImage(
|
||||
uri: Uri.parse(activity.imageURL!),
|
||||
width: width,
|
||||
height: width,
|
||||
cacheKey: activity.bookmarkId,
|
||||
fit: BoxFit.cover,
|
||||
)
|
||||
: CachedNetworkImage(
|
||||
imageUrl: activity.imageURL!,
|
||||
placeholder: (context, url) => const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
errorWidget: (context, url, error) =>
|
||||
const SizedBox(),
|
||||
fit: BoxFit.cover,
|
||||
)
|
||||
: null,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
|
|
@ -180,19 +162,10 @@ class ActivitySuggestionCard extends StatelessWidget {
|
|||
right: 4.0,
|
||||
child: IconButton(
|
||||
icon: Icon(
|
||||
isBookmarked ? Icons.save : Icons.save_outlined,
|
||||
controller.isBookmarked ? Icons.save : Icons.save_outlined,
|
||||
color: Theme.of(context).colorScheme.onPrimaryContainer,
|
||||
),
|
||||
onPressed: onPressed != null
|
||||
? () async {
|
||||
await (isBookmarked
|
||||
? BookmarkedActivitiesRepo.remove(
|
||||
activity.bookmarkId,
|
||||
)
|
||||
: BookmarkedActivitiesRepo.save(activity));
|
||||
onChange();
|
||||
}
|
||||
: null,
|
||||
onPressed: controller.toggleBookmarkedActivity,
|
||||
style: IconButton.styleFrom(
|
||||
backgroundColor: Theme.of(context)
|
||||
.colorScheme
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
|
|
@ -40,6 +42,22 @@ class ActivitySuggestionDialogState extends State<ActivitySuggestionDialog> {
|
|||
? 400.0
|
||||
: MediaQuery.of(context).size.width;
|
||||
|
||||
StreamSubscription? _stateSubscription;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_stateSubscription = widget.controller.stateStream.stream.listen((state) {
|
||||
if (mounted) setState(() {});
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_stateSubscription?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> launchActivity() async {
|
||||
try {
|
||||
if (!widget.controller.room.isSpace) {
|
||||
|
|
|
|||
|
|
@ -185,16 +185,16 @@ class ActivitySuggestionsAreaState extends State<ActivitySuggestionsArea> {
|
|||
})
|
||||
: _activityItems
|
||||
.mapIndexed((index, activity) {
|
||||
return ActivitySuggestionCard(
|
||||
activity: activity,
|
||||
onPressed: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return ActivityPlannerBuilder(
|
||||
initialActivity: activity,
|
||||
room: widget.room,
|
||||
builder: (controller) {
|
||||
return ActivityPlannerBuilder(
|
||||
initialActivity: activity,
|
||||
room: widget.room,
|
||||
builder: (controller) {
|
||||
return ActivitySuggestionCard(
|
||||
controller: controller,
|
||||
onPressed: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return ActivitySuggestionDialog(
|
||||
controller: controller,
|
||||
buttonText: L10n.of(context).saveAndLaunch,
|
||||
|
|
@ -204,13 +204,10 @@ class ActivitySuggestionsAreaState extends State<ActivitySuggestionsArea> {
|
|||
},
|
||||
);
|
||||
},
|
||||
width: cardWidth,
|
||||
height: cardHeight,
|
||||
);
|
||||
},
|
||||
width: cardWidth,
|
||||
height: cardHeight,
|
||||
onChange: () {
|
||||
if (mounted) setState(() {});
|
||||
},
|
||||
);
|
||||
})
|
||||
.cast<Widget>()
|
||||
|
|
|
|||
|
|
@ -93,7 +93,7 @@ class GetAnalyticsController extends BaseController {
|
|||
await _getConstructs();
|
||||
|
||||
final offset =
|
||||
_pangeaController.userController.publicProfile?.xpOffset ?? 0;
|
||||
_pangeaController.userController.analyticsProfile?.xpOffset ?? 0;
|
||||
constructListModel.updateConstructs(
|
||||
[
|
||||
...(_getConstructsLocal() ?? []),
|
||||
|
|
@ -149,7 +149,7 @@ class GetAnalyticsController extends BaseController {
|
|||
final oldLevel = constructListModel.level;
|
||||
|
||||
final offset =
|
||||
_pangeaController.userController.publicProfile?.xpOffset ?? 0;
|
||||
_pangeaController.userController.analyticsProfile?.xpOffset ?? 0;
|
||||
|
||||
final prevUnlockedMorphs = constructListModel
|
||||
.unlockedLemmas(
|
||||
|
|
@ -203,7 +203,7 @@ class GetAnalyticsController extends BaseController {
|
|||
// If the level hasn't changed, this will not send an update to the server.
|
||||
// Do this on all updates (not just on level updates) to account for cases
|
||||
// of target language updates being missed (https://github.com/pangeachat/client/issues/2006)
|
||||
_pangeaController.userController.updatePublicProfile(
|
||||
_pangeaController.userController.updateAnalyticsProfile(
|
||||
level: constructListModel.level,
|
||||
);
|
||||
}
|
||||
|
|
@ -237,7 +237,7 @@ class GetAnalyticsController extends BaseController {
|
|||
await _pangeaController.userController.addXPOffset(offset);
|
||||
constructListModel.updateConstructs(
|
||||
[],
|
||||
_pangeaController.userController.publicProfile!.xpOffset!,
|
||||
_pangeaController.userController.analyticsProfile!.xpOffset!,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ class LevelDisplayName extends StatelessWidget {
|
|||
),
|
||||
child: FutureBuilder(
|
||||
future: MatrixState.pangeaController.userController
|
||||
.getPublicProfile(userId),
|
||||
.getPublicAnalyticsProfile(userId),
|
||||
builder: (context, snapshot) {
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
|
|
|
|||
|
|
@ -151,7 +151,7 @@ class PutAnalyticsController extends BaseController<AnalyticsStream> {
|
|||
);
|
||||
_pangeaController.resetAnalytics().then((_) {
|
||||
final level = _pangeaController.getAnalytics.constructListModel.level;
|
||||
_pangeaController.userController.updatePublicProfile(level: level);
|
||||
_pangeaController.userController.updateAnalyticsProfile(level: level);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -738,7 +738,7 @@ class RoomParticipantsSection extends StatelessWidget {
|
|||
Membership.leave => null,
|
||||
};
|
||||
|
||||
final publicProfile = participantsLoader.getPublicProfile(
|
||||
final publicProfile = participantsLoader.getAnalyticsProfile(
|
||||
user.id,
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -181,4 +181,6 @@ class ModelKey {
|
|||
|
||||
static const String autoIGC = "auto_igc";
|
||||
static const String roomIds = "room_ids";
|
||||
|
||||
static const String bookmarkedActivities = "bookmarked_activities";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -111,6 +111,7 @@ class PangeaController {
|
|||
static final List<String> _storageKeys = [
|
||||
'mode_list_storage',
|
||||
'activity_plan_storage',
|
||||
'activity_plan_by_id_storage',
|
||||
'bookmarked_activities',
|
||||
'objective_list_storage',
|
||||
'topic_list_storage',
|
||||
|
|
|
|||
|
|
@ -66,6 +66,29 @@ class Requests {
|
|||
return response;
|
||||
}
|
||||
|
||||
Future<http.Response> patch({
|
||||
required String url,
|
||||
required Map<dynamic, dynamic> body,
|
||||
}) async {
|
||||
body[ModelKey.cefrLevel] = MatrixState
|
||||
.pangeaController.userController.profile.userSettings.cefrLevel.string;
|
||||
|
||||
dynamic encoded;
|
||||
encoded = jsonEncode(body);
|
||||
|
||||
debugPrint(baseUrl! + url);
|
||||
|
||||
final http.Response response = await http.patch(
|
||||
_uriBuilder(url),
|
||||
body: encoded,
|
||||
headers: _headers,
|
||||
);
|
||||
|
||||
handleError(response, body: body);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
Future<http.Response> get({required String url, String objectId = ""}) async {
|
||||
final http.Response response =
|
||||
await http.get(_uriBuilder(url + objectId), headers: _headers);
|
||||
|
|
|
|||
|
|
@ -58,13 +58,16 @@ class PApiUrls {
|
|||
"${PApiUrls.choreoEndpoint}/lemma_definition/edit";
|
||||
static String morphDictionary = "${PApiUrls.choreoEndpoint}/morph_meaning";
|
||||
|
||||
static String activityPlan = "${PApiUrls.choreoEndpoint}/activity_plan";
|
||||
static String activityPlanGeneration =
|
||||
"${PApiUrls.choreoEndpoint}/activity_plan";
|
||||
"${PApiUrls.choreoEndpoint}/activity_plan/generate";
|
||||
static String activityPlanSearch =
|
||||
"${PApiUrls.choreoEndpoint}/activity_plan/search";
|
||||
|
||||
static String activityModeList = "${PApiUrls.choreoEndpoint}/modes";
|
||||
static String objectiveList = "${PApiUrls.choreoEndpoint}/objectives";
|
||||
static String topicList = "${PApiUrls.choreoEndpoint}/topics";
|
||||
static String activityPlanSearch =
|
||||
"${PApiUrls.choreoEndpoint}/activity_plan/search";
|
||||
|
||||
static String activitySummary = "${PApiUrls.choreoEndpoint}/activity_summary";
|
||||
|
||||
static String morphFeaturesAndTags = "${PApiUrls.choreoEndpoint}/morphs";
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ class PangeaEventTypes {
|
|||
|
||||
/// Profile information related to a user's analytics
|
||||
static const profileAnalytics = "pangea.analytics_profile";
|
||||
static const profileActivities = "pangea.activities_profile";
|
||||
|
||||
static const activityRoomIds = "pangea.activity_room_ids";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -225,7 +225,7 @@ class UserSettingsState extends State<UserSettingsPage> {
|
|||
},
|
||||
waitForDataInSync: true,
|
||||
),
|
||||
_pangeaController.userController.updatePublicProfile(
|
||||
_pangeaController.userController.updateAnalyticsProfile(
|
||||
targetLanguage: selectedTargetLanguage,
|
||||
baseLanguage: _systemLanguage,
|
||||
level: 1,
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ import 'package:fluffychat/pangea/space_analytics/space_analytics_download_enum.
|
|||
import 'package:fluffychat/pangea/space_analytics/space_analytics_inactive_dialog.dart';
|
||||
import 'package:fluffychat/pangea/space_analytics/space_analytics_request_dialog.dart';
|
||||
import 'package:fluffychat/pangea/space_analytics/space_analytics_view.dart';
|
||||
import 'package:fluffychat/pangea/user/models/profile_model.dart';
|
||||
import 'package:fluffychat/pangea/user/models/analytics_profile_model.dart';
|
||||
import 'package:fluffychat/widgets/future_loading_dialog.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
||||
|
|
@ -119,7 +119,7 @@ class SpaceAnalyticsState extends State<SpaceAnalytics> {
|
|||
Map<User, AnalyticsDownload> downloads = {};
|
||||
|
||||
DateTime? _lastUpdated;
|
||||
final Map<User, PublicProfileModel> _profiles = {};
|
||||
final Map<User, AnalyticsProfileModel> _profiles = {};
|
||||
final Map<LanguageModel, List<User>> _langsToUsers = {};
|
||||
|
||||
Room? get room => Matrix.of(context).client.getRoomById(widget.roomId);
|
||||
|
|
@ -233,7 +233,7 @@ class SpaceAnalyticsState extends State<SpaceAnalytics> {
|
|||
Future<void> _loadProfiles() async {
|
||||
final futures = _availableUsers.map((u) async {
|
||||
final resp = await MatrixState.pangeaController.userController
|
||||
.getPublicProfile(u.id);
|
||||
.getPublicAnalyticsProfile(u.id);
|
||||
|
||||
_profiles[u] = resp;
|
||||
if (resp.languageAnalytics == null) return;
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import 'package:matrix/matrix.dart';
|
|||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/pangea/bot/utils/bot_name.dart';
|
||||
import 'package:fluffychat/pangea/common/utils/error_handler.dart';
|
||||
import 'package:fluffychat/pangea/user/models/profile_model.dart';
|
||||
import 'package:fluffychat/pangea/user/models/analytics_profile_model.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
||||
class LoadParticipantsUtil extends StatefulWidget {
|
||||
|
|
@ -26,7 +26,7 @@ class LoadParticipantsUtilState extends State<LoadParticipantsUtil> {
|
|||
bool loading = true;
|
||||
String? error;
|
||||
|
||||
final Map<String, PublicProfileModel> _levelsCache = {};
|
||||
final Map<String, AnalyticsProfileModel> _levelsCache = {};
|
||||
|
||||
List<User> get participants => widget.space.getParticipants();
|
||||
|
||||
|
|
@ -92,8 +92,8 @@ class LoadParticipantsUtilState extends State<LoadParticipantsUtil> {
|
|||
return -1;
|
||||
}
|
||||
|
||||
final PublicProfileModel? aProfile = _levelsCache[a.id];
|
||||
final PublicProfileModel? bProfile = _levelsCache[b.id];
|
||||
final AnalyticsProfileModel? aProfile = _levelsCache[a.id];
|
||||
final AnalyticsProfileModel? bProfile = _levelsCache[b.id];
|
||||
|
||||
return (bProfile?.level ?? 0).compareTo(aProfile?.level ?? 0);
|
||||
});
|
||||
|
|
@ -106,12 +106,12 @@ class LoadParticipantsUtilState extends State<LoadParticipantsUtil> {
|
|||
if (_levelsCache[user.id] == null && user.membership == Membership.join) {
|
||||
_levelsCache[user.id] = await MatrixState
|
||||
.pangeaController.userController
|
||||
.getPublicProfile(user.id);
|
||||
.getPublicAnalyticsProfile(user.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PublicProfileModel? getPublicProfile(String userId) {
|
||||
AnalyticsProfileModel? getAnalyticsProfile(String userId) {
|
||||
return _levelsCache[userId];
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -71,7 +71,8 @@ class LeaderboardParticipantListState
|
|||
itemCount: participants.length,
|
||||
itemBuilder: (context, i) {
|
||||
final user = participants[i];
|
||||
final publicProfile = participantsLoader.getPublicProfile(
|
||||
final publicProfile =
|
||||
participantsLoader.getAnalyticsProfile(
|
||||
user.id,
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:get_storage/get_storage.dart';
|
||||
import 'package:jwt_decode/jwt_decode.dart';
|
||||
import 'package:matrix/matrix.dart' as matrix;
|
||||
import 'package:sentry_flutter/sentry_flutter.dart';
|
||||
|
||||
import 'package:fluffychat/pangea/activity_planner/activity_plan_model.dart';
|
||||
import 'package:fluffychat/pangea/analytics_misc/client_analytics_extension.dart';
|
||||
import 'package:fluffychat/pangea/bot/utils/bot_name.dart';
|
||||
import 'package:fluffychat/pangea/common/constants/model_keys.dart';
|
||||
|
|
@ -15,7 +17,8 @@ import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart';
|
|||
import 'package:fluffychat/pangea/learning_settings/constants/language_constants.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/models/language_model.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/utils/p_language_store.dart';
|
||||
import 'package:fluffychat/pangea/user/models/profile_model.dart';
|
||||
import 'package:fluffychat/pangea/user/models/activities_profile_model.dart';
|
||||
import 'package:fluffychat/pangea/user/models/analytics_profile_model.dart';
|
||||
import '../models/user_model.dart';
|
||||
|
||||
class LanguageUpdate {
|
||||
|
|
@ -54,7 +57,8 @@ class UserController {
|
|||
/// to be read in from client's account data each time it is accessed.
|
||||
Profile? _cachedProfile;
|
||||
|
||||
PublicProfileModel? publicProfile;
|
||||
AnalyticsProfileModel? analyticsProfile;
|
||||
ActivitiesProfileModel? activitiesProfile;
|
||||
|
||||
/// Listens for account updates and updates the cached profile
|
||||
StreamSubscription? _profileListener;
|
||||
|
|
@ -146,6 +150,7 @@ class UserController {
|
|||
_initializing = true;
|
||||
|
||||
try {
|
||||
await GetStorage.init('activity_plan_by_id_storage');
|
||||
await _initialize();
|
||||
_addProfileListener();
|
||||
_addAnalyticsRoomIdsToPublicProfile();
|
||||
|
|
@ -184,21 +189,25 @@ class UserController {
|
|||
if (client.userID == null) return;
|
||||
try {
|
||||
final resp = await client.getUserProfile(client.userID!);
|
||||
publicProfile = PublicProfileModel.fromJson(resp.additionalProperties);
|
||||
analyticsProfile =
|
||||
AnalyticsProfileModel.fromJson(resp.additionalProperties);
|
||||
activitiesProfile =
|
||||
ActivitiesProfileModel.fromJson(resp.additionalProperties);
|
||||
} catch (e) {
|
||||
// getting a 404 error for some users without pre-existing profile
|
||||
// still want to set other properties, so catch this error
|
||||
publicProfile = PublicProfileModel();
|
||||
analyticsProfile = AnalyticsProfileModel();
|
||||
activitiesProfile = ActivitiesProfileModel.empty;
|
||||
}
|
||||
|
||||
// Do not await. This function pulls level from analytics,
|
||||
// so it waits for analytics to finish initializing. Analytics waits for user controller to
|
||||
// finish initializing, so this would cause a deadlock.
|
||||
if (publicProfile!.isEmpty) {
|
||||
if (analyticsProfile!.isEmpty) {
|
||||
_pangeaController.getAnalytics.initCompleter.future
|
||||
.timeout(const Duration(seconds: 10))
|
||||
.then((_) {
|
||||
updatePublicProfile(
|
||||
updateAnalyticsProfile(
|
||||
level: _pangeaController.getAnalytics.constructListModel.level,
|
||||
);
|
||||
}).catchError((e, s) {
|
||||
|
|
@ -206,7 +215,7 @@ class UserController {
|
|||
e: e,
|
||||
s: s,
|
||||
data: {
|
||||
"publicProfile": publicProfile?.toJson(),
|
||||
"publicProfile": analyticsProfile?.toJson(),
|
||||
"userId": client.userID,
|
||||
},
|
||||
level:
|
||||
|
|
@ -231,85 +240,6 @@ class UserController {
|
|||
await initialize();
|
||||
}
|
||||
|
||||
Future<void> updatePublicProfile({
|
||||
required int level,
|
||||
LanguageModel? baseLanguage,
|
||||
LanguageModel? targetLanguage,
|
||||
}) async {
|
||||
targetLanguage ??= _pangeaController.languageController.userL2;
|
||||
baseLanguage ??= _pangeaController.languageController.userL1;
|
||||
if (targetLanguage == null || publicProfile == null) return;
|
||||
|
||||
final analyticsRoom =
|
||||
_pangeaController.matrixState.client.analyticsRoomLocal(targetLanguage);
|
||||
|
||||
if (publicProfile!.targetLanguage == targetLanguage &&
|
||||
publicProfile!.baseLanguage == baseLanguage &&
|
||||
publicProfile!.languageAnalytics?[targetLanguage]?.level == level &&
|
||||
publicProfile!.analyticsRoomIdByLanguage(targetLanguage) ==
|
||||
analyticsRoom?.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
publicProfile!.baseLanguage = baseLanguage;
|
||||
publicProfile!.targetLanguage = targetLanguage;
|
||||
publicProfile!.setLanguageInfo(
|
||||
targetLanguage,
|
||||
level,
|
||||
analyticsRoom?.id,
|
||||
);
|
||||
await _savePublicProfile();
|
||||
}
|
||||
|
||||
Future<void> _addAnalyticsRoomIdsToPublicProfile() async {
|
||||
if (publicProfile?.languageAnalytics == null) return;
|
||||
final analyticsRooms =
|
||||
_pangeaController.matrixState.client.allMyAnalyticsRooms;
|
||||
|
||||
if (analyticsRooms.isEmpty) return;
|
||||
for (final analyticsRoom in analyticsRooms) {
|
||||
final lang = analyticsRoom.madeForLang?.split("-").first;
|
||||
if (lang == null || publicProfile?.languageAnalytics == null) continue;
|
||||
final langKey = publicProfile!.languageAnalytics!.keys.firstWhereOrNull(
|
||||
(l) => l.langCodeShort == lang,
|
||||
);
|
||||
|
||||
if (langKey == null) continue;
|
||||
if (publicProfile!.languageAnalytics![langKey]!.analyticsRoomId ==
|
||||
analyticsRoom.id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
publicProfile!.setLanguageInfo(
|
||||
langKey,
|
||||
publicProfile!.languageAnalytics![langKey]!.level,
|
||||
analyticsRoom.id,
|
||||
);
|
||||
}
|
||||
|
||||
await _savePublicProfile();
|
||||
}
|
||||
|
||||
Future<void> addXPOffset(int offset) async {
|
||||
final targetLanguage = _pangeaController.languageController.userL2;
|
||||
if (targetLanguage == null || publicProfile == null) return;
|
||||
|
||||
publicProfile!.addXPOffset(
|
||||
targetLanguage,
|
||||
offset,
|
||||
_pangeaController.matrixState.client
|
||||
.analyticsRoomLocal(targetLanguage)
|
||||
?.id,
|
||||
);
|
||||
await _savePublicProfile();
|
||||
}
|
||||
|
||||
Future<void> _savePublicProfile() async => client.setUserProfile(
|
||||
client.userID!,
|
||||
PangeaEventTypes.profileAnalytics,
|
||||
publicProfile!.toJson(),
|
||||
);
|
||||
|
||||
/// Returns a boolean value indicating whether a new JWT (JSON Web Token) is needed.
|
||||
bool needNewJWT(String token) => Jwt.isExpired(token);
|
||||
|
||||
|
|
@ -421,14 +351,171 @@ class UserController {
|
|||
return email?.address;
|
||||
}
|
||||
|
||||
Future<PublicProfileModel> getPublicProfile(String userId) async {
|
||||
Future<void> _savePublicProfileUpdate(
|
||||
String type,
|
||||
Map<String, dynamic> content,
|
||||
) async =>
|
||||
client.setUserProfile(
|
||||
client.userID!,
|
||||
type,
|
||||
content,
|
||||
);
|
||||
|
||||
Future<void> updateAnalyticsProfile({
|
||||
required int level,
|
||||
LanguageModel? baseLanguage,
|
||||
LanguageModel? targetLanguage,
|
||||
}) async {
|
||||
targetLanguage ??= _pangeaController.languageController.userL2;
|
||||
baseLanguage ??= _pangeaController.languageController.userL1;
|
||||
if (targetLanguage == null || analyticsProfile == null) return;
|
||||
|
||||
final analyticsRoom =
|
||||
_pangeaController.matrixState.client.analyticsRoomLocal(targetLanguage);
|
||||
|
||||
if (analyticsProfile!.targetLanguage == targetLanguage &&
|
||||
analyticsProfile!.baseLanguage == baseLanguage &&
|
||||
analyticsProfile!.languageAnalytics?[targetLanguage]?.level == level &&
|
||||
analyticsProfile!.analyticsRoomIdByLanguage(targetLanguage) ==
|
||||
analyticsRoom?.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
analyticsProfile!.baseLanguage = baseLanguage;
|
||||
analyticsProfile!.targetLanguage = targetLanguage;
|
||||
analyticsProfile!.setLanguageInfo(
|
||||
targetLanguage,
|
||||
level,
|
||||
analyticsRoom?.id,
|
||||
);
|
||||
await _savePublicProfileUpdate(
|
||||
PangeaEventTypes.profileAnalytics,
|
||||
analyticsProfile!.toJson(),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _addAnalyticsRoomIdsToPublicProfile() async {
|
||||
if (analyticsProfile?.languageAnalytics == null) return;
|
||||
final analyticsRooms =
|
||||
_pangeaController.matrixState.client.allMyAnalyticsRooms;
|
||||
|
||||
if (analyticsRooms.isEmpty) return;
|
||||
for (final analyticsRoom in analyticsRooms) {
|
||||
final lang = analyticsRoom.madeForLang?.split("-").first;
|
||||
if (lang == null || analyticsProfile?.languageAnalytics == null) continue;
|
||||
final langKey =
|
||||
analyticsProfile!.languageAnalytics!.keys.firstWhereOrNull(
|
||||
(l) => l.langCodeShort == lang,
|
||||
);
|
||||
|
||||
if (langKey == null) continue;
|
||||
if (analyticsProfile!.languageAnalytics![langKey]!.analyticsRoomId ==
|
||||
analyticsRoom.id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
analyticsProfile!.setLanguageInfo(
|
||||
langKey,
|
||||
analyticsProfile!.languageAnalytics![langKey]!.level,
|
||||
analyticsRoom.id,
|
||||
);
|
||||
}
|
||||
|
||||
await _savePublicProfileUpdate(
|
||||
PangeaEventTypes.profileAnalytics,
|
||||
analyticsProfile!.toJson(),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> addXPOffset(int offset) async {
|
||||
final targetLanguage = _pangeaController.languageController.userL2;
|
||||
if (targetLanguage == null || analyticsProfile == null) return;
|
||||
|
||||
analyticsProfile!.addXPOffset(
|
||||
targetLanguage,
|
||||
offset,
|
||||
_pangeaController.matrixState.client
|
||||
.analyticsRoomLocal(targetLanguage)
|
||||
?.id,
|
||||
);
|
||||
await _savePublicProfileUpdate(
|
||||
PangeaEventTypes.profileAnalytics,
|
||||
analyticsProfile!.toJson(),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> addBookmarkedActivity({
|
||||
required String activityId,
|
||||
}) async {
|
||||
if (activitiesProfile == null) {
|
||||
throw Exception("Activities profile is not initialized");
|
||||
}
|
||||
|
||||
activitiesProfile!.addBookmark(activityId);
|
||||
await _savePublicProfileUpdate(
|
||||
PangeaEventTypes.profileActivities,
|
||||
activitiesProfile!.toJson(),
|
||||
);
|
||||
}
|
||||
|
||||
Future<List<ActivityPlanModel>> getBookmarkedActivities() async {
|
||||
if (activitiesProfile == null) {
|
||||
throw Exception("Activities profile is not initialized");
|
||||
}
|
||||
|
||||
return activitiesProfile!.getBookmarkedActivities();
|
||||
}
|
||||
|
||||
List<ActivityPlanModel> getBookmarkedActivitiesSync() {
|
||||
if (activitiesProfile == null) {
|
||||
throw Exception("Activities profile is not initialized");
|
||||
}
|
||||
|
||||
return activitiesProfile!.getBookmarkedActivitiesSync();
|
||||
}
|
||||
|
||||
Future<void> updateBookmarkedActivity({
|
||||
required String activityId,
|
||||
required String newActivityId,
|
||||
}) async {
|
||||
if (activitiesProfile == null) {
|
||||
throw Exception("Activities profile is not initialized");
|
||||
}
|
||||
|
||||
activitiesProfile!.removeBookmark(activityId);
|
||||
activitiesProfile!.addBookmark(newActivityId);
|
||||
await _savePublicProfileUpdate(
|
||||
PangeaEventTypes.profileActivities,
|
||||
activitiesProfile!.toJson(),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> removeBookmarkedActivity({
|
||||
required String activityId,
|
||||
}) async {
|
||||
if (activitiesProfile == null) {
|
||||
throw Exception("Activities profile is not initialized");
|
||||
}
|
||||
|
||||
activitiesProfile!.removeBookmark(activityId);
|
||||
await _savePublicProfileUpdate(
|
||||
PangeaEventTypes.profileActivities,
|
||||
activitiesProfile!.toJson(),
|
||||
);
|
||||
}
|
||||
|
||||
bool isBookmarked(String id) => activitiesProfile?.isBookmarked(id) ?? false;
|
||||
|
||||
Future<AnalyticsProfileModel> getPublicAnalyticsProfile(
|
||||
String userId,
|
||||
) async {
|
||||
try {
|
||||
if (userId == BotName.byEnvironment) {
|
||||
return PublicProfileModel();
|
||||
return AnalyticsProfileModel();
|
||||
}
|
||||
|
||||
final resp = await client.getUserProfile(userId);
|
||||
return PublicProfileModel.fromJson(resp.additionalProperties);
|
||||
return AnalyticsProfileModel.fromJson(resp.additionalProperties);
|
||||
} catch (e, s) {
|
||||
ErrorHandler.logError(
|
||||
e: e,
|
||||
|
|
@ -437,7 +524,7 @@ class UserController {
|
|||
userId: userId,
|
||||
},
|
||||
);
|
||||
return PublicProfileModel();
|
||||
return AnalyticsProfileModel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
55
lib/pangea/user/models/activities_profile_model.dart
Normal file
55
lib/pangea/user/models/activities_profile_model.dart
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
import 'package:fluffychat/pangea/activity_planner/activity_plan_model.dart';
|
||||
import 'package:fluffychat/pangea/activity_suggestions/activity_plan_repo.dart';
|
||||
import 'package:fluffychat/pangea/common/constants/model_keys.dart';
|
||||
import 'package:fluffychat/pangea/events/constants/pangea_event_types.dart';
|
||||
|
||||
class ActivitiesProfileModel {
|
||||
final List<String> _bookmarkedActivities;
|
||||
|
||||
ActivitiesProfileModel({
|
||||
required List<String> bookmarkedActivities,
|
||||
}) : _bookmarkedActivities = bookmarkedActivities;
|
||||
|
||||
static ActivitiesProfileModel get empty => ActivitiesProfileModel(
|
||||
bookmarkedActivities: [],
|
||||
);
|
||||
|
||||
bool isBookmarked(String id) => _bookmarkedActivities.contains(id);
|
||||
|
||||
void addBookmark(String activityId) {
|
||||
if (!_bookmarkedActivities.contains(activityId)) {
|
||||
_bookmarkedActivities.add(activityId);
|
||||
}
|
||||
}
|
||||
|
||||
void removeBookmark(String activityId) {
|
||||
_bookmarkedActivities.remove(activityId);
|
||||
}
|
||||
|
||||
Future<List<ActivityPlanModel>> getBookmarkedActivities() => Future.wait(
|
||||
_bookmarkedActivities.map((id) => ActivityPlanRepo.get(id)).toList(),
|
||||
);
|
||||
|
||||
List<ActivityPlanModel> getBookmarkedActivitiesSync() => _bookmarkedActivities
|
||||
.map((id) => ActivityPlanRepo.getCached(id))
|
||||
.whereType<ActivityPlanModel>()
|
||||
.toList();
|
||||
|
||||
static ActivitiesProfileModel fromJson(Map<String, dynamic> json) {
|
||||
if (!json.containsKey(PangeaEventTypes.profileActivities)) {
|
||||
return ActivitiesProfileModel.empty;
|
||||
}
|
||||
|
||||
final profileJson = json[PangeaEventTypes.profileActivities];
|
||||
return ActivitiesProfileModel(
|
||||
bookmarkedActivities:
|
||||
List<String>.from(profileJson[ModelKey.bookmarkedActivities] ?? []),
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
ModelKey.bookmarkedActivities: _bookmarkedActivities,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -3,20 +3,20 @@ import 'package:fluffychat/pangea/events/constants/pangea_event_types.dart';
|
|||
import 'package:fluffychat/pangea/learning_settings/models/language_model.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/utils/p_language_store.dart';
|
||||
|
||||
class PublicProfileModel {
|
||||
class AnalyticsProfileModel {
|
||||
LanguageModel? baseLanguage;
|
||||
LanguageModel? targetLanguage;
|
||||
Map<LanguageModel, LanguageAnalyticsProfileEntry>? languageAnalytics;
|
||||
|
||||
PublicProfileModel({
|
||||
AnalyticsProfileModel({
|
||||
this.baseLanguage,
|
||||
this.targetLanguage,
|
||||
this.languageAnalytics,
|
||||
});
|
||||
|
||||
factory PublicProfileModel.fromJson(Map<String, dynamic> json) {
|
||||
factory AnalyticsProfileModel.fromJson(Map<String, dynamic> json) {
|
||||
if (!json.containsKey(PangeaEventTypes.profileAnalytics)) {
|
||||
return PublicProfileModel();
|
||||
return AnalyticsProfileModel();
|
||||
}
|
||||
|
||||
final profileJson = json[PangeaEventTypes.profileAnalytics];
|
||||
|
|
@ -47,7 +47,7 @@ class PublicProfileModel {
|
|||
}
|
||||
}
|
||||
|
||||
final profile = PublicProfileModel(
|
||||
final profile = AnalyticsProfileModel(
|
||||
baseLanguage: baseLanguage,
|
||||
targetLanguage: targetLanguage,
|
||||
languageAnalytics: languageAnalytics,
|
||||
Loading…
Add table
Reference in a new issue