Git Product home page Git Product logo

morning4coffe-dev / recurrents Goto Github PK

View Code? Open in Web Editor NEW
10.0 10.0 1.0 27.91 MB

Tired of losing track of your spending on many different services? With Recurrents, just input your subscriptions for an easy overview and stay in control with reminders, save money, and enjoy a stress-free experience. Take control of your expenses effortlessly!

Home Page: https://www.microsoft.com/store/apps/9N5MJT8G06KC

License: MIT License

C# 99.34% CSS 0.62% JavaScript 0.04%
csharp dotnet fluent modern subscription-manager uno-platform windows winui

recurrents's Introduction

Hi there ๐Ÿ‘‹, Iโ€™m Morning4coffe

A developer and tech lover from Czech Republic

  • ๐Ÿ‘จโ€๐ŸŽ“ Iโ€™m an IT high school student
  • ๐Ÿ’ผ I'm part of Microsoft Student Trainee Center
  • ๐Ÿ”ญ Iโ€™m currently working on Uno Platform
  • ๐Ÿ‘จโ€๐Ÿ’ป Iโ€™m developing on Uno Platform & Flutter in C# & Dart
  • ๐ŸŒฑ Iโ€™m currently struggling to learn PHP & SQL
  • ๐Ÿ“ซ Reach me via Twitter
  • โšก Fun fact: I think Windows 8 was a cool idea

Stats Top Langs

recurrents's People

Contributors

morning4coffe-dev avatar szv avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

Forkers

happyjem

recurrents's Issues

Change from id to Item name

https://api.github.com/morning4coffe-dev/project-sbs/blob/eb2e3fb54ca95468f4058c6b18678cf3be478426/ProjectSBS/ProjectSBS/Services/Notification/NotificationService.cs#L156

๏ปฟ#if __ANDROID__

using Android;
using Android.App;
using Android.Content;
using Android.Content.PM;
using Android.OS;
using Android.Support.V4.App;
using Android.Util;
using Android.Views;
using AndroidX.Core.App;
using AndroidX.Lifecycle;
using System;

namespace ProjectSBS.Services.Notifications;

// TODO: Rename to AndroidNotificationService
public class NotificationService : NotificationServiceBase
{
    private Context context = Android.App.Application.Context;

    public NotificationService()
    {
    }

    public override bool IsEnabledOnDevice()
    {
        if (Build.VERSION.SdkInt >= BuildVersionCodes.N)
        {
            //TODO Returns true on all
            //return true;
        }

        return NotificationManagerCompat.From(context).AreNotificationsEnabled();
    }

    public override void ShowInAppNotification(string notification, bool autoHide)
    {
        InvokeInAppNotificationRequested(new InAppNotificationRequestedEventArgs
        {
            NotificationText = notification,
            NotificationTime = autoHide ? 1500 : 0
        });
    }

    public override async void ScheduleNotification(string id, string title, string text, DateOnly day, TimeOnly time)
    {
        if (!IsEnabledOnDevice())
        {
            //TODO make this not only for not scheduled notifications  
            var current = await ApplicationActivity.GetCurrent(new CancellationToken());
            if (ActivityCompat.CheckSelfPermission(current, Manifest.Permission.PostNotifications) != Android.Content.PM.Permission.Granted)
            {
                ActivityCompat.RequestPermissions(current, new string[] { Manifest.Permission.PostNotifications }, 1);
            }
        }

        id = Guid.NewGuid().ToString();

        DateTime notificationDateTime = new(day.Year, day.Month, day.Day, time.Hour, time.Minute, time.Second);
        long totalMilliSeconds = (long)(notificationDateTime.ToUniversalTime() - DateTime.Now).TotalMilliseconds;

        text += $" Scheduled for {notificationDateTime}";

        var (manager, intent) = CreateAlarm(id, title, text, notificationDateTime);

        GetAlarm();

        manager.SetExact(AlarmType.ElapsedRealtime, totalMilliSeconds, intent);
    }

    private (AlarmManager, PendingIntent) CreateAlarm(string id, string title, string text, DateTime notificationDateTime)
    {
        if (notificationDateTime > DateTime.Now)
        {
            Intent notificationIntent = new(context, typeof(NotificationReceiver));
            notificationIntent.PutExtra("id", id);
            notificationIntent.PutExtra("title", title);
            notificationIntent.PutExtra("text", text);

            var random = new Random();
            int requestCode = random.Next(0, 5000); // TODO: ID here You can convert the id to an integer for the requestCode

            PendingIntent pendingIntent = PendingIntent.GetBroadcast(context, requestCode, notificationIntent, PendingIntentFlags.Immutable);

            AlarmManager alarmManager = (AlarmManager)context.GetSystemService(Context.AlarmService);

            return (alarmManager, pendingIntent);
        }

        //TODO throw new Exception("Desired time was set in the past.");
        return (null, null);
    }

    // TODO: Test this function properly
    public override void RemoveScheduledNotifications(string id)
    {
        Intent notificationIntent = new(context, typeof(NotificationReceiver));
        notificationIntent.PutExtra("id", id);

        var random = new Random();
        int requestCode = random.Next(0, 5000); // TODO: ID here You can convert the id to an integer for the requestCode

        PendingIntent pendingIntent = PendingIntent.GetBroadcast(context, requestCode, notificationIntent, PendingIntentFlags.Immutable);

        AlarmManager alarmManager = (AlarmManager)context.GetSystemService(Context.AlarmService);

        alarmManager.Cancel(pendingIntent);
    }

    private void GetAlarm() 
    {
        AlarmManager alarmManager = (AlarmManager)context.GetSystemService(Context.AlarmService);

        //var c = alarmManager.CanScheduleExactAlarms();
        //var d = alarmManager.NextAlarmClock;
    }

    public override void ShowBasicToastNotification(string title, string description)
    {
        var notificationManager = (NotificationManager)context.GetSystemService(Context.NotificationService);

        var channelId = "ProjectSBS-channel";
        var channelName = "Other";
        var importance = NotificationImportance.High;

        var notificationChannel = new NotificationChannel(channelId, channelName, importance);
        notificationManager.CreateNotificationChannel(notificationChannel);

        var notificationBuilder = new NotificationCompat.Builder(context, channelId)
            .SetSmallIcon(Resource.Drawable.abc_vector_test)
            .SetContentTitle(title)
            .SetContentText(description)
            .SetAutoCancel(true);

        var notification = notificationBuilder.Build();

        notificationManager.Notify(0, notification);
    }
}

[BroadcastReceiver(Enabled = true)]
public class NotificationReceiver : BroadcastReceiver
{
    public override void OnReceive(Context context, Intent intent)
    {
        string id = intent.GetStringExtra("id");
        string title = intent.GetStringExtra("title");
        string text = intent.GetStringExtra("text");

        var notificationManager = (NotificationManager)context.GetSystemService(Context.NotificationService);

        PendingIntent pendingIntent = PendingIntent.GetActivity(context, 0, intent, PendingIntentFlags.Immutable);

        var channelId = id;
        var channelName = id; // TODO: Change from id to Item name
        var importance = NotificationImportance.High;

        var notificationChannel = new NotificationChannel(channelId, channelName, importance);
        notificationManager.CreateNotificationChannel(notificationChannel);

        var notificationBuilder = new NotificationCompat.Builder(context, channelId)
            .SetSmallIcon(Resource.Drawable.abc_vector_test)
            .SetContentTitle(title)
            .SetContentText(text)
            .SetPriority(NotificationCompat.PriorityHigh)
            .SetContentIntent(pendingIntent)
            .SetAutoCancel(true);

        var notification = notificationBuilder.Build();

        notificationManager.Notify(0, notification);
    }
}

#endif

Change from id to Item name

https://api.github.com/morning4coffe-dev/project-sbs/blob/67446d1f21d894c32f1aa2ca87e945dbb1e0ebfb/src/ProjectSBS/ProjectSBS/Services/Notification/NotificationService.cs#L121

        PendingIntent pendingIntent = PendingIntent.GetActivity(Android.App.Application.Context, 0, intent, PendingIntentFlags.Immutable);

        var channelId = id;
        var channelName = id; // TODO: Change from id to Item name
        var importance = NotificationImportance.High;

        var notificationChannel = new NotificationChannel(channelId, channelName, importance);

Switch to new Dialog on Dispatcher

await MainViewModel.Navigator.ShowMessageDialogAsync(

this,

title: _localizer?["Leave"] ?? "Really wanna leave?",

content: _localizer?["Dialog_Ok"] ?? "Really?",

buttons: new[]

{

new DialogAction(

Label: _localizer?["Ok"] ?? "Ok",

Action: () => { IsEditing = false; }),

new DialogAction(

Label: _localizer?["Cancel"] ?? "Cancel")

});

https://api.github.com/morning4coffe-dev/project-sbs/blob/bb1a0f4feb86841b9d6092fb387e7bb1d7b9bd6d/ProjectSBS/ProjectSBS/Presentation/Components/ItemDetailsViewModel.cs#L113

    private async Task<bool> Close()
    {
        if (IsEditing && !_isNew)
        {
            //TODO Switch to new Dialog on Dispatcher
            //await MainViewModel.Navigator.ShowMessageDialogAsync(
            //    this,
            //    title: _localizer?["Leave"] ?? "Really wanna leave?",
            //    content: _localizer?["Dialog_Ok"] ?? "Really?",
            //    buttons: new[]
            //    {
            //        new DialogAction(
            //            Label: _localizer?["Ok"] ?? "Ok",
            //            Action: () => { IsEditing = false; }),
            //        new DialogAction(
            //            Label: _localizer?["Cancel"] ?? "Cancel")
            //    });

            return false;
        }

        WeakReferenceMessenger.Default.Send(new ItemUpdated(SelectedItem, Canceled: true));

        return true;
    }

    private async Task Save()
    {
        WeakReferenceMessenger.Default.Send(new ItemUpdated(SelectedItem, ToSave: true));
    }

    private async Task DeleteItem()

improvements to Android (pobably on Uno site)

https://api.github.com/morning4coffe-dev/project-sbs/blob/eb2e3fb54ca95468f4058c6b18678cf3be478426/ProjectSBS/ProjectSBS/Presentation/Components/HomePage.xaml#L41

<Page x:Class="ProjectSBS.Presentation.Components.HomePage"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:local="using:ProjectSBS.Presentation.Components"
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      xmlns:not_win="http://uno.ui/not_win"
      mc:Ignorable="d not_win android"
      xmlns:uen="using:Uno.Extensions.Navigation.UI"
      xmlns:utu="using:Uno.Toolkit.UI"
      xmlns:not_android="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:android="http://uno.ui/android"
      xmlns:models="using:ProjectSBS.Business.Models"
      NavigationCacheMode="Required">
    <!--not_win:Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">-->

    <Grid RowSpacing="20">
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            <RowDefinition Height="1.8*" />
        </Grid.RowDefinitions>

        <Border CornerRadius="16"
                Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">

        </Border>

        <GridView ItemsSource="{x:Bind Items, Mode=TwoWay}"
                  SelectedItem="{x:Bind SelectedItem, Mode=TwoWay}"
                  android:SelectionMode="None"
                  Grid.Row="1">

            <GridView.Resources>
                <FontIconSource x:Key="ReplyAllIcon"
                                Glyph="&#xE8C2;" />
                <FontIconSource x:Key="ReadIcon"
                                Glyph="&#xE8C3;" />
                <FontIconSource x:Key="DeleteIcon"
                                Glyph="&#xE74D;" />

                <!--TODO improvements to Android (pobably on Uno site)-->
                <SwipeItems x:Key="left"
                            Mode="Reveal">
                    <SwipeItem Text="Reply All"
                               IconSource="{StaticResource ReplyAllIcon}" />
                    <SwipeItem Text="Open"
                               IconSource="{StaticResource ReadIcon}" />
                </SwipeItems>
                <SwipeItems x:Key="right"
                            Mode="Execute">
                    <SwipeItem Text="Delete"
                               IconSource="{StaticResource DeleteIcon}"
                               Foreground="White"
                               Background="#FFF4B183" />
                    <!--Invoked="SwipeItem_Invoked"-->
                </SwipeItems>
            </GridView.Resources>

            <GridView.ItemsPanel>
                <ItemsPanelTemplate>
                    <ItemsWrapGrid MaximumRowsOrColumns="1"
                                   Orientation="Horizontal" />
                </ItemsPanelTemplate>
            </GridView.ItemsPanel>

            <GridView.ItemTemplate>
                <DataTemplate x:DataType="local:ItemViewModel">
                    <UserControl android:Height="100">
                        <!--android:Height="100"-->
                        <SwipeControl LeftItems="{StaticResource left}"
                                      RightItems="{StaticResource right}"
                                      Background="Transparent"
                                      android:CornerRadius="20"
                                      not_android:CornerRadius="8">

                            <Border android:Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
                                    android:CornerRadius="20">

                                <Grid Padding="20">
                                    <!--<Background="{x:Bind Tag.Color, Converter={StaticResource BackgroundColorConverter}}"
                                      not_android:Background="{StaticResource LayerOnMicaBaseAltFillColorDefault}"
                                      android:CornerRadius="20"
                                      not_android:CornerRadius="8">-->
                                    <Grid.ColumnDefinitions>
                                        <ColumnDefinition Width="*" />
                                        <ColumnDefinition Width="*" />
                                    </Grid.ColumnDefinitions>

                                    <StackPanel Spacing="12">
                                        <!--<not_android:Border Background="{x:Bind TagId, Converter={StaticResource ColorConverter}}"
                                                            CornerRadius="4"
                                                            Height="6"
                                                            Width="80"
                                                            HorizontalAlignment="Left" />-->
                                        <!--android:Foreground="{x:Bind Tag.Color, Converter={StaticResource ColorConverter}}"-->
                                        <TextBlock Text="{x:Bind Item.Name}"
                                                   Style="{StaticResource SubtitleTextBlockStyle}" />
                                        <TextBlock Text="in 6 days" />
                                    </StackPanel>

                                    <StackPanel Grid.Column="1"
                                                Spacing="12"
                                                HorizontalAlignment="Right"
                                                VerticalAlignment="Center">
                                        <TextBlock Style="{StaticResource SubtitleTextBlockStyle}"
                                                   HorizontalAlignment="Right">
                                        
                                        <Run Text="{x:Bind Item.Billing.BasePrice}" />
                                        <Run Text="CZK" />
                                        </TextBlock>
                                        <TextBlock Text="255,00 CZK / 2m"
                                                   HorizontalAlignment="Right" />
                                    </StackPanel>
                                </Grid>
                            </Border>
                        </SwipeControl>
                    </UserControl>
                </DataTemplate>
            </GridView.ItemTemplate>
        </GridView>

        <Button CornerRadius="22"
                Height="65"
                VerticalAlignment="Bottom"
                HorizontalAlignment="Right"
                ToolTipService.ToolTip="Add new"
                Style="{StaticResource AccentButtonStyle}"
                Command="{Binding AddNewCommand}"
                Grid.Row="1"
                Padding="22,0">

            <!--TODO Rotate this icon - previous project Easter Egg-->
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="auto" />
                    <ColumnDefinition Width="auto" />
                </Grid.ColumnDefinitions>
                <SymbolIcon x:Name="fabIcon"
                            Symbol="Add" />

                <TextBlock Text="Add"
                           Grid.Column="1"
                           Margin="12,0,0,0" />
            </Grid>
        </Button>
    </Grid>
