Skip to content

Commit 54ea537

Browse files
committed
Birthday picker mode #24
1 parent d496b84 commit 54ea537

5 files changed

Lines changed: 193 additions & 31 deletions

File tree

src/MiScaleExporter.MAUI/App.xaml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,15 @@
33
x:Class="MiScaleExporter.MAUI.App"
44
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
55
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
6-
xmlns:local="clr-namespace:MiScaleExporter.MAUI">
6+
xmlns:local="clr-namespace:MiScaleExporter.MAUI"
7+
xmlns:converters="clr-namespace:MiScaleExporter.MAUI.Converters">
78
<Application.Resources>
89
<ResourceDictionary>
910
<ResourceDictionary.MergedDictionaries>
1011
<ResourceDictionary Source="Resources/Styles/Colors.xaml" />
1112
<ResourceDictionary Source="Resources/Styles/Styles.xaml" />
1213
</ResourceDictionary.MergedDictionaries>
14+
<converters:InvertedBoolConverter x:Key="InvertedBoolConverter" />
1315
</ResourceDictionary>
1416
</Application.Resources>
1517
</Application>
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
using System.Globalization;
2+
3+
namespace MiScaleExporter.MAUI.Converters;
4+
5+
public class InvertedBoolConverter : IValueConverter
6+
{
7+
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
8+
{
9+
if (value is bool boolValue)
10+
{
11+
return !boolValue;
12+
}
13+
return value;
14+
}
15+
16+
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
17+
{
18+
if (value is bool boolValue)
19+
{
20+
return !boolValue;
21+
}
22+
return value;
23+
}
24+
}

src/MiScaleExporter.MAUI/Models/PreferencesKeys.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
public static class PreferencesKeys
44
{
55
public static string UserAge = "UserAge";
6+
public static string UserBirthDate = "UserBirthDate";
7+
public static string UseBirthDateMode = "UseBirthDateMode";
68
public static string UserHeight = "UserHeight";
79
public static string UserSex = "UserSex";
810
public static string MiScaleBluetoothAddress = "MiScaleBluetoothAddress";

src/MiScaleExporter.MAUI/ViewModels/SettingsViewModel.cs

Lines changed: 114 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ public class SettingsViewModel : BaseViewModel, ISettingsViewModel
1212
public SettingsViewModel()
1313
{
1414
this.Title = AppSnippets.Settings;
15+
// Initialize birthDate with a reasonable default to avoid binding issues
16+
this._birthDate = DateTime.Today.AddYears(-25);
1517
ResetCommand = new Command(() =>
1618
{
1719
Preferences.Remove(PreferencesKeys.ApiServerAddressOverride);
@@ -23,6 +25,11 @@ public SettingsViewModel()
2325
Preferences.Remove(PreferencesKeys.MuscleMassAsPercentage);
2426
Preferences.Remove(PreferencesKeys.DisplayWeightInLbs);
2527
Preferences.Remove(PreferencesKeys.UseChinaServer);
28+
Preferences.Remove(PreferencesKeys.UserAge);
29+
Preferences.Remove(PreferencesKeys.UserBirthDate);
30+
Preferences.Remove(PreferencesKeys.UseBirthDateMode);
31+
this.UseBirthDateMode = false;
32+
this.ManualAge = "25";
2633
}
2734
);
2835
GetBLEKeyCommand = new Command(async () => await Launcher.OpenAsync("https://lswiderski.github.io/mi-scale-exporter/#steps-to-connect-xiaomi-body-composition-scale-s400"));
@@ -36,31 +43,55 @@ public SettingsViewModel()
3643

3744
public async Task LoadPreferencesAsync()
3845
{
39-
this._apiAddress = Preferences.Get(PreferencesKeys.ApiServerAddressOverride, string.Empty);
40-
this._oneClickScanAndUpload = Preferences.Get(PreferencesKeys.OneClickScanAndUpload, false);
41-
this._useExternalAPI = Preferences.Get(PreferencesKeys.UseExternalAPI, false);
42-
this._showDebugInfo = Preferences.Get(PreferencesKeys.ShowDebugInfo, false);
43-
this._hideAds = Preferences.Get(PreferencesKeys.HideAds, false);
44-
this._muscleMassAsPercentage = Preferences.Get(PreferencesKeys.MuscleMassAsPercentage, false);
45-
this._displayWeightInLbs = Preferences.Get(PreferencesKeys.DisplayWeightInLbs, false);
46-
this._useChinaServer = Preferences.Get(PreferencesKeys.UseChinaServer, false);
47-
48-
this._age = Preferences.Get(PreferencesKeys.UserAge, 25);
49-
this._height = Preferences.Get(PreferencesKeys.UserHeight, 170);
50-
this._sex = (Sex)Preferences.Get(PreferencesKeys.UserSex, (byte)Sex.Male);
51-
this._address = Preferences.Get(PreferencesKeys.MiScaleBluetoothAddress, string.Empty);
52-
this._scaleType = (ScaleType)Preferences.Get(PreferencesKeys.ScaleType, (byte)ScaleType.MiBodyCompositionScale);
53-
this._email = Preferences.Get(PreferencesKeys.GarminUserEmail, string.Empty);
54-
this._password = await SecureStorage.GetAsync(PreferencesKeys.GarminUserPassword);
55-
this._bindkey = Preferences.Get(PreferencesKeys.S400Bindkey, string.Empty);
56-
NotifyAllPropertiesChanged();
46+
_isLoadingPreferences = true;
47+
try
48+
{
49+
this._apiAddress = Preferences.Get(PreferencesKeys.ApiServerAddressOverride, string.Empty);
50+
this._oneClickScanAndUpload = Preferences.Get(PreferencesKeys.OneClickScanAndUpload, false);
51+
this._useExternalAPI = Preferences.Get(PreferencesKeys.UseExternalAPI, false);
52+
this._showDebugInfo = Preferences.Get(PreferencesKeys.ShowDebugInfo, false);
53+
this._hideAds = Preferences.Get(PreferencesKeys.HideAds, false);
54+
this._muscleMassAsPercentage = Preferences.Get(PreferencesKeys.MuscleMassAsPercentage, false);
55+
this._displayWeightInLbs = Preferences.Get(PreferencesKeys.DisplayWeightInLbs, false);
56+
this._useChinaServer = Preferences.Get(PreferencesKeys.UseChinaServer, false);
57+
58+
// Load age or birthday mode
59+
this._useBirthDateMode = Preferences.Get(PreferencesKeys.UseBirthDateMode, false);
60+
61+
if (_useBirthDateMode)
62+
{
63+
// Load birthday
64+
var birthDateTicks = Preferences.Get(PreferencesKeys.UserBirthDate, 0L);
65+
this._birthDate = birthDateTicks > 0 ? new DateTime(birthDateTicks) : DateTime.Today.AddYears(-25);
66+
this._manualAge = 0; // Not used in birthday mode
67+
}
68+
else
69+
{
70+
// Load manual age
71+
this._manualAge = Preferences.Get(PreferencesKeys.UserAge, 25);
72+
this._birthDate = DateTime.Today.AddYears(-_manualAge); // For reference only
73+
}
74+
75+
this._height = Preferences.Get(PreferencesKeys.UserHeight, 170);
76+
this._sex = (Sex)Preferences.Get(PreferencesKeys.UserSex, (byte)Sex.Male);
77+
this._address = Preferences.Get(PreferencesKeys.MiScaleBluetoothAddress, string.Empty);
78+
this._scaleType = (ScaleType)Preferences.Get(PreferencesKeys.ScaleType, (byte)ScaleType.MiBodyCompositionScale);
79+
this._email = Preferences.Get(PreferencesKeys.GarminUserEmail, string.Empty);
80+
this._password = await SecureStorage.GetAsync(PreferencesKeys.GarminUserPassword);
81+
this._bindkey = Preferences.Get(PreferencesKeys.S400Bindkey, string.Empty);
82+
NotifyAllPropertiesChanged();
83+
}
84+
finally
85+
{
86+
_isLoadingPreferences = false;
87+
}
5788
}
5889

5990
private bool ValidateProfile()
6091
{
6192
return !String.IsNullOrWhiteSpace(_address)
6293
&& _height > 0 && _height < 220
63-
&& _age > 0 && _age < 99;
94+
&& Age > 0 && Age < 99;
6495
}
6596

6697
private string _apiAddress;
@@ -228,17 +259,78 @@ public string Bindkey
228259

229260
private int _age;
230261

231-
public string Age
262+
public int Age
263+
{
264+
get
265+
{
266+
if (_useBirthDateMode)
267+
{
268+
// Calculate age from birthday
269+
var today = DateTime.Today;
270+
var age = today.Year - _birthDate.Year;
271+
if (_birthDate.Date > today.AddYears(-age))
272+
{
273+
age--;
274+
}
275+
return age;
276+
}
277+
else
278+
{
279+
// Return manually entered age
280+
return _manualAge;
281+
}
282+
}
283+
}
284+
285+
private int _manualAge;
286+
287+
public string ManualAge
232288
{
233-
get => _age.ToString();
289+
get => _manualAge.ToString();
234290
set
235291
{
236292
if (value is null) return;
237293
if (int.TryParse(value, out var result))
238294
{
239-
SetProperty(ref _age, result);
295+
SetProperty(ref _manualAge, result);
240296
if (result == 0) return;
241297
Preferences.Set(PreferencesKeys.UserAge, result);
298+
OnPropertyChanged(nameof(Age));
299+
}
300+
}
301+
}
302+
303+
private bool _useBirthDateMode;
304+
305+
public bool UseBirthDateMode
306+
{
307+
get => _useBirthDateMode;
308+
set
309+
{
310+
if (SetProperty(ref _useBirthDateMode, value))
311+
{
312+
Preferences.Set(PreferencesKeys.UseBirthDateMode, value);
313+
OnPropertyChanged(nameof(Age));
314+
}
315+
}
316+
}
317+
318+
private DateTime _birthDate;
319+
private bool _isLoadingPreferences = false;
320+
321+
public DateTime BirthDate
322+
{
323+
get => _birthDate;
324+
set
325+
{
326+
if (SetProperty(ref _birthDate, value))
327+
{
328+
// Only save to preferences if we're not in the middle of loading
329+
if (!_isLoadingPreferences)
330+
{
331+
Preferences.Set(PreferencesKeys.UserBirthDate, value.Ticks);
332+
OnPropertyChanged(nameof(Age));
333+
}
242334
}
243335
}
244336
}

src/MiScaleExporter.MAUI/Views/SettingsPage.xaml

Lines changed: 50 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -42,14 +42,56 @@
4242
HorizontalOptions="Fill"
4343
Color="Gray" />
4444
<Label FontSize="Small" Text="{x:Static localization:AppSnippets.Age}" />
45-
<Entry
46-
FontSize="Medium"
47-
Keyboard="Numeric"
48-
Text="{Binding Age, Mode=TwoWay}">
49-
<Entry.Behaviors>
50-
<behaviors:NumericIntValidationBehavior />
51-
</Entry.Behaviors>
52-
</Entry>
45+
<HorizontalStackLayout Spacing="20">
46+
<StackLayout Orientation="Horizontal" Spacing="10" HorizontalOptions="FillAndExpand">
47+
<RadioButton
48+
GroupName="AgeMode"
49+
IsChecked="{Binding UseBirthDateMode, Converter={StaticResource InvertedBoolConverter}}"
50+
VerticalOptions="Center"
51+
Content="Enter age manually"/>
52+
</StackLayout>
53+
<StackLayout Orientation="Horizontal" Spacing="10" HorizontalOptions="FillAndExpand">
54+
<RadioButton
55+
GroupName="AgeMode"
56+
IsChecked="{Binding UseBirthDateMode}"
57+
VerticalOptions="Center"
58+
Content="Use birth date"/>
59+
60+
</StackLayout>
61+
</HorizontalStackLayout>
62+
63+
<StackLayout IsVisible="{Binding UseBirthDateMode, Converter={StaticResource InvertedBoolConverter}}">
64+
<Entry
65+
FontSize="Medium"
66+
Keyboard="Numeric"
67+
Text="{Binding ManualAge, Mode=TwoWay}">
68+
<Entry.Behaviors>
69+
<behaviors:NumericIntValidationBehavior />
70+
</Entry.Behaviors>
71+
</Entry>
72+
</StackLayout>
73+
74+
<StackLayout IsVisible="{Binding UseBirthDateMode}">
75+
<DatePicker
76+
FontSize="Medium"
77+
Date="{Binding BirthDate, Mode=TwoWay}"
78+
Format="dd/MM/yyyy" />
79+
<StackLayout Orientation="Horizontal" Spacing="10">
80+
<Label
81+
FontSize="Small"
82+
Text="Your age:"
83+
VerticalOptions="Center" />
84+
<Label
85+
FontSize="Medium"
86+
Text="{Binding Age}"
87+
VerticalOptions="Center"
88+
FontAttributes="Bold" />
89+
<Label
90+
FontSize="Small"
91+
Text="years old"
92+
VerticalOptions="Center" />
93+
</StackLayout>
94+
</StackLayout>
5395

5496
<Label FontSize="Small" Text="{x:Static localization:AppSnippets.Height}" />
5597
<Entry

0 commit comments

Comments
 (0)