Merge branch 'main' into 737-private-class-code

This commit is contained in:
ggurdin 2024-10-16 09:13:59 -04:00 committed by GitHub
commit 2900634900
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 252 additions and 128 deletions

View file

@ -4234,5 +4234,9 @@
"reportContentIssueTitle": "Report content issue",
"feedback": "Optional feedback",
"reportContentIssueDescription": "Uh oh! AI can faciliate personalized learning experiences but... also hallucinates. Please provide any feedback you have and we'll try again.",
"clickTheWordAgainToDeselect": "Click the selected word to deselect it."
"clickTheWordAgainToDeselect": "Click the selected word to deselect it.",
"l2SupportNa": "Not Available",
"l2SupportAlpha": "Alpha",
"l2SupportBeta": "Beta",
"l2SupportFull": "Full"
}

View file

@ -470,6 +470,14 @@ class ChatController extends State<ChatPageWithRoom>
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
// #Pangea
// On iOS, if the toolbar is open and the app is closed, then the user goes
// back to do more toolbar activities, the toolbar buttons / selection don't
// update properly. So, when the user closes the app, close the toolbar overlay.
if (state == AppLifecycleState.paused) {
clearSelectedEvents();
}
// Pangea#
if (state != AppLifecycleState.resumed) return;
setReadMarker();
}

View file

@ -105,6 +105,7 @@ class IgcController {
return;
}
choreographer.chatController.inputFocus.unfocus();
OverlayUtil.showPositionedCard(
context: context,
cardToShow: SpanCard(
@ -124,7 +125,7 @@ class IgcController {
),
roomId: choreographer.roomId,
),
cardSize: match.isITStart ? const Size(350, 260) : const Size(400, 400),
cardSize: match.isITStart ? const Size(350, 260) : const Size(350, 350),
transformTargetId: choreographer.inputTransformTargetKey,
);
}

View file

