Calendar buttons etc.

This commit is contained in:
Burak Kaan Köse
2026-03-20 13:26:16 +01:00
parent 1fe569e0ac
commit eb8cd7651d
8 changed files with 94 additions and 586 deletions
@@ -910,19 +910,37 @@ public partial class CalendarPageViewModel : CalendarBaseViewModel,
}
}
List<DateRange> flipItemRanges = [];
if (calendarLoadDirection == CalendarLoadDirection.Replace &&
StatePersistanceService.CalendarDisplayType == CalendarDisplayType.Month)
{
var previousRange = strategy.GetPreviousDateRange(flipLoadRange, StatePersistanceService.DayDisplayCount);
var nextRange = strategy.GetNextDateRange(flipLoadRange, StatePersistanceService.DayDisplayCount);
flipItemRanges.Add(previousRange);
flipItemRanges.Add(flipLoadRange);
flipItemRanges.Add(nextRange);
}
else
{
// Create day ranges for each flip item until we reach the total days to load.
int totalFlipItemCount = (int)Math.Ceiling((double)flipLoadRange.TotalDays / eachFlipItemCount);
List<DayRangeRenderModel> renderModels = new();
for (int i = 0; i < totalFlipItemCount; i++)
{
var startDate = flipLoadRange.StartDate.AddDays(i * eachFlipItemCount);
var endDate = startDate.AddDays(eachFlipItemCount);
var range = new DateRange(startDate, endDate);
var renderOptions = new CalendarRenderOptions(range, CurrentSettings);
flipItemRanges.Add(new DateRange(startDate, endDate));
}
}
List<DayRangeRenderModel> renderModels = [];
foreach (var range in flipItemRanges)
{
var renderOptions = new CalendarRenderOptions(range, CurrentSettings);
var dayRangeHeaderModel = new DayRangeRenderModel(renderOptions);
renderModels.Add(dayRangeHeaderModel);
}
@@ -84,7 +84,6 @@
"Buttons_SyncAliases": "Synchronize Aliases",
"Buttons_TryAgain": "Try Again",
"Buttons_Yes": "Yes",
"Purchased": "Purchased",
"Sync_SynchronizingFolder": "Synchronizing {0} {1}%",
"Sync_DownloadedMessages": "Downloaded {0} messages from {1}",
"SyncAction_Archiving": "Archiving {0} mail(s)",
@@ -830,12 +829,6 @@
"SettingsHome_StorageCard_Description": "See how much local MIME content Wino keeps on this device and clean it up when needed.",
"SettingsHome_StorageEmptySummary": "No cached MIME content detected yet.",
"SettingsHome_StorageLoading": "Checking local MIME usage...",
"SettingsHome_WinoAccount_Title": "Wino Account",
"SettingsHome_WinoAccount_SignedOutDescription": "Sign in to sync settings across devices and access add-ons like Wino AI Pack.",
"SettingsHome_WinoAccount_ManageLink": "Manage Wino Account",
"SettingsHome_AiPack_Title": "Wino AI Pack",
"SettingsHome_SettingsSync_Title": "Settings sync",
"SettingsHome_SettingsSync_Description": "Export or import your preferences to keep them in sync across devices.",
"SettingsHome_Tips_Title": "Tips & tricks",
"SettingsHome_Tips_Description": "A few small changes can make Wino feel much more personal.",
"SettingsHome_Tip_Theme": "Want dark mode or accent changes? Open Personalization.",
@@ -856,58 +849,6 @@
"SettingsSearch_Personalization_Keywords": "theme;dark;light;appearance;accent;color;colour;mode;layout;density",
"SettingsSearch_About_Keywords": "about;version;website;privacy;github;donate;store;support",
"SettingsSearch_KeyboardShortcuts_Keywords": "shortcut;shortcuts;hotkey;hotkeys;keyboard;keys",
"SettingsSearch_WinoAccount_Keywords": "winoaccount;account;accounts;wino",
"WinoAccount_Management_Description": "Manage your Wino Account, AI Pack access, and synchronized settings.",
"WinoAccount_Management_SignedOutTitle": "Sign in to Wino Mail",
"WinoAccount_Management_SignedOutDescription": "Sign in or create an account to sync your email, access AI features, and manage your settings across devices.",
"WinoAccount_Management_ProfileSectionHeader": "Profile",
"WinoAccount_Management_AddOnsSectionHeader": "Wino Add-Ons",
"WinoAccount_Management_DataSectionHeader": "Data",
"WinoAccount_Management_AccountActionsSectionHeader": "Account actions",
"WinoAccount_Management_AccountCardTitle": "Account",
"WinoAccount_Management_AccountCardDescription": "Your Wino Account email address and current account state.",
"WinoAccount_Management_AiPackCardTitle": "AI Pack",
"WinoAccount_Management_AiPackCardDescription": "See whether Wino AI Pack is active and how much usage is left.",
"WinoAccount_Management_AiPackActive": "AI Pack is active",
"WinoAccount_Management_AiPackInactive": "AI Pack is not active",
"WinoAccount_Management_AiPackUsage": "{0} of {1} uses consumed. {2} remaining.",
"WinoAccount_Management_AiPackBillingPeriod": "Billing period: {0:d} - {1:d}",
"WinoAccount_Management_AiPackUnknownUsage": "Usage details are not available yet.",
"WinoAccount_Management_AiPackBuyDescription": "Buy Wino AI Pack to translate, rewrite or summarize emails with AI.",
"WinoAccount_Management_AiPackPromoTitle": "Unlock AI Pack",
"WinoAccount_Management_AiPackPromoDescription": "Supercharge your email workflow with AI-powered tools. Translate messages into 50+ languages, rewrite for clarity and tone, and get instant summaries of long threads.",
"WinoAccount_Management_AiPackPromoPrice": "$4.99 / mo",
"WinoAccount_Management_AiPackPromoRequests": "1,000 requests",
"WinoAccount_Management_AiPackGetButton": "Get AI Pack",
"WinoAccount_Management_PurchaseRequiresSignIn": "Sign in with your Wino Account to complete this purchase.",
"WinoAccount_Management_PurchaseStartFailed": "Wino could not start the checkout session for this add-on.",
"WinoAccount_Management_AiPackSubscriptionActive": "Your subscription is active",
"WinoAccount_Management_AiPackRenews": "Renews {0:d}",
"WinoAccount_Management_AiPackRequestsUsed": "Requests used this month",
"WinoAccount_Management_AiPackResets": "Resets {0:d}",
"WinoAccount_Management_AiPackFeatureTranslate": "Translate",
"WinoAccount_Management_AiPackFeatureRewrite": "Rewrite",
"WinoAccount_Management_AiPackFeatureSummarize": "Summarize",
"WinoAccount_Management_SyncPreferencesTitle": "Synchronize Preferences",
"WinoAccount_Management_SyncPreferencesDescription": "Import or export your preferences to cloud. Import them across devices.",
"WinoAccount_Management_SignOutTitle": "Sign out",
"WinoAccount_Management_SignOutDescription": "Sign out of your account on this device",
"WinoAccount_Management_StatusLabel": "Status: {0}",
"WinoAccount_Management_NoRemoteSettings": "There are no synchronized settings stored for this account yet.",
"WinoAccount_Management_ExportSucceeded": "Your settings were exported to your Wino Account.",
"WinoAccount_Management_ImportSucceeded": "Imported {0} settings from your Wino Account.",
"WinoAccount_Management_ImportPartial": "Imported {0} settings. {1} settings could not be restored.",
"WinoAccount_Management_SerializeFailed": "Wino could not serialize your current preferences.",
"WinoAccount_Management_EmptyExport": "There are no preference values to export.",
"WinoAccount_Management_ImportEmpty": "The synchronized settings payload does not contain any values to restore.",
"WinoAccount_Management_LoadFailed": "Wino could not load the latest Wino Account information.",
"WinoAccount_Management_ActionFailed": "The Wino Account request could not be completed.",
"WinoAddOn_AI_PACK_Name": "AI Pack",
"WinoAddOn_AI_PACK_Description": "Translate, rewrite, and summarize emails with Wino AI.",
"WinoAddOn_AI_PACK_Keywords": "Translate · Rewrite · Summarize",
"WinoAddOn_UNLIMITED_ACCOUNTS_Name": "Unlimited Accounts",
"WinoAddOn_UNLIMITED_ACCOUNTS_Description": "Remove the 3-account limit and add as many accounts as you need.",
"WinoAddOn_UNLIMITED_ACCOUNTS_Keywords": "More accounts · No limit · One-time purchase",
"SettingsSearch_MessageList_Keywords": "message;messages;list;threading;threads;avatar;preview;sender",
"SettingsSearch_ReadComposePane_Keywords": "reader;compose;composer;font;fonts;external content;display;reading",
"SettingsSearch_SignatureAndEncryption_Keywords": "signature;signatures;encryption;certificate;certificates;s mime;smime;security",
@@ -1041,9 +982,8 @@
"SystemFolderConfigDialogValidation_InboxSelected": "You can't assign Inbox folder to any other system folder.",
"SystemFolderConfigSetupSuccess_Message": "System folders are successfully configured.",
"SystemFolderConfigSetupSuccess_Title": "System Folders Setup",
"SystemTrayMenu_Open": "Open",
"SystemTrayMenu_ShowWino": "Open Mail",
"SystemTrayMenu_ShowWinoCalendar": "Open Calendar",
"SystemTrayMenu_ShowWino": "Open Wino Mail",
"SystemTrayMenu_ShowWinoCalendar": "Open Wino Calendar",
"SystemTrayMenu_ExitWino": "Exit",
"TestingImapConnectionMessage": "Testing server connection...",
"TitleBarServerDisconnectedButton_Description": "Wino is disconnected from the network. Click reconnect to restore connection.",
@@ -1228,10 +1168,6 @@
"WinoAccount_RegisterDialog_DifferenceTitle": "Wino Account is separate from your mail accounts",
"WinoAccount_RegisterDialog_DifferenceDescription": "Your Outlook, Gmail, IMAP, or other email accounts stay exactly as they are. A Wino Account only manages Wino-specific features and account-based add-ons.",
"WinoAccount_RegisterDialog_PrimaryButton": "Register",
"WinoAccount_RegisterDialog_PrivacyTitle": "Privacy and API processing",
"WinoAccount_RegisterDialog_PrivacyDescription": "Optional add-ons such as Wino AI Pack may send selected email HTML content to the Wino API service only when you use those features.",
"WinoAccount_RegisterDialog_PrivacyLinkText": "Read the privacy policy",
"WinoAccount_RegisterDialog_PrivacyCheckbox": "I agree to the privacy policy.",
"WinoAccount_LoginDialog_Title": "Sign In to Wino Account",
"WinoAccount_LoginDialog_Description": "Sign in to your Wino Account to sync your Wino setup and access account-based features.",
"WinoAccount_LoginDialog_HeroTitle": "Welcome back",
@@ -1239,28 +1175,17 @@
"WinoAccount_LoginDialog_BenefitsDescription": "Use your Wino Account to continue syncing settings across devices and to access paid add-ons such as Wino AI Pack.",
"WinoAccount_LoginDialog_DifferenceTitle": "This is not your email mailbox sign-in",
"WinoAccount_LoginDialog_DifferenceDescription": "Signing in here does not add or replace your Outlook, Gmail, or IMAP accounts in Wino. It only signs you in to Wino-specific services.",
"WinoAccount_LoginDialog_ForgotPasswordLink": "Forgot password?",
"WinoAccount_EmailLabel": "Email",
"WinoAccount_EmailPlaceholder": "name@example.com",
"WinoAccount_PasswordLabel": "Password",
"WinoAccount_ConfirmPasswordLabel": "Confirm password",
"WinoAccount_ForgotPasswordDialog_Title": "Reset your password",
"WinoAccount_ForgotPasswordDialog_PrimaryButton": "Send reset email",
"WinoAccount_ForgotPasswordDialog_BackToSignIn": "Back to sign in",
"WinoAccount_ForgotPasswordDialog_Description": "Enter your Wino Account email address and we will send you a password reset link if the address is registered.",
"WinoAccount_Validation_EmailRequired": "Email is required.",
"WinoAccount_Validation_PasswordRequired": "Password is required.",
"WinoAccount_Validation_PasswordMismatch": "Passwords do not match.",
"WinoAccount_Validation_PrivacyConsentRequired": "You must accept the privacy policy before creating a Wino Account.",
"WinoAccount_Error_InvalidCredentials": "The email address or password is incorrect.",
"WinoAccount_Error_AccountLocked": "This account is temporarily locked.",
"WinoAccount_Error_AccountBanned": "This account has been banned.",
"WinoAccount_Error_AccountSuspended": "This account has been suspended.",
"WinoAccount_Error_EmailNotConfirmed": "Please confirm your email address before signing in.",
"WinoAccount_Error_EmailConfirmationRequired": "Please confirm your email address before signing in.",
"WinoAccount_Error_EmailConfirmationResendNotAvailable": "A new confirmation email is not available yet.",
"WinoAccount_Error_EmailConfirmationResendInvalid": "This confirmation request is no longer valid. Please try signing in again.",
"WinoAccount_Error_EmailNotRegistered": "This email address is not registered.",
"WinoAccount_Error_RefreshTokenInvalid": "Your session is no longer valid. Please sign in again.",
"WinoAccount_Error_EmailAlreadyRegistered": "This email address is already registered.",
"WinoAccount_Error_ExternalLoginEmailRequired": "An email address is required to complete external sign-in.",
@@ -1271,21 +1196,6 @@
"WinoAccount_Error_ValidationFailed": "The request is invalid. Please review the entered values.",
"WinoAccount_RegisterSuccessMessage": "Wino Account registration completed for {0}.",
"WinoAccount_LoginSuccessMessage": "Signed in to Wino Account as {0}.",
"WinoAccount_EmailConfirmationSentDialog_Title": "Confirm your email address",
"WinoAccount_EmailConfirmationSentDialog_Message": "We sent an email confirmation to {0}. Please confirm it and try signing in again.",
"WinoAccount_EmailConfirmationPendingDialog_Title": "Email confirmation required",
"WinoAccount_EmailConfirmationPendingDialog_Message": "We are still waiting for you to confirm {0}.",
"WinoAccount_EmailConfirmationPendingDialog_ResendButton": "Resend confirmation email",
"WinoAccount_EmailConfirmationPendingDialog_Countdown": "You can resend the confirmation email in {0}.",
"WinoAccount_EmailConfirmationPendingDialog_ReadyToResend": "You can resend the confirmation email now.",
"WinoAccount_EmailConfirmationResentDialog_Title": "Confirmation email resent",
"WinoAccount_EmailConfirmationResentDialog_Message": "We sent another confirmation email to {0}. Please confirm it and try signing in again.",
"WinoAccount_ForgotPasswordDialog_SuccessTitle": "Password reset email sent",
"WinoAccount_ForgotPasswordDialog_SuccessMessage": "We sent a password reset email to {0}. Open that message to choose a new password.",
"WinoAccount_ChangePassword_Title": "Change password",
"WinoAccount_ChangePassword_Description": "Send a password reset email to this Wino Account.",
"WinoAccount_ChangePassword_Action": "Send reset email",
"WinoAccount_ChangePassword_ConfirmationMessage": "Do you want Wino to send a password reset email to {0}?",
"WinoAccount_SignOut_SuccessMessage": "Signed out from Wino Account {0}.",
"WinoAccount_SignOut_NoAccountMessage": "There is no active Wino Account to sign out.",
"WinoAccount_Titlebar_SignedOutTitle": "Wino Account",
@@ -11,26 +11,18 @@ using Wino.Core.Domain.Entities.Shared;
using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Interfaces;
using Wino.Core.Domain.Models.Navigation;
using Wino.Core.Domain.Models.Accounts;
using Wino.Core.Domain.Models.Personalization;
using Wino.Core.Domain.Models.Settings;
using Wino.Core.Domain.Models.Translations;
using Wino.Core.Extensions;
using Wino.Core.ViewModels.Data;
using Wino.Mail.Api.Contracts.Ai;
using Wino.Mail.ViewModels.Data;
using Wino.Messaging.Client.Navigation;
using Wino.Messaging.UI;
namespace Wino.Core.ViewModels;
public partial class SettingOptionsPageViewModel : CoreBaseViewModel,
IRecipient<WinoAccountProfileUpdatedMessage>,
IRecipient<WinoAccountProfileDeletedMessage>,
IRecipient<WinoAccountAddOnPurchasedMessage>
public partial class SettingOptionsPageViewModel : CoreBaseViewModel
{
private const string BuyAiPackUrl = "https://example.com/wino-ai-pack";
private readonly INativeAppService _nativeAppService;
private readonly IAccountService _accountService;
private readonly IMimeStorageService _mimeStorageService;
@@ -39,8 +31,6 @@ public partial class SettingOptionsPageViewModel : CoreBaseViewModel,
private readonly INewThemeService _newThemeService;
private readonly IPreferencesService _preferencesService;
private readonly IProviderService _providerService;
private readonly IWinoAccountProfileService _profileService;
private readonly IMailDialogService _dialogService;
private bool _isInitializingSettings;
private bool _isAppearanceSelectionPaused;
@@ -92,42 +82,6 @@ public partial class SettingOptionsPageViewModel : CoreBaseViewModel,
[ObservableProperty]
public partial bool UseAccentColor { get; set; }
// Wino Account hero card properties
[ObservableProperty]
public partial bool IsWinoAccountBusy { get; set; }
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(IsWinoAccountSignedOut))]
[NotifyPropertyChangedFor(nameof(CanShowBuyAiPack))]
public partial bool IsWinoAccountSignedIn { get; set; }
[ObservableProperty]
public partial string WinoAccountEmail { get; set; } = string.Empty;
[ObservableProperty]
public partial string WinoAccountStatusText { get; set; } = string.Empty;
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(CanShowBuyAiPack))]
public partial bool HasAiPack { get; set; }
[ObservableProperty]
public partial string AiPackStateText { get; set; } = Translator.WinoAccount_Management_AiPackInactive;
[ObservableProperty]
public partial string AiUsageSummary { get; set; } = string.Empty;
[ObservableProperty]
public partial string AiBillingPeriodSummary { get; set; } = string.Empty;
[ObservableProperty]
public partial double AiUsagePercent { get; set; }
public bool IsWinoAccountSignedOut => !IsWinoAccountSignedIn;
public bool CanShowAiUsage => HasAiPack;
public bool CanShowBuyAiPack => IsWinoAccountSignedIn && !HasAiPack;
public SettingOptionsPageViewModel(INativeAppService nativeAppService,
IAccountService accountService,
IMimeStorageService mimeStorageService,
@@ -135,9 +89,7 @@ public partial class SettingOptionsPageViewModel : CoreBaseViewModel,
ITranslationService translationService,
INewThemeService newThemeService,
IPreferencesService preferencesService,
IProviderService providerService,
IWinoAccountProfileService profileService,
IMailDialogService dialogService)
IProviderService providerService)
{
_nativeAppService = nativeAppService;
_accountService = accountService;
@@ -147,8 +99,6 @@ public partial class SettingOptionsPageViewModel : CoreBaseViewModel,
_newThemeService = newThemeService;
_preferencesService = preferencesService;
_providerService = providerService;
_profileService = profileService;
_dialogService = dialogService;
}
public override void OnNavigatedTo(NavigationMode mode, object parameters)
@@ -162,7 +112,6 @@ public partial class SettingOptionsPageViewModel : CoreBaseViewModel,
InitializeQuickSettings();
_ = LoadDashboardAsync();
_ = LoadWinoAccountAsync();
}
public void UpdateSearchSuggestions(string query)
@@ -416,212 +365,6 @@ public partial class SettingOptionsPageViewModel : CoreBaseViewModel,
await _translationService.InitializeLanguageAsync(language.Language);
}
// Wino Account message recipients
protected override void RegisterRecipients()
{
base.RegisterRecipients();
Messenger.Register<WinoAccountProfileUpdatedMessage>(this);
Messenger.Register<WinoAccountProfileDeletedMessage>(this);
Messenger.Register<WinoAccountAddOnPurchasedMessage>(this);
}
protected override void UnregisterRecipients()
{
base.UnregisterRecipients();
Messenger.Unregister<WinoAccountProfileUpdatedMessage>(this);
Messenger.Unregister<WinoAccountProfileDeletedMessage>(this);
Messenger.Unregister<WinoAccountAddOnPurchasedMessage>(this);
}
public void Receive(WinoAccountProfileUpdatedMessage message)
=> _ = LoadWinoAccountAsync();
public void Receive(WinoAccountProfileDeletedMessage message)
=> _ = ResetWinoAccountStateAsync();
public void Receive(WinoAccountAddOnPurchasedMessage message)
=> _ = LoadWinoAccountAsync();
// Wino Account hero card commands and helpers
[RelayCommand]
private async Task WinoAccountRegisterAsync()
{
var account = await _dialogService.ShowWinoAccountRegistrationDialogAsync();
if (account == null) return;
_dialogService.InfoBarMessage(Translator.GeneralTitle_Info,
string.Format(Translator.WinoAccount_RegisterSuccessMessage, account.Email),
InfoBarMessageType.Success);
await LoadWinoAccountAsync();
}
[RelayCommand]
private async Task WinoAccountSignInAsync()
{
var account = await _dialogService.ShowWinoAccountLoginDialogAsync();
if (account == null) return;
_dialogService.InfoBarMessage(Translator.GeneralTitle_Info,
string.Format(Translator.WinoAccount_LoginSuccessMessage, account.Email),
InfoBarMessageType.Success);
await LoadWinoAccountAsync();
}
[RelayCommand]
private async Task WinoAccountSignOutAsync()
{
var account = await _profileService.GetActiveAccountAsync().ConfigureAwait(false);
if (account == null) return;
await _profileService.SignOutAsync().ConfigureAwait(false);
_dialogService.InfoBarMessage(Translator.GeneralTitle_Info,
string.Format(Translator.WinoAccount_SignOut_SuccessMessage, account.Email),
InfoBarMessageType.Success);
await ResetWinoAccountStateAsync();
}
[RelayCommand]
private async Task OpenBuyAiPackPageAsync() => await _nativeAppService.LaunchUriAsync(new Uri(BuyAiPackUrl));
[RelayCommand]
private void NavigateToWinoAccountManagement()
=> NavigateSubDetail(WinoPage.WinoAccountManagementPage);
private async Task LoadWinoAccountAsync()
{
try
{
var cachedAccount = await _profileService.GetActiveAccountAsync().ConfigureAwait(false);
var cachedAddOns = await _profileService.GetCachedAddOnSnapshotAsync().ConfigureAwait(false);
if (cachedAccount != null)
{
await ApplyWinoAccountStateAsync(cachedAccount.Email, cachedAccount.AccountStatus).ConfigureAwait(false);
UpdateAiPackState(cachedAddOns);
}
await ExecuteUIThread(() => IsWinoAccountBusy = cachedAccount == null);
var account = await _profileService.GetAuthenticatedAccountAsync().ConfigureAwait(false);
if (account == null)
{
await ResetWinoAccountStateAsync();
return;
}
var aiStatusResponse = await _profileService.GetAiStatusAsync().ConfigureAwait(false);
var profileRefreshResult = await _profileService.RefreshProfileAsync().ConfigureAwait(false);
var resolvedAccount = profileRefreshResult.IsSuccess && profileRefreshResult.Account != null
? profileRefreshResult.Account
: account;
await ApplyWinoAccountStateAsync(resolvedAccount.Email, resolvedAccount.AccountStatus).ConfigureAwait(false);
if (aiStatusResponse.IsSuccess)
{
UpdateAiPackState(aiStatusResponse.Result);
}
else
{
UpdateAiPackState(cachedAddOns);
}
}
catch
{
await ResetWinoAccountStateAsync();
}
finally
{
await ExecuteUIThread(() => IsWinoAccountBusy = false);
}
}
private async Task ApplyWinoAccountStateAsync(string email, string status)
{
await ExecuteUIThread(() =>
{
IsWinoAccountSignedIn = true;
WinoAccountEmail = email;
WinoAccountStatusText = string.Format(Translator.WinoAccount_Management_StatusLabel, status);
});
}
private async Task ResetWinoAccountStateAsync()
{
await ExecuteUIThread(() =>
{
IsWinoAccountSignedIn = false;
WinoAccountEmail = string.Empty;
WinoAccountStatusText = string.Empty;
HasAiPack = false;
AiPackStateText = Translator.WinoAccount_Management_AiPackInactive;
AiUsageSummary = string.Empty;
AiBillingPeriodSummary = string.Empty;
AiUsagePercent = 0;
});
}
private void UpdateAiPackState(AiStatusResultDto aiStatus)
{
UpdateAiPackState(
aiStatus?.HasAiPack == true,
aiStatus?.Used,
aiStatus?.MonthlyLimit,
aiStatus?.Remaining,
aiStatus?.CurrentPeriodStartUtc,
aiStatus?.CurrentPeriodEndUtc);
}
private void UpdateAiPackState(WinoAccountAddOnSnapshot addOnSnapshot)
{
var remaining = addOnSnapshot?.HasAiPack == true && addOnSnapshot.UsageLimit is int limit && addOnSnapshot.UsageCount is int used
? limit - used
: (int?)null;
UpdateAiPackState(
addOnSnapshot?.HasAiPack == true,
addOnSnapshot?.UsageCount,
addOnSnapshot?.UsageLimit,
remaining,
addOnSnapshot?.BillingPeriodStartUtc,
addOnSnapshot?.BillingPeriodEndUtc);
}
private void UpdateAiPackState(bool hasAiPack, int? used, int? limit, int? remaining, DateTimeOffset? periodStart, DateTimeOffset? periodEnd)
{
var usageText = Translator.WinoAccount_Management_AiPackUnknownUsage;
var billingText = string.Empty;
var usagePercent = 0d;
if (hasAiPack && used is int usageCount && limit is int usageLimit && remaining is int remainingCount)
{
usageText = string.Format(Translator.WinoAccount_Management_AiPackUsage, usageCount, usageLimit, remainingCount);
usagePercent = usageLimit > 0 ? (double)usageCount / usageLimit * 100 : 0;
}
if (hasAiPack && periodStart is DateTimeOffset billingStart && periodEnd is DateTimeOffset billingEnd)
{
billingText = string.Format(Translator.WinoAccount_Management_AiPackBillingPeriod, billingStart.LocalDateTime, billingEnd.LocalDateTime);
}
_ = ExecuteUIThread(() =>
{
HasAiPack = hasAiPack;
AiPackStateText = hasAiPack
? Translator.WinoAccount_Management_AiPackActive
: Translator.WinoAccount_Management_AiPackInactive;
AiUsageSummary = hasAiPack ? usageText : string.Empty;
AiBillingPeriodSummary = hasAiPack ? billingText : string.Empty;
AiUsagePercent = usagePercent;
});
}
[RelayCommand]
public void NavigateSubDetail(object type)
{
@@ -38,10 +38,7 @@ public partial class CustomCalendarFlipView : FlipView
{
base.OnApplyTemplate();
PreviousButtonHorizontal = GetTemplateChild(PART_PreviousButtonHorizontal) as Button;
NextButtonHorizontal = GetTemplateChild(PART_NextButtonHorizontal) as Button;
PreviousButtonVertical = GetTemplateChild(PART_PreviousButtonVertical) as Button;
NextButtonVertical = GetTemplateChild(PART_NextButtonVertical) as Button;
RefreshNavigationButtons();
// Hide navigation buttons
HideButton(PreviousButtonHorizontal);
@@ -78,25 +75,47 @@ public partial class CustomCalendarFlipView : FlipView
public void GoPreviousFlip()
{
var previousButton = DisplayType == CalendarDisplayType.Month
? PreviousButtonVertical ?? PreviousButtonHorizontal
: PreviousButtonHorizontal ?? PreviousButtonVertical;
if (previousButton == null) return;
var backPeer = new ButtonAutomationPeer(previousButton);
backPeer.Invoke();
InvokeNavigationButton(PreviousButtonHorizontal, PreviousButtonVertical);
}
public void GoNextFlip()
{
var nextButton = DisplayType == CalendarDisplayType.Month
? NextButtonVertical ?? NextButtonHorizontal
: NextButtonHorizontal ?? NextButtonVertical;
InvokeNavigationButton(NextButtonHorizontal, NextButtonVertical);
}
if (nextButton == null) return;
private void RefreshNavigationButtons()
{
PreviousButtonHorizontal = GetTemplateChild(PART_PreviousButtonHorizontal) as Button;
NextButtonHorizontal = GetTemplateChild(PART_NextButtonHorizontal) as Button;
PreviousButtonVertical = GetTemplateChild(PART_PreviousButtonVertical) as Button;
NextButtonVertical = GetTemplateChild(PART_NextButtonVertical) as Button;
}
var nextPeer = new ButtonAutomationPeer(nextButton);
nextPeer.Invoke();
private void InvokeNavigationButton(Button? primaryButton, Button? secondaryButton)
{
if (Items == null || Items.Count == 0)
return;
RefreshNavigationButtons();
var previousIndex = SelectedIndex;
if (TryInvokeNavigationButton(primaryButton, previousIndex))
{
return;
}
TryInvokeNavigationButton(secondaryButton, previousIndex);
}
private bool TryInvokeNavigationButton(Button? navigationButton, int previousIndex)
{
if (navigationButton == null)
return false;
var peer = new ButtonAutomationPeer(navigationButton);
peer.Invoke();
return SelectedIndex != previousIndex;
}
}
+9 -20
View File
@@ -92,27 +92,17 @@
BorderBrush="Transparent"
Visibility="{x:Bind helpers:XamlHelpers.ReverseBoolToVisibilityConverter(PreferencesService.IsWinoAccountButtonHidden), Mode=OneWay}">
<Button.Flyout>
<Flyout
x:Name="WinoAccountFlyout"
Placement="Bottom">
<Flyout x:Name="WinoAccountFlyout" Placement="Bottom">
<Grid MinWidth="320" MaxWidth="360">
<StackPanel
x:Name="WinoAccountSignedOutView"
Spacing="12">
<TextBlock
Style="{StaticResource BodyStrongTextBlockStyle}"
Text="{x:Bind domain:Translator.WinoAccount_Titlebar_SignedOutTitle}" />
<StackPanel x:Name="WinoAccountSignedOutView" Spacing="12">
<TextBlock Style="{StaticResource BodyStrongTextBlockStyle}" Text="{x:Bind domain:Translator.WinoAccount_Titlebar_SignedOutTitle}" />
<TextBlock
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Text="{x:Bind domain:Translator.WinoAccount_Titlebar_SignedOutDescription}"
TextWrapping="WrapWholeWords" />
<StackPanel Orientation="Horizontal" Spacing="8">
<Button
Click="RegisterWinoAccountClicked"
Content="{x:Bind domain:Translator.WinoAccount_RegisterButton_Action}" />
<Button
Click="LoginWinoAccountClicked"
Content="{x:Bind domain:Translator.WinoAccount_LoginButton_Action}" />
<Button Click="RegisterWinoAccountClicked" Content="{x:Bind domain:Translator.WinoAccount_RegisterButton_Action}" />
<Button Click="LoginWinoAccountClicked" Content="{x:Bind domain:Translator.WinoAccount_LoginButton_Action}" />
</StackPanel>
</StackPanel>
@@ -155,13 +145,12 @@
<Border
x:Name="WinoAccountSignedOutIcon"
Width="30"
Height="30"
Background="{ThemeResource AccentFillColorDefaultBrush}"
CornerRadius="15">
<FontIcon
Height="30">
<Image Source="/Assets/AppEntries/MailAssets/Square150x150Logo.png" />
<!--<FontIcon
FontSize="16"
Foreground="White"
Glyph="&#xE77B;" />
Glyph="&#xE77B;" />-->
</Border>
</Grid>
</Button>
@@ -63,87 +63,6 @@
</Style>
</Page.Resources>
<abstract:CalendarAppShellAbstract.ShellContent>
<Grid
Margin="4,0,0,0"
Background="Transparent"
ColumnSpacing="12">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid x:Name="ShellContentArea" ColumnSpacing="12">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<StackPanel
x:Name="NavigationTitleStack"
Grid.Column="0"
Margin="0,0,12,4"
Orientation="Horizontal"
Spacing="6">
<Button
x:Name="PreviousDateButton"
Click="PreviousDateClicked"
Style="{StaticResource CalendarNavigationButtonStyle}">
<PathIcon x:Name="PreviousDateButtonPathIcon" Data="F1 M 8.72 18.599998 C 8.879999 18.733334 9.059999 18.799999 9.26 18.799999 C 9.459999 18.799999 9.633332 18.719999 9.78 18.559999 C 9.926666 18.4 10 18.219999 10 18.019999 C 10 17.82 9.92 17.653332 9.76 17.52 L 4.52 12.559999 L 17.24 12.559999 C 17.453333 12.559999 17.633331 12.486667 17.779999 12.339999 C 17.926666 12.193334 18 12.013333 18 11.799999 C 18 11.586666 17.926666 11.406667 17.779999 11.259999 C 17.633331 11.113333 17.453333 11.039999 17.24 11.039999 L 4.52 11.039999 L 9.76 6.08 C 9.973333 5.893333 10.046666 5.653332 9.98 5.359999 C 9.913333 5.066666 9.74 4.880001 9.46 4.799999 C 9.179999 4.720001 8.933332 4.786667 8.72 5 L 2.32 11.08 C 2.16 11.24 2.053333 11.426666 2 11.639999 C 1.973333 11.746666 1.973333 11.853333 2 11.959999 C 2.053333 12.173333 2.16 12.360001 2.32 12.52 Z " />
</Button>
<Button
x:Name="NextDateButton"
Click="NextDateClicked"
Style="{StaticResource CalendarNavigationButtonStyle}">
<PathIcon x:Name="NextDateButtonPathIcon" Data="F1 M 11.28 5 C 11.12 4.866667 10.94 4.806667 10.74 4.82 C 10.539999 4.833334 10.366666 4.913334 10.219999 5.059999 C 10.073333 5.206665 10 5.379999 10 5.58 C 10 5.779999 10.08 5.946667 10.24 6.08 L 15.48 11.039999 L 2.76 11.039999 C 2.546667 11.039999 2.366667 11.113333 2.22 11.259999 C 2.073333 11.406667 2 11.586666 2 11.799999 C 2 12.013333 2.073333 12.193334 2.22 12.339999 C 2.366667 12.486667 2.546667 12.559999 2.76 12.559999 L 15.48 12.559999 L 10.24 17.52 C 10.026667 17.706665 9.953333 17.946667 10.02 18.24 C 10.086666 18.533333 10.259999 18.719999 10.54 18.799999 C 10.82 18.879999 11.066667 18.813334 11.28 18.599998 L 17.68 12.52 C 17.84 12.360001 17.946667 12.173333 18 11.959999 C 18 11.853333 18 11.746666 18 11.639999 C 17.946667 11.426666 17.84 11.24 17.68 11.08 Z " />
</Button>
<calendarControls:CustomCalendarFlipView
x:Name="DayHeaderNavigationItemsFlipView"
MaxHeight="30"
Margin="8,4,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
HorizontalContentAlignment="Left"
Background="Transparent"
DisplayType="{x:Bind ViewModel.StatePersistenceService.CalendarDisplayType, Mode=OneWay}"
FontSize="14"
FontWeight="Normal"
IsHitTestVisible="False"
ItemsSource="{x:Bind ViewModel.DateNavigationHeaderItems}"
SelectedIndex="{x:Bind ViewModel.SelectedDateNavigationHeaderIndex, Mode=OneWay}">
<FlipView.ItemTemplate>
<DataTemplate x:DataType="x:String">
<TextBlock
VerticalAlignment="Center"
FontSize="18"
Style="{StaticResource BodyTextBlockStyle}"
Text="{x:Bind}" />
</DataTemplate>
</FlipView.ItemTemplate>
</calendarControls:CustomCalendarFlipView>
</StackPanel>
<AutoSuggestBox
x:Name="SearchBox"
Grid.Column="1"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
BorderBrush="Transparent"
PlaceholderText="Search" />
</Grid>
<calendarControls:WinoCalendarTypeSelectorControl
x:Name="CalendarTypeSelector"
Grid.Column="2"
HorizontalAlignment="Right"
DisplayDayCount="{x:Bind ViewModel.StatePersistenceService.DayDisplayCount, Mode=OneWay}"
SelectedType="{x:Bind ViewModel.StatePersistenceService.CalendarDisplayType, Mode=TwoWay}"
TodayClickedCommand="{x:Bind ViewModel.TodayClickedCommand}" />
</Grid>
</abstract:CalendarAppShellAbstract.ShellContent>
<Grid
x:Name="RootGrid"
Padding="0"
@@ -179,9 +98,9 @@
MenuItemTemplateSelector="{StaticResource NavigationMenuTemplateSelector}"
MenuItemsSource="{x:Bind ViewModel.MenuItems, Mode=OneWay}"
OpenPaneLength="{x:Bind ViewModel.StatePersistenceService.OpenPaneLength, Mode=TwoWay}"
PaneClosed="NavigationPaneClosed"
PaneDisplayMode="Auto"
PaneOpened="NavigationPaneOpened"
PaneClosed="NavigationPaneClosed"
Style="{StaticResource CalendarShellNavigationViewStyle}">
<muxc:NavigationView.PaneCustomContent>
<Grid x:Name="PaneCustomContent" Padding="0,0,0,6">
+13 -103
View File
@@ -80,18 +80,22 @@
</TransitionCollection>
</StackPanel.ChildrenTransitions>
<!-- App About hero card -->
<!-- About hero card. -->
<Border
Padding="24,20"
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}"
BorderThickness="1"
CornerRadius="8">
<Grid ColumnSpacing="16">
<Grid ColumnSpacing="16" RowSpacing="16">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<StackPanel
VerticalAlignment="Center"
@@ -104,8 +108,12 @@
Source="ms-appx:///Assets/AppEntries/MailAssets/Square150x150Logo.scale-100.png"
Stretch="Uniform" />
<StackPanel VerticalAlignment="Center">
<TextBlock Style="{StaticResource TitleTextBlockStyle}" Text="Wino Mail" />
<TextBlock
VerticalAlignment="Center"
Style="{StaticResource TitleTextBlockStyle}"
Text="Wino Mail" />
<TextBlock
VerticalAlignment="Top"
Foreground="{ThemeResource TextFillColorTertiaryBrush}"
Style="{StaticResource CaptionTextBlockStyle}"
Text="{x:Bind ViewModel.VersionText, Mode=OneWay}" />
@@ -130,6 +138,7 @@
Stretch="Uniform" />
</Viewbox>
</Button>
<Button
Command="{x:Bind ViewModel.NavigateExternalCommand}"
CommandParameter="{x:Bind ViewModel.GitHubUrl, Mode=OneWay}"
@@ -142,6 +151,7 @@
Stretch="Uniform" />
</Viewbox>
</Button>
<Button
Command="{x:Bind ViewModel.NavigateExternalCommand}"
CommandParameter="Store"
@@ -157,106 +167,6 @@
</Grid>
</Border>
<!-- Loading indicator -->
<ProgressRing
x:Name="WinoAccountBusyRing"
Width="24"
Height="24"
HorizontalAlignment="Center"
x:Load="{x:Bind ViewModel.IsWinoAccountBusy, Mode=OneWay}"
IsActive="True" />
<!-- Wino Account: Signed-out state -->
<controls:SettingsCard
x:Name="SignedOutCard"
Description="{x:Bind domain:Translator.SettingsHome_WinoAccount_SignedOutDescription}"
Header="{x:Bind domain:Translator.SettingsHome_WinoAccount_Title}"
x:Load="{x:Bind ViewModel.IsWinoAccountSignedOut, Mode=OneWay}">
<controls:SettingsCard.HeaderIcon>
<FontIcon Glyph="&#xE77B;" />
</controls:SettingsCard.HeaderIcon>
<StackPanel Orientation="Horizontal" Spacing="8">
<Button
Command="{x:Bind ViewModel.WinoAccountSignInCommand}"
Content="{x:Bind domain:Translator.Buttons_SignIn}"
Style="{StaticResource AccentButtonStyle}" />
<Button Command="{x:Bind ViewModel.WinoAccountRegisterCommand}" Content="{x:Bind domain:Translator.Buttons_CreateAccount}" />
</StackPanel>
</controls:SettingsCard>
<!-- Wino Account: Signed-in state -->
<controls:SettingsExpander
x:Name="SignedInExpander"
Description="{x:Bind ViewModel.WinoAccountStatusText, Mode=OneWay}"
Header="{x:Bind ViewModel.WinoAccountEmail, Mode=OneWay}"
x:Load="{x:Bind ViewModel.IsWinoAccountSignedIn, Mode=OneWay}">
<controls:SettingsExpander.HeaderIcon>
<FontIcon Glyph="&#xE77B;" />
</controls:SettingsExpander.HeaderIcon>
<Button
Command="{x:Bind ViewModel.WinoAccountSignOutCommand}"
Content="{x:Bind domain:Translator.WinoAccount_SignOutButton_Action}" />
<controls:SettingsExpander.Items>
<!-- AI Pack active (with progress bar) -->
<controls:SettingsCard
x:Name="AiPackActiveCard"
Description="{x:Bind ViewModel.AiUsageSummary, Mode=OneWay}"
Header="{x:Bind domain:Translator.SettingsHome_AiPack_Title}"
x:Load="{x:Bind ViewModel.CanShowAiUsage, Mode=OneWay}">
<controls:SettingsCard.HeaderIcon>
<ImageIcon Source="ms-appx:///Assets/AIPackIcon.png" />
</controls:SettingsCard.HeaderIcon>
<StackPanel MinWidth="200" Spacing="4">
<ProgressBar
Height="8"
Maximum="100"
Value="{x:Bind ViewModel.AiUsagePercent, Mode=OneWay}" />
<TextBlock
Foreground="{ThemeResource TextFillColorTertiaryBrush}"
Style="{StaticResource CaptionTextBlockStyle}"
Text="{x:Bind ViewModel.AiBillingPeriodSummary, Mode=OneWay}" />
</StackPanel>
</controls:SettingsCard>
<!-- AI Pack not purchased -->
<controls:SettingsCard
x:Name="AiPackBuyCard"
Description="{x:Bind domain:Translator.WinoAccount_Management_AiPackBuyDescription}"
Header="{x:Bind domain:Translator.SettingsHome_AiPack_Title}"
x:Load="{x:Bind ViewModel.CanShowBuyAiPack, Mode=OneWay}">
<controls:SettingsCard.HeaderIcon>
<ImageIcon Source="ms-appx:///Assets/AIPackIcon.png" />
</controls:SettingsCard.HeaderIcon>
<Button
Command="{x:Bind ViewModel.OpenBuyAiPackPageCommand}"
Content="{x:Bind domain:Translator.Buttons_Purchase}"
Style="{StaticResource AccentButtonStyle}" />
</controls:SettingsCard>
<!-- Settings sync -->
<controls:SettingsCard
Click="WinoAccountManagementClicked"
Description="{x:Bind domain:Translator.SettingsHome_SettingsSync_Description}"
Header="{x:Bind domain:Translator.SettingsHome_SettingsSync_Title}"
IsClickEnabled="True">
<controls:SettingsCard.HeaderIcon>
<FontIcon Glyph="&#xE895;" />
</controls:SettingsCard.HeaderIcon>
</controls:SettingsCard>
<!-- Manage Wino Account -->
<controls:SettingsCard
Click="WinoAccountManagementClicked"
Header="{x:Bind domain:Translator.SettingsHome_WinoAccount_ManageLink}"
IsClickEnabled="True">
<controls:SettingsCard.HeaderIcon>
<FontIcon Glyph="&#xE713;" />
</controls:SettingsCard.HeaderIcon>
</controls:SettingsCard>
</controls:SettingsExpander.Items>
</controls:SettingsExpander>
<Border
Padding="12,0,34,20"
+3 -3
View File
@@ -523,10 +523,10 @@
ItemInvoked="NavigationViewItemInvoked"
MenuItemTemplateSelector="{StaticResource NavigationMenuTemplateSelector}"
OpenPaneLength="{x:Bind ViewModel.StatePersistenceService.OpenPaneLength, Mode=TwoWay}"
PaneDisplayMode="Auto"
PaneOpening="NavigationPaneOpening"
PaneOpened="NavigationPaneOpened"
PaneClosed="NavigationPaneClosed"
PaneDisplayMode="Auto"
PaneOpened="NavigationPaneOpened"
PaneOpening="NavigationPaneOpening"
ScrollViewer.VerticalScrollBarVisibility="Hidden"
SelectionChanged="MenuSelectionChanged"
Style="{StaticResource CalendarShellNavigationViewStyle}">