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"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:model="clr-namespace:TodoDetails.Model"
x:Class="TodoDetails.MainPage">
<ScrollView>
<CollectionView>
<CollectionView.ItemsSource>
<x:Array Type="{x:Type model:Todo}">
<model:Todo Title="Create ViewModel"
Description="Create a ViewModel in the next step to learn MVVM."
Category="Default"
IsDone="False"/>
<model:Todo Title="Add Theming"
Description="Integrate your own theme in the app."
Category="Default"
IsDone="False"/>
<model:Todo Title="Add local database"
Description="Learn how to add a local database to work with (optional)."
Category="Default"
IsDone="False"/>
</x:Array>
</CollectionView.ItemsSource>
xmlns:viewmodel="clr-namespace:TodoDetails.ViewModel"
x:DataType="viewmodel:TodosViewModel"
x:Class="TodoDetails.MainPage"
Title="{Binding Title}">
<Grid ColumnSpacing="5"
RowSpacing="0">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<CollectionView ItemsSource="{Binding Todos}"
SelectionMode="None"
Grid.ColumnSpan="2">
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="model:Todo">
<HorizontalStackLayout Padding="10"
Spacing="10"
HorizontalOptions="FillAndExpand">
<CheckBox IsChecked="{Binding IsDone}"
VerticalOptions="Start"/>
<VerticalStackLayout VerticalOptions="Center">
<Label Text="{Binding Title}"
<Grid Padding="10">
<Border HeightRequest="125">
<Grid Padding="0">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<CheckBox IsChecked="{Binding IsDone}"/>
<VerticalStackLayout Grid.Column="1"
VerticalOptions="Center"
FontSize="16"
TextColor="Gray"/>
Padding="10">
<Label Text="{Binding Title}"
FontSize="16"/>
<Label Text="{Binding Category}"
FontSize="12"
TextColor="Gray"/>
FontSize="12"/>
</VerticalStackLayout>
</HorizontalStackLayout>
<Label Text="{Binding DueDate}"
Grid.Column="2"
VerticalOptions="Center"/>
</Grid>
</Border>
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</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>

View file

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

View file

@ -1,4 +1,6 @@
using Microsoft.Extensions.Logging;
using TodoDetails.Services;
using TodoDetails.ViewModel;
namespace TodoDetails
{
@ -18,6 +20,9 @@ namespace TodoDetails
#if DEBUG
builder.Logging.AddDebug();
#endif
builder.Services.AddSingleton<TodoService>();
builder.Services.AddSingleton<TodosViewModel>();
builder.Services.AddSingleton<MainPage>();
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>
<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.Compatibility" Version="$(MauiVersion)" />
<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;
}
}
}
}