feat: widget for customizing SVG colors (#1498)

* feat: widget for customizing SVG colors

* feat: replace morph icons with customized morph SVGs
pull/1593/head
ggurdin 10 months ago committed by GitHub
parent fd3f851995
commit 3d85d2ec9f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,184 @@
import 'package:flutter/material.dart';
import 'package:fluffychat/pangea/common/utils/error_handler.dart';
String? getMorphSvgLink({
required String morphFeature,
String? morphTag,
required BuildContext context,
}) {
const baseURL =
"https://pangea-chat-client-assets.s3.us-east-1.amazonaws.com";
if (morphTag == null) {
final key = morphFeature.toLowerCase();
String? filename;
switch (key) {
case "advtype":
filename = "AdverbType.svg";
case "aspect":
filename = "Aspect.svg";
case "conjtype":
filename = "ConjunctionType.svg";
case "definite":
filename = "Definite.svg";
case "degree":
filename = "Degree.svg";
case "mood":
filename = "Mood.svg";
case "number":
filename = "Number.svg";
case "pos":
filename = "PartOfSpeech.svg";
case "person":
filename = "Person.svg";
case "polarity":
filename = "Polarity.svg";
case "prontype":
filename = "PronounType.svg";
case "verbform":
"VerbForm.svg";
case "voice":
filename = "Voice.svg";
}
if (filename == null) {
ErrorHandler.logError(
e: "Missing morphFeature in getMorphSvgLink",
data: {"morphFeature": morphFeature},
);
debugPrint("Missing morphFeature in getMorphSvgLink: $morphFeature");
return null;
}
return "$baseURL/$filename";
}
final key = "${morphFeature.toLowerCase()}${morphTag.toLowerCase()}";
String? filename;
switch (key) {
case "advtypeadverbial":
filename = "AdverbType_Adverbial.svg";
case "advtypetim":
filename = "AdverbType_TemporalAdverb.svg";
case "aspecthab":
filename = "Aspect_Habitual.svg";
case "aspectimp":
filename = "Aspect_Imperfective.svg";
case "aspectperf":
filename = "Aspect_Perfective.svg";
case "aspectprog":
filename = "Aspect_Progressive.svg";
case "conjtypecoord":
filename = "ConjunctionType_Coordinating.svg";
case "conjtypesub":
filename = "ConjunctionType_Subordinating.svg";
case "definitedef":
filename = "Definite_Definite.svg";
case "definiteind":
filename = "Definite_Indefinite.svg";
case "degreecmp":
filename = "Degree_Comparative.svg";
case "degreepos":
filename = "Degree_Positive.svg";
case "degreesup":
filename = "Degree_Superlative.svg";
case "moodcnd":
filename = "Mood_Conditional.svg";
case "moodimp":
filename = "Mood_Imperative.svg";
case "moodind":
filename = "Mood_Indicative.svg";
case "moodopt":
filename = "Mood_Optative.svg";
case "moodsub":
filename = "Mood_Subjunctive.svg";
case "numberplur":
filename = "Number_Plural.svg";
case "numbersing":
filename = "Number_Singular.svg";
case "posadv":
filename = "PartOfSpeech_Adverb.svg";
case "posadj":
filename = "PartOfSpeech_Adjective.svg";
case "posadp":
filename = "PartOfSpeech_Adposition.svg";
case "posaux":
filename = "PartOfSpeech_Auxiliary.svg";
case "posconj":
filename = "PartOfSpeech_Conjunction.svg";
case "posdet":
filename = "PartOfSpeech_Determiner.svg";
case "posnoun":
filename = "PartOfSpeech_Noun.svg";
case "posnum":
filename = "PartOfSpeech_Numeral.svg";
case "pospron":
filename = "PartOfSpeech_Pronoun.svg";
case "pospunct":
filename = "PartOfSpeech_Punctuation.svg";
case "possconj":
filename = "PartOfSpeech_Subconjunction.svg";
case "posverb":
filename = "PartOfSpeech_Verb.svg";
case "person1":
filename = "Person_FirstPerson.svg";
case "person2":
filename = "Person_SecondPerson.svg";
case "person3":
filename = "Person_ThirdPerson.svg";
case "polarityneg":
filename = "Polarity_Negative.svg";
case "polaritypos":
filename = "Polarity_Positive.svg";
case "prontypedem":
filename = "PronounType_Demonstrative.svg";
case "prontypeind":
filename = "PronounType_Indefinite.svg";
case "prontypeint":
filename = "PronounType_Interrogative.svg";
case "prontypeneg":
filename = "PronounType_Negative.svg";
case "prontypeprs":
filename = "PronounType_Personal.svg";
case "prontyperel":
filename = "PronounType_Relative.svg";
case "prontypetot":
filename = "PronounType_Total.svg";
case "tensefut":
filename = "Tense_future.svg";
case "tenseimp":
filename = "Tense_imperfect.svg";
case "tensepast":
filename = "Tense_past.svg";
case "tensepres":
filename = "Tense_present.svg";
case "verbformfin":
filename = "VerbForm_Finite.svg";
case "verbformger":
filename = "VerbForm_Gerund.svg";
case "verbforminf":
filename = "VerbForm_Infinitive.svg";
case "verbformpart":
filename = "VerbForm_Participle.svg";
case "voiceact":
filename = "Voice_Active.svg";
case "voicemid":
filename = "Voice_Middle.svg";
case "voicepass":
filename = "Voice_Passive.svg";
}
if (filename == null) {
ErrorHandler.logError(
e: "Missing morphFeature and morphTag in getMorphSvgLink",
data: {"morphFeature": morphFeature, "morphTag": morphTag},
);
debugPrint(
"Missing morphFeature and morphTag in getMorphSvgLink: $morphFeature, $morphTag",
);
return null;
}
return "$baseURL/$filename";
}

@ -0,0 +1,67 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:get_storage/get_storage.dart';
import 'package:http/http.dart' as http;
class CustomizedSvg extends StatelessWidget {
final String svgUrl;
final String cacheKey;
final Map<String, String> colorReplacements;
final IconData? errorIcon;
const CustomizedSvg({
super.key,
required this.svgUrl,
required this.cacheKey,
required this.colorReplacements,
this.errorIcon = Icons.error_outline,
});
static final GetStorage _svgStorage = GetStorage('svg_cache');
Future<String> _fetchSvg() async {
final cachedSvg = _svgStorage.read(cacheKey);
if (cachedSvg != null) {
return cachedSvg;
}
final response = await http.get(Uri.parse(svgUrl));
if (response.statusCode != 200) {
throw Exception('Failed to load SVG: ${response.statusCode}');
}
final String svgContent = response.body;
await _svgStorage.write(cacheKey, svgContent);
return svgContent;
}
Future<String> _getModifiedSvg() async {
final svgContent = await _fetchSvg();
String modifiedSvg = svgContent;
modifiedSvg = modifiedSvg.replaceAll("fill=\"none\"", '');
for (final entry in colorReplacements.entries) {
modifiedSvg = modifiedSvg.replaceAll(entry.key, entry.value);
}
return modifiedSvg;
}
@override
Widget build(BuildContext context) {
return FutureBuilder<String>(
future: _getModifiedSvg(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const CircularProgressIndicator();
} else if (snapshot.hasError) {
return Icon(errorIcon);
} else if (snapshot.hasData) {
return SvgPicture.string(snapshot.data!);
} else {
return const SizedBox.shrink();
}
},
);
}
}

