1380 content challenges (#1391)
* use gold consistently for positive xp color * fix: dont point to local choreo
This commit is contained in:
parent
a26895ad81
commit
8084cc24cc
22 changed files with 566 additions and 500 deletions
|
|
@ -16,6 +16,7 @@ analyzer:
|
|||
exclude:
|
||||
- lib/generated_plugin_registrant.dart
|
||||
- lib/l10n/*.dart
|
||||
- assets/l10n/*.arb
|
||||
|
||||
dart_code_metrics:
|
||||
metrics:
|
||||
|
|
|
|||
|
|
@ -4659,7 +4659,7 @@
|
|||
"publicProfileTitle": "Allow my profile to be found in search",
|
||||
"publicProfileDesc": "By enabling this option, I confirm that I am of legal age in my country of residence",
|
||||
"clickWordsInstructions": "Click on individual words for more activities.",
|
||||
"chooseBestDefinition": "Choose the best definition",
|
||||
"chooseBestDefinition": "What does this word mean?",
|
||||
"chooseBaseForm": "Choose the base form",
|
||||
"notTheCodeError": "Sorry, that's not the code!",
|
||||
"totalXP": "Total XP",
|
||||
|
|
@ -4697,4 +4697,4 @@
|
|||
"downloading": "Downloading...",
|
||||
"failedFetchUserAnalytics": "Failed to download user analytics",
|
||||
"downloadComplete": "Download complete!"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +1,4 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
import 'package:get_storage/get_storage.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/pangea/config/environment.dart';
|
||||
import 'package:fluffychat/pangea/controllers/language_list_controller.dart';
|
||||
|
|
@ -15,6 +7,13 @@ import 'package:fluffychat/pangea/utils/firebase_analytics.dart';
|
|||
import 'package:fluffychat/utils/client_manager.dart';
|
||||
import 'package:fluffychat/utils/platform_infos.dart';
|
||||
import 'package:fluffychat/widgets/error_widget.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
import 'package:get_storage/get_storage.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
import 'config/setting_keys.dart';
|
||||
import 'utils/background_push.dart';
|
||||
import 'widgets/fluffy_chat_app.dart';
|
||||
|
|
|
|||
|
|
@ -1,12 +1,7 @@
|
|||
import 'dart:ui' as ui;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:badges/badges.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/config/themes.dart';
|
||||
import 'package:fluffychat/pages/chat/chat.dart';
|
||||
import 'package:fluffychat/pages/chat/chat_app_bar_list_tile.dart';
|
||||
|
|
@ -29,6 +24,11 @@ import 'package:fluffychat/widgets/future_loading_dialog.dart';
|
|||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
import 'package:fluffychat/widgets/mxc_image.dart';
|
||||
import 'package:fluffychat/widgets/unread_rooms_badge.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import '../../utils/stream_extension.dart';
|
||||
|
||||
enum _EventContextAction { info, report }
|
||||
|
|
@ -445,10 +445,8 @@ class ChatView extends StatelessWidget {
|
|||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
PointsGainedAnimation(
|
||||
gainColor: Theme.of(context)
|
||||
.colorScheme
|
||||
.onPrimary,
|
||||
const PointsGainedAnimation(
|
||||
gainColor: AppConfig.gold,
|
||||
origin:
|
||||
AnalyticsUpdateOrigin.sendMessage,
|
||||
),
|
||||
|
|
|
|||
25
lib/pangea/choreographer/widgets/text_loading_shimmer.dart
Normal file
25
lib/pangea/choreographer/widgets/text_loading_shimmer.dart
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:shimmer/shimmer.dart';
|
||||
|
||||
class TextLoadingShimmer extends StatelessWidget {
|
||||
final double width;
|
||||
const TextLoadingShimmer({
|
||||
super.key,
|
||||
this.width = 140.0,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Shimmer.fromColors(
|
||||
baseColor: Colors.transparent, // Base color of the shimmer effect
|
||||
// for higlight, use white with 50 opacity
|
||||
highlightColor: AppConfig.primaryColor.withAlpha(70),
|
||||
child: Container(
|
||||
height: AppConfig.messageFontSize * AppConfig.fontSizeFactor,
|
||||
width: width, // Width of the rectangle
|
||||
color: AppConfig.primaryColor, // Background color of the rectangle
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +1,11 @@
|
|||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
import 'package:fluffychat/pangea/controllers/get_analytics_controller.dart';
|
||||
import 'package:fluffychat/pangea/enum/activity_type_enum.dart';
|
||||
import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_message_event.dart';
|
||||
import 'package:fluffychat/pangea/models/pangea_token_model.dart';
|
||||
import 'package:fluffychat/pangea/models/practice_activities.dart/practice_activity_model.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
/// Picks which tokens to do activities on and what types of activities to do
|
||||
/// Caches result so that we don't have to recompute it
|
||||
|
|
@ -160,9 +159,11 @@ class MessageAnalyticsEntry {
|
|||
return null;
|
||||
}
|
||||
|
||||
// we will only do hidden word listening 50% of the time
|
||||
// we will only do hidden word listening 30% of the time
|
||||
// if there are no other activities to do, we will always do hidden word listening
|
||||
if (numOtherActivities >= _maxQueueLength && Random().nextDouble() < 0.5) {
|
||||
if (Random().nextDouble() < 0.7) {
|
||||
// @ggurdin - just want you to review this change. i'm not sure what numOtherActivities >= _maxQueueLength was doing
|
||||
// if (numOtherActivities >= _maxQueueLength && Random().nextDouble() < 0.5) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
|||
31
lib/pangea/models/content_feedback.dart
Normal file
31
lib/pangea/models/content_feedback.dart
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
abstract class JsonSerializable {
|
||||
Map<String, dynamic> toJson();
|
||||
factory JsonSerializable.fromJson(Map<String, dynamic> json) {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
|
||||
class ContentFeedback<T extends JsonSerializable> {
|
||||
final JsonSerializable content;
|
||||
final String feedback;
|
||||
|
||||
ContentFeedback(this.content, this.feedback);
|
||||
|
||||
toJson() {
|
||||
return {
|
||||
'content': content.toJson(),
|
||||
'feedback': feedback,
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is ContentFeedback &&
|
||||
runtimeType == other.runtimeType &&
|
||||
content == other.content &&
|
||||
feedback == other.feedback;
|
||||
|
||||
@override
|
||||
int get hashCode => content.hashCode ^ feedback.hashCode;
|
||||
}
|
||||
|
|
@ -1,11 +1,7 @@
|
|||
import 'dart:developer';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import 'package:fluffychat/pangea/constants/language_constants.dart';
|
||||
import 'package:fluffychat/pangea/constants/pangea_event_types.dart';
|
||||
import 'package:fluffychat/pangea/enum/activity_type_enum.dart';
|
||||
|
|
@ -16,9 +12,13 @@ import 'package:fluffychat/pangea/models/analytics/construct_use_model.dart';
|
|||
import 'package:fluffychat/pangea/models/analytics/constructs_model.dart';
|
||||
import 'package:fluffychat/pangea/models/pangea_token_text_model.dart';
|
||||
import 'package:fluffychat/pangea/models/practice_activities.dart/practice_activity_model.dart';
|
||||
import 'package:fluffychat/pangea/repo/lemma_definition_repo.dart';
|
||||
import 'package:fluffychat/pangea/repo/lemma_info/lemma_info_repo.dart';
|
||||
import 'package:fluffychat/pangea/repo/lemma_info/lemma_info_request.dart';
|
||||
import 'package:fluffychat/pangea/utils/error_handler.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import '../constants/model_keys.dart';
|
||||
import 'lemma.dart';
|
||||
|
||||
|
|
@ -373,7 +373,7 @@ class PangeaToken {
|
|||
);
|
||||
return distractors.isNotEmpty;
|
||||
case ActivityTypeEnum.wordMeaning:
|
||||
return LemmaDictionaryRepo.getDistractorDefinitions(
|
||||
return LemmaInfoRepo.getDistractorDefinitions(
|
||||
lemma.text,
|
||||
1,
|
||||
).isNotEmpty;
|
||||
|
|
@ -519,9 +519,9 @@ class PangeaToken {
|
|||
};
|
||||
}
|
||||
|
||||
Future<List<String>> getEmojiChoices() => LemmaDictionaryRepo.get(
|
||||
LemmaDefinitionRequest(
|
||||
lemma: lemma,
|
||||
Future<List<String>> getEmojiChoices() => LemmaInfoRepo.get(
|
||||
LemmaInfoRequest(
|
||||
lemma: lemma.text,
|
||||
partOfSpeech: pos,
|
||||
lemmaLang: MatrixState
|
||||
.pangeaController.languageController.userL2?.langCode ??
|
||||
|
|
|
|||
|
|
@ -1,175 +0,0 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:http/http.dart';
|
||||
|
||||
import 'package:fluffychat/pangea/config/environment.dart';
|
||||
import 'package:fluffychat/pangea/models/lemma.dart';
|
||||
import 'package:fluffychat/pangea/network/requests.dart';
|
||||
import 'package:fluffychat/pangea/network/urls.dart';
|
||||
import 'package:fluffychat/pangea/utils/error_handler.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
||||
class LemmaDefinitionRequest {
|
||||
final Lemma _lemma;
|
||||
final String partOfSpeech;
|
||||
final String lemmaLang;
|
||||
final String userL1;
|
||||
|
||||
LemmaDefinitionRequest({
|
||||
required this.partOfSpeech,
|
||||
required this.lemmaLang,
|
||||
required this.userL1,
|
||||
required Lemma lemma,
|
||||
}) : _lemma = lemma;
|
||||
|
||||
String get lemma {
|
||||
if (_lemma.text.isNotEmpty) {
|
||||
return _lemma.text;
|
||||
}
|
||||
ErrorHandler.logError(
|
||||
e: "Found lemma with empty text",
|
||||
data: {
|
||||
'lemma': _lemma,
|
||||
'part_of_speech': partOfSpeech,
|
||||
'lemma_lang': lemmaLang,
|
||||
'user_l1': userL1,
|
||||
},
|
||||
);
|
||||
return _lemma.form;
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'lemma': lemma,
|
||||
'part_of_speech': partOfSpeech,
|
||||
'lemma_lang': lemmaLang,
|
||||
'user_l1': userL1,
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is LemmaDefinitionRequest &&
|
||||
runtimeType == other.runtimeType &&
|
||||
lemma == other.lemma &&
|
||||
partOfSpeech == other.partOfSpeech &&
|
||||
lemmaLang == other.lemmaLang &&
|
||||
userL1 == other.userL1;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
lemma.hashCode ^
|
||||
partOfSpeech.hashCode ^
|
||||
lemmaLang.hashCode ^
|
||||
userL1.hashCode;
|
||||
}
|
||||
|
||||
class LemmaDefinitionResponse {
|
||||
final List<String> emoji;
|
||||
final String meaning;
|
||||
|
||||
LemmaDefinitionResponse({
|
||||
required this.emoji,
|
||||
required this.meaning,
|
||||
});
|
||||
|
||||
factory LemmaDefinitionResponse.fromJson(Map<String, dynamic> json) {
|
||||
return LemmaDefinitionResponse(
|
||||
emoji: (json['emoji'] as List<dynamic>).map((e) => e as String).toList(),
|
||||
meaning: json['meaning'] as String,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'emoji': emoji,
|
||||
'meaning': meaning,
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is LemmaDefinitionResponse &&
|
||||
runtimeType == other.runtimeType &&
|
||||
emoji.length == other.emoji.length &&
|
||||
emoji.every((element) => other.emoji.contains(element)) &&
|
||||
meaning == other.meaning;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
emoji.fold(0, (prev, element) => prev ^ element.hashCode) ^
|
||||
meaning.hashCode;
|
||||
}
|
||||
|
||||
class LemmaDictionaryRepo {
|
||||
// In-memory cache with timestamps
|
||||
static final Map<LemmaDefinitionRequest, LemmaDefinitionResponse> _cache = {};
|
||||
static final Map<LemmaDefinitionRequest, DateTime> _cacheTimestamps = {};
|
||||
|
||||
static const Duration _cacheDuration = Duration(days: 2);
|
||||
|
||||
static Future<LemmaDefinitionResponse> get(
|
||||
LemmaDefinitionRequest request,
|
||||
) async {
|
||||
_clearExpiredEntries();
|
||||
|
||||
// Check the cache first
|
||||
if (_cache.containsKey(request)) {
|
||||
return _cache[request]!;
|
||||
}
|
||||
|
||||
final Requests req = Requests(
|
||||
choreoApiKey: Environment.choreoApiKey,
|
||||
accessToken: MatrixState.pangeaController.userController.accessToken,
|
||||
);
|
||||
|
||||
final requestBody = request.toJson();
|
||||
final Response res = await req.post(
|
||||
url: PApiUrls.lemmaDictionary,
|
||||
body: requestBody,
|
||||
);
|
||||
|
||||
final decodedBody = jsonDecode(utf8.decode(res.bodyBytes));
|
||||
final response = LemmaDefinitionResponse.fromJson(decodedBody);
|
||||
|
||||
// Store the response and timestamp in the cache
|
||||
_cache[request] = response;
|
||||
_cacheTimestamps[request] = DateTime.now();
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
/// From the cache, get a random set of cached definitions that are not for a specific lemma
|
||||
static List<String> getDistractorDefinitions(
|
||||
String lemma,
|
||||
int count,
|
||||
) {
|
||||
_clearExpiredEntries();
|
||||
|
||||
final List<String> definitions = [];
|
||||
for (final entry in _cache.entries) {
|
||||
if (entry.key.lemma != lemma) {
|
||||
definitions.add(entry.value.meaning);
|
||||
}
|
||||
}
|
||||
|
||||
definitions.shuffle();
|
||||
|
||||
return definitions.take(count).toList();
|
||||
}
|
||||
|
||||
static void _clearExpiredEntries() {
|
||||
final now = DateTime.now();
|
||||
final expiredKeys = _cacheTimestamps.entries
|
||||
.where((entry) => now.difference(entry.value) > _cacheDuration)
|
||||
.map((entry) => entry.key)
|
||||
.toList();
|
||||
|
||||
for (final key in expiredKeys) {
|
||||
_cache.remove(key);
|
||||
_cacheTimestamps.remove(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
107
lib/pangea/repo/lemma_info/lemma_info_repo.dart
Normal file
107
lib/pangea/repo/lemma_info/lemma_info_repo.dart
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:fluffychat/pangea/models/content_feedback.dart';
|
||||
import 'package:fluffychat/pangea/network/urls.dart';
|
||||
import 'package:fluffychat/pangea/repo/lemma_info/lemma_info_request.dart';
|
||||
import 'package:fluffychat/pangea/repo/lemma_info/lemma_info_response.dart';
|
||||
import 'package:fluffychat/pangea/utils/error_handler.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:http/http.dart';
|
||||
|
||||
import '../../config/environment.dart';
|
||||
import '../../network/requests.dart';
|
||||
|
||||
class LemmaInfoRepo {
|
||||
// In-memory cache with timestamps
|
||||
static final Map<LemmaInfoRequest, LemmaInfoResponse> _cache = {};
|
||||
static final Map<LemmaInfoRequest, DateTime> _cacheTimestamps = {};
|
||||
|
||||
static const Duration _cacheDuration = Duration(days: 2);
|
||||
|
||||
static Future<LemmaInfoResponse> get(
|
||||
LemmaInfoRequest request, [
|
||||
String? feedback,
|
||||
]) async {
|
||||
_clearExpiredEntries();
|
||||
|
||||
if (_cache.containsKey(request)) {
|
||||
final cached = _cache[request]!;
|
||||
|
||||
if (feedback == null) {
|
||||
// in this case, we just return the cached response
|
||||
return cached;
|
||||
} else {
|
||||
// we're adding this within the service to avoid needing to have the widgets
|
||||
// save state including the bad response
|
||||
request.feedback = ContentFeedback(
|
||||
cached,
|
||||
feedback,
|
||||
);
|
||||
}
|
||||
} else if (feedback != null) {
|
||||
// the cache should have the request in order for the user to provide feedback
|
||||
// this would be a strange situation and indicate some error in our logic
|
||||
debugger(when: kDebugMode);
|
||||
ErrorHandler.logError(
|
||||
m: 'Feedback provided for a non-cached request',
|
||||
data: request.toJson(),
|
||||
);
|
||||
} else {
|
||||
debugPrint('No cached response for lemma ${request.lemma}');
|
||||
}
|
||||
|
||||
final Requests req = Requests(
|
||||
choreoApiKey: Environment.choreoApiKey,
|
||||
accessToken: MatrixState.pangeaController.userController.accessToken,
|
||||
);
|
||||
|
||||
final requestBody = request.toJson();
|
||||
final Response res = await req.post(
|
||||
url: PApiUrls.lemmaDictionary,
|
||||
body: requestBody,
|
||||
);
|
||||
|
||||
final decodedBody = jsonDecode(utf8.decode(res.bodyBytes));
|
||||
final response = LemmaInfoResponse.fromJson(decodedBody);
|
||||
|
||||
// Store the response and timestamp in the cache
|
||||
_cache[request] = response;
|
||||
_cacheTimestamps[request] = DateTime.now();
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
/// From the cache, get a random set of cached definitions that are not for a specific lemma
|
||||
static List<String> getDistractorDefinitions(
|
||||
String lemma,
|
||||
int count,
|
||||
) {
|
||||
_clearExpiredEntries();
|
||||
|
||||
final Set<String> definitions = {};
|
||||
for (final entry in _cache.entries) {
|
||||
if (entry.key.lemma != lemma) {
|
||||
definitions.add(entry.value.meaning);
|
||||
}
|
||||
}
|
||||
|
||||
definitions.toList().shuffle();
|
||||
|
||||
return definitions.take(count).toList();
|
||||
}
|
||||
|
||||
static void _clearExpiredEntries() {
|
||||
final now = DateTime.now();
|
||||
final expiredKeys = _cacheTimestamps.entries
|
||||
.where((entry) => now.difference(entry.value) > _cacheDuration)
|
||||
.map((entry) => entry.key)
|
||||
.toList();
|
||||
|
||||
for (final key in expiredKeys) {
|
||||
_cache.remove(key);
|
||||
_cacheTimestamps.remove(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
43
lib/pangea/repo/lemma_info/lemma_info_request.dart
Normal file
43
lib/pangea/repo/lemma_info/lemma_info_request.dart
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
import 'package:fluffychat/pangea/models/content_feedback.dart';
|
||||
import 'package:fluffychat/pangea/repo/lemma_info/lemma_info_response.dart';
|
||||
|
||||
class LemmaInfoRequest {
|
||||
final String lemma;
|
||||
final String partOfSpeech;
|
||||
final String lemmaLang;
|
||||
final String userL1;
|
||||
|
||||
ContentFeedback<LemmaInfoResponse>? feedback;
|
||||
|
||||
LemmaInfoRequest({
|
||||
required String partOfSpeech,
|
||||
required String lemmaLang,
|
||||
required this.userL1,
|
||||
required this.lemma,
|
||||
this.feedback,
|
||||
}) : partOfSpeech = partOfSpeech.toLowerCase(),
|
||||
lemmaLang = lemmaLang.toLowerCase();
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'lemma': lemma,
|
||||
'part_of_speech': partOfSpeech,
|
||||
'lemma_lang': lemmaLang,
|
||||
'user_l1': userL1,
|
||||
'feedback': feedback?.toJson(),
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is LemmaInfoRequest &&
|
||||
runtimeType == other.runtimeType &&
|
||||
lemma == other.lemma &&
|
||||
partOfSpeech == other.partOfSpeech &&
|
||||
feedback == other.feedback;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
lemma.hashCode ^ partOfSpeech.hashCode ^ feedback.hashCode;
|
||||
}
|
||||
40
lib/pangea/repo/lemma_info/lemma_info_response.dart
Normal file
40
lib/pangea/repo/lemma_info/lemma_info_response.dart
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
import 'package:fluffychat/pangea/models/content_feedback.dart';
|
||||
|
||||
class LemmaInfoResponse implements JsonSerializable {
|
||||
final List<String> emoji;
|
||||
final String meaning;
|
||||
|
||||
LemmaInfoResponse({
|
||||
required this.emoji,
|
||||
required this.meaning,
|
||||
});
|
||||
|
||||
factory LemmaInfoResponse.fromJson(Map<String, dynamic> json) {
|
||||
return LemmaInfoResponse(
|
||||
emoji: (json['emoji'] as List<dynamic>).map((e) => e as String).toList(),
|
||||
meaning: json['meaning'] as String,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'emoji': emoji,
|
||||
'meaning': meaning,
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is LemmaInfoResponse &&
|
||||
runtimeType == other.runtimeType &&
|
||||
emoji.length == other.emoji.length &&
|
||||
emoji.every((element) => other.emoji.contains(element)) &&
|
||||
meaning == other.meaning;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
emoji.fold(0, (prev, element) => prev ^ element.hashCode) ^
|
||||
meaning.hashCode;
|
||||
}
|
||||
|
|
@ -1,13 +1,12 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
|
||||
import 'package:fluffychat/pangea/enum/activity_type_enum.dart';
|
||||
import 'package:fluffychat/pangea/enum/construct_type_enum.dart';
|
||||
import 'package:fluffychat/pangea/models/practice_activities.dart/message_activity_request.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:fluffychat/pangea/repo/lemma_definition_repo.dart';
|
||||
import 'package:fluffychat/pangea/repo/lemma_info/lemma_info_repo.dart';
|
||||
import 'package:fluffychat/pangea/repo/lemma_info/lemma_info_request.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
|
||||
class WordMeaningActivityGenerator {
|
||||
Future<MessageActivityResponse> get(
|
||||
|
|
@ -15,24 +14,25 @@ class WordMeaningActivityGenerator {
|
|||
BuildContext context,
|
||||
) async {
|
||||
final ConstructIdentifier lemmaId = ConstructIdentifier(
|
||||
lemma: req.targetTokens[0].lemma.text,
|
||||
lemma: req.targetTokens[0].lemma.text.isNotEmpty
|
||||
? req.targetTokens[0].lemma.text
|
||||
: req.targetTokens[0].lemma.form,
|
||||
type: ConstructTypeEnum.vocab,
|
||||
category: req.targetTokens[0].pos,
|
||||
);
|
||||
|
||||
final LemmaDefinitionRequest lemmaDefReq = LemmaDefinitionRequest(
|
||||
lemma: req.targetTokens[0].lemma,
|
||||
final lemmaDefReq = LemmaInfoRequest(
|
||||
lemma: lemmaId.lemma,
|
||||
partOfSpeech: lemmaId.category,
|
||||
|
||||
/// This assumes that the user's L2 is the language of the lemma
|
||||
// Note that this assumes that the user's L2 is the language of the lemma.
|
||||
lemmaLang: req.userL2,
|
||||
userL1: req.userL1,
|
||||
);
|
||||
|
||||
final res = await LemmaDictionaryRepo.get(lemmaDefReq);
|
||||
final res = await LemmaInfoRepo.get(lemmaDefReq);
|
||||
|
||||
final choices =
|
||||
LemmaDictionaryRepo.getDistractorDefinitions(lemmaDefReq.lemma, 3);
|
||||
LemmaInfoRepo.getDistractorDefinitions(lemmaDefReq.lemma, 3);
|
||||
|
||||
if (!choices.contains(res.meaning)) {
|
||||
choices.add(res.meaning);
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/pangea/controllers/get_analytics_controller.dart';
|
||||
import 'package:fluffychat/pangea/controllers/put_analytics_controller.dart';
|
||||
import 'package:fluffychat/pangea/utils/bot_style.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class PointsGainedAnimation extends StatefulWidget {
|
||||
final Color? gainColor;
|
||||
|
|
@ -15,7 +15,7 @@ class PointsGainedAnimation extends StatefulWidget {
|
|||
const PointsGainedAnimation({
|
||||
super.key,
|
||||
required this.origin,
|
||||
this.gainColor,
|
||||
this.gainColor = AppConfig.gold,
|
||||
this.loseColor = Colors.red,
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,3 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
|
||||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/pangea/controllers/get_analytics_controller.dart';
|
||||
import 'package:fluffychat/pangea/enum/construct_type_enum.dart';
|
||||
|
|
@ -10,6 +6,8 @@ import 'package:fluffychat/pangea/models/analytics/constructs_model.dart';
|
|||
import 'package:fluffychat/pangea/utils/grammar/get_grammar_copy.dart';
|
||||
import 'package:fluffychat/pangea/widgets/chat_list/analytics_summary/learning_progress_bar.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
|
||||
class LevelBarPopup extends StatelessWidget {
|
||||
const LevelBarPopup({
|
||||
|
|
@ -156,20 +154,23 @@ class LevelBarPopup extends StatelessWidget {
|
|||
children: [
|
||||
Text(
|
||||
"${use.pointValue > 0 ? '+' : ''}${use.pointValue}",
|
||||
style: const TextStyle(
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
height: 1,
|
||||
color: use.pointValue > 0
|
||||
? AppConfig.gold
|
||||
: Colors.red,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 5),
|
||||
const CircleAvatar(
|
||||
radius: 8,
|
||||
child: Icon(
|
||||
size: 10,
|
||||
Icons.star,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
// const SizedBox(width: 5),
|
||||
// const CircleAvatar(
|
||||
// radius: 8,
|
||||
// child: Icon(
|
||||
// size: 10,
|
||||
// Icons.star,
|
||||
// color: Colors.white,
|
||||
// ),
|
||||
// ),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
import 'package:fluffychat/utils/feedback_dialog.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
|
||||
import 'package:fluffychat/pangea/widgets/common/bot_face_svg.dart';
|
||||
|
||||
class ContentIssueButton extends StatelessWidget {
|
||||
final bool isActive;
|
||||
final void Function(String) submitFeedback;
|
||||
|
|
@ -27,61 +25,8 @@ class ContentIssueButton extends StatelessWidget {
|
|||
if (!isActive) {
|
||||
return;
|
||||
}
|
||||
final TextEditingController feedbackController =
|
||||
TextEditingController();
|
||||
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text(
|
||||
L10n.of(context).reportContentIssueTitle,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
content: SingleChildScrollView(
|
||||
child: Container(
|
||||
constraints: const BoxConstraints(maxWidth: 300),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const BotFace(
|
||||
width: 60,
|
||||
expression: BotExpression.addled,
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Text(L10n.of(context).reportContentIssueDescription),
|
||||
const SizedBox(height: 10),
|
||||
TextField(
|
||||
controller: feedbackController,
|
||||
decoration: InputDecoration(
|
||||
labelText: L10n.of(context).feedback,
|
||||
border: const OutlineInputBorder(),
|
||||
),
|
||||
maxLines: 4,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop(); // Close the dialog
|
||||
},
|
||||
child: Text(L10n.of(context).cancel),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
// Call the additional callback function
|
||||
submitFeedback(feedbackController.text);
|
||||
Navigator.of(context).pop(); // Close the dialog
|
||||
},
|
||||
child: Text(L10n.of(context).submit),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
showFeedbackDialog(context, submitFeedback);
|
||||
},
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -1,62 +0,0 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
|
||||
import 'package:fluffychat/pangea/constants/language_constants.dart';
|
||||
import 'package:fluffychat/pangea/models/pangea_token_model.dart';
|
||||
import 'package:fluffychat/pangea/repo/lemma_definition_repo.dart';
|
||||
import 'package:fluffychat/pangea/widgets/igc/card_error_widget.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
||||
class ContextualTranslationWidget extends StatelessWidget {
|
||||
final PangeaToken token;
|
||||
final String langCode;
|
||||
|
||||
const ContextualTranslationWidget({
|
||||
super.key,
|
||||
required this.token,
|
||||
required this.langCode,
|
||||
});
|
||||
|
||||
Future<String> _fetchDefinition() async {
|
||||
final LemmaDefinitionRequest lemmaDefReq = LemmaDefinitionRequest(
|
||||
lemma: token.lemma,
|
||||
partOfSpeech: token.pos,
|
||||
|
||||
/// This assumes that the user's L2 is the language of the lemma
|
||||
lemmaLang: langCode,
|
||||
userL1:
|
||||
MatrixState.pangeaController.languageController.userL1?.langCode ??
|
||||
LanguageKeys.defaultLanguage,
|
||||
);
|
||||
|
||||
final res = await LemmaDictionaryRepo.get(lemmaDefReq);
|
||||
return res.meaning;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FutureBuilder(
|
||||
future: _fetchDefinition(),
|
||||
builder: (context, snapshot) {
|
||||
return Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: snapshot.connectionState != ConnectionState.done
|
||||
? const CircularProgressIndicator()
|
||||
: snapshot.hasError
|
||||
? CardErrorWidget(
|
||||
error: L10n.of(context).oopsSomethingWentWrong,
|
||||
padding: 0,
|
||||
maxWidth: 500,
|
||||
)
|
||||
: Text(
|
||||
snapshot.data ?? "...",
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,71 +0,0 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:fluffychat/pangea/constants/language_constants.dart';
|
||||
import 'package:fluffychat/pangea/models/pangea_token_model.dart';
|
||||
import 'package:fluffychat/pangea/repo/lemma_definition_repo.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
||||
class LemmaDefinitionWidget extends StatefulWidget {
|
||||
final PangeaToken token;
|
||||
final String tokenLang;
|
||||
final VoidCallback onPressed;
|
||||
|
||||
const LemmaDefinitionWidget({
|
||||
super.key,
|
||||
required this.token,
|
||||
required this.tokenLang,
|
||||
required this.onPressed,
|
||||
});
|
||||
|
||||
@override
|
||||
LemmaDefinitionWidgetState createState() => LemmaDefinitionWidgetState();
|
||||
}
|
||||
|
||||
class LemmaDefinitionWidgetState extends State<LemmaDefinitionWidget> {
|
||||
late Future<String> _definition;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_definition = _fetchDefinition();
|
||||
}
|
||||
|
||||
Future<String> _fetchDefinition() async {
|
||||
if (widget.token.shouldDoPosActivity) {
|
||||
return '?';
|
||||
} else {
|
||||
final res = await LemmaDictionaryRepo.get(
|
||||
LemmaDefinitionRequest(
|
||||
lemma: widget.token.lemma,
|
||||
partOfSpeech: widget.token.pos,
|
||||
lemmaLang: widget.tokenLang,
|
||||
userL1: MatrixState
|
||||
.pangeaController.languageController.userL1?.langCode ??
|
||||
LanguageKeys.defaultLanguage,
|
||||
),
|
||||
);
|
||||
return res.meaning;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FutureBuilder<String>(
|
||||
future: _definition,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||
return const CircularProgressIndicator();
|
||||
} else if (snapshot.hasError) {
|
||||
// TODO better error widget
|
||||
return Text('Error: ${snapshot.error}');
|
||||
} else {
|
||||
return ActionChip(
|
||||
avatar: const Icon(Icons.book),
|
||||
label: Text(snapshot.data ?? 'No definition found'),
|
||||
onPressed: widget.onPressed,
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
97
lib/pangea/widgets/word_zoom/lemma_meaning_widget.dart
Normal file
97
lib/pangea/widgets/word_zoom/lemma_meaning_widget.dart
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
import 'dart:developer';
|
||||
|
||||
import 'package:fluffychat/pangea/choreographer/widgets/text_loading_shimmer.dart';
|
||||
import 'package:fluffychat/pangea/constants/language_constants.dart';
|
||||
import 'package:fluffychat/pangea/repo/lemma_info/lemma_info_repo.dart';
|
||||
import 'package:fluffychat/pangea/repo/lemma_info/lemma_info_request.dart';
|
||||
import 'package:fluffychat/utils/feedback_dialog.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class LemmaMeaningWidget extends StatefulWidget {
|
||||
final String lemma;
|
||||
final String pos;
|
||||
final String langCode;
|
||||
|
||||
const LemmaMeaningWidget({
|
||||
super.key,
|
||||
required this.lemma,
|
||||
required this.pos,
|
||||
required this.langCode,
|
||||
});
|
||||
|
||||
@override
|
||||
_LemmaMeaningWidgetState createState() => _LemmaMeaningWidgetState();
|
||||
}
|
||||
|
||||
class _LemmaMeaningWidgetState extends State<LemmaMeaningWidget> {
|
||||
late Future<String> _definitionFuture;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_definitionFuture = _fetchDefinition();
|
||||
}
|
||||
|
||||
Future<String> _fetchDefinition([String? feedback]) async {
|
||||
final LemmaInfoRequest lemmaDefReq = LemmaInfoRequest(
|
||||
lemma: widget.lemma,
|
||||
partOfSpeech: widget.pos,
|
||||
|
||||
/// This assumes that the user's L2 is the language of the lemma
|
||||
lemmaLang: widget.langCode,
|
||||
userL1:
|
||||
MatrixState.pangeaController.languageController.userL1?.langCode ??
|
||||
LanguageKeys.defaultLanguage,
|
||||
);
|
||||
|
||||
final res = await LemmaInfoRepo.get(lemmaDefReq, feedback);
|
||||
return res.meaning;
|
||||
}
|
||||
|
||||
void _showFeedbackDialog(String offendingContentString) async {
|
||||
// setState(() {
|
||||
// _definitionFuture = _fetchDefinition(offendingContentString);
|
||||
// });
|
||||
await showFeedbackDialog(
|
||||
context,
|
||||
Text(offendingContentString),
|
||||
(feedback) async {
|
||||
setState(() {
|
||||
_definitionFuture = _fetchDefinition(feedback);
|
||||
});
|
||||
return;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FutureBuilder(
|
||||
future: _definitionFuture,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState != ConnectionState.done) {
|
||||
return const TextLoadingShimmer();
|
||||
}
|
||||
|
||||
if (snapshot.hasError) {
|
||||
debugger(when: kDebugMode);
|
||||
return Text(
|
||||
snapshot.error.toString(),
|
||||
textAlign: TextAlign.center,
|
||||
);
|
||||
}
|
||||
|
||||
return GestureDetector(
|
||||
onLongPress: () => _showFeedbackDialog(snapshot.data as String),
|
||||
onDoubleTap: () => _showFeedbackDialog(snapshot.data as String),
|
||||
child: Text(
|
||||
snapshot.data as String,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
74
lib/pangea/widgets/word_zoom/word_zoom_center_widget.dart
Normal file
74
lib/pangea/widgets/word_zoom/word_zoom_center_widget.dart
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
import 'package:fluffychat/pangea/controllers/message_analytics_controller.dart';
|
||||
import 'package:fluffychat/pangea/widgets/chat/toolbar_content_loading_indicator.dart';
|
||||
import 'package:fluffychat/pangea/widgets/practice_activity/practice_activity_card.dart';
|
||||
import 'package:fluffychat/pangea/widgets/word_zoom/lemma_meaning_widget.dart';
|
||||
import 'package:fluffychat/pangea/widgets/word_zoom/word_zoom_widget.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class WordZoomCenterWidget extends StatelessWidget {
|
||||
final WordZoomSelection? selectionType;
|
||||
final String? selectedMorphFeature;
|
||||
final bool shouldDoActivity;
|
||||
final bool locked;
|
||||
final WordZoomWidgetState wordDetailsController;
|
||||
|
||||
const WordZoomCenterWidget({
|
||||
required this.selectionType,
|
||||
required this.selectedMorphFeature,
|
||||
required this.shouldDoActivity,
|
||||
required this.locked,
|
||||
required this.wordDetailsController,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (selectionType == null) {
|
||||
return const ToolbarContentLoadingIndicator();
|
||||
}
|
||||
|
||||
if (shouldDoActivity || locked) {
|
||||
return PracticeActivityCard(
|
||||
pangeaMessageEvent: wordDetailsController.widget.messageEvent,
|
||||
targetTokensAndActivityType: TargetTokensAndActivityType(
|
||||
tokens: [wordDetailsController.widget.token],
|
||||
activityType: selectionType!.activityType,
|
||||
),
|
||||
overlayController: wordDetailsController.widget.overlayController,
|
||||
morphFeature: selectedMorphFeature,
|
||||
wordDetailsController: wordDetailsController,
|
||||
);
|
||||
}
|
||||
|
||||
if (selectionType == WordZoomSelection.meaning) {
|
||||
return Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: LemmaMeaningWidget(
|
||||
lemma: wordDetailsController.widget.token.lemma.text.isNotEmpty
|
||||
? wordDetailsController.widget.token.lemma.text
|
||||
: wordDetailsController.widget.token.lemma.form,
|
||||
pos: wordDetailsController.widget.token.pos,
|
||||
langCode: wordDetailsController
|
||||
.widget.messageEvent.messageDisplayLangCode,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
ActivityAnswerWidget(
|
||||
token: wordDetailsController.widget.token,
|
||||
selectionType: selectionType!,
|
||||
selectedMorphFeature: selectedMorphFeature,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,34 +1,30 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/pangea/controllers/message_analytics_controller.dart';
|
||||
import 'package:fluffychat/pangea/enum/activity_type_enum.dart';
|
||||
import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_message_event.dart';
|
||||
import 'package:fluffychat/pangea/models/pangea_token_model.dart';
|
||||
import 'package:fluffychat/pangea/utils/grammar/get_grammar_copy.dart';
|
||||
import 'package:fluffychat/pangea/widgets/chat/message_selection_overlay.dart';
|
||||
import 'package:fluffychat/pangea/widgets/chat/toolbar_content_loading_indicator.dart';
|
||||
import 'package:fluffychat/pangea/widgets/chat/tts_controller.dart';
|
||||
import 'package:fluffychat/pangea/widgets/practice_activity/emoji_practice_button.dart';
|
||||
import 'package:fluffychat/pangea/widgets/practice_activity/practice_activity_card.dart';
|
||||
import 'package:fluffychat/pangea/widgets/practice_activity/word_text_with_audio_button.dart';
|
||||
import 'package:fluffychat/pangea/widgets/word_zoom/contextual_translation_widget.dart';
|
||||
import 'package:fluffychat/pangea/widgets/word_zoom/lemma_widget.dart';
|
||||
import 'package:fluffychat/pangea/widgets/word_zoom/morphological_widget.dart';
|
||||
import 'package:fluffychat/pangea/widgets/word_zoom/word_zoom_center_widget.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
enum WordZoomSelection {
|
||||
translation,
|
||||
meaning,
|
||||
emoji,
|
||||
lemma,
|
||||
morph,
|
||||
}
|
||||
|
||||
extension on WordZoomSelection {
|
||||
extension WordZoomSelectionUtils on WordZoomSelection {
|
||||
ActivityTypeEnum get activityType {
|
||||
switch (this) {
|
||||
case WordZoomSelection.translation:
|
||||
case WordZoomSelection.meaning:
|
||||
return ActivityTypeEnum.wordMeaning;
|
||||
case WordZoomSelection.emoji:
|
||||
return ActivityTypeEnum.emoji;
|
||||
|
|
@ -117,7 +113,7 @@ class WordZoomWidgetState extends State<WordZoomWidget> {
|
|||
WordZoomSelection get _defaultSelectionType =>
|
||||
_shouldShowActivity(WordZoomSelection.lemma)
|
||||
? WordZoomSelection.lemma
|
||||
: WordZoomSelection.translation;
|
||||
: WordZoomSelection.meaning;
|
||||
|
||||
Future<void> _setSelectionType(
|
||||
WordZoomSelection type, {
|
||||
|
|
@ -167,7 +163,7 @@ class WordZoomWidgetState extends State<WordZoomWidget> {
|
|||
_lockActivity();
|
||||
Future.delayed(savorTheJoyDuration, () {
|
||||
if (_selectionType == WordZoomSelection.lemma) {
|
||||
_setSelectionType(WordZoomSelection.translation);
|
||||
_setSelectionType(WordZoomSelection.meaning);
|
||||
}
|
||||
_unlockActivity();
|
||||
});
|
||||
|
|
@ -186,7 +182,7 @@ class WordZoomWidgetState extends State<WordZoomWidget> {
|
|||
switch (selection) {
|
||||
case WordZoomSelection.lemma:
|
||||
return _canGenerateLemmaActivity;
|
||||
case WordZoomSelection.translation:
|
||||
case WordZoomSelection.meaning:
|
||||
case WordZoomSelection.morph:
|
||||
return widget.token.canGenerateDistractors(
|
||||
selection.activityType,
|
||||
|
|
@ -316,68 +312,8 @@ class ActivityAnswerWidget extends StatelessWidget {
|
|||
return token.getEmoji() != null
|
||||
? Text(token.getEmoji()!)
|
||||
: const Text("emoji is null");
|
||||
case WordZoomSelection.translation:
|
||||
case WordZoomSelection.meaning:
|
||||
return const SizedBox();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class WordZoomCenterWidget extends StatelessWidget {
|
||||
final WordZoomSelection? selectionType;
|
||||
final String? selectedMorphFeature;
|
||||
final bool shouldDoActivity;
|
||||
final bool locked;
|
||||
final WordZoomWidgetState wordDetailsController;
|
||||
|
||||
const WordZoomCenterWidget({
|
||||
required this.selectionType,
|
||||
required this.selectedMorphFeature,
|
||||
required this.shouldDoActivity,
|
||||
required this.locked,
|
||||
required this.wordDetailsController,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (selectionType == null) {
|
||||
return const ToolbarContentLoadingIndicator();
|
||||
}
|
||||
|
||||
if (shouldDoActivity || locked) {
|
||||
return PracticeActivityCard(
|
||||
pangeaMessageEvent: wordDetailsController.widget.messageEvent,
|
||||
targetTokensAndActivityType: TargetTokensAndActivityType(
|
||||
tokens: [wordDetailsController.widget.token],
|
||||
activityType: selectionType!.activityType,
|
||||
),
|
||||
overlayController: wordDetailsController.widget.overlayController,
|
||||
morphFeature: selectedMorphFeature,
|
||||
wordDetailsController: wordDetailsController,
|
||||
);
|
||||
}
|
||||
|
||||
if (selectionType == WordZoomSelection.translation) {
|
||||
return ContextualTranslationWidget(
|
||||
token: wordDetailsController.widget.token,
|
||||
langCode:
|
||||
wordDetailsController.widget.messageEvent.messageDisplayLangCode,
|
||||
);
|
||||
}
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
ActivityAnswerWidget(
|
||||
token: wordDetailsController.widget.token,
|
||||
selectionType: selectionType!,
|
||||
selectedMorphFeature: selectedMorphFeature,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
76
lib/utils/feedback_dialog.dart
Normal file
76
lib/utils/feedback_dialog.dart
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
|
||||
import '../pangea/widgets/common/bot_face_svg.dart';
|
||||
|
||||
Future<dynamic> showFeedbackDialog(
|
||||
BuildContext context,
|
||||
Widget offendingContent,
|
||||
void Function(String) submitFeedback,
|
||||
) {
|
||||
final TextEditingController feedbackController = TextEditingController();
|
||||
return showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text(
|
||||
L10n.of(context).reportContentIssueTitle,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
content: SingleChildScrollView(
|
||||
child: Container(
|
||||
constraints: const BoxConstraints(maxWidth: 300),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const BotFace(
|
||||
width: 60,
|
||||
expression: BotExpression.addled,
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Text(L10n.of(context).reportContentIssueDescription),
|
||||
const SizedBox(height: 10),
|
||||
Container(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(8.0),
|
||||
border: Border.all(
|
||||
color: AppConfig.warning,
|
||||
),
|
||||
),
|
||||
child: offendingContent,
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
TextField(
|
||||
controller: feedbackController,
|
||||
decoration: InputDecoration(
|
||||
labelText: L10n.of(context).feedback,
|
||||
border: const OutlineInputBorder(),
|
||||
),
|
||||
maxLines: 4,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop(); // Close the dialog
|
||||
},
|
||||
child: Text(L10n.of(context).cancel),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
// Call the additional callback function
|
||||
submitFeedback(feedbackController.text);
|
||||
Navigator.of(context).pop(); // Close the dialog
|
||||
},
|
||||
child: Text(L10n.of(context).submit),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue