diff --git a/FinTrack/App.xaml.cs b/FinTrack/App.xaml.cs index c890ba3..cea8ae9 100644 --- a/FinTrack/App.xaml.cs +++ b/FinTrack/App.xaml.cs @@ -4,12 +4,15 @@ using FinTrackForWindows.Services.Accounts; using FinTrackForWindows.Services.Api; using FinTrackForWindows.Services.AppInNotifications; +using FinTrackForWindows.Services.ApplySettings; using FinTrackForWindows.Services.Budgets; using FinTrackForWindows.Services.Camera; using FinTrackForWindows.Services.Currencies; using FinTrackForWindows.Services.Debts; +using FinTrackForWindows.Services.Dialog; using FinTrackForWindows.Services.Memberships; using FinTrackForWindows.Services.Reports; +using FinTrackForWindows.Services.StoresRefresh; using FinTrackForWindows.Services.Transactions; using FinTrackForWindows.Services.Users; using FinTrackForWindows.ViewModels; @@ -94,7 +97,10 @@ private void ConfigureServices(IServiceCollection services) services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); services.AddTransient(); + services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); } diff --git a/FinTrack/Dtos/CurrencyDtos/ChangeSummaryDto.cs b/FinTrack/Dtos/CurrencyDtos/ChangeSummaryDto.cs index e8c9726..0ac594b 100644 --- a/FinTrack/Dtos/CurrencyDtos/ChangeSummaryDto.cs +++ b/FinTrack/Dtos/CurrencyDtos/ChangeSummaryDto.cs @@ -2,6 +2,10 @@ { public class ChangeSummaryDto { + // Günlük + public decimal? DailyLow { get; set; } + public decimal? DailyHigh { get; set; } + // Değişim Miktarı public decimal? DailyChangeValue { get; set; } public decimal? WeeklyChangeValue { get; set; } diff --git a/FinTrack/Dtos/SettingsDtos/ProfileSettingsDto.cs b/FinTrack/Dtos/SettingsDtos/ProfileSettingsDto.cs deleted file mode 100644 index 1a27a8f..0000000 --- a/FinTrack/Dtos/SettingsDtos/ProfileSettingsDto.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace FinTrackForWindows.Dtos.SettingsDtos -{ - public class ProfileSettingsDto - { - public int Id { get; set; } - public string FullName { get; set; } = string.Empty; - public string Email { get; set; } = string.Empty; - public string? ProfilePictureUrl { get; set; } - public DateTime CreatedAtUtc { get; set; } - public DateTime? UpdatedAtUtc { get; set; } - } -} diff --git a/FinTrack/Dtos/SettingsDtos/ProfileSettingsUpdateDto.cs b/FinTrack/Dtos/SettingsDtos/ProfileSettingsUpdateDto.cs deleted file mode 100644 index 01dc96b..0000000 --- a/FinTrack/Dtos/SettingsDtos/ProfileSettingsUpdateDto.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace FinTrackForWindows.Dtos.SettingsDtos -{ - public class ProfileSettingsUpdateDto - { - public string? FullName { get; set; } - public string? Email { get; set; } - public string? ProfilePictureUrl { get; set; } - } -} diff --git a/FinTrack/Dtos/SettingsDtos/UpdateProfilePictureDto.cs b/FinTrack/Dtos/SettingsDtos/UpdateProfilePictureDto.cs new file mode 100644 index 0000000..8ee9f4c --- /dev/null +++ b/FinTrack/Dtos/SettingsDtos/UpdateProfilePictureDto.cs @@ -0,0 +1,7 @@ +namespace FinTrackForWindows.Dtos.SettingsDtos +{ + public class UpdateProfilePictureDto + { + public string ProfilePictureUrl { get; set; } = string.Empty; + } +} diff --git a/FinTrack/Dtos/SettingsDtos/UpdateUserEmailDto.cs b/FinTrack/Dtos/SettingsDtos/UpdateUserEmailDto.cs new file mode 100644 index 0000000..f87a15c --- /dev/null +++ b/FinTrack/Dtos/SettingsDtos/UpdateUserEmailDto.cs @@ -0,0 +1,8 @@ +namespace FinTrackForWindows.Dtos.SettingsDtos +{ + public class UpdateUserEmailDto + { + public string NewEmail { get; set; } = string.Empty; + public string OtpCode { get; set; } = string.Empty; + } +} diff --git a/FinTrack/Dtos/SettingsDtos/UpdateUserNameDto.cs b/FinTrack/Dtos/SettingsDtos/UpdateUserNameDto.cs new file mode 100644 index 0000000..d1c5ca5 --- /dev/null +++ b/FinTrack/Dtos/SettingsDtos/UpdateUserNameDto.cs @@ -0,0 +1,8 @@ +namespace FinTrackForWindows.Dtos.SettingsDtos +{ + public class UpdateUserNameDto + { + public string FirstName { get; set; } = string.Empty; + public string LastName { get; set; } = string.Empty; + } +} diff --git a/FinTrack/Dtos/SettingsDtos/UpdateUserPasswordDto.cs b/FinTrack/Dtos/SettingsDtos/UpdateUserPasswordDto.cs new file mode 100644 index 0000000..ebed954 --- /dev/null +++ b/FinTrack/Dtos/SettingsDtos/UpdateUserPasswordDto.cs @@ -0,0 +1,8 @@ +namespace FinTrackForWindows.Dtos.SettingsDtos +{ + public class UpdateUserPasswordDto + { + public string CurrentPassword { get; set; } = string.Empty; + public string NewPassword { get; set; } = string.Empty; + } +} diff --git a/FinTrack/Dtos/SettingsDtos/UserAppSettingsDto.cs b/FinTrack/Dtos/SettingsDtos/UserAppSettingsDto.cs index 7626a81..db11b95 100644 --- a/FinTrack/Dtos/SettingsDtos/UserAppSettingsDto.cs +++ b/FinTrack/Dtos/SettingsDtos/UserAppSettingsDto.cs @@ -7,6 +7,7 @@ public class UserAppSettingsDto public int Id { get; set; } public AppearanceType Appearance { get; set; } public BaseCurrencyType Currency { get; set; } + public LanguageType Language { get; set; } public DateTime CreatedAtUtc { get; set; } public DateTime? UpdatedAtUtc { get; set; } } diff --git a/FinTrack/Dtos/SettingsDtos/UserAppSettingsUpdateDto.cs b/FinTrack/Dtos/SettingsDtos/UserAppSettingsUpdateDto.cs index 9a5d025..45d13a3 100644 --- a/FinTrack/Dtos/SettingsDtos/UserAppSettingsUpdateDto.cs +++ b/FinTrack/Dtos/SettingsDtos/UserAppSettingsUpdateDto.cs @@ -6,5 +6,6 @@ public class UserAppSettingsUpdateDto { public AppearanceType Appearance { get; set; } public BaseCurrencyType Currency { get; set; } + public LanguageType Language { get; set; } } } diff --git a/FinTrack/Dtos/UserDtos/UserProfileDto.cs b/FinTrack/Dtos/UserDtos/UserProfileDto.cs new file mode 100644 index 0000000..04bda0f --- /dev/null +++ b/FinTrack/Dtos/UserDtos/UserProfileDto.cs @@ -0,0 +1,42 @@ +using FinTrackForWindows.Enums; + +namespace FinTrackForWindows.Dtos.UserDtos +{ + public class UserProfileDto + { + // Temel Bilgiler + public int Id { get; set; } + public string UserName { get; set; } = string.Empty; + public string Email { get; set; } = string.Empty; + public string? ProfilePictureUrl { get; set; } + public DateTime CreatedAtUtc { get; set; } + + // Üyelik Bilgileri + public int CurrentMembershipPlanId { get; set; } + public string CurrentMembershipPlanType { get; set; } = string.Empty; + public DateTime MembershipStartDateUtc { get; set; } + public DateTime MembershipExpirationDateUtc { get; set; } + + // Ayarlar + public AppearanceType Thema { get; set; } + public LanguageType Language { get; set; } + public BaseCurrencyType Currency { get; set; } + public bool SpendingLimitWarning { get; set; } + public bool ExpectedBillReminder { get; set; } + public bool WeeklySpendingSummary { get; set; } + public bool NewFeaturesAndAnnouncements { get; set; } + public bool EnableDesktopNotifications { get; set; } + + // Kullanımlar + public List CurrentAccounts = new(); + public List CurrentBudgets = new(); + public List CurrentTransactions = new(); + public List CurrentBudgetsCategories = new(); + public List CurrentTransactionsCategories = new(); + public List CurrentLenderDebts = new(); + public List CurrentBorrowerDebts = new(); + public List CurrentNotifications = new(); + public List CurrentFeedbacks = new(); + public List CurrentVideos = new(); + } +} diff --git a/FinTrack/Dtos/UserProfileDto.cs b/FinTrack/Dtos/UserProfileDto.cs deleted file mode 100644 index 7a4c6fc..0000000 --- a/FinTrack/Dtos/UserProfileDto.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace FinTrackForWindows.Dtos -{ - public class UserProfileDto - { - public int Id { get; set; } - public string UserName { get; set; } = string.Empty; - public string Email { get; set; } = string.Empty; - public string? ProfilePicture { get; set; } - public string MembershipType { get; set; } = string.Empty; - } -} diff --git a/FinTrack/Enums/LanguageType.cs b/FinTrack/Enums/LanguageType.cs new file mode 100644 index 0000000..dd7959e --- /dev/null +++ b/FinTrack/Enums/LanguageType.cs @@ -0,0 +1,25 @@ +using System.ComponentModel; + +namespace FinTrackForWindows.Enums +{ + public enum LanguageType + { + [Description("Turkish")] + tr_TR, + + [Description("English - US")] + en_US, + + [Description("German")] + de_DE, + + [Description("French")] + fr_FR, + + [Description("Spanish")] + es_ES, + + [Description("Italian")] + it_IT, + } +} diff --git a/FinTrack/FinTrackForWindows.csproj b/FinTrack/FinTrackForWindows.csproj index c42aee8..affbc4a 100644 --- a/FinTrack/FinTrackForWindows.csproj +++ b/FinTrack/FinTrackForWindows.csproj @@ -195,4 +195,19 @@ + + + True + True + Strings.resx + + + + + + ResXFileCodeGenerator + Strings.Designer.cs + + + diff --git a/FinTrack/Helpers/TranslateExtension.cs b/FinTrack/Helpers/TranslateExtension.cs new file mode 100644 index 0000000..578e9bc --- /dev/null +++ b/FinTrack/Helpers/TranslateExtension.cs @@ -0,0 +1,27 @@ +using System.Windows.Data; +using System.Windows.Markup; + +namespace FinTrackForWindows.Helpers +{ + [MarkupExtensionReturnType(typeof(string))] + public class TranslateExtension : MarkupExtension + { + public string Key { get; set; } + + public TranslateExtension(string key) + { + Key = key; + } + + public override object ProvideValue(IServiceProvider serviceProvider) + { + var binding = new Binding($"[{Key}]") + { + Source = TranslationManager.Instance, + Mode = BindingMode.OneWay + }; + + return binding.ProvideValue(serviceProvider); + } + } +} \ No newline at end of file diff --git a/FinTrack/Helpers/TranslationManager.cs b/FinTrack/Helpers/TranslationManager.cs new file mode 100644 index 0000000..52b8b4d --- /dev/null +++ b/FinTrack/Helpers/TranslationManager.cs @@ -0,0 +1,93 @@ +using FinTrackForWindows.Enums; +using FinTrackForWindows.Services.ApplySettings; +using FinTrackForWindows.Services.Users; +using System.ComponentModel; +using System.Globalization; +using System.Resources; + +namespace FinTrackForWindows.Helpers +{ + public class TranslationManager : INotifyPropertyChanged + { + private static readonly TranslationManager _instance = new TranslationManager(); + public static TranslationManager Instance => _instance; + + public event PropertyChangedEventHandler? PropertyChanged; + + private readonly ResourceManager _resourceManager = new ResourceManager("FinTrackForWindows.Localization.Strings", typeof(TranslationManager).Assembly); + + private CultureInfo _currentLanguage = CultureInfo.CurrentUICulture; + + public CultureInfo CurrentLanguage + { + get => _currentLanguage; + private set + { + if (_currentLanguage.Name != value.Name) + { + _currentLanguage = value; + Thread.CurrentThread.CurrentUICulture = value; + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(string.Empty)); + } + } + } + + private static IUserStore? _userStore; + + public static void Initialize(IApplySettingsService settingsService, IUserStore userStore) + { + _userStore = userStore; + settingsService.SettingsChanged += OnSettingsChanged; + + UpdateLanguageFromStore(); + } + + private static void OnSettingsChanged() + { + UpdateLanguageFromStore(); + } + + private static void UpdateLanguageFromStore() + { + if (_userStore?.CurrentUser == null) return; + + var languageType = _userStore.CurrentUser.Language; + CultureInfo newCulture; + + switch (languageType) + { + case LanguageType.tr_TR: + newCulture = new CultureInfo("tr-TR"); + break; + case LanguageType.de_DE: + newCulture = new CultureInfo("de-DE"); + break; + case LanguageType.fr_FR: + newCulture = new CultureInfo("fr-FR"); + break; + case LanguageType.es_ES: + newCulture = new CultureInfo("es-ES"); + break; + case LanguageType.it_IT: + newCulture = new CultureInfo("it-IT"); + break; + case LanguageType.en_US: + newCulture = new CultureInfo("en-US"); + break; + default: + newCulture = new CultureInfo("en-US"); + break; + } + + Instance.CurrentLanguage = newCulture; + } + + public string GetString(string key) + { + string? result = _resourceManager.GetString(key, _currentLanguage); + return result ?? $"_{key}_"; + } + + public string this[string key] => GetString(key); + } +} diff --git a/FinTrack/Localization/Strings.Designer.cs b/FinTrack/Localization/Strings.Designer.cs new file mode 100644 index 0000000..6053df5 --- /dev/null +++ b/FinTrack/Localization/Strings.Designer.cs @@ -0,0 +1,378 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace FinTrackForWindows.Localization { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Strings { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Strings() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("FinTrackForWindows.Localization.Strings", typeof(Strings).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Account Name. + /// + internal static string Account_Name { + get { + return ResourceManager.GetString("Account_Name", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Account_Type. + /// + internal static string Account_Type { + get { + return ResourceManager.GetString("Account_Type", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Accounts. + /// + internal static string Accounts_Nav_Title { + get { + return ResourceManager.GetString("Accounts_Nav_Title", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Apperance. + /// + internal static string Apperance { + get { + return ResourceManager.GetString("Apperance", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Budget_Name. + /// + internal static string Budget_Name { + get { + return ResourceManager.GetString("Budget_Name", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Budgets. + /// + internal static string Budgets_Nav_Title { + get { + return ResourceManager.GetString("Budgets_Nav_Title", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Category_Name. + /// + internal static string Category_Name { + get { + return ResourceManager.GetString("Category_Name", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Select an existing category or type to create a new one.. + /// + internal static string Category_ToolTip { + get { + return ResourceManager.GetString("Category_ToolTip", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Clear Form. + /// + internal static string Clear_Form { + get { + return ResourceManager.GetString("Clear_Form", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Click to edit. + /// + internal static string Click_To_Edit { + get { + return ResourceManager.GetString("Click_To_Edit", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Currencies. + /// + internal static string Currencies_Nav_Title { + get { + return ResourceManager.GetString("Currencies_Nav_Title", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Currency. + /// + internal static string Currency { + get { + return ResourceManager.GetString("Currency", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Dashboard. + /// + internal static string Dashboard_Nav_Title { + get { + return ResourceManager.GetString("Dashboard_Nav_Title", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Debts. + /// + internal static string Debts_Nav_Title { + get { + return ResourceManager.GetString("Debts_Nav_Title", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Delete. + /// + internal static string Delete { + get { + return ResourceManager.GetString("Delete", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Description. + /// + internal static string Description { + get { + return ResourceManager.GetString("Description", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Edit. + /// + internal static string Edit { + get { + return ResourceManager.GetString("Edit", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to End Date. + /// + internal static string End_Date { + get { + return ResourceManager.GetString("End_Date", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Expense. + /// + internal static string Expense { + get { + return ResourceManager.GetString("Expense", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Filter By Name. + /// + internal static string Filter_By_Name { + get { + return ResourceManager.GetString("Filter_By_Name", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to FinBot. + /// + internal static string FinBot_Nav_Title { + get { + return ResourceManager.GetString("FinBot_Nav_Title", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Income. + /// + internal static string Income { + get { + return ResourceManager.GetString("Income", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Language. + /// + internal static string Language { + get { + return ResourceManager.GetString("Language", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Max Balance. + /// + internal static string Max_Balance { + get { + return ResourceManager.GetString("Max_Balance", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Min Balance. + /// + internal static string Min_Balance { + get { + return ResourceManager.GetString("Min_Balance", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to My Accounts. + /// + internal static string My_Accounts_Title { + get { + return ResourceManager.GetString("My_Accounts_Title", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to My Budgets. + /// + internal static string My_Budgets_Title { + get { + return ResourceManager.GetString("My_Budgets_Title", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to No budget found to display.. + /// + internal static string No_Budgets { + get { + return ResourceManager.GetString("No_Budgets", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Reached Amount. + /// + internal static string Reached_Amount { + get { + return ResourceManager.GetString("Reached_Amount", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Reports. + /// + internal static string Reports_Nav_Title { + get { + return ResourceManager.GetString("Reports_Nav_Title", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Save. + /// + internal static string Save { + get { + return ResourceManager.GetString("Save", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Start Date. + /// + internal static string Start_Date { + get { + return ResourceManager.GetString("Start_Date", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Target Amount. + /// + internal static string Target_Amount { + get { + return ResourceManager.GetString("Target_Amount", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Transaction_Type. + /// + internal static string Transaction_Type { + get { + return ResourceManager.GetString("Transaction_Type", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Transactions. + /// + internal static string Transactions_Nav_Title { + get { + return ResourceManager.GetString("Transactions_Nav_Title", resourceCulture); + } + } + } +} diff --git a/FinTrack/Localization/Strings.de-DE.resx b/FinTrack/Localization/Strings.de-DE.resx new file mode 100644 index 0000000..f80889f --- /dev/null +++ b/FinTrack/Localization/Strings.de-DE.resx @@ -0,0 +1,225 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Budgets + + + FinBot + + + Armaturenbrett + + + Konten + + + Transaktionen + + + Berichte + + + Preise + + + Meine Konten + + + Schulden + + + Meine Budgets + + + Nach Name Filtern + + + Mindestguthaben + + + Maximaler Kontostand + + + Kontoname + + + Kontotyp + + + Devisen + + + Kategoriebezeichnung + + + Transaktionsart + + + Einkommen + + + Ausgaben + + + Speichern + + + Formular Löschen + + + Aussehen + + + Sprache + + + Zum Bearbeiten anklicken + + + Löschen + + + Bearbeiten + + + Es wurde kein anzeigbares Budget gefunden. + + + Budget Name + + + Zielmenge + + + Erreichte Menge + + + Erklärung + + + Startdatum + + + Enddatum + + + Wählen Sie eine vorhandene Kategorie oder einen Typ aus, um eine neue Kategorie oder einen neuen Typ zu erstellen. + + \ No newline at end of file diff --git a/FinTrack/Localization/Strings.es-ES.resx b/FinTrack/Localization/Strings.es-ES.resx new file mode 100644 index 0000000..62c5a2f --- /dev/null +++ b/FinTrack/Localization/Strings.es-ES.resx @@ -0,0 +1,225 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Presupuestos + + + FinBot + + + Panel + + + Cuentas + + + Actas + + + Informes + + + Tarifas + + + Mis Cuentas + + + Deudas + + + Mis presupuestos + + + Filtrar Por Nombre + + + Saldo Mínimo + + + Saldo Máximo + + + Nombre de la Cuenta + + + Tipo de Cuenta + + + Divisas + + + Nombre de la Categoría + + + Tipo de Operación + + + Ingresos + + + Gasto + + + Guardar + + + Borrar Formulario + + + Aspecto + + + Idioma + + + Haga clic para editar. + + + Borrar + + + Editar + + + No se ha encontrado ningún presupuesto disponible. + + + Nombre del Presupuesto + + + Cantidad Objetivo + + + Cantidad Alcanzada + + + Explicación + + + Fecha de inicio + + + Fecha de finalización + + + Seleccione una categoría o tipo existente para crear uno nuevo. + + \ No newline at end of file diff --git a/FinTrack/Localization/Strings.fr-FR.resx b/FinTrack/Localization/Strings.fr-FR.resx new file mode 100644 index 0000000..9898147 --- /dev/null +++ b/FinTrack/Localization/Strings.fr-FR.resx @@ -0,0 +1,225 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Budgets + + + FinBot + + + Panneau de Contrôle + + + Comptes + + + Transactions + + + Rapports + + + Tarifs + + + Mes comptes + + + Dettes + + + Mes budgets + + + Filtrer Par Nom + + + Solde Minimum + + + Solde Maximal + + + Nom du Compte + + + Type de Compte + + + Devise + + + Nom de la Catégorie + + + Type d'opération + + + Revenu + + + Dépense + + + Enregistrer + + + Effacer le Formulaire + + + Apparence + + + Langue + + + Cliquez pour modifier + + + Supprimer + + + Modifier + + + Aucun budget disponible n'a été trouvé. + + + Nom du Budget + + + Quantité Cible + + + Quantité Atteinte + + + Explication + + + Date de Début + + + Date de fin + + + Sélectionnez une catégorie ou un type existant pour en créer un nouveau. + + \ No newline at end of file diff --git a/FinTrack/Localization/Strings.it-IT.resx b/FinTrack/Localization/Strings.it-IT.resx new file mode 100644 index 0000000..4cc3a19 --- /dev/null +++ b/FinTrack/Localization/Strings.it-IT.resx @@ -0,0 +1,225 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Budget + + + FinBot + + + Pannello di Controllo + + + Conti + + + Transazioni + + + Rapporti + + + Tariffe + + + I miei conti + + + Debiti + + + I miei budget + + + Filtra Per Nome + + + Saldo Minimo + + + Saldo Massimo + + + Nome Dell'account + + + Tipo di Conto + + + Valuta + + + Nome della Categoria + + + Tipo di Operazione + + + Reddito + + + Spese + + + Salva + + + Cancella il Modulo + + + Aspetto + + + Lingua + + + Clicca per modificare + + + Cancella + + + Modifica + + + Non è stato trovato alcun budget visualizzabile. + + + Nome del Bilancio + + + Quantità Target + + + Quantità Raggiunta + + + Spiegazione + + + Data di inizio + + + Data di Scadenza + + + Seleziona una categoria o un tipo esistente per crearne uno nuovo. + + \ No newline at end of file diff --git a/FinTrack/Localization/Strings.resx b/FinTrack/Localization/Strings.resx new file mode 100644 index 0000000..0c180eb --- /dev/null +++ b/FinTrack/Localization/Strings.resx @@ -0,0 +1,225 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Budgets + + + FinBot + + + Dashboard + + + Accounts + + + Transactions + + + Reports + + + Currencies + + + My Accounts + + + Debts + + + My Budgets + + + Filter By Name + + + Min Balance + + + Max Balance + + + Account Name + + + Account_Type + + + Currency + + + Category_Name + + + Transaction_Type + + + Income + + + Expense + + + Save + + + Clear Form + + + Apperance + + + Language + + + Click to edit + + + Delete + + + Edit + + + No budget found to display. + + + Budget_Name + + + Target Amount + + + Reached Amount + + + Description + + + Start Date + + + End Date + + + Select an existing category or type to create a new one. + + \ No newline at end of file diff --git a/FinTrack/Localization/Strings.tr-TR.resx b/FinTrack/Localization/Strings.tr-TR.resx new file mode 100644 index 0000000..b658da4 --- /dev/null +++ b/FinTrack/Localization/Strings.tr-TR.resx @@ -0,0 +1,225 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Bütçeler + + + FinBot + + + Ana Sayfa + + + Hesaplar + + + İşlemler + + + Raporlar + + + Kurlar + + + Hesaplarım + + + Borçlar + + + Bütçelerim + + + İsime Göre Filtrele + + + Minimum Değer + + + Maksimum Değer + + + Hesap Adı + + + Hesap Türü + + + Para Birimi + + + Kategori Adı + + + İşlem Türü + + + Gelir + + + Gider + + + Kaydet + + + Formu Temizle + + + Görünüm + + + Dil + + + Düzenlemek için tıkla + + + Sil + + + Düzenle + + + Görüntülenebilecek bütçe bulunamadı. + + + Bütçe Adı + + + Hedef Miktar + + + Ulaşılan Miktar + + + Açıklama + + + Başlangıç Tarihi + + + Bitiş Tarihi + + + Mevcut bir kategori veya tür seçin veya yeni bir tane oluşturun. + + \ No newline at end of file diff --git a/FinTrack/Models/Currency/CurrencyModel.cs b/FinTrack/Models/Currency/CurrencyModel.cs index a1be66d..d209484 100644 --- a/FinTrack/Models/Currency/CurrencyModel.cs +++ b/FinTrack/Models/Currency/CurrencyModel.cs @@ -33,6 +33,9 @@ public partial class CurrencyModel : ObservableObject [ObservableProperty] private string dailyHigh = "N/A"; + [ObservableProperty] + private string dailyChange = "N/A"; + [ObservableProperty] private string weeklyChange = "N/A"; @@ -47,6 +50,10 @@ public partial class CurrencyModel : ObservableObject [NotifyPropertyChangedFor(nameof(MonthlyChangeForeground))] private CurrencyConversionType monthlyChangeType = CurrencyConversionType.Neutral; + [ObservableProperty] + [NotifyPropertyChangedFor(nameof(WeeklyChangeForeground))] + private CurrencyConversionType dailyChangeType = CurrencyConversionType.Neutral; + private static readonly Brush IncreaseBrush = new SolidColorBrush(Color.FromRgb(46, 204, 113)); private static readonly Brush DecreaseBrush = new SolidColorBrush(Color.FromRgb(231, 76, 60)); private static readonly Brush DefaultBrush = new SolidColorBrush(Colors.Gray); @@ -58,6 +65,13 @@ public partial class CurrencyModel : ObservableObject _ => DefaultBrush }; + public Brush DailyChangeForeground => DailyChangeType switch + { + CurrencyConversionType.Increase => IncreaseBrush, + CurrencyConversionType.Decrease => DecreaseBrush, + _ => DefaultBrush + }; + public Brush WeeklyChangeForeground => WeeklyChangeType switch { CurrencyConversionType.Increase => IncreaseBrush, diff --git a/FinTrack/Models/User/UserModel.cs b/FinTrack/Models/User/UserModel.cs index 89ccefe..939d415 100644 --- a/FinTrack/Models/User/UserModel.cs +++ b/FinTrack/Models/User/UserModel.cs @@ -1,12 +1,42 @@ -namespace FinTrackForWindows.Models.User +using FinTrackForWindows.Enums; + +namespace FinTrackForWindows.Models.User { public class UserModel { + // Temel Bilgiler public int Id { get; set; } public string UserName { get; set; } = string.Empty; public string Email { get; set; } = string.Empty; - public string ProfilePictureUrl { get; set; } = string.Empty; - public DateTime CreatedAt { get; set; } = DateTime.UtcNow; - public string MembershipPlan { get; set; } = string.Empty; + public string? ProfilePictureUrl { get; set; } + public DateTime CreatedAtUtc { get; set; } + + // Üyelik Bilgileri + public int CurrentMembershipPlanId { get; set; } + public string CurrentMembershipPlanType { get; set; } = string.Empty; + public DateTime MembershipStartDateUtc { get; set; } + public DateTime MembershipExpirationDateUtc { get; set; } + + // Ayarlar + public AppearanceType Thema { get; set; } + public LanguageType Language { get; set; } + public BaseCurrencyType Currency { get; set; } + public bool SpendingLimitWarning { get; set; } + public bool ExpectedBillReminder { get; set; } + public bool WeeklySpendingSummary { get; set; } + public bool NewFeaturesAndAnnouncements { get; set; } + public bool EnableDesktopNotifications { get; set; } + + // Kullanımlar + public List CurrentAccounts = new(); + public List CurrentBudgets = new(); + public List CurrentTransactions = new(); + public List CurrentBudgetsCategories = new(); + public List CurrentTransactionsCategories = new(); + public List CurrentLenderDebts = new(); + public List CurrentBorrowerDebts = new(); + public List CurrentNotifications = new(); + public List CurrentFeedbacks = new(); + public List CurrentVideos = new(); } } diff --git a/FinTrack/Services/ApplySettings/ApplySettingsService.cs b/FinTrack/Services/ApplySettings/ApplySettingsService.cs new file mode 100644 index 0000000..7915af2 --- /dev/null +++ b/FinTrack/Services/ApplySettings/ApplySettingsService.cs @@ -0,0 +1,22 @@ +namespace FinTrackForWindows.Services.ApplySettings +{ + public class ApplySettingsService : IApplySettingsService + { + public event Action? SettingsChanged; + + public void BaseCurrencyApply() + { + SettingsChanged?.Invoke(); + } + + public void AppearanceApply() + { + SettingsChanged?.Invoke(); + } + + public void LanguageApply() + { + SettingsChanged?.Invoke(); + } + } +} diff --git a/FinTrack/Services/ApplySettings/IApplySettingsService.cs b/FinTrack/Services/ApplySettings/IApplySettingsService.cs new file mode 100644 index 0000000..f3a2aa5 --- /dev/null +++ b/FinTrack/Services/ApplySettings/IApplySettingsService.cs @@ -0,0 +1,11 @@ +namespace FinTrackForWindows.Services.ApplySettings +{ + public interface IApplySettingsService + { + event Action? SettingsChanged; + + void BaseCurrencyApply(); + void AppearanceApply(); + void LanguageApply(); + } +} diff --git a/FinTrack/Services/Currencies/CurrenciesStore.cs b/FinTrack/Services/Currencies/CurrenciesStore.cs index 69ad05c..6a594a6 100644 --- a/FinTrack/Services/Currencies/CurrenciesStore.cs +++ b/FinTrack/Services/Currencies/CurrenciesStore.cs @@ -4,6 +4,7 @@ using FinTrackWebApi.Dtos.CurrencyDtos; using Microsoft.Extensions.Logging; using System.Collections.ObjectModel; +using System.Collections.Specialized; namespace FinTrackForWindows.Services.Currencies { @@ -15,6 +16,8 @@ public class CurrenciesStore : ICurrenciesStore public ReadOnlyObservableCollection Currencies { get; } + public event NotifyCollectionChangedEventHandler? CurrenciesChanged; + public CurrenciesStore(IApiService apiService, ILogger logger) { _apiService = apiService; @@ -22,6 +25,11 @@ public CurrenciesStore(IApiService apiService, ILogger logger) _currencies = new ObservableCollection(); Currencies = new ReadOnlyObservableCollection(_currencies); + + _currencies.CollectionChanged += (sender, e) => + { + CurrenciesChanged?.Invoke(this, e); + }; } public async Task LoadCurrenciesAsync() diff --git a/FinTrack/Services/Currencies/ICurrenciesStore.cs b/FinTrack/Services/Currencies/ICurrenciesStore.cs index 0e25ced..c93627c 100644 --- a/FinTrack/Services/Currencies/ICurrenciesStore.cs +++ b/FinTrack/Services/Currencies/ICurrenciesStore.cs @@ -1,6 +1,7 @@ using FinTrackForWindows.Models.Currency; using FinTrackWebApi.Dtos.CurrencyDtos; using System.Collections.ObjectModel; +using System.Collections.Specialized; namespace FinTrackForWindows.Services.Currencies { @@ -8,6 +9,7 @@ public interface ICurrenciesStore { ReadOnlyObservableCollection Currencies { get; } Task LoadCurrenciesAsync(); + event NotifyCollectionChangedEventHandler? CurrenciesChanged; Task GetHistoricalDataAsync(string targetCurrencyCode, string period); Task GetConvertCurrencies(string fromCurrencyCode, string toCurrencyCode, decimal amount); } diff --git a/FinTrack/Services/Dialog/IDialogService.cs b/FinTrack/Services/Dialog/IDialogService.cs new file mode 100644 index 0000000..afc8e13 --- /dev/null +++ b/FinTrack/Services/Dialog/IDialogService.cs @@ -0,0 +1,7 @@ +namespace FinTrackForWindows.Services.Dialog +{ + public interface IDialogService + { + string? ShowOtpDialog(string newEmail); + } +} diff --git a/FinTrack/Services/Dialog/WpfDialogService.cs b/FinTrack/Services/Dialog/WpfDialogService.cs new file mode 100644 index 0000000..bc98dd7 --- /dev/null +++ b/FinTrack/Services/Dialog/WpfDialogService.cs @@ -0,0 +1,17 @@ +using FinTrackForWindows.Views; + +namespace FinTrackForWindows.Services.Dialog +{ + public class WpfDialogService : IDialogService + { + public string? ShowOtpDialog(string newEmail) + { + var dialog = new OtpInputDialogWindow(newEmail); + if (dialog.ShowDialog() == true) + { + return dialog.OtpCode; + } + return null; + } + } +} diff --git a/FinTrack/Services/StoresRefresh/IStoresRefresh.cs b/FinTrack/Services/StoresRefresh/IStoresRefresh.cs new file mode 100644 index 0000000..8a6ce27 --- /dev/null +++ b/FinTrack/Services/StoresRefresh/IStoresRefresh.cs @@ -0,0 +1,9 @@ +namespace FinTrackForWindows.Services.StoresRefresh +{ + public interface IStoresRefresh + { + event Action? RefreshStarted; + event Action? RefreshCompleted; + Task RefreshAllStoresAsync(); + } +} diff --git a/FinTrack/Services/StoresRefresh/StoresRefresh.cs b/FinTrack/Services/StoresRefresh/StoresRefresh.cs new file mode 100644 index 0000000..87d6c79 --- /dev/null +++ b/FinTrack/Services/StoresRefresh/StoresRefresh.cs @@ -0,0 +1,76 @@ +using FinTrackForWindows.Services.Accounts; +using FinTrackForWindows.Services.Budgets; +using FinTrackForWindows.Services.Currencies; +using FinTrackForWindows.Services.Debts; +using FinTrackForWindows.Services.Memberships; +using FinTrackForWindows.Services.Transactions; +using Microsoft.Extensions.Logging; + +namespace FinTrackForWindows.Services.StoresRefresh +{ + public class StoresRefresh : IStoresRefresh + { + private readonly IAccountStore _accountStore; + private readonly IBudgetStore _budgetStore; + private readonly ITransactionStore _transactionStore; + private readonly IDebtStore _debtStore; + private readonly IMembershipStore _membershipStore; + private readonly ICurrenciesStore _currenciesStore; + + private readonly ILogger _logger; + + public event Action? RefreshStarted; + public event Action? RefreshCompleted; + + public StoresRefresh(IAccountStore accountStore, + IBudgetStore budgetStore, + ITransactionStore transactionStore, + IDebtStore debtStore, + IMembershipStore membershipStore, + ICurrenciesStore currenciesStore, + ILogger logger) + { + _accountStore = accountStore; + _budgetStore = budgetStore; + _transactionStore = transactionStore; + _debtStore = debtStore; + _membershipStore = membershipStore; + _currenciesStore = currenciesStore; + _logger = logger; + } + + public async Task RefreshAllStoresAsync() + { + _logger.LogInformation("Refresh process starting for all data stores..."); + RefreshStarted?.Invoke(); + + try + { + Task loadAccountsTask = _accountStore.LoadAccountsAsync(); + Task loadBudgetsTask = _budgetStore.LoadBudgetsAsync(); + Task loadDebtsTask = _debtStore.LoadDebtsAsync(); + Task loadMembershipTask = _membershipStore.LoadAllMembershipDataAsync(); + Task loadTransactionsTask = _transactionStore.LoadTransactionsAsync(); + Task loadCurrenciesTask = _currenciesStore.LoadCurrenciesAsync(); + + await Task.WhenAll( + loadAccountsTask, + loadBudgetsTask, + loadDebtsTask, + loadMembershipTask, + loadCurrenciesTask, + loadTransactionsTask); + + _logger.LogInformation("All data stores have been successfully refreshed."); + RefreshCompleted?.Invoke(true); + return true; + } + catch (Exception ex) + { + _logger.LogError(ex, "An error occurred while refreshing the data stores."); + RefreshCompleted?.Invoke(false); + return false; + } + } + } +} diff --git a/FinTrack/Services/Users/IUserStore.cs b/FinTrack/Services/Users/IUserStore.cs index 547e2bc..34887b6 100644 --- a/FinTrack/Services/Users/IUserStore.cs +++ b/FinTrack/Services/Users/IUserStore.cs @@ -1,4 +1,5 @@ -using FinTrackForWindows.Models.User; +using FinTrackForWindows.Dtos.SettingsDtos; +using FinTrackForWindows.Models.User; namespace FinTrackForWindows.Services.Users { @@ -7,7 +8,15 @@ public interface IUserStore UserModel? CurrentUser { get; } event Action? UserChanged; Task LoadCurrentUserAsync(); - Task UpdateUserAsync(UserModel updatedUserData); + + Task UpdateProfilePictureAsync(UpdateProfilePictureDto pictureDto); + Task UpdateUserNameAsync(UpdateUserNameDto nameDto); + Task UpdateUserPasswordAsync(UpdateUserPasswordDto passwordDto); + Task RequestEmailChangeOtpAsync(); + Task UpdateUserEmailAsync(UpdateUserEmailDto emailDto); + + Task UpdateAppSettingsAsync(UserAppSettingsUpdateDto settingsDto); + Task UpdateNotificationSettingsAsync(UserNotificationSettingsUpdateDto settingsDto); void ClearCurrentUser(); } diff --git a/FinTrack/Services/Users/UserStore.cs b/FinTrack/Services/Users/UserStore.cs index 9ce1e96..832fb57 100644 --- a/FinTrack/Services/Users/UserStore.cs +++ b/FinTrack/Services/Users/UserStore.cs @@ -1,102 +1,223 @@ -using FinTrackForWindows.Dtos; +using FinTrackForWindows.Dtos.SettingsDtos; +using FinTrackForWindows.Dtos.UserDtos; +using FinTrackForWindows.Helpers; using FinTrackForWindows.Models.User; using FinTrackForWindows.Services.Api; -using Microsoft.Extensions.Logging; +using FinTrackForWindows.Services.ApplySettings; namespace FinTrackForWindows.Services.Users { public class UserStore : IUserStore { private readonly IApiService _apiService; - private readonly ILogger _logger; private UserModel? _currentUser; - public UserModel? CurrentUser + public UserModel? CurrentUser => _currentUser; + public event Action? UserChanged; + + private readonly IApplySettingsService _applySettingsService; + + public UserStore(IApiService apiService, IApplySettingsService applySettingsService) + { + _apiService = apiService; + _applySettingsService = applySettingsService; + } + + public async Task LoadCurrentUserAsync() { - get => _currentUser; - private set + try { - if (_currentUser != value) + var userProfile = await _apiService.GetAsync("user"); + + if (userProfile != null) { - _currentUser = value; - UserChanged?.Invoke(); + _currentUser = MapProfileDtoToUserModel(userProfile); + TranslationManager.Initialize(_applySettingsService, this); + OnUserChanged(); } } + catch (Exception ex) + { + Console.WriteLine($"Error loading current user: {ex.Message}"); + ClearCurrentUser(); + } } - public event Action? UserChanged; + public void ClearCurrentUser() + { + if (_currentUser != null) + { + _currentUser = null; + OnUserChanged(); + } + } - public UserStore(IApiService apiService, ILogger logger) + public async Task UpdateProfilePictureAsync(UpdateProfilePictureDto pictureDto) { - _apiService = apiService; - _logger = logger; + if (CurrentUser == null) return false; + + try + { + await _apiService.PostAsync("usersettings/update-profile-picture", pictureDto); + + CurrentUser.ProfilePictureUrl = pictureDto.ProfilePictureUrl; + OnUserChanged(); + return true; + } + catch (Exception ex) + { + Console.WriteLine($"Error updating profile picture: {ex.Message}"); + return false; + } } - public async Task LoadCurrentUserAsync() + public async Task UpdateUserNameAsync(UpdateUserNameDto nameDto) { - _logger.LogInformation("Mevcut kullanıcı bilgileri yükleniyor..."); + if (CurrentUser == null) return false; + try { - var userDto = await _apiService.GetAsync("User"); - var user = new UserModel - { - Id = userDto.Id, - UserName = userDto.UserName, - Email = userDto.Email, - ProfilePictureUrl = userDto.ProfilePicture ?? string.Empty, - MembershipPlan = userDto.MembershipType, - }; - if (userDto != null) - { - CurrentUser = user; - _logger.LogInformation("Kullanıcı bilgileri başarıyla yüklendi: {UserName}", userDto.UserName); - } - else - { - _logger.LogWarning("API'den kullanıcı bilgileri alınamadı veya kullanıcı bulunamadı."); - ClearCurrentUser(); - } + await _apiService.PostAsync("usersettings/update-username", nameDto); + + CurrentUser.UserName = $"{nameDto.FirstName.Trim()}_{nameDto.LastName.Trim()}"; + OnUserChanged(); + return true; } catch (Exception ex) { - _logger.LogError(ex, "Kullanıcı bilgileri yüklenirken bir hata oluştu."); - ClearCurrentUser(); + Console.WriteLine($"Error updating username: {ex.Message}"); + return false; } } - public async Task UpdateUserAsync(UserModel updatedUserData) + public async Task UpdateUserPasswordAsync(UpdateUserPasswordDto passwordDto) { - //_logger.LogInformation("Kullanıcı bilgileri güncelleniyor: {UserName}", updatedUserData.UserName); - //try - //{ - // var updatedUserFromApi = await _apiService.PutAsync("User", updatedUserData); - - // if (updatedUserFromApi != null) - // { - // CurrentUser = updatedUserFromApi; - // _logger.LogInformation("Kullanıcı bilgileri başarıyla güncellendi."); - // return true; - // } - - // _logger.LogWarning("Kullanıcı güncelleme isteği API tarafından başarısız oldu veya geçersiz veri döndü."); - // return false; - //} - //catch (Exception ex) - //{ - // _logger.LogError(ex, "Kullanıcı bilgileri güncellenirken bir hata oluştu."); - // return false; - //} - await Task.Delay(1000); - return false; + try + { + await _apiService.PostAsync("usersettings/update-password", passwordDto); + return true; + } + catch (Exception ex) + { + Console.WriteLine($"Error updating password: {ex.Message}"); + return false; + } } - public void ClearCurrentUser() + public async Task RequestEmailChangeOtpAsync() { - if (CurrentUser != null) + try + { + await _apiService.PostAsync("usersettings/request-email-change", null); + } + catch (Exception ex) + { + Console.WriteLine($"Error requesting email change OTP: {ex.Message}"); + } + } + + public async Task UpdateUserEmailAsync(UpdateUserEmailDto emailDto) + { + if (CurrentUser == null) return false; + + try { - _logger.LogInformation("Mevcut kullanıcı bilgileri temizleniyor."); - CurrentUser = null; + await _apiService.PostAsync("usersettings/confirm-email-change", emailDto); + + CurrentUser.Email = emailDto.NewEmail; + OnUserChanged(); + return true; + } + catch (Exception ex) + { + Console.WriteLine($"Error confirming email change: {ex.Message}"); + return false; + } + } + + public async Task UpdateNotificationSettingsAsync(UserNotificationSettingsUpdateDto settingsDto) + { + if (CurrentUser == null) return false; + + try + { + await _apiService.PostAsync("usersettings/user-notificationettings", settingsDto); + + CurrentUser.SpendingLimitWarning = settingsDto.SpendingLimitWarning; + CurrentUser.ExpectedBillReminder = settingsDto.ExpectedBillReminder; + CurrentUser.WeeklySpendingSummary = settingsDto.WeeklySpendingSummary; + CurrentUser.NewFeaturesAndAnnouncements = settingsDto.NewFeaturesAndAnnouncements; + CurrentUser.EnableDesktopNotifications = settingsDto.EnableDesktopNotifications; + OnUserChanged(); + return true; } + catch (Exception ex) + { + Console.WriteLine($"Error updating notification settings: {ex.Message}"); + return false; + } + } + + public async Task UpdateAppSettingsAsync(UserAppSettingsUpdateDto settingsDto) + { + if (CurrentUser == null) return false; + + try + { + await _apiService.PostAsync("usersettings/app-settings", settingsDto); + + CurrentUser.Thema = settingsDto.Appearance; + CurrentUser.Currency = settingsDto.Currency; + CurrentUser.Language = settingsDto.Language; + + TranslationManager.Initialize(_applySettingsService, this); + OnUserChanged(); + return true; + } + catch (Exception ex) + { + Console.WriteLine($"Error updating app settings: {ex.Message}"); + return false; + } + } + + private void OnUserChanged() + { + UserChanged?.Invoke(); + } + + private UserModel MapProfileDtoToUserModel(UserProfileDto dto) + { + return new UserModel + { + Id = dto.Id, + UserName = dto.UserName, + Email = dto.Email, + ProfilePictureUrl = dto.ProfilePictureUrl, + CreatedAtUtc = dto.CreatedAtUtc, + CurrentMembershipPlanId = dto.CurrentMembershipPlanId, + CurrentMembershipPlanType = dto.CurrentMembershipPlanType, + MembershipStartDateUtc = dto.MembershipStartDateUtc, + MembershipExpirationDateUtc = dto.MembershipExpirationDateUtc, + Thema = dto.Thema, + Language = dto.Language, + Currency = dto.Currency, + SpendingLimitWarning = dto.SpendingLimitWarning, + ExpectedBillReminder = dto.ExpectedBillReminder, + WeeklySpendingSummary = dto.WeeklySpendingSummary, + NewFeaturesAndAnnouncements = dto.NewFeaturesAndAnnouncements, + EnableDesktopNotifications = dto.EnableDesktopNotifications, + CurrentAccounts = dto.CurrentAccounts, + CurrentBudgets = dto.CurrentBudgets, + CurrentTransactions = dto.CurrentTransactions, + CurrentBudgetsCategories = dto.CurrentBudgetsCategories, + CurrentTransactionsCategories = dto.CurrentTransactionsCategories, + CurrentLenderDebts = dto.CurrentLenderDebts, + CurrentBorrowerDebts = dto.CurrentBorrowerDebts, + CurrentNotifications = dto.CurrentNotifications, + CurrentFeedbacks = dto.CurrentFeedbacks, + CurrentVideos = dto.CurrentVideos + }; } } -} +} \ No newline at end of file diff --git a/FinTrack/Styles/ModernStyles.xaml b/FinTrack/Styles/ModernStyles.xaml index e7dcce7..228aa82 100644 --- a/FinTrack/Styles/ModernStyles.xaml +++ b/FinTrack/Styles/ModernStyles.xaml @@ -1,53 +1,50 @@ - - - - - - - - - - - - - - #FF58A6FF + + + + + + - - + - - - - - + + + + - + + + - + + + #58A6FF + - - - - - + + + + + + + + + + + + - - - - - - + - + Segoe UI 6 - + - + - - + + - + - + - - + + - + - - + + - + - - + + diff --git a/FinTrack/ViewModels/AccountViewModel.cs b/FinTrack/ViewModels/AccountViewModel.cs index a2c99fc..5448463 100644 --- a/FinTrack/ViewModels/AccountViewModel.cs +++ b/FinTrack/ViewModels/AccountViewModel.cs @@ -93,7 +93,7 @@ private void InitializeEmptyChart() private async Task InitializeViewModel() { - await LoadData(); + ApplyFilters(); if (FilteredAccounts.Any()) { @@ -118,13 +118,6 @@ partial void OnSelectedAccountChanged(AccountModel? value) } } - private async Task LoadData() - { - await _accountStore.LoadAccountsAsync(); - - ApplyFilters(); - } - private async Task LoadTransactionHistory(int accountId, string accountName) { var transactions = await _apiService.GetAsync>($"Transactions/account-id/{accountId}"); diff --git a/FinTrack/Models/Transaction/AddCategoryViewModel.cs b/FinTrack/ViewModels/AddCategoryViewModel.cs similarity index 96% rename from FinTrack/Models/Transaction/AddCategoryViewModel.cs rename to FinTrack/ViewModels/AddCategoryViewModel.cs index 209bdbf..ebb53c1 100644 --- a/FinTrack/Models/Transaction/AddCategoryViewModel.cs +++ b/FinTrack/ViewModels/AddCategoryViewModel.cs @@ -34,7 +34,7 @@ private async Task Save() return; } - var createDto = new TransactionCategoryDto + var createDto = new TransactionCategoryCreateDto { Name = this.Name, Type = this.Type diff --git a/FinTrack/ViewModels/AppSettingsContentViewModel.cs b/FinTrack/ViewModels/AppSettingsContentViewModel.cs index e3cb347..19aa57a 100644 --- a/FinTrack/ViewModels/AppSettingsContentViewModel.cs +++ b/FinTrack/ViewModels/AppSettingsContentViewModel.cs @@ -2,10 +2,11 @@ using CommunityToolkit.Mvvm.Input; using FinTrackForWindows.Dtos.SettingsDtos; using FinTrackForWindows.Enums; -using FinTrackForWindows.Services.Api; +using FinTrackForWindows.Services.AppInNotifications; +using FinTrackForWindows.Services.ApplySettings; +using FinTrackForWindows.Services.Users; using Microsoft.Extensions.Logging; using System.Collections.ObjectModel; -using System.Windows; namespace FinTrackForWindows.ViewModels { @@ -13,6 +14,7 @@ public partial class AppSettingsContentViewModel : ObservableObject { public ObservableCollection AppearanceTypes { get; } public ObservableCollection CurrencyTypes { get; } + public ObservableCollection LanguageTypes { get; } [ObservableProperty] private AppearanceType _selectedAppearanceType; @@ -21,7 +23,7 @@ public partial class AppSettingsContentViewModel : ObservableObject private BaseCurrencyType _selectedCurrencyType; [ObservableProperty] - private bool _startWithWindows; + private LanguageType _selectedLanguageType; [ObservableProperty] private bool _isLoading; @@ -31,41 +33,42 @@ public partial class AppSettingsContentViewModel : ObservableObject private bool _isSaving; private readonly ILogger _logger; - private readonly IApiService _apiService; + private readonly IUserStore _userStore; + private readonly IAppInNotificationService _appInNotificationService; + private readonly IApplySettingsService _applySettingsService; - public AppSettingsContentViewModel(ILogger logger, IApiService apiService) + public AppSettingsContentViewModel(ILogger logger, + IUserStore userStore, IAppInNotificationService appInNotificationService, + IApplySettingsService applySettingsService) { _logger = logger; - _apiService = apiService; + _userStore = userStore; + _appInNotificationService = appInNotificationService; + _applySettingsService = applySettingsService; AppearanceTypes = new ObservableCollection(Enum.GetValues()); CurrencyTypes = new ObservableCollection(Enum.GetValues()); + LanguageTypes = new ObservableCollection(Enum.GetValues()); - _ = LoadAppSettings(); + _userStore.UserChanged += OnUserChanged; + LoadDataFromStore(); } - private async Task LoadAppSettings() + private void OnUserChanged() + { + LoadDataFromStore(); + } + + private void LoadDataFromStore() { IsLoading = true; - try - { - var settings = await _apiService.GetAsync("UserSettings/AppSettings"); - if (settings != null) - { - SelectedAppearanceType = settings.Appearance; - SelectedCurrencyType = settings.Currency; - // StartWithWindows = settings.StartWithWindows; // DTO'ya eklendiğinde bu satır açılmalı - } - } - catch (Exception ex) + if (_userStore.CurrentUser != null) { - _logger.LogError(ex, "Failed to load application settings."); - MessageBox.Show("Could not load application settings. Default values will be used.", "Error", MessageBoxButton.OK, MessageBoxImage.Warning); - } - finally - { - IsLoading = false; + SelectedAppearanceType = _userStore.CurrentUser.Thema; + SelectedCurrencyType = _userStore.CurrentUser.Currency; + SelectedLanguageType = _userStore.CurrentUser.Language; } + IsLoading = false; } [RelayCommand(CanExecute = nameof(CanSaveChanges))] @@ -77,22 +80,30 @@ private async Task SaveChanges() var settingsToUpdate = new UserAppSettingsUpdateDto { Appearance = SelectedAppearanceType, - Currency = SelectedCurrencyType - // StartWithWindows = this.StartWithWindows; // DTO'ya eklendiğinde bu satır açılmalı + Currency = SelectedCurrencyType, + Language = SelectedLanguageType, }; - await _apiService.PostAsync("UserSettings/AppSettings", settingsToUpdate); - - // TODO: StartWithWindows ayarını gerçekten sisteme uygulayacak bir servis çağrısı burada yapılmalı. - // Örneğin: _startupService.SetStartup(this.StartWithWindows); + bool success = await _userStore.UpdateAppSettingsAsync(settingsToUpdate); + if (success) + { + _logger.LogInformation("Application settings have been updated by the user."); + _appInNotificationService.ShowSuccess("Application settings saved successfully!"); - _logger.LogInformation("Application settings have been updated by the user."); - MessageBox.Show("Settings saved successfully!", "Application Settings", MessageBoxButton.OK, MessageBoxImage.Information); + _applySettingsService.AppearanceApply(); + _applySettingsService.BaseCurrencyApply(); + _applySettingsService.LanguageApply(); + } + else + { + _logger.LogError("Failed to save application settings via UserStore."); + _appInNotificationService.ShowError("An error occurred while saving your settings. Please try again."); + } } catch (Exception ex) { - _logger.LogError(ex, "Failed to save application settings."); - MessageBox.Show("An error occurred while saving your settings. Please try again.", "Error", MessageBoxButton.OK, MessageBoxImage.Error); + _logger.LogError(ex, "An exception occurred while saving application settings."); + _appInNotificationService.ShowError("An unexpected error occurred. Please try again."); } finally { diff --git a/FinTrack/ViewModels/BottomBarViewModel.cs b/FinTrack/ViewModels/BottomBarViewModel.cs index 62b85c8..4382968 100644 --- a/FinTrack/ViewModels/BottomBarViewModel.cs +++ b/FinTrack/ViewModels/BottomBarViewModel.cs @@ -1,12 +1,13 @@ using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; +using FinTrackForWindows.Services.StoresRefresh; using Microsoft.Extensions.Logging; using System.Windows; namespace FinTrackForWindows.ViewModels { - public partial class BottomBarViewModel : ObservableObject + public partial class BottomBarViewModel : ObservableObject, IDisposable { [ObservableProperty] private string company = string.Empty; @@ -15,19 +16,60 @@ public partial class BottomBarViewModel : ObservableObject private string version = string.Empty; [ObservableProperty] - private string lastSyncStatus = string.Empty; + private string lastSyncStatus = "Awaiting first synchronization..."; private readonly ILogger _logger; + private readonly IStoresRefresh _storesRefresh; + private readonly Timer _refreshTimer; - public BottomBarViewModel(ILogger logger) + public BottomBarViewModel(ILogger logger, IStoresRefresh storesRefresh) { _logger = logger; + _storesRefresh = storesRefresh; int year = DateTime.Now.Year; Company = $"© {year} FinTrack Inc."; - Version = "v1.0.0"; - LastSyncStatus = "Last Synchronization: Successful"; + + _storesRefresh.RefreshStarted += OnRefreshStarted; + _storesRefresh.RefreshCompleted += OnRefreshCompleted; + + _refreshTimer = new Timer( + callback: RefreshStores, + state: null, + dueTime: TimeSpan.FromSeconds(5), + period: TimeSpan.FromMinutes(5)); + } + + private void OnRefreshStarted() + { + Application.Current.Dispatcher.Invoke(() => + { + LastSyncStatus = "Synchronization in progress..."; + }); + } + + private void OnRefreshCompleted(bool success) + { + Application.Current.Dispatcher.Invoke(() => + { + if (success) + { + LastSyncStatus = $"Last Synchronization: Successful at {DateTime.Now:HH:mm:ss}"; + _logger.LogInformation("Synchronization completed successfully."); + } + else + { + LastSyncStatus = $"Last Synchronization: Failed at {DateTime.Now:HH:mm:ss}"; + _logger.LogWarning("Synchronization failed."); + } + }); + } + + private async void RefreshStores(object? state) + { + _logger.LogInformation("Periodic synchronization triggered."); + await _storesRefresh.RefreshAllStoresAsync(); } [RelayCommand] @@ -36,5 +78,13 @@ private void AddNewTransaction() _logger.LogInformation("Adding new transaction..."); MessageBox.Show("The add new transaction feature is not implemented yet.", "Information", MessageBoxButton.OK, MessageBoxImage.Information); } + + public void Dispose() + { + _refreshTimer?.Dispose(); + _storesRefresh.RefreshStarted -= OnRefreshStarted; + _storesRefresh.RefreshCompleted -= OnRefreshCompleted; + GC.SuppressFinalize(this); + } } } diff --git a/FinTrack/ViewModels/BudgetViewModel.cs b/FinTrack/ViewModels/BudgetViewModel.cs index 806f7d9..0db22e8 100644 --- a/FinTrack/ViewModels/BudgetViewModel.cs +++ b/FinTrack/ViewModels/BudgetViewModel.cs @@ -72,7 +72,6 @@ public BudgetViewModel(IBudgetStore budgetStore, ILogger logger private async Task InitializeViewModelAsync() { await LoadCategoriesAsync(); - await _budgetStore.LoadBudgetsAsync(); PrepareForNewBudget(); } diff --git a/FinTrack/ViewModels/CurrenciesViewModel.cs b/FinTrack/ViewModels/CurrenciesViewModel.cs index 0098957..14ab203 100644 --- a/FinTrack/ViewModels/CurrenciesViewModel.cs +++ b/FinTrack/ViewModels/CurrenciesViewModel.cs @@ -16,6 +16,7 @@ using Microsoft.Extensions.Logging; using SkiaSharp; using System.Collections.ObjectModel; +using System.Collections.Specialized; using System.Globalization; namespace FinTrackForWindows.ViewModels @@ -86,7 +87,9 @@ public CurrenciesViewModel(ILogger logger, ICurrenciesStore _currenciesStore = currenciesStore; InitializeEmptyChart(); - _ = InitializeDataAsync(); + InitializeData(); + + _currenciesStore.CurrenciesChanged += OnCurrenciesStoreChanged; } partial void OnCurrencySearchChanged(string value) @@ -104,14 +107,13 @@ async partial void OnSelectedCurrencyChanged(CurrencyModel? value) await LoadHistoricalDataAsync(value.ToCurrencyCode); } - private async Task InitializeDataAsync() + private void InitializeData() { try { - await _currenciesStore.LoadCurrenciesAsync(); FilterCurrencies(); - if (FilteredCurrencies.Any()) + if (FilteredCurrencies.Any() && SelectedCurrency == null) { SelectedCurrency = FilteredCurrencies.First(); } @@ -312,9 +314,9 @@ private void UpdateDetails(ChangeSummaryDto? summary) return; } - SelectedCurrency.DailyLow = "N/A"; - SelectedCurrency.DailyHigh = "N/A"; - + SelectedCurrency.DailyHigh = summary.DailyHigh.ToString() ?? "N/A"; + SelectedCurrency.DailyLow = summary.DailyLow.ToString() ?? "N/A"; + (SelectedCurrency.DailyChange, SelectedCurrency.WeeklyChangeType) = FormatChangeValue(summary.DailyChangePercentage); (SelectedCurrency.WeeklyChange, SelectedCurrency.WeeklyChangeType) = FormatChangeValue(summary.WeeklyChangePercentage); (SelectedCurrency.MonthlyChange, SelectedCurrency.MonthlyChangeType) = FormatChangeValue(summary.MonthlyChangePercentage); (SelectedCurrency.ToCurrencyChange, SelectedCurrency.Type) = FormatChangeValue(summary.DailyChangePercentage); @@ -376,5 +378,26 @@ private void FilterCurrencies() } _logger.LogInformation("Currencies filtered by search text: '{SearchText}'.", CurrencySearch); } + + private void OnCurrenciesStoreChanged(object? sender, NotifyCollectionChangedEventArgs e) + { + App.Current.Dispatcher.Invoke(() => + { + _logger.LogInformation("CurrenciesStore has been updated in the background. Re-filtering the list."); + var previouslySelectedCode = SelectedCurrency?.ToCurrencyCode; + FilterCurrencies(); + + if (previouslySelectedCode != null) + { + SelectedCurrency = FilteredCurrencies.FirstOrDefault(c => c.ToCurrencyCode == previouslySelectedCode); + } + + if (SelectedCurrency == null && FilteredCurrencies.Any()) + { + SelectedCurrency = FilteredCurrencies.FirstOrDefault(); + } + }); + } + } } \ No newline at end of file diff --git a/FinTrack/ViewModels/DashboardViewModel.cs b/FinTrack/ViewModels/DashboardViewModel.cs index 1860168..ad4d7c5 100644 --- a/FinTrack/ViewModels/DashboardViewModel.cs +++ b/FinTrack/ViewModels/DashboardViewModel.cs @@ -120,25 +120,17 @@ public DashboardViewModel( _transactionStore.TransactionsChanged += OnTransactionsChanged; _membershipStore.CurrentUserMembershipChanged += OnMembershipChanged; _debtStore.DebtsChanged += OnDebtsChanged; + _currenciesStore.CurrenciesChanged += OnCurrenciesChanged; if (SessionManager.IsLoggedIn) { - _logger.LogInformation("Kullanıcı zaten giriş yapmış. DashboardViewModel verileri yüklüyor."); + _logger.LogInformation("The user is already logged in. DashboardViewModel is loading data."); _ = LoadInitialDataAsync(); } } private async Task LoadInitialDataAsync() { - await Task.WhenAll( - _budgetStore.LoadBudgetsAsync(), - _accountStore.LoadAccountsAsync(), - _transactionStore.LoadTransactionsAsync(), - _currenciesStore.LoadCurrenciesAsync(), - _membershipStore.LoadAllMembershipDataAsync(), - _debtStore.LoadDebtsAsync() - ); - RefreshDashboardBudgets(); RefreshDashboardAccounts(); RefreshDashboardTransactions(); @@ -392,6 +384,16 @@ private void RefreshDashboardCurrencies() } } + private void OnCurrenciesChanged(object? sender, NotifyCollectionChangedEventArgs e) + { + App.Current.Dispatcher.Invoke(() => + { + _logger.LogInformation("CurrenciesStore changed, refreshing dashboard currencies and total balance."); + RefreshDashboardCurrencies(); + _ = CalculateTotalBalance(); + }); + } + // ------ private void RefreshDashboardMembership() diff --git a/FinTrack/ViewModels/NotificationSettingsContentViewModel.cs b/FinTrack/ViewModels/NotificationSettingsContentViewModel.cs index 7cd5b53..9949f2d 100644 --- a/FinTrack/ViewModels/NotificationSettingsContentViewModel.cs +++ b/FinTrack/ViewModels/NotificationSettingsContentViewModel.cs @@ -3,8 +3,7 @@ using FinTrackForWindows.Dtos.SettingsDtos; using FinTrackForWindows.Enums; using FinTrackForWindows.Models.Settings; -using FinTrackForWindows.Services.Api; -using FinTrackForWindows.Services.AppInNotifications; +using FinTrackForWindows.Services.Users; using Microsoft.Extensions.Logging; using System.Collections.ObjectModel; @@ -14,106 +13,68 @@ public partial class NotificationSettingsContentViewModel : ObservableObject { public ObservableCollection EmailNotificationSettings { get; } - [ObservableProperty] - private bool _enableDesktopNotifications; - - [ObservableProperty] - private bool _isLoading; - - [ObservableProperty] - [NotifyCanExecuteChangedFor(nameof(SaveChangesCommand))] - private bool _isSaving; + [ObservableProperty] private bool _enableDesktopNotifications; + [ObservableProperty] private bool _isLoading; + [ObservableProperty, NotifyCanExecuteChangedFor(nameof(SaveChangesCommand))] private bool _isSaving; private readonly ILogger _logger; - private readonly IApiService _apiService; - private readonly IAppInNotificationService _appInNotificationService; + private readonly IUserStore _userStore; - public NotificationSettingsContentViewModel(ILogger logger, IApiService apiService, IAppInNotificationService appInNotificationService) + public NotificationSettingsContentViewModel(ILogger logger, IUserStore userStore) { _logger = logger; - _apiService = apiService; - _appInNotificationService = appInNotificationService; - + _userStore = userStore; EmailNotificationSettings = new ObservableCollection(); - _ = LoadSettings(); + + _userStore.UserChanged += OnUserChanged; + LoadDataFromStore(); } - private async Task LoadSettings() + private void OnUserChanged() { - IsLoading = true; - try - { - var settingsDto = await _apiService.GetAsync("UserSettings/UserNotificationSettings"); - EmailNotificationSettings.Clear(); + LoadDataFromStore(); + } - if (settingsDto != null) - { - // DTO'dan Model Listesine Mapping - EmailNotificationSettings.Add(new NotificationSettingItemModel(NotificationSettingsType.SpendingLimitWarning, settingsDto.SpendingLimitWarning)); - EmailNotificationSettings.Add(new NotificationSettingItemModel(NotificationSettingsType.ExpectedBillReminder, settingsDto.ExpectedBillReminder)); - EmailNotificationSettings.Add(new NotificationSettingItemModel(NotificationSettingsType.WeeklySpendingSummary, settingsDto.WeeklySpendingSummary)); - EmailNotificationSettings.Add(new NotificationSettingItemModel(NotificationSettingsType.NewFeaturesAndAnnouncements, settingsDto.NewFeaturesAndAnnouncements)); - EnableDesktopNotifications = settingsDto.EnableDesktopNotifications; - } - else - { - // API'den veri gelmezse varsayılan değerlerle doldur - InitializeDefaultSettings(); - } - } - catch (Exception ex) - { - _logger.LogError(ex, "Failed to load notification settings."); - _appInNotificationService.ShowError("Failed to load notification settings. Default values will be used.", ex); - InitializeDefaultSettings(); - } - finally + private void LoadDataFromStore() + { + IsLoading = true; + EmailNotificationSettings.Clear(); + if (_userStore.CurrentUser != null) { - IsLoading = false; + EmailNotificationSettings.Add(new NotificationSettingItemModel(NotificationSettingsType.SpendingLimitWarning, _userStore.CurrentUser.SpendingLimitWarning)); + EmailNotificationSettings.Add(new NotificationSettingItemModel(NotificationSettingsType.ExpectedBillReminder, _userStore.CurrentUser.ExpectedBillReminder)); + EmailNotificationSettings.Add(new NotificationSettingItemModel(NotificationSettingsType.WeeklySpendingSummary, _userStore.CurrentUser.WeeklySpendingSummary)); + EmailNotificationSettings.Add(new NotificationSettingItemModel(NotificationSettingsType.NewFeaturesAndAnnouncements, _userStore.CurrentUser.NewFeaturesAndAnnouncements)); + EnableDesktopNotifications = _userStore.CurrentUser.EnableDesktopNotifications; } + IsLoading = false; } [RelayCommand(CanExecute = nameof(CanSaveChanges))] private async Task SaveChanges() { IsSaving = true; - try + var dto = new UserNotificationSettingsUpdateDto { - var settingsUpdateDto = new UserNotificationSettingsUpdateDto - { - EnableDesktopNotifications = this.EnableDesktopNotifications, - SpendingLimitWarning = EmailNotificationSettings.FirstOrDefault(s => s.SettingType == NotificationSettingsType.SpendingLimitWarning)?.IsEnabled ?? false, - ExpectedBillReminder = EmailNotificationSettings.FirstOrDefault(s => s.SettingType == NotificationSettingsType.ExpectedBillReminder)?.IsEnabled ?? false, - WeeklySpendingSummary = EmailNotificationSettings.FirstOrDefault(s => s.SettingType == NotificationSettingsType.WeeklySpendingSummary)?.IsEnabled ?? false, - NewFeaturesAndAnnouncements = EmailNotificationSettings.FirstOrDefault(s => s.SettingType == NotificationSettingsType.NewFeaturesAndAnnouncements)?.IsEnabled ?? false - }; + EnableDesktopNotifications = this.EnableDesktopNotifications, + SpendingLimitWarning = EmailNotificationSettings.FirstOrDefault(s => s.SettingType == NotificationSettingsType.SpendingLimitWarning)?.IsEnabled ?? false, + ExpectedBillReminder = EmailNotificationSettings.FirstOrDefault(s => s.SettingType == NotificationSettingsType.ExpectedBillReminder)?.IsEnabled ?? false, + WeeklySpendingSummary = EmailNotificationSettings.FirstOrDefault(s => s.SettingType == NotificationSettingsType.WeeklySpendingSummary)?.IsEnabled ?? false, + NewFeaturesAndAnnouncements = EmailNotificationSettings.FirstOrDefault(s => s.SettingType == NotificationSettingsType.NewFeaturesAndAnnouncements)?.IsEnabled ?? false + }; - await _apiService.PostAsync("UserSettings/UserNotificationSettings", settingsUpdateDto); - - _logger.LogInformation("User notification settings have been successfully saved."); - _appInNotificationService.ShowInfo("Your notification settings have been saved successfully."); - } - catch (Exception ex) + bool success = await _userStore.UpdateNotificationSettingsAsync(dto); + if (success) { - _logger.LogError(ex, "Failed to save notification settings."); - _appInNotificationService.ShowError("Failed to save notification settings.", ex); + _logger.LogInformation("Notification settings saved successfully."); } - finally + else { - IsSaving = false; + _logger.LogError("Failed to save notification settings."); } + IsSaving = false; } private bool CanSaveChanges() => !IsSaving; - - private void InitializeDefaultSettings() - { - EmailNotificationSettings.Clear(); - EmailNotificationSettings.Add(new NotificationSettingItemModel(NotificationSettingsType.SpendingLimitWarning, true)); - EmailNotificationSettings.Add(new NotificationSettingItemModel(NotificationSettingsType.ExpectedBillReminder, true)); - EmailNotificationSettings.Add(new NotificationSettingItemModel(NotificationSettingsType.WeeklySpendingSummary, false)); - EmailNotificationSettings.Add(new NotificationSettingItemModel(NotificationSettingsType.NewFeaturesAndAnnouncements, false)); - EnableDesktopNotifications = true; - } } } \ No newline at end of file diff --git a/FinTrack/ViewModels/ProfileSettingsContentViewModel.cs b/FinTrack/ViewModels/ProfileSettingsContentViewModel.cs index f4ecf28..1e3d6bc 100644 --- a/FinTrack/ViewModels/ProfileSettingsContentViewModel.cs +++ b/FinTrack/ViewModels/ProfileSettingsContentViewModel.cs @@ -1,60 +1,106 @@ using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using FinTrackForWindows.Dtos.SettingsDtos; -using FinTrackForWindows.Services.Api; using FinTrackForWindows.Services.AppInNotifications; +using FinTrackForWindows.Services.Dialog; +using FinTrackForWindows.Services.Users; using Microsoft.Extensions.Logging; namespace FinTrackForWindows.ViewModels { public partial class ProfileSettingsContentViewModel : ObservableObject { - [ObservableProperty] - private string fullName = string.Empty; - - [ObservableProperty] - private string email = string.Empty; - - [ObservableProperty] - private string profilePhotoUrl = string.Empty; + [ObservableProperty] private string firstName = string.Empty; + [ObservableProperty] private string lastName = string.Empty; + [ObservableProperty] private string email = string.Empty; + [ObservableProperty] private string profilePhotoUrl = string.Empty; + [ObservableProperty, NotifyCanExecuteChangedFor(nameof(SaveChangesCommand))] private bool _isSaving; private readonly ILogger _logger; - - private readonly IApiService _apiService; - + private readonly IUserStore _userStore; + private readonly IDialogService _dialogService; private readonly IAppInNotificationService _appInNotificationService; - public ProfileSettingsContentViewModel(ILogger logger, IApiService apiService, IAppInNotificationService appInNotificationService) + public ProfileSettingsContentViewModel(ILogger logger, IUserStore userStore, IDialogService dialogService, IAppInNotificationService appInNotificationService) { _logger = logger; - _apiService = apiService; + _userStore = userStore; + _dialogService = dialogService; _appInNotificationService = appInNotificationService; - _ = LoadProfileData(); + _userStore.UserChanged += OnUserChanged; + LoadDataFromStore(); + } + + private void OnUserChanged() + { + LoadDataFromStore(); } - private async Task LoadProfileData() + private void LoadDataFromStore() { - var profile = await _apiService.GetAsync("UserSettings/ProfileSettings"); - if (profile != null) + if (_userStore.CurrentUser != null) { - FullName = profile.FullName; - Email = profile.Email; - ProfilePhotoUrl = profile.ProfilePictureUrl ?? "N/A"; + var nameParts = _userStore.CurrentUser.UserName.Split('_'); + FirstName = nameParts.FirstOrDefault() ?? string.Empty; + LastName = nameParts.Length > 1 ? nameParts.Last() : string.Empty; + Email = _userStore.CurrentUser.Email; + ProfilePhotoUrl = _userStore.CurrentUser.ProfilePictureUrl ?? string.Empty; } } [RelayCommand] - private async Task ProfileSettingsContentSaveChanges() + private async Task RequestEmailChange() { - await _apiService.PostAsync("UserSettings/ProfileSettings", new ProfileSettingsUpdateDto + string newEmail = this.Email; + if (newEmail == _userStore.CurrentUser?.Email) + { + _appInNotificationService.ShowWarning("The new email address cannot be the same as the current one."); + return; + } + + await _userStore.RequestEmailChangeOtpAsync(); + var otp = _dialogService.ShowOtpDialog(newEmail); + + if (!string.IsNullOrEmpty(otp)) { - FullName = FullName, - Email = Email, - ProfilePictureUrl = ProfilePhotoUrl - }); - _logger.LogInformation("New profile information saved: {FullName}, {Email}, {ProfilePhotoUrl}", FullName, Email, ProfilePhotoUrl); - _appInNotificationService.ShowSuccess("Your profile information has been successfully saved."); + var dto = new UpdateUserEmailDto { NewEmail = newEmail, OtpCode = otp }; + bool success = await _userStore.UpdateUserEmailAsync(dto); + if (success) + { + _appInNotificationService.ShowSuccess("Your email has been changed successfully."); + } + else + { + _appInNotificationService.ShowError("Failed to change your email. The OTP may be incorrect or expired."); + } + } } + + [RelayCommand(CanExecute = nameof(CanSaveChanges))] + private async Task SaveChanges() + { + IsSaving = true; + + var nameDto = new UpdateUserNameDto { FirstName = this.FirstName, LastName = this.LastName }; + var picDto = new UpdateProfilePictureDto { ProfilePictureUrl = this.ProfilePhotoUrl }; + + bool nameSuccess = await _userStore.UpdateUserNameAsync(nameDto); + bool picSuccess = await _userStore.UpdateProfilePictureAsync(picDto); + + if (nameSuccess && picSuccess) + { + _logger.LogInformation("Profile settings saved successfully."); + _appInNotificationService.ShowSuccess("Your profile has been updated."); + } + else + { + _logger.LogError("Failed to save one or more profile settings."); + _appInNotificationService.ShowError("There was an issue saving your profile."); + } + IsSaving = false; + } + + private bool CanSaveChanges() => !IsSaving; } -} +} \ No newline at end of file diff --git a/FinTrack/ViewModels/SecuritySettingsContentViewModel.cs b/FinTrack/ViewModels/SecuritySettingsContentViewModel.cs index 846c0bb..b09705b 100644 --- a/FinTrack/ViewModels/SecuritySettingsContentViewModel.cs +++ b/FinTrack/ViewModels/SecuritySettingsContentViewModel.cs @@ -1,33 +1,55 @@ using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; +using FinTrackForWindows.Dtos.SettingsDtos; using FinTrackForWindows.Services.AppInNotifications; +using FinTrackForWindows.Services.Users; using Microsoft.Extensions.Logging; namespace FinTrackForWindows.ViewModels { public partial class SecuritySettingsContentViewModel : ObservableObject { - [ObservableProperty] - private string currentPassword = string.Empty; - - [ObservableProperty] - private string newPassword = string.Empty; - - readonly ILogger _logger; + [ObservableProperty] private string currentPassword = string.Empty; + [ObservableProperty] private string newPassword = string.Empty; + [ObservableProperty, NotifyCanExecuteChangedFor(nameof(SaveChangesCommand))] private bool _isSaving; + private readonly ILogger _logger; private readonly IAppInNotificationService _appInNotificationService; + private readonly IUserStore _userStore; - public SecuritySettingsContentViewModel(ILogger logger, IAppInNotificationService appInNotificationService) + public SecuritySettingsContentViewModel(ILogger logger, IAppInNotificationService appInNotificationService, IUserStore userStore) { _logger = logger; _appInNotificationService = appInNotificationService; + _userStore = userStore; } - [RelayCommand] - private void SecuritySettingsContentSaveChanges() + [RelayCommand(CanExecute = nameof(CanSaveChanges))] + private async Task SaveChanges() { - _logger.LogInformation("Security settings saved."); - _appInNotificationService.ShowSuccess("Security settings saved."); + IsSaving = true; + var dto = new UpdateUserPasswordDto + { + CurrentPassword = this.CurrentPassword, + NewPassword = this.NewPassword + }; + + bool success = await _userStore.UpdateUserPasswordAsync(dto); + if (success) + { + _logger.LogInformation("Password updated successfully."); + _appInNotificationService.ShowSuccess("Your password has been changed successfully."); + CurrentPassword = string.Empty; + NewPassword = string.Empty; + } + else + { + _logger.LogError("Failed to update password."); + _appInNotificationService.ShowError("Failed to change your password. Please check your current password."); + } + IsSaving = false; } + + private bool CanSaveChanges() => !IsSaving; } -} +} \ No newline at end of file diff --git a/FinTrack/ViewModels/TopBarViewModel.cs b/FinTrack/ViewModels/TopBarViewModel.cs index c68dd78..d9e10a7 100644 --- a/FinTrack/ViewModels/TopBarViewModel.cs +++ b/FinTrack/ViewModels/TopBarViewModel.cs @@ -65,12 +65,12 @@ private async Task LoadProfile() if (userProfile != null) { - UserAvatar = userProfile.ProfilePictureUrl; + UserAvatar = userProfile.ProfilePictureUrl ?? string.Empty; UserFullName = userProfile.UserName; UserEmail = userProfile.Email; - UserMembershipType = $"{userProfile.MembershipPlan} Member"; + UserMembershipType = $"{userProfile.CurrentMembershipPlanType} Member"; _logger.LogInformation("User profile loaded successfully. Name: {UserName}, Email: {Email}, Membership Type: {MembershipType}", - userProfile.UserName, userProfile.Email, userProfile.MembershipPlan); + userProfile.UserName, userProfile.Email, userProfile.CurrentMembershipPlanType); } else { diff --git a/FinTrack/ViewModels/TransactionsViewModel.cs b/FinTrack/ViewModels/TransactionsViewModel.cs index d76bc89..abca109 100644 --- a/FinTrack/ViewModels/TransactionsViewModel.cs +++ b/FinTrack/ViewModels/TransactionsViewModel.cs @@ -523,9 +523,7 @@ private async Task AddNewCategory() private async Task LoadData() { - await _accountStore.LoadAccountsAsync(); await LoadCategories(); - await _transactionStore.LoadTransactionsAsync(); } } } \ No newline at end of file diff --git a/FinTrack/Views/AccountView.xaml b/FinTrack/Views/AccountView.xaml index b224d10..46724b5 100644 --- a/FinTrack/Views/AccountView.xaml +++ b/FinTrack/Views/AccountView.xaml @@ -1,12 +1,12 @@  @@ -26,7 +26,7 @@ - + @@ -37,19 +37,19 @@ - + - + - + @@ -120,10 +120,10 @@ - + - + @@ -133,7 +133,7 @@ - + diff --git a/FinTrack/Views/AddCategoryWindow.xaml b/FinTrack/Views/AddCategoryWindow.xaml index 31493a5..0427673 100644 --- a/FinTrack/Views/AddCategoryWindow.xaml +++ b/FinTrack/Views/AddCategoryWindow.xaml @@ -19,18 +19,18 @@ - + - + - - - -