Implement a ToDo overview based on a JSON file

This commit is contained in:
Manuel Thalmann 2024-06-06 23:40:25 +02:00
parent 37f27fa415
commit 468f66ab59
8 changed files with 219 additions and 40 deletions

View file

@ -2,46 +2,62 @@
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui" <ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:model="clr-namespace:TodoDetails.Model" xmlns:model="clr-namespace:TodoDetails.Model"
x:Class="TodoDetails.MainPage"> xmlns:viewmodel="clr-namespace:TodoDetails.ViewModel"
x:DataType="viewmodel:TodosViewModel"
<ScrollView> x:Class="TodoDetails.MainPage"
<CollectionView> Title="{Binding Title}">
<CollectionView.ItemsSource> <Grid ColumnSpacing="5"
<x:Array Type="{x:Type model:Todo}"> RowSpacing="0">
<model:Todo Title="Create ViewModel" <Grid.ColumnDefinitions>
Description="Create a ViewModel in the next step to learn MVVM." <ColumnDefinition/>
Category="Default" <ColumnDefinition/>
IsDone="False"/> </Grid.ColumnDefinitions>
<model:Todo Title="Add Theming" <Grid.RowDefinitions>
Description="Integrate your own theme in the app." <RowDefinition/>
Category="Default" <RowDefinition Height="Auto"/>
IsDone="False"/> </Grid.RowDefinitions>
<model:Todo Title="Add local database" <CollectionView ItemsSource="{Binding Todos}"
Description="Learn how to add a local database to work with (optional)." SelectionMode="None"
Category="Default" Grid.ColumnSpan="2">
IsDone="False"/>
</x:Array>
</CollectionView.ItemsSource>
<CollectionView.ItemTemplate> <CollectionView.ItemTemplate>
<DataTemplate x:DataType="model:Todo"> <DataTemplate x:DataType="model:Todo">
<HorizontalStackLayout Padding="10" <Grid Padding="10">
Spacing="10" <Border HeightRequest="125">
HorizontalOptions="FillAndExpand"> <Grid Padding="0">
<CheckBox IsChecked="{Binding IsDone}" <Grid.ColumnDefinitions>
VerticalOptions="Start"/> <ColumnDefinition/>
<VerticalStackLayout VerticalOptions="Center"> <ColumnDefinition/>
<Label Text="{Binding Title}" <ColumnDefinition/>
</Grid.ColumnDefinitions>
<CheckBox IsChecked="{Binding IsDone}"/>
<VerticalStackLayout Grid.Column="1"
VerticalOptions="Center" VerticalOptions="Center"
FontSize="16" Padding="10">
TextColor="Gray"/> <Label Text="{Binding Title}"
FontSize="16"/>
<Label Text="{Binding Category}" <Label Text="{Binding Category}"
FontSize="12" FontSize="12"/>
TextColor="Gray"/>
</VerticalStackLayout> </VerticalStackLayout>
</HorizontalStackLayout> <Label Text="{Binding DueDate}"
Grid.Column="2"
VerticalOptions="Center"/>
</Grid>
</Border>
</Grid>
</DataTemplate> </DataTemplate>
</CollectionView.ItemTemplate> </CollectionView.ItemTemplate>
</CollectionView> </CollectionView>
</ScrollView> <Button Text="Fetch Todos"
Command="{Binding GetTodosCommand}"
IsEnabled="{Binding IsNotBusy}"
Grid.Row="1"
Margin="8"/>
<ActivityIndicator IsVisible="{Binding IsBusy}"
IsRunning="{Binding IsBusy}"
HorizontalOptions="Fill"
VerticalOptions="Center"
Color="{DynamicResource Primary}"
Grid.RowSpan="2"
Grid.ColumnSpan="2"/>
</Grid>
</ContentPage> </ContentPage>

View file