</Page>

Add proper Selectors for FilterCategories

https://api.github.com/morning4coffe-dev/project-sbs/blob/8f6a69d55dea283f3efebb8899a982469ce0821e/ProjectSBS/ProjectSBS/Services/Items/Tags/TagService.cs#L11

๏ปฟusing System.Drawing;

namespace ProjectSBS.Services.Items.Tags;

public class TagService : ITagService
{
    public List<Tag> Tags { get; }

    public TagService(IStringLocalizer localizer)
    {
        //TODO: Add proper Selectors for FilterCategories
        Tags = new()
        {
           new Tag(localizer["Welcome"], Color.FromName("Red")),
           new Tag(localizer["Welcome"], Color.FromName("Red")),
           new Tag(localizer["Welcome"], Color.FromName("Red"))
        };
    }
}

Black blackground

https://api.github.com/morning4coffe-dev/project-sbs/blob/350185b311410abd6e7dfa92aee5824d21892c27/ProjectSBS/ProjectSBS/Presentation/NestedPages/HomePage.xaml#L18

๏ปฟ<Page x:Class="ProjectSBS.Presentation.NestedPages.HomePage"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:local="using:ProjectSBS.Presentation.NestedPages"
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      xmlns:comp="using:ProjectSBS.Presentation.Components"
      xmlns:muxc="using:Microsoft.UI.Xaml.Controls"
      xmlns:toolkit="using:CommunityToolkit.WinUI"
      xmlns:controls="using:CommunityToolkit.WinUI.Controls"
      xmlns:animations="using:CommunityToolkit.WinUI.Animations"
      xmlns:triggers="using:CommunityToolkit.WinUI.UI.Triggers"
      xmlns:not_win="http://uno.ui/not_win"
      xmlns:not_android="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:android="http://uno.ui/android"
      mc:Ignorable="d not_win android">

    <!--TODO Black blackground-->

    <Border>

        <VisualStateManager.VisualStateGroups>

            <!--Pane Opening DetailsPane-->
            <VisualStateGroup>
                <VisualState>
                    <VisualState.StateTriggers>
                        <triggers:IsNullOrEmptyStateTrigger Value="{x:Bind ViewModel.SelectedItem, Mode=OneWay}" />
                    </VisualState.StateTriggers>
                    <VisualState.Setters>
                        <Setter Target="pane2Holder.Visibility"
                                Value="Collapsed" />
                        <Setter Target="RootPanel.PanePriority"
                                Value="Pane1" />
                        <Setter Target="RootPanel.Pane2Length"
                                Value="0" />
                    </VisualState.Setters>
                </VisualState>
            </VisualStateGroup>

            <!--Pane1 Empty Image-->
            <VisualStateGroup>
                <VisualState />
                <VisualState>
                    <VisualState.StateTriggers>
                        <triggers:IsNullOrEmptyStateTrigger Value="{x:Bind ViewModel.Items, Mode=OneWay}" />
                    </VisualState.StateTriggers>
                    <VisualState.Setters>
                        <Setter Target="itemsEmptyImage.Visibility"
                                Value="Visible" />
                    </VisualState.Setters>
                </VisualState>
            </VisualStateGroup>

            <VisualStateGroup>
                <VisualState>
                    <VisualState.StateTriggers>
                        <AdaptiveTrigger MinWindowWidth="0" />

                    </VisualState.StateTriggers>

                    <VisualState.Setters>
                        <Setter Target="mobileAddButton.Visibility"
                                Value="Visible" />
                        <Setter Target="contentHolder.Margin"
                                Value="8,8,8,0" />
                    </VisualState.Setters>
                </VisualState>

                <VisualState>
                    <VisualState.StateTriggers>
                        <AdaptiveTrigger MinWindowWidth="640" />
                    </VisualState.StateTriggers>
                </VisualState>
            </VisualStateGroup>

        </VisualStateManager.VisualStateGroups>

        <muxc:TwoPaneView x:Name="RootPanel"
                          Pane1Length="*"
                          Pane2Length="350"
                          PanePriority="Pane2"
                          TallModeConfiguration="SinglePane"
                          WideModeConfiguration="LeftRight">

            <muxc:TwoPaneView.Pane1>
                <Grid x:Name="contentHolder"
                      Margin="48,48,48,0">
                    <StackPanel Spacing="16">

                        <TextBlock Text="Home"
                                   Style="{StaticResource TitleTextBlockStyle}" />

                        <Button x:Name="desktopAddButton"
                                Visibility="Visible"
                                Content="New Item" />

                        <ScrollViewer>
                            <controls:Segmented SelectedIndex="0">
                                <controls:SegmentedItem Content="Item 1"
                                                        Icon="{toolkit:FontIcon Glyph=&#xEA3A;}" />
                                <controls:SegmentedItem Content="Item 2"
                                                        Icon="{toolkit:FontIcon Glyph=&#xEA3A;}" />
                                <controls:SegmentedItem Content="Item 3 with a long label"
                                                        Icon="{toolkit:FontIcon Glyph=&#xEA3A;}" />
                                <controls:SegmentedItem Content="Item 4"
                                                        Icon="{toolkit:FontIcon Glyph=&#xEA3A;}" />
                            </controls:Segmented>
                        </ScrollViewer>

                        <comp:StatsBanner />

                        <Image x:Name="itemsEmptyImage"
                               Visibility="Collapsed"
                               Source="/ProjectSBS/Assets/Illustrations/undraw_Newsletter_re_wrob.png" />

                        <GridView x:Name="MainList"
                                  ScrollViewer.VerticalScrollBarVisibility="Disabled"
                                  ItemsSource="{x:Bind ViewModel.Items, Mode=OneWay}"
                                  SelectedItem="{x:Bind ViewModel.SelectedItem, Mode=TwoWay}"
                                  Grid.Row="3">

                            <GridView.Resources>
                                <FontIconSource x:Key="EditIcon"
                                                Glyph="&#xE70F;" />
                                <FontIconSource x:Key="ArchiveIcon"
                                                Glyph="&#xE7B8;" />
                                <FontIconSource x:Key="DeleteIcon"
                                                Glyph="&#xE74D;" />

                                <SwipeItems x:Key="Left"
                                            Mode="Reveal">
                                    <SwipeItem Text="Edit"
                                               Background="Transparent"
                                               IconSource="{StaticResource EditIcon}"
                                               Invoked="EditItem_Invoked" />
                                    <SwipeItem Text="Archive"
                                               Background="Transparent"
                                               IconSource="{StaticResource ArchiveIcon}"
                                               Invoked="ArchiveItem_Invoked" />
                                </SwipeItems>
                                <SwipeItems x:Key="Right"
                                            Mode="Execute">
                                    <SwipeItem Text="Delete"
                                               IconSource="{StaticResource DeleteIcon}"
                                               Foreground="{StaticResource TextOnAccentFillColorPrimaryBrush}"
                                               Background="{StaticResource SystemAccentColor}"
                                               Invoked="DeleteItem_Invoked" />
                                </SwipeItems>
                            </GridView.Resources>

                            <GridView.ItemsPanel>
                                <ItemsPanelTemplate>
                                    <ItemsWrapGrid MaximumRowsOrColumns="1"
                                                   Orientation="Horizontal" />
                                </ItemsPanelTemplate>
                            </GridView.ItemsPanel>

                            <GridView.ItemTemplate>
                                <DataTemplate x:DataType="comp:ItemViewModel">
                                    <UserControl android:Height="100">
                                        <SwipeControl LeftItems="{StaticResource Left}"
                                                      RightItems="{StaticResource Right}"
                                                      Background="Transparent"
                                                      android:CornerRadius="20"
                                                      not_android:CornerRadius="8">

                                            <Border android:Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
                                                    android:CornerRadius="20">

                                                <Grid Padding="20">
                                                    <Grid.ColumnDefinitions>
                                                        <ColumnDefinition Width="*" />
                                                        <ColumnDefinition Width="*" />
                                                    </Grid.ColumnDefinitions>

                                                    <StackPanel Spacing="12">
                                                        <TextBlock Text="{x:Bind Item.Name, Mode=OneWay}"
                                                                   Style="{StaticResource SubtitleTextBlockStyle}" />
                                                        <TextBlock Text="in 6 days" />
                                                    </StackPanel>

                                                    <StackPanel Grid.Column="1"
                                                                Spacing="12"
                                                                HorizontalAlignment="Right"
                                                                VerticalAlignment="Center">
                                                        <TextBlock Style="{StaticResource SubtitleTextBlockStyle}"
                                                                   HorizontalAlignment="Right">
                                        
                                                            <Run Text="{x:Bind Item.Billing.BasePrice, Mode=OneWay}" />
                                                            <Run Text="{x:Bind Item.Billing.CurrencyId, Mode=OneWay}" />
                                                        </TextBlock>
                                                        <TextBlock Text="255,00 CZK / 2m"
                                                                   HorizontalAlignment="Right" />
                                                    </StackPanel>
                                                </Grid>
                                            </Border>
                                        </SwipeControl>
                                    </UserControl>
                                </DataTemplate>
                            </GridView.ItemTemplate>
                        </GridView>

                    </StackPanel>

                    <Button x:Name="mobileAddButton"
                            CornerRadius="22"
                            Height="65"
                            VerticalAlignment="Bottom"
                            HorizontalAlignment="Right"
                            ToolTipService.ToolTip="Add new"
                            Style="{StaticResource AccentButtonStyle}"
                            Grid.Row="2"
                            Visibility="Collapsed"
                            Margin="20"
                            Padding="22,0">
                        <!--Command="{x:Bind AddNewCommand, Mode=TwoWay}"-->

                        <!--TODO Rotate this icon - previous project Easter Egg-->
                        <Grid>
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="auto" />
                                <ColumnDefinition Width="auto" />
                            </Grid.ColumnDefinitions>
                            <SymbolIcon x:Name="fabIcon"
                                        Symbol="Add" />

                            <TextBlock Text="Add"
                                       Grid.Column="1"
                                       Margin="12,0,0,0" />
                        </Grid>
                    </Button>
                </Grid>

            </muxc:TwoPaneView.Pane1>

            <muxc:TwoPaneView.Pane2>
                <StackPanel x:Name="pane2Holder"
                            Visibility="Visible">
                    <!--<animations:Implicit.ShowAnimations>
                        <animations:TranslationAnimation From="0, -200, 0"
                                                         To="0"
                                                         Duration="0:0:1" />
                        <animations:OpacityAnimation From="0"
                                                     To="1.0"
                                                     Duration="0:0:1" />
                    </animations:Implicit.ShowAnimations>-->

                    <comp:ItemDetails />

                </StackPanel>
            </muxc:TwoPaneView.Pane2>

        </muxc:TwoPaneView>

    </Border>
</Page>

Does not propperly work

https://api.github.com/morning4coffe-dev/project-sbs/blob/7efed74b6abaab29e05bd593d1e7a0f2c462778f/ProjectSBS/ProjectSBS/Presentation/Components/ItemDetails.xaml#L47

                    Value="16" />
        </Style>
    </Page.Resources>
    <VisualStateManager.VisualStateGroups>
        <!--AdaptiveTriggers-->
        <VisualStateGroup>
            <VisualState>
                <VisualState.StateTriggers>
                    <AdaptiveTrigger MinWindowWidth="0" />
                </VisualState.StateTriggers>

                <VisualState.Setters>
                    <Setter Target="pageShell.Visibility"
                            Value="Visible" />
                    <Setter Target="desktopHeader.Visibility"
                            Value="Collapsed" />
                </VisualState.Setters>
            </VisualState>

            <VisualState>
                <VisualState.StateTriggers>
                    <AdaptiveTrigger MinWindowWidth="640" />
                </VisualState.StateTriggers>
            </VisualState>
        </VisualStateGroup>
        
        <!--TODO Does not propperly work-->

    </VisualStateManager.VisualStateGroups>

    <comp:PageShell x:Name="pageShell"
                    MobileTitleVisibility="Visible">
        <comp:PageShell.ContentView>

            <UserControl>
                <Grid>
                    <Grid.RowDefinitions>
                        <RowDefinition Height="Auto" />
                        <RowDefinition Height="*" />
                    </Grid.RowDefinitions>

                    <Grid x:Name="desktopHeader"
                          Visibility="Visible">
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition />
                            <ColumnDefinition Width="Auto" />
                        </Grid.ColumnDefinitions>

                        <TextBlock Text="Details"
                                   Style="{StaticResource TitleTextBlockStyle}" />

                        <StackPanel Orientation="Horizontal"
                                    Spacing="8"
                                    Grid.Column="1">
                            <Button Visibility="{x:Bind ViewModel.IsEditing, Mode=OneWay, Converter={StaticResource FalseToVisibleConverter}}"
                                    Command="{Binding EnableEditingCommand}"
                                    Padding="12, 8">
                                <FontIcon Glyph="&#xE70F;"
                                          FontSize="14" />
                            </Button>

                            <Button Visibility="{x:Bind ViewModel.IsEditing, Mode=OneWay, Converter={StaticResource FalseToVisibleConverter}}"
                                    Command="{x:Bind ViewModel.DeleteCommand}"
                                    Padding="12, 8">
                                <FontIcon Glyph="&#xE74D;"
                                          FontSize="14" />
                            </Button>

                            <Button Command="{x:Bind ViewModel.CloseCommand}"
                                    Padding="12, 8">
                                <FontIcon Glyph="&#xE8BB;"
                                          FontSize="14" />
                            </Button>
                        </StackPanel>
                    </Grid>

                    <ScrollViewer Visibility="{x:Bind ViewModel.IsEditing, Mode=OneWay, Converter={StaticResource FalseToVisibleConverter}}"
                                  Grid.Row="1">
                        <StackPanel Spacing="8"
                                    Margin="8">
                            <Border Style="{StaticResource DetailsBorder}">

