@page "/Learn" @using System.Text; @using CCharLearn.ExtensionMethods; @inject NavigationManager navigator @inject Blazored.LocalStorage.ILocalStorageService localStorage @inject HttpClient httpClient @inject ISnackbar Snackbar @inject SpeechSynthesis SpeechSynthesis @if (!Answers.Any(x => x == null)) { @*Color="@(selectedCorrect ? Color.Success : Color.Error)"*@ Avoid } @if (!Answers.Any(x => x == null)) {

@GetDisplayChar()

}
@if (!Answers.Any(x=>x == null)) { @for (int i = 0; i < Answers.Length; i++) { int buttonIndex = i; @Answers[buttonIndex].cchar.pinyin.ToTitleCase() } } Meaning Speech @if (!Answers.Any(x => x == null)) { Submit }
@code { bool isSavedLocally = false; bool selectedCorrect = false; public Answer[] Answers = new Answer[4]; public void SelectButton(int selectedIndex) { for (int i = 0; i < Answers.Length; i++) { Answers[i].isSelected = (i == selectedIndex); } selectedCorrect = Answers[selectedIndex].isCorrect; } private CChar[]? _charecters; public CChar[]? Charecters { get { if (_charecters == null) throw new Exception("Loaded dataset empty?"); return _charecters; } set { _charecters = value; } } public List? DontSkipTheseCChar; private async Task GetFileContentsAsync(string filePath) { var fileResponse = await httpClient.GetByteArrayAsync(filePath); return Encoding.UTF8.GetString(fileResponse); } protected override async Task OnInitializedAsync() { Program.UpdateUiEvent += OnUiUpdate; isSavedLocally = await localStorage.ContainKeyAsync("Normalized_chunk_001.json"); int selectedChunk = await localStorage.GetItemAsync("SelectedChunk"); if (!isSavedLocally) Charecters = await httpClient.GetFromJsonAsync($"Data/Normalized_chunk_{selectedChunk.ToString("000")}.json"); else { string json = await localStorage.GetItemAsync($"Normalized_chunk_{selectedChunk.ToString("000")}.json"); Charecters = JsonConvert.DeserializeObject(json); } DontSkipTheseCChar = Charecters.Select(x=>new CCharStats(x)).ToList(); Program.CCharsLeft = DontSkipTheseCChar.Count; Program.InvokeUiUpdate(); GenerateQuestion(); } void OnUiUpdate() => StateHasChanged(); void GenerateQuestion() { if (DontSkipTheseCChar.Count < 5) { FinishAndKickBackToLearn(); return; } int correctIndex = Random.Shared.Next(0, Answers.Length); for (int i = 0; i < Answers.Length; i++) { bool isCorrect = i == correctIndex; CChar randomCChar; repickRandomCChar: randomCChar = DontSkipTheseCChar[Random.Shared.Next(0, DontSkipTheseCChar.Count)].cchar; if (Answers.Any(x =>x != null && x.cchar == randomCChar)) goto repickRandomCChar; Answers[i] = new Answer(randomCChar, isCorrect); } string correctPinyin = GetCorrectCChar().pinyin; for (int i = 0; i < Answers.Length; i++) { if (Answers[i].isCorrect) continue; if (Answers[i].cchar.pinyin != correctPinyin) continue; Answers[i].cchar.pinyin += "_"; } } void FinishAndKickBackToLearn() { Snackbar.Add("Congrats, you have compleated this chunk!", Severity.Success, config => { config.RequireInteraction = true; config.CloseAfterNavigation = false; }); Program.UpdateUiEvent -= OnUiUpdate; Program.CCharsLeft = 0; navigator.NavigateTo(""); } async void Submit() { bool isCorrect = Answers.Any(x => x.isCorrect && x.isSelected); CCharStats correctCCharStats = GetCorrectCCharStats(); CChar correctCChar = correctCCharStats.cchar; if (isCorrect) { Snackbar.Add($"Definition: {correctCChar.definition}", Severity.Success, config => { config.VisibleStateDuration = 1000; }); increaseCCharStat(GetCorrectCCharStats(), StatType.NumOfCorrects); } else { Snackbar.Add( @

Incorrect!

  • CChar: @correctCChar.charcter
  • Correct answer: @correctCChar.pinyin.ToTitleCase()
  • Meaning: @correctCChar.definition
, Severity.Error); increaseCCharStat(GetCorrectCCharStats(), StatType.NumOfWrongs); } if (correctCCharStats.TotalAnswers >= 3) { if (correctCCharStats.Accuracy > 0.7f) { RemoveCCharFromSelection(true); Snackbar.Add($"CChar '{correctCChar.charcter}' compleated!", Severity.Success); } } GenerateQuestion(); } public class Answer { public Answer() { } public Answer(CChar cchar, bool isCorrect) { this.cchar = cchar; this.isCorrect = isCorrect; } public CChar cchar { get; set; } public bool isCorrect { get; set; } = false; public bool isSelected { get; set; } = false; } public char GetDisplayChar() { char? cc = GetCorrectCChar().charcter; if (cc == null) return ' '; else return (char)cc; } public CChar GetCorrectCChar() { CChar cchar = Answers.FirstOrDefault(x => x.isCorrect)?.cchar; return cchar; } public CCharStats GetCorrectCCharStats() { CChar correctChar = GetCorrectCChar(); CCharStats correctCCharStats = DontSkipTheseCChar.First(x => x.cchar == correctChar); return correctCCharStats; } public void increaseCCharStat(CCharStats stats, StatType statType) { int correctStatsIndex = DontSkipTheseCChar.IndexOf(stats); switch (statType) { case StatType.NumOfCorrects: DontSkipTheseCChar[correctStatsIndex].NumOfCorrects++; break; case StatType.NumOfWrongs: DontSkipTheseCChar[correctStatsIndex].NumOfWrongs++; break; } } public void RemoveCCharFromSelection(bool ignoreSelection = false) { CCharStats correctCCharStats = GetCorrectCCharStats(); if (!selectedCorrect && !ignoreSelection) { Snackbar.Add("Selected is wrong. Try again!", Severity.Error); return; } else if (!ignoreSelection)// Hacky way to only display this snackbar when is manually removed { Snackbar.Add($"Removed '{correctCCharStats.cchar.charcter}' from selection", Severity.Info, config => config.VisibleStateDuration = 3000); } DontSkipTheseCChar.Remove(correctCCharStats); Console.WriteLine("Remaining CChars: " + DontSkipTheseCChar); Program.CCharsLeft = DontSkipTheseCChar.Count; Program.InvokeUiUpdate(); GenerateQuestion(); } public void ShowMeaning() { Snackbar.Add($"Definition: {GetCorrectCChar().definition}", Severity.Info); } public void ShowPinyin() { Snackbar.Add($"Pinyin: {GetCorrectCChar().pinyin.ToTitleCase()}", Severity.Info); } SpeechSynthesisVoice? SpeechVoice; protected async override Task OnAfterRenderAsync(bool firstRender) { if (firstRender) { // Gets chinese voice on windows or iphone. this.SpeechVoice = ((IEnumerable)(await this.SpeechSynthesis.GetVoicesAsync())).FirstOrDefault(v => v.Name.Contains("Yaoyao") || v.Name.Contains("Ting-Ting")); this.StateHasChanged(); } } async Task SayCorrectChar() { if (SpeechVoice == null) { Snackbar.Add("Couldn't play sound", Severity.Error); return; } var utterancet = new SpeechSynthesisUtterance { Text = this.GetCorrectCChar().charcter.ToString(), Voice = this.SpeechVoice, Rate = 0.75f }; await this.SpeechSynthesis.SpeakAsync(utterancet); // 👈 Speak with "Haruka"'s voice! } }