@ -1,12 +1,13 @@
using TodoDetails.ViewModel;
namespace TodoDetails namespace TodoDetails
{ {
public partial class MainPage : ContentPage public partial class MainPage : ContentPage
{ {
int count = 0; public MainPage(TodosViewModel viewModel)
public MainPage()
{ {
InitializeComponent(); InitializeComponent();
BindingContext = viewModel;
} }
} }

View file

@ -1,4 +1,6 @@
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using TodoDetails.Services;
using TodoDetails.ViewModel;
namespace TodoDetails namespace TodoDetails
{ {
@ -18,6 +20,9 @@ namespace TodoDetails
#if DEBUG #if DEBUG
builder.Logging.AddDebug(); builder.Logging.AddDebug();
#endif #endif
builder.Services.AddSingleton<TodoService>();
builder.Services.AddSingleton<TodosViewModel>();
builder.Services.AddSingleton<MainPage>();
return builder.Build(); return builder.Build();
} }

View file

@ -0,0 +1,23 @@
[
{
"Title": "Create ViewModel",
"Description": "Create a ViewModel in the next step to learn MVVM.",
"IsDone": true,
"Category": "Default",
"DueDate": "2023-05-31T00:00:00"
},
{
"Title": "Add Themeing",
"Description": "Integrate your own theme in the app.",
"IsDone": false,
"Category": "Default",
"DueDate": "2023-05-31T00:00:00"
},
{
"Title": "Add local database",
"Description": "Learn how to add a local database.",
"IsDone": false,
"Category": "Default",
"DueDate": "2023-05-31T00:00:00"
}
]

View file

@ -0,0 +1,40 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using TodoDetails.Model;
namespace TodoDetails.Services
{
public class TodoService
{
Lazy<Task<List<Todo>>> todoList;
public TodoService()
{
todoList = new Lazy<Task<List<Todo>>>(
async () =>
{
List<Todo>? result = null;
using var stream = await FileSystem.OpenAppPackageFileAsync("tododata.json");
using var reader = new StreamReader(stream);
var contents = await reader.ReadToEndAsync();
if (contents != null)
{
result = JsonSerializer.Deserialize<List<Todo>>(contents);
}
return result ?? new();
});
}
public async Task<List<Todo>> GetTodos()
{
return await todoList.Value;
}
}
}

View file

@ -57,6 +57,11 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Remove="Resources\raw\tododata.json" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.2" />
<PackageReference Include="Microsoft.Maui.Controls" Version="$(MauiVersion)" /> <PackageReference Include="Microsoft.Maui.Controls" Version="$(MauiVersion)" />
<PackageReference Include="Microsoft.Maui.Controls.Compatibility" Version="$(MauiVersion)" /> <PackageReference Include="Microsoft.Maui.Controls.Compatibility" Version="$(MauiVersion)" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="8.0.0" /> <PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="8.0.0" />

View file

@ -0,0 +1,21 @@
using CommunityToolkit.Mvvm.ComponentModel;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace TodoDetails.ViewModel
{
public partial class BaseViewModel : ObservableObject
{
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(IsNotBusy))]
bool isBusy;
[ObservableProperty]
string? title;
public bool IsNotBusy => !IsBusy;
}
}

View file

@ -0,0 +1,68 @@
using CommunityToolkit.Mvvm.Input;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using TodoDetails.Model;
using TodoDetails.Services;
namespace TodoDetails.ViewModel
{
public partial class TodosViewModel : BaseViewModel
{
public ObservableCollection<Todo> Todos { get; } = new ObservableCollection<Todo>();
TodoService todoService;
public TodosViewModel(TodoService todoService)
{
Title = "Todos";
this.todoService = todoService;
}
[RelayCommand]
async Task GetTodosAsync()
{
if (IsBusy)
{
return;
}
try
{
IsBusy = true;
var todos = await todoService.GetTodos();
if (Todos.Count != 0)
{
Todos.Clear();
}
foreach (var todo in todos)
{
Todos.Add(todo);
}
}
catch (Exception e)
{
Debug.WriteLine($"UInable to get todos: {e.Message}");
await Shell.Current.DisplayAlert("Error!", e.Message, "OK");
}
finally
{
IsBusy = false;
}
}
[RelayCommand]
async Task GoToDetails(Todo todo)
{
if (todo == null)
{
return;
}
}
}
}