Rotate this icon - previous project Easter Egg

https://api.github.com/morning4coffe-dev/project-sbs/blob/eb2e3fb54ca95468f4058c6b18678cf3be478426/ProjectSBS/ProjectSBS/Presentation/Components/HomePage.xaml#L132

<Page x:Class="ProjectSBS.Presentation.Components.HomePage"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:local="using:ProjectSBS.Presentation.Components"
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      xmlns:not_win="http://uno.ui/not_win"
      mc:Ignorable="d not_win android"
      xmlns:uen="using:Uno.Extensions.Navigation.UI"
      xmlns:utu="using:Uno.Toolkit.UI"
      xmlns:not_android="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:android="http://uno.ui/android"
      xmlns:models="using:ProjectSBS.Business.Models"
      NavigationCacheMode="Required">
    <!--not_win:Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">-->

    <Grid RowSpacing="20">
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            <RowDefinition Height="1.8*" />
        </Grid.RowDefinitions>

        <Border CornerRadius="16"
                Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">

        </Border>

        <GridView ItemsSource="{x:Bind Items, Mode=TwoWay}"
                  SelectedItem="{x:Bind SelectedItem, Mode=TwoWay}"
                  android:SelectionMode="None"
                  Grid.Row="1">

            <GridView.Resources>
                <FontIconSource x:Key="ReplyAllIcon"
                                Glyph="&#xE8C2;" />
                <FontIconSource x:Key="ReadIcon"
                                Glyph="&#xE8C3;" />
                <FontIconSource x:Key="DeleteIcon"
                                Glyph="&#xE74D;" />

                <!--TODO improvements to Android (pobably on Uno site)-->
                <SwipeItems x:Key="left"
                            Mode="Reveal">
                    <SwipeItem Text="Reply All"
                               IconSource="{StaticResource ReplyAllIcon}" />
                    <SwipeItem Text="Open"
                               IconSource="{StaticResource ReadIcon}" />
                </SwipeItems>
                <SwipeItems x:Key="right"
                            Mode="Execute">
                    <SwipeItem Text="Delete"
                               IconSource="{StaticResource DeleteIcon}"
                               Foreground="White"
                               Background="#FFF4B183" />
                    <!--Invoked="SwipeItem_Invoked"-->
                </SwipeItems>
            </GridView.Resources>

            <GridView.ItemsPanel>
                <ItemsPanelTemplate>
                    <ItemsWrapGrid MaximumRowsOrColumns="1"
                                   Orientation="Horizontal" />
                </ItemsPanelTemplate>
            </GridView.ItemsPanel>

            <GridView.ItemTemplate>
                <DataTemplate x:DataType="local:ItemViewModel">
                    <UserControl android:Height="100">
                        <!--android:Height="100"-->
                        <SwipeControl LeftItems="{StaticResource left}"
                                      RightItems="{StaticResource right}"
                                      Background="Transparent"
                                      android:CornerRadius="20"
                                      not_android:CornerRadius="8">

                            <Border android:Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
                                    android:CornerRadius="20">

                                <Grid Padding="20">
                                    <!--<Background="{x:Bind Tag.Color, Converter={StaticResource BackgroundColorConverter}}"
                                      not_android:Background="{StaticResource LayerOnMicaBaseAltFillColorDefault}"
                                      android:CornerRadius="20"
                                      not_android:CornerRadius="8">-->
                                    <Grid.ColumnDefinitions>
                                        <ColumnDefinition Width="*" />
                                        <ColumnDefinition Width="*" />
                                    </Grid.ColumnDefinitions>

                                    <StackPanel Spacing="12">
                                        <!--<not_android:Border Background="{x:Bind TagId, Converter={StaticResource ColorConverter}}"
                                                            CornerRadius="4"
                                                            Height="6"
                                                            Width="80"
                                                            HorizontalAlignment="Left" />-->
                                        <!--android:Foreground="{x:Bind Tag.Color, Converter={StaticResource ColorConverter}}"-->
                                        <TextBlock Text="{x:Bind Item.Name}"
                                                   Style="{StaticResource SubtitleTextBlockStyle}" />
                                        <TextBlock Text="in 6 days" />
                                    </StackPanel>

                                    <StackPanel Grid.Column="1"
                                                Spacing="12"
                                                HorizontalAlignment="Right"
                                                VerticalAlignment="Center">
                                        <TextBlock Style="{StaticResource SubtitleTextBlockStyle}"
                                                   HorizontalAlignment="Right">
                                        
                                        <Run Text="{x:Bind Item.Billing.BasePrice}" />
                                        <Run Text="CZK" />
                                        </TextBlock>
                                        <TextBlock Text="255,00 CZK / 2m"
                                                   HorizontalAlignment="Right" />
                                    </StackPanel>
                                </Grid>
                            </Border>
                        </SwipeControl>
                    </UserControl>
                </DataTemplate>
            </GridView.ItemTemplate>
        </GridView>

        <Button CornerRadius="22"
                Height="65"
                VerticalAlignment="Bottom"
                HorizontalAlignment="Right"
                ToolTipService.ToolTip="Add new"
                Style="{StaticResource AccentButtonStyle}"
                Command="{Binding AddNewCommand}"
                Grid.Row="1"
                Padding="22,0">

            <!--TODO Rotate this icon - previous project Easter Egg-->
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="auto" />
                    <ColumnDefinition Width="auto" />
                </Grid.ColumnDefinitions>
                <SymbolIcon x:Name="fabIcon"
                            Symbol="Add" />

                <TextBlock Text="Add"
                           Grid.Column="1"
                           Margin="12,0,0,0" />
            </Grid>
        </Button>
    </Grid>
</Page>

Test this function properly

https://api.github.com/morning4coffe-dev/project-sbs/blob/67446d1f21d894c32f1aa2ca87e945dbb1e0ebfb/src/ProjectSBS/ProjectSBS/Services/Notification/NotificationService.cs#L65

        throw new Exception("Desired time was set in the past.");
    }

    // TODO: Test this function properly
    public override void RemoveScheduledNotifications(string id)
    {
        Intent notificationIntent = new(Android.App.Application.Context, typeof(NotificationReceiver));

        notificationIntent.PutExtra("id", id);

        int requestCode = 0; // You should use the same requestCode that you used when scheduling the notification

        PendingIntent pendingIntent = PendingIntent.GetBroadcast(Android.App.Application.Context, requestCode, notificationIntent, PendingIntentFlags.Immutable);

        if (pendingIntent != null)
        {
            AlarmManager alarmManager = (AlarmManager)Android.App.Application.Context.GetSystemService(Context.AlarmService);
            alarmManager.Cancel(pendingIntent);
            pendingIntent.Cancel();
        }
    }

    public override void ShowBasicToastNotification(string title, string description)

Foreground of StatusBar

https://api.github.com/morning4coffe-dev/project-sbs/blob/eb2e3fb54ca95468f4058c6b18678cf3be478426/ProjectSBS/ProjectSBS/Presentation/Components/PageShell.xaml#L12

๏ปฟ<Page x:Class="ProjectSBS.Presentation.Components.PageShell"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:local="using:ProjectSBS.Presentation.Components"
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      xmlns:win="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:utu="using:Uno.Toolkit.UI"
      utu:StatusBar.Foreground="Auto"
      utu:StatusBar.Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
      mc:Ignorable="d">
      <!--TODO Foreground of StatusBar-->

    <Grid utu:SafeArea.Insets="All">
        <Grid.RowDefinitions>

Issue with AOT on .NET 7 with Graph and Toolkit

https://api.github.com/morning4coffe-dev/project-sbs/blob/d32f2b662ab08ba9b8fb26db0b9da6325ac926a5/ProjectSBS/ProjectSBS.Mobile/ProjectSBS.Mobile.csproj#L24

		<UseInterpreter Condition="'$(Configuration)' == 'Debug' and $([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) != 'maccatalyst'">True</UseInterpreter>
		<IsUnoHead>true</IsUnoHead>
	</PropertyGroup>
	
	<!--TODO Issue with AOT on .NET 7 with Graph and Toolkit-->
	<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|net7.0-android|AnyCPU'">
		<RunAOTCompilation>False</RunAOTCompilation>
		<AndroidEnableProfiledAot>False</AndroidEnableProfiledAot>
	</PropertyGroup>

	<!--<ItemGroup>
		<TrimmableAssembly Include="Microsoft.Graph" />
		<TrimmableAssembly Include="CommunityToolkit.WinUI" />
	</ItemGroup>-->

	<ItemGroup>
		<PackageReference Include="CommunityToolkit.Labs.WinUI.SettingsControls" />
		<PackageReference Include="CommunityToolkit.WinUI.Converters" />
		<PackageReference Include="CommunityToolkit.WinUI.Triggers" />
		<PackageReference Include="LiveChartsCore.SkiaSharpView.Uno.WinUI" />
		<PackageReference Include="Microsoft.AppCenter" />
		<PackageReference Include="Microsoft.AppCenter.Analytics" />
		<PackageReference Include="Microsoft.AppCenter.Crashes" />
		<PackageReference Include="Uno.WinUI" />
		<PackageReference Include="CommunityToolkit.Mvvm" />
		<PackageReference Include="Uno.Extensions.Configuration" />

Log if null

https://api.github.com/morning4coffe-dev/project-sbs/blob/350185b311410abd6e7dfa92aee5824d21892c27/ProjectSBS/ProjectSBS/Presentation/Components/ItemDetails.xaml.cs#L9

๏ปฟnamespace ProjectSBS.Presentation.Components;

public sealed partial class ItemDetails : Page
{
    public ItemDetails()
    {
        this.InitializeComponent();

        //TODO Log if null
        this.DataContext = (Application.Current as App)!.Host?.Services.GetService<ItemDetailsViewModel>()!;
    }

    public ItemDetailsViewModel ViewModel => (ItemDetailsViewModel)DataContext;
}

Ask to save?

https://api.github.com/morning4coffe-dev/project-sbs/blob/350185b311410abd6e7dfa92aee5824d21892c27/ProjectSBS/ProjectSBS/Presentation/Components/ItemDetailsViewModel.cs#L99

using CommunityToolkit.Mvvm.Messaging;
using ProjectSBS.Business;
using ProjectSBS.Services.Items;
using ProjectSBS.Services.Items.Tags;
using System.Collections.ObjectModel;
using Windows.UI.Core;

namespace ProjectSBS.Presentation.Components;

public partial class ItemDetailsViewModel : ObservableObject
{
    private INavigator _navigator;
    private IDispatcher _dispatch;

    [ObservableProperty]
    private ItemViewModel? _selectedItem;

    [ObservableProperty]
    private string _itemName = "";

    [ObservableProperty]
    private bool _isEditing = false;

    public ObservableCollection<Tag> Tags { get; }

    [ObservableProperty]
    public Dictionary<string, double> _currencies = new();

    public string SaveText { get; }
    public string EditText { get; }

    public ObservableCollection<ItemViewModel> Items { get; } = new();

    public ICommand EnableEditingCommand { get; }
    public ICommand CloseCommand { get; }

    public ItemDetailsViewModel(
        INavigator navigator,
        IDispatcher dispatch,
        IStringLocalizer localizer,
        ITagService tagService,
        ICurrencyCache currencyCache,
        IItemService itemService)
    {
        _navigator = navigator;
        _dispatch = dispatch;

        EnableEditingCommand = new AsyncRelayCommand(EnableEditing);
        CloseCommand = new AsyncRelayCommand(Close);

        SaveText = localizer["Save"];
        EditText = localizer["Edit"];

        WeakReferenceMessenger.Default.Register<ItemSelectionChanged>(this, (r, m) =>
        {
#if HAS_UNO
            SystemNavigationManager.GetForCurrentView().BackRequested += System_BackRequested;
#endif
            if (m.SelectedItem is { } item)
            {
                SelectedItem = item;
            }
        });

        Tags = tagService.Tags.ToObservableCollection();

        InitializeCurrency(currencyCache);

        ItemName = SelectedItem?.Item?.Name ?? "New Item";

        //Done after init of itemService
        Items = itemService.GetItems().ToObservableCollection();
    }

    private async void InitializeCurrency(ICurrencyCache currencyCache)
    {
        var currency = await currencyCache.GetCurrency(CancellationToken.None);

        if (currency?.Rates.Count == 0)
        {
            return;
        }

        await MainViewModel.Dispatch.ExecuteAsync(() =>
        {
            Currencies.AddRange(currency.Rates);
        });
    }


    private async Task EnableEditing()
    {
        IsEditing = true;
    }

    private async Task Close()
    {
        //await _navigator.ShowMessageDialogAsync(this, title: "...", content: "Really?");
        //TODO Ask to save?

        WeakReferenceMessenger.Default.Send(new ItemUpdated(SelectedItem));
    }
    private void System_BackRequested(object? sender, BackRequestedEventArgs e)
    {
        e.Handled = true;
        _ = Close();
        SystemNavigationManager.GetForCurrentView().BackRequested -= System_BackRequested;
    }
}

Add proper Selectors for FilterCategories

https://api.github.com/morning4coffe-dev/project-sbs/blob/8f6a69d55dea283f3efebb8899a982469ce0821e/ProjectSBS/ProjectSBS/Services/Items/Filtering/ItemFilterService.cs#L9

๏ปฟnamespace ProjectSBS.Services.Items.Filtering;

public class ItemFilterService : IItemFilterService
{
    public List<FilterCategory> Categories { get; }

    public ItemFilterService(IStringLocalizer localizer)
    {
        //TODO: Add proper Selectors for FilterCategories
        Categories = new()
        {
            new(localizer["Home"], "\uE80F"),
            new(localizer["Upcoming"], "\uE752", i => !i.IsPaid),
            new(localizer["Overdue"], "\uEC92", i => i.Item.Name is "Sample Item 1"),
            new(localizer["Expensive"], "๐Ÿฅฉ", i => i.Item.Billing.BasePrice > 50),
        };
    }
}

Test this function properly

https://api.github.com/morning4coffe-dev/project-sbs/blob/eb2e3fb54ca95468f4058c6b18678cf3be478426/ProjectSBS/ProjectSBS/Services/Notification/NotificationService.cs#L95

๏ปฟ#if __ANDROID__

using Android;
using Android.App;
using Android.Content;
using Android.Content.PM;
using Android.OS;
using Android.Support.V4.App;
using Android.Util;
using Android.Views;
using AndroidX.Core.App;
using AndroidX.Lifecycle;
using System;

namespace ProjectSBS.Services.Notifications;

// TODO: Rename to AndroidNotificationService
public class NotificationService : NotificationServiceBase
{
    private Context context = Android.App.Application.Context;

    public NotificationService()
    {
    }

    public override bool IsEnabledOnDevice()
    {
        if (Build.VERSION.SdkInt >= BuildVersionCodes.N)
        {
            //TODO Returns true on all
            //return true;
        }

        return NotificationManagerCompat.From(context).AreNotificationsEnabled();
    }

    public override void ShowInAppNotification(string notification, bool autoHide)
    {
        InvokeInAppNotificationRequested(new InAppNotificationRequestedEventArgs
        {
            NotificationText = notification,
            NotificationTime = autoHide ? 1500 : 0
        });
    }

    public override async void ScheduleNotification(string id, string title, string text, DateOnly day, TimeOnly time)
    {
        if (!IsEnabledOnDevice())
        {
            //TODO make this not only for not scheduled notifications  
            var current = await ApplicationActivity.GetCurrent(new CancellationToken());
            if (ActivityCompat.CheckSelfPermission(current, Manifest.Permission.PostNotifications) != Android.Content.PM.Permission.Granted)
            {
                ActivityCompat.RequestPermissions(current, new string[] { Manifest.Permission.PostNotifications }, 1);
            }
        }

        id = Guid.NewGuid().ToString();

        DateTime notificationDateTime = new(day.Year, day.Month, day.Day, time.Hour, time.Minute, time.Second);
        long totalMilliSeconds = (long)(notificationDateTime.ToUniversalTime() - DateTime.Now).TotalMilliseconds;

        text += $" Scheduled for {notificationDateTime}";

        var (manager, intent) = CreateAlarm(id, title, text, notificationDateTime);

        GetAlarm();

        manager.SetExact(AlarmType.ElapsedRealtime, totalMilliSeconds, intent);
    }

    private (AlarmManager, PendingIntent) CreateAlarm(string id, string title, string text, DateTime notificationDateTime)
    {
        if (notificationDateTime > DateTime.Now)
        {
            Intent notificationIntent = new(context, typeof(NotificationReceiver));
            notificationIntent.PutExtra("id", id);
            notificationIntent.PutExtra("title", title);
            notificationIntent.PutExtra("text", text);

            var random = new Random();
            int requestCode = random.Next(0, 5000); // TODO: ID here You can convert the id to an integer for the requestCode

            PendingIntent pendingIntent = PendingIntent.GetBroadcast(context, requestCode, notificationIntent, PendingIntentFlags.Immutable);

            AlarmManager alarmManager = (AlarmManager)context.GetSystemService(Context.AlarmService);

            return (alarmManager, pendingIntent);
        }

        //TODO throw new Exception("Desired time was set in the past.");
        return (null, null);
    }

    // TODO: Test this function properly
    public override void RemoveScheduledNotifications(string id)
    {
        Intent notificationIntent = new(context, typeof(NotificationReceiver));
        notificationIntent.PutExtra("id", id);

        var random = new Random();
        int requestCode = random.Next(0, 5000); // TODO: ID here You can convert the id to an integer for the requestCode

        PendingIntent pendingIntent = PendingIntent.GetBroadcast(context, requestCode, notificationIntent, PendingIntentFlags.Immutable);

        AlarmManager alarmManager = (AlarmManager)context.GetSystemService(Context.AlarmService);

        alarmManager.Cancel(pendingIntent);
    }

    private void GetAlarm() 
    {
        AlarmManager alarmManager = (AlarmManager)context.GetSystemService(Context.AlarmService);

        //var c = alarmManager.CanScheduleExactAlarms();
        //var d = alarmManager.NextAlarmClock;
    }

    public override void ShowBasicToastNotification(string title, string description)
    {
        var notificationManager = (NotificationManager)context.GetSystemService(Context.NotificationService);

        var channelId = "ProjectSBS-channel";
        var channelName = "Other";
        var importance = NotificationImportance.High;

        var notificationChannel = new NotificationChannel(channelId, channelName, importance);
        notificationManager.CreateNotificationChannel(notificationChannel);

        var notificationBuilder = new NotificationCompat.Builder(context, channelId)
            .SetSmallIcon(Resource.Drawable.abc_vector_test)
            .SetContentTitle(title)
            .SetContentText(description)
            .SetAutoCancel(true);

        var notification = notificationBuilder.Build();

        notificationManager.Notify(0, notification);
    }
}

[BroadcastReceiver(Enabled = true)]
public class NotificationReceiver : BroadcastReceiver
{
    public override void OnReceive(Context context, Intent intent)
    {
        string id = intent.GetStringExtra("id");
        string title = intent.GetStringExtra("title");
        string text = intent.GetStringExtra("text");

        var notificationManager = (NotificationManager)context.GetSystemService(Context.NotificationService);

        PendingIntent pendingIntent = PendingIntent.GetActivity(context, 0, intent, PendingIntentFlags.Immutable);

        var channelId = id;
        var channelName = id; // TODO: Change from id to Item name
        var importance = NotificationImportance.High;

        var notificationChannel = new NotificationChannel(channelId, channelName, importance);
        notificationManager.CreateNotificationChannel(notificationChannel);

        var notificationBuilder = new NotificationCompat.Builder(context, channelId)
            .SetSmallIcon(Resource.Drawable.abc_vector_test)
            .SetContentTitle(title)
            .SetContentText(text)
            .SetPriority(NotificationCompat.PriorityHigh)
            .SetContentIntent(pendingIntent)
            .SetAutoCancel(true);

        var notification = notificationBuilder.Build();

        notificationManager.Notify(0, notification);
    }
}

#endif

[Login UI] Doesn't switch on login

https://api.github.com/morning4coffe-dev/project-sbs/blob/1c086dc941ebcd831c7959e8a969480776da0a4c/ProjectSBS/ProjectSBS/Presentation/LoginPage.xaml#L56

                                <VisualState.StateTriggers>
                                    <AdaptiveTrigger MinWindowWidth="1000" />
                                </VisualState.StateTriggers>
                            </VisualState>
                        </VisualStateGroup>

                        <VisualStateGroup>
                            <VisualState>
                                <VisualState.StateTriggers>
                                    <triggers:IsNullOrEmptyStateTrigger Value="{Binding User, Mode=OneWay}" />
                                </VisualState.StateTriggers>
                                <VisualState.Setters>
                                    <!--TODO [Login UI] Doesn't switch on login-->
                                    <Setter Target="personPicture.Visibility"
                                            Value="Collapsed" />
                                    <Setter Target="secImage.Visibility"
                                            Value="Visible" />
                                </VisualState.Setters>
                            </VisualState>
                        </VisualStateGroup>

Make the Tags more safe for future versions, check for inner TagId

https://api.github.com/morning4coffe-dev/project-sbs/blob/350185b311410abd6e7dfa92aee5824d21892c27/ProjectSBS/ProjectSBS/Presentation/Components/ItemDetails.xaml#L104

            <Setter Property="Padding"
                    Value="16" />
        </Style>
    </Page.Resources>

    <comp:PageShell MobileTitleVisibility="Visible">
        <comp:PageShell.ContentView>

            <UserControl>
                <Grid>

                    <ScrollViewer Visibility="{Binding IsEditing, Mode=OneWay, Converter={StaticResource FalseToVisibleConverter}}">
                        <StackPanel Spacing="8">
                            <Border Style="{StaticResource DetailsBorder}">
                                <StackPanel Spacing="8">
                                    <TextBlock Text="{Binding SelectedItem.Item.Name, Mode=OneWay}"
                                               Style="{StaticResource SubtitleTextBlockStyle}"
                                               TextWrapping="WrapWholeWords" />
                                    <TextBlock TextWrapping="WrapWholeWords">
                                    <Run Text="{Binding SelectedItem.Item.Billing.BasePrice, Mode=OneWay}" />
                                    <Run Text="{Binding SelectedItem.Item.Billing.CurrencyId, Mode=OneWay}" />
                                    </TextBlock>
                                </StackPanel>
                            </Border>

                            <Border Style="{StaticResource DetailsBorder}">
                                <TextBlock Text="{Binding SelectedItem.Item.Description, Mode=OneWay}"
                                           TextWrapping="WrapWholeWords" />
                            </Border>

                            <Border Style="{StaticResource DetailsBorder}">
                                <ScrollViewer HorizontalScrollMode="Auto"
                                              HorizontalScrollBarVisibility="Auto"
                                              Margin="-16"
                                              Padding="16">
                                    <ItemsRepeater VerticalAlignment="Top"
                                                   ItemsSource="{Binding Items}">
                                        <ItemsRepeater.Layout>
                                            <StackLayout Orientation="Horizontal"
                                                         Spacing="8" />
                                        </ItemsRepeater.Layout>

                                        <ItemsRepeater.ItemTemplate>
                                            <DataTemplate x:DataType="local:ItemViewModel">
                                                <Grid Height="100"
                                                      Width="140"
                                                      CornerRadius="4"
                                                      Background="Blue">
                                                    <TextBlock Text="{x:Bind Item.Name}"
                                                               Padding="8" />
                                                </Grid>
                                            </DataTemplate>
                                        </ItemsRepeater.ItemTemplate>
                                    </ItemsRepeater>
                                </ScrollViewer>
                            </Border>
                        </StackPanel>
                    </ScrollViewer>

                    <Grid Visibility="{Binding IsEditing, Mode=TwoWay, Converter={StaticResource TrueToVisibleConverter}}"
                          RowSpacing="12">
                        <Grid.RowDefinitions>
                            <RowDefinition />
                            <RowDefinition Height="auto" />
                        </Grid.RowDefinitions>

                        <ScrollViewer>
                            <StackPanel Spacing="16">
                                <TextBox Header="Name"
                                         Text="{Binding SelectedItem.Item.Name, Mode=TwoWay}" />

                                <Grid ColumnSpacing="8">
                                    <Grid.ColumnDefinitions>
                                        <ColumnDefinition />
                                        <ColumnDefinition Width="auto" />
                                    </Grid.ColumnDefinitions>

                                    <NumberBox Header="Price"
                                               Value="{Binding SelectedItem.Item.Billing.BasePrice, Mode=TwoWay, Converter={StaticResource DecimalToDoubleConverter}}"
                                               SpinButtonPlacementMode="Inline" />
                                    <ComboBox VerticalAlignment="Bottom"
                                              ItemsSource="{Binding Currencies, Mode=OneWay}"
                                              SelectedItem="{Binding}"
                                              Grid.Column="1" />
                                </Grid>

                                <!--TODO Make the Tags more safe for future versions, check for inner TagId-->
                                <ComboBox Header="Tag"
                                          ItemsSource="{Binding Tags, Mode=OneTime}"
                                          SelectedIndex="{Binding SelectedItem.Item.TagId, Mode=TwoWay}"
                                          VerticalAlignment="Bottom">
                                    <ComboBox.ItemTemplate>
                                        <DataTemplate x:DataType="models:Tag">
                                            <StackPanel Orientation="Horizontal"
                                                        Spacing="12">
                                                <Rectangle Fill="{Binding Color, Converter={StaticResource ColorToBrushConverter}}"
                                                           Width="16"
                                                           Height="16"
                                                           RadiusX="4"
                                                           RadiusY="4" />
                                                <TextBlock Text="{Binding Name}" />
                                            </StackPanel>
                                        </DataTemplate>
                                    </ComboBox.ItemTemplate>
                                </ComboBox>
                                <CalendarDatePicker Header="Billing Date"
                                                    Date="{Binding SelectedItem.Item.Billing.InitialDate, Mode=TwoWay, Converter={StaticResource DateOnlyToDateTimeOffsetConverter}}"
                                                    HorizontalAlignment="Stretch" />
                                <Grid>
                                    <CheckBox Content="Notifications {NI}" />
                                    <FontIcon HorizontalAlignment="Right"
                                              Opacity="0.7"
                                              FontSize="18"
                                              Glyph="๏‰ฒ" />
                                </Grid>

                                <Grid>
                                    <CheckBox Content="Split between people {NI}" />
                                    <FontIcon HorizontalAlignment="Right"
                                              Opacity="0.7"
                                              FontSize="18"
                                              Glyph="๏„ณ" />
                                </Grid>
                                <TextBox Header="Description"
                                         Text="{Binding SelectedItem.Item.Description, Mode=TwoWay}"
                                         Height="120"
                                         TextWrapping="Wrap"
                                         AcceptsReturn="True" />
                            </StackPanel>
                        </ScrollViewer>

                        <Button Content="{Binding SaveText, Mode=OneWay}"
                                HorizontalAlignment="Stretch"
                                Height="45"
                                Grid.Row="1" />

                    </Grid>
                </Grid>
            </UserControl>

        </comp:PageShell.ContentView>
    </comp:PageShell>
</Page>

This ScrollViewer is has Margin, especially on mobile it looks weird

https://api.github.com/morning4coffe-dev/project-sbs/blob/d32f2b662ab08ba9b8fb26db0b9da6325ac926a5/ProjectSBS/ProjectSBS/Presentation/Components/ItemDetails.xaml#L56

๏ปฟ<UserControl x:Class="ProjectSBS.Presentation.Components.ItemDetails"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="using:ProjectSBS.Presentation.Components"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:not_win="http://uno.ui/not_win"
             mc:Ignorable="d not_win android"
             xmlns:uen="using:Uno.Extensions.Navigation.UI"
             xmlns:utu="using:Uno.Toolkit.UI"
             xmlns:not_android="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:android="http://uno.ui/android"
             xmlns:models="using:ProjectSBS.Business.Models"
             xmlns:comp="using:ProjectSBS.Presentation.Components">

    <UserControl.Resources>
        <Style x:Key="DetailsBorder"
               TargetType="Border">
            <Setter Property="CornerRadius"
                    Value="4" />
            <Setter Property="Background"
                    Value="{ThemeResource CardBackgroundFillColorDefault}" />
            <Setter Property="Padding"
                    Value="16" />
        </Style>
    </UserControl.Resources>

    <Grid>
        <ScrollViewer Visibility="{x:Bind IsEditing, Mode=TwoWay, Converter={StaticResource FalseToVisibleConverter}}">
            <StackPanel Spacing="8">
                <Border Style="{StaticResource DetailsBorder}">
                    <StackPanel Spacing="8">
                        <TextBlock Text="{x:Bind SelectedItem.Item.Name, Mode=OneWay}"
                                   Style="{StaticResource SubtitleTextBlockStyle}" />
                        <TextBlock Text="{x:Bind SelectedItem.Item.Name, Mode=OneWay}" />
                    </StackPanel>
                </Border>

                <Border Style="{StaticResource DetailsBorder}">
                    <TextBlock Text="xx" />
                </Border>

                <Border Style="{StaticResource DetailsBorder}">
                    <TextBlock Text="xx" />
                </Border>
            </StackPanel>
        </ScrollViewer>

        <Grid Visibility="{x:Bind IsEditing, Mode=TwoWay, Converter={StaticResource TrueToVisibleConverter}}"
              RowSpacing="12">
            <Grid.RowDefinitions>
                <RowDefinition />
                <RowDefinition Height="auto" />
            </Grid.RowDefinitions>

            <!--TODO This ScrollViewer is has Margin, especially on mobile it looks weird-->
            <ScrollViewer>
                <StackPanel Spacing="12">
                    <TextBox Header="Name"
                             Text="{x:Bind SelectedItem.Item.Name, Mode=TwoWay}" />
                    <StackPanel Orientation="Horizontal"
                                Spacing="8">
                        <NumberBox Header="Price"
                                   SpinButtonPlacementMode="Inline" />
                        <!--Value="{x:Bind SelectedItem.Item.Billing.BasePrice}"-->
                        <ComboBox VerticalAlignment="Bottom" />
                    </StackPanel>

Rename to AndroidNotificationService

https://api.github.com/morning4coffe-dev/project-sbs/blob/67446d1f21d894c32f1aa2ca87e945dbb1e0ebfb/src/ProjectSBS/ProjectSBS/Services/Notification/NotificationService.cs#L12

using Android.OS;
using Android.Support.V4.App;
using AndroidX.Core.App;
using System;

namespace ProjectSBS.Services.Notifications;

// TODO: Rename to AndroidNotificationService
public class NotificationService : NotificationServiceBase
{
    public NotificationService()
    {
    }

    public override bool IsEnabledOnDevice()
    {
        //TODO implement 
        return false;
    }

    public override void ShowInAppNotification(string notification, bool autoHide)
    {
        InvokeInAppNotificationRequested(new InAppNotificationRequestedEventArgs
        {
            NotificationText = notification,
            NotificationTime = autoHide ? 1500 : 0
        });
    }

    public override void ScheduleNotification(string id, string title, string text, DateOnly day, TimeOnly time)

make the Background Black

https://api.github.com/morning4coffe-dev/project-sbs/blob/661bcfcca089b6637b6aad842cfbf490b87350bb/ProjectSBS/ProjectSBS/Presentation/MainPage.xaml#L16

      mc:Ignorable="d not_win"
      xmlns:uen="using:Uno.Extensions.Navigation.UI"
      xmlns:utu="using:Uno.Toolkit.UI"
      xmlns:comp="using:ProjectSBS.Presentation.Components"
      xmlns:converters="using:CommunityToolkit.WinUI.Converters"
      NavigationCacheMode="Required"
      xmlns:toolkit="using:CommunityToolkit.WinUI">

    <!--TODO make the Background Black-->

    <Page.Resources>
        <converters:EmptyObjectToObjectConverter x:Key="EmptyToFalseConverter"
                                                 EmptyValue="False"
                                                 NotEmptyValue="True" />

        <MenuFlyout x:Key="UserFlyout">
            <MenuFlyoutItem Text="Settings"
                            Icon="{toolkit:FontIcon Glyph=&#xE713;}"
                            AutomationProperties.AutomationId="SecondPageButton"
                            Padding="100, 16"
                            Command="{Binding GoToSecond}" />

            <MenuFlyoutItem Text="Conversions"
                            Icon="{toolkit:FontIcon Glyph=&#xE8C7;}"
                            Padding="100, 16"
                            Command="{Binding}" />

            <MenuFlyoutItem Text="Logout"
                            Padding="100, 16"
                            Command="{Binding Logout}">
                <MenuFlyoutItem.Icon>
                    <FontIcon Glyph="&#xF3B1;" />
                </MenuFlyoutItem.Icon>
            </MenuFlyoutItem>
        </MenuFlyout>

    </Page.Resources>

    <comp:PageShell>
        <comp:PageShell.ContentView>

            <UserControl>
                <Grid>
                    <VisualStateManager.VisualStateGroups>
                        <VisualStateGroup>
                            <VisualState x:Name="NarrowState">
                                <VisualState.StateTriggers>
                                    <AdaptiveTrigger MinWindowWidth="0" />
                                </VisualState.StateTriggers>

                                <VisualState.Setters>
                                    <Setter Target="navigation.IsPaneVisible"
                                            Value="False" />
                                    <Setter Target="mobileHeader.Visibility"
                                            Value="Visible" />
                                    <Setter Target="desktopHeader.Visibility"
                                            Value="Collapsed" />
                                    <Setter Target="contentHolder.Padding"
                                            Value="18,12" />
                                </VisualState.Setters>
                            </VisualState>

                            <VisualState x:Name="WideState">
                                <VisualState.StateTriggers>
                                    <AdaptiveTrigger MinWindowWidth="640" />
                                </VisualState.StateTriggers>

                                <VisualState.Setters>
                                </VisualState.Setters>
                            </VisualState>
                        </VisualStateGroup>

                        <VisualStateGroup>
                            <VisualState x:Name="connected">
                                <VisualState.StateTriggers>
                                    <toolkit:NetworkConnectionStateTrigger ConnectionState="Connected" />
                                </VisualState.StateTriggers>
                                <VisualState.Setters>
                                </VisualState.Setters>
                            </VisualState>
                        </VisualStateGroup>

                        <!--<VisualStateGroup>
                            <VisualState x:Name="paneOpen">
                                <VisualState.StateTriggers>
                                    <toolkit:CompareStateTrigger Comparison="Equal"
                                                                 Value="{Binding navigation.IsPaneOpen, ElementName=NavigationView, Mode=OneWay}"
                                                                 To="True" />
                                </VisualState.StateTriggers>
                                <VisualState.Setters>
                                    <Setter Target="desktopPerson.Width" Value="25" />
                                </VisualState.Setters>
                            </VisualState>
                        </VisualStateGroup>-->

                    </VisualStateManager.VisualStateGroups>

                    <NavigationView x:Name="navigation"
                                    Grid.Row="1"
                                    IsBackButtonVisible="Collapsed"
                                    IsSettingsVisible="True"
                                    IsPaneVisible="True"
                                    uen:Region.Attached="True">

                        <NavigationView.PaneCustomContent>
                            <StackPanel>
                                <Button Background="Transparent"
                                        BorderThickness="0"
                                        BorderBrush="Transparent"
                                        HorizontalAlignment="Stretch"
                                        Margin="4"
                                        HorizontalContentAlignment="Left"
                                        Flyout="{StaticResource UserFlyout}">

                                    <StackPanel Orientation="Horizontal"
                                                Spacing="12">
                                        <PersonPicture x:Name="desktopPerson"
                                                       Width="45"
                                                       DisplayName="{Binding User.Name}" />
                                        <TextBlock  Text="{Binding User.Name}"
                                                    VerticalAlignment="Center" />
                                    </StackPanel>
                                </Button>

                            </StackPanel>
                        </NavigationView.PaneCustomContent>

                        <NavigationView.MenuItems>
                            <NavigationViewItem Content="One"
                                                IsSelected="True"
                                                Icon="AddFriend" />

                            <NavigationViewItem Content="Two"
                                                Icon="CellPhone" />
                        </NavigationView.MenuItems>

                        <SplitView IsPaneOpen="{Binding SelectedItem, Converter={StaticResource EmptyToFalseConverter}}"
                                   PaneBackground="Transparent"
                                   PanePlacement="Right"
                                   OpenPaneLength="350"
                                   DisplayMode="Inline">

                            <Grid x:Name="contentHolder"
                                  RowSpacing="12"
                                  Padding="50, 20">
                                <Grid.RowDefinitions>
                                    <RowDefinition Height="Auto" />
                                    <RowDefinition Height="*" />
                                </Grid.RowDefinitions>

                                <Grid x:Name="desktopHeader"
                                      Height="70">
                                    <TextBlock Text="Home"
                                               VerticalAlignment="Center"
                                               Style="{StaticResource TitleTextBlockStyle}" />
                                </Grid>

                                <Grid x:Name="mobileHeader"
                                      Visibility="Collapsed"
                                      Height="70">
                                    <Grid.ColumnDefinitions>
                                        <ColumnDefinition Width="*" />
                                        <ColumnDefinition Width="Auto" />
                                    </Grid.ColumnDefinitions>

                                    <Grid>
                                        <TextBlock Text="Good Morning"
                                                   Style="{StaticResource TitleTextBlockStyle}"
                                                   VerticalAlignment="Center" />
                                    </Grid>

                                    <Button Grid.Column="1"
                                            Padding="0"
                                            CornerRadius="50"
                                            Background="Transparent"
                                            BorderBrush="Transparent"
                                            Flyout="{StaticResource UserFlyout}">

                                        <PersonPicture Width="55"
                                                       DisplayName="{Binding User.Name}" />

                                    </Button>

                                </Grid>

                                <comp:HomePage Items="{Binding Items, Mode=TwoWay}"
                                               SelectedItem="{Binding SelectedItem, Mode=TwoWay}"
                                               AddNewCommand="{Binding AddNewCommand}"
                                               Grid.Row="1" />
                            </Grid>

                            <SplitView.Pane>

                                <Grid Background="{ThemeResource CardBackgroundFillColorDefault}"
                                      RowSpacing="12"
                                      Padding="24">
                                    <Grid.RowDefinitions>
                                        <RowDefinition Height="auto" />
                                        <RowDefinition />
                                    </Grid.RowDefinitions>

                                    <Border Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
                                            Margin="-24"
                                            Grid.RowSpan="2" />

                                    <Grid ColumnSpacing="8">
                                        <Grid.ColumnDefinitions>
                                            <ColumnDefinition Width="*" />
                                            <ColumnDefinition Width="Auto" />
                                        </Grid.ColumnDefinitions>

                                        <TextBlock Text="Details"
                                                   Style="{StaticResource TitleTextBlockStyle}" />

                                        <StackPanel Orientation="Horizontal"
                                                    Spacing="8"
                                                    Grid.Column="1">
                                            <Button Visibility="{Binding IsEditing, Converter={StaticResource FalseToVisibleConverter}}"
                                                    Command="{Binding EnableEditingCommand}"
                                                    Padding="12, 8">
                                                <FontIcon Glyph="&#xE70F;"
                                                          FontSize="14" />
                                            </Button>

                                            <Button Visibility="{Binding IsEditing, Converter={StaticResource FalseToVisibleConverter}}"
                                                    Command="{Binding DeleteItemCommand}"
                                                    Padding="12, 8">
                                                <FontIcon Glyph="&#xE74D;"
                                                          FontSize="14" />
                                            </Button>

                                            <Button Command="{Binding CloseDetailsCommand}"
                                                    Padding="12, 8">
                                                <FontIcon Glyph="&#xE8BB;"
                                                          FontSize="14" />
                                            </Button>
                                        </StackPanel>
                                    </Grid>

                                    <comp:ItemDetails SelectedItem="{Binding SelectedItem, Mode=TwoWay}"
                                                      IsEditing="{Binding IsEditing, Mode=TwoWay}"
                                                      Grid.Row="1" />

                                </Grid>

                            </SplitView.Pane>
                        </SplitView>

                    </NavigationView>

                </Grid>
            </UserControl>

        </comp:PageShell.ContentView>
    </comp:PageShell>
</Page>

ID here You can convert the id to an integer for the requestCode

https://api.github.com/morning4coffe-dev/project-sbs/blob/eb2e3fb54ca95468f4058c6b18678cf3be478426/ProjectSBS/ProjectSBS/Services/Notification/NotificationService.cs#L102

๏ปฟ#if __ANDROID__

using Android;
using Android.App;
using Android.Content;
using Android.Content.PM;
using Android.OS;
using Android.Support.V4.App;
using Android.Util;
using Android.Views;
using AndroidX.Core.App;
using AndroidX.Lifecycle;
using System;

namespace ProjectSBS.Services.Notifications;

// TODO: Rename to AndroidNotificationService
public class NotificationService : NotificationServiceBase
{
    private Context context = Android.App.Application.Context;

    public NotificationService()
    {
    }

    public override bool IsEnabledOnDevice()
    {
        if (Build.VERSION.SdkInt >= BuildVersionCodes.N)
        {
            //TODO Returns true on all
            //return true;
        }

        return NotificationManagerCompat.From(context).AreNotificationsEnabled();
    }

    public override void ShowInAppNotification(string notification, bool autoHide)
    {
        InvokeInAppNotificationRequested(new InAppNotificationRequestedEventArgs
        {
            NotificationText = notification,
            NotificationTime = autoHide ? 1500 : 0
        });
    }

    public override async void ScheduleNotification(string id, string title, string text, DateOnly day, TimeOnly time)
    {
        if (!IsEnabledOnDevice())
        {
            //TODO make this not only for not scheduled notifications  
            var current = await ApplicationActivity.GetCurrent(new CancellationToken());
            if (ActivityCompat.CheckSelfPermission(current, Manifest.Permission.PostNotifications) != Android.Content.PM.Permission.Granted)
            {
                ActivityCompat.RequestPermissions(current, new string[] { Manifest.Permission.PostNotifications }, 1);
            }
        }

        id = Guid.NewGuid().ToString();

        DateTime notificationDateTime = new(day.Year, day.Month, day.Day, time.Hour, time.Minute, time.Second);
        long totalMilliSeconds = (long)(notificationDateTime.ToUniversalTime() - DateTime.Now).TotalMilliseconds;

        text += $" Scheduled for {notificationDateTime}";

        var (manager, intent) = CreateAlarm(id, title, text, notificationDateTime);

        GetAlarm();

        manager.SetExact(AlarmType.ElapsedRealtime, totalMilliSeconds, intent);
    }

