diff --git a/Wino.Calendar.ViewModels/AccountManagementViewModel.cs b/Wino.Calendar.ViewModels/AccountManagementViewModel.cs new file mode 100644 index 00000000..24e46b47 --- /dev/null +++ b/Wino.Calendar.ViewModels/AccountManagementViewModel.cs @@ -0,0 +1,37 @@ +using System.Threading.Tasks; +using Wino.Core.Domain.Interfaces; +using Wino.Core.Domain.Models.Navigation; +using Wino.Core.Domain.Models.Store; +using Wino.Core.ViewModels; + +namespace Wino.Calendar.ViewModels +{ + public partial class AccountManagementViewModel : AccountManagementPageViewModelBase + { + public AccountManagementViewModel(ICalendarDialogService dialogService, + IWinoServerConnectionManager winoServerConnectionManager, + INavigationService navigationService, + IAccountService accountService, + IProviderService providerService, + IStoreManagementService storeManagementService, + IAuthenticationProvider authenticationProvider, + IPreferencesService preferencesService) : base(dialogService, winoServerConnectionManager, navigationService, accountService, providerService, storeManagementService, authenticationProvider, preferencesService) + { + CalendarDialogService = dialogService; + } + + public ICalendarDialogService CalendarDialogService { get; } + + public override Task InitializeAccountsAsync() + { + return Task.CompletedTask; + } + + public override async void OnNavigatedTo(NavigationMode mode, object parameters) + { + base.OnNavigatedTo(mode, parameters); + + var t = await StoreManagementService.HasProductAsync(StoreProductType.UnlimitedAccounts); + } + } +} diff --git a/Wino.Calendar.ViewModels/AppShellViewModel.cs b/Wino.Calendar.ViewModels/AppShellViewModel.cs new file mode 100644 index 00000000..83ac3e0a --- /dev/null +++ b/Wino.Calendar.ViewModels/AppShellViewModel.cs @@ -0,0 +1,237 @@ +using System; +using System.Globalization; +using System.Linq; +using System.Threading.Tasks; +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using CommunityToolkit.Mvvm.Messaging; +using Wino.Core.Domain.Enums; +using Wino.Core.Domain.Interfaces; +using Wino.Core.Domain.Models.Calendar; +using Wino.Core.Domain.Models.Navigation; +using Wino.Core.Extensions; +using Wino.Core.MenuItems; +using Wino.Core.ViewModels; +using Wino.Messaging.Client.Calendar; +using Wino.Messaging.Client.Navigation; + +namespace Wino.Calendar.ViewModels +{ + public partial class AppShellViewModel : CalendarBaseViewModel, + IRecipient, + IRecipient, + IRecipient, + IRecipient + { + public event EventHandler DisplayTypeChanged; + public IPreferencesService PreferencesService { get; } + public IStatePersistanceService StatePersistenceService { get; } + public INavigationService NavigationService { get; } + public IWinoServerConnectionManager ServerConnectionManager { get; } + public MenuItemCollection FooterItems { get; set; } + public MenuItemCollection MenuItems { get; set; } + + [ObservableProperty] + private IMenuItem _selectedMenuItem; + + [ObservableProperty] + private bool isCalendarEnabled; + + /// + /// Gets or sets the active connection status of the Wino server. + /// + [ObservableProperty] + private WinoServerConnectionStatus activeConnectionStatus; + + /// + /// Gets or sets the display date of the calendar. + /// + [ObservableProperty] + private DateTimeOffset _displayDate; + + /// + /// Gets or sets the highlighted range in the CalendarView and displayed date range in FlipView. + /// + [ObservableProperty] + private DateRange highlightedDateRange; + + [ObservableProperty] + private ObservableRangeCollection dateNavigationHeaderItems = []; + + [ObservableProperty] + private int _selectedDateNavigationHeaderIndex; + + public bool IsVerticalCalendar => StatePersistenceService.CalendarDisplayType == CalendarDisplayType.Month; + + public AppShellViewModel(IPreferencesService preferencesService, + IStatePersistanceService statePersistanceService, + INavigationService navigationService, + IWinoServerConnectionManager serverConnectionManager) + { + NavigationService = navigationService; + ServerConnectionManager = serverConnectionManager; + PreferencesService = preferencesService; + + StatePersistenceService = statePersistanceService; + StatePersistenceService.StatePropertyChanged += PrefefencesChanged; + } + + private void PrefefencesChanged(object sender, string e) + { + if (e == nameof(StatePersistenceService.CalendarDisplayType)) + { + DisplayTypeChanged?.Invoke(this, StatePersistenceService.CalendarDisplayType); + OnPropertyChanged(nameof(IsVerticalCalendar)); + + // Change the calendar. + DateClicked(new CalendarViewDayClickedEventArgs(GetDisplayTypeSwitchDate())); + } + } + + public override void OnNavigatedTo(NavigationMode mode, object parameters) + { + base.OnNavigatedTo(mode, parameters); + + CreateFooterItems(); + UpdateDateNavigationHeaderItems(); + } + + partial void OnSelectedMenuItemChanged(IMenuItem oldValue, IMenuItem newValue) + { + if (newValue is SettingsItem) + { + NavigationService.Navigate(WinoPage.SettingsPage); + } + else if (newValue is ManageAccountsMenuItem) + { + NavigationService.Navigate(WinoPage.AccountManagementPage); + } + } + + /// + /// When calendar type switches, we need to navigate to the most ideal date. + /// This method returns that date. + /// + private DateTime GetDisplayTypeSwitchDate() + { + switch (StatePersistenceService.CalendarDisplayType) + { + case CalendarDisplayType.Day: + if (HighlightedDateRange.IsInRange(DateTime.Now)) return DateTime.Now.Date; + + return HighlightedDateRange.StartDate; + case CalendarDisplayType.Week: + // TODO: From settings + if (HighlightedDateRange.IsInRange(DateTime.Now)) return DateTime.Now.Date.GetWeekStartDateForDate(DayOfWeek.Monday); + + return HighlightedDateRange.StartDate.GetWeekStartDateForDate(DayOfWeek.Monday); + case CalendarDisplayType.WorkWeek: + break; + case CalendarDisplayType.Month: + break; + case CalendarDisplayType.Year: + break; + default: + break; + } + + return DateTime.Today.Date; + } + + protected override void OnDispatcherAssigned() + { + base.OnDispatcherAssigned(); + + MenuItems = new MenuItemCollection(Dispatcher); + FooterItems = new MenuItemCollection(Dispatcher); + } + + public override void OnPageLoaded() + { + base.OnPageLoaded(); + + NavigationService.Navigate(WinoPage.CalendarPage, new CalendarPageNavigationArgs() + { + RequestDefaultNavigation = true + }); + } + + private void CreateFooterItems() + { + FooterItems.Clear(); + FooterItems.Add(new ManageAccountsMenuItem()); + FooterItems.Add(new SettingsItem()); + } + + #region Commands + + [RelayCommand] + private void TodayClicked() => Messenger.Send(new GoToCalendarDayMessage(DateTime.Now.Date)); + + [RelayCommand] + public void ManageAccounts() => NavigationService.Navigate(WinoPage.AccountManagementPage); + + [RelayCommand] + private Task ReconnectServerAsync() => ServerConnectionManager.ConnectAsync(); + + [RelayCommand] + private void DateClicked(CalendarViewDayClickedEventArgs clickedDate) + => Messenger.Send(new CalendarInitializeMessage(clickedDate.ClickedDate, CalendarInitInitiative.User)); + + #endregion + + public void Receive(VisibleDateRangeChangedMessage message) => HighlightedDateRange = message.DateRange; + + /// + /// Sets the header navigation items based on visible date range and calendar type. + /// + private void UpdateDateNavigationHeaderItems() + { + DateNavigationHeaderItems.Clear(); + + // TODO: From settings + var testInfo = new CultureInfo("en-US"); + + switch (StatePersistenceService.CalendarDisplayType) + { + case CalendarDisplayType.Day: + case CalendarDisplayType.Week: + case CalendarDisplayType.WorkWeek: + case CalendarDisplayType.Month: + DateNavigationHeaderItems.ReplaceRange(testInfo.DateTimeFormat.MonthNames); + break; + case CalendarDisplayType.Year: + break; + default: + break; + } + + SetDateNavigationHeaderItems(); + } + + partial void OnHighlightedDateRangeChanged(DateRange value) => SetDateNavigationHeaderItems(); + + private void SetDateNavigationHeaderItems() + { + if (HighlightedDateRange == null) return; + + if (DateNavigationHeaderItems.Count == 0) + { + UpdateDateNavigationHeaderItems(); + } + + // TODO: Year view + var monthIndex = HighlightedDateRange.GetMostVisibleMonthIndex(); + + SelectedDateNavigationHeaderIndex = Math.Max(monthIndex - 1, -1); + } + + public async void Receive(CalendarEnableStatusChangedMessage message) + => await ExecuteUIThread(() => IsCalendarEnabled = message.IsEnabled); + + // Calendar page is loaded and calendar is ready to recieve render requests. + public void Receive(CalendarInitializedMessage message) => Messenger.Send(new GoToCalendarDayMessage(DateTime.Now.Date)); + + public void Receive(NavigateManageAccountsRequested message) => SelectedMenuItem = FooterItems.FirstOrDefault(a => a is ManageAccountsMenuItem); + } +} diff --git a/Wino.Calendar.ViewModels/CalendarPageViewModel.cs b/Wino.Calendar.ViewModels/CalendarPageViewModel.cs new file mode 100644 index 00000000..7552de6e --- /dev/null +++ b/Wino.Calendar.ViewModels/CalendarPageViewModel.cs @@ -0,0 +1,436 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using CommunityToolkit.Diagnostics; +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Messaging; +using Wino.Calendar.Models.CalendarTypeStrategies; +using Wino.Core.Domain.Enums; +using Wino.Core.Domain.Interfaces; +using Wino.Core.Domain.Models.Calendar; +using Wino.Core.Domain.Models.Navigation; +using Wino.Core.MenuItems; +using Wino.Core.ViewModels; +using Wino.Messaging.Client.Calendar; + +namespace Wino.Calendar.ViewModels +{ + public partial class CalendarPageViewModel : CalendarBaseViewModel, + IRecipient + { + [ObservableProperty] + private ObservableRangeCollection _dayRanges = []; + + [ObservableProperty] + private int _selectedDateRangeIndex; + + [ObservableProperty] + private DayRangeRenderModel _selectedDayRange; + + [ObservableProperty] + private bool _isCalendarEnabled = true; + + // Get rid of some of the items if we have too many. + private const int maxDayRangeSize = 10; + private readonly IPreferencesService _preferencesService; + + // Store latest rendered options. + private CalendarDisplayType _currentDisplayType; + private int _displayDayCount; + + private SemaphoreSlim _calendarLoadingSemaphore = new(1); + private bool isLoadMoreBlocked = false; + private CalendarSettings _currentSettings = null; + + public IStatePersistanceService StatePersistanceService { get; } + + public CalendarPageViewModel(IStatePersistanceService statePersistanceService, + IPreferencesService preferencesService) + { + StatePersistanceService = statePersistanceService; + _preferencesService = preferencesService; + + _currentSettings = _preferencesService.GetCurrentCalendarSettings(); + } + + public override void OnNavigatedTo(NavigationMode mode, object parameters) + { + base.OnNavigatedTo(mode, parameters); + + Messenger.Send(new CalendarInitializedMessage()); + } + + // TODO: Replace when calendar settings are updated. + // Should be a field ideally. + private BaseCalendarTypeDrawingStrategy GetDrawingStrategy(CalendarDisplayType displayType) + { + return displayType switch + { + CalendarDisplayType.Day => new DayCalendarDrawingStrategy(_currentSettings), + CalendarDisplayType.Week => new WeekCalendarDrawingStrategy(_currentSettings), + _ => throw new NotImplementedException(), + }; + } + + partial void OnIsCalendarEnabledChanging(bool oldValue, bool newValue) => Messenger.Send(new CalendarEnableStatusChangedMessage(newValue)); + + private bool ShouldResetDayRanges(CalendarInitializeMessage message) + { + // Never reset if the initiative is from the app. + if (message.CalendarInitInitiative == CalendarInitInitiative.App) return false; + + // 1. Display type is different. + // 2. Day display count is different. + // 3. Display date is not in the visible range. + + return + (_currentDisplayType != StatePersistanceService.CalendarDisplayType || + _displayDayCount != StatePersistanceService.DayDisplayCount || + (DayRanges != null && !DayRanges.Select(a => a.CalendarRenderOptions).Any(b => b.DateRange.IsInRange(message.DisplayDate)))); + } + + public async void Receive(CalendarInitializeMessage message) + { + await _calendarLoadingSemaphore.WaitAsync(); + + try + { + await ExecuteUIThread(() => IsCalendarEnabled = false); + + if (ShouldResetDayRanges(message)) + { + DayRanges.Clear(); + + Debug.WriteLine("Will reset day ranges."); + } + else if (ShouldScrollToItem(message)) + { + // Scroll to the selected date. + + Messenger.Send(new ScrollToDateMessage(message.DisplayDate)); + Debug.WriteLine("Scrolling to selected date."); + return; + } + + // This will replace the whole collection because the user initiated a new render. + await RenderDatesAsync(message.CalendarInitInitiative, + message.DisplayDate, + CalendarLoadDirection.Replace); + } + catch (Exception ex) + { + Debugger.Break(); + } + finally + { + _calendarLoadingSemaphore.Release(); + + await ExecuteUIThread(() => IsCalendarEnabled = true); + } + } + + private async Task RenderDatesAsync(CalendarInitInitiative calendarInitInitiative, + DateTime? loadingDisplayDate = null, + CalendarLoadDirection calendarLoadDirection = CalendarLoadDirection.Replace) + { + // This is the part we arrange the flip view calendar logic. + + /* Loading for a month of the selected date is fine. + * If the selected date is in the loaded range, we'll just change the selected flip index to scroll. + * If the selected date is not in the loaded range: + * 1. Detect the direction of the scroll. + * 2. Load the next month. + * 3. Replace existing month with the new month. + */ + + // 2 things are important: How many items should 1 flip have, and, where we should start loading? + + // User initiated renders must always have a date to start with. + if (calendarInitInitiative == CalendarInitInitiative.User) Guard.IsNotNull(loadingDisplayDate, nameof(loadingDisplayDate)); + + var strategy = GetDrawingStrategy(StatePersistanceService.CalendarDisplayType); + var displayDate = loadingDisplayDate.GetValueOrDefault(); + + // How many days should be placed in 1 flip view item? + int eachFlipItemCount = strategy.GetRenderDayCount(displayDate, StatePersistanceService.DayDisplayCount); + + DateRange flipLoadRange = null; + + if (calendarInitInitiative == CalendarInitInitiative.User) + { + flipLoadRange = strategy.GetRenderDateRange(displayDate, StatePersistanceService.DayDisplayCount); + } + else + { + var minimumLoadedDate = DayRanges[0].CalendarRenderOptions.DateRange.StartDate; + var maximumLoadedDate = DayRanges[DayRanges.Count - 1].CalendarRenderOptions.DateRange.EndDate; + + var currentInitializedDateRange = new DateRange(minimumLoadedDate, maximumLoadedDate); + + // App is trying to load. + // This should be based on direction. We'll load the next or previous range. + // DisplayDate is either the start or end date of the current visible range. + + if (calendarLoadDirection == CalendarLoadDirection.Previous) + { + flipLoadRange = strategy.GetPreviousDateRange(currentInitializedDateRange, StatePersistanceService.DayDisplayCount); + } + else + { + flipLoadRange = strategy.GetNextDateRange(currentInitializedDateRange, StatePersistanceService.DayDisplayCount); + } + } + + // 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 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); + + renderModels.Add(new DayRangeRenderModel(renderOptions)); + } + + CalendarLoadDirection animationDirection = calendarLoadDirection; + + bool removeCurrent = calendarLoadDirection == CalendarLoadDirection.Replace; + + if (calendarLoadDirection == CalendarLoadDirection.Replace) + { + // New date ranges are being replaced. + // We must preserve existing selection if any, add the items before/after the current one, remove the current one. + // This will make sure the new dates are animated in the correct direction. + + isLoadMoreBlocked = true; + + // Remove all other dates except this one. + + await ExecuteUIThread(() => + { + DayRanges.RemoveRange(DayRanges.Where(a => a != SelectedDayRange).ToList()); + }); + + animationDirection = displayDate <= SelectedDayRange?.CalendarRenderOptions.DateRange.StartDate ? + CalendarLoadDirection.Previous : CalendarLoadDirection.Next; + } + + if (animationDirection == CalendarLoadDirection.Next) + { + await ExecuteUIThread(() => + { + foreach (var item in renderModels) + { + DayRanges.Add(item); + } + }); + } + else if (animationDirection == CalendarLoadDirection.Previous) + { + // Wait for the animation to finish. + // Otherwise it somehow shutters a little, which is annoying. + + if (!removeCurrent) await Task.Delay(500); + + // Insert each render model in reverse order. + for (int i = renderModels.Count - 1; i >= 0; i--) + { + await ExecuteUIThread(() => + { + DayRanges.Insert(0, renderModels[i]); + }); + } + } + + Debug.WriteLine($"Flip count: ({DayRanges.Count})"); + + foreach (var item in DayRanges) + { + Debug.WriteLine($"- {item.CalendarRenderOptions.DateRange.ToString()}"); + } + + if (removeCurrent) + { + await ExecuteUIThread(() => + { + DayRanges.Remove(SelectedDayRange); + }); + } + + // TODO... + // await TryConsolidateItemsAsync(); + + isLoadMoreBlocked = false; + + // Only scroll if the render is initiated by user. + // Otherwise we'll scroll to the app rendered invisible date range. + if (calendarInitInitiative == CalendarInitInitiative.User) + { + // Save the current settings for the page for later comparison. + _currentDisplayType = StatePersistanceService.CalendarDisplayType; + _displayDayCount = StatePersistanceService.DayDisplayCount; + + Messenger.Send(new ScrollToDateMessage(displayDate)); + } + } + + private async Task TryConsolidateItemsAsync() + { + // Check if trimming is necessary + if (DayRanges.Count > maxDayRangeSize) + { + Debug.WriteLine("Trimming items."); + + isLoadMoreBlocked = true; + + var removeCount = DayRanges.Count - maxDayRangeSize; + + await Task.Delay(500); + + // Right shifted, remove from the start. + if (SelectedDateRangeIndex > DayRanges.Count / 2) + { + DayRanges.RemoveRange(DayRanges.Take(removeCount).ToList()); + } + else + { + // Left shifted, remove from the end. + DayRanges.RemoveRange(DayRanges.Skip(DayRanges.Count - removeCount).Take(removeCount)); + } + + SelectedDateRangeIndex = DayRanges.IndexOf(SelectedDayRange); + } + } + + private bool ShouldScrollToItem(CalendarInitializeMessage message) + { + // Never scroll if the initiative is from the app. + if (message.CalendarInitInitiative == CalendarInitInitiative.App) return false; + + // Nothing to scroll. + if (DayRanges.Count == 0) return false; + + var minimumLoadedDate = DayRanges[0].CalendarRenderOptions.DateRange.StartDate; + var maximumLoadedDate = DayRanges[DayRanges.Count - 1].CalendarRenderOptions.DateRange.EndDate; + + var selectedDate = message.DisplayDate; + + return selectedDate >= minimumLoadedDate && selectedDate <= maximumLoadedDate; + } + + partial void OnSelectedDayRangeChanged(DayRangeRenderModel value) + { + if (DayRanges.Count == 0 || SelectedDateRangeIndex < 0) return; + + if (isLoadMoreBlocked) return; + + var selectedRange = DayRanges[SelectedDateRangeIndex]; + + if (selectedRange != null) + { + // Send the loading message initiated by the app. + if (SelectedDateRangeIndex == DayRanges.Count - 1) + { + // Load next, starting from the end date. + _ = LoadMoreAsync(CalendarLoadDirection.Next); + } + else if (SelectedDateRangeIndex == 0) + { + // Load previous, starting from the start date. + + _ = LoadMoreAsync(CalendarLoadDirection.Previous); + } + } + } + + private async Task LoadMoreAsync(CalendarLoadDirection direction) + { + Debug.WriteLine($"Loading {direction} items."); + + try + { + await _calendarLoadingSemaphore.WaitAsync(); + await RenderDatesAsync(CalendarInitInitiative.App, calendarLoadDirection: direction); + } + catch (Exception) + { + Debugger.Break(); + } + finally + { + _calendarLoadingSemaphore.Release(); + } + } + + protected override async void OnCalendarEventAdded(ICalendarItem calendarItem) + { + base.OnCalendarEventAdded(calendarItem); + + // Test + var eventDays = DayRanges.SelectMany(a => a.CalendarDays).Where(b => b.Period.Start.Date == calendarItem.StartTime.Date); + + var beforeAllDay = new CalendarItem(calendarItem.StartTime.Date.AddHours(0), calendarItem.StartTime.Date.AddMinutes(30)) + { + Name = "kj" + }; + + var allday = new CalendarItem(calendarItem.StartTime.Date.AddHours(1), calendarItem.StartTime.Date.AddHours(10).AddMinutes(59)) + { + Name = "All day" + }; + + var test = new CalendarItem(calendarItem.StartTime.Date.AddHours(4), calendarItem.StartTime.Date.AddHours(4).AddMinutes(30)) + { + Name = "test" + }; + + var hour = new CalendarItem(calendarItem.StartTime.Date.AddHours(7), calendarItem.StartTime.Date.AddHours(8)) + { + Name = "1 h" + }; + + var hourandhalf = new CalendarItem(calendarItem.StartTime.Date.AddHours(7), calendarItem.StartTime.Date.AddHours(8).AddMinutes(30)) + { + Name = "1.5 h" + }; + var halfhour1 = new CalendarItem(calendarItem.StartTime.Date.AddHours(7), calendarItem.StartTime.Date.AddHours(7).AddMinutes(30)) + { + Name = "30 min" + }; + + var halfhour2 = new CalendarItem(calendarItem.StartTime.Date.AddHours(7).AddMinutes(30), calendarItem.StartTime.Date.AddHours(8)) + { + Name = "30 min" + }; + var halfhour3 = new CalendarItem(calendarItem.StartTime.Date.AddHours(8), calendarItem.StartTime.Date.AddHours(8).AddMinutes(30)) + { + Name = "30 min" + }; + + foreach (var day in eventDays) + { + await ExecuteUIThread(() => + { + day.Events.Add(beforeAllDay); + day.Events.Add(allday); + day.Events.Add(hourandhalf); + day.Events.Add(hour); + day.Events.Add(halfhour1); + day.Events.Add(halfhour2); + day.Events.Add(halfhour3); + day.Events.Add(test); + }); + } + + return; + } + } +} diff --git a/Wino.Calendar.ViewModels/CalendarSettingsPageViewModel.cs b/Wino.Calendar.ViewModels/CalendarSettingsPageViewModel.cs new file mode 100644 index 00000000..b9c21f97 --- /dev/null +++ b/Wino.Calendar.ViewModels/CalendarSettingsPageViewModel.cs @@ -0,0 +1,127 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Messaging; +using Wino.Core.Domain.Interfaces; +using Wino.Core.Domain.Translations; +using Wino.Core.ViewModels; +using Wino.Messaging.Client.Calendar; + +namespace Wino.Calendar.ViewModels +{ + public partial class CalendarSettingsPageViewModel : CalendarBaseViewModel + { + [ObservableProperty] + private double _cellHourHeight; + + [ObservableProperty] + private int _selectedFirstDayOfWeekIndex; + + [ObservableProperty] + private bool _is24HourHeaders; + + [ObservableProperty] + private TimeSpan _workingHourStart; + + [ObservableProperty] + private TimeSpan _workingHourEnd; + + [ObservableProperty] + private List _dayNames = []; + + [ObservableProperty] + private int _workingDayStartIndex; + + [ObservableProperty] + private int _workingDayEndIndex; + + public IPreferencesService PreferencesService { get; } + + private readonly bool _isLoaded = false; + + public CalendarSettingsPageViewModel(IPreferencesService preferencesService) + { + PreferencesService = preferencesService; + + var currentLanguageLanguageCode = WinoTranslationDictionary.GetLanguageFileNameRelativePath(preferencesService.CurrentLanguage); + + var cultureInfo = new CultureInfo(currentLanguageLanguageCode); + + // Populate the day names list + for (var i = 0; i < 7; i++) + { + _dayNames.Add(cultureInfo.DateTimeFormat.DayNames[i]); + } + + var cultureFirstDayName = cultureInfo.DateTimeFormat.GetDayName(preferencesService.FirstDayOfWeek); + + _selectedFirstDayOfWeekIndex = _dayNames.IndexOf(cultureFirstDayName); + _is24HourHeaders = preferencesService.Prefer24HourTimeFormat; + _workingHourStart = preferencesService.WorkingHourStart; + _workingHourEnd = preferencesService.WorkingHourEnd; + _cellHourHeight = preferencesService.HourHeight; + + _workingDayStartIndex = _dayNames.IndexOf(cultureInfo.DateTimeFormat.GetDayName(preferencesService.WorkingDayStart)); + _workingDayEndIndex = _dayNames.IndexOf(cultureInfo.DateTimeFormat.GetDayName(preferencesService.WorkingDayEnd)); + + _isLoaded = true; + } + + partial void OnCellHourHeightChanged(double oldValue, double newValue) => SaveSettings(); + partial void OnIs24HourHeadersChanged(bool value) => SaveSettings(); + partial void OnSelectedFirstDayOfWeekIndexChanged(int value) => SaveSettings(); + partial void OnWorkingHourStartChanged(TimeSpan value) => SaveSettings(); + partial void OnWorkingHourEndChanged(TimeSpan value) => SaveSettings(); + partial void OnWorkingDayStartIndexChanged(int value) => SaveSettings(); + partial void OnWorkingDayEndIndexChanged(int value) => SaveSettings(); + + public void SaveSettings() + { + if (!_isLoaded) return; + + PreferencesService.FirstDayOfWeek = SelectedFirstDayOfWeekIndex switch + { + 0 => DayOfWeek.Sunday, + 1 => DayOfWeek.Monday, + 2 => DayOfWeek.Tuesday, + 3 => DayOfWeek.Wednesday, + 4 => DayOfWeek.Thursday, + 5 => DayOfWeek.Friday, + 6 => DayOfWeek.Saturday, + _ => throw new ArgumentOutOfRangeException() + }; + + PreferencesService.WorkingDayStart = WorkingDayStartIndex switch + { + 0 => DayOfWeek.Sunday, + 1 => DayOfWeek.Monday, + 2 => DayOfWeek.Tuesday, + 3 => DayOfWeek.Wednesday, + 4 => DayOfWeek.Thursday, + 5 => DayOfWeek.Friday, + 6 => DayOfWeek.Saturday, + _ => throw new ArgumentOutOfRangeException() + }; + + PreferencesService.WorkingDayEnd = WorkingDayEndIndex switch + { + 0 => DayOfWeek.Sunday, + 1 => DayOfWeek.Monday, + 2 => DayOfWeek.Tuesday, + 3 => DayOfWeek.Wednesday, + 4 => DayOfWeek.Thursday, + 5 => DayOfWeek.Friday, + 6 => DayOfWeek.Saturday, + _ => throw new ArgumentOutOfRangeException() + }; + + PreferencesService.Prefer24HourTimeFormat = Is24HourHeaders; + PreferencesService.WorkingHourStart = WorkingHourStart; + PreferencesService.WorkingHourEnd = WorkingHourEnd; + PreferencesService.HourHeight = CellHourHeight; + + Messenger.Send(new CalendarSettingsUpdatedMessage()); + } + } +} diff --git a/Wino.Calendar.ViewModels/Wino.Calendar.ViewModels.csproj b/Wino.Calendar.ViewModels/Wino.Calendar.ViewModels.csproj new file mode 100644 index 00000000..8a6621ff --- /dev/null +++ b/Wino.Calendar.ViewModels/Wino.Calendar.ViewModels.csproj @@ -0,0 +1,20 @@ + + + + netstandard2.0 + 12 + AnyCPU;x64;x86 + + + + + + + + + + + + + + diff --git a/Wino.Calendar/Activation/DefaultActivationHandler.cs b/Wino.Calendar/Activation/DefaultActivationHandler.cs new file mode 100644 index 00000000..52e8fcfd --- /dev/null +++ b/Wino.Calendar/Activation/DefaultActivationHandler.cs @@ -0,0 +1,24 @@ +using System.Threading.Tasks; +using Windows.ApplicationModel.Activation; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Media.Animation; +using Wino.Activation; +using Wino.Calendar.Views; + +namespace Wino.Calendar.Activation +{ + public class DefaultActivationHandler : ActivationHandler + { + protected override Task HandleInternalAsync(IActivatedEventArgs args) + { + (Window.Current.Content as Frame).Navigate(typeof(AppShell), null, new DrillInNavigationTransitionInfo()); + + return Task.CompletedTask; + } + + // Only navigate if Frame content doesn't exist. + protected override bool CanHandleInternal(IActivatedEventArgs args) + => (Window.Current?.Content as Frame)?.Content == null; + } +} diff --git a/Wino.Calendar/App.xaml b/Wino.Calendar/App.xaml index 5707d1ac..7d679b49 100644 --- a/Wino.Calendar/App.xaml +++ b/Wino.Calendar/App.xaml @@ -1,7 +1,34 @@ - + xmlns:controls="using:Microsoft.UI.Xaml.Controls" + xmlns:core="using:Wino.Core.UWP" + xmlns:coreStyles="using:Wino.Core.UWP.Styles" + xmlns:local="using:Wino.Calendar" + xmlns:styles="using:Wino.Calendar.Styles"> + + + + + + + + + + + + + + + + + + + + + + + + - diff --git a/Wino.Calendar/App.xaml.cs b/Wino.Calendar/App.xaml.cs index 971e694b..7ff40a35 100644 --- a/Wino.Calendar/App.xaml.cs +++ b/Wino.Calendar/App.xaml.cs @@ -1,100 +1,89 @@ using System; using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Runtime.InteropServices.WindowsRuntime; -using Windows.ApplicationModel; +using Microsoft.Extensions.DependencyInjection; using Windows.ApplicationModel.Activation; -using Windows.Foundation; -using Windows.Foundation.Collections; -using Windows.UI.Xaml; -using Windows.UI.Xaml.Controls; -using Windows.UI.Xaml.Controls.Primitives; -using Windows.UI.Xaml.Data; -using Windows.UI.Xaml.Input; -using Windows.UI.Xaml.Media; -using Windows.UI.Xaml.Navigation; +using Windows.UI.Core.Preview; +using Wino.Activation; +using Wino.Calendar.Activation; +using Wino.Calendar.Services; +using Wino.Calendar.ViewModels; +using Wino.Core; +using Wino.Core.Domain.Interfaces; +using Wino.Core.UWP; namespace Wino.Calendar { - /// - /// Provides application-specific behavior to supplement the default Application class. - /// - sealed partial class App : Application + public sealed partial class App : WinoApplication { - /// - /// Initializes the singleton application object. This is the first line of authored code - /// executed, and as such is the logical equivalent of main() or WinMain(). - /// + public override string AppCenterKey => "dfdad6ab-95f9-44cc-9112-45ec6730c49e"; + public App() { - this.InitializeComponent(); - this.Suspending += OnSuspending; + InitializeComponent(); } - /// - /// Invoked when the application is launched normally by the end user. Other entry points - /// will be used such as when the application is launched to open a specific file. - /// - /// Details about the launch request and process. - protected override void OnLaunched(LaunchActivatedEventArgs e) + public override IServiceProvider ConfigureServices() { - Frame rootFrame = Window.Current.Content as Frame; + var services = new ServiceCollection(); - // Do not repeat app initialization when the Window already has content, - // just ensure that the window is active - if (rootFrame == null) + services.RegisterCoreServices(); + services.RegisterCoreUWPServices(); + services.RegisterCoreViewModels(); + + RegisterUWPServices(services); + RegisterViewModels(services); + RegisterActivationHandlers(services); + + return services.BuildServiceProvider(); + } + + #region Dependency Injection + + private void RegisterActivationHandlers(IServiceCollection services) + { + //services.AddTransient(); + //services.AddTransient(); + //services.AddTransient(); + } + + private void RegisterUWPServices(IServiceCollection services) + { + services.AddSingleton(); + services.AddSingleton(); + services.AddTransient(); + } + + private void RegisterViewModels(IServiceCollection services) + { + services.AddSingleton(typeof(AppShellViewModel)); + services.AddTransient(typeof(CalendarPageViewModel)); + services.AddTransient(typeof(CalendarSettingsPageViewModel)); + services.AddTransient(typeof(AccountManagementViewModel)); + } + + #endregion + + protected override void OnApplicationCloseRequested(object sender, SystemNavigationCloseRequestedPreviewEventArgs e) + { + // TODO: Check server running. + } + + protected override async void OnLaunched(LaunchActivatedEventArgs args) + { + LogActivation($"OnLaunched -> {args.GetType().Name}, Kind -> {args.Kind}, PreviousExecutionState -> {args.PreviousExecutionState}, IsPrelaunch -> {args.PrelaunchActivated}"); + + if (!args.PrelaunchActivated) { - // Create a Frame to act as the navigation context and navigate to the first page - rootFrame = new Frame(); - - rootFrame.NavigationFailed += OnNavigationFailed; - - if (e.PreviousExecutionState == ApplicationExecutionState.Terminated) - { - //TODO: Load state from previously suspended application - } - - // Place the frame in the current Window - Window.Current.Content = rootFrame; - } - - if (e.PrelaunchActivated == false) - { - if (rootFrame.Content == null) - { - // When the navigation stack isn't restored navigate to the first page, - // configuring the new page by passing required information as a navigation - // parameter - rootFrame.Navigate(typeof(MainPage), e.Arguments); - } - // Ensure the current window is active - Window.Current.Activate(); + await ActivateWinoAsync(args); } } - /// - /// Invoked when Navigation to a certain page fails - /// - /// The Frame which failed navigation - /// Details about the navigation failure - void OnNavigationFailed(object sender, NavigationFailedEventArgs e) + protected override IEnumerable GetActivationHandlers() { - throw new Exception("Failed to load Page " + e.SourcePageType.FullName); + return null; } - /// - /// Invoked when application execution is being suspended. Application state is saved - /// without knowing whether the application will be terminated or resumed with the contents - /// of memory still intact. - /// - /// The source of the suspend request. - /// Details about the suspend request. - private void OnSuspending(object sender, SuspendingEventArgs e) - { - var deferral = e.SuspendingOperation.GetDeferral(); - //TODO: Save application state and stop any background activity - deferral.Complete(); - } + protected override ActivationHandler GetDefaultActivationHandler() + => new DefaultActivationHandler(); } } diff --git a/Wino.Calendar/Args/TimelineCellSelectedArgs.cs b/Wino.Calendar/Args/TimelineCellSelectedArgs.cs new file mode 100644 index 00000000..31f9e270 --- /dev/null +++ b/Wino.Calendar/Args/TimelineCellSelectedArgs.cs @@ -0,0 +1,41 @@ +using System; +using Windows.Foundation; + +namespace Wino.Calendar.Args +{ + /// + /// When a new timeline cell is selected. + /// + public class TimelineCellSelectedArgs : EventArgs + { + public TimelineCellSelectedArgs(DateTime clickedDate, Point canvasPoint, Point positionerPoint, Size cellSize) + { + ClickedDate = clickedDate; + CanvasPoint = canvasPoint; + PositionerPoint = positionerPoint; + CellSize = cellSize; + } + + /// + /// Clicked date and time information for the cell. + /// + public DateTime ClickedDate { get; set; } + + /// + /// Position relative to the cell drawing part of the canvas. + /// Used to detect clicked cell from the position. + /// + public Point CanvasPoint { get; } + + /// + /// Position relative to the main root positioner element of the drawing canvas. + /// Used to show the create event dialog teaching tip in correct position. + /// + public Point PositionerPoint { get; } + + /// + /// Size of the cell. + /// + public Size CellSize { get; } + } +} diff --git a/Wino.Calendar/Args/TimelineCellUnselectedArgs.cs b/Wino.Calendar/Args/TimelineCellUnselectedArgs.cs new file mode 100644 index 00000000..7541a831 --- /dev/null +++ b/Wino.Calendar/Args/TimelineCellUnselectedArgs.cs @@ -0,0 +1,9 @@ +using System; + +namespace Wino.Calendar.Args +{ + /// + /// When selected timeline cell is unselected. + /// + public class TimelineCellUnselectedArgs : EventArgs { } +} diff --git a/Wino.Calendar/Assets/LargeTile.scale-100.png b/Wino.Calendar/Assets/LargeTile.scale-100.png new file mode 100644 index 00000000..42334872 Binary files /dev/null and b/Wino.Calendar/Assets/LargeTile.scale-100.png differ diff --git a/Wino.Calendar/Assets/LargeTile.scale-125.png b/Wino.Calendar/Assets/LargeTile.scale-125.png new file mode 100644 index 00000000..9c03a75c Binary files /dev/null and b/Wino.Calendar/Assets/LargeTile.scale-125.png differ diff --git a/Wino.Calendar/Assets/LargeTile.scale-150.png b/Wino.Calendar/Assets/LargeTile.scale-150.png new file mode 100644 index 00000000..e29aa6e0 Binary files /dev/null and b/Wino.Calendar/Assets/LargeTile.scale-150.png differ diff --git a/Wino.Calendar/Assets/LargeTile.scale-200.png b/Wino.Calendar/Assets/LargeTile.scale-200.png new file mode 100644 index 00000000..5ad11086 Binary files /dev/null and b/Wino.Calendar/Assets/LargeTile.scale-200.png differ diff --git a/Wino.Calendar/Assets/LargeTile.scale-400.png b/Wino.Calendar/Assets/LargeTile.scale-400.png new file mode 100644 index 00000000..af446cf4 Binary files /dev/null and b/Wino.Calendar/Assets/LargeTile.scale-400.png differ diff --git a/Wino.Calendar/Assets/SmallTile.scale-100.png b/Wino.Calendar/Assets/SmallTile.scale-100.png new file mode 100644 index 00000000..2d1167f7 Binary files /dev/null and b/Wino.Calendar/Assets/SmallTile.scale-100.png differ diff --git a/Wino.Calendar/Assets/SmallTile.scale-125.png b/Wino.Calendar/Assets/SmallTile.scale-125.png new file mode 100644 index 00000000..d8bc11c5 Binary files /dev/null and b/Wino.Calendar/Assets/SmallTile.scale-125.png differ diff --git a/Wino.Calendar/Assets/SmallTile.scale-150.png b/Wino.Calendar/Assets/SmallTile.scale-150.png new file mode 100644 index 00000000..e124da41 Binary files /dev/null and b/Wino.Calendar/Assets/SmallTile.scale-150.png differ diff --git a/Wino.Calendar/Assets/SmallTile.scale-200.png b/Wino.Calendar/Assets/SmallTile.scale-200.png new file mode 100644 index 00000000..3854c86c Binary files /dev/null and b/Wino.Calendar/Assets/SmallTile.scale-200.png differ diff --git a/Wino.Calendar/Assets/SmallTile.scale-400.png b/Wino.Calendar/Assets/SmallTile.scale-400.png new file mode 100644 index 00000000..f242a580 Binary files /dev/null and b/Wino.Calendar/Assets/SmallTile.scale-400.png differ diff --git a/Wino.Calendar/Assets/SplashScreen.scale-100.png b/Wino.Calendar/Assets/SplashScreen.scale-100.png new file mode 100644 index 00000000..86ade338 Binary files /dev/null and b/Wino.Calendar/Assets/SplashScreen.scale-100.png differ diff --git a/Wino.Calendar/Assets/SplashScreen.scale-125.png b/Wino.Calendar/Assets/SplashScreen.scale-125.png new file mode 100644 index 00000000..55d0473d Binary files /dev/null and b/Wino.Calendar/Assets/SplashScreen.scale-125.png differ diff --git a/Wino.Calendar/Assets/SplashScreen.scale-150.png b/Wino.Calendar/Assets/SplashScreen.scale-150.png new file mode 100644 index 00000000..80b9ed0d Binary files /dev/null and b/Wino.Calendar/Assets/SplashScreen.scale-150.png differ diff --git a/Wino.Calendar/Assets/SplashScreen.scale-200.png b/Wino.Calendar/Assets/SplashScreen.scale-200.png index 023e7f1f..8510c90c 100644 Binary files a/Wino.Calendar/Assets/SplashScreen.scale-200.png and b/Wino.Calendar/Assets/SplashScreen.scale-200.png differ diff --git a/Wino.Calendar/Assets/SplashScreen.scale-400.png b/Wino.Calendar/Assets/SplashScreen.scale-400.png new file mode 100644 index 00000000..7efc715b Binary files /dev/null and b/Wino.Calendar/Assets/SplashScreen.scale-400.png differ diff --git a/Wino.Calendar/Assets/Square150x150Logo.scale-100.png b/Wino.Calendar/Assets/Square150x150Logo.scale-100.png new file mode 100644 index 00000000..37501fe7 Binary files /dev/null and b/Wino.Calendar/Assets/Square150x150Logo.scale-100.png differ diff --git a/Wino.Calendar/Assets/Square150x150Logo.scale-125.png b/Wino.Calendar/Assets/Square150x150Logo.scale-125.png new file mode 100644 index 00000000..0622878d Binary files /dev/null and b/Wino.Calendar/Assets/Square150x150Logo.scale-125.png differ diff --git a/Wino.Calendar/Assets/Square150x150Logo.scale-150.png b/Wino.Calendar/Assets/Square150x150Logo.scale-150.png new file mode 100644 index 00000000..15dc07b5 Binary files /dev/null and b/Wino.Calendar/Assets/Square150x150Logo.scale-150.png differ diff --git a/Wino.Calendar/Assets/Square150x150Logo.scale-200.png b/Wino.Calendar/Assets/Square150x150Logo.scale-200.png index af49fec1..75c9945a 100644 Binary files a/Wino.Calendar/Assets/Square150x150Logo.scale-200.png and b/Wino.Calendar/Assets/Square150x150Logo.scale-200.png differ diff --git a/Wino.Calendar/Assets/Square150x150Logo.scale-400.png b/Wino.Calendar/Assets/Square150x150Logo.scale-400.png new file mode 100644 index 00000000..cb218b68 Binary files /dev/null and b/Wino.Calendar/Assets/Square150x150Logo.scale-400.png differ diff --git a/Wino.Calendar/Assets/Square44x44Logo.altform-lightunplated_targetsize-16.png b/Wino.Calendar/Assets/Square44x44Logo.altform-lightunplated_targetsize-16.png new file mode 100644 index 00000000..2c72b37d Binary files /dev/null and b/Wino.Calendar/Assets/Square44x44Logo.altform-lightunplated_targetsize-16.png differ diff --git a/Wino.Calendar/Assets/Square44x44Logo.altform-lightunplated_targetsize-24.png b/Wino.Calendar/Assets/Square44x44Logo.altform-lightunplated_targetsize-24.png new file mode 100644 index 00000000..895753c9 Binary files /dev/null and b/Wino.Calendar/Assets/Square44x44Logo.altform-lightunplated_targetsize-24.png differ diff --git a/Wino.Calendar/Assets/Square44x44Logo.altform-lightunplated_targetsize-256.png b/Wino.Calendar/Assets/Square44x44Logo.altform-lightunplated_targetsize-256.png new file mode 100644 index 00000000..eeaaad33 Binary files /dev/null and b/Wino.Calendar/Assets/Square44x44Logo.altform-lightunplated_targetsize-256.png differ diff --git a/Wino.Calendar/Assets/Square44x44Logo.altform-lightunplated_targetsize-32.png b/Wino.Calendar/Assets/Square44x44Logo.altform-lightunplated_targetsize-32.png new file mode 100644 index 00000000..e7c0cb66 Binary files /dev/null and b/Wino.Calendar/Assets/Square44x44Logo.altform-lightunplated_targetsize-32.png differ diff --git a/Wino.Calendar/Assets/Square44x44Logo.altform-lightunplated_targetsize-48.png b/Wino.Calendar/Assets/Square44x44Logo.altform-lightunplated_targetsize-48.png new file mode 100644 index 00000000..56792258 Binary files /dev/null and b/Wino.Calendar/Assets/Square44x44Logo.altform-lightunplated_targetsize-48.png differ diff --git a/Wino.Calendar/Assets/Square44x44Logo.altform-unplated_targetsize-16.png b/Wino.Calendar/Assets/Square44x44Logo.altform-unplated_targetsize-16.png new file mode 100644 index 00000000..2c72b37d Binary files /dev/null and b/Wino.Calendar/Assets/Square44x44Logo.altform-unplated_targetsize-16.png differ diff --git a/Wino.Calendar/Assets/Square44x44Logo.altform-unplated_targetsize-256.png b/Wino.Calendar/Assets/Square44x44Logo.altform-unplated_targetsize-256.png new file mode 100644 index 00000000..eeaaad33 Binary files /dev/null and b/Wino.Calendar/Assets/Square44x44Logo.altform-unplated_targetsize-256.png differ diff --git a/Wino.Calendar/Assets/Square44x44Logo.altform-unplated_targetsize-32.png b/Wino.Calendar/Assets/Square44x44Logo.altform-unplated_targetsize-32.png new file mode 100644 index 00000000..e7c0cb66 Binary files /dev/null and b/Wino.Calendar/Assets/Square44x44Logo.altform-unplated_targetsize-32.png differ diff --git a/Wino.Calendar/Assets/Square44x44Logo.altform-unplated_targetsize-48.png b/Wino.Calendar/Assets/Square44x44Logo.altform-unplated_targetsize-48.png new file mode 100644 index 00000000..56792258 Binary files /dev/null and b/Wino.Calendar/Assets/Square44x44Logo.altform-unplated_targetsize-48.png differ diff --git a/Wino.Calendar/Assets/Square44x44Logo.scale-100.png b/Wino.Calendar/Assets/Square44x44Logo.scale-100.png new file mode 100644 index 00000000..43b04c94 Binary files /dev/null and b/Wino.Calendar/Assets/Square44x44Logo.scale-100.png differ diff --git a/Wino.Calendar/Assets/Square44x44Logo.scale-125.png b/Wino.Calendar/Assets/Square44x44Logo.scale-125.png new file mode 100644 index 00000000..c6ca31f5 Binary files /dev/null and b/Wino.Calendar/Assets/Square44x44Logo.scale-125.png differ diff --git a/Wino.Calendar/Assets/Square44x44Logo.scale-150.png b/Wino.Calendar/Assets/Square44x44Logo.scale-150.png new file mode 100644 index 00000000..97bf5eb8 Binary files /dev/null and b/Wino.Calendar/Assets/Square44x44Logo.scale-150.png differ diff --git a/Wino.Calendar/Assets/Square44x44Logo.scale-200.png b/Wino.Calendar/Assets/Square44x44Logo.scale-200.png index ce342a2e..f1a2ddef 100644 Binary files a/Wino.Calendar/Assets/Square44x44Logo.scale-200.png and b/Wino.Calendar/Assets/Square44x44Logo.scale-200.png differ diff --git a/Wino.Calendar/Assets/Square44x44Logo.scale-400.png b/Wino.Calendar/Assets/Square44x44Logo.scale-400.png new file mode 100644 index 00000000..b25cb5bc Binary files /dev/null and b/Wino.Calendar/Assets/Square44x44Logo.scale-400.png differ diff --git a/Wino.Calendar/Assets/Square44x44Logo.targetsize-16.png b/Wino.Calendar/Assets/Square44x44Logo.targetsize-16.png new file mode 100644 index 00000000..22a4ac74 Binary files /dev/null and b/Wino.Calendar/Assets/Square44x44Logo.targetsize-16.png differ diff --git a/Wino.Calendar/Assets/Square44x44Logo.targetsize-24.png b/Wino.Calendar/Assets/Square44x44Logo.targetsize-24.png new file mode 100644 index 00000000..ac5a485d Binary files /dev/null and b/Wino.Calendar/Assets/Square44x44Logo.targetsize-24.png differ diff --git a/Wino.Calendar/Assets/Square44x44Logo.targetsize-24_altform-unplated.png b/Wino.Calendar/Assets/Square44x44Logo.targetsize-24_altform-unplated.png index f6c02ce9..895753c9 100644 Binary files a/Wino.Calendar/Assets/Square44x44Logo.targetsize-24_altform-unplated.png and b/Wino.Calendar/Assets/Square44x44Logo.targetsize-24_altform-unplated.png differ diff --git a/Wino.Calendar/Assets/Square44x44Logo.targetsize-256.png b/Wino.Calendar/Assets/Square44x44Logo.targetsize-256.png new file mode 100644 index 00000000..9e6a53b2 Binary files /dev/null and b/Wino.Calendar/Assets/Square44x44Logo.targetsize-256.png differ diff --git a/Wino.Calendar/Assets/Square44x44Logo.targetsize-32.png b/Wino.Calendar/Assets/Square44x44Logo.targetsize-32.png new file mode 100644 index 00000000..79388f0d Binary files /dev/null and b/Wino.Calendar/Assets/Square44x44Logo.targetsize-32.png differ diff --git a/Wino.Calendar/Assets/Square44x44Logo.targetsize-48.png b/Wino.Calendar/Assets/Square44x44Logo.targetsize-48.png new file mode 100644 index 00000000..47aea727 Binary files /dev/null and b/Wino.Calendar/Assets/Square44x44Logo.targetsize-48.png differ diff --git a/Wino.Calendar/Assets/StoreLogo.png b/Wino.Calendar/Assets/StoreLogo.backup.png similarity index 100% rename from Wino.Calendar/Assets/StoreLogo.png rename to Wino.Calendar/Assets/StoreLogo.backup.png diff --git a/Wino.Calendar/Assets/StoreLogo.scale-100.png b/Wino.Calendar/Assets/StoreLogo.scale-100.png new file mode 100644 index 00000000..caafaee8 Binary files /dev/null and b/Wino.Calendar/Assets/StoreLogo.scale-100.png differ diff --git a/Wino.Calendar/Assets/StoreLogo.scale-125.png b/Wino.Calendar/Assets/StoreLogo.scale-125.png new file mode 100644 index 00000000..59f77d4e Binary files /dev/null and b/Wino.Calendar/Assets/StoreLogo.scale-125.png differ diff --git a/Wino.Calendar/Assets/StoreLogo.scale-150.png b/Wino.Calendar/Assets/StoreLogo.scale-150.png new file mode 100644 index 00000000..6adf5536 Binary files /dev/null and b/Wino.Calendar/Assets/StoreLogo.scale-150.png differ diff --git a/Wino.Calendar/Assets/StoreLogo.scale-200.png b/Wino.Calendar/Assets/StoreLogo.scale-200.png new file mode 100644 index 00000000..b4b471f8 Binary files /dev/null and b/Wino.Calendar/Assets/StoreLogo.scale-200.png differ diff --git a/Wino.Calendar/Assets/StoreLogo.scale-400.png b/Wino.Calendar/Assets/StoreLogo.scale-400.png new file mode 100644 index 00000000..be94d352 Binary files /dev/null and b/Wino.Calendar/Assets/StoreLogo.scale-400.png differ diff --git a/Wino.Calendar/Assets/Wide310x150Logo.scale-100.png b/Wino.Calendar/Assets/Wide310x150Logo.scale-100.png new file mode 100644 index 00000000..0847e264 Binary files /dev/null and b/Wino.Calendar/Assets/Wide310x150Logo.scale-100.png differ diff --git a/Wino.Calendar/Assets/Wide310x150Logo.scale-125.png b/Wino.Calendar/Assets/Wide310x150Logo.scale-125.png new file mode 100644 index 00000000..0be7583f Binary files /dev/null and b/Wino.Calendar/Assets/Wide310x150Logo.scale-125.png differ diff --git a/Wino.Calendar/Assets/Wide310x150Logo.scale-150.png b/Wino.Calendar/Assets/Wide310x150Logo.scale-150.png new file mode 100644 index 00000000..f969318f Binary files /dev/null and b/Wino.Calendar/Assets/Wide310x150Logo.scale-150.png differ diff --git a/Wino.Calendar/Assets/Wide310x150Logo.scale-200.png b/Wino.Calendar/Assets/Wide310x150Logo.scale-200.png index 288995b3..86ade338 100644 Binary files a/Wino.Calendar/Assets/Wide310x150Logo.scale-200.png and b/Wino.Calendar/Assets/Wide310x150Logo.scale-200.png differ diff --git a/Wino.Calendar/Assets/Wide310x150Logo.scale-400.png b/Wino.Calendar/Assets/Wide310x150Logo.scale-400.png new file mode 100644 index 00000000..8510c90c Binary files /dev/null and b/Wino.Calendar/Assets/Wide310x150Logo.scale-400.png differ diff --git a/Wino.Calendar/Controls/CalendarDayItemsControl.cs b/Wino.Calendar/Controls/CalendarDayItemsControl.cs new file mode 100644 index 00000000..d5d06971 --- /dev/null +++ b/Wino.Calendar/Controls/CalendarDayItemsControl.cs @@ -0,0 +1,132 @@ +using System.Collections.ObjectModel; +using System.Collections.Specialized; +using System.Linq; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Wino.Core.Domain.Interfaces; +using Wino.Core.Domain.Models.Calendar; + +namespace Wino.Calendar.Controls +{ + public class CalendarDayItemsControl : Control + { + private const string PART_CalendarPanel = nameof(PART_CalendarPanel); + + private WinoCalendarPanel CalendarPanel; + + public CalendarDayModel DayModel + { + get { return (CalendarDayModel)GetValue(DayModelProperty); } + set { SetValue(DayModelProperty, value); } + } + + + public static readonly DependencyProperty DayModelProperty = DependencyProperty.Register(nameof(DayModel), typeof(CalendarDayModel), typeof(CalendarDayItemsControl), new PropertyMetadata(null, new PropertyChangedCallback(OnRepresentingDateChanged))); + + private static void OnRepresentingDateChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is CalendarDayItemsControl control) + { + if (e.OldValue != null && e.OldValue is CalendarDayModel oldCalendarDayModel) + { + control.DetachCollection(oldCalendarDayModel.Events); + } + + if (e.NewValue != null && e.NewValue is CalendarDayModel newCalendarDayModel) + { + control.AttachCollection(newCalendarDayModel.Events); + } + + control.ResetItems(); + control.RenderEvents(); + } + } + + public CalendarDayItemsControl() + { + DefaultStyleKey = typeof(CalendarDayItemsControl); + } + + protected override void OnApplyTemplate() + { + base.OnApplyTemplate(); + + CalendarPanel = GetTemplateChild(PART_CalendarPanel) as WinoCalendarPanel; + + RenderEvents(); + } + + private void ResetItems() + { + if (CalendarPanel == null) return; + + CalendarPanel.Children.Clear(); + } + + private void RenderEvents() + { + if (CalendarPanel == null || CalendarPanel.DayModel == null) return; + + RenderCalendarItems(); + } + + private void AttachCollection(ObservableCollection newCollection) + => newCollection.CollectionChanged += CalendarItemsChanged; + + private void DetachCollection(ObservableCollection oldCollection) + => oldCollection.CollectionChanged -= CalendarItemsChanged; + + private void CalendarItemsChanged(object sender, NotifyCollectionChangedEventArgs e) + { + switch (e.Action) + { + case NotifyCollectionChangedAction.Add: + foreach (ICalendarItem item in e.NewItems) + { + AddItem(item); + } + break; + case NotifyCollectionChangedAction.Remove: + foreach (ICalendarItem item in e.OldItems) + { + var control = GetCalendarItemControl(item); + if (control != null) + { + CalendarPanel.Children.Remove(control); + } + } + break; + case NotifyCollectionChangedAction.Reset: + ResetItems(); + break; + default: + break; + } + } + + private CalendarItemControl GetCalendarItemControl(ICalendarItem item) + => CalendarPanel.Children.Where(c => c is CalendarItemControl calendarItemControl && calendarItemControl.Item == item).FirstOrDefault() as CalendarItemControl; + + private void RenderCalendarItems() + { + if (DayModel == null || DayModel.Events == null || DayModel.Events.Count == 0) + { + ResetItems(); + return; + } + + foreach (var item in DayModel.Events) + { + AddItem(item); + } + } + + private void AddItem(ICalendarItem item) + { + CalendarPanel.Children.Add(new CalendarItemControl() + { + Item = item + }); + } + } +} diff --git a/Wino.Calendar/Controls/CalendarItemControl.cs b/Wino.Calendar/Controls/CalendarItemControl.cs new file mode 100644 index 00000000..23a66563 --- /dev/null +++ b/Wino.Calendar/Controls/CalendarItemControl.cs @@ -0,0 +1,42 @@ +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Wino.Core.Domain.Interfaces; + +namespace Wino.Calendar.Controls +{ + public class CalendarItemControl : Control + { + public ICalendarItem Item + { + get { return (ICalendarItem)GetValue(ItemProperty); } + set { SetValue(ItemProperty, value); } + } + + public static readonly DependencyProperty ItemProperty = DependencyProperty.Register(nameof(Item), typeof(ICalendarItem), typeof(CalendarItemControl), new PropertyMetadata(null, new PropertyChangedCallback(OnItemChanged))); + + public CalendarItemControl() + { + DefaultStyleKey = typeof(CalendarItemControl); + } + + private static void OnItemChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is CalendarItemControl control) + { + control.UpdateDateRendering(); + } + } + + private void UpdateDateRendering() + { + if (Item == null) return; + + UpdateLayout(); + } + + public override string ToString() + { + return Item?.Name ?? "NA"; + } + } +} diff --git a/Wino.Calendar/Controls/CustomCalendarFlipView.cs b/Wino.Calendar/Controls/CustomCalendarFlipView.cs new file mode 100644 index 00000000..e8a5b8e4 --- /dev/null +++ b/Wino.Calendar/Controls/CustomCalendarFlipView.cs @@ -0,0 +1,42 @@ +using Windows.UI.Xaml.Automation.Peers; +using Windows.UI.Xaml.Controls; + +namespace Wino.Calendar.Controls +{ + /// + /// FlipView that hides the navigation buttons and exposes methods to navigate to the next and previous items with animations. + /// + public class CustomCalendarFlipView : FlipView + { + private const string PART_PreviousButton = "PreviousButtonHorizontal"; + private const string PART_NextButton = "NextButtonHorizontal"; + + private Button PreviousButton; + private Button NextButton; + + protected override void OnApplyTemplate() + { + base.OnApplyTemplate(); + + PreviousButton = GetTemplateChild(PART_PreviousButton) as Button; + NextButton = GetTemplateChild(PART_NextButton) as Button; + + // Hide navigation buttons + PreviousButton.Opacity = NextButton.Opacity = 0; + PreviousButton.IsHitTestVisible = NextButton.IsHitTestVisible = false; + } + + public void GoPreviousFlip() + { + var backPeer = new ButtonAutomationPeer(PreviousButton); + backPeer.Invoke(); + } + + public void GoNextFlip() + { + var nextPeer = new ButtonAutomationPeer(NextButton); + nextPeer.Invoke(); + } + + } +} diff --git a/Wino.Calendar/Controls/DayColumnControl.cs b/Wino.Calendar/Controls/DayColumnControl.cs new file mode 100644 index 00000000..43d17877 --- /dev/null +++ b/Wino.Calendar/Controls/DayColumnControl.cs @@ -0,0 +1,65 @@ +using System; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Wino.Core.Domain.Models.Calendar; + +namespace Wino.Calendar.Controls +{ + public class DayColumnControl : Control + { + private const string PART_HeaderDateDayText = nameof(PART_HeaderDateDayText); + private const string PART_IsTodayBorder = nameof(PART_IsTodayBorder); + private const string PART_ColumnHeaderText = nameof(PART_ColumnHeaderText); + + private const string TodayState = nameof(TodayState); + private const string NotTodayState = nameof(NotTodayState); + + private TextBlock HeaderDateDayText; + private TextBlock ColumnHeaderText; + private Border IsTodayBorder; + + public CalendarDayModel DayModel + { + get { return (CalendarDayModel)GetValue(DayModelProperty); } + set { SetValue(DayModelProperty, value); } + } + + public static readonly DependencyProperty DayModelProperty = DependencyProperty.Register(nameof(DayModel), typeof(CalendarDayModel), typeof(DayColumnControl), new PropertyMetadata(null, new PropertyChangedCallback(OnRenderingPropertiesChanged))); + + public DayColumnControl() + { + DefaultStyleKey = typeof(DayColumnControl); + } + + protected override void OnApplyTemplate() + { + base.OnApplyTemplate(); + + HeaderDateDayText = GetTemplateChild(PART_HeaderDateDayText) as TextBlock; + ColumnHeaderText = GetTemplateChild(PART_ColumnHeaderText) as TextBlock; + IsTodayBorder = GetTemplateChild(PART_IsTodayBorder) as Border; + + UpdateValues(); + } + + private static void OnRenderingPropertiesChanged(DependencyObject control, DependencyPropertyChangedEventArgs e) + { + if (control is DayColumnControl columnControl) + { + columnControl.UpdateValues(); + } + } + + private void UpdateValues() + { + if (HeaderDateDayText == null || ColumnHeaderText == null || IsTodayBorder == null || DayModel == null) return; + + HeaderDateDayText.Text = DayModel.RepresentingDate.Day.ToString(); + ColumnHeaderText.Text = DayModel.RepresentingDate.ToString("dddd", DayModel.CalendarRenderOptions.CalendarSettings.CultureInfo); + + bool isToday = DayModel.RepresentingDate.Date == DateTime.Now.Date; + + VisualStateManager.GoToState(this, isToday ? TodayState : NotTodayState, false); + } + } +} diff --git a/Wino.Calendar/Controls/DayHeaderControl.cs b/Wino.Calendar/Controls/DayHeaderControl.cs new file mode 100644 index 00000000..1a5d09f8 --- /dev/null +++ b/Wino.Calendar/Controls/DayHeaderControl.cs @@ -0,0 +1,57 @@ +using System; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Wino.Core.Domain.Enums; + +namespace Wino.Calendar.Controls +{ + public class DayHeaderControl : Control + { + private const string PART_DayHeaderTextBlock = nameof(PART_DayHeaderTextBlock); + private TextBlock HeaderTextblock; + + public DayHeaderDisplayType DisplayType + { + get { return (DayHeaderDisplayType)GetValue(DisplayTypeProperty); } + set { SetValue(DisplayTypeProperty, value); } + } + + public DateTime Date + { + get { return (DateTime)GetValue(DateProperty); } + set { SetValue(DateProperty, value); } + } + + public static readonly DependencyProperty DateProperty = DependencyProperty.Register(nameof(Date), typeof(DateTime), typeof(DayHeaderControl), new PropertyMetadata(default(DateTime), new PropertyChangedCallback(OnHeaderPropertyChanged))); + public static readonly DependencyProperty DisplayTypeProperty = DependencyProperty.Register(nameof(DisplayType), typeof(DayHeaderDisplayType), typeof(DayHeaderControl), new PropertyMetadata(DayHeaderDisplayType.TwentyFourHour, new PropertyChangedCallback(OnHeaderPropertyChanged))); + + public DayHeaderControl() + { + DefaultStyleKey = typeof(DayHeaderControl); + } + + protected override void OnApplyTemplate() + { + base.OnApplyTemplate(); + + HeaderTextblock = GetTemplateChild(PART_DayHeaderTextBlock) as TextBlock; + UpdateHeaderText(); + } + + private static void OnHeaderPropertyChanged(DependencyObject control, DependencyPropertyChangedEventArgs e) + { + if (control is DayHeaderControl headerControl) + { + headerControl.UpdateHeaderText(); + } + } + + private void UpdateHeaderText() + { + if (HeaderTextblock != null) + { + HeaderTextblock.Text = DisplayType == DayHeaderDisplayType.TwelveHour ? Date.ToString("h tt") : Date.ToString("HH:mm"); + } + } + } +} diff --git a/Wino.Calendar/Controls/WinoCalendarControl.cs b/Wino.Calendar/Controls/WinoCalendarControl.cs new file mode 100644 index 00000000..f42f937f --- /dev/null +++ b/Wino.Calendar/Controls/WinoCalendarControl.cs @@ -0,0 +1,144 @@ +using System; +using System.Collections.ObjectModel; +using CommunityToolkit.Mvvm.Messaging; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Wino.Calendar.Args; +using Wino.Core.Domain.Models.Calendar; +using Wino.Messaging.Client.Calendar; + +namespace Wino.Calendar.Controls +{ + public class WinoCalendarControl : Control + { + private const string PART_WinoFlipView = nameof(PART_WinoFlipView); + + public event EventHandler TimelineCellSelected; + public event EventHandler TimelineCellUnselected; + + #region Dependency Properties + + public static readonly DependencyProperty DayRangesProperty = DependencyProperty.Register(nameof(DayRanges), typeof(ObservableCollection), typeof(WinoCalendarControl), new PropertyMetadata(null)); + public static readonly DependencyProperty SelectedFlipViewIndexProperty = DependencyProperty.Register(nameof(SelectedFlipViewIndex), typeof(int), typeof(WinoCalendarControl), new PropertyMetadata(-1)); + public static readonly DependencyProperty SelectedFlipViewDayRangeProperty = DependencyProperty.Register(nameof(SelectedFlipViewDayRange), typeof(DayRangeRenderModel), typeof(WinoCalendarControl), new PropertyMetadata(null)); + + public DayRangeRenderModel SelectedFlipViewDayRange + { + get { return (DayRangeRenderModel)GetValue(SelectedFlipViewDayRangeProperty); } + set { SetValue(SelectedFlipViewDayRangeProperty, value); } + } + + /// + /// Gets or sets the collection of day ranges to render. + /// Each day range usually represents a week, but it may support other ranges. + /// + public ObservableCollection DayRanges + { + get { return (ObservableCollection)GetValue(DayRangesProperty); } + set { SetValue(DayRangesProperty, value); } + } + + public int SelectedFlipViewIndex + { + get { return (int)GetValue(SelectedFlipViewIndexProperty); } + set { SetValue(SelectedFlipViewIndexProperty, value); } + } + + #endregion + + private WinoDayTimelineCanvas _activeCanvas; + + public WinoDayTimelineCanvas ActiveCanvas + { + get { return _activeCanvas; } + set + { + // FlipView's timeline is changing. + // Make sure to unregister from the old one. + + if (_activeCanvas != null) + { + // Dismiss any selection on the old canvas. + + _activeCanvas.SelectedDateTime = null; + _activeCanvas.TimelineCellSelected -= ActiveTimelineCellSelected; + _activeCanvas.TimelineCellUnselected -= ActiveTimelineCellUnselected; + } + + _activeCanvas = value; + + if (_activeCanvas != null) + { + _activeCanvas.TimelineCellSelected += ActiveTimelineCellSelected; + _activeCanvas.TimelineCellUnselected += ActiveTimelineCellUnselected; + + // Raise visible date range change to shell. + WeakReferenceMessenger.Default.Send(new VisibleDateRangeChangedMessage(_activeCanvas.RenderOptions.DateRange)); + } + } + } + + private WinoCalendarFlipView InternalFlipView; + + public WinoCalendarControl() + { + DefaultStyleKey = typeof(WinoCalendarControl); + SizeChanged += CalendarSizeChanged; + } + + private void CalendarSizeChanged(object sender, SizeChangedEventArgs e) + { + if (ActiveCanvas == null) return; + + ActiveCanvas.SelectedDateTime = null; + } + + protected override void OnApplyTemplate() + { + base.OnApplyTemplate(); + + InternalFlipView = GetTemplateChild(PART_WinoFlipView) as WinoCalendarFlipView; + + // Each FlipViewItem will have 1 timeline canvas to draw hour cells in the background that supports selection of them. + // When the selection changes, we need to stop listening to the old canvas and start listening to the new one to catch events. + + InternalFlipView.ActiveTimelineCanvasChanged += FlipViewsActiveTimelineCanvasChanged; + } + + private void FlipViewsActiveTimelineCanvasChanged(object sender, WinoDayTimelineCanvas e) + { + ActiveCanvas = e; + + SelectedFlipViewDayRange = InternalFlipView.SelectedItem as DayRangeRenderModel; + } + + private void ActiveTimelineCellUnselected(object sender, TimelineCellUnselectedArgs e) + => TimelineCellUnselected?.Invoke(this, e); + + private void ActiveTimelineCellSelected(object sender, TimelineCellSelectedArgs e) + => TimelineCellSelected?.Invoke(this, e); + + public void NavigateToDay(DateTime dateTime) => InternalFlipView.NavigateToDay(dateTime); + + public void ResetTimelineSelection() + { + if (ActiveCanvas == null) return; + + ActiveCanvas.SelectedDateTime = null; + } + + public void GoNextRange() + { + if (InternalFlipView == null) return; + + InternalFlipView.GoNextFlip(); + } + + public void GoPreviousRange() + { + if (InternalFlipView == null) return; + + InternalFlipView.GoPreviousFlip(); + } + } +} diff --git a/Wino.Calendar/Controls/WinoCalendarFlipView.cs b/Wino.Calendar/Controls/WinoCalendarFlipView.cs new file mode 100644 index 00000000..c61dfbb6 --- /dev/null +++ b/Wino.Calendar/Controls/WinoCalendarFlipView.cs @@ -0,0 +1,91 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using CommunityToolkit.WinUI; +using Windows.UI.Xaml.Controls; +using Wino.Core.Domain.Models.Calendar; +using Wino.Core.MenuItems; + +namespace Wino.Calendar.Controls +{ + public class WinoCalendarFlipView : CustomCalendarFlipView + { + public event EventHandler ActiveTimelineCanvasChanged; + + public WinoCalendarFlipView() + { + SelectionChanged += CalendarDisplayRangeChanged; + } + + private async void CalendarDisplayRangeChanged(object sender, SelectionChangedEventArgs e) + { + if (SelectedIndex < 0) + ActiveTimelineCanvasChanged?.Invoke(this, null); + else + { + // TODO: Refactor this mechanism by listening to PrepareContainerForItemOverride and Loaded events together. + while (ContainerFromIndex(SelectedIndex) == null) + { + await Task.Delay(250); + } + + if (ContainerFromIndex(SelectedIndex) is FlipViewItem flipViewItem) + { + var canvas = flipViewItem.FindDescendant(); + ActiveTimelineCanvasChanged?.Invoke(this, canvas); + } + } + } + + /// + /// Navigates to the specified date in the calendar. + /// + /// Date to navigate. + public async void NavigateToDay(DateTime dateTime) + { + // Find the day range that contains the date. + var dayRange = GetItemsSource()?.FirstOrDefault(a => a.CalendarDays.Any(b => b.RepresentingDate.Date == dateTime.Date)); + + if (dayRange != null) + { + var navigationItemIndex = GetItemsSource().IndexOf(dayRange); + + if (Math.Abs(navigationItemIndex - SelectedIndex) > 4) + { + // Difference between dates are high. + // No need to animate this much, just go without animating. + + SelectedIndex = navigationItemIndex; + } + else + { + // Until we reach the day in the flip, simulate next-prev button clicks. + // This will make sure the FlipView animations are triggered. + // Setting SelectedIndex directly doesn't trigger the animations. + + while (SelectedIndex != navigationItemIndex) + { + if (SelectedIndex > navigationItemIndex) + { + GoPreviousFlip(); + } + else + { + GoNextFlip(); + } + } + } + } + } + + + public void NavigateHour(TimeSpan hourTimeSpan) + { + // Total height of the FlipViewItem is the same as vertical ScrollViewer to position day headers. + // Find the day range that contains the hour. + } + + private ObservableRangeCollection GetItemsSource() + => ItemsSource as ObservableRangeCollection; + } +} diff --git a/Wino.Calendar/Controls/WinoCalendarPanel.cs b/Wino.Calendar/Controls/WinoCalendarPanel.cs new file mode 100644 index 00000000..9e19ae62 --- /dev/null +++ b/Wino.Calendar/Controls/WinoCalendarPanel.cs @@ -0,0 +1,254 @@ + +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Linq; +using Windows.Foundation; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Wino.Calendar.Models; +using Wino.Core.Domain.Interfaces; +using Wino.Core.Domain.Models.Calendar; + +namespace Wino.Calendar.Controls +{ + public class WinoCalendarPanel : Panel + { + private const double LastItemRightExtraMargin = 12d; + + // Store each ICalendarItem measurements by their Id. + private readonly Dictionary _measurements = new Dictionary(); + + public static readonly DependencyProperty EventItemMarginProperty = DependencyProperty.Register(nameof(EventItemMargin), typeof(Thickness), typeof(WinoCalendarPanel), new PropertyMetadata(new Thickness(0, 0, 0, 0))); + public static readonly DependencyProperty DayModelProperty = DependencyProperty.Register(nameof(DayModel), typeof(CalendarDayModel), typeof(WinoCalendarPanel), new PropertyMetadata(null, new PropertyChangedCallback(OnDayChanged))); + + public CalendarDayModel DayModel + { + get { return (CalendarDayModel)GetValue(DayModelProperty); } + set { SetValue(DayModelProperty, value); } + } + + public Thickness EventItemMargin + { + get { return (Thickness)GetValue(EventItemMarginProperty); } + set { SetValue(EventItemMarginProperty, value); } + } + + private static void OnDayChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is WinoCalendarPanel control) + { + // We need to listen for new events being added or removed from the collection to reset measurements. + if (e.OldValue is CalendarDayModel oldDayModel) + { + control.DetachCollection(oldDayModel.Events); + } + + if (e.NewValue is CalendarDayModel newDayModel) + { + control.AttachCollection(newDayModel.Events); + } + + control.ResetMeasurements(); + control.UpdateLayout(); + } + } + + private void AttachCollection(IEnumerable events) + { + if (events is INotifyCollectionChanged collection) + { + collection.CollectionChanged += EventCollectionChanged; + } + } + + private void DetachCollection(IEnumerable events) + { + if (events is INotifyCollectionChanged collection) + { + collection.CollectionChanged -= EventCollectionChanged; + } + } + + private void ResetMeasurements() => _measurements.Clear(); + + // No need to handle actions. Each action requires a full measurement update. + private void EventCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) => ResetMeasurements(); + + private double GetChildTopMargin(DateTime childStart, double availableHeight) + { + double totalMinutes = 1440; + double minutesFromStart = (childStart - DayModel.RepresentingDate).TotalMinutes; + return (minutesFromStart / totalMinutes) * availableHeight; + } + + private double GetChildWidth(CalendarItemMeasurement calendarItemMeasurement, double availableWidth) + { + return (calendarItemMeasurement.Right - calendarItemMeasurement.Left) * availableWidth; + } + + private double GetChildLeftMargin(CalendarItemMeasurement calendarItemMeasurement, double availableWidth) + => availableWidth * calendarItemMeasurement.Left; + + private double GetChildHeight(DateTime childStart, DateTime childEnd) + { + double totalMinutes = 1440; + double availableHeight = DayModel.CalendarRenderOptions.CalendarSettings.HourHeight * 24; + double childDuration = (childEnd - childStart).TotalMinutes; + return (childDuration / totalMinutes) * availableHeight; + } + + protected override Size ArrangeOverride(Size finalSize) + { + // Measure/arrange each child height and width. + // This is a vertical calendar. Therefore the height of each child is the duration of the event. + // Children weights for left and right will be saved if they don't exist. + // This is important because we don't want to measure the weights again. + // They don't change until new event is added or removed. + // Width of the each child may depend on the rectangle packing algorithm. + // Children are first categorized into columns. Then each column is shifted to the left until + // no overlap occurs. The width of each child is calculated based on the number of columns it spans. + + double availableHeight = finalSize.Height; + double availableWidth = finalSize.Width; + + var calendarControls = Children.Cast(); + + if (_measurements.Count == 0 && DayModel.Events.Count > 0) + { + // We keep track of this collection when event is added/removed/reset etc. + // So if the collection is empty, we must fill it up again for proper calculations. + + LayoutEvents(DayModel.Events); + } + + foreach (var child in calendarControls) + { + // We can't arrange this child. It doesn't have a valid ICalendarItem or measurement. + if (child.Item == null || !_measurements.ContainsKey(child.Item.Id)) continue; + + var childMeasurement = _measurements[child.Item.Id]; + + double childHeight = Math.Max(0, GetChildHeight(child.Item.StartTime, child.Item.EndTime)); + double childWidth = Math.Max(0, GetChildWidth(childMeasurement, finalSize.Width)); + double childTop = Math.Max(0, GetChildTopMargin(child.Item.StartTime, availableHeight)); + double childLeft = Math.Max(0, GetChildLeftMargin(childMeasurement, availableWidth)); + + bool isHorizontallyLastItem = childMeasurement.Right == 1; + + // Add additional right margin to items that falls on the right edge of the panel. + // Max of 5% of the width or 20px. + var extraRightMargin = isHorizontallyLastItem ? Math.Max(LastItemRightExtraMargin, finalSize.Width * 5 / 100) : 0; + + var finalChildWidth = childWidth - extraRightMargin; + + if (finalChildWidth < 0) finalChildWidth = 1; + + child.Measure(new Size(childWidth, childHeight)); + + var arrangementRect = new Rect(childLeft + EventItemMargin.Left, childTop + EventItemMargin.Top, childWidth - extraRightMargin, childHeight); + + child.Arrange(arrangementRect); + } + + return finalSize; + } + + #region ColumSpanning and Packing Algorithm + + private void AddOrUpdateMeasurement(ICalendarItem calendarItem, CalendarItemMeasurement measurement) + { + if (_measurements.ContainsKey(calendarItem.Id)) + { + _measurements[calendarItem.Id] = measurement; + } + else + { + _measurements.Add(calendarItem.Id, measurement); + } + } + + // Pick the left and right positions of each event, such that there are no overlap. + private void LayoutEvents(IEnumerable events) + { + var columns = new List>(); + DateTime? lastEventEnding = null; + + foreach (var ev in events.OrderBy(ev => ev.Period.Start).ThenBy(ev => ev.Period.End)) + { + if (ev.Period.Start >= lastEventEnding) + { + PackEvents(columns); + columns.Clear(); + lastEventEnding = null; + } + + bool placed = false; + + foreach (var col in columns) + { + if (!col.Last().Period.OverlapsWith(ev.Period)) + { + col.Add(ev); + placed = true; + break; + } + } + if (!placed) + { + columns.Add(new List { ev }); + } + if (lastEventEnding == null || ev.Period.End > lastEventEnding.Value) + { + lastEventEnding = ev.Period.End; + } + } + if (columns.Count > 0) + { + PackEvents(columns); + } + } + + // Set the left and right positions for each event in the connected group. + private void PackEvents(List> columns) + { + float numColumns = columns.Count; + int iColumn = 0; + + foreach (var col in columns) + { + foreach (var ev in col) + { + int colSpan = ExpandEvent(ev, iColumn, columns); + + var leftWeight = iColumn / numColumns; + var rightWeight = (iColumn + colSpan) / numColumns; + + AddOrUpdateMeasurement(ev, new CalendarItemMeasurement(leftWeight, rightWeight)); + } + + iColumn++; + } + } + + // Checks how many columns the event can expand into, without colliding with other events. + private int ExpandEvent(ICalendarItem ev, int iColumn, List> columns) + { + int colSpan = 1; + + foreach (var col in columns.Skip(iColumn + 1)) + { + foreach (var ev1 in col) + { + if (ev1.Period.OverlapsWith(ev.Period)) return colSpan; + } + + colSpan++; + } + + return colSpan; + } + + #endregion + } +} diff --git a/Wino.Calendar/Controls/WinoCalendarTypeSelectorControl.cs b/Wino.Calendar/Controls/WinoCalendarTypeSelectorControl.cs new file mode 100644 index 00000000..14169438 --- /dev/null +++ b/Wino.Calendar/Controls/WinoCalendarTypeSelectorControl.cs @@ -0,0 +1,92 @@ +using System.Windows.Input; +using CommunityToolkit.Diagnostics; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Wino.Core.Domain.Enums; + +namespace Wino.Calendar.Controls +{ + public class WinoCalendarTypeSelectorControl : Control + { + private const string PART_TodayButton = nameof(PART_TodayButton); + private const string PART_DayToggle = nameof(PART_DayToggle); + private const string PART_WeekToggle = nameof(PART_WeekToggle); + private const string PART_MonthToggle = nameof(PART_MonthToggle); + private const string PART_YearToggle = nameof(PART_YearToggle); + + public static readonly DependencyProperty SelectedTypeProperty = DependencyProperty.Register(nameof(SelectedType), typeof(CalendarDisplayType), typeof(WinoCalendarTypeSelectorControl), new PropertyMetadata(CalendarDisplayType.Week)); + public static readonly DependencyProperty DisplayDayCountProperty = DependencyProperty.Register(nameof(DisplayDayCount), typeof(int), typeof(WinoCalendarTypeSelectorControl), new PropertyMetadata(0)); + public static readonly DependencyProperty TodayClickedCommandProperty = DependencyProperty.Register(nameof(TodayClickedCommand), typeof(ICommand), typeof(WinoCalendarTypeSelectorControl), new PropertyMetadata(null)); + + public ICommand TodayClickedCommand + { + get { return (ICommand)GetValue(TodayClickedCommandProperty); } + set { SetValue(TodayClickedCommandProperty, value); } + } + + public CalendarDisplayType SelectedType + { + get { return (CalendarDisplayType)GetValue(SelectedTypeProperty); } + set { SetValue(SelectedTypeProperty, value); } + } + + public int DisplayDayCount + { + get { return (int)GetValue(DisplayDayCountProperty); } + set { SetValue(DisplayDayCountProperty, value); } + } + + private AppBarButton _todayButton; + private AppBarToggleButton _dayToggle; + private AppBarToggleButton _weekToggle; + private AppBarToggleButton _monthToggle; + private AppBarToggleButton _yearToggle; + + public WinoCalendarTypeSelectorControl() + { + DefaultStyleKey = typeof(WinoCalendarTypeSelectorControl); + } + + protected override void OnApplyTemplate() + { + base.OnApplyTemplate(); + + _todayButton = GetTemplateChild(PART_TodayButton) as AppBarButton; + _dayToggle = GetTemplateChild(PART_DayToggle) as AppBarToggleButton; + _weekToggle = GetTemplateChild(PART_WeekToggle) as AppBarToggleButton; + _monthToggle = GetTemplateChild(PART_MonthToggle) as AppBarToggleButton; + _yearToggle = GetTemplateChild(PART_YearToggle) as AppBarToggleButton; + + Guard.IsNotNull(_todayButton, nameof(_todayButton)); + Guard.IsNotNull(_dayToggle, nameof(_dayToggle)); + Guard.IsNotNull(_weekToggle, nameof(_weekToggle)); + Guard.IsNotNull(_monthToggle, nameof(_monthToggle)); + Guard.IsNotNull(_yearToggle, nameof(_yearToggle)); + + _todayButton.Click += TodayClicked; + + _dayToggle.Click += (s, e) => { SetSelectedType(CalendarDisplayType.Day); }; + _weekToggle.Click += (s, e) => { SetSelectedType(CalendarDisplayType.Week); }; + _monthToggle.Click += (s, e) => { SetSelectedType(CalendarDisplayType.Month); }; + _yearToggle.Click += (s, e) => { SetSelectedType(CalendarDisplayType.Year); }; + + UpdateToggleButtonStates(); + } + + private void TodayClicked(object sender, RoutedEventArgs e) => TodayClickedCommand?.Execute(null); + + private void SetSelectedType(CalendarDisplayType type) + { + SelectedType = type; + UpdateToggleButtonStates(); + } + + private void UpdateToggleButtonStates() + { + _dayToggle.IsChecked = SelectedType == CalendarDisplayType.Day; + _weekToggle.IsChecked = SelectedType == CalendarDisplayType.Week; + _monthToggle.IsChecked = SelectedType == CalendarDisplayType.Month; + _yearToggle.IsChecked = SelectedType == CalendarDisplayType.Year; + } + } +} diff --git a/Wino.Calendar/Controls/WinoCalendarView.cs b/Wino.Calendar/Controls/WinoCalendarView.cs new file mode 100644 index 00000000..5d94bc6c --- /dev/null +++ b/Wino.Calendar/Controls/WinoCalendarView.cs @@ -0,0 +1,145 @@ +using System; +using System.Windows.Input; +using CommunityToolkit.Diagnostics; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Media; +using Wino.Core.Domain.Models.Calendar; +using Wino.Helpers; + +namespace Wino.Calendar.Controls +{ + public class WinoCalendarView : Control + { + private const string PART_DayViewItemBorder = nameof(PART_DayViewItemBorder); + private const string PART_CalendarView = nameof(PART_CalendarView); + + public static readonly DependencyProperty HighlightedDateRangeProperty = DependencyProperty.Register(nameof(HighlightedDateRange), typeof(DateRange), typeof(WinoCalendarView), new PropertyMetadata(null, new PropertyChangedCallback(OnHighlightedDateRangeChanged))); + public static readonly DependencyProperty VisibleDateBackgroundProperty = DependencyProperty.Register(nameof(VisibleDateBackground), typeof(Brush), typeof(WinoCalendarView), new PropertyMetadata(null, new PropertyChangedCallback(OnPropertiesChanged))); + public static readonly DependencyProperty TodayBackgroundBrushProperty = DependencyProperty.Register(nameof(TodayBackgroundBrush), typeof(Brush), typeof(WinoCalendarView), new PropertyMetadata(null)); + public static readonly DependencyProperty DateClickedCommandProperty = DependencyProperty.Register(nameof(DateClickedCommand), typeof(ICommand), typeof(WinoCalendarView), new PropertyMetadata(null)); + + /// + /// Gets or sets the command to execute when a date is picked. + /// Unused. + /// + public ICommand DateClickedCommand + { + get { return (ICommand)GetValue(DateClickedCommandProperty); } + set { SetValue(DateClickedCommandProperty, value); } + } + + /// + /// Gets or sets the highlighted range of dates. + /// + public DateRange HighlightedDateRange + { + get { return (DateRange)GetValue(HighlightedDateRangeProperty); } + set { SetValue(HighlightedDateRangeProperty, value); } + } + + public Brush VisibleDateBackground + { + get { return (Brush)GetValue(VisibleDateBackgroundProperty); } + set { SetValue(VisibleDateBackgroundProperty, value); } + } + + public Brush TodayBackgroundBrush + { + get { return (Brush)GetValue(TodayBackgroundBrushProperty); } + set { SetValue(TodayBackgroundBrushProperty, value); } + } + + private CalendarView CalendarView; + + public WinoCalendarView() + { + DefaultStyleKey = typeof(WinoCalendarView); + } + + protected override void OnApplyTemplate() + { + base.OnApplyTemplate(); + + CalendarView = GetTemplateChild(PART_CalendarView) as CalendarView; + + Guard.IsNotNull(CalendarView, nameof(CalendarView)); + + CalendarView.SelectedDatesChanged -= InternalCalendarViewSelectionChanged; + CalendarView.SelectedDatesChanged += InternalCalendarViewSelectionChanged; + + // TODO: Should come from settings. + CalendarView.FirstDayOfWeek = Windows.Globalization.DayOfWeek.Monday; + + // Everytime display mode changes, update the visible date range backgrounds. + // If users go back from year -> month -> day, we need to update the visible date range backgrounds. + + CalendarView.RegisterPropertyChangedCallback(CalendarView.DisplayModeProperty, (s, e) => UpdateVisibleDateRangeBackgrounds()); + } + + private void InternalCalendarViewSelectionChanged(CalendarView sender, CalendarViewSelectedDatesChangedEventArgs args) + { + if (args.AddedDates?.Count > 0) + { + var clickedDate = args.AddedDates[0].Date; + SetInnerDisplayDate(clickedDate); + + var clickArgs = new CalendarViewDayClickedEventArgs(clickedDate); + DateClickedCommand?.Execute(clickArgs); + } + + // Reset selection, we don't show selected dates but react to them. + CalendarView.SelectedDates.Clear(); + } + + private static void OnPropertiesChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is WinoCalendarView control) + { + control.UpdateVisibleDateRangeBackgrounds(); + } + } + + private void SetInnerDisplayDate(DateTime dateTime) => CalendarView?.SetDisplayDate(dateTime); + + // Changing selected dates will trigger the selection changed event. + // It will behave like user clicked the date. + public void GoToDay(DateTime dateTime) => CalendarView.SelectedDates.Add(dateTime); + + private static void OnHighlightedDateRangeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is WinoCalendarView control) + { + control.SetInnerDisplayDate(control.HighlightedDateRange.StartDate); + control.UpdateVisibleDateRangeBackgrounds(); + } + } + + public void UpdateVisibleDateRangeBackgrounds() + { + if (HighlightedDateRange == null || VisibleDateBackground == null || TodayBackgroundBrush == null || CalendarView == null) return; + + var markDateCalendarDayItems = WinoVisualTreeHelper.FindDescendants(CalendarView); + + foreach (var calendarDayItem in markDateCalendarDayItems) + { + var border = WinoVisualTreeHelper.GetChildObject(calendarDayItem, PART_DayViewItemBorder); + + if (border == null) return; + + if (calendarDayItem.Date.Date == DateTime.Today.Date) + { + border.Background = TodayBackgroundBrush; + } + else if (calendarDayItem.Date.Date >= HighlightedDateRange.StartDate.Date && calendarDayItem.Date.Date < HighlightedDateRange.EndDate.Date) + { + border.Background = VisibleDateBackground; + } + else + { + border.Background = null; + } + } + } + } +} diff --git a/Wino.Calendar/Controls/WinoDayTimelineCanvas.cs b/Wino.Calendar/Controls/WinoDayTimelineCanvas.cs new file mode 100644 index 00000000..7fa509c3 --- /dev/null +++ b/Wino.Calendar/Controls/WinoDayTimelineCanvas.cs @@ -0,0 +1,277 @@ +using System; +using System.Diagnostics; +using Microsoft.Graphics.Canvas.Geometry; +using Microsoft.Graphics.Canvas.UI.Xaml; +using Windows.Foundation; +using Windows.UI.Input; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Media; +using Wino.Calendar.Args; +using Wino.Core.Domain.Models.Calendar; + +namespace Wino.Calendar.Controls +{ + public class WinoDayTimelineCanvas : Control, IDisposable + { + public event EventHandler TimelineCellSelected; + public event EventHandler TimelineCellUnselected; + + private const string PART_InternalCanvas = nameof(PART_InternalCanvas); + private CanvasControl Canvas; + + public static readonly DependencyProperty RenderOptionsProperty = DependencyProperty.Register(nameof(RenderOptions), typeof(CalendarRenderOptions), typeof(WinoDayTimelineCanvas), new PropertyMetadata(null, new PropertyChangedCallback(OnRenderingPropertiesChanged))); + public static readonly DependencyProperty SeperatorColorProperty = DependencyProperty.Register(nameof(SeperatorColor), typeof(SolidColorBrush), typeof(WinoDayTimelineCanvas), new PropertyMetadata(null, new PropertyChangedCallback(OnRenderingPropertiesChanged))); + public static readonly DependencyProperty HalfHourSeperatorColorProperty = DependencyProperty.Register(nameof(HalfHourSeperatorColor), typeof(SolidColorBrush), typeof(WinoDayTimelineCanvas), new PropertyMetadata(null, new PropertyChangedCallback(OnRenderingPropertiesChanged))); + public static readonly DependencyProperty SelectedCellBackgroundBrushProperty = DependencyProperty.Register(nameof(SelectedCellBackgroundBrush), typeof(SolidColorBrush), typeof(WinoDayTimelineCanvas), new PropertyMetadata(null, new PropertyChangedCallback(OnRenderingPropertiesChanged))); + public static readonly DependencyProperty WorkingHourCellBackgroundColorProperty = DependencyProperty.Register(nameof(WorkingHourCellBackgroundColor), typeof(SolidColorBrush), typeof(WinoDayTimelineCanvas), new PropertyMetadata(null, new PropertyChangedCallback(OnRenderingPropertiesChanged))); + public static readonly DependencyProperty SelectedDateTimeProperty = DependencyProperty.Register(nameof(SelectedDateTime), typeof(DateTime?), typeof(WinoDayTimelineCanvas), new PropertyMetadata(null, new PropertyChangedCallback(OnSelectedDateTimeChanged))); + public static readonly DependencyProperty PositionerUIElementProperty = DependencyProperty.Register(nameof(PositionerUIElement), typeof(UIElement), typeof(WinoDayTimelineCanvas), new PropertyMetadata(null)); + + public UIElement PositionerUIElement + { + get { return (UIElement)GetValue(PositionerUIElementProperty); } + set { SetValue(PositionerUIElementProperty, value); } + } + + public CalendarRenderOptions RenderOptions + { + get { return (CalendarRenderOptions)GetValue(RenderOptionsProperty); } + set { SetValue(RenderOptionsProperty, value); } + } + + public SolidColorBrush HalfHourSeperatorColor + { + get { return (SolidColorBrush)GetValue(HalfHourSeperatorColorProperty); } + set { SetValue(HalfHourSeperatorColorProperty, value); } + } + + public SolidColorBrush SeperatorColor + { + get { return (SolidColorBrush)GetValue(SeperatorColorProperty); } + set { SetValue(SeperatorColorProperty, value); } + } + + public SolidColorBrush WorkingHourCellBackgroundColor + { + get { return (SolidColorBrush)GetValue(WorkingHourCellBackgroundColorProperty); } + set { SetValue(WorkingHourCellBackgroundColorProperty, value); } + } + + public SolidColorBrush SelectedCellBackgroundBrush + { + get { return (SolidColorBrush)GetValue(SelectedCellBackgroundBrushProperty); } + set { SetValue(SelectedCellBackgroundBrushProperty, value); } + } + + public DateTime? SelectedDateTime + { + get { return (DateTime?)GetValue(SelectedDateTimeProperty); } + set { SetValue(SelectedDateTimeProperty, value); } + } + + protected override void OnApplyTemplate() + { + base.OnApplyTemplate(); + + Canvas = GetTemplateChild(PART_InternalCanvas) as CanvasControl; + + Canvas.Draw += OnCanvasDraw; + Canvas.PointerPressed += OnCanvasPointerPressed; + + ForceDraw(); + } + + private static void OnSelectedDateTimeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is WinoDayTimelineCanvas control) + { + if (e.OldValue != null && e.NewValue == null) + { + control.RaiseCellUnselected(); + } + + control.ForceDraw(); + } + } + + private void RaiseCellUnselected() + { + TimelineCellUnselected?.Invoke(this, new TimelineCellUnselectedArgs()); + } + + private void OnCanvasPointerPressed(object sender, Windows.UI.Xaml.Input.PointerRoutedEventArgs e) + { + if (RenderOptions == null) return; + + var hourHeight = RenderOptions.CalendarSettings.HourHeight; + + // When users click to cell we need to find the day, hour and minutes (first 30 minutes or second 30 minutes) that it represents on the timeline. + + PointerPoint positionerRootPoint = e.GetCurrentPoint(PositionerUIElement); + PointerPoint canvasPointerPoint = e.GetCurrentPoint(Canvas); + + Point touchPoint = canvasPointerPoint.Position; + + var singleDayWidth = (Canvas.ActualWidth / RenderOptions.TotalDayCount); + + int day = (int)(touchPoint.X / singleDayWidth); + int hour = (int)(touchPoint.Y / hourHeight); + + bool isSecondHalf = touchPoint.Y % hourHeight > (hourHeight / 2); + + var diffX = positionerRootPoint.Position.X - touchPoint.X; + var diffY = positionerRootPoint.Position.Y - touchPoint.Y; + + var cellStartRelativePositionX = diffX + (day * singleDayWidth); + var cellEndRelativePositionX = cellStartRelativePositionX + singleDayWidth; + + var cellStartRelativePositionY = diffY + (hour * hourHeight) + (isSecondHalf ? hourHeight / 2 : 0); + var cellEndRelativePositionY = cellStartRelativePositionY + (isSecondHalf ? (hourHeight / 2) : hourHeight); + + var cellSize = new Size(cellEndRelativePositionX - cellStartRelativePositionX, hourHeight / 2); + var positionerPoint = new Point(cellStartRelativePositionX, cellStartRelativePositionY); + + var clickedDateTime = RenderOptions.DateRange.StartDate.AddDays(day).AddHours(hour).AddMinutes(isSecondHalf ? 30 : 0); + + // If there is already a selected date, in order to mimic the popup behavior, we need to dismiss the previous selection first. + // Next click will be a new selection. + + // Raise the events directly here instead of DP to not lose pointer position. + if (clickedDateTime == SelectedDateTime || SelectedDateTime != null) + { + SelectedDateTime = null; + } + else + { + TimelineCellSelected?.Invoke(this, new TimelineCellSelectedArgs(clickedDateTime, touchPoint, positionerPoint, cellSize)); + SelectedDateTime = clickedDateTime; + } + + Debug.WriteLine($"Clicked: {clickedDateTime}"); + } + + public WinoDayTimelineCanvas() + { + DefaultStyleKey = typeof(WinoDayTimelineCanvas); + } + + private static void OnRenderingPropertiesChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is WinoDayTimelineCanvas control) + { + control.ForceDraw(); + } + } + + private void ForceDraw() => Canvas?.Invalidate(); + + private bool CanDrawTimeline() + { + return RenderOptions != null + && Canvas != null + && Canvas.ReadyToDraw + && WorkingHourCellBackgroundColor != null + && SeperatorColor != null + && HalfHourSeperatorColor != null + && SelectedCellBackgroundBrush != null; + } + + private void OnCanvasDraw(CanvasControl sender, CanvasDrawEventArgs args) + { + if (!CanDrawTimeline()) return; + + int hours = 24; + + double canvasWidth = Canvas.ActualWidth; + double canvasHeight = Canvas.ActualHeight; + + if (canvasWidth == 0 || canvasHeight == 0) return; + + // Calculate the width of each rectangle (1 day column) + // Equal distribution of the whole width. + double rectWidth = canvasWidth / RenderOptions.TotalDayCount; + + // Calculate the height of each rectangle (1 hour row) + double rectHeight = RenderOptions.CalendarSettings.HourHeight; + + // Define stroke and fill colors + var strokeColor = SeperatorColor.Color; + float strokeThickness = 0.5f; + + for (int day = 0; day < RenderOptions.TotalDayCount; day++) + { + var currentDay = RenderOptions.DateRange.StartDate.AddDays(day); + + bool isWorkingDay = RenderOptions.CalendarSettings.WorkingDays.Contains(currentDay.DayOfWeek); + + // Loop through each hour (rows) + for (int hour = 0; hour < hours; hour++) + { + var renderTime = TimeSpan.FromHours(hour); + + var representingDateTime = currentDay.AddHours(hour); + + // Calculate the position and size of the rectangle + double x = day * rectWidth; + double y = hour * rectHeight; + + var rectangle = new Rect(x, y, rectWidth, rectHeight); + + // Draw the rectangle border. + // This is the main rectangle. + args.DrawingSession.DrawRectangle(rectangle, strokeColor, strokeThickness); + + // Fill another rectangle with the working hour background color + // This rectangle must be placed with -1 margin to prevent invisible borders of the main rectangle. + if (isWorkingDay && renderTime >= RenderOptions.CalendarSettings.WorkingHourStart && renderTime <= RenderOptions.CalendarSettings.WorkingHourEnd) + { + var backgroundRectangle = new Rect(x + 1, y + 1, rectWidth - 1, rectHeight - 1); + + args.DrawingSession.DrawRectangle(backgroundRectangle, strokeColor, strokeThickness); + args.DrawingSession.FillRectangle(backgroundRectangle, WorkingHourCellBackgroundColor.Color); + } + + // Draw a line in the center of the rectangle for representing half hours. + double lineY = y + rectHeight / 2; + + args.DrawingSession.DrawLine((float)x, (float)lineY, (float)(x + rectWidth), (float)lineY, HalfHourSeperatorColor.Color, strokeThickness, new CanvasStrokeStyle() + { + DashStyle = CanvasDashStyle.Dot + }); + } + + // Draw selected item background color for the date if possible. + if (SelectedDateTime != null) + { + var selectedDateTime = SelectedDateTime.Value; + if (selectedDateTime.Date == currentDay.Date) + { + var selectionRectHeight = rectHeight / 2; + var selectedY = selectedDateTime.Hour * rectHeight + (selectedDateTime.Minute / 60) * rectHeight; + + // Second half of the hour is selected. + if (selectedDateTime.TimeOfDay.Minutes == 30) + { + selectedY += rectHeight / 2; + } + + var selectedRectangle = new Rect(day * rectWidth, selectedY, rectWidth, selectionRectHeight); + args.DrawingSession.FillRectangle(selectedRectangle, SelectedCellBackgroundBrush.Color); + } + } + } + } + + public void Dispose() + { + if (Canvas == null) return; + + Canvas.Draw -= OnCanvasDraw; + Canvas.PointerPressed -= OnCanvasPointerPressed; + Canvas.RemoveFromVisualTree(); + + Canvas = null; + } + } +} diff --git a/Wino.Calendar/MainPage.xaml b/Wino.Calendar/MainPage.xaml index 136e046c..f9187649 100644 --- a/Wino.Calendar/MainPage.xaml +++ b/Wino.Calendar/MainPage.xaml @@ -2,13 +2,11 @@ x:Class="Wino.Calendar.MainPage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" - xmlns:local="using:Wino.Calendar" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:local="using:Wino.Calendar" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - mc:Ignorable="d" - Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> + Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" + mc:Ignorable="d"> - - - + diff --git a/Wino.Calendar/Models/CalendarItemMeasurement.cs b/Wino.Calendar/Models/CalendarItemMeasurement.cs new file mode 100644 index 00000000..b9a30d5b --- /dev/null +++ b/Wino.Calendar/Models/CalendarItemMeasurement.cs @@ -0,0 +1,17 @@ +namespace Wino.Calendar.Models +{ + public struct CalendarItemMeasurement + { + // Where to start? + public double Left { get; set; } + + // Extend until where? + public double Right { get; set; } + + public CalendarItemMeasurement(double left, double right) + { + Left = left; + Right = right; + } + } +} diff --git a/Wino.Calendar/Package.appxmanifest b/Wino.Calendar/Package.appxmanifest index 6e261c84..fac0c7f2 100644 --- a/Wino.Calendar/Package.appxmanifest +++ b/Wino.Calendar/Package.appxmanifest @@ -18,7 +18,7 @@ + Version="1.0.7.0" /> @@ -46,7 +46,7 @@ Square44x44Logo="Assets\Square44x44Logo.png" Description="Wino.Calendar" BackgroundColor="transparent"> - + diff --git a/Wino.Calendar/Properties/Default.rd.xml b/Wino.Calendar/Properties/Default.rd.xml index af00722c..68cdeacf 100644 --- a/Wino.Calendar/Properties/Default.rd.xml +++ b/Wino.Calendar/Properties/Default.rd.xml @@ -21,11 +21,24 @@ An Assembly element with Name="*Application*" applies to all assemblies in the application package. The asterisks are not wildcards. --> - - - - + + + + + + + + + + + + + + + + + - \ No newline at end of file + diff --git a/Wino.Calendar/Services/ApplicationResourceManager.cs b/Wino.Calendar/Services/ApplicationResourceManager.cs new file mode 100644 index 00000000..fdd3776d --- /dev/null +++ b/Wino.Calendar/Services/ApplicationResourceManager.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Wino.Calendar.Services +{ + internal class ApplicationResourceManager + { + } +} diff --git a/Wino.Calendar/Services/DialogService.cs b/Wino.Calendar/Services/DialogService.cs new file mode 100644 index 00000000..0fdd80b3 --- /dev/null +++ b/Wino.Calendar/Services/DialogService.cs @@ -0,0 +1,15 @@ +using Windows.UI.Xaml; +using Wino.Core.Domain.Interfaces; +using Wino.Core.UWP.Services; + +namespace Wino.Calendar.Services +{ + public class DialogService : DialogServiceBase, ICalendarDialogService + { + public DialogService(IThemeService themeService, + IConfigurationService configurationService, + IApplicationResourceManager applicationResourceManager) : base(themeService, configurationService, applicationResourceManager) + { + } + } +} diff --git a/Wino.Calendar/Services/NavigationService.cs b/Wino.Calendar/Services/NavigationService.cs new file mode 100644 index 00000000..269eaf54 --- /dev/null +++ b/Wino.Calendar/Services/NavigationService.cs @@ -0,0 +1,51 @@ +using System; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Wino.Calendar.Views; +using Wino.Calendar.Views.Account; +using Wino.Calendar.Views.Settings; +using Wino.Core.Domain.Enums; +using Wino.Core.Domain.Interfaces; +using Wino.Core.Domain.Models.Navigation; +using Wino.Core.UWP.Services; +using Wino.Views; + +namespace Wino.Calendar.Services +{ + public class NavigationService : NavigationServiceBase, INavigationService + { + public Type GetPageType(WinoPage winoPage) + { + switch (winoPage) + { + case WinoPage.CalendarPage: + return typeof(CalendarPage); + case WinoPage.SettingsPage: + return typeof(SettingsPage); + case WinoPage.CalendarSettingsPage: + return typeof(CalendarSettingsPage); + case WinoPage.AccountManagementPage: + return typeof(AccountManagementPage); + default: + throw new Exception("Page is not implemented yet."); + } + } + + public bool Navigate(WinoPage page, object parameter = null, NavigationReferenceFrame frame = NavigationReferenceFrame.ShellFrame, NavigationTransitionType transition = NavigationTransitionType.None) + { + // All navigations are performed on shell frame for calendar. + + if (Window.Current.Content is Frame appFrame && appFrame.Content is AppShell shellPage) + { + var shellFrame = shellPage.GetShellFrame(); + + var pageType = GetPageType(page); + + shellFrame.Navigate(pageType, parameter); + return true; + } + + return false; + } + } +} diff --git a/Wino.Calendar/Services/SettingsBuilderService.cs b/Wino.Calendar/Services/SettingsBuilderService.cs new file mode 100644 index 00000000..2177d8dc --- /dev/null +++ b/Wino.Calendar/Services/SettingsBuilderService.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; +using Wino.Core.Domain; +using Wino.Core.Domain.Enums; +using Wino.Core.Domain.Interfaces; +using Wino.Core.Domain.Models.Settings; + +namespace Wino.Calendar.Services +{ + public class SettingsBuilderService : ISettingsBuilderService + { + public List GetSettingItems() + => new List() + { + new SettingOption(Translator.SettingsManageAccountSettings_Title, Translator.SettingsManageAccountSettings_Description, WinoPage.AccountManagementPage,"F1 M 3.75 5 L 3.75 4.902344 C 3.75 4.225262 3.885091 3.588867 4.155273 2.993164 C 4.425456 2.397461 4.790039 1.878256 5.249023 1.435547 C 5.708008 0.99284 6.238606 0.642904 6.84082 0.385742 C 7.443034 0.128582 8.079427 0 8.75 0 C 9.420572 0 10.056966 0.128582 10.65918 0.385742 C 11.261393 0.642904 11.791992 0.99284 12.250977 1.435547 C 12.709961 1.878256 13.074544 2.397461 13.344727 2.993164 C 13.614908 3.588867 13.75 4.225262 13.75 4.902344 C 13.75 5.397137 13.689778 5.87077 13.569336 6.323242 C 13.448893 6.775717 13.258463 7.213542 12.998047 7.636719 C 12.229817 7.799479 11.510416 8.079428 10.839844 8.476562 C 10.169271 8.873698 9.583333 9.378256 9.082031 9.990234 C 9.042969 9.996745 9.005533 10 8.969727 10 C 8.933919 10 8.899739 10 8.867188 10 L 8.652344 10 C 7.97526 10 7.338867 9.864909 6.743164 9.594727 C 6.147461 9.324545 5.628255 8.959961 5.185547 8.500977 C 4.742838 8.041993 4.392903 7.511395 4.135742 6.90918 C 3.878581 6.306967 3.75 5.670573 3.75 5 Z M 12.5 5 L 12.5 4.921875 C 12.5 4.414062 12.399088 3.937176 12.197266 3.491211 C 11.995442 3.045248 11.722005 2.65625 11.376953 2.324219 C 11.0319 1.992188 10.633138 1.730145 10.180664 1.538086 C 9.728189 1.346029 9.251302 1.25 8.75 1.25 C 8.229166 1.25 7.740885 1.347656 7.285156 1.542969 C 6.829427 1.738281 6.432292 2.005209 6.09375 2.34375 C 5.755208 2.682293 5.488281 3.079428 5.292969 3.535156 C 5.097656 3.990887 5 4.479167 5 5 C 5 5.520834 5.097656 6.009115 5.292969 6.464844 C 5.488281 6.920573 5.755208 7.317709 6.09375 7.65625 C 6.432292 7.994792 6.829427 8.261719 7.285156 8.457031 C 7.740885 8.652344 8.229166 8.75 8.75 8.75 C 9.270833 8.75 9.759114 8.652344 10.214844 8.457031 C 10.670572 8.261719 11.067708 7.994792 11.40625 7.65625 C 11.744791 7.317709 12.011719 6.920573 12.207031 6.464844 C 12.402344 6.009115 12.5 5.520834 12.5 5 Z M 12.148438 19.365234 C 11.777344 19.208984 11.41276 19.010416 11.054688 18.769531 C 11.113281 18.561197 11.170247 18.357748 11.225586 18.15918 C 11.280924 17.960611 11.308594 17.753906 11.308594 17.539062 C 11.308594 17.252604 11.259766 16.977539 11.162109 16.713867 C 11.064453 16.450195 10.930989 16.210938 10.761719 15.996094 C 10.592447 15.78125 10.390625 15.597331 10.15625 15.444336 C 9.921875 15.291342 9.664713 15.182292 9.384766 15.117188 L 8.779297 14.980469 C 8.759766 14.778646 8.75 14.576823 8.75 14.375 C 8.75 14.179688 8.759766 13.977865 8.779297 13.769531 L 9.384766 13.632812 C 9.664713 13.567709 9.921875 13.458659 10.15625 13.305664 C 10.390625 13.15267 10.592447 12.96875 10.761719 12.753906 C 10.930989 12.539062 11.064453 12.299805 11.162109 12.036133 C 11.259766 11.772461 11.308594 11.497396 11.308594 11.210938 C 11.308594 10.996094 11.280924 10.789389 11.225586 10.59082 C 11.170247 10.392253 11.113281 10.188803 11.054688 9.980469 C 11.41276 9.739584 11.777344 9.541016 12.148438 9.384766 C 12.324219 9.560547 12.488606 9.720053 12.641602 9.863281 C 12.794596 10.006511 12.957356 10.131836 13.129883 10.239258 C 13.302408 10.34668 13.487955 10.429688 13.686523 10.488281 C 13.885091 10.546875 14.114583 10.576172 14.375 10.576172 C 14.635416 10.576172 14.866535 10.546875 15.068359 10.488281 C 15.270182 10.429688 15.455729 10.34668 15.625 10.239258 C 15.79427 10.131836 15.955402 10.006511 16.108398 9.863281 C 16.261393 9.720053 16.425781 9.560547 16.601562 9.384766 C 16.972656 9.541016 17.337238 9.739584 17.695312 9.980469 C 17.636719 10.188803 17.579752 10.392253 17.524414 10.59082 C 17.469074 10.789389 17.441406 10.996094 17.441406 11.210938 C 17.441406 11.497396 17.488605 11.772461 17.583008 12.036133 C 17.677408 12.299805 17.810871 12.539062 17.983398 12.753906 C 18.155924 12.96875 18.359375 13.15267 18.59375 13.305664 C 18.828125 13.458659 19.085285 13.567709 19.365234 13.632812 L 19.970703 13.769531 C 19.990234 13.977865 20 14.179688 20 14.375 C 20 14.576823 19.990234 14.778646 19.970703 14.980469 L 19.365234 15.117188 C 19.085285 15.182292 18.828125 15.291342 18.59375 15.444336 C 18.359375 15.597331 18.155924 15.78125 17.983398 15.996094 C 17.810871 16.210938 17.677408 16.450195 17.583008 16.713867 C 17.488605 16.977539 17.441406 17.252604 17.441406 17.539062 C 17.441406 17.753906 17.469074 17.960611 17.524414 18.15918 C 17.579752 18.357748 17.636719 18.561197 17.695312 18.769531 C 17.337238 19.010416 16.972656 19.208984 16.601562 19.365234 C 16.425781 19.189453 16.261393 19.029947 16.108398 18.886719 C 15.955402 18.74349 15.79427 18.618164 15.625 18.510742 C 15.455729 18.40332 15.270182 18.320312 15.068359 18.261719 C 14.866535 18.203125 14.635416 18.173828 14.375 18.173828 C 14.108072 18.173828 13.875324 18.203125 13.676758 18.261719 C 13.478189 18.320312 13.294271 18.40332 13.125 18.510742 C 12.955729 18.618164 12.794596 18.74349 12.641602 18.886719 C 12.488606 19.029947 12.324219 19.189453 12.148438 19.365234 Z M 0 13.75 C 0 13.404948 0.065104 13.081055 0.195312 12.77832 C 0.325521 12.475586 0.504557 12.210287 0.732422 11.982422 C 0.960286 11.754558 1.225586 11.575521 1.52832 11.445312 C 1.831055 11.315104 2.154948 11.25 2.5 11.25 L 8.251953 11.25 C 8.043619 11.660156 7.880859 12.076823 7.763672 12.5 L 2.5 12.5 C 2.324219 12.5 2.159831 12.532553 2.006836 12.597656 C 1.853841 12.662761 1.722005 12.750651 1.611328 12.861328 C 1.500651 12.972006 1.41276 13.103842 1.347656 13.256836 C 1.282552 13.409831 1.25 13.574219 1.25 13.75 C 1.25 14.420573 1.360677 15.008139 1.582031 15.512695 C 1.803385 16.017252 2.102865 16.455078 2.480469 16.826172 C 2.858073 17.197266 3.297526 17.50651 3.798828 17.753906 C 4.30013 18.001303 4.827474 18.198242 5.380859 18.344727 C 5.934244 18.491211 6.50065 18.595377 7.080078 18.657227 C 7.659505 18.719076 8.216146 18.75 8.75 18.75 L 9.072266 18.75 C 9.449869 19.199219 9.866536 19.5931 10.322266 19.931641 C 10.061849 19.964193 9.801432 19.983725 9.541016 19.990234 C 9.280599 19.996744 9.016927 20 8.75 20 C 8.190104 20 7.618814 19.973959 7.036133 19.921875 C 6.45345 19.869791 5.878906 19.775391 5.3125 19.638672 C 4.746094 19.501953 4.197591 19.319662 3.666992 19.091797 C 3.136393 18.863932 2.646484 18.574219 2.197266 18.222656 C 1.474609 17.66276 0.927734 17.005209 0.556641 16.25 C 0.185547 15.494792 0 14.661458 0 13.75 Z M 15.625 14.375 C 15.625 14.205729 15.592447 14.044597 15.527344 13.891602 C 15.462239 13.738607 15.372721 13.605144 15.258789 13.491211 C 15.144855 13.377279 15.011393 13.287761 14.858398 13.222656 C 14.705403 13.157553 14.544271 13.125 14.375 13.125 C 14.199219 13.125 14.036458 13.157553 13.886719 13.222656 C 13.736979 13.287761 13.605143 13.377279 13.491211 13.491211 C 13.377278 13.605144 13.28776 13.736979 13.222656 13.886719 C 13.157551 14.036459 13.124999 14.199219 13.125 14.375 C 13.124999 14.550781 13.157551 14.71517 13.222656 14.868164 C 13.28776 15.021159 13.37565 15.152995 13.486328 15.263672 C 13.597004 15.37435 13.72884 15.46224 13.881836 15.527344 C 14.03483 15.592448 14.199219 15.625 14.375 15.625 C 14.550781 15.625 14.713541 15.592448 14.863281 15.527344 C 15.01302 15.46224 15.144855 15.372722 15.258789 15.258789 C 15.372721 15.144857 15.462239 15.013021 15.527344 14.863281 C 15.592447 14.713542 15.625 14.550781 15.625 14.375 Z "), + new SettingOption(Translator.SettingsAppPreferences_Title, Translator.SettingsAppPreferences_Description, WinoPage.AppPreferencesPage,"F1 M 15.078125 1.25 C 15.566406 1.25 16.033527 1.349285 16.479492 1.547852 C 16.925455 1.74642 17.31608 2.013348 17.651367 2.348633 C 17.986652 2.68392 18.25358 3.074545 18.452148 3.520508 C 18.650715 3.966473 18.75 4.433594 18.75 4.921875 L 18.75 15.078125 C 18.75 15.566406 18.650715 16.033529 18.452148 16.479492 C 18.25358 16.925455 17.986652 17.31608 17.651367 17.651367 C 17.31608 17.986654 16.925455 18.25358 16.479492 18.452148 C 16.033527 18.650717 15.566406 18.75 15.078125 18.75 L 4.921875 18.75 C 4.433594 18.75 3.966471 18.650717 3.520508 18.452148 C 3.074544 18.25358 2.683919 17.986654 2.348633 17.651367 C 2.013346 17.31608 1.746419 16.925455 1.547852 16.479492 C 1.349284 16.033529 1.25 15.566406 1.25 15.078125 L 1.25 4.921875 C 1.25 4.433594 1.349284 3.966473 1.547852 3.520508 C 1.746419 3.074545 2.013346 2.68392 2.348633 2.348633 C 2.683919 2.013348 3.074544 1.74642 3.520508 1.547852 C 3.966471 1.349285 4.433594 1.25 4.921875 1.25 Z M 4.951172 2.5 C 4.625651 2.5 4.314778 2.566732 4.018555 2.700195 C 3.722331 2.83366 3.461914 3.012695 3.237305 3.237305 C 3.012695 3.461914 2.833659 3.722332 2.700195 4.018555 C 2.566732 4.314779 2.5 4.625651 2.5 4.951172 L 2.5 5 L 17.5 5 L 17.5 4.951172 C 17.5 4.625651 17.433268 4.314779 17.299805 4.018555 C 17.16634 3.722332 16.987305 3.461914 16.762695 3.237305 C 16.538086 3.012695 16.277668 2.83366 15.981445 2.700195 C 15.685221 2.566732 15.374349 2.5 15.048828 2.5 Z M 15.048828 17.5 C 15.374349 17.5 15.685221 17.433268 15.981445 17.299805 C 16.277668 17.166342 16.538086 16.987305 16.762695 16.762695 C 16.987305 16.538086 17.16634 16.27767 17.299805 15.981445 C 17.433268 15.685222 17.5 15.37435 17.5 15.048828 L 17.5 6.25 L 2.5 6.25 L 2.5 15.048828 C 2.5 15.37435 2.566732 15.685222 2.700195 15.981445 C 2.833659 16.27767 3.012695 16.538086 3.237305 16.762695 C 3.461914 16.987305 3.722331 17.166342 4.018555 17.299805 C 4.314778 17.433268 4.625651 17.5 4.951172 17.5 Z M 12.724609 8.935547 C 12.724609 9.195964 12.762044 9.446615 12.836914 9.6875 C 12.911783 9.928386 13.020832 10.151367 13.164062 10.356445 C 13.307291 10.561523 13.476562 10.742188 13.671875 10.898438 C 13.867188 11.054688 14.088541 11.178386 14.335938 11.269531 C 14.348957 11.367188 14.358723 11.466472 14.365234 11.567383 C 14.371744 11.668295 14.375 11.770834 14.375 11.875 C 14.375 11.979167 14.371744 12.081706 14.365234 12.182617 C 14.358723 12.283529 14.348957 12.382812 14.335938 12.480469 C 14.088541 12.571615 13.867188 12.695312 13.671875 12.851562 C 13.476562 13.007812 13.307291 13.188477 13.164062 13.393555 C 13.020832 13.598633 12.911783 13.821615 12.836914 14.0625 C 12.762044 14.303386 12.724609 14.554037 12.724609 14.814453 C 12.724609 14.886068 12.727864 14.960938 12.734375 15.039062 C 12.740885 15.117188 12.75065 15.192058 12.763672 15.263672 C 12.470703 15.511068 12.145182 15.712891 11.787109 15.869141 C 11.546224 15.621745 11.274414 15.43457 10.97168 15.307617 C 10.668945 15.180664 10.345052 15.117188 10 15.117188 C 9.654947 15.117188 9.331055 15.180664 9.02832 15.307617 C 8.725586 15.43457 8.453775 15.621745 8.212891 15.869141 C 7.854817 15.712891 7.529296 15.511068 7.236328 15.263672 C 7.249349 15.192058 7.259114 15.117188 7.265625 15.039062 C 7.272135 14.960938 7.275391 14.886068 7.275391 14.814453 C 7.275391 14.554037 7.237956 14.303386 7.163086 14.0625 C 7.088216 13.821615 6.979167 13.598633 6.835938 13.393555 C 6.692708 13.188477 6.52181 13.007812 6.323242 12.851562 C 6.124674 12.695312 5.904948 12.571615 5.664062 12.480469 C 5.651042 12.382812 5.641276 12.283529 5.634766 12.182617 C 5.628255 12.081706 5.625 11.979167 5.625 11.875 C 5.625 11.770834 5.628255 11.668295 5.634766 11.567383 C 5.641276 11.466472 5.651042 11.367188 5.664062 11.269531 C 5.904948 11.178386 6.124674 11.054688 6.323242 10.898438 C 6.52181 10.742188 6.692708 10.561523 6.835938 10.356445 C 6.979167 10.151367 7.088216 9.928386 7.163086 9.6875 C 7.237956 9.446615 7.275391 9.195964 7.275391 8.935547 C 7.275391 8.863933 7.272135 8.789062 7.265625 8.710938 C 7.259114 8.632812 7.249349 8.557943 7.236328 8.486328 C 7.529296 8.238933 7.854817 8.037109 8.212891 7.880859 C 8.440755 8.121745 8.712564 8.307292 9.02832 8.4375 C 9.344075 8.567709 9.667969 8.632812 10 8.632812 C 10.332031 8.632812 10.655924 8.567709 10.97168 8.4375 C 11.287435 8.307292 11.559244 8.121745 11.787109 7.880859 C 12.145182 8.037109 12.470703 8.238933 12.763672 8.486328 C 12.75065 8.557943 12.740885 8.632812 12.734375 8.710938 C 12.727864 8.789062 12.724609 8.863933 12.724609 8.935547 Z M 10.9375 11.875 C 10.9375 11.614584 10.846354 11.393229 10.664062 11.210938 C 10.481771 11.028646 10.260416 10.9375 10 10.9375 C 9.739583 10.9375 9.518229 11.028646 9.335938 11.210938 C 9.153646 11.393229 9.0625 11.614584 9.0625 11.875 C 9.0625 12.135417 9.153646 12.356771 9.335938 12.539062 C 9.518229 12.721354 9.739583 12.8125 10 12.8125 C 10.260416 12.8125 10.481771 12.721354 10.664062 12.539062 C 10.846354 12.356771 10.9375 12.135417 10.9375 11.875 Z "), + new SettingOption(Translator.SettingsCalendarSettings_Title, Translator.SettingsCalendarSettings_Description, WinoPage.CalendarSettingsPage,"F1 M 1.25 3.75 C 1.25 3.404949 1.315104 3.081055 1.445312 2.77832 C 1.575521 2.475586 1.754557 2.210287 1.982422 1.982422 C 2.210286 1.754559 2.475586 1.575521 2.77832 1.445312 C 3.081055 1.315105 3.404948 1.25 3.75 1.25 L 16.25 1.25 C 16.595051 1.25 16.918945 1.315105 17.22168 1.445312 C 17.524414 1.575521 17.789713 1.754559 18.017578 1.982422 C 18.245441 2.210287 18.424479 2.475586 18.554688 2.77832 C 18.684895 3.081055 18.75 3.404949 18.75 3.75 L 18.75 5 L 1.25 5 Z M 3.75 18.75 C 3.404948 18.75 3.081055 18.684896 2.77832 18.554688 C 2.475586 18.424479 2.210286 18.245443 1.982422 18.017578 C 1.754557 17.789713 1.575521 17.524414 1.445312 17.22168 C 1.315104 16.918945 1.25 16.595053 1.25 16.25 L 1.25 6.25 L 18.75 6.25 L 18.75 16.25 C 18.75 16.595053 18.684895 16.918945 18.554688 17.22168 C 18.424479 17.524414 18.245441 17.789713 18.017578 18.017578 C 17.789713 18.245443 17.524414 18.424479 17.22168 18.554688 C 16.918945 18.684896 16.595051 18.75 16.25 18.75 Z M 7.65625 9.990234 C 7.65625 9.794922 7.618815 9.612631 7.543945 9.443359 C 7.469075 9.274089 7.368164 9.125977 7.241211 8.999023 C 7.114258 8.87207 6.966146 8.772787 6.796875 8.701172 C 6.627604 8.629558 6.445312 8.59375 6.25 8.59375 C 6.054688 8.59375 5.870768 8.631186 5.698242 8.706055 C 5.525716 8.780925 5.375977 8.881836 5.249023 9.008789 C 5.12207 9.135742 5.022786 9.283854 4.951172 9.453125 C 4.879557 9.622396 4.84375 9.804688 4.84375 10 C 4.84375 10.195312 4.879557 10.377604 4.951172 10.546875 C 5.022786 10.716146 5.12207 10.864258 5.249023 10.991211 C 5.375977 11.118164 5.524088 11.219076 5.693359 11.293945 C 5.86263 11.368815 6.044922 11.40625 6.240234 11.40625 C 6.442057 11.40625 6.629231 11.370443 6.801758 11.298828 C 6.974283 11.227214 7.124023 11.12793 7.250977 11.000977 C 7.377929 10.874023 7.477213 10.724284 7.548828 10.551758 C 7.620442 10.379232 7.65625 10.192058 7.65625 9.990234 Z M 11.40625 9.990234 C 11.40625 9.794922 11.368814 9.612631 11.293945 9.443359 C 11.219075 9.274089 11.118164 9.125977 10.991211 8.999023 C 10.864258 8.87207 10.716146 8.772787 10.546875 8.701172 C 10.377604 8.629558 10.195312 8.59375 10 8.59375 C 9.804688 8.59375 9.620768 8.631186 9.448242 8.706055 C 9.275716 8.780925 9.125977 8.881836 8.999023 9.008789 C 8.87207 9.135742 8.772786 9.283854 8.701172 9.453125 C 8.629557 9.622396 8.59375 9.804688 8.59375 10 C 8.59375 10.195312 8.629557 10.377604 8.701172 10.546875 C 8.772786 10.716146 8.87207 10.864258 8.999023 10.991211 C 9.125977 11.118164 9.274088 11.219076 9.443359 11.293945 C 9.61263 11.368815 9.794922 11.40625 9.990234 11.40625 C 10.192057 11.40625 10.379231 11.370443 10.551758 11.298828 C 10.724283 11.227214 10.874023 11.12793 11.000977 11.000977 C 11.12793 10.874023 11.227213 10.724284 11.298828 10.551758 C 11.370442 10.379232 11.40625 10.192058 11.40625 9.990234 Z M 15.15625 9.990234 C 15.15625 9.794922 15.118814 9.612631 15.043945 9.443359 C 14.969075 9.274089 14.868164 9.125977 14.741211 8.999023 C 14.614258 8.87207 14.466146 8.772787 14.296875 8.701172 C 14.127604 8.629558 13.945312 8.59375 13.75 8.59375 C 13.554688 8.59375 13.370768 8.631186 13.198242 8.706055 C 13.025716 8.780925 12.875977 8.881836 12.749023 9.008789 C 12.62207 9.135742 12.522786 9.283854 12.451172 9.453125 C 12.379557 9.622396 12.34375 9.804688 12.34375 10 C 12.34375 10.195312 12.379557 10.377604 12.451172 10.546875 C 12.522786 10.716146 12.62207 10.864258 12.749023 10.991211 C 12.875977 11.118164 13.024088 11.219076 13.193359 11.293945 C 13.362629 11.368815 13.544921 11.40625 13.740234 11.40625 C 13.942057 11.40625 14.129231 11.370443 14.301758 11.298828 C 14.474283 11.227214 14.624022 11.12793 14.750977 11.000977 C 14.877929 10.874023 14.977213 10.724284 15.048828 10.551758 C 15.120442 10.379232 15.15625 10.192058 15.15625 9.990234 Z M 7.65625 13.740234 C 7.65625 13.544922 7.618815 13.362631 7.543945 13.193359 C 7.469075 13.024089 7.368164 12.875977 7.241211 12.749023 C 7.114258 12.62207 6.966146 12.522787 6.796875 12.451172 C 6.627604 12.379558 6.445312 12.34375 6.25 12.34375 C 6.054688 12.34375 5.870768 12.381186 5.698242 12.456055 C 5.525716 12.530925 5.375977 12.631836 5.249023 12.758789 C 5.12207 12.885742 5.022786 13.033854 4.951172 13.203125 C 4.879557 13.372396 4.84375 13.554688 4.84375 13.75 C 4.84375 13.945312 4.879557 14.127604 4.951172 14.296875 C 5.022786 14.466146 5.12207 14.614258 5.249023 14.741211 C 5.375977 14.868164 5.524088 14.969076 5.693359 15.043945 C 5.86263 15.118815 6.044922 15.15625 6.240234 15.15625 C 6.442057 15.15625 6.629231 15.120443 6.801758 15.048828 C 6.974283 14.977214 7.124023 14.87793 7.250977 14.750977 C 7.377929 14.624023 7.477213 14.474284 7.548828 14.301758 C 7.620442 14.129232 7.65625 13.942058 7.65625 13.740234 Z M 11.40625 13.740234 C 11.40625 13.544922 11.368814 13.362631 11.293945 13.193359 C 11.219075 13.024089 11.118164 12.875977 10.991211 12.749023 C 10.864258 12.62207 10.716146 12.522787 10.546875 12.451172 C 10.377604 12.379558 10.195312 12.34375 10 12.34375 C 9.804688 12.34375 9.620768 12.381186 9.448242 12.456055 C 9.275716 12.530925 9.125977 12.631836 8.999023 12.758789 C 8.87207 12.885742 8.772786 13.033854 8.701172 13.203125 C 8.629557 13.372396 8.59375 13.554688 8.59375 13.75 C 8.59375 13.945312 8.629557 14.127604 8.701172 14.296875 C 8.772786 14.466146 8.87207 14.614258 8.999023 14.741211 C 9.125977 14.868164 9.274088 14.969076 9.443359 15.043945 C 9.61263 15.118815 9.794922 15.15625 9.990234 15.15625 C 10.192057 15.15625 10.379231 15.120443 10.551758 15.048828 C 10.724283 14.977214 10.874023 14.87793 11.000977 14.750977 C 11.12793 14.624023 11.227213 14.474284 11.298828 14.301758 C 11.370442 14.129232 11.40625 13.942058 11.40625 13.740234 Z "), + new SettingOption(Translator.SettingsAbout_Title, Translator.SettingsAbout_Description, WinoPage.AboutPage,"F1 M 9.375 18.75 C 8.509114 18.75 7.677409 18.639322 6.879883 18.417969 C 6.082356 18.196615 5.335286 17.882486 4.638672 17.475586 C 3.942057 17.068686 3.308919 16.580404 2.739258 16.010742 C 2.169596 15.441081 1.681315 14.807943 1.274414 14.111328 C 0.867513 13.414714 0.553385 12.667644 0.332031 11.870117 C 0.110677 11.072592 0 10.240886 0 9.375 C 0 8.509115 0.110677 7.677409 0.332031 6.879883 C 0.553385 6.082357 0.867513 5.335287 1.274414 4.638672 C 1.681315 3.942059 2.169596 3.30892 2.739258 2.739258 C 3.308919 2.169598 3.942057 1.681316 4.638672 1.274414 C 5.335286 0.867514 6.082356 0.553387 6.879883 0.332031 C 7.677409 0.110678 8.509114 0 9.375 0 C 10.240885 0 11.072591 0.110678 11.870117 0.332031 C 12.667643 0.553387 13.414713 0.867514 14.111328 1.274414 C 14.807942 1.681316 15.44108 2.169598 16.010742 2.739258 C 16.580402 3.30892 17.068684 3.942059 17.475586 4.638672 C 17.882486 5.335287 18.196613 6.082357 18.417969 6.879883 C 18.639322 7.677409 18.75 8.509115 18.75 9.375 C 18.75 10.240886 18.639322 11.072592 18.417969 11.870117 C 18.196613 12.667644 17.882486 13.414714 17.475586 14.111328 C 17.068684 14.807943 16.580402 15.441081 16.010742 16.010742 C 15.44108 16.580404 14.807942 17.068686 14.111328 17.475586 C 13.414713 17.882486 12.667643 18.196615 11.870117 18.417969 C 11.072591 18.639322 10.240885 18.75 9.375 18.75 Z M 9.375 1.25 C 8.626302 1.25 7.906901 1.347656 7.216797 1.542969 C 6.526692 1.738281 5.880533 2.011719 5.27832 2.363281 C 4.676106 2.714844 4.127604 3.138021 3.632812 3.632812 C 3.138021 4.127604 2.714844 4.676107 2.363281 5.27832 C 2.011719 5.880534 1.738281 6.52832 1.542969 7.22168 C 1.347656 7.915039 1.25 8.632812 1.25 9.375 C 1.25 10.117188 1.347656 10.834961 1.542969 11.52832 C 1.738281 12.22168 2.011719 12.869467 2.363281 13.47168 C 2.714844 14.073894 3.138021 14.622396 3.632812 15.117188 C 4.127604 15.611979 4.676106 16.035156 5.27832 16.386719 C 5.880533 16.738281 6.526692 17.011719 7.216797 17.207031 C 7.906901 17.402344 8.626302 17.5 9.375 17.5 C 10.117188 17.5 10.834961 17.402344 11.52832 17.207031 C 12.221679 17.011719 12.869465 16.738281 13.47168 16.386719 C 14.073893 16.035156 14.622396 15.611979 15.117188 15.117188 C 15.611979 14.622396 16.035156 14.073894 16.386719 13.47168 C 16.738281 12.869467 17.011719 12.223308 17.207031 11.533203 C 17.402344 10.8431 17.5 10.123698 17.5 9.375 C 17.5 8.632812 17.402344 7.915039 17.207031 7.22168 C 17.011719 6.52832 16.738281 5.880534 16.386719 5.27832 C 16.035156 4.676107 15.611979 4.127604 15.117188 3.632812 C 14.622396 3.138021 14.073893 2.714844 13.47168 2.363281 C 12.869465 2.011719 12.221679 1.738281 11.52832 1.542969 C 10.834961 1.347656 10.117188 1.25 9.375 1.25 Z M 8.75 7.5 L 10 7.5 L 10 13.75 L 8.75 13.75 Z M 8.75 5 L 10 5 L 10 6.25 L 8.75 6.25 Z "), + }; + } +} diff --git a/Wino.Calendar/Styles/CalendarDayItemsControl.xaml b/Wino.Calendar/Styles/CalendarDayItemsControl.xaml new file mode 100644 index 00000000..4385be31 --- /dev/null +++ b/Wino.Calendar/Styles/CalendarDayItemsControl.xaml @@ -0,0 +1,21 @@ + + + + + diff --git a/Wino.Calendar/Styles/CalendarItemControlResources.xaml b/Wino.Calendar/Styles/CalendarItemControlResources.xaml new file mode 100644 index 00000000..eaa70e09 --- /dev/null +++ b/Wino.Calendar/Styles/CalendarItemControlResources.xaml @@ -0,0 +1,29 @@ + + + + + + diff --git a/Wino.Calendar/Styles/CalendarItemControlResources.xaml.cs b/Wino.Calendar/Styles/CalendarItemControlResources.xaml.cs new file mode 100644 index 00000000..9d0cb0d1 --- /dev/null +++ b/Wino.Calendar/Styles/CalendarItemControlResources.xaml.cs @@ -0,0 +1,12 @@ +using Windows.UI.Xaml; + +namespace Wino.Calendar.Styles +{ + public sealed partial class CalendarItemControlResources : ResourceDictionary + { + public CalendarItemControlResources() + { + InitializeComponent(); + } + } +} diff --git a/Wino.Calendar/Styles/CalendarRenderStyles.xaml b/Wino.Calendar/Styles/CalendarRenderStyles.xaml new file mode 100644 index 00000000..000bb58c --- /dev/null +++ b/Wino.Calendar/Styles/CalendarRenderStyles.xaml @@ -0,0 +1,26 @@ + + + + + + + #D9D9D9 + #ffffff + #f9f9f9 + #ebebeb + Yellow + #dfe4ea + + + + #000000 + #262626 + #1a1a1a + #121212 + + #3d3d3d + #2f3542 + + + + diff --git a/Wino.Calendar/Styles/DayColumnControl.xaml b/Wino.Calendar/Styles/DayColumnControl.xaml new file mode 100644 index 00000000..a6651ad6 --- /dev/null +++ b/Wino.Calendar/Styles/DayColumnControl.xaml @@ -0,0 +1,93 @@ + + + + + + + + diff --git a/Wino.Calendar/Styles/DayHeaderControl.xaml b/Wino.Calendar/Styles/DayHeaderControl.xaml new file mode 100644 index 00000000..396a0cf0 --- /dev/null +++ b/Wino.Calendar/Styles/DayHeaderControl.xaml @@ -0,0 +1,23 @@ + + + + + + diff --git a/Wino.Calendar/Styles/WinoCalendarResources.xaml b/Wino.Calendar/Styles/WinoCalendarResources.xaml new file mode 100644 index 00000000..07c09302 --- /dev/null +++ b/Wino.Calendar/Styles/WinoCalendarResources.xaml @@ -0,0 +1,128 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1000 + 1000 + + diff --git a/Wino.Calendar/Styles/WinoCalendarResources.xaml.cs b/Wino.Calendar/Styles/WinoCalendarResources.xaml.cs new file mode 100644 index 00000000..7a24cde1 --- /dev/null +++ b/Wino.Calendar/Styles/WinoCalendarResources.xaml.cs @@ -0,0 +1,12 @@ +using Windows.UI.Xaml; + +namespace Wino.Calendar.Styles +{ + public sealed partial class WinoCalendarResources : ResourceDictionary + { + public WinoCalendarResources() + { + this.InitializeComponent(); + } + } +} diff --git a/Wino.Calendar/Styles/WinoCalendarTypeSelectorControl.xaml b/Wino.Calendar/Styles/WinoCalendarTypeSelectorControl.xaml new file mode 100644 index 00000000..bf2362e2 --- /dev/null +++ b/Wino.Calendar/Styles/WinoCalendarTypeSelectorControl.xaml @@ -0,0 +1,64 @@ + + + + + diff --git a/Wino.Calendar/Styles/WinoCalendarView.xaml b/Wino.Calendar/Styles/WinoCalendarView.xaml new file mode 100644 index 00000000..393ae107 --- /dev/null +++ b/Wino.Calendar/Styles/WinoCalendarView.xaml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + diff --git a/Wino.Calendar/Styles/WinoDayTimelineCanvas.xaml b/Wino.Calendar/Styles/WinoDayTimelineCanvas.xaml new file mode 100644 index 00000000..96257f2f --- /dev/null +++ b/Wino.Calendar/Styles/WinoDayTimelineCanvas.xaml @@ -0,0 +1,19 @@ + + + + + diff --git a/Wino.Calendar/Views/Abstract/AccountManagementPageAbstract.cs b/Wino.Calendar/Views/Abstract/AccountManagementPageAbstract.cs new file mode 100644 index 00000000..50509dd2 --- /dev/null +++ b/Wino.Calendar/Views/Abstract/AccountManagementPageAbstract.cs @@ -0,0 +1,7 @@ +using Wino.Calendar.ViewModels; +using Wino.Core.UWP; + +namespace Wino.Calendar.Views.Abstract +{ + public class AccountManagementPageAbstract : BasePage { } +} diff --git a/Wino.Calendar/Views/Abstract/AppShellAbstract.cs b/Wino.Calendar/Views/Abstract/AppShellAbstract.cs new file mode 100644 index 00000000..39eb8c5c --- /dev/null +++ b/Wino.Calendar/Views/Abstract/AppShellAbstract.cs @@ -0,0 +1,7 @@ +using Wino.Calendar.ViewModels; +using Wino.Core.UWP; + +namespace Wino.Calendar.Views.Abstract +{ + public abstract class AppShellAbstract : BasePage { } +} diff --git a/Wino.Calendar/Views/Abstract/CalendarPageAbstract.cs b/Wino.Calendar/Views/Abstract/CalendarPageAbstract.cs new file mode 100644 index 00000000..9c40d5d6 --- /dev/null +++ b/Wino.Calendar/Views/Abstract/CalendarPageAbstract.cs @@ -0,0 +1,7 @@ +using Wino.Calendar.ViewModels; +using Wino.Core.UWP; + +namespace Wino.Calendar.Views.Abstract +{ + public abstract class CalendarPageAbstract : BasePage { } +} diff --git a/Wino.Calendar/Views/Abstract/CalendarSettingsPageAbstract.cs b/Wino.Calendar/Views/Abstract/CalendarSettingsPageAbstract.cs new file mode 100644 index 00000000..00c511a6 --- /dev/null +++ b/Wino.Calendar/Views/Abstract/CalendarSettingsPageAbstract.cs @@ -0,0 +1,7 @@ +using Wino.Calendar.ViewModels; +using Wino.Core.UWP; + +namespace Wino.Calendar.Views.Abstract +{ + public abstract class CalendarSettingsPageAbstract : BasePage { } +} diff --git a/Wino.Calendar/Views/Account/AccountManagementPage.xaml b/Wino.Calendar/Views/Account/AccountManagementPage.xaml new file mode 100644 index 00000000..7748bf0a --- /dev/null +++ b/Wino.Calendar/Views/Account/AccountManagementPage.xaml @@ -0,0 +1,12 @@ + + + + diff --git a/Wino.Calendar/Views/Account/AccountManagementPage.xaml.cs b/Wino.Calendar/Views/Account/AccountManagementPage.xaml.cs new file mode 100644 index 00000000..813889b1 --- /dev/null +++ b/Wino.Calendar/Views/Account/AccountManagementPage.xaml.cs @@ -0,0 +1,12 @@ +using Wino.Calendar.Views.Abstract; + +namespace Wino.Calendar.Views.Account +{ + public sealed partial class AccountManagementPage : AccountManagementPageAbstract + { + public AccountManagementPage() + { + this.InitializeComponent(); + } + } +} diff --git a/Wino.Calendar/Views/AppShell.xaml b/Wino.Calendar/Views/AppShell.xaml new file mode 100644 index 00000000..3f1ec9c0 --- /dev/null +++ b/Wino.Calendar/Views/AppShell.xaml @@ -0,0 +1,251 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Wino.Calendar/Views/AppShell.xaml.cs b/Wino.Calendar/Views/AppShell.xaml.cs new file mode 100644 index 00000000..ecf59c9c --- /dev/null +++ b/Wino.Calendar/Views/AppShell.xaml.cs @@ -0,0 +1,57 @@ +using CommunityToolkit.Mvvm.Messaging; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Wino.Calendar.Views.Abstract; +using Wino.Messaging.Client.Calendar; + +namespace Wino.Calendar.Views +{ + public sealed partial class AppShell : AppShellAbstract, + IRecipient + { + private const string STATE_HorizontalCalendar = "HorizontalCalendar"; + private const string STATE_VerticalCalendar = "VerticalCalendar"; + + public Frame GetShellFrame() => ShellFrame; + public AppShell() + { + InitializeComponent(); + + Window.Current.SetTitleBar(DragArea); + + ViewModel.DisplayTypeChanged += CalendarDisplayTypeChanged; + } + + private void CalendarDisplayTypeChanged(object sender, Core.Domain.Enums.CalendarDisplayType e) + { + // Go to different states based on the display type. + if (ViewModel.IsVerticalCalendar) + { + VisualStateManager.GoToState(this, STATE_VerticalCalendar, false); + } + else + { + VisualStateManager.GoToState(this, STATE_HorizontalCalendar, false); + } + } + + private void ShellFrameContentNavigated(object sender, Windows.UI.Xaml.Navigation.NavigationEventArgs e) + { + + } + + private void BackButtonClicked(Core.UWP.Controls.WinoAppTitleBar sender, Windows.UI.Xaml.RoutedEventArgs args) + { + + } + + public void Receive(GoToCalendarDayMessage message) + { + CalendarView.GoToDay(message.DateTime); + } + + private void PreviousDateClicked(object sender, RoutedEventArgs e) => WeakReferenceMessenger.Default.Send(new GoPreviousDateRequestedMessage()); + + private void NextDateClicked(object sender, RoutedEventArgs e) => WeakReferenceMessenger.Default.Send(new GoNextDateRequestedMessage()); + } +} diff --git a/Wino.Calendar/Views/CalendarPage.xaml b/Wino.Calendar/Views/CalendarPage.xaml new file mode 100644 index 00000000..03a2b72d --- /dev/null +++ b/Wino.Calendar/Views/CalendarPage.xaml @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Wino.Mail/Controls/Advanced/WinoAppTitleBar.xaml.cs b/Wino.Core.UWP/Controls/WinoAppTitleBar.xaml.cs similarity index 84% rename from Wino.Mail/Controls/Advanced/WinoAppTitleBar.xaml.cs rename to Wino.Core.UWP/Controls/WinoAppTitleBar.xaml.cs index 4ecbf99a..7514fdd8 100644 --- a/Wino.Mail/Controls/Advanced/WinoAppTitleBar.xaml.cs +++ b/Wino.Core.UWP/Controls/WinoAppTitleBar.xaml.cs @@ -4,7 +4,7 @@ using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; using Wino.Core.Domain.Enums; -namespace Wino.Controls.Advanced +namespace Wino.Core.UWP.Controls { public sealed partial class WinoAppTitleBar : UserControl { @@ -22,6 +22,8 @@ namespace Wino.Controls.Advanced public static readonly DependencyProperty ReadingPaneLengthProperty = DependencyProperty.Register(nameof(ReadingPaneLength), typeof(double), typeof(WinoAppTitleBar), new PropertyMetadata(420d, OnDrawingPropertyChanged)); public static readonly DependencyProperty ConnectionStatusProperty = DependencyProperty.Register(nameof(ConnectionStatus), typeof(WinoServerConnectionStatus), typeof(WinoAppTitleBar), new PropertyMetadata(WinoServerConnectionStatus.None, new PropertyChangedCallback(OnConnectionStatusChanged))); public static readonly DependencyProperty ReconnectCommandProperty = DependencyProperty.Register(nameof(ReconnectCommand), typeof(ICommand), typeof(WinoAppTitleBar), new PropertyMetadata(null)); + public static readonly DependencyProperty ShrinkShellContentOnExpansionProperty = DependencyProperty.Register(nameof(ShrinkShellContentOnExpansion), typeof(bool), typeof(WinoAppTitleBar), new PropertyMetadata(true)); + public static readonly DependencyProperty IsDragAreaProperty = DependencyProperty.Register(nameof(IsDragArea), typeof(bool), typeof(WinoAppTitleBar), new PropertyMetadata(false, new PropertyChangedCallback(OnIsDragAreaChanged))); public ICommand ReconnectCommand { @@ -41,6 +43,18 @@ namespace Wino.Controls.Advanced set { SetValue(CoreWindowTextProperty, value); } } + + + public bool IsDragArea + { + get { return (bool)GetValue(IsDragAreaProperty); } + set { SetValue(IsDragAreaProperty, value); } + } + + + + + public double SystemReserved { get { return (double)GetValue(SystemReservedProperty); } @@ -59,6 +73,12 @@ namespace Wino.Controls.Advanced set { SetValue(NavigationViewDisplayModeProperty, value); } } + public bool ShrinkShellContentOnExpansion + { + get { return (bool)GetValue(ShrinkShellContentOnExpansionProperty); } + set { SetValue(ShrinkShellContentOnExpansionProperty, value); } + } + public bool IsNavigationPaneOpen { get { return (bool)GetValue(IsNavigationPaneOpenProperty); } @@ -122,6 +142,22 @@ namespace Wino.Controls.Advanced } } + private static void OnIsDragAreaChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args) + { + if (obj is WinoAppTitleBar bar) + { + bar.SetDragArea(); + } + } + + private void SetDragArea() + { + if (IsDragArea) + { + Window.Current.SetTitleBar(this); + } + } + private void UpdateConnectionStatus() { @@ -147,7 +183,7 @@ namespace Wino.Controls.Advanced { // Icons are visible. - if (!IsReaderNarrowed) + if (!IsReaderNarrowed && ShrinkShellContentOnExpansion) { ShellContentContainer.HorizontalAlignment = HorizontalAlignment.Left; ShellContentContainer.Width = ReadingPaneLength; @@ -162,7 +198,7 @@ namespace Wino.Controls.Advanced // LMargin = OpenPaneLength - LeftMenuStackPanel ShellContentContainer.Margin = new Thickness(OpenPaneLength - LeftMenuStackPanel.ActualSize.X, 0, 0, 0); - if (!IsReaderNarrowed) + if (!IsReaderNarrowed && ShrinkShellContentOnExpansion) { ShellContentContainer.HorizontalAlignment = HorizontalAlignment.Left; ShellContentContainer.Width = ReadingPaneLength; @@ -170,16 +206,15 @@ namespace Wino.Controls.Advanced } else { - EmptySpaceWidth.Width = new GridLength(ReadingPaneLength, GridUnitType.Pixel); + // EmptySpaceWidth.Width = new GridLength(ReadingPaneLength, GridUnitType.Pixel); + EmptySpaceWidth.Width = new GridLength(ReadingPaneLength, GridUnitType.Star); } } } public WinoAppTitleBar() { - this.InitializeComponent(); - - Window.Current.SetTitleBar(dragbar); + InitializeComponent(); } private void BackClicked(object sender, RoutedEventArgs e) diff --git a/Wino.Mail/Controls/WinoFontIcon.cs b/Wino.Core.UWP/Controls/WinoFontIcon.cs similarity index 98% rename from Wino.Mail/Controls/WinoFontIcon.cs rename to Wino.Core.UWP/Controls/WinoFontIcon.cs index 91dd7736..12ce11b0 100644 --- a/Wino.Mail/Controls/WinoFontIcon.cs +++ b/Wino.Core.UWP/Controls/WinoFontIcon.cs @@ -1,7 +1,7 @@ using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; -namespace Wino.Controls +namespace Wino.Core.UWP.Controls { public enum WinoIconGlyph { diff --git a/Wino.Mail/Controls/WinoFontIconSource.cs b/Wino.Core.UWP/Controls/WinoFontIconSource.cs similarity index 97% rename from Wino.Mail/Controls/WinoFontIconSource.cs rename to Wino.Core.UWP/Controls/WinoFontIconSource.cs index 150bf2d8..e8bcaf7a 100644 --- a/Wino.Mail/Controls/WinoFontIconSource.cs +++ b/Wino.Core.UWP/Controls/WinoFontIconSource.cs @@ -1,4 +1,5 @@ using Windows.UI.Xaml; +using Wino.Core.UWP.Controls; namespace Wino.Controls { diff --git a/Wino.Mail/Controls/WinoInfoBar.cs b/Wino.Core.UWP/Controls/WinoInfoBar.cs similarity index 99% rename from Wino.Mail/Controls/WinoInfoBar.cs rename to Wino.Core.UWP/Controls/WinoInfoBar.cs index 6f2e288e..70f18869 100644 --- a/Wino.Mail/Controls/WinoInfoBar.cs +++ b/Wino.Core.UWP/Controls/WinoInfoBar.cs @@ -5,7 +5,7 @@ using Microsoft.UI.Xaml.Controls; using Windows.UI.Xaml; using Wino.Core.Domain.Enums; -namespace Wino.Controls +namespace Wino.Core.UWP.Controls { public class WinoInfoBar : InfoBar { diff --git a/Wino.Mail/Controls/WinoNavigationViewItem.cs b/Wino.Core.UWP/Controls/WinoNavigationViewItem.cs similarity index 95% rename from Wino.Mail/Controls/WinoNavigationViewItem.cs rename to Wino.Core.UWP/Controls/WinoNavigationViewItem.cs index e28070b8..b831567c 100644 --- a/Wino.Mail/Controls/WinoNavigationViewItem.cs +++ b/Wino.Core.UWP/Controls/WinoNavigationViewItem.cs @@ -3,7 +3,7 @@ using Microsoft.UI.Xaml.Controls; using Windows.UI.Xaml; using Windows.UI.Xaml.Hosting; -namespace Wino.Controls +namespace Wino.Core.UWP.Controls { public class WinoNavigationViewItem : NavigationViewItem { @@ -37,7 +37,7 @@ namespace Wino.Controls private void ScaleAnimation(Vector3 vector) { - if (this.Content is UIElement content) + if (Content is UIElement content) { var visual = ElementCompositionPreview.GetElementVisual(content); visual.Scale = vector; diff --git a/Wino.Mail/Converters/GridLengthConverter.cs b/Wino.Core.UWP/Converters/GridLengthConverter.cs similarity index 100% rename from Wino.Mail/Converters/GridLengthConverter.cs rename to Wino.Core.UWP/Converters/GridLengthConverter.cs diff --git a/Wino.Mail/Converters/ReverseBooleanConverter.cs b/Wino.Core.UWP/Converters/ReverseBooleanConverter.cs similarity index 100% rename from Wino.Mail/Converters/ReverseBooleanConverter.cs rename to Wino.Core.UWP/Converters/ReverseBooleanConverter.cs diff --git a/Wino.Mail/Converters/ReverseBooleanToVisibilityConverter.cs b/Wino.Core.UWP/Converters/ReverseBooleanToVisibilityConverter.cs similarity index 100% rename from Wino.Mail/Converters/ReverseBooleanToVisibilityConverter.cs rename to Wino.Core.UWP/Converters/ReverseBooleanToVisibilityConverter.cs diff --git a/Wino.Core.UWP/CoreGeneric.xaml b/Wino.Core.UWP/CoreGeneric.xaml new file mode 100644 index 00000000..167005ca --- /dev/null +++ b/Wino.Core.UWP/CoreGeneric.xaml @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + 0 + + 19 + 19 + 24,24,24,24 + + + + + + + + + + + + + + + + + + + + diff --git a/Wino.Core.UWP/CoreGeneric.xaml.cs b/Wino.Core.UWP/CoreGeneric.xaml.cs new file mode 100644 index 00000000..116a0255 --- /dev/null +++ b/Wino.Core.UWP/CoreGeneric.xaml.cs @@ -0,0 +1,9 @@ +using Windows.UI.Xaml; + +namespace Wino.Core.UWP +{ + public partial class CoreGeneric : ResourceDictionary + { + public CoreGeneric() => InitializeComponent(); + } +} diff --git a/Wino.Core.UWP/CoreUWPContainerSetup.cs b/Wino.Core.UWP/CoreUWPContainerSetup.cs index dd825f77..ab2ef6fc 100644 --- a/Wino.Core.UWP/CoreUWPContainerSetup.cs +++ b/Wino.Core.UWP/CoreUWPContainerSetup.cs @@ -1,7 +1,9 @@ using Microsoft.Extensions.DependencyInjection; using Windows.ApplicationModel.AppService; +using Windows.UI.Xaml; using Wino.Core.Domain.Interfaces; using Wino.Core.UWP.Services; +using Wino.Core.ViewModels; using Wino.Services; namespace Wino.Core.UWP @@ -14,11 +16,15 @@ namespace Wino.Core.UWP services.AddSingleton(serverConnectionManager); services.AddSingleton>(serverConnectionManager); + services.AddSingleton, ApplicationResourceManager>(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); services.AddTransient(); @@ -29,6 +35,17 @@ namespace Wino.Core.UWP services.AddTransient(); services.AddTransient(); services.AddSingleton(); + + } + + public static void RegisterCoreViewModels(this IServiceCollection services) + { + services.AddTransient(typeof(SettingsDialogViewModel)); + services.AddTransient(typeof(PersonalizationPageViewModel)); + services.AddTransient(typeof(SettingOptionsPageViewModel)); + services.AddTransient(typeof(AboutPageViewModel)); + services.AddTransient(typeof(SettingsPageViewModel)); + services.AddTransient(typeof(NewAccountManagementPageViewModel)); } } } diff --git a/Wino.Mail/Dialogs/AccountCreationDialog.xaml b/Wino.Core.UWP/Dialogs/AccountCreationDialog.xaml similarity index 100% rename from Wino.Mail/Dialogs/AccountCreationDialog.xaml rename to Wino.Core.UWP/Dialogs/AccountCreationDialog.xaml diff --git a/Wino.Mail/Dialogs/AccountCreationDialog.xaml.cs b/Wino.Core.UWP/Dialogs/AccountCreationDialog.xaml.cs similarity index 91% rename from Wino.Mail/Dialogs/AccountCreationDialog.xaml.cs rename to Wino.Core.UWP/Dialogs/AccountCreationDialog.xaml.cs index 47bf6e51..f8847e9e 100644 --- a/Wino.Mail/Dialogs/AccountCreationDialog.xaml.cs +++ b/Wino.Core.UWP/Dialogs/AccountCreationDialog.xaml.cs @@ -3,6 +3,7 @@ using System.Threading.Tasks; using CommunityToolkit.Mvvm.Messaging; using Microsoft.Extensions.DependencyInjection; using Wino.Core.Domain.Interfaces; +using Wino.Core.UWP; using Wino.Messaging.UI; namespace Wino.Dialogs @@ -35,7 +36,7 @@ namespace Wino.Dialogs { if (string.IsNullOrEmpty(copyClipboardURL)) return; - var clipboardService = App.Current.Services.GetService(); + var clipboardService = WinoApplication.Current.Services.GetService(); await clipboardService.CopyClipboardAsync(copyClipboardURL); } } diff --git a/Wino.Mail/Dialogs/AccountEditDialog.xaml b/Wino.Core.UWP/Dialogs/AccountEditDialog.xaml similarity index 78% rename from Wino.Mail/Dialogs/AccountEditDialog.xaml rename to Wino.Core.UWP/Dialogs/AccountEditDialog.xaml index 34cba8f8..08963c0f 100644 --- a/Wino.Mail/Dialogs/AccountEditDialog.xaml +++ b/Wino.Core.UWP/Dialogs/AccountEditDialog.xaml @@ -2,18 +2,18 @@ x:Class="Wino.Dialogs.AccountEditDialog" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" - xmlns:domain="using:Wino.Core.Domain" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:domain="using:Wino.Core.Domain" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - mc:Ignorable="d" Title="{x:Bind domain:Translator.AccountEditDialog_Title}" - Style="{StaticResource WinoDialogStyle}" DefaultButton="Primary" + PrimaryButtonClick="SaveClicked" PrimaryButtonText="{x:Bind domain:Translator.Buttons_Save}" SecondaryButtonText="{x:Bind domain:Translator.Buttons_Discard}" - PrimaryButtonClick="SaveClicked"> + Style="{StaticResource WinoDialogStyle}" + mc:Ignorable="d"> - + diff --git a/Wino.Mail/Dialogs/AccountEditDialog.xaml.cs b/Wino.Core.UWP/Dialogs/AccountEditDialog.xaml.cs similarity index 92% rename from Wino.Mail/Dialogs/AccountEditDialog.xaml.cs rename to Wino.Core.UWP/Dialogs/AccountEditDialog.xaml.cs index e158b8b3..dae0a599 100644 --- a/Wino.Mail/Dialogs/AccountEditDialog.xaml.cs +++ b/Wino.Core.UWP/Dialogs/AccountEditDialog.xaml.cs @@ -1,5 +1,5 @@ using Windows.UI.Xaml.Controls; -using Wino.Core.Domain.Entities; +using Wino.Core.Domain.Entities.Shared; namespace Wino.Dialogs { diff --git a/Wino.Mail/Dialogs/AccountPickerDialog.xaml b/Wino.Core.UWP/Dialogs/AccountPickerDialog.xaml similarity index 88% rename from Wino.Mail/Dialogs/AccountPickerDialog.xaml rename to Wino.Core.UWP/Dialogs/AccountPickerDialog.xaml index f06da6d2..e7bcd9ce 100644 --- a/Wino.Mail/Dialogs/AccountPickerDialog.xaml +++ b/Wino.Core.UWP/Dialogs/AccountPickerDialog.xaml @@ -3,17 +3,17 @@ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" - xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - mc:Ignorable="d" - Style="{StaticResource WinoDialogStyle}" - PrimaryButtonText="{x:Bind domain:Translator.Buttons_Cancel}" xmlns:domain="using:Wino.Core.Domain" - Title="{x:Bind domain:Translator.AccountPickerDialog_Title}"> + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + Title="{x:Bind domain:Translator.AccountPickerDialog_Title}" + PrimaryButtonText="{x:Bind domain:Translator.Buttons_Cancel}" + Style="{StaticResource WinoDialogStyle}" + mc:Ignorable="d"> + IsItemClickEnabled="True" + ItemClick="AccountClicked" + ItemsSource="{x:Bind AvailableAccounts}" + SelectionMode="None" /> diff --git a/Wino.Mail/Dialogs/AccountPickerDialog.xaml.cs b/Wino.Core.UWP/Dialogs/AccountPickerDialog.xaml.cs similarity index 94% rename from Wino.Mail/Dialogs/AccountPickerDialog.xaml.cs rename to Wino.Core.UWP/Dialogs/AccountPickerDialog.xaml.cs index 9b4c46c4..a8d50246 100644 --- a/Wino.Mail/Dialogs/AccountPickerDialog.xaml.cs +++ b/Wino.Core.UWP/Dialogs/AccountPickerDialog.xaml.cs @@ -1,6 +1,6 @@ using System.Collections.Generic; using Windows.UI.Xaml.Controls; -using Wino.Core.Domain.Entities; +using Wino.Core.Domain.Entities.Shared; namespace Wino.Dialogs { diff --git a/Wino.Mail/Dialogs/BaseAccountCreationDialog.cs b/Wino.Core.UWP/Dialogs/BaseAccountCreationDialog.cs similarity index 100% rename from Wino.Mail/Dialogs/BaseAccountCreationDialog.cs rename to Wino.Core.UWP/Dialogs/BaseAccountCreationDialog.cs diff --git a/Wino.Mail/Dialogs/CustomMessageDialogInformationContainer.cs b/Wino.Core.UWP/Dialogs/CustomMessageDialogInformationContainer.cs similarity index 100% rename from Wino.Mail/Dialogs/CustomMessageDialogInformationContainer.cs rename to Wino.Core.UWP/Dialogs/CustomMessageDialogInformationContainer.cs diff --git a/Wino.Mail/Dialogs/CustomThemeBuilderDialog.xaml b/Wino.Core.UWP/Dialogs/CustomThemeBuilderDialog.xaml similarity index 94% rename from Wino.Mail/Dialogs/CustomThemeBuilderDialog.xaml rename to Wino.Core.UWP/Dialogs/CustomThemeBuilderDialog.xaml index 89e46480..8010ea9c 100644 --- a/Wino.Mail/Dialogs/CustomThemeBuilderDialog.xaml +++ b/Wino.Core.UWP/Dialogs/CustomThemeBuilderDialog.xaml @@ -2,21 +2,21 @@ x:Class="Wino.Dialogs.CustomThemeBuilderDialog" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" - xmlns:local="using:Wino.Dialogs" - xmlns:muxc="using:Microsoft.UI.Xaml.Controls" - Style="{StaticResource WinoDialogStyle}" xmlns:controls="using:CommunityToolkit.WinUI.Controls" - FullSizeDesired="False" - xmlns:domain="using:Wino.Core.Domain" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:domain="using:Wino.Core.Domain" + xmlns:local="using:Wino.Dialogs" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:muxc="using:Microsoft.UI.Xaml.Controls" + Title="{x:Bind domain:Translator.CustomThemeBuilder_Title}" + DefaultButton="Primary" + FullSizeDesired="False" IsPrimaryButtonEnabled="False" - mc:Ignorable="d" + PrimaryButtonClick="ApplyClicked" PrimaryButtonText="{x:Bind domain:Translator.Buttons_ApplyTheme}" SecondaryButtonText="{x:Bind domain:Translator.Buttons_Cancel}" - DefaultButton="Primary" - PrimaryButtonClick="ApplyClicked" - Title="{x:Bind domain:Translator.CustomThemeBuilder_Title}"> + Style="{StaticResource WinoDialogStyle}" + mc:Ignorable="d"> 600 @@ -28,7 +28,7 @@ - + @@ -38,18 +38,18 @@ + Description="{x:Bind domain:Translator.CustomThemeBuilder_WallpaperDescription}" + Header="{x:Bind domain:Translator.CustomThemeBuilder_WallpaperTitle}"> - diff --git a/Wino.Mail/Dialogs/CustomThemeBuilderDialog.xaml.cs b/Wino.Core.UWP/Dialogs/CustomThemeBuilderDialog.xaml.cs similarity index 89% rename from Wino.Mail/Dialogs/CustomThemeBuilderDialog.xaml.cs rename to Wino.Core.UWP/Dialogs/CustomThemeBuilderDialog.xaml.cs index 633fce37..a4154918 100644 --- a/Wino.Mail/Dialogs/CustomThemeBuilderDialog.xaml.cs +++ b/Wino.Core.UWP/Dialogs/CustomThemeBuilderDialog.xaml.cs @@ -4,7 +4,7 @@ using Microsoft.Extensions.DependencyInjection; using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Media; using Wino.Core.Domain.Interfaces; -using Wino.Services; +using Wino.Core.UWP; namespace Wino.Dialogs { @@ -19,7 +19,7 @@ namespace Wino.Dialogs { InitializeComponent(); - _themeService = App.Current.Services.GetService(); + _themeService = WinoApplication.Current.Services.GetService(); } private async void ApplyClicked(ContentDialog sender, ContentDialogButtonClickEventArgs args) @@ -45,7 +45,7 @@ namespace Wino.Dialogs private async void BrowseWallpaperClicked(object sender, Windows.UI.Xaml.RoutedEventArgs e) { - var dialogService = App.Current.Services.GetService(); + var dialogService = WinoApplication.Current.Services.GetService(); var pickedFileData = await dialogService.PickWindowsFileContentAsync(".jpg", ".png"); diff --git a/Wino.Mail/Dialogs/TextInputDialog.xaml b/Wino.Core.UWP/Dialogs/TextInputDialog.xaml similarity index 100% rename from Wino.Mail/Dialogs/TextInputDialog.xaml rename to Wino.Core.UWP/Dialogs/TextInputDialog.xaml index 6ca651f1..89a7cd8b 100644 --- a/Wino.Mail/Dialogs/TextInputDialog.xaml +++ b/Wino.Core.UWP/Dialogs/TextInputDialog.xaml @@ -3,15 +3,15 @@ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" - xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - Style="{StaticResource WinoDialogStyle}" - DefaultButton="Primary" - HorizontalContentAlignment="Stretch" xmlns:domain="using:Wino.Core.Domain" - PrimaryButtonClick="UpdateOrCreateClicked" - SecondaryButtonText="{x:Bind domain:Translator.Buttons_Cancel}" - SecondaryButtonClick="CancelClicked" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch" + DefaultButton="Primary" + PrimaryButtonClick="UpdateOrCreateClicked" + SecondaryButtonClick="CancelClicked" + SecondaryButtonText="{x:Bind domain:Translator.Buttons_Cancel}" + Style="{StaticResource WinoDialogStyle}" mc:Ignorable="d"> diff --git a/Wino.Mail/Dialogs/TextInputDialog.xaml.cs b/Wino.Core.UWP/Dialogs/TextInputDialog.xaml.cs similarity index 100% rename from Wino.Mail/Dialogs/TextInputDialog.xaml.cs rename to Wino.Core.UWP/Dialogs/TextInputDialog.xaml.cs diff --git a/Wino.Mail/Extensions/AnimationExtensions.cs b/Wino.Core.UWP/Extensions/AnimationExtensions.cs similarity index 100% rename from Wino.Mail/Extensions/AnimationExtensions.cs rename to Wino.Core.UWP/Extensions/AnimationExtensions.cs diff --git a/Wino.Mail/Extensions/CompositionEnums.cs b/Wino.Core.UWP/Extensions/CompositionEnums.cs similarity index 100% rename from Wino.Mail/Extensions/CompositionEnums.cs rename to Wino.Core.UWP/Extensions/CompositionEnums.cs diff --git a/Wino.Mail/Extensions/CompositionExtensions.Implicit.cs b/Wino.Core.UWP/Extensions/CompositionExtensions.Implicit.cs similarity index 100% rename from Wino.Mail/Extensions/CompositionExtensions.Implicit.cs rename to Wino.Core.UWP/Extensions/CompositionExtensions.Implicit.cs diff --git a/Wino.Mail/Extensions/CompositionExtensions.Size.cs b/Wino.Core.UWP/Extensions/CompositionExtensions.Size.cs similarity index 100% rename from Wino.Mail/Extensions/CompositionExtensions.Size.cs rename to Wino.Core.UWP/Extensions/CompositionExtensions.Size.cs diff --git a/Wino.Mail/Extensions/UIExtensions.cs b/Wino.Core.UWP/Extensions/UIExtensions.cs similarity index 100% rename from Wino.Mail/Extensions/UIExtensions.cs rename to Wino.Core.UWP/Extensions/UIExtensions.cs diff --git a/Wino.Mail/Extensions/UtilExtensions.cs b/Wino.Core.UWP/Extensions/UtilExtensions.cs similarity index 97% rename from Wino.Mail/Extensions/UtilExtensions.cs rename to Wino.Core.UWP/Extensions/UtilExtensions.cs index 50367afc..6a1185c0 100644 --- a/Wino.Mail/Extensions/UtilExtensions.cs +++ b/Wino.Core.UWP/Extensions/UtilExtensions.cs @@ -83,7 +83,7 @@ namespace Wino.Extensions } public static void ScrollToElement(this ScrollViewer scrollViewer, FrameworkElement element, - bool isVerticalScrolling = true, bool smoothScrolling = true, float? zoomFactor = null, bool bringToTopOrLeft = true) + bool isVerticalScrolling = true, bool smoothScrolling = true, float? zoomFactor = null, bool bringToTopOrLeft = true, bool addMargin = true) { if (!bringToTopOrLeft && element.IsFullyVisibile(scrollViewer)) return; @@ -94,7 +94,7 @@ namespace Wino.Extensions if (isVerticalScrolling) { // Accomodate for additional header. - scrollViewer.ChangeView(null, Math.Max(0, position.Y - 48), zoomFactor, !smoothScrolling); + scrollViewer.ChangeView(null, Math.Max(0, position.Y - (addMargin ? 48 : 0)), zoomFactor, !smoothScrolling); } else { diff --git a/Wino.Mail/Helpers/WinoVisualTreeHelper.cs b/Wino.Core.UWP/Helpers/WinoVisualTreeHelper.cs similarity index 55% rename from Wino.Mail/Helpers/WinoVisualTreeHelper.cs rename to Wino.Core.UWP/Helpers/WinoVisualTreeHelper.cs index ded3482e..10c15bc7 100644 --- a/Wino.Mail/Helpers/WinoVisualTreeHelper.cs +++ b/Wino.Core.UWP/Helpers/WinoVisualTreeHelper.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using System.Collections.Generic; using Windows.UI.Xaml; using Windows.UI.Xaml.Media; @@ -34,5 +30,25 @@ namespace Wino.Helpers } return null; } + + public static IEnumerable FindDescendants(this DependencyObject depObj) where T : DependencyObject + { + if (depObj != null) + { + for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++) + { + var child = VisualTreeHelper.GetChild(depObj, i); + if (child != null && child is T) + { + yield return (T)child; + } + + foreach (T childOfChild in FindDescendants(child)) + { + yield return childOfChild; + } + } + } + } } } diff --git a/Wino.Mail/Helpers/XamlHelpers.cs b/Wino.Core.UWP/Helpers/XamlHelpers.cs similarity index 99% rename from Wino.Mail/Helpers/XamlHelpers.cs rename to Wino.Core.UWP/Helpers/XamlHelpers.cs index 9a2cb1ed..5b882f99 100644 --- a/Wino.Mail/Helpers/XamlHelpers.cs +++ b/Wino.Core.UWP/Helpers/XamlHelpers.cs @@ -8,10 +8,10 @@ using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Markup; using Windows.UI.Xaml.Media; -using Wino.Controls; using Wino.Core.Domain; using Wino.Core.Domain.Enums; using Wino.Core.Domain.Models.MailItem; +using Wino.Core.UWP.Controls; namespace Wino.Helpers { diff --git a/Wino.Mail/Selectors/AppThemePreviewTemplateSelector.cs b/Wino.Core.UWP/Selectors/AppThemePreviewTemplateSelector.cs similarity index 95% rename from Wino.Mail/Selectors/AppThemePreviewTemplateSelector.cs rename to Wino.Core.UWP/Selectors/AppThemePreviewTemplateSelector.cs index f3afe31e..d1091e85 100644 --- a/Wino.Mail/Selectors/AppThemePreviewTemplateSelector.cs +++ b/Wino.Core.UWP/Selectors/AppThemePreviewTemplateSelector.cs @@ -2,7 +2,7 @@ using Windows.UI.Xaml.Controls; using Wino.Core.UWP.Models.Personalization; -namespace Wino.Selectors +namespace Wino.Core.UWP.Selectors { public class AppThemePreviewTemplateSelector : DataTemplateSelector { diff --git a/Wino.Mail/Selectors/CustomWinoMessageDialogIconSelector.cs b/Wino.Core.UWP/Selectors/CustomWinoMessageDialogIconSelector.cs similarity index 97% rename from Wino.Mail/Selectors/CustomWinoMessageDialogIconSelector.cs rename to Wino.Core.UWP/Selectors/CustomWinoMessageDialogIconSelector.cs index 2dad8413..6f339354 100644 --- a/Wino.Mail/Selectors/CustomWinoMessageDialogIconSelector.cs +++ b/Wino.Core.UWP/Selectors/CustomWinoMessageDialogIconSelector.cs @@ -3,7 +3,7 @@ using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; using Wino.Core.Domain.Enums; -namespace Wino.Selectors +namespace Wino.Core.UWP.Selectors { public class CustomWinoMessageDialogIconSelector : DataTemplateSelector { diff --git a/Wino.Mail/Selectors/FileAttachmentTypeSelector.cs b/Wino.Core.UWP/Selectors/FileAttachmentTypeSelector.cs similarity index 91% rename from Wino.Mail/Selectors/FileAttachmentTypeSelector.cs rename to Wino.Core.UWP/Selectors/FileAttachmentTypeSelector.cs index e117fcd2..e83507a5 100644 --- a/Wino.Mail/Selectors/FileAttachmentTypeSelector.cs +++ b/Wino.Core.UWP/Selectors/FileAttachmentTypeSelector.cs @@ -1,13 +1,8 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Windows.UI.Xaml; +using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; using Wino.Core.Domain.Enums; -namespace Wino.Selectors +namespace Wino.Core.UWP.Selectors { public class FileAttachmentTypeSelector : DataTemplateSelector { diff --git a/Wino.Mail/Selectors/NavigationMenuTemplateSelector.cs b/Wino.Core.UWP/Selectors/NavigationMenuTemplateSelector.cs similarity index 95% rename from Wino.Mail/Selectors/NavigationMenuTemplateSelector.cs rename to Wino.Core.UWP/Selectors/NavigationMenuTemplateSelector.cs index ce929a30..13d2a7c8 100644 --- a/Wino.Mail/Selectors/NavigationMenuTemplateSelector.cs +++ b/Wino.Core.UWP/Selectors/NavigationMenuTemplateSelector.cs @@ -2,7 +2,7 @@ using Windows.UI.Xaml.Controls; using Wino.Core.MenuItems; -namespace Wino.Selectors +namespace Wino.Core.UWP.Selectors { public class NavigationMenuTemplateSelector : DataTemplateSelector { @@ -47,7 +47,7 @@ namespace Wino.Selectors else if (item is FolderMenuItem) return FolderMenuTemplate; else if (item is FixAccountIssuesMenuItem fixAccountIssuesMenuItem) - return fixAccountIssuesMenuItem.Account.AttentionReason == Core.Domain.Enums.AccountAttentionReason.MissingSystemFolderConfiguration + return fixAccountIssuesMenuItem.Account.AttentionReason == Domain.Enums.AccountAttentionReason.MissingSystemFolderConfiguration ? FixMissingFolderConfigTemplate : FixAuthenticationIssueTemplate; else { diff --git a/Wino.Core.UWP/Services/ApplicationResourceManager.cs b/Wino.Core.UWP/Services/ApplicationResourceManager.cs new file mode 100644 index 00000000..0e50dc1f --- /dev/null +++ b/Wino.Core.UWP/Services/ApplicationResourceManager.cs @@ -0,0 +1,27 @@ +using System.Linq; +using Windows.UI.Xaml; +using Wino.Core.Domain.Interfaces; +using Wino.Core.UWP; + +namespace Wino.Services +{ + public class ApplicationResourceManager : IApplicationResourceManager + { + public void AddResource(ResourceDictionary resource) + => WinoApplication.Current.Resources.MergedDictionaries.Add(resource); + public void RemoveResource(ResourceDictionary resource) + => WinoApplication.Current.Resources.MergedDictionaries.Remove(resource); + + public bool ContainsResourceKey(string resourceKey) + => WinoApplication.Current.Resources.ContainsKey(resourceKey); + + public ResourceDictionary GetLastResource() + => WinoApplication.Current.Resources.MergedDictionaries.LastOrDefault(); + + public void ReplaceResource(string resourceKey, object resource) + => WinoApplication.Current.Resources[resourceKey] = resource; + + public TReturnType GetResource(string resourceKey) + => (TReturnType)WinoApplication.Current.Resources[resourceKey]; + } +} diff --git a/Wino.Core.UWP/Services/ConfigurationService.cs b/Wino.Core.UWP/Services/ConfigurationService.cs index 79455800..7c905742 100644 --- a/Wino.Core.UWP/Services/ConfigurationService.cs +++ b/Wino.Core.UWP/Services/ConfigurationService.cs @@ -34,6 +34,11 @@ namespace Wino.Core.UWP.Services return (T)TypeDescriptor.GetConverter(typeof(T)).ConvertFromInvariantString(value); } + if (typeof(T) == typeof(TimeSpan)) + { + return (T)(object)TimeSpan.Parse(value); + } + return (T)Convert.ChangeType(value, typeof(T)); } diff --git a/Wino.Core.UWP/Services/DialogServiceBase.cs b/Wino.Core.UWP/Services/DialogServiceBase.cs new file mode 100644 index 00000000..13d8ab07 --- /dev/null +++ b/Wino.Core.UWP/Services/DialogServiceBase.cs @@ -0,0 +1,205 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using CommunityToolkit.Mvvm.Messaging; +using Microsoft.Toolkit.Uwp.Helpers; +using Serilog; +using Windows.Storage; +using Windows.Storage.Pickers; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Wino.Core.Domain; +using Wino.Core.Domain.Enums; +using Wino.Core.Domain.Interfaces; +using Wino.Core.UWP.Extensions; +using Wino.Dialogs; +using Wino.Messaging.Client.Shell; + +namespace Wino.Core.UWP.Services +{ + public class DialogServiceBase : IDialogServiceBase + { + private SemaphoreSlim _presentationSemaphore = new SemaphoreSlim(1); + + protected IThemeService ThemeService { get; } + protected IConfigurationService ConfigurationService { get; } + + protected IApplicationResourceManager ApplicationResourceManager { get; } + + public DialogServiceBase(IThemeService themeService, IConfigurationService configurationService, IApplicationResourceManager applicationResourceManager) + { + ThemeService = themeService; + ConfigurationService = configurationService; + ApplicationResourceManager = applicationResourceManager; + } + + private async Task PickFileAsync(params object[] typeFilters) + { + var picker = new FileOpenPicker + { + ViewMode = PickerViewMode.Thumbnail + }; + + foreach (var filter in typeFilters) + { + picker.FileTypeFilter.Add(filter.ToString()); + } + + var file = await picker.PickSingleFileAsync(); + + if (file == null) return null; + + Windows.Storage.AccessCache.StorageApplicationPermissions.FutureAccessList.AddOrReplace("FilePickerPath", file); + + return file; + } + + public async Task PickWindowsFileContentAsync(params object[] typeFilters) + { + var file = await PickFileAsync(typeFilters); + + if (file == null) return []; + + return await file.ReadBytesAsync(); + } + + public Task ShowMessageAsync(string message, string title, WinoCustomMessageDialogIcon icon = WinoCustomMessageDialogIcon.Information) + => ShowWinoCustomMessageDialogAsync(title, message, Translator.Buttons_Close, icon); + + public Task ShowConfirmationDialogAsync(string question, string title, string confirmationButtonTitle) + => ShowWinoCustomMessageDialogAsync(title, question, confirmationButtonTitle, WinoCustomMessageDialogIcon.Question, Translator.Buttons_Cancel, string.Empty); + + public async Task ShowWinoCustomMessageDialogAsync(string title, + string description, + string approveButtonText, + WinoCustomMessageDialogIcon? icon, + string cancelButtonText = "", + string dontAskAgainConfigurationKey = "") + + { + // This config key has been marked as don't ask again already. + // Return immidiate result without presenting the dialog. + + bool isDontAskEnabled = !string.IsNullOrEmpty(dontAskAgainConfigurationKey); + + if (isDontAskEnabled && ConfigurationService.Get(dontAskAgainConfigurationKey, false)) return false; + + var informationContainer = new CustomMessageDialogInformationContainer(title, description, icon.Value, isDontAskEnabled); + + var dialog = new ContentDialog + { + Style = ApplicationResourceManager.GetResource - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0 - - - - - + diff --git a/Wino.Mail/App.xaml.cs b/Wino.Mail/App.xaml.cs index e3cbbf7e..cd740c52 100644 --- a/Wino.Mail/App.xaml.cs +++ b/Wino.Mail/App.xaml.cs @@ -1,14 +1,8 @@ using System; using System.Collections.Generic; -using System.IO; -using System.Linq; using System.Text; -using System.Threading.Tasks; using CommunityToolkit.Mvvm.Messaging; using CommunityToolkit.WinUI.Notifications; -using Microsoft.AppCenter; -using Microsoft.AppCenter.Analytics; -using Microsoft.AppCenter.Crashes; using Microsoft.Extensions.DependencyInjection; using Nito.AsyncEx; using Serilog; @@ -16,16 +10,8 @@ using Windows.ApplicationModel; using Windows.ApplicationModel.Activation; using Windows.ApplicationModel.AppService; using Windows.ApplicationModel.Background; -using Windows.ApplicationModel.Core; -using Windows.Foundation.Metadata; -using Windows.Storage; -using Windows.System.Profile; -using Windows.UI; using Windows.UI.Core.Preview; using Windows.UI.Notifications; -using Windows.UI.ViewManagement; -using Windows.UI.Xaml; -using Windows.UI.Xaml.Controls; using Wino.Activation; using Wino.Core; using Wino.Core.Domain; @@ -34,9 +20,7 @@ using Wino.Core.Domain.Exceptions; using Wino.Core.Domain.Interfaces; using Wino.Core.Domain.Models.MailItem; using Wino.Core.Domain.Models.Synchronization; -using Wino.Core.Services; using Wino.Core.UWP; -using Wino.Core.UWP.Services; using Wino.Mail.ViewModels; using Wino.Messaging.Client.Connection; using Wino.Messaging.Client.Navigation; @@ -45,139 +29,23 @@ using Wino.Services; namespace Wino { - public sealed partial class App : Application, IRecipient + public sealed partial class App : WinoApplication, IRecipient { - private const string WinoLaunchLogPrefix = "[Wino Launch] "; - private const string AppCenterKey = "90deb1d0-a77f-47d0-8a6b-7eaf111c6b72"; - - public new static App Current => (App)Application.Current; - public IServiceProvider Services { get; } + public override string AppCenterKey { get; } = "90deb1d0-a77f-47d0-8a6b-7eaf111c6b72"; private BackgroundTaskDeferral connectionBackgroundTaskDeferral; private BackgroundTaskDeferral toastActionBackgroundTaskDeferral; - private readonly IWinoServerConnectionManager _appServiceConnectionManager; - private readonly ILogInitializer _logInitializer; - private readonly IThemeService _themeService; - private readonly IDatabaseService _databaseService; - private readonly IApplicationConfiguration _appInitializerService; - private readonly ITranslationService _translationService; - private readonly IApplicationConfiguration _applicationFolderConfiguration; - private readonly IDialogService _dialogService; - - // Order matters. - private List initializeServices => new List() - { - _databaseService, - _translationService, - _themeService, - }; - public App() { InitializeComponent(); - UnhandledException += OnAppUnhandledException; - - Resuming += OnResuming; - Suspending += OnSuspending; - - Services = ConfigureServices(); - - _logInitializer = Services.GetService(); - - ConfigureLogger(); - ConfigureAppCenter(); - ConfigurePrelaunch(); - ConfigureXbox(); - - _applicationFolderConfiguration = Services.GetService(); - - // Make sure the paths are setup on app start. - _applicationFolderConfiguration.ApplicationDataFolderPath = ApplicationData.Current.LocalFolder.Path; - _applicationFolderConfiguration.PublisherSharedFolderPath = ApplicationData.Current.GetPublisherCacheFolder(ApplicationConfiguration.SharedFolderName).Path; - _applicationFolderConfiguration.ApplicationTempFolderPath = ApplicationData.Current.TemporaryFolder.Path; - - _appServiceConnectionManager = Services.GetService>(); - _themeService = Services.GetService(); - _databaseService = Services.GetService(); - _appInitializerService = Services.GetService(); - _translationService = Services.GetService(); - _dialogService = Services.GetService(); - Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); WeakReferenceMessenger.Default.Register(this); } - private async void ApplicationCloseRequested(object sender, SystemNavigationCloseRequestedPreviewEventArgs e) - { - var deferral = e.GetDeferral(); - - // Wino should notify user on app close if: - // 1. Startup behavior is not Enabled. - // 2. Server terminate behavior is set to Terminate. - - // User has some accounts. Check if Wino Server runs on system startup. - - var dialogService = Services.GetService(); - var startupBehaviorService = Services.GetService(); - var preferencesService = Services.GetService(); - - var currentStartupBehavior = await startupBehaviorService.GetCurrentStartupBehaviorAsync(); - - bool? isGoToAppPreferencesRequested = null; - - if (preferencesService.ServerTerminationBehavior == ServerBackgroundMode.Terminate) - { - // Starting the server is fine, but check if server termination behavior is set to terminate. - // This state will kill the server once the app is terminated. - - isGoToAppPreferencesRequested = await _dialogService.ShowWinoCustomMessageDialogAsync(Translator.AppCloseBackgroundSynchronizationWarningTitle, - $"{Translator.AppCloseTerminateBehaviorWarningMessageFirstLine}\n{Translator.AppCloseTerminateBehaviorWarningMessageSecondLine}\n\n{Translator.AppCloseTerminateBehaviorWarningMessageThirdLine}", - Translator.Buttons_Yes, - WinoCustomMessageDialogIcon.Warning, - Translator.Buttons_No, - "DontAskTerminateServerBehavior"); - } - - if (isGoToAppPreferencesRequested == null && currentStartupBehavior != StartupBehaviorResult.Enabled) - { - // Startup behavior is not enabled. - - isGoToAppPreferencesRequested = await dialogService.ShowWinoCustomMessageDialogAsync(Translator.AppCloseBackgroundSynchronizationWarningTitle, - $"{Translator.AppCloseStartupLaunchDisabledWarningMessageFirstLine}\n{Translator.AppCloseStartupLaunchDisabledWarningMessageSecondLine}\n\n{Translator.AppCloseStartupLaunchDisabledWarningMessageThirdLine}", - Translator.Buttons_Yes, - WinoCustomMessageDialogIcon.Warning, - Translator.Buttons_No, - "DontAskDisabledStartup"); - } - - if (isGoToAppPreferencesRequested == true) - { - WeakReferenceMessenger.Default.Send(new NavigateAppPreferencesRequested()); - e.Handled = true; - } - else if (preferencesService.ServerTerminationBehavior == ServerBackgroundMode.Terminate) - { - try - { - var isServerKilled = await _appServiceConnectionManager.GetResponseAsync(new TerminateServerRequested()); - - isServerKilled.ThrowIfFailed(); - - Log.Information("Server is killed."); - } - catch (Exception ex) - { - Log.Error(ex, "Failed to kill server."); - } - } - - deferral.Complete(); - } - - private async void OnResuming(object sender, object e) + public override async void OnResuming(object sender, object e) { // App Service connection was lost on suspension. // We must restore it. @@ -185,7 +53,7 @@ namespace Wino try { - await _appServiceConnectionManager.ConnectAsync(); + await AppServiceConnectionManager.ConnectAsync(); } catch (OperationCanceledException) { @@ -197,19 +65,13 @@ namespace Wino } } - private void OnSuspending(object sender, SuspendingEventArgs e) - { - var deferral = e.SuspendingOperation.GetDeferral(); - deferral.Complete(); - } - - private void LogActivation(string log) => Log.Information($"{WinoLaunchLogPrefix}{log}"); - private IServiceProvider ConfigureServices() + public override IServiceProvider ConfigureServices() { var services = new ServiceCollection(); services.RegisterCoreServices(); services.RegisterCoreUWPServices(); + services.RegisterCoreViewModels(); RegisterUWPServices(services); RegisterViewModels(services); @@ -229,30 +91,22 @@ namespace Wino private void RegisterUWPServices(IServiceCollection services) { - services.AddSingleton, ApplicationResourceManager>(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); } private void RegisterViewModels(IServiceCollection services) { services.AddSingleton(typeof(AppShellViewModel)); - services.AddTransient(typeof(SettingsDialogViewModel)); - services.AddTransient(typeof(PersonalizationPageViewModel)); - services.AddTransient(typeof(SettingOptionsPageViewModel)); + services.AddTransient(typeof(MailListPageViewModel)); services.AddTransient(typeof(MailRenderingPageViewModel)); services.AddTransient(typeof(AccountManagementViewModel)); services.AddTransient(typeof(WelcomePageViewModel)); - services.AddTransient(typeof(AboutPageViewModel)); + services.AddTransient(typeof(ComposePageViewModel)); services.AddTransient(typeof(IdlePageViewModel)); - services.AddTransient(typeof(SettingsPageViewModel)); - services.AddTransient(typeof(NewAccountManagementPageViewModel)); + services.AddTransient(typeof(AccountDetailsPageViewModel)); services.AddTransient(typeof(SignatureManagementPageViewModel)); services.AddTransient(typeof(MessageListPageViewModel)); @@ -265,98 +119,6 @@ namespace Wino #endregion - #region Misc Configuration - - private void ConfigureLogger() - { - string logFilePath = Path.Combine(ApplicationData.Current.LocalFolder.Path, Constants.ClientLogFile); - _logInitializer.SetupLogger(logFilePath); - } - - private void ConfigureAppCenter() => AppCenter.Start(AppCenterKey, typeof(Analytics), typeof(Crashes)); - - private void ConfigurePrelaunch() - { - if (ApiInformation.IsMethodPresent("Windows.ApplicationModel.Core.CoreApplication", "EnablePrelaunch")) - CoreApplication.EnablePrelaunch(true); - } - - private void ConfigureXbox() - { - // Xbox users should use Reveal focus. - if (ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 6)) - { - FocusVisualKind = AnalyticsInfo.VersionInfo.DeviceFamily == "Xbox" ? FocusVisualKind.Reveal : FocusVisualKind.HighVisibility; - } - } - - private void ConfigureTitleBar() - { - var coreTitleBar = CoreApplication.GetCurrentView().TitleBar; - var applicationViewTitleBar = ApplicationView.GetForCurrentView().TitleBar; - - // Extend shell content into core window to meet design requirements. - coreTitleBar.ExtendViewIntoTitleBar = true; - - // Change system buttons and background colors to meet design requirements. - applicationViewTitleBar.ButtonBackgroundColor = Colors.Transparent; - applicationViewTitleBar.BackgroundColor = Colors.Transparent; - applicationViewTitleBar.ButtonInactiveBackgroundColor = Colors.Transparent; - applicationViewTitleBar.ButtonForegroundColor = Colors.White; - } - - #endregion - - protected override void OnWindowCreated(WindowCreatedEventArgs args) - { - base.OnWindowCreated(args); - - LogActivation($"OnWindowCreated -> IsWindowNull: {args.Window == null}"); - - ConfigureTitleBar(); - TryRegisterAppCloseChange(); - } - - protected override async void OnLaunched(LaunchActivatedEventArgs args) - { - LogActivation($"OnLaunched -> {args.GetType().Name}, Kind -> {args.Kind}, PreviousExecutionState -> {args.PreviousExecutionState}, IsPrelaunch -> {args.PrelaunchActivated}"); - - if (!args.PrelaunchActivated) - { - await ActivateWinoAsync(args); - } - } - - private void TryRegisterAppCloseChange() - { - try - { - var systemNavigationManagerPreview = SystemNavigationManagerPreview.GetForCurrentView(); - - systemNavigationManagerPreview.CloseRequested -= ApplicationCloseRequested; - systemNavigationManagerPreview.CloseRequested += ApplicationCloseRequested; - } - catch { } - } - - protected override async void OnFileActivated(FileActivatedEventArgs args) - { - base.OnFileActivated(args); - - LogActivation($"OnFileActivated -> ItemCount: {args.Files.Count}, Kind: {args.Kind}, PreviousExecutionState: {args.PreviousExecutionState}"); - - await ActivateWinoAsync(args); - } - - protected override async void OnActivated(IActivatedEventArgs args) - { - base.OnActivated(args); - - Log.Information($"OnActivated -> {args.GetType().Name}, Kind -> {args.Kind}, Prev Execution State -> {args.PreviousExecutionState}"); - - await ActivateWinoAsync(args); - } - protected override async void OnBackgroundActivated(BackgroundActivatedEventArgs args) { base.OnBackgroundActivated(args); @@ -373,7 +135,7 @@ namespace Wino connectionBackgroundTaskDeferral = args.TaskInstance.GetDeferral(); args.TaskInstance.Canceled += OnConnectionBackgroundTaskCanceled; - _appServiceConnectionManager.Connection = appServiceTriggerDetails.AppServiceConnection; + AppServiceConnectionManager.Connection = appServiceTriggerDetails.AppServiceConnection; WeakReferenceMessenger.Default.Send(new WinoServerConnectionEstablished()); } @@ -404,9 +166,9 @@ namespace Wino // At this point server should've already been connected. - var processor = Services.GetService(); - var delegator = Services.GetService(); - var mailService = Services.GetService(); + var processor = base.Services.GetService(); + var delegator = base.Services.GetService(); + var mailService = base.Services.GetService(); var mailItem = await mailService.GetSingleMailItemAsync(mailUniqueId); @@ -439,72 +201,7 @@ namespace Wino toastActionBackgroundTaskDeferral = null; } - private void OnAppUnhandledException(object sender, Windows.UI.Xaml.UnhandledExceptionEventArgs e) - { - var parameters = new Dictionary() - { - { "BaseMessage", e.Exception.GetBaseException().Message }, - { "BaseStackTrace", e.Exception.GetBaseException().StackTrace }, - { "StackTrace", e.Exception.StackTrace }, - { "Message", e.Exception.Message }, - }; - - Log.Error(e.Exception, "[Wino Crash]"); - - Crashes.TrackError(e.Exception, parameters); - Analytics.TrackEvent("Wino Crashed", parameters); - } - - private bool IsInteractiveLaunchArgs(object args) => args is IActivatedEventArgs; - - private Task InitializeServicesAsync() => initializeServices.Select(a => a.InitializeAsync()).WhenAll(); - - private async Task ActivateWinoAsync(object args) - { - await InitializeServicesAsync(); - - if (IsInteractiveLaunchArgs(args)) - { - if (Window.Current.Content == null) - { - var mainFrame = new Frame(); - - Window.Current.Content = mainFrame; - - await _themeService.InitializeAsync(); - } - } - - await HandleActivationAsync(args); - - if (IsInteractiveLaunchArgs(args)) - { - Window.Current.Activate(); - - LogActivation("Window activated"); - } - } - - private async Task HandleActivationAsync(object activationArgs) - { - var activationHandler = GetActivationHandlers().FirstOrDefault(h => h.CanHandle(activationArgs)); - - if (activationHandler != null) - { - await activationHandler.HandleAsync(activationArgs); - } - - if (IsInteractiveLaunchArgs(activationArgs)) - { - var defaultHandler = new DefaultActivationHandler(); - if (defaultHandler.CanHandle(activationArgs)) - { - await defaultHandler.HandleAsync(activationArgs); - } - } - } - - private IEnumerable GetActivationHandlers() + protected override IEnumerable GetActivationHandlers() { yield return Services.GetService(); yield return Services.GetService(); @@ -520,20 +217,90 @@ namespace Wino connectionBackgroundTaskDeferral?.Complete(); connectionBackgroundTaskDeferral = null; - _appServiceConnectionManager.Connection = null; + AppServiceConnectionManager.Connection = null; } public async void Receive(NewSynchronizationRequested message) { try { - var synchronizationResultResponse = await _appServiceConnectionManager.GetResponseAsync(message); + var synchronizationResultResponse = await AppServiceConnectionManager.GetResponseAsync(message); synchronizationResultResponse.ThrowIfFailed(); } catch (WinoServerException serverException) { - _dialogService.InfoBarMessage(Translator.Info_SyncFailedTitle, serverException.Message, InfoBarMessageType.Error); + DialogService.InfoBarMessage(Translator.Info_SyncFailedTitle, serverException.Message, InfoBarMessageType.Error); } } + + protected override async void OnApplicationCloseRequested(object sender, SystemNavigationCloseRequestedPreviewEventArgs e) + { + var deferral = e.GetDeferral(); + + // Wino should notify user on app close if: + // 1. Startup behavior is not Enabled. + // 2. Server terminate behavior is set to Terminate. + + // User has some accounts. Check if Wino Server runs on system startup. + + var dialogService = base.Services.GetService(); + var startupBehaviorService = base.Services.GetService(); + var preferencesService = base.Services.GetService(); + + var currentStartupBehavior = await startupBehaviorService.GetCurrentStartupBehaviorAsync(); + + bool? isGoToAppPreferencesRequested = null; + + if (preferencesService.ServerTerminationBehavior == ServerBackgroundMode.Terminate) + { + // Starting the server is fine, but check if server termination behavior is set to terminate. + // This state will kill the server once the app is terminated. + + isGoToAppPreferencesRequested = await DialogService.ShowWinoCustomMessageDialogAsync(Translator.AppCloseBackgroundSynchronizationWarningTitle, + $"{Translator.AppCloseTerminateBehaviorWarningMessageFirstLine}\n{Translator.AppCloseTerminateBehaviorWarningMessageSecondLine}\n\n{Translator.AppCloseTerminateBehaviorWarningMessageThirdLine}", + Translator.Buttons_Yes, + WinoCustomMessageDialogIcon.Warning, + Translator.Buttons_No, + "DontAskTerminateServerBehavior"); + } + + if (isGoToAppPreferencesRequested == null && currentStartupBehavior != StartupBehaviorResult.Enabled) + { + // Startup behavior is not enabled. + + isGoToAppPreferencesRequested = await dialogService.ShowWinoCustomMessageDialogAsync(Translator.AppCloseBackgroundSynchronizationWarningTitle, + $"{Translator.AppCloseStartupLaunchDisabledWarningMessageFirstLine}\n{Translator.AppCloseStartupLaunchDisabledWarningMessageSecondLine}\n\n{Translator.AppCloseStartupLaunchDisabledWarningMessageThirdLine}", + Translator.Buttons_Yes, + WinoCustomMessageDialogIcon.Warning, + Translator.Buttons_No, + "DontAskDisabledStartup"); + } + + if (isGoToAppPreferencesRequested == true) + { + WeakReferenceMessenger.Default.Send(new NavigateAppPreferencesRequested()); + e.Handled = true; + } + else if (preferencesService.ServerTerminationBehavior == ServerBackgroundMode.Terminate) + { + try + { + var isServerKilled = await AppServiceConnectionManager.GetResponseAsync(new TerminateServerRequested()); + + isServerKilled.ThrowIfFailed(); + + Log.Information("Server is killed."); + } + catch (Exception ex) + { + Log.Error(ex, "Failed to kill server."); + } + } + + deferral.Complete(); + } + + protected override ActivationHandler GetDefaultActivationHandler() + => new DefaultActivationHandler(); } } diff --git a/Wino.Mail/AppShell.xaml b/Wino.Mail/AppShell.xaml index 5bf79342..8e3e7dee 100644 --- a/Wino.Mail/AppShell.xaml +++ b/Wino.Mail/AppShell.xaml @@ -8,6 +8,8 @@ xmlns:animations="using:CommunityToolkit.WinUI.Animations" xmlns:controls="using:Wino.Controls" xmlns:controls1="using:CommunityToolkit.WinUI.Controls" + xmlns:coreControls="using:Wino.Core.UWP.Controls" + xmlns:coreSelectors="using:Wino.Core.UWP.Selectors" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:domain="using:Wino.Core.Domain" xmlns:enums="using:Wino.Core.Domain.Enums" @@ -15,21 +17,12 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:menu="using:Wino.Core.MenuItems" xmlns:muxc="using:Microsoft.UI.Xaml.Controls" - xmlns:selectors="using:Wino.Selectors" x:Name="Root" muxc:BackdropMaterial.ApplyToRootOrPageBackground="{ThemeResource UseMica}" mc:Ignorable="d"> - - - - - - - - - + - + - + - - + + - - + + - - + + - - - - - - - - - - - + + - - + - + - - - - + + + - + @@ -261,14 +242,14 @@ Visibility="{x:Bind helpers:XamlHelpers.CountToVisibilityConverter(UnreadItemCount), Mode=OneWay}" Value="{x:Bind UnreadItemCount, Mode=OneWay}" /> - + - - + + - + @@ -294,7 +275,7 @@ - - - - + + + - + - - - - - + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -493,7 +433,7 @@ - - RealAppBar.ShellFrameContent = (e.Content as BasePage).ShellContent; - private void BackButtonClicked(Controls.Advanced.WinoAppTitleBar sender, RoutedEventArgs args) + private void BackButtonClicked(WinoAppTitleBar sender, RoutedEventArgs args) { WeakReferenceMessenger.Default.Send(new ClearMailSelectionsRequested()); WeakReferenceMessenger.Default.Send(new DisposeRenderingFrameRequested()); diff --git a/Wino.Mail/Behaviors/BindableCommandBarBehavior.cs b/Wino.Mail/Behaviors/BindableCommandBarBehavior.cs index 2cf6c8c1..09e86a7d 100644 --- a/Wino.Mail/Behaviors/BindableCommandBarBehavior.cs +++ b/Wino.Mail/Behaviors/BindableCommandBarBehavior.cs @@ -7,6 +7,7 @@ using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Controls.Primitives; using Wino.Controls; using Wino.Core.Domain.Models.Menus; +using Wino.Core.UWP.Controls; using Wino.Helpers; namespace Wino.Behaviors diff --git a/Wino.Mail/Behaviors/CreateMailNavigationItemBehavior.cs b/Wino.Mail/Behaviors/CreateMailNavigationItemBehavior.cs index 6726a351..77d35081 100644 --- a/Wino.Mail/Behaviors/CreateMailNavigationItemBehavior.cs +++ b/Wino.Mail/Behaviors/CreateMailNavigationItemBehavior.cs @@ -1,8 +1,8 @@ using System.Collections.ObjectModel; using Microsoft.Xaml.Interactivity; using Windows.UI.Xaml; -using Wino.Controls; using Wino.Core.MenuItems; +using Wino.Core.UWP.Controls; namespace Wino.Behaviors { diff --git a/Wino.Mail/Controls/AccountNavigationItem.cs b/Wino.Mail/Controls/AccountNavigationItem.cs index d19e3f6f..7172d94b 100644 --- a/Wino.Mail/Controls/AccountNavigationItem.cs +++ b/Wino.Mail/Controls/AccountNavigationItem.cs @@ -2,6 +2,7 @@ using Microsoft.UI.Xaml.Controls; using Windows.UI.Xaml; using Wino.Core.Domain.Interfaces; +using Wino.Core.UWP.Controls; namespace Wino.Controls { diff --git a/Wino.Mail/Controls/Advanced/WinoAppTitleBar.xaml b/Wino.Mail/Controls/Advanced/WinoAppTitleBar.xaml deleted file mode 100644 index b862d210..00000000 --- a/Wino.Mail/Controls/Advanced/WinoAppTitleBar.xaml +++ /dev/null @@ -1,173 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Wino.Mail/Controls/MailItemDisplayInformationControl.xaml b/Wino.Mail/Controls/MailItemDisplayInformationControl.xaml index c5f01fd5..f989f972 100644 --- a/Wino.Mail/Controls/MailItemDisplayInformationControl.xaml +++ b/Wino.Mail/Controls/MailItemDisplayInformationControl.xaml @@ -4,6 +4,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:animatedvisuals="using:Microsoft.UI.Xaml.Controls.AnimatedVisuals" xmlns:controls="using:Wino.Controls" + xmlns:coreControls="using:Wino.Core.UWP.Controls" xmlns:domain="using:Wino.Core.Domain" xmlns:enums="using:Wino.Core.Domain.Enums" xmlns:helpers="using:Wino.Helpers" @@ -139,19 +140,19 @@ diff --git a/Wino.Mail/Controls/MailItemDisplayInformationControl.xaml.cs b/Wino.Mail/Controls/MailItemDisplayInformationControl.xaml.cs index 5e104b0b..b37f2927 100644 --- a/Wino.Mail/Controls/MailItemDisplayInformationControl.xaml.cs +++ b/Wino.Mail/Controls/MailItemDisplayInformationControl.xaml.cs @@ -3,7 +3,7 @@ using System.Numerics; using System.Windows.Input; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; -using Wino.Core.Domain.Entities; +using Wino.Core.Domain.Entities.Mail; using Wino.Core.Domain.Enums; using Wino.Core.Domain.Models.MailItem; using Wino.Extensions; diff --git a/Wino.Mail/Dialogs/AccountReorderDialog.xaml b/Wino.Mail/Dialogs/AccountReorderDialog.xaml index 4f733a3a..d37c129e 100644 --- a/Wino.Mail/Dialogs/AccountReorderDialog.xaml +++ b/Wino.Mail/Dialogs/AccountReorderDialog.xaml @@ -2,21 +2,22 @@ x:Class="Wino.Dialogs.AccountReorderDialog" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" - xmlns:local="using:Wino.Dialogs" - xmlns:d="http://schemas.microsoft.com/expression/blend/2008" - xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - xmlns:interfaces="using:Wino.Core.Domain.Interfaces" xmlns:controls="using:Wino.Controls" - xmlns:helpers="using:Wino.Helpers" + xmlns:coreControls="using:Wino.Core.UWP.Controls" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:domain="using:Wino.Core.Domain" + xmlns:helpers="using:Wino.Helpers" + xmlns:interfaces="using:Wino.Core.Domain.Interfaces" + xmlns:local="using:Wino.Dialogs" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:selectors="using:Wino.Selectors" - mc:Ignorable="d" Title="Reorder Accounnts" Closed="DialogClosed" - Opened="DialogOpened" DefaultButton="Secondary" + Opened="DialogOpened" + SecondaryButtonText="{x:Bind domain:Translator.Buttons_Cancel}" Style="{StaticResource WinoDialogStyle}" - SecondaryButtonText="{x:Bind domain:Translator.Buttons_Cancel}"> + mc:Ignorable="d"> @@ -29,19 +30,19 @@ - + VerticalAlignment="Center" + FontSize="24" + Icon="{x:Bind helpers:XamlHelpers.GetProviderIcon(ProviderDetail.Type)}" /> + FontWeight="SemiBold" + Text="{x:Bind StartupEntityTitle}" /> + Text="{x:Bind StartupEntityAddresses}" /> @@ -59,13 +60,13 @@ VerticalAlignment="Center" Data="F1 M 8.613281 17.5 C 8.75 17.942709 8.945312 18.359375 9.199219 18.75 L 4.921875 18.75 C 4.433594 18.75 3.966471 18.650717 3.520508 18.452148 C 3.074544 18.25358 2.683919 17.986654 2.348633 17.651367 C 2.013346 17.31608 1.746419 16.925455 1.547852 16.479492 C 1.349284 16.033529 1.25 15.566406 1.25 15.078125 L 1.25 4.921875 C 1.25 4.433594 1.349284 3.966473 1.547852 3.520508 C 1.746419 3.074545 2.013346 2.68392 2.348633 2.348633 C 2.683919 2.013348 3.074544 1.74642 3.520508 1.547852 C 3.966471 1.349285 4.433594 1.25 4.921875 1.25 L 15.078125 1.25 C 15.566406 1.25 16.033527 1.349285 16.479492 1.547852 C 16.925455 1.74642 17.31608 2.013348 17.651367 2.348633 C 17.986652 2.68392 18.25358 3.074545 18.452148 3.520508 C 18.650715 3.966473 18.75 4.433594 18.75 4.921875 L 18.75 6.572266 C 18.580729 6.344402 18.390299 6.132813 18.178711 5.9375 C 17.967121 5.742188 17.740885 5.566407 17.5 5.410156 L 17.5 4.951172 C 17.5 4.625651 17.433268 4.314779 17.299805 4.018555 C 17.16634 3.722332 16.987305 3.461914 16.762695 3.237305 C 16.538086 3.012695 16.277668 2.83366 15.981445 2.700195 C 15.685221 2.566732 15.374349 2.5 15.048828 2.5 L 4.951172 2.5 C 4.619141 2.5 4.303385 2.568359 4.003906 2.705078 C 3.704427 2.841797 3.44401 3.02409 3.222656 3.251953 C 3.001302 3.479818 2.825521 3.745117 2.695312 4.047852 C 2.565104 4.350587 2.5 4.66797 2.5 5 L 13.310547 5 C 12.60091 5.266928 11.998697 5.683594 11.503906 6.25 L 2.5 6.25 L 2.5 15.048828 C 2.5 15.38737 2.568359 15.704753 2.705078 16.000977 C 2.841797 16.297201 3.024088 16.55599 3.251953 16.777344 C 3.479818 16.998697 3.745117 17.174479 4.047852 17.304688 C 4.350586 17.434896 4.667969 17.5 5 17.5 Z M 18.125 9.443359 C 18.125 9.866537 18.040363 10.263672 17.871094 10.634766 C 17.701822 11.005859 17.473957 11.329753 17.1875 11.606445 C 16.901041 11.883139 16.56901 12.101237 16.191406 12.260742 C 15.813802 12.420248 15.416666 12.5 15 12.5 C 14.563802 12.5 14.1569 12.41862 13.779297 12.255859 C 13.401691 12.0931 13.071288 11.870117 12.788086 11.586914 C 12.504882 11.303711 12.2819 10.973308 12.119141 10.595703 C 11.95638 10.2181 11.875 9.811198 11.875 9.375 C 11.875 8.938803 11.95638 8.531901 12.119141 8.154297 C 12.2819 7.776693 12.504882 7.446289 12.788086 7.163086 C 13.071288 6.879883 13.401691 6.656901 13.779297 6.494141 C 14.1569 6.331381 14.563802 6.25 15 6.25 C 15.449218 6.25 15.864257 6.333008 16.245117 6.499023 C 16.625977 6.665039 16.956379 6.892904 17.236328 7.182617 C 17.516275 7.472331 17.734375 7.810873 17.890625 8.198242 C 18.046875 8.585612 18.125 9.000651 18.125 9.443359 Z M 20 16.25 C 20 16.666666 19.926758 17.049154 19.780273 17.397461 C 19.633789 17.745768 19.435221 18.058268 19.18457 18.334961 C 18.933918 18.611654 18.642578 18.854166 18.310547 19.0625 C 17.978516 19.270834 17.626953 19.444986 17.255859 19.584961 C 16.884766 19.724936 16.505533 19.829102 16.118164 19.897461 C 15.730794 19.96582 15.358072 20 15 20 C 14.654947 20 14.291992 19.96582 13.911133 19.897461 C 13.530273 19.829102 13.154297 19.726562 12.783203 19.589844 C 12.412109 19.453125 12.058919 19.282227 11.723633 19.077148 C 11.388346 18.87207 11.092122 18.632812 10.834961 18.359375 C 10.577799 18.085938 10.374349 17.779947 10.224609 17.441406 C 10.074869 17.102865 10 16.731771 10 16.328125 L 10 15.78125 C 10 15.501303 10.052083 15.237631 10.15625 14.990234 C 10.260416 14.742839 10.405273 14.526367 10.59082 14.34082 C 10.776367 14.155273 10.991211 14.010417 11.235352 13.90625 C 11.479492 13.802084 11.744791 13.75 12.03125 13.75 L 17.96875 13.75 C 18.248697 13.75 18.512369 13.803711 18.759766 13.911133 C 19.00716 14.018555 19.222004 14.163412 19.404297 14.345703 C 19.586588 14.527995 19.731445 14.742839 19.838867 14.990234 C 19.946289 15.237631 20 15.501303 20 15.78125 Z " /> + FontWeight="SemiBold" + Text="{x:Bind StartupEntityTitle}" /> + Text="{x:Bind StartupEntityAddresses}" /> @@ -76,9 +77,9 @@ + SelectionMode="None" /> diff --git a/Wino.Mail/Dialogs/CreateAccountAliasDialog.xaml b/Wino.Mail/Dialogs/CreateAccountAliasDialog.xaml index ed0323e7..2e18c01b 100644 --- a/Wino.Mail/Dialogs/CreateAccountAliasDialog.xaml +++ b/Wino.Mail/Dialogs/CreateAccountAliasDialog.xaml @@ -2,24 +2,24 @@ x:Class="Wino.Dialogs.CreateAccountAliasDialog" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" - xmlns:local="using:Wino.Dialogs" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" - xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - mc:Ignorable="d" xmlns:domain="using:Wino.Core.Domain" - PrimaryButtonText="{x:Bind domain:Translator.Buttons_Create}" - SecondaryButtonText="{x:Bind domain:Translator.Buttons_Cancel}" + xmlns:local="using:Wino.Dialogs" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + Title="{x:Bind domain:Translator.CreateAccountAliasDialog_Title}" DefaultButton="Primary" PrimaryButtonClick="CreateClicked" - Title="{x:Bind domain:Translator.CreateAccountAliasDialog_Title}" - Style="{StaticResource WinoDialogStyle}"> + PrimaryButtonText="{x:Bind domain:Translator.Buttons_Create}" + SecondaryButtonText="{x:Bind domain:Translator.Buttons_Cancel}" + Style="{StaticResource WinoDialogStyle}" + mc:Ignorable="d"> - + + Header="{x:Bind domain:Translator.CreateAccountAliasDialog_AliasAddress}" + PlaceholderText="{x:Bind domain:Translator.CreateAccountAliasDialog_AliasAddressPlaceholder}" /> + Header="{x:Bind domain:Translator.CreateAccountAliasDialog_ReplyToAddress}" + PlaceholderText="{x:Bind domain:Translator.CreateAccountAliasDialog_ReplyToAddressPlaceholder}" /> diff --git a/Wino.Mail/Dialogs/CreateAccountAliasDialog.xaml.cs b/Wino.Mail/Dialogs/CreateAccountAliasDialog.xaml.cs index a3dfc7f0..806f661f 100644 --- a/Wino.Mail/Dialogs/CreateAccountAliasDialog.xaml.cs +++ b/Wino.Mail/Dialogs/CreateAccountAliasDialog.xaml.cs @@ -1,6 +1,6 @@ using System; using Windows.UI.Xaml.Controls; -using Wino.Core.Domain.Entities; +using Wino.Core.Domain.Entities.Mail; using Wino.Core.Domain.Interfaces; namespace Wino.Dialogs diff --git a/Wino.Mail/Dialogs/MoveMailDialog.xaml b/Wino.Mail/Dialogs/MoveMailDialog.xaml index dff1bd8c..570d7ff8 100644 --- a/Wino.Mail/Dialogs/MoveMailDialog.xaml +++ b/Wino.Mail/Dialogs/MoveMailDialog.xaml @@ -3,17 +3,18 @@ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:controls="using:Wino.Controls" - xmlns:helpers="using:Wino.Helpers" - xmlns:muxc="using:Microsoft.UI.Xaml.Controls" + xmlns:coreControls="using:Wino.Core.UWP.Controls" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" - xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - xmlns:folders="using:Wino.Core.Domain.Models.Folders" - Title="{x:Bind domain:Translator.MoveMailDialog_Title}" - Style="{StaticResource WinoDialogStyle}" - PrimaryButtonText="{x:Bind domain:Translator.Buttons_Cancel}" xmlns:domain="using:Wino.Core.Domain" + xmlns:folders="using:Wino.Core.Domain.Models.Folders" + xmlns:helpers="using:Wino.Helpers" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:muxc="using:Microsoft.UI.Xaml.Controls" + Title="{x:Bind domain:Translator.MoveMailDialog_Title}" DefaultButton="Primary" PrimaryButtonClick="CancelClicked" + PrimaryButtonText="{x:Bind domain:Translator.Buttons_Cancel}" + Style="{StaticResource WinoDialogStyle}" mc:Ignorable="d"> @@ -23,12 +24,12 @@ 756 - + - + @@ -44,17 +45,17 @@ + Foreground="{ThemeResource InfoBarWarningSeverityIconBackground}" + Visibility="Collapsed" /> diff --git a/Wino.Mail/Dialogs/NewImapSetupDialog.xaml b/Wino.Mail/Dialogs/NewImapSetupDialog.xaml index 09ffaba7..f161ddd3 100644 --- a/Wino.Mail/Dialogs/NewImapSetupDialog.xaml +++ b/Wino.Mail/Dialogs/NewImapSetupDialog.xaml @@ -2,15 +2,15 @@ x:Class="Wino.Dialogs.NewImapSetupDialog" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" - xmlns:local="using:Wino.Dialogs" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" - Style="{StaticResource WinoDialogStyle}" - DefaultButton="Secondary" - Closing="OnDialogClosing" - Closed="ImapSetupDialogClosed" - Opened="ImapSetupDialogOpened" - FullSizeDesired="False" + xmlns:local="using:Wino.Dialogs" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + Closed="ImapSetupDialogClosed" + Closing="OnDialogClosing" + DefaultButton="Secondary" + FullSizeDesired="False" + Opened="ImapSetupDialogOpened" + Style="{StaticResource WinoDialogStyle}" mc:Ignorable="d"> diff --git a/Wino.Mail/Dialogs/NewImapSetupDialog.xaml.cs b/Wino.Mail/Dialogs/NewImapSetupDialog.xaml.cs index 1d01e32f..34453f7f 100644 --- a/Wino.Mail/Dialogs/NewImapSetupDialog.xaml.cs +++ b/Wino.Mail/Dialogs/NewImapSetupDialog.xaml.cs @@ -4,7 +4,7 @@ using System.Threading.Tasks; using CommunityToolkit.Mvvm.Messaging; using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Media.Animation; -using Wino.Core.Domain.Entities; +using Wino.Core.Domain.Entities.Shared; using Wino.Core.Domain.Enums; using Wino.Core.Domain.Interfaces; using Wino.Messaging.Client.Mails; diff --git a/Wino.Mail/Dialogs/SignatureEditorDialog.xaml b/Wino.Mail/Dialogs/SignatureEditorDialog.xaml index 3326191c..a10a2149 100644 --- a/Wino.Mail/Dialogs/SignatureEditorDialog.xaml +++ b/Wino.Mail/Dialogs/SignatureEditorDialog.xaml @@ -5,6 +5,7 @@ xmlns:accounts="using:Wino.Core.Domain.Models.Accounts" xmlns:controls="using:Wino.Controls" xmlns:controls1="using:CommunityToolkit.WinUI.Controls" + xmlns:coreControls="using:Wino.Core.UWP.Controls" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:domain="using:Wino.Core.Domain" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" @@ -59,7 +60,7 @@ ToolTipService.ToolTip="Light Theme" Visibility="{x:Bind IsComposerDarkMode, Mode=OneWay}"> - + @@ -70,7 +71,7 @@ ToolTipService.ToolTip="Dark Theme" Visibility="{x:Bind IsComposerDarkMode, Mode=OneWay, Converter={StaticResource ReverseBooleanToVisibilityConverter}}"> - + diff --git a/Wino.Mail/Dialogs/SignatureEditorDialog.xaml.cs b/Wino.Mail/Dialogs/SignatureEditorDialog.xaml.cs index 821c7ec0..f8c0a316 100644 --- a/Wino.Mail/Dialogs/SignatureEditorDialog.xaml.cs +++ b/Wino.Mail/Dialogs/SignatureEditorDialog.xaml.cs @@ -8,7 +8,7 @@ using Windows.UI.ViewManagement.Core; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; using Wino.Core.Domain; -using Wino.Core.Domain.Entities; +using Wino.Core.Domain.Entities.Mail; using Wino.Core.Domain.Interfaces; using Wino.Core.Domain.Models.Reader; using Wino.Views.Settings; diff --git a/Wino.Mail/Dialogs/SystemFolderConfigurationDialog.xaml b/Wino.Mail/Dialogs/SystemFolderConfigurationDialog.xaml index 44b98813..51f82af3 100644 --- a/Wino.Mail/Dialogs/SystemFolderConfigurationDialog.xaml +++ b/Wino.Mail/Dialogs/SystemFolderConfigurationDialog.xaml @@ -2,21 +2,22 @@ x:Class="Wino.Dialogs.SystemFolderConfigurationDialog" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" - xmlns:d="http://schemas.microsoft.com/expression/blend/2008" - xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:controls="using:Wino.Controls" xmlns:controls1="using:CommunityToolkit.WinUI.Controls" - mc:Ignorable="d" - Style="{StaticResource WinoDialogStyle}" + xmlns:coreControls="using:Wino.Core.UWP.Controls" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:domain="using:Wino.Core.Domain" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + Title="{x:Bind domain:Translator.SettingsConfigureSpecialFolders_Title}" + Closing="DialogClosing" DefaultButton="Primary" IsPrimaryButtonEnabled="True" - SecondaryButtonText="{x:Bind domain:Translator.Buttons_Cancel}" - xmlns:domain="using:Wino.Core.Domain" - PrimaryButtonText="{x:Bind domain:Translator.Buttons_SaveConfiguration}" PrimaryButtonClick="SaveClicked" - Closing="DialogClosing" + PrimaryButtonText="{x:Bind domain:Translator.Buttons_SaveConfiguration}" SecondaryButtonClick="CancelClicked" - Title="{x:Bind domain:Translator.SettingsConfigureSpecialFolders_Title}"> + SecondaryButtonText="{x:Bind domain:Translator.Buttons_Cancel}" + Style="{StaticResource WinoDialogStyle}" + mc:Ignorable="d">