@ -308,6 +308,8 @@ class ITChoices extends StatelessWidget {
);
return;
}
controller.choreographer.chatController.inputFocus.unfocus();
OverlayUtil.showPositionedCard(
context: context,
cardToShow: choiceFeedback == null

View file

@ -27,7 +27,9 @@ class PangeaLanguage {
static Future<void> initialize() async {
try {
_langList = await _getCachedFlags();
if (await _shouldFetch || _langList.isEmpty) {
if (await _shouldFetch ||
_langList.isEmpty ||
_langList.every((lang) => !lang.l2)) {
_langList = await LanguageRepo.fetchLanguages();
await _saveFlags(_langList);

View file

@ -0,0 +1,98 @@
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
enum L2SupportEnum {
na,
alpha,
beta,
full,
}
extension L2SupportEnumExtension on L2SupportEnum {
String get storageString {
switch (this) {
case L2SupportEnum.na:
return 'na';
case L2SupportEnum.alpha:
return 'alpha';
case L2SupportEnum.beta:
return 'beta';
case L2SupportEnum.full:
return 'full';
}
}
L2SupportEnum fromStorageString(String storageString) {
switch (storageString) {
case 'na':
case 'L2SupportEnum.na':
return L2SupportEnum.na;
case 'alpha':
case 'L2SupportEnum.alpha':
return L2SupportEnum.alpha;
case 'beta':
case 'L2SupportEnum.beta':
return L2SupportEnum.beta;
case 'full':
case 'L2SupportEnum.full':
return L2SupportEnum.full;
default:
throw Exception('Unknown L2SupportEnum storage string: $storageString');
}
}
String toLocalizedString(BuildContext context) {
final l10n = L10n.of(context)!;
switch (this) {
case L2SupportEnum.na:
return l10n.l2SupportNa;
case L2SupportEnum.alpha:
return l10n.l2SupportAlpha;
case L2SupportEnum.beta:
return l10n.l2SupportBeta;
case L2SupportEnum.full:
return l10n.l2SupportFull;
}
}
Badge toBadge(BuildContext context) {
final theme = Theme.of(context);
Color color;
String label;
switch (this) {
case L2SupportEnum.na:
color = theme.colorScheme.onSurface.withOpacity(0.4); // Muted grey
label = toLocalizedString(context);
break;
case L2SupportEnum.alpha:
color = theme.colorScheme.primary.withOpacity(0.4); // Subtle primary
label = toLocalizedString(context);
break;
case L2SupportEnum.beta:
color =
theme.colorScheme.secondary.withOpacity(0.4); // Subtle secondary
label = toLocalizedString(context);
break;
case L2SupportEnum.full:
color = theme.colorScheme.tertiary.withOpacity(0.4); // Subtle tertiary
label = toLocalizedString(context);
break;
}
return Badge(
label: Text(
label,
style: theme.textTheme.bodySmall?.copyWith(
color: theme.colorScheme.onSurface.withOpacity(0.8), // Dimmed text
fontWeight: FontWeight.w500,
),
),
backgroundColor: color,
alignment: Alignment.center,
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
smallSize: 20, // A smaller badge for subtlety
);
}
}

View file

@ -1,6 +1,7 @@
import 'dart:developer';
import 'package:fluffychat/pangea/constants/language_constants.dart';
import 'package:fluffychat/pangea/enum/l2_support_enum.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
@ -12,15 +13,15 @@ class LanguageModel {
final String languageFlag;
final String displayName;
final String? languageEmoji;
final bool l2;
final bool l1;
final L2SupportEnum l2Support;
LanguageModel({
required this.langCode,
required this.languageFlag,
required this.displayName,
required this.l2,
required this.l1,
this.l2Support = L2SupportEnum.na,
this.languageEmoji,
});
@ -37,9 +38,11 @@ class LanguageModel {
displayName: _LanguageLocal.getDisplayName(
code != LanguageKeys.unknownLanguage ? code : json['language_name'],
),
l2: json["l2"] ?? code.contains("es") || code.contains("en"),
l1: json["l1"] ?? !code.contains("es") && !code.contains("en"),
languageEmoji: json['language_emoji'],
l2Support: json['l2_support'] != null
? L2SupportEnum.na.fromStorageString(json['l2_support'])
: L2SupportEnum.na,
);
}
@ -47,11 +50,13 @@ class LanguageModel {
'language_code': langCode,
'language_name': displayName,
'language_flag': languageFlag,
'l2': l2,
'l1': l1,
'language_emoji': languageEmoji,
'l2_support': l2Support.storageString,
};
bool get l2 => l2Support != L2SupportEnum.na;
// Discuss with Jordan - adding langCode field to language objects as separate from displayName
static String codeFromNameOrCode(String codeOrName, [String? url]) {
if (codeOrName.isEmpty) return LanguageKeys.unknownLanguage;
@ -73,7 +78,6 @@ class LanguageModel {
langCode: LanguageKeys.unknownLanguage,
languageFlag: "",
displayName: "Unknown",
l2: false,
l1: false,
);
@ -81,16 +85,12 @@ class LanguageModel {
displayName: context != null
? L10n.of(context)!.multiLingualSpace
: "Multilingual Space",
l2: false,
l1: false,
langCode: LanguageKeys.multiLanguage,
languageFlag: 'assets/colors.png',
languageEmoji: "🌎",
);
// Discuss with Jordan
bool get hasContextualDefinitionSupport => l2;
String? getDisplayName(BuildContext context) {
switch (langCode) {
case 'ab':

View file

@ -234,6 +234,7 @@ class LanguageSelectionRow extends StatelessWidget {
targetLanguage: isSource ? null : language,
);
},
isL2List: !isSource,
initialLanguage: isSource
? controller.sourceLanguageSearch
: controller.targetLanguageSearch,

View file

@ -1,8 +1,6 @@
import 'dart:developer';
import 'dart:math';
import 'dart:ui';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/pangea/utils/any_state_holder.dart';
import 'package:fluffychat/pangea/widgets/common_widgets/overlay_container.dart';
import 'package:flutter/foundation.dart';
@ -22,7 +20,6 @@ class OverlayUtil {
required BuildContext context,
required Widget child,
required String transformTargetId,
// Size? size,
double? width,
double? height,
Offset? offset,
@ -63,13 +60,13 @@ class OverlayUtil {
child: (position != OverlayPositionEnum.transform)
? child
: CompositedTransformFollower(
targetAnchor: targetAnchor ?? Alignment.topLeft,
followerAnchor: followerAnchor ?? Alignment.topLeft,
targetAnchor: targetAnchor ?? Alignment.topCenter,
followerAnchor:
followerAnchor ?? Alignment.bottomCenter,
link: MatrixState.pAnyState
.layerLinkAndKey(transformTargetId)
.link,
showWhenUnlinked: false,
offset: offset ?? Offset.zero,
child: child,
),
),
@ -103,11 +100,6 @@ class OverlayUtil {
return;
}
final Offset cardOffset = _calculateCardOffset(
cardSize: cardSize,
transformTargetContext: layerLinkAndKey.key.currentContext!,
);
final Widget child = Material(
borderOnForeground: false,
color: Colors.transparent,
@ -124,7 +116,6 @@ class OverlayUtil {
width: cardSize.width,
height: cardSize.height,
transformTargetId: transformTargetId,
offset: cardOffset,
backDropToDismiss: backDropToDismiss,
borderColor: borderColor,
closePrevOverlay: closePrevOverlay,
@ -135,73 +126,73 @@ class OverlayUtil {
}
}
/// calculates the card offset relative to the target
/// identified by [transformTargetKey]
static Offset _calculateCardOffset({
required Size cardSize,
required BuildContext transformTargetContext,
final double minPadding = 10.0,
}) {
// debugger(when: kDebugMode);
//Note: assumes overlay in chatview
final OverlayConstraints constraints =
ChatViewConstraints(transformTargetContext);
// /// calculates the card offset relative to the target
// /// identified by [transformTargetKey]
// static Offset _calculateCardOffset({
// required Size cardSize,
// required BuildContext transformTargetContext,
// final double minPadding = 10.0,
// }) {
// // debugger(when: kDebugMode);
// //Note: assumes overlay in chatview
// final OverlayConstraints constraints =
// ChatViewConstraints(transformTargetContext);
final RenderObject? targetRenderBox =
transformTargetContext.findRenderObject();
if (targetRenderBox == null) return Offset.zero;
final Offset transformTargetOffset =
(targetRenderBox as RenderBox).localToGlobal(Offset.zero);
final Size transformTargetSize = targetRenderBox.size;
// final RenderObject? targetRenderBox =
// transformTargetContext.findRenderObject();
// if (targetRenderBox == null) return Offset.zero;
// final Offset transformTargetOffset =
// (targetRenderBox as RenderBox).localToGlobal(Offset.zero);
// final Size transformTargetSize = targetRenderBox.size;
// ideally horizontally centered on target
double dx = transformTargetSize.width / 2 - cardSize.width / 2;
// make sure it's not off the left edge of the screen
// if transformTargetOffset.dx + dc < constraints.x0 + minPadding
// // ideally horizontally centered on target
// double dx = transformTargetSize.width / 2 - cardSize.width / 2;
// // make sure it's not off the left edge of the screen
// // if transformTargetOffset.dx + dc < constraints.x0 + minPadding
if (transformTargetOffset.dx + dx < minPadding + constraints.x0) {
debugPrint("setting dx");
dx = minPadding + constraints.x0 - transformTargetOffset.dx;
}
// make sure it's not off the right edge of the screen
if (transformTargetOffset.dx + dx + cardSize.width + minPadding >
constraints.x1) {
dx = constraints.x1 -
transformTargetOffset.dx -
cardSize.width -
minPadding;
}
// if (transformTargetOffset.dx + dx < minPadding + constraints.x0) {
// debugPrint("setting dx");
// dx = minPadding + constraints.x0 - transformTargetOffset.dx;
// }
// // make sure it's not off the right edge of the screen
// if (transformTargetOffset.dx + dx + cardSize.width + minPadding >
// constraints.x1) {
// dx = constraints.x1 -
// transformTargetOffset.dx -
// cardSize.width -
// minPadding;
// }
// if there's more room above target,
// put the card there
// else,
// put it below
// debugPrint(
// "transformTargetOffset.dx ${transformTargetOffset.dx} transformTargetOffset.dy ${transformTargetOffset.dy}");
// debugPrint(
// "transformTargetSize.width ${transformTargetSize.width} transformTargetSize.height ${transformTargetSize.height}");
double dy = transformTargetOffset.dy >
constraints.y1 -
transformTargetOffset.dy -
transformTargetSize.height
? -cardSize.height - minPadding
: transformTargetSize.height + minPadding;
// make sure it's not off the top edge of the screen
if (dy < minPadding + constraints.y0 - transformTargetOffset.dy) {
dy = minPadding + constraints.y0 - transformTargetOffset.dy;
}
// make sure it's not off the bottom edge of the screen
if (transformTargetOffset.dy + dy + cardSize.height + minPadding >
constraints.y1) {
dy = constraints.y1 -
transformTargetOffset.dy -
cardSize.height -
minPadding;
}
// debugPrint("dx $dx dy $dy");
// // if there's more room above target,
// // put the card there
// // else,
// // put it below
// // debugPrint(
// // "transformTargetOffset.dx ${transformTargetOffset.dx} transformTargetOffset.dy ${transformTargetOffset.dy}");
// // debugPrint(
// // "transformTargetSize.width ${transformTargetSize.width} transformTargetSize.height ${transformTargetSize.height}");
// double dy = transformTargetOffset.dy >
// constraints.y1 -
// transformTargetOffset.dy -
// transformTargetSize.height
// ? -cardSize.height - minPadding
// : transformTargetSize.height + minPadding;
// // make sure it's not off the top edge of the screen
// if (dy < minPadding + constraints.y0 - transformTargetOffset.dy) {
// dy = minPadding + constraints.y0 - transformTargetOffset.dy;
// }
// // make sure it's not off the bottom edge of the screen
// if (transformTargetOffset.dy + dy + cardSize.height + minPadding >
// constraints.y1) {
// dy = constraints.y1 -
// transformTargetOffset.dy -
// cardSize.height -
// minPadding;
// }
// // debugPrint("dx $dx dy $dy");
return Offset(dx, dy);
}
// return Offset(dx, dy);
// }
static bool get isOverlayOpen => MatrixState.pAnyState.entries.isNotEmpty;
}
@ -250,48 +241,48 @@ class TransparentBackdrop extends StatelessWidget {
}
}
/// global coordinates that the overlay should stay inside
abstract class OverlayConstraints {
late double x0;
late double y0;
late double x1;
late double y1;
}
// /// global coordinates that the overlay should stay inside
// abstract class OverlayConstraints {
// late double x0;
// late double y0;
// late double x1;
// late double y1;
// }
class ChatViewConstraints implements OverlayConstraints {
@override
late double x0;
@override
late double y0;
@override
late double x1;
@override
late double y1;
// class ChatViewConstraints implements OverlayConstraints {
// @override
// late double x0;
// @override
// late double y0;
// @override
// late double x1;
// @override
// late double y1;
ChatViewConstraints(BuildContext context) {
final MediaQueryData mediaQueryData =
MediaQuery.of(Scaffold.of(context).context);
final bool isColumnMode = FluffyThemes.isColumnMode(context);
// ChatViewConstraints(BuildContext context) {
// final MediaQueryData mediaQueryData =
// MediaQuery.of(Scaffold.of(context).context);
// final bool isColumnMode = FluffyThemes.isColumnMode(context);
x0 = isColumnMode
? AppConfig.columnWidth + 70.0
: max(mediaQueryData.viewPadding.left, mediaQueryData.viewInsets.left);
y0 = max(mediaQueryData.viewPadding.top, mediaQueryData.viewInsets.top);
x1 = mediaQueryData.size.width -
max(mediaQueryData.viewPadding.right, mediaQueryData.viewInsets.right);
y1 = mediaQueryData.size.height -
max(
mediaQueryData.viewPadding.bottom,
mediaQueryData.viewInsets.bottom,
);
// x0 = isColumnMode
// ? AppConfig.columnWidth + 70.0
// : max(mediaQueryData.viewPadding.left, mediaQueryData.viewInsets.left);
// y0 = max(mediaQueryData.viewPadding.top, mediaQueryData.viewInsets.top);
// x1 = mediaQueryData.size.width -
// max(mediaQueryData.viewPadding.right, mediaQueryData.viewInsets.right);
// y1 = mediaQueryData.size.height -
// max(
// mediaQueryData.viewPadding.bottom,
// mediaQueryData.viewInsets.bottom,
// );
// https://medium.com/flutter-community/a-flutter-guide-to-visual-overlap-padding-viewpadding-and-viewinsets-a63e214be6e8
// debugPrint(
// "viewInsets ${mediaQueryData.viewInsets.left} ${mediaQueryData.viewInsets.top} ${mediaQueryData.viewInsets.right} ${mediaQueryData.viewInsets.bottom}");
// debugPrint(
// "padding ${mediaQueryData.padding.left} ${mediaQueryData.padding.top} ${mediaQueryData.padding.right} ${mediaQueryData.padding.bottom}");
// debugPrint(
// "viewPadding ${mediaQueryData.viewPadding.left} ${mediaQueryData.viewPadding.top} ${mediaQueryData.viewPadding.right} ${mediaQueryData.viewPadding.bottom}");
// debugPrint("chatViewConstraints x0: $x0 y0: $y0 x1: $x1 y1: $y1");
}
}
// // https://medium.com/flutter-community/a-flutter-guide-to-visual-overlap-padding-viewpadding-and-viewinsets-a63e214be6e8
// // debugPrint(
// // "viewInsets ${mediaQueryData.viewInsets.left} ${mediaQueryData.viewInsets.top} ${mediaQueryData.viewInsets.right} ${mediaQueryData.viewInsets.bottom}");
// // debugPrint(
// // "padding ${mediaQueryData.padding.left} ${mediaQueryData.padding.top} ${mediaQueryData.padding.right} ${mediaQueryData.padding.bottom}");
// // debugPrint(
// // "viewPadding ${mediaQueryData.viewPadding.left} ${mediaQueryData.viewPadding.top} ${mediaQueryData.viewPadding.right} ${mediaQueryData.viewPadding.bottom}");
// // debugPrint("chatViewConstraints x0: $x0 y0: $y0 x1: $x1 y1: $y1");
// }
// }

View file

@ -245,6 +245,7 @@ class WordMatchContent extends StatelessWidget {
controller: scrollController,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// const SizedBox(height: 10.0),
// if (matchCopy.description != null)

View file

@ -231,6 +231,10 @@ class MessagePracticeActivityCardState extends State<PracticeActivityCard> {
return;
}
// clear the current activity and record
currentActivity = null;
currentCompletionRecord = null;
_fetchNewActivity(
ActivityQualityFeedback(
feedbackText: feedback,

View file

@ -63,6 +63,7 @@ Future<void> pLanguageDialog(
setState(() => selectedSourceLanguage = p0),
initialLanguage: selectedSourceLanguage,
languages: pangeaController.pLanguageStore.baseOptions,
isL2List: false,
),
PQuestionContainer(
title: L10n.of(parentContext)!.whatLanguageYouWantToLearn,
@ -72,6 +73,7 @@ Future<void> pLanguageDialog(
setState(() => selectedTargetLanguage = p0),
initialLanguage: selectedTargetLanguage,
languages: pangeaController.pLanguageStore.targetOptions,
isL2List: true,
),
],
),

View file

@ -1,5 +1,6 @@
// Flutter imports:
import 'package:fluffychat/pangea/enum/l2_support_enum.dart';
import 'package:fluffychat/pangea/models/language_model.dart';
import 'package:flutter/material.dart';
@ -10,6 +11,7 @@ class PLanguageDropdown extends StatefulWidget {
final LanguageModel initialLanguage;
final Function(LanguageModel) onChange;
final bool showMultilingual;
final bool isL2List;
const PLanguageDropdown({
super.key,
@ -17,6 +19,7 @@ class PLanguageDropdown extends StatefulWidget {
required this.onChange,
required this.initialLanguage,
this.showMultilingual = false,
required this.isL2List,
});
@override
@ -98,6 +101,7 @@ class _PLanguageDropdownState extends State<PLanguageDropdown> {
value: LanguageModel.multiLingual(context),
child: LanguageDropDownEntry(
languageModel: LanguageModel.multiLingual(context),
isL2List: widget.isL2List,
),
),
...sortedLanguages.map(
@ -105,6 +109,7 @@ class _PLanguageDropdownState extends State<PLanguageDropdown> {
value: languageModel,
child: LanguageDropDownEntry(
languageModel: languageModel,
isL2List: widget.isL2List,
),
),
),
@ -118,9 +123,11 @@ class _PLanguageDropdownState extends State<PLanguageDropdown> {
class LanguageDropDownEntry extends StatelessWidget {
final LanguageModel languageModel;
final bool isL2List;
const LanguageDropDownEntry({
super.key,
required this.languageModel,
required this.isL2List,
});
@override
@ -144,6 +151,9 @@ class LanguageDropDownEntry extends StatelessWidget {
overflow: TextOverflow.clip,
textAlign: TextAlign.center,
),
const SizedBox(width: 10),
if (isL2List && languageModel.l2Support != L2SupportEnum.full)
languageModel.l2Support.toBadge(context),
],
),
);

View file

@ -6,7 +6,7 @@ description: Learn a language while texting your friends.
# Pangea#
publish_to: none
# On version bump also increase the build number for F-Droid
version: 1.21.4+3539
version: 1.21.4+3540
environment:
sdk: ">=3.0.0 <4.0.0"