    private (AlarmManager, PendingIntent) CreateAlarm(string id, string title, string text, DateTime notificationDateTime)
    {
        if (notificationDateTime > DateTime.Now)
        {
            Intent notificationIntent = new(context, typeof(NotificationReceiver));
            notificationIntent.PutExtra("id", id);
            notificationIntent.PutExtra("title", title);
            notificationIntent.PutExtra("text", text);

            var random = new Random();
            int requestCode = random.Next(0, 5000); // TODO: ID here You can convert the id to an integer for the requestCode

            PendingIntent pendingIntent = PendingIntent.GetBroadcast(context, requestCode, notificationIntent, PendingIntentFlags.Immutable);

            AlarmManager alarmManager = (AlarmManager)context.GetSystemService(Context.AlarmService);

            return (alarmManager, pendingIntent);
        }

        //TODO throw new Exception("Desired time was set in the past.");
        return (null, null);
    }

    // TODO: Test this function properly
    public override void RemoveScheduledNotifications(string id)
    {
        Intent notificationIntent = new(context, typeof(NotificationReceiver));
        notificationIntent.PutExtra("id", id);

        var random = new Random();
        int requestCode = random.Next(0, 5000); // TODO: ID here You can convert the id to an integer for the requestCode

        PendingIntent pendingIntent = PendingIntent.GetBroadcast(context, requestCode, notificationIntent, PendingIntentFlags.Immutable);

        AlarmManager alarmManager = (AlarmManager)context.GetSystemService(Context.AlarmService);

        alarmManager.Cancel(pendingIntent);
    }

    private void GetAlarm() 
    {
        AlarmManager alarmManager = (AlarmManager)context.GetSystemService(Context.AlarmService);

        //var c = alarmManager.CanScheduleExactAlarms();
        //var d = alarmManager.NextAlarmClock;
    }

    public override void ShowBasicToastNotification(string title, string description)
    {
        var notificationManager = (NotificationManager)context.GetSystemService(Context.NotificationService);

        var channelId = "ProjectSBS-channel";
        var channelName = "Other";
        var importance = NotificationImportance.High;

        var notificationChannel = new NotificationChannel(channelId, channelName, importance);
        notificationManager.CreateNotificationChannel(notificationChannel);

        var notificationBuilder = new NotificationCompat.Builder(context, channelId)
            .SetSmallIcon(Resource.Drawable.abc_vector_test)
            .SetContentTitle(title)
            .SetContentText(description)
            .SetAutoCancel(true);

        var notification = notificationBuilder.Build();

        notificationManager.Notify(0, notification);
    }
}

[BroadcastReceiver(Enabled = true)]
public class NotificationReceiver : BroadcastReceiver
{
    public override void OnReceive(Context context, Intent intent)
    {
        string id = intent.GetStringExtra("id");
        string title = intent.GetStringExtra("title");
        string text = intent.GetStringExtra("text");

        var notificationManager = (NotificationManager)context.GetSystemService(Context.NotificationService);

        PendingIntent pendingIntent = PendingIntent.GetActivity(context, 0, intent, PendingIntentFlags.Immutable);

        var channelId = id;
        var channelName = id; // TODO: Change from id to Item name
        var importance = NotificationImportance.High;

        var notificationChannel = new NotificationChannel(channelId, channelName, importance);
        notificationManager.CreateNotificationChannel(notificationChannel);

        var notificationBuilder = new NotificationCompat.Builder(context, channelId)
            .SetSmallIcon(Resource.Drawable.abc_vector_test)
            .SetContentTitle(title)
            .SetContentText(text)
            .SetPriority(NotificationCompat.PriorityHigh)
            .SetContentIntent(pendingIntent)
            .SetAutoCancel(true);

        var notification = notificationBuilder.Build();

        notificationManager.Notify(0, notification);
    }
}

#endif

doesn't scroll

https://api.github.com/morning4coffe-dev/project-sbs/blob/5e4a1364d059f647170cbe5abdb2ff33e4f1dff3/src/ProjectSBS/ProjectSBS/SettingsPage.xaml#L61

๏ปฟ<Page x:Class="ProjectSBS.Presentation.SettingsPage"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      xmlns:utu="using:Uno.Toolkit.UI"
      xmlns:labs="using:CommunityToolkit.Labs.WinUI"
      xmlns:android="http://uno.ui/android"
      xmlns:ios="http://uno.ui/ios"
      mc:Ignorable="d android ios">

    <Page.Resources>
        <Style x:Key="ControlHeaderStyle"
               TargetType="TextBlock">
            <Setter Property="Margin"
                    Value="2,12,0,2" />
        </Style>
    </Page.Resources>

    <Grid x:Name="gridHolder">
        <Grid.RowDefinitions>
            <RowDefinition Height="auto" />
            <RowDefinition />
        </Grid.RowDefinitions>

        <VisualStateManager.VisualStateGroups>
            <VisualStateGroup>
                <VisualState x:Name="SmallScreen">
                    <VisualState.StateTriggers>
                        <AdaptiveTrigger MinWindowWidth="200" />
                    </VisualState.StateTriggers>
                    <VisualState.Setters>
                        <Setter Target="holder.Margin"
                                Value="5" />
                        <Setter Target="holder.Spacing"
                                Value="0" />
                        <android:Setter Target="gridHolder.Background"
                                        Value="{ThemeResource ApplicationPageBackgroundThemeBrush}" />
                        <ios:Setter Target="gridHolder.Background"
                                    Value="{ThemeResource ApplicationPageBackgroundThemeBrush}" />
                        <Setter Target="navigationBar.Visibility"
                                Value="Visible" />
                    </VisualState.Setters>
                </VisualState>
                <VisualState x:Name="LargeScreen">
                    <VisualState.StateTriggers>
                        <AdaptiveTrigger MinWindowWidth="1000" />
                    </VisualState.StateTriggers>
                    <VisualState.Setters>
                    </VisualState.Setters>
                </VisualState>
            </VisualStateGroup>
        </VisualStateManager.VisualStateGroups>

        <android:Grid>
            <utu:NavigationBar x:Name="navigationBar"
                               Visibility="Collapsed"
                               Content="Settings" />
        </android:Grid>

        <!--TODO doesn't scroll-->
        <ScrollViewer Grid.Row="1">
            <StackPanel x:Name="holder"
                        Margin="32"
                        Spacing="4">
                <!--MaxWidth="900"-->
                <!--HorizontalAlignment="Left"-->

                <labs:SettingsCard android:Style="{StaticResource MobileSettingsCardStyle}">
                    <labs:SettingsCard.Header>
                        <StackPanel Orientation="Horizontal"
                                    Spacing="20">
                            <PersonPicture Width="80" />
                            <StackPanel VerticalAlignment="Center"
                                        Spacing="4">
                                <TextBlock  Text="John Doe"
                                            Style="{StaticResource SubtitleTextBlockStyle}" />
                                <TextBlock  Text="[email protected]" />
                            </StackPanel>
                        </StackPanel>
                    </labs:SettingsCard.Header>
                    <Button Content="Sign out" />
                </labs:SettingsCard>

                <TextBlock Text="Appearance"
                           Style="{StaticResource ControlHeaderStyle}" />
                <labs:SettingsCard Header="Theme"
                                   android:Style="{StaticResource MobileSettingsCardStyle}">
                    <labs:SettingsCard.HeaderIcon>
                        <FontIcon Glyph="&#xE793;" />
                    </labs:SettingsCard.HeaderIcon>

                    <ComboBox 
                              android:Style="{StaticResource ComboBoxStyle}"
                              PlaceholderText="System Default" />
                </labs:SettingsCard>

                <labs:SettingsCard Header="Language"
                                   ContentAlignment="Right"
                                   android:Style="{StaticResource MobileSettingsCardStyle}">
                    <labs:SettingsCard.HeaderIcon>
                        <FontIcon Glyph="&#xF2B7;" />
                    </labs:SettingsCard.HeaderIcon>

                    <ComboBox 
                              android:Style="{StaticResource ComboBoxStyle}"
                              PlaceholderText="System Default" />
                </labs:SettingsCard>

                <TextBlock Text="Some text"
                           Style="{StaticResource ControlHeaderStyle}" />
                <labs:SettingsCard Header="Currency"
                                   ContentAlignment="Right"
                                   android:Style="{StaticResource MobileSettingsCardStyle}">
                    <labs:SettingsCard.HeaderIcon>
                        <FontIcon Glyph="&#xE8C7;" />
                    </labs:SettingsCard.HeaderIcon>

                    <ComboBox android:Style="{StaticResource ComboBoxStyle}" />
                </labs:SettingsCard>

                <labs:SettingsCard Header="Sync"
                                   android:Style="{StaticResource MobileSettingsCardStyle}">
                    <labs:SettingsCard.HeaderIcon>
                        <FontIcon Glyph="&#xE895;" />
                    </labs:SettingsCard.HeaderIcon>

                    <ToggleSwitch android:Style="{StaticResource ToggleSwitchStyle}" />
                </labs:SettingsCard>

                <TextBlock Text="Debug"
                           Style="{StaticResource ControlHeaderStyle}" />
                <labs:SettingsCard Header="Reset Settings"
                                   Description="DEBUG ONLY"
                                   android:Style="{StaticResource MobileSettingsCardStyle}">
                    <labs:SettingsCard.HeaderIcon>
                        <FontIcon Glyph="&#xED10;" />
                    </labs:SettingsCard.HeaderIcon>

                    <Button Content="Reset"
                            /><!--Command="{x:Bind ViewModel.ResetSettingsValuesCommand}"--> 
                </labs:SettingsCard>

                <TextBlock Margin="0,12,0,2"
                           Text="2023 ยฉ Morning4coffe" />
            </StackPanel>
        </ScrollViewer>
    </Grid>
</Page>

Play sound or vibrate the device

}

else

{

await _billingService.RemoveLastPaymentLogAsync(Item);

}

}

{

var itemLogs = _billingService.GetPaymentLogsForItem(item, logs);

{

return false;

}

{

if (lastPayment > DateOnly.FromDateTime(DateTime.Today) ||

(lastLog.PaymentDate >= lastPayment && lastLog.PaymentDate < nextPayment))

{

return true;

}

}

}

https://api.github.com/morning4coffe-dev/project-sbs/blob/221473be04beb37fe032f6412da20f043c79e9ed/ProjectSBS/ProjectSBS/Presentation/Components/ItemViewModel.cs#L85

        return _billingService.GetFuturePayments(billing.InitialDate, billing.PeriodType, billing.RecurEvery, numberOfPayments);
    }

    #region FEAT: feature/IndividualPayments
    //[ObservableProperty]
    //private bool _isPaid;

    //public List<ItemLog> PaymentLogs { get; } = new();

    //private async Task OnPay()
    //{

    //await Task.CompletedTask;

    //if (IsPaid)
    //{
    //    //TODO Play sound or vibrate the device

    //    await _billingService.NewPaymentLogAsync(Item);
    //}
    //else
    //{
    //    await _billingService.RemoveLastPaymentLogAsync(Item);
    //}
    //}

    //private bool CalculateIsPaid(Item item, IEnumerable<ItemLog> logs)
    //{
    //    var itemLogs = _billingService.GetPaymentLogsForItem(item, logs);

    //    if (!Enumerable.Any(logs))
    //    {
    //        return false;
    //    }

    //    var (lastPayment, nextPayment) = _billingService.GetBillingDates(item.Billing.InitialDate, item.Billing.PeriodType, item.Billing.RecurEvery);

    //    if (itemLogs.LastOrDefault() is { } lastLog)
    //    {
    //        if (lastPayment > DateOnly.FromDateTime(DateTime.Today) ||
    //            (lastLog.PaymentDate >= lastPayment && lastLog.PaymentDate < nextPayment))
    //        {
    //            return true;
    //        }
    //    }

    //    return false;
    //}
    #endregion
}

Doesn't account for currency or the previous value of the currency

