Skip to content

Commit

Permalink
Merge pull request #302 from pangeachat/practice
Browse files Browse the repository at this point in the history
[WIP] Practice
  • Loading branch information
ggurdin authored Jun 25, 2024
2 parents fffa902 + c304da4 commit eb0fdbd
Show file tree
Hide file tree
Showing 28 changed files with 1,700 additions and 117 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@

# Special thanks

* Pangea Chat is a fork of [FluffyChat](https://fluffychat.im), is an open source, nonprofit and cute [[matrix](https://matrix.org)] client written in [Flutter](https://flutter.dev). The goal of FluffyChat is to create an easy to use instant messenger which is open source and accessible for everyone. You can [support the primary maker of FluffyChat directly here.](https://ko-fi.com/C1C86VN53)
* Pangea Chat is a fork of [FluffyChat](https://fluffychat.im) which is a [[matrix](https://matrix.org)] client written in [Flutter](https://flutter.dev). You can [support the primary maker of FluffyChat directly here.](https://ko-fi.com/C1C86VN53)

* <a href="https://github.com/fabiyamada">Fabiyamada</a> is a graphics designer and has made the fluffychat logo and the banner. Big thanks for her great designs.

Expand Down
5 changes: 4 additions & 1 deletion assets/l10n/intl_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -4055,5 +4055,8 @@
"spaceAnalytics": "Space Analytics",
"changeAnalyticsLanguage": "Change Analytics Language",
"suggestToSpace": "Suggest this space",
"suggestToSpaceDesc": "Suggested spaces will appear in the chat lists for their parent spaces"
"suggestToSpaceDesc": "Suggested spaces will appear in the chat lists for their parent spaces",
"practice": "Practice",
"noLanguagesSet": "No languages set",
"noActivitiesFound": "No practice activities found for this message"
}
30 changes: 27 additions & 3 deletions lib/pages/chat/events/message.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import 'package:fluffychat/pages/chat/chat.dart';
import 'package:fluffychat/pangea/enum/use_type.dart';
import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_message_event.dart';
import 'package:fluffychat/pangea/models/language_model.dart';
import 'package:fluffychat/pangea/widgets/chat/message_buttons.dart';
import 'package:fluffychat/pangea/widgets/chat/message_toolbar.dart';
import 'package:fluffychat/utils/date_time_extension.dart';
import 'package:fluffychat/utils/string_color.dart';
Expand Down Expand Up @@ -524,7 +525,14 @@ class Message extends StatelessWidget {
Widget container;
final showReceiptsRow =
event.hasAggregatedEvents(timeline, RelationshipTypes.reaction);
if (showReceiptsRow || displayTime || selected || displayReadMarker) {
// #Pangea
// if (showReceiptsRow || displayTime || selected || displayReadMarker) {
if (showReceiptsRow ||
displayTime ||
selected ||
displayReadMarker ||
(pangeaMessageEvent?.showMessageButtons ?? false)) {
// Pangea#
container = Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment:
Expand Down Expand Up @@ -561,15 +569,31 @@ class Message extends StatelessWidget {
AnimatedSize(
duration: FluffyThemes.animationDuration,
curve: FluffyThemes.animationCurve,
child: !showReceiptsRow
// #Pangea
child: !showReceiptsRow &&
!(pangeaMessageEvent?.showMessageButtons ?? false)
// child: !showReceiptsRow
// Pangea#
? const SizedBox.shrink()
: Padding(
padding: EdgeInsets.only(
top: 4.0,
left: (ownMessage ? 0 : Avatar.defaultSize) + 12.0,
right: ownMessage ? 0 : 12.0,
),
child: MessageReactions(event, timeline),
// #Pangea
child: Row(
mainAxisAlignment: ownMessage
? MainAxisAlignment.end
: MainAxisAlignment.start,
children: [
if (pangeaMessageEvent?.showMessageButtons ?? false)
MessageButtons(toolbarController: toolbarController),
MessageReactions(event, timeline),
],
),
// child: MessageReactions(event, timeline),
// Pangea#
),
),
if (displayReadMarker)
Expand Down
93 changes: 48 additions & 45 deletions lib/pangea/choreographer/widgets/choice_array.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@ import 'dart:math';

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';

import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:matrix/matrix.dart';

import '../../utils/bot_style.dart';
import 'it_shimmer.dart';
Expand All @@ -18,6 +16,10 @@ class ChoicesArray extends StatelessWidget {
final int? selectedChoiceIndex;
final String originalSpan;
final String Function(int) uniqueKeyForLayerLink;

/// some uses of this widget want to disable the choices
final bool isActive;

const ChoicesArray({
super.key,
required this.isLoading,
Expand All @@ -26,6 +28,7 @@ class ChoicesArray extends StatelessWidget {
required this.originalSpan,
required this.uniqueKeyForLayerLink,
required this.selectedChoiceIndex,
this.isActive = true,
this.onLongPress,
});

Expand All @@ -42,8 +45,8 @@ class ChoicesArray extends StatelessWidget {
.map(
(entry) => ChoiceItem(
theme: theme,
onLongPress: onLongPress,
onPressed: onPressed,
onLongPress: isActive ? onLongPress : null,
onPressed: isActive ? onPressed : (_) {},
entry: entry,
isSelected: selectedChoiceIndex == entry.key,
),
Expand Down Expand Up @@ -109,19 +112,19 @@ class ChoiceItem extends StatelessWidget {
: null,
child: TextButton(
style: ButtonStyle(
padding: MaterialStateProperty.all(
padding: WidgetStateProperty.all(
const EdgeInsets.symmetric(horizontal: 7),
),
//if index is selected, then give the background a slight primary color
backgroundColor: MaterialStateProperty.all<Color>(
backgroundColor: WidgetStateProperty.all<Color>(
entry.value.color != null
? entry.value.color!.withOpacity(0.2)
: theme.colorScheme.primary.withOpacity(0.1),
),
textStyle: MaterialStateProperty.all(
textStyle: WidgetStateProperty.all(
BotStyle.text(context),
),
shape: MaterialStateProperty.all(
shape: WidgetStateProperty.all(
RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
Expand Down Expand Up @@ -177,21 +180,21 @@ class ChoiceAnimationWidgetState extends State<ChoiceAnimationWidget>
);

_animation = widget.isGold
? Tween<double>(begin: 1.0, end: 1.2).animate(_controller)
: TweenSequence<double>([
TweenSequenceItem<double>(
tween: Tween<double>(begin: 0, end: -8 * pi / 180),
weight: 1.0,
),
TweenSequenceItem<double>(
tween: Tween<double>(begin: -8 * pi / 180, end: 16 * pi / 180),
weight: 2.0,
),
TweenSequenceItem<double>(
tween: Tween<double>(begin: 16 * pi / 180, end: 0),
weight: 1.0,
),
]).animate(_controller);
? Tween<double>(begin: 1.0, end: 1.2).animate(_controller)
: TweenSequence<double>([
TweenSequenceItem<double>(
tween: Tween<double>(begin: 0, end: -8 * pi / 180),
weight: 1.0,
),
TweenSequenceItem<double>(
tween: Tween<double>(begin: -8 * pi / 180, end: 16 * pi / 180),
weight: 2.0,
),
TweenSequenceItem<double>(
tween: Tween<double>(begin: 16 * pi / 180, end: 0),
weight: 1.0,
),
]).animate(_controller);

if (widget.selected && !animationPlayed) {
_controller.forward();
Expand Down Expand Up @@ -221,28 +224,28 @@ class ChoiceAnimationWidgetState extends State<ChoiceAnimationWidget>
@override
Widget build(BuildContext context) {
return widget.isGold
? AnimatedBuilder(
key: UniqueKey(),
animation: _animation,
builder: (context, child) {
return Transform.scale(
scale: _animation.value,
child: child,
);
},
child: widget.child,
)
: AnimatedBuilder(
key: UniqueKey(),
animation: _animation,
builder: (context, child) {
return Transform.rotate(
angle: _animation.value,
child: child,
);
},
child: widget.child,
);
? AnimatedBuilder(
key: UniqueKey(),
animation: _animation,
builder: (context, child) {
return Transform.scale(
scale: _animation.value,
child: child,
);
},
child: widget.child,
)
: AnimatedBuilder(
key: UniqueKey(),
animation: _animation,
builder: (context, child) {
return Transform.rotate(
angle: _animation.value,
child: child,
);
},
child: widget.child,
);
}

@override
Expand Down
4 changes: 4 additions & 0 deletions lib/pangea/constants/pangea_event_types.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,8 @@ class PangeaEventTypes {

static const String report = 'm.report';
static const textToSpeechRule = "p.rule.text_to_speech";

static const pangeaActivityRes = "pangea.activity_res";
static const acitivtyRequest = "pangea.activity_req";
static const activityRecord = "pangea.activity_completion";
}
7 changes: 7 additions & 0 deletions lib/pangea/controllers/pangea_controller.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import 'dart:async';
import 'dart:developer';
import 'dart:math';

Expand All @@ -12,6 +13,8 @@ import 'package:fluffychat/pangea/controllers/local_settings.dart';
import 'package:fluffychat/pangea/controllers/message_data_controller.dart';
import 'package:fluffychat/pangea/controllers/my_analytics_controller.dart';
import 'package:fluffychat/pangea/controllers/permissions_controller.dart';
import 'package:fluffychat/pangea/controllers/practice_activity_generation_controller.dart';
import 'package:fluffychat/pangea/controllers/practice_activity_record_controller.dart';
import 'package:fluffychat/pangea/controllers/speech_to_text_controller.dart';
import 'package:fluffychat/pangea/controllers/subscription_controller.dart';
import 'package:fluffychat/pangea/controllers/text_to_speech_controller.dart';
Expand Down Expand Up @@ -53,6 +56,8 @@ class PangeaController {
late TextToSpeechController textToSpeech;
late SpeechToTextController speechToText;
late LanguageDetectionController languageDetection;
late PracticeActivityRecordController activityRecordController;
late PracticeGenerationController practiceGenerationController;

///store Services
late PLocalStore pStoreService;
Expand Down Expand Up @@ -101,6 +106,8 @@ class PangeaController {
textToSpeech = TextToSpeechController(this);
speechToText = SpeechToTextController(this);
languageDetection = LanguageDetectionController(this);
activityRecordController = PracticeActivityRecordController(this);
practiceGenerationController = PracticeGenerationController();
PAuthGaurd.pController = this;
}

Expand Down
102 changes: 102 additions & 0 deletions lib/pangea/controllers/practice_activity_generation_controller.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import 'dart:async';

import 'package:fluffychat/pangea/constants/pangea_event_types.dart';
import 'package:fluffychat/pangea/enum/activity_type_enum.dart';
import 'package:fluffychat/pangea/enum/construct_type_enum.dart';
import 'package:fluffychat/pangea/extensions/pangea_room_extension/pangea_room_extension.dart';
import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_message_event.dart';
import 'package:fluffychat/pangea/matrix_event_wrappers/practice_activity_event.dart';
import 'package:fluffychat/pangea/models/practice_activities.dart/multiple_choice_activity_model.dart';
import 'package:fluffychat/pangea/models/practice_activities.dart/practice_activity_model.dart';
import 'package:matrix/matrix.dart';

/// Represents an item in the completion cache.
class _RequestCacheItem {
PracticeActivityRequest req;

Future<PracticeActivityEvent?> practiceActivityEvent;

_RequestCacheItem({
required this.req,
required this.practiceActivityEvent,
});
}

/// Controller for handling activity completions.
class PracticeGenerationController {
static final Map<int, _RequestCacheItem> _cache = {};
Timer? _cacheClearTimer;

PracticeGenerationController() {
_initializeCacheClearing();
}

void _initializeCacheClearing() {
const duration = Duration(minutes: 2);
_cacheClearTimer = Timer.periodic(duration, (Timer t) => _clearCache());
}

void _clearCache() {
_cache.clear();
}

void dispose() {
_cacheClearTimer?.cancel();
}

Future<PracticeActivityEvent?> _sendAndPackageEvent(
PracticeActivityModel model,
PangeaMessageEvent pangeaMessageEvent,
) async {
final Event? activityEvent = await pangeaMessageEvent.room.sendPangeaEvent(
content: model.toJson(),
parentEventId: pangeaMessageEvent.eventId,
type: PangeaEventTypes.pangeaActivityRes,
);

if (activityEvent == null) {
return null;
}

return PracticeActivityEvent(
event: activityEvent,
timeline: pangeaMessageEvent.timeline,
);
}

Future<PracticeActivityEvent?> getPracticeActivity(
PracticeActivityRequest req,
PangeaMessageEvent event,
) async {
final int cacheKey = req.hashCode;

if (_cache.containsKey(cacheKey)) {
return _cache[cacheKey]!.practiceActivityEvent;
} else {
//TODO - send request to server/bot, either via API or via event of type pangeaActivityReq
// for now, just make and send the event from the client
final Future<PracticeActivityEvent?> eventFuture =
_sendAndPackageEvent(dummyModel(event), event);

_cache[cacheKey] =
_RequestCacheItem(req: req, practiceActivityEvent: eventFuture);

return _cache[cacheKey]!.practiceActivityEvent;
}
}

PracticeActivityModel dummyModel(PangeaMessageEvent event) =>
PracticeActivityModel(
tgtConstructs: [
ConstructIdentifier(lemma: "be", type: ConstructType.vocab),
],
activityType: ActivityTypeEnum.multipleChoice,
langCode: event.messageDisplayLangCode,
msgId: event.eventId,
multipleChoice: MultipleChoice(
question: "What is a synonym for 'happy'?",
choices: ["sad", "angry", "joyful", "tired"],
answer: "joyful",
),
);
}
Loading

0 comments on commit eb0fdbd

Please sign in to comment.