Speech, auto avoid, bug fixes, iu improments
This commit is contained in:
parent
bc45154b70
commit
bb125e7c7d
|
@ -0,0 +1,28 @@
|
||||||
|
namespace LearningChineseFixed
|
||||||
|
{
|
||||||
|
public class CCharStats
|
||||||
|
{
|
||||||
|
public CChar cchar { get; set; }
|
||||||
|
public int NumOfCorrects { get; set; } = 0;
|
||||||
|
public int NumOfWrongs { get; set; } = 0;
|
||||||
|
public int TotalAnswers { get => NumOfCorrects + NumOfWrongs; }
|
||||||
|
public float Accuracy
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return (float)NumOfCorrects / (float)TotalAnswers;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public CCharStats(CChar cchar)
|
||||||
|
{
|
||||||
|
this.cchar = cchar;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum StatType
|
||||||
|
{
|
||||||
|
NumOfCorrects,
|
||||||
|
NumOfWrongs
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,6 +4,7 @@ using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
|
||||||
using MudBlazor;
|
using MudBlazor;
|
||||||
using Blazored.LocalStorage;
|
using Blazored.LocalStorage;
|
||||||
using MudBlazor.Services;
|
using MudBlazor.Services;
|
||||||
|
using Toolbelt.Blazor.Extensions.DependencyInjection;
|
||||||
|
|
||||||
namespace LearningChineseFixed
|
namespace LearningChineseFixed
|
||||||
{
|
{
|
||||||
|
@ -27,6 +28,7 @@ namespace LearningChineseFixed
|
||||||
config.SnackbarConfiguration.PositionClass = Defaults.Classes.Position.BottomCenter;
|
config.SnackbarConfiguration.PositionClass = Defaults.Classes.Position.BottomCenter;
|
||||||
});
|
});
|
||||||
builder.Services.AddBlazoredLocalStorage();
|
builder.Services.AddBlazoredLocalStorage();
|
||||||
|
builder.Services.AddSpeechSynthesis();
|
||||||
|
|
||||||
await builder.Build().RunAsync();
|
await builder.Build().RunAsync();
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
<PackageReference Include="Microsoft.NET.Build.Containers" Version="7.0.305" />
|
<PackageReference Include="Microsoft.NET.Build.Containers" Version="7.0.305" />
|
||||||
<PackageReference Include="MudBlazor" Version="6.5.0" />
|
<PackageReference Include="MudBlazor" Version="6.5.0" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||||
|
<PackageReference Include="Toolbelt.Blazor.SpeechSynthesis" Version="10.0.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
@inject NavigationManager navigator
|
@inject NavigationManager navigator
|
||||||
@inject HttpClient httpClient
|
@inject HttpClient httpClient
|
||||||
@inject Blazored.LocalStorage.ILocalStorageService localStorage
|
@inject Blazored.LocalStorage.ILocalStorageService localStorage
|
||||||
|
@inject SpeechSynthesis SpeechSynthesis
|
||||||
|
|
||||||
<PageTitle>Index</PageTitle>
|
<PageTitle>Index</PageTitle>
|
||||||
|
|
||||||
|
@ -105,17 +106,35 @@
|
||||||
Parallel.For(0, numOfChunks, async (i)=>
|
Parallel.For(0, numOfChunks, async (i)=>
|
||||||
{
|
{
|
||||||
await Task.Delay(10*i);
|
await Task.Delay(10*i);
|
||||||
jsonChunks[i] = await httpClient.GetStringAsync($"Data/Normalized_chunk_{i.ToString("000")}.json");
|
try
|
||||||
|
{
|
||||||
|
jsonChunks[i] = await httpClient.GetStringAsync($"Data/Normalized_chunk_{i.ToString("000")}.json");
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
await Task.Delay(100);
|
||||||
|
jsonChunks[i] = await httpClient.GetStringAsync($"Data/Normalized_chunk_{i.ToString("000")}.json");
|
||||||
|
}
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(jsonChunks[i])) return;
|
if (string.IsNullOrEmpty(jsonChunks[i])) throw new Exception("Couldn't load chunk: " + i);
|
||||||
|
|
||||||
await localStorage.SetItemAsync($"Normalized_chunk_{i.ToString("000")}.json", jsonChunks[i]);
|
try
|
||||||
savedChunks = i + 1;
|
{
|
||||||
|
await localStorage.SetItemAsync($"Normalized_chunk_{i.ToString("000")}.json", jsonChunks[i]);
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
await Task.Delay(100);
|
||||||
|
await localStorage.SetItemAsync($"Normalized_chunk_{i.ToString("000")}.json", jsonChunks[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!await localStorage.ContainKeyAsync($"Normalized_chunk_{i.ToString("000")}.json")) throw new Exception("Couldn't save chunk: " + i);
|
||||||
});
|
});
|
||||||
|
|
||||||
while (jsonChunks.Any(x => x == null))
|
while (jsonChunks.Any(x => x == null))
|
||||||
{
|
{
|
||||||
await Task.Delay(1);
|
await Task.Delay(1);
|
||||||
|
savedChunks = jsonChunks.Count(x=>x != null);
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
@inject Blazored.LocalStorage.ILocalStorageService localStorage
|
@inject Blazored.LocalStorage.ILocalStorageService localStorage
|
||||||
@inject HttpClient httpClient
|
@inject HttpClient httpClient
|
||||||
@inject ISnackbar Snackbar
|
@inject ISnackbar Snackbar
|
||||||
|
@inject SpeechSynthesis SpeechSynthesis
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.LargeCharecter{
|
.LargeCharecter{
|
||||||
|
@ -27,9 +27,13 @@
|
||||||
<MudContainer Class="justify-center d-flex ma-0" Style="height: 100%;">
|
<MudContainer Class="justify-center d-flex ma-0" Style="height: 100%;">
|
||||||
<MudStack Class="d-flex">
|
<MudStack Class="d-flex">
|
||||||
<MudStack Row=true Class="pt-2 d-flex justify-center">
|
<MudStack Row=true Class="pt-2 d-flex justify-center">
|
||||||
<MudButton Color="Color.Default" OnClick="RemoveCCharFromSelection" Variant="Variant.Outlined">Avoid</MudButton>
|
@if (!Answers.Any(x => x == null))
|
||||||
|
{
|
||||||
|
@*Color="@(selectedCorrect ? Color.Success : Color.Error)"*@
|
||||||
|
<MudButton Disabled="@(!(Answers.Any(x=>x.isSelected)))" OnClick="()=>RemoveCCharFromSelection()" Variant="Variant.Outlined">Avoid</MudButton>
|
||||||
|
}
|
||||||
<MudButton Color="Color.Default" Variant="Variant.Outlined" OnClick="ShowMeaning">Meaning</MudButton>
|
<MudButton Color="Color.Default" Variant="Variant.Outlined" OnClick="ShowMeaning">Meaning</MudButton>
|
||||||
<MudButton Color="Color.Default" Variant="Variant.Outlined" OnClick="ShowPinyin">Pinyin</MudButton>
|
<MudButton Color="Color.Default" Variant="Variant.Outlined" OnClick="SayCorrectChar">Speech</MudButton>
|
||||||
</MudStack>
|
</MudStack>
|
||||||
<MudContainer>
|
<MudContainer>
|
||||||
<MudPaper Class="pa-16 ma-2 rounded-xl mud-dark" Elevation="1">
|
<MudPaper Class="pa-16 ma-2 rounded-xl mud-dark" Elevation="1">
|
||||||
|
@ -71,13 +75,11 @@
|
||||||
</MudStack>
|
</MudStack>
|
||||||
</MudContainer>
|
</MudContainer>
|
||||||
|
|
||||||
<MudOverlay @bind-Visible="ShowOverlay" DarkBackground="true" AutoClose="true" />
|
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
public bool ShowOverlay = false;
|
|
||||||
|
|
||||||
bool isSavedLocally = false;
|
bool isSavedLocally = false;
|
||||||
|
|
||||||
|
bool selectedCorrect = false;
|
||||||
|
|
||||||
public Answer[] Answers = new Answer[4];
|
public Answer[] Answers = new Answer[4];
|
||||||
public void SelectButton(int selectedIndex)
|
public void SelectButton(int selectedIndex)
|
||||||
{
|
{
|
||||||
|
@ -85,6 +87,8 @@
|
||||||
{
|
{
|
||||||
Answers[i].isSelected = (i == selectedIndex);
|
Answers[i].isSelected = (i == selectedIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
selectedCorrect = Answers[selectedIndex].isCorrect;
|
||||||
}
|
}
|
||||||
|
|
||||||
private CChar[]? _charecters;
|
private CChar[]? _charecters;
|
||||||
|
@ -99,7 +103,8 @@
|
||||||
set { _charecters = value; }
|
set { _charecters = value; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<CChar>? DontSkipTheseCChar;
|
|
||||||
|
public List<CCharStats>? DontSkipTheseCChar;
|
||||||
|
|
||||||
private async Task<string> GetFileContentsAsync(string filePath)
|
private async Task<string> GetFileContentsAsync(string filePath)
|
||||||
{
|
{
|
||||||
|
@ -123,7 +128,7 @@
|
||||||
Charecters = JsonConvert.DeserializeObject<CChar[]>(json);
|
Charecters = JsonConvert.DeserializeObject<CChar[]>(json);
|
||||||
}
|
}
|
||||||
|
|
||||||
DontSkipTheseCChar = Charecters.ToList();
|
DontSkipTheseCChar = Charecters.Select(x=>new CCharStats(x)).ToList();
|
||||||
Program.CCharsLeft = DontSkipTheseCChar.Count;
|
Program.CCharsLeft = DontSkipTheseCChar.Count;
|
||||||
Program.InvokeUiUpdate();
|
Program.InvokeUiUpdate();
|
||||||
|
|
||||||
|
@ -148,11 +153,21 @@
|
||||||
CChar randomCChar;
|
CChar randomCChar;
|
||||||
|
|
||||||
repickRandomCChar:
|
repickRandomCChar:
|
||||||
randomCChar = DontSkipTheseCChar[Random.Shared.Next(0, DontSkipTheseCChar.Count)];
|
randomCChar = DontSkipTheseCChar[Random.Shared.Next(0, DontSkipTheseCChar.Count)].cchar;
|
||||||
if (Answers.Any(x =>x != null && x.cchar == randomCChar)) goto repickRandomCChar;
|
if (Answers.Any(x =>x != null && x.cchar == randomCChar)) goto repickRandomCChar;
|
||||||
|
|
||||||
Answers[i] = new Answer(randomCChar, isCorrect);
|
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()
|
void FinishAndKickBackToLearn()
|
||||||
|
@ -171,11 +186,13 @@
|
||||||
async void Submit()
|
async void Submit()
|
||||||
{
|
{
|
||||||
bool isCorrect = Answers.Any(x => x.isCorrect && x.isSelected);
|
bool isCorrect = Answers.Any(x => x.isCorrect && x.isSelected);
|
||||||
CChar correctCChar = GetCorrectCChar();
|
CCharStats correctCCharStats = GetCorrectCCharStats();
|
||||||
|
CChar correctCChar = correctCCharStats.cchar;
|
||||||
|
|
||||||
if (isCorrect)
|
if (isCorrect)
|
||||||
{
|
{
|
||||||
Snackbar.Add($"<b>Definition:</b> {correctCChar.definition}", Severity.Success, config => { config.VisibleStateDuration = 1000; });
|
Snackbar.Add($"<b>Definition:</b> {correctCChar.definition}", Severity.Success, config => { config.VisibleStateDuration = 1000; });
|
||||||
|
increaseCCharStat(GetCorrectCCharStats(), StatType.NumOfCorrects);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -187,8 +204,19 @@
|
||||||
<li>Correct answer: @correctCChar.pinyin.ToTitleCase()</li>
|
<li>Correct answer: @correctCChar.pinyin.ToTitleCase()</li>
|
||||||
<li>Meaning: @correctCChar.definition</li>
|
<li>Meaning: @correctCChar.definition</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
, Severity.Error);
|
, 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();
|
GenerateQuestion();
|
||||||
|
@ -224,10 +252,43 @@
|
||||||
return cchar;
|
return cchar;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RemoveCCharFromSelection()
|
public CCharStats GetCorrectCCharStats()
|
||||||
{
|
{
|
||||||
CChar correctChar = GetCorrectCChar();
|
CChar correctChar = GetCorrectCChar();
|
||||||
DontSkipTheseCChar.Remove(correctChar);
|
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);
|
Console.WriteLine("Remaining CChars: " + DontSkipTheseCChar);
|
||||||
Program.CCharsLeft = DontSkipTheseCChar.Count;
|
Program.CCharsLeft = DontSkipTheseCChar.Count;
|
||||||
Program.InvokeUiUpdate();
|
Program.InvokeUiUpdate();
|
||||||
|
@ -243,4 +304,34 @@
|
||||||
{
|
{
|
||||||
Snackbar.Add($"<b>Pinyin:</b> {GetCorrectCChar().pinyin.ToTitleCase()}", Severity.Info);
|
Snackbar.Add($"<b>Pinyin:</b> {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<SpeechSynthesisVoice>)(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!
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
},
|
},
|
||||||
"dotnetRunMessages": true,
|
"dotnetRunMessages": true,
|
||||||
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
|
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
|
||||||
"applicationUrl": "http://localhost:5198;https://0.0.0.0:420/"
|
"applicationUrl": "http://localhost:5198"
|
||||||
},
|
},
|
||||||
"IIS Express": {
|
"IIS Express": {
|
||||||
"commandName": "IISExpress",
|
"commandName": "IISExpress",
|
||||||
|
|
|
@ -10,4 +10,5 @@
|
||||||
@using LearningChineseFixed.Shared
|
@using LearningChineseFixed.Shared
|
||||||
@using MudBlazor
|
@using MudBlazor
|
||||||
@using Blazored.LocalStorage
|
@using Blazored.LocalStorage
|
||||||
@using Newtonsoft.Json
|
@using Newtonsoft.Json
|
||||||
|
@using Toolbelt.Blazor.SpeechSynthesis
|
|
@ -25,7 +25,7 @@
|
||||||
</div>
|
</div>
|
||||||
<script src="_framework/blazor.webassembly.js"></script>
|
<script src="_framework/blazor.webassembly.js"></script>
|
||||||
<script src="_content/MudBlazor/MudBlazor.min.js"></script>
|
<script src="_content/MudBlazor/MudBlazor.min.js"></script>
|
||||||
<script>navigator.serviceWorker.register('service-worker.js');</script>
|
<!--<script>navigator.serviceWorker.register('service-worker.js');</script>-->
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|
Loading…
Reference in New Issue