https://api.github.com/morning4coffe-dev/project-sbs/blob/221473be04beb37fe032f6412da20f043c79e9ed/ProjectSBS/ProjectSBS/Presentation/Components/ItemViewModel.cs#L56

        }
    }

    public decimal TotalPrice
    {
        get
        {
            if (Item?.Billing is not { } billing)
            {
                return 0M;
            }

            var dates = _billingService.GetLastPayments(billing.InitialDate, billing.PeriodType, billing.RecurEvery);

            //TODO Doesn't account for currency or the previous value of the currency
            var price = Enumerable.Count(dates) * billing.BasePrice;
            return price;
        }
    }

    public List<DateOnly> GetFuturePayments(int numberOfPayments = 12)
    {
        if (Item?.Billing is not { } billing)
        {

Rename to AndroidNotificationService

https://api.github.com/morning4coffe-dev/project-sbs/blob/eb2e3fb54ca95468f4058c6b18678cf3be478426/ProjectSBS/ProjectSBS/Services/Notification/NotificationService.cs#L17

๏ปฟ#if __ANDROID__

using Android;
using Android.App;
using Android.Content;
using Android.Content.PM;
using Android.OS;
using Android.Support.V4.App;
using Android.Util;
using Android.Views;
using AndroidX.Core.App;
using AndroidX.Lifecycle;
using System;

namespace ProjectSBS.Services.Notifications;

// TODO: Rename to AndroidNotificationService
public class NotificationService : NotificationServiceBase
{
    private Context context = Android.App.Application.Context;

    public NotificationService()
    {
    }

    public override bool IsEnabledOnDevice()
    {
        if (Build.VERSION.SdkInt >= BuildVersionCodes.N)
        {
            //TODO Returns true on all
            //return true;
        }

        return NotificationManagerCompat.From(context).AreNotificationsEnabled();
    }

    public override void ShowInAppNotification(string notification, bool autoHide)
    {
        InvokeInAppNotificationRequested(new InAppNotificationRequestedEventArgs
        {
            NotificationText = notification,
            NotificationTime = autoHide ? 1500 : 0
        });
    }

    public override async void ScheduleNotification(string id, string title, string text, DateOnly day, TimeOnly time)
    {
        if (!IsEnabledOnDevice())
        {
            //TODO make this not only for not scheduled notifications  
            var current = await ApplicationActivity.GetCurrent(new CancellationToken());
            if (ActivityCompat.CheckSelfPermission(current, Manifest.Permission.PostNotifications) != Android.Content.PM.Permission.Granted)
            {
                ActivityCompat.RequestPermissions(current, new string[] { Manifest.Permission.PostNotifications }, 1);
            }
        }

        id = Guid.NewGuid().ToString();

        DateTime notificationDateTime = new(day.Year, day.Month, day.Day, time.Hour, time.Minute, time.Second);
        long totalMilliSeconds = (long)(notificationDateTime.ToUniversalTime() - DateTime.Now).TotalMilliseconds;

        text += $" Scheduled for {notificationDateTime}";

        var (manager, intent) = CreateAlarm(id, title, text, notificationDateTime);

        GetAlarm();

        manager.SetExact(AlarmType.ElapsedRealtime, totalMilliSeconds, intent);
    }

    private (AlarmManager, PendingIntent) CreateAlarm(string id, string title, string text, DateTime notificationDateTime)
    {
        if (notificationDateTime > DateTime.Now)
        {
            Intent notificationIntent = new(context, typeof(NotificationReceiver));
            notificationIntent.PutExtra("id", id);
            notificationIntent.PutExtra("title", title);
            notificationIntent.PutExtra("text", text);

            var random = new Random();
            int requestCode = random.Next(0, 5000); // TODO: ID here You can convert the id to an integer for the requestCode

            PendingIntent pendingIntent = PendingIntent.GetBroadcast(context, requestCode, notificationIntent, PendingIntentFlags.Immutable);

            AlarmManager alarmManager = (AlarmManager)context.GetSystemService(Context.AlarmService);

            return (alarmManager, pendingIntent);
        }

        //TODO throw new Exception("Desired time was set in the past.");
        return (null, null);
    }

    // TODO: Test this function properly
    public override void RemoveScheduledNotifications(string id)
    {
        Intent notificationIntent = new(context, typeof(NotificationReceiver));
        notificationIntent.PutExtra("id", id);

        var random = new Random();
        int requestCode = random.Next(0, 5000); // TODO: ID here You can convert the id to an integer for the requestCode

        PendingIntent pendingIntent = PendingIntent.GetBroadcast(context, requestCode, notificationIntent, PendingIntentFlags.Immutable);

        AlarmManager alarmManager = (AlarmManager)context.GetSystemService(Context.AlarmService);

        alarmManager.Cancel(pendingIntent);
    }

    private void GetAlarm() 
    {
        AlarmManager alarmManager = (AlarmManager)context.GetSystemService(Context.AlarmService);

        //var c = alarmManager.CanScheduleExactAlarms();
        //var d = alarmManager.NextAlarmClock;
    }

    public override void ShowBasicToastNotification(string title, string description)
    {
        var notificationManager = (NotificationManager)context.GetSystemService(Context.NotificationService);

        var channelId = "ProjectSBS-channel";
        var channelName = "Other";
        var importance = NotificationImportance.High;

        var notificationChannel = new NotificationChannel(channelId, channelName, importance);
        notificationManager.CreateNotificationChannel(notificationChannel);

        var notificationBuilder = new NotificationCompat.Builder(context, channelId)
            .SetSmallIcon(Resource.Drawable.abc_vector_test)
            .SetContentTitle(title)
            .SetContentText(description)
            .SetAutoCancel(true);

        var notification = notificationBuilder.Build();

        notificationManager.Notify(0, notification);
    }
}

[BroadcastReceiver(Enabled = true)]
public class NotificationReceiver : BroadcastReceiver
{
    public override void OnReceive(Context context, Intent intent)
    {
        string id = intent.GetStringExtra("id");
        string title = intent.GetStringExtra("title");
        string text = intent.GetStringExtra("text");

        var notificationManager = (NotificationManager)context.GetSystemService(Context.NotificationService);

        PendingIntent pendingIntent = PendingIntent.GetActivity(context, 0, intent, PendingIntentFlags.Immutable);

        var channelId = id;
        var channelName = id; // TODO: Change from id to Item name
        var importance = NotificationImportance.High;

        var notificationChannel = new NotificationChannel(channelId, channelName, importance);
        notificationManager.CreateNotificationChannel(notificationChannel);

        var notificationBuilder = new NotificationCompat.Builder(context, channelId)
            .SetSmallIcon(Resource.Drawable.abc_vector_test)
            .SetContentTitle(title)
            .SetContentText(text)
            .SetPriority(NotificationCompat.PriorityHigh)
            .SetContentIntent(pendingIntent)
            .SetAutoCancel(true);

        var notification = notificationBuilder.Build();

        notificationManager.Notify(0, notification);
    }
}

#endif

Dont clear the whole thing, just removem update and add changed

https://api.github.com/morning4coffe-dev/project-sbs/blob/350185b311410abd6e7dfa92aee5824d21892c27/ProjectSBS/ProjectSBS/Presentation/NestedPages/HomeViewModel.cs#L106

using System.Collections.ObjectModel;
using CommunityToolkit.Mvvm.Messaging;
using ProjectSBS.Business;
using ProjectSBS.Services.Items;
using Windows.UI.Core;

namespace ProjectSBS.Presentation.NestedPages;

public partial class HomeViewModel : ObservableObject
{
    private INavigator _navigator;
    private IDispatcher _dispatch;
    private IItemService _itemService;

    [ObservableProperty]
    private Type? _itemDetails;

    [ObservableProperty]
    private decimal _sum;

    [ObservableProperty]
    private bool _isEditing = false;

    private ItemViewModel? _selectedItem;
    public ItemViewModel? SelectedItem
    {
        get => _selectedItem;
        set
        {
            if (_selectedItem == value)
            {
                return;
            }

            //Created only after user first requests opening item
            ItemDetails ??= typeof(ItemDetails);

            WeakReferenceMessenger.Default.Send(new ItemSelectionChanged(value));

            IsEditing = false;

            _selectedItem = value;
            OnPropertyChanged();
        }
    }

    public ObservableCollection<ItemViewModel> Items { get; } = new();

    public ICommand EnableEditingCommand { get; }
    public ICommand CloseCommand { get; }

    public HomeViewModel(
        INavigator navigator,
        IDispatcher dispatch,
        IItemService itemService)
    {
        _navigator = navigator;
        _dispatch = dispatch;
        _itemService = itemService;

#if HAS_UNO
        SystemNavigationManager.GetForCurrentView().BackRequested += System_BackRequested;
#endif

        Task.Run(InitializeAsync);

        WeakReferenceMessenger.Default.Register<ItemUpdated>(this, (r, m) =>
        {
            ItemViewModel? item = null;

            if (SelectedItem is not null)
            {
                item = Items.FirstOrDefault(i => i.Item.Id == SelectedItem.Item?.Id);
            }

            if (item is null)
            {
                _itemService.NewItem(m.Item.Item);
            }

            SelectedItem = null;
        });

        //InitializeAsync();
    }

    private async Task InitializeAsync()
    {
        await _itemService.InitializeAsync();

        var items = await RefreshItems();

        await MainViewModel.Dispatch.ExecuteAsync(() =>
        {
            Sum = items.Sum(i => i.Item.Billing.BasePrice);
        });
    }

    private async Task<IEnumerable<ItemViewModel>> RefreshItems()
    {
        var items = _itemService.GetItems(/*TODO Filtering SelectedCategory.Selector*/);

        await MainViewModel.Dispatch.ExecuteAsync(() =>
        {
            Items.Clear();
            //TODO Dont clear the whole thing, just removem update and add changed
            Items.AddRange(items);
        });

        foreach (var item in items)
        {
            Items.Add(item);
        }

        return items;
    }

    private void System_BackRequested(object? sender, BackRequestedEventArgs e)
    {
        e.Handled = true;
        SystemNavigationManager.GetForCurrentView().BackRequested -= System_BackRequested;
    }
}

Add changing icon here

https://api.github.com/morning4coffe-dev/project-sbs/blob/b3feed2025c112e4acf25e4634f86e50900c4899/ProjectSBS/ProjectSBS/Presentation/Components/ItemDetails.xaml#L211

                                <CalendarDatePicker Header="Billing Date"
                                                    Date="{Binding SelectedItem.Item.Billing.InitialDate, Mode=TwoWay, Converter={StaticResource DateOnlyToDateTimeOffsetConverter}}"
                                                    HorizontalAlignment="Stretch" />

                                <Grid ColumnSpacing="8">
                                    <Grid.ColumnDefinitions>
                                        <ColumnDefinition />
                                        <ColumnDefinition Width="Auto" />
                                    </Grid.ColumnDefinitions>

                                    <NumberBox Header="Repete every"
                                               Value="{Binding SelectedItem.Item.Billing.RecurEvery, Mode=TwoWay}"
                                               SpinButtonPlacementMode="Inline" />
                                    <ComboBox VerticalAlignment="Bottom"
                                              ItemsSource="{toolkit:EnumValues Type=models:Period}"
                                              SelectedItem="{Binding SelectedItem.Item.Billing.PeriodType, Mode=TwoWay}"
                                              Grid.Column="1" />
                                </Grid>
                                
                                <Grid>
                                    <!--TODO Add changing icon here-->
                                    <CheckBox Content="Get notified {NI}"
                                              IsEnabled="True" />
                                    <FontIcon HorizontalAlignment="Right"
                                              Opacity="0.75"
                                              FontSize="18"
                                              Glyph="&#xEA8F;" />
                                </Grid>

                                <Grid>

Instead of IsEditing, check if the item is dirty

https://api.github.com/morning4coffe-dev/project-sbs/blob/5b0dc3b37078514d633ac69f23e8ddda25ae6bfb/ProjectSBS/ProjectSBS/Presentation/NestedPages/HomeViewModel.cs#L185

        e.Handled = true;
        SystemNavigationManager.GetForCurrentView().BackRequested -= System_BackRequested;
    }

    private void AddNew()
    {
        SelectedItem = new ItemViewModel(null);

        IsEditing = true;
    }

    private async Task SubmitChanges()
    {
        SelectedItem = null;

        IsEditing = false;
    }

    private async Task EnableEditing()
    {
        IsEditing = true;
    }

    private async Task DeleteItem()
    {
        //TODO Find item by Id in database and delete it
        return;
    }

    private async Task ArchiveItem()
    {
        //TODO Find item by Id in database and delete it
        return;
    }

    private async Task CloseDetails()
    {
        //TODO Instead of IsEditing, check if the item is dirty
        if (IsEditing)
        {
            await _navigator.ShowMessageDialogAsync(this, title: "...", content: "Really?");
        }
        else
        {
            SelectedItem = null;
        }

        IsEditing = false;
    }
}

There is no profile picture

https://api.github.com/morning4coffe-dev/project-sbs/blob/0f435e876af42d2fcca52b6ed94fc8573b931365/ProjectSBS/ProjectSBS/Services/User/MsalUser.cs#L79

        var mail = user?.Mail;

        // Retrieve the user's profile picture
        BitmapImage? photoBitmap = null;
        try
        {
            var photoStream = await _client.Me
                .Photo
                .Content
                .GetAsync();

            photoBitmap = new BitmapImage();
            var stream = new MemoryStream();
            await photoStream.CopyToAsync(stream);
            stream.Seek(0, SeekOrigin.Begin);
            await photoBitmap.SetSourceAsync(stream.AsRandomAccessStream());
        }
        catch
        {
            // TODO There is no profile picture
        }

        return new UserModel.User(fullName, mail, photoBitmap);
    }

Register your services

https://api.github.com/morning4coffe-dev/project-sbs/blob/86ec130ece1fbedbab81fe3962efa19fc227d136/ProjectSBS/ProjectSBS/App.cs#L79

using ProjectSBS.Services.Storage;
using ProjectSBS.Services.Storage.Data;
using ProjectSBS.Services.User;

namespace ProjectSBS;

public class App : Application
{
    protected Window? MainWindow { get; private set; }
    protected IHost? Host { get; private set; }

    protected async override void OnLaunched(LaunchActivatedEventArgs args)
    {
        var builder = this.CreateBuilder(args)
            // Add navigation support for toolkit controls such as TabBar and NavigationView
            .UseToolkitNavigation()
            .Configure(host => host
#if DEBUG
				// Switch to Development environment when running in DEBUG
				.UseEnvironment(Environments.Development)
#endif
                .UseLogging(configure: (context, logBuilder) =>
                {
                    // Configure log levels for different categories of logging
                    logBuilder
                        .SetMinimumLevel(
                            context.HostingEnvironment.IsDevelopment() ?
                                LogLevel.Information :
                                LogLevel.Warning)

                        // Default filters for core Uno Platform namespaces
                        .CoreLogLevel(LogLevel.Warning);

                    // Uno Platform namespace filter groups
                    // Uncomment individual methods to see more detailed logging
                    //// Generic Xaml events
                    //logBuilder.XamlLogLevel(LogLevel.Debug);
                    //// Layouter specific messages
                    //logBuilder.XamlLayoutLogLevel(LogLevel.Debug);
                    //// Storage messages
                    //logBuilder.StorageLogLevel(LogLevel.Debug);
                    //// Binding related messages
                    //logBuilder.XamlBindingLogLevel(LogLevel.Debug);
                    //// Binder memory references tracking
                    //logBuilder.BinderMemoryReferenceLogLevel(LogLevel.Debug);
                    //// RemoteControl and HotReload related
                    //logBuilder.HotReloadCoreLogLevel(LogLevel.Information);
                    //// Debug JS interop
                    //logBuilder.WebAssemblyLogLevel(LogLevel.Debug);

                }, enableUnoLogging: true)
                .UseConfiguration(configure: configBuilder =>
                    configBuilder
                        .EmbeddedSource<App>()
                        .Section<AppConfig>()
                )
                // Enable localization (see appsettings.json for supported languages)
                .UseLocalization()
                // Register Json serializers (ISerializer and ISerializer)
                .UseSerialization((context, services) => services
                    .AddContentSerializer(context))
                .UseHttp((context, services) => services
                        // Register HttpClient
#if DEBUG
						// DelegatingHandler will be automatically injected into Refit Client
						.AddTransient<DelegatingHandler, DebugHttpHandler>()
#endif
                        .AddSingleton<ICurrencyCache, CurrencyCache>()
                        .AddRefitClient<IApiClient>(context))
                .UseAuthentication(auth =>
                    auth.AddMsal(name: "MsalAuthentication")
                )
                .ConfigureServices((context, services) =>
                {
                    services.AddSingleton<IStorageService, StorageService>();
                    services.AddSingleton<IStorageService, RemoteStorageService>();
                    services.AddSingleton<IDataService, DataService>();
                    services.AddSingleton<IUserService, MsalUser>();
                    // TODO: Register your services
                    //services.AddSingleton<IMyService, MyService>();
                })
                .UseNavigation(RegisterRoutes)
            );
        MainWindow = builder.Window;

        Host = await builder.NavigateAsync<Shell>(initialNavigate:
            async (services, navigator) =>
            {
                var auth = services.GetRequiredService<IAuthenticationService>();
                var authenticated = await auth.RefreshAsync();
                if (authenticated)
                {
                    await navigator.NavigateViewModelAsync<MainViewModel>(this, qualifier: Qualifiers.Nested);
                }
                else
                {
                    await navigator.NavigateViewModelAsync<LoginViewModel>(this, qualifier: Qualifiers.Nested);
                }
            });
    }