@ -1,20 +1,27 @@
import 'package:flutter/material.dart';
import 'package:fluffychat/pangea/analytics/enums/morph_categories_enum.dart';
import 'package:fluffychat/pangea/analytics/utils/get_grammar_copy.dart';
import 'package:fluffychat/pangea/common/widgets/customized_svg.dart';
import 'package:fluffychat/pangea/toolbar/widgets/practice_activity/word_zoom_activity_button.dart';
import 'package:fluffychat/utils/color_value.dart';
class MorphologicalListItem extends StatelessWidget {
final Function(String) onPressed;
final String morphCategory;
final String morphFeature;
final String morphTag;
final IconData icon;
final String? svgLink;
final bool isUnlocked;
final bool isSelected;
const MorphologicalListItem({
required this.onPressed,
required this.morphCategory,
required this.morphFeature,
required this.morphTag,
required this.icon,
this.svgLink,
this.isUnlocked = true,
this.isSelected = false,
super.key,
@ -22,15 +29,37 @@ class MorphologicalListItem extends StatelessWidget {
@override
Widget build(BuildContext context) {
return WordZoomActivityButton(
icon: Icon(icon),
isSelected: isSelected,
onPressed: () => onPressed(morphCategory),
tooltip: getMorphologicalCategoryCopy(
morphCategory,
context,
return SizedBox(
width: 40,
height: 40,
child: WordZoomActivityButton(
icon: svgLink != null
? CustomizedSvg(
svgUrl: svgLink!,
cacheKey: svgLink!,
colorReplacements: {
"white": Theme.of(context).cardColor.hexValue.toString(),
"black": Theme.of(context).brightness == Brightness.dark
? "white"
: "black",
},
errorIcon: icon,
)
: Icon(icon),
isSelected: isSelected,
onPressed: () => onPressed(morphFeature),
tooltip: isUnlocked
? getGrammarCopy(
category: morphFeature,
lemma: morphTag,
context: context,
)
: getMorphologicalCategoryCopy(
morphFeature,
context,
),
opacity: (isSelected || !isUnlocked) ? 1 : 0.5,
),
opacity: (isSelected || !isUnlocked) ? 1 : 0.5,
);
}
}

@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:fluffychat/pangea/analytics/constants/morph_categories_and_labels.dart';
import 'package:fluffychat/pangea/analytics/utils/get_svg_link.dart';
import 'package:fluffychat/pangea/events/models/pangea_token_model.dart';
import 'package:fluffychat/pangea/toolbar/widgets/word_zoom/morphs/morphological_list_item.dart';
@ -73,8 +74,14 @@ class MorphologicalListWidget extends StatelessWidget {
padding: const EdgeInsets.all(2.0),
child: MorphologicalListItem(
onPressed: setMorphFeature,
morphCategory: morph.morphFeature,
morphFeature: morph.morphFeature,
morphTag: morph.morphTag,
icon: getIconForMorphFeature(morph.morphFeature),
svgLink: getMorphSvgLink(
morphFeature: morph.morphFeature,
morphTag: morph.revealed ? morph.morphTag : null,
context: context,
),
isUnlocked: morph.revealed,
isSelected: selectedMorphFeature == morph.morphFeature,
),

Loading…
Cancel
Save