    private static void RegisterRoutes(IViewRegistry views, IRouteRegistry routes)
    {
        views.Register(
            new ViewMap(ViewModel: typeof(ShellViewModel)),
            new ViewMap<LoginPage, LoginViewModel>(),
            new ViewMap<MainPage, MainViewModel>(),
            new DataViewMap<SecondPage, SecondViewModel, Entity>()
        );

        routes.Register(
            new RouteMap("", View: views.FindByViewModel<ShellViewModel>(),
                Nested: new RouteMap[]
                {
                new RouteMap("Login", View: views.FindByViewModel<LoginViewModel>()),
                new RouteMap("Main", View: views.FindByViewModel<MainViewModel>()),
                new RouteMap("Second", View: views.FindByViewModel<SecondViewModel>()),
                }
            )
        );
    }
}

[Optimization] instead of waiting, maybe load items, or stuff like that here

https://api.github.com/morning4coffe-dev/project-sbs/blob/1c086dc941ebcd831c7959e8a969480776da0a4c/ProjectSBS/ProjectSBS/Presentation/LoginViewModel.cs#L62

        if (success)
        {
            await _dispatcher.ExecuteAsync(async () =>
            {
                User = await _userService.GetUser();

                //TODO [Optimization] instead of waiting, maybe load items, or stuff like that here
                await Task.Delay(100000);

                await _navigator.NavigateViewModelAsync<MainViewModel>(this, qualifier: Qualifiers.ClearBackStack);
            });
        }
    }

move this to only Filtering

WeakReferenceMessenger.Default.Send(new CategorySelectionChanged());

https://github.com/morning4coffe-dev/project-sbs/blob/ef1ba0811b2b8345beb19472eeb85e2477f3cbd7/ProjectSBS/ProjectSBS/Presentation/MainViewModel.cs#L41

    public string? Title { get; }

    public NavigationCategory SelectedCategory
    {
        get => _navigation.SelectedCategory;
        set
        {
            if (_navigation.SelectedCategory == value)
            {
                return;
            }

            _navigation.SelectedCategory = value;

            //TODO move this to only Filtering
            //WeakReferenceMessenger.Default.Send(new CategorySelectionChanged());

            OnPropertyChanged();
        }
    }

    public List<NavigationCategory> Categories { get; }

    public ICommand GoToSettingsCommand { get; }
    public ICommand LogoutCommand { get; }

<receiver android:name="ProjectSBS.Services.Notifications.NotificationReceiver"

android:enabled="true"

android:exported="true" />

https://api.github.com/morning4coffe-dev/project-sbs/blob/bd6eae250dfc2f83b8165376bf538c02457633f6/src/ProjectSBS/ProjectSBS.Mobile/Android/AndroidManifest.xml#L7

๏ปฟ<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
	<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
	<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
	<uses-permission android:name="android.permission.SET_ALARM" />
	<uses-permission android:name="android.permission.WAKE_LOCK" />
	<!--TODO <receiver android:name="ProjectSBS.Services.Notifications.NotificationReceiver"
    android:enabled="true"
    android:exported="true" />-->
	<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
	
	<application android:allowBackup="true" android:supportsRtl="true">
		<receiver android:name=".NotificationReceiver" />
	</application>
	<uses-sdk android:minSdkVersion="23" android:targetSdkVersion="33"/>
</manifest>

Add loading indicator

https://api.github.com/morning4coffe-dev/project-sbs/blob/350185b311410abd6e7dfa92aee5824d21892c27/ProjectSBS/ProjectSBS/Presentation/MainViewModel.cs#L169

            else
            {
                //TODO enable login button
            }            
        });

        while ((Application.Current as App)!.Host == null)
        {
            //Wait till Host is created
            await Task.Delay(200);

            //TODO Add loading indicator
        }

        await Dispatch.ExecuteAsync(() =>
        {
            PageType = typeof(HomePage);
        });
    }

    private async Task GoToSecondView()
    {
        PageType = typeof(SettingsPage);
    }

    public async Task DoLogout(CancellationToken token)
    {
        await _authentication.LogoutAsync(Dispatch, token);
        //TODO probably will have to clean the token too
    }

Log Dispatch is null on Initialization

https://api.github.com/morning4coffe-dev/project-sbs/blob/5b0dc3b37078514d633ac69f23e8ddda25ae6bfb/ProjectSBS/ProjectSBS/Presentation/MainViewModel.cs#L111

        _authentication = authentication;
#endif
        _userService = userService;
        _navigator = navigator;
        Dispatch = dispatch;
        _filterService = filterService;


        Title = "Main";
        Title += $" - {localizer["ApplicationName"]}";
        Title += $" - {appInfo?.Value?.Environment}";

        GoToSettingsCommand = new AsyncRelayCommand(GoToSettings);
        LogoutCommand = new AsyncRelayCommand(DoLogout);

        Categories = filterService.Categories;

        Task.Run(InitializeAsync);

        WeakReferenceMessenger.Default.Register<CategorySelectionChanged>(this, (r, m) =>
        {
            OnPropertyChanged(nameof(SelectedCategory));
        });
    }

    private async Task InitializeAsync()
    {
        var user = await _userService.GetUser();

        //TODO Log Dispatch is null on Initialization

        await Dispatch.ExecuteAsync(async () =>
        {
            if (user is not null)
            {
                User = user;
                Name = User.Name;
            }

            IsSignedIn = await _authentication.IsAuthenticated();
        });

        while ((Application.Current as App)!.Host == null)

ID here You can convert the id to an integer for the requestCode

https://api.github.com/morning4coffe-dev/project-sbs/blob/eb2e3fb54ca95468f4058c6b18678cf3be478426/ProjectSBS/ProjectSBS/Services/Notification/NotificationService.cs#L82

๏ปฟ#if __ANDROID__

using Android;
using Android.App;
using Android.Content;
using Android.Content.PM;
using Android.OS;
using Android.Support.V4.App;
using Android.Util;
using Android.Views;
using AndroidX.Core.App;
using AndroidX.Lifecycle;
using System;

namespace ProjectSBS.Services.Notifications;

// TODO: Rename to AndroidNotificationService
public class NotificationService : NotificationServiceBase
{
    private Context context = Android.App.Application.Context;

    public NotificationService()
    {
    }

    public override bool IsEnabledOnDevice()
    {
        if (Build.VERSION.SdkInt >= BuildVersionCodes.N)
        {
            //TODO Returns true on all
            //return true;
        }

        return NotificationManagerCompat.From(context).AreNotificationsEnabled();
    }

    public override void ShowInAppNotification(string notification, bool autoHide)
    {
        InvokeInAppNotificationRequested(new InAppNotificationRequestedEventArgs
        {
            NotificationText = notification,
            NotificationTime = autoHide ? 1500 : 0
        });
    }

    public override async void ScheduleNotification(string id, string title, string text, DateOnly day, TimeOnly time)
    {
        if (!IsEnabledOnDevice())
        {
            //TODO make this not only for not scheduled notifications  
            var current = await ApplicationActivity.GetCurrent(new CancellationToken());
            if (ActivityCompat.CheckSelfPermission(current, Manifest.Permission.PostNotifications) != Android.Content.PM.Permission.Granted)
            {
                ActivityCompat.RequestPermissions(current, new string[] { Manifest.Permission.PostNotifications }, 1);
            }
        }

        id = Guid.NewGuid().ToString();

        DateTime notificationDateTime = new(day.Year, day.Month, day.Day, time.Hour, time.Minute, time.Second);
        long totalMilliSeconds = (long)(notificationDateTime.ToUniversalTime() - DateTime.Now).TotalMilliseconds;

        text += $" Scheduled for {notificationDateTime}";

        var (manager, intent) = CreateAlarm(id, title, text, notificationDateTime);

        GetAlarm();

        manager.SetExact(AlarmType.ElapsedRealtime, totalMilliSeconds, intent);
    }

    private (AlarmManager, PendingIntent) CreateAlarm(string id, string title, string text, DateTime notificationDateTime)
    {
        if (notificationDateTime > DateTime.Now)
        {
            Intent notificationIntent = new(context, typeof(NotificationReceiver));
            notificationIntent.PutExtra("id", id);
            notificationIntent.PutExtra("title", title);
            notificationIntent.PutExtra("text", text);

            var random = new Random();
            int requestCode = random.Next(0, 5000); // TODO: ID here You can convert the id to an integer for the requestCode

            PendingIntent pendingIntent = PendingIntent.GetBroadcast(context, requestCode, notificationIntent, PendingIntentFlags.Immutable);

            AlarmManager alarmManager = (AlarmManager)context.GetSystemService(Context.AlarmService);

            return (alarmManager, pendingIntent);
        }

        //TODO throw new Exception("Desired time was set in the past.");
        return (null, null);
    }

    // TODO: Test this function properly
    public override void RemoveScheduledNotifications(string id)
    {
        Intent notificationIntent = new(context, typeof(NotificationReceiver));
        notificationIntent.PutExtra("id", id);

        var random = new Random();
        int requestCode = random.Next(0, 5000); // TODO: ID here You can convert the id to an integer for the requestCode

        PendingIntent pendingIntent = PendingIntent.GetBroadcast(context, requestCode, notificationIntent, PendingIntentFlags.Immutable);

        AlarmManager alarmManager = (AlarmManager)context.GetSystemService(Context.AlarmService);

        alarmManager.Cancel(pendingIntent);
    }

    private void GetAlarm() 
    {
        AlarmManager alarmManager = (AlarmManager)context.GetSystemService(Context.AlarmService);

        //var c = alarmManager.CanScheduleExactAlarms();
        //var d = alarmManager.NextAlarmClock;
    }

    public override void ShowBasicToastNotification(string title, string description)
    {
        var notificationManager = (NotificationManager)context.GetSystemService(Context.NotificationService);

        var channelId = "ProjectSBS-channel";
        var channelName = "Other";
        var importance = NotificationImportance.High;

        var notificationChannel = new NotificationChannel(channelId, channelName, importance);
        notificationManager.CreateNotificationChannel(notificationChannel);

        var notificationBuilder = new NotificationCompat.Builder(context, channelId)
            .SetSmallIcon(Resource.Drawable.abc_vector_test)
            .SetContentTitle(title)
            .SetContentText(description)
            .SetAutoCancel(true);

        var notification = notificationBuilder.Build();

        notificationManager.Notify(0, notification);
    }
}

[BroadcastReceiver(Enabled = true)]
public class NotificationReceiver : BroadcastReceiver
{
    public override void OnReceive(Context context, Intent intent)
    {
        string id = intent.GetStringExtra("id");
        string title = intent.GetStringExtra("title");
        string text = intent.GetStringExtra("text");

        var notificationManager = (NotificationManager)context.GetSystemService(Context.NotificationService);

        PendingIntent pendingIntent = PendingIntent.GetActivity(context, 0, intent, PendingIntentFlags.Immutable);

        var channelId = id;
        var channelName = id; // TODO: Change from id to Item name
        var importance = NotificationImportance.High;

        var notificationChannel = new NotificationChannel(channelId, channelName, importance);
        notificationManager.CreateNotificationChannel(notificationChannel);

        var notificationBuilder = new NotificationCompat.Builder(context, channelId)
            .SetSmallIcon(Resource.Drawable.abc_vector_test)
            .SetContentTitle(title)
            .SetContentText(text)
            .SetPriority(NotificationCompat.PriorityHigh)
            .SetContentIntent(pendingIntent)
            .SetAutoCancel(true);

        var notification = notificationBuilder.Build();

        notificationManager.Notify(0, notification);
    }
}

#endif

Tag color

https://api.github.com/morning4coffe-dev/project-sbs/blob/0f435e876af42d2fcca52b6ed94fc8573b931365/ProjectSBS/ProjectSBS/Presentation/Components/ItemDetails.xaml#L152

                        <StackPanel Spacing="8"
                                    Style="{StaticResource ContentStack}">

                            <!--TODO Tag color-->
                            <Grid Height="22">
                                <Rectangle Fill="Red"
                                           RadiusY="10"
                                           RadiusX="10" />
                                <TextBlock Text="{x:Bind ViewModel.SelectedItem.Item.TagId, Mode=OneWay}"
                                           Margin="12,0" />
                            </Grid>

                            <local:DetailsBox Header="for the price of">
                                <local:DetailsBox.Content>
                                    <TextBlock TextWrapping="WrapWholeWords">
                                     <Run Text="{x:Bind ViewModel.SelectedItem.Item.Billing.BasePrice, Mode=OneWay}" />
                                     <Run Text="{x:Bind ViewModel.SelectedItem.Item.Billing.CurrencyId, Mode=OneWay}" />
                                    </TextBlock>
                                </local:DetailsBox.Content>
                            </local:DetailsBox>

                            <local:DetailsBox Header="paid in total">
                                <local:DetailsBox.Content>
                                    <TextBlock TextWrapping="WrapWholeWords">
                                       <Run Text="N/A" />
                                       <Run Text="{x:Bind ViewModel.SelectedItem.Item.Billing.CurrencyId, Mode=OneWay}" />
                                    </TextBlock>
                                </local:DetailsBox.Content>
                            </local:DetailsBox>

                            <local:DetailsBox Header="billing period"
                                              Text="{x:Bind ViewModel.SelectedItem.Item.Billing.PeriodType, Mode=OneWay}" />

                            <local:DetailsBox Header="payment method"
                                              Text="N/A" />

                            <local:DetailsBox Header="next payments">
                                <local:DetailsBox.Content>
                                    <ScrollViewer HorizontalScrollMode="Auto"
                                                  HorizontalScrollBarVisibility="Auto"
                                                  Margin="-16"

ID here You can convert the id to an integer for the requestCode

https://api.github.com/morning4coffe-dev/project-sbs/blob/67446d1f21d894c32f1aa2ca87e945dbb1e0ebfb/src/ProjectSBS/ProjectSBS/Services/Notification/NotificationService.cs#L53

            notificationIntent.PutExtra("text", text);

            var random = new Random();
            int requestCode = random.Next(0, 5000); // TODO: ID here You can convert the id to an integer for the requestCode

            PendingIntent pendingIntent = PendingIntent.GetBroadcast(Android.App.Application.Context, requestCode, notificationIntent, PendingIntentFlags.Immutable);

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.