Skip to content

Commit

Permalink
Merge pull request #40 from nabond251/feature/new-example
Browse files Browse the repository at this point in the history
Feature/new example
  • Loading branch information
nabond251 authored Mar 29, 2024
2 parents 636b640 + 7f51411 commit 344771d
Show file tree
Hide file tree
Showing 26 changed files with 846 additions and 243 deletions.
29 changes: 28 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,33 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.

## 2024-03-28

### Changes

---

Packages with breaking changes:

- [`rearch.reactor` - `v0.0.0-dev.4`](#rearch.reactor---v000-dev4)

Packages with other changes:

- [`rearch` - `v0.0.0-dev.4`](#rearch---v000-dev4)

---

#### `rearch.reactor` - `v0.0.0-dev.4`

- **FIX**: bug fixes and prototype todo list example app.
- **FEAT**: allow type inference in capsule warm up.
- **FEAT**: allow generic type props in capsule consumers.

#### `rearch` - `v0.0.0-dev.4`

- **FIX**: bug fixes and prototype todo list example app.


## 2024-02-22

### Changes
Expand All @@ -16,7 +43,7 @@ Packages with breaking changes:
Packages with other changes:

- [`rearch` - `v0.0.0-dev.3`](#rearch---v000-dev3)
- [`flutter_rearch` - `v0.0.0-dev.3`](#flutter_rearch---v000-dev3)
- [`rearch.reactor` - `v0.0.0-dev.3`](#rearch.reactor---v000-dev3)

---

Expand Down
34 changes: 34 additions & 0 deletions examples/Rearch.Reactor.Example/Capsules/ContextCapsules.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using Microsoft.Extensions.DependencyInjection;
using ReactorData;
using Rearch.Reactor.Example.Helpers;
using Rearch.Types;
using System;
using System.Threading.Tasks;

namespace Rearch.Reactor.Example.Capsules
{
internal static class ContextCapsules
{
/// Represents the <see cref="IModelContext"/> that contains the todos
/// dataset.
internal static async Task<IModelContext> ContextAsyncCapsule(ICapsuleHandle use)
{
await Task.Delay(1000);
return (await ServiceHelper.ServicesAsync).GetRequiredService<IModelContext>();
}

/// Allows for the <see cref="ContextAsyncCapsule"/> to more easily be
/// warmed up for use in <see cref="ContextCapsule"/>.
internal static AsyncValue<IModelContext> ContextWarmUpCapsule(ICapsuleHandle use)
{
var task = use.Invoke(ContextAsyncCapsule);
return use.Task(task);
}

/// Acts as a proxy to the warmed-up <see cref="ContextAsyncCapsule"/>.
internal static IModelContext ContextCapsule(ICapsuleHandle use) =>
use.Invoke(ContextWarmUpCapsule).GetData().UnwrapOrElse(
() => throw new InvalidOperationException(
"ContextWarmUpCapsule was not warmed up!"));
}
}
134 changes: 134 additions & 0 deletions examples/Rearch.Reactor.Example/Capsules/TodoCapsules.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
using ReactorData;
using System;
using System.Collections.Generic;
using System.Linq;
using Rearch.Reactor.Example.Models;
using static Rearch.Reactor.Example.Capsules.ContextCapsules;
using Rearch.Types;
using System.Reactive.Linq;
using System.Collections.Specialized;

namespace Rearch.Reactor.Example.Capsules;

public static class TodoCapsules
{
/// <summary>
/// Provides a way to create/update and delete todos.
/// </summary>
/// <param name="use">Capsule handle.</param>
/// <returns>Actions to add, update, or delete todos.</returns>
internal static (
Action<Todo> AddTodo,
Action<Todo> UpdateTodo,
Action<Todo> DeleteTodo)
TodoItemsManagerCapsule(ICapsuleHandle use)
{
var context = use.Invoke(ContextCapsule);

return (
AddTodo: todo =>
{
context.Add(todo);
context.Save();
},
UpdateTodo: todo =>
{
context.Replace(todo, new Todo
{
Id = todo.Id,
Title = todo.Title,
Description = todo.Description,
Completed = todo.Completed
});
context.Save();
},
DeleteTodo: todo =>
{
context.Delete(todo);
context.Save();
}
);
}

/// <summary>
/// Represents the current filter to search with
/// (<see cref="string.Empty"/> as a query string represents no current
/// query).
/// </summary>
/// <param name="use">Capsule handle.</param>
/// <returns>Filter data and actions.</returns>
internal static (
TodoListFilter Filter,
Action<string> SetQueryString,
Action ToggleCompletionStatus)
FilterCapsule(ICapsuleHandle use)
{
var (query, setQuery) = use.State(string.Empty);
var (completionStatus, setCompletionStatus) = use.State(false);
return (
new TodoListFilter
{
Query = query,
CompletionStatus = completionStatus,
},
setQuery,
() => setCompletionStatus(!completionStatus)
);
}

/// <summary>
/// Represents the todos list using the filter from the
/// <see cref="FilterCapsule"/>.
/// </summary>
/// <param name="use">Capsule handle.</param>
/// <returns>Query of to-do list items.</returns>
internal static IQuery<Todo> TodoQueryCapsule(ICapsuleHandle use)
{
var context = use.Invoke(ContextCapsule);
var (filter, _, _) = use.Invoke(FilterCapsule);
var filterQuery = filter.Query;
var completionStatus = filter.CompletionStatus;

// When query is null/empty, it does not affect the search.
var todosQuery = use.Memo(
() => context.Query<Todo>(
query => query
.Where(
todo =>
(
todo.Title.Contains(filterQuery, StringComparison.InvariantCultureIgnoreCase) ||
(
todo.Description != null &&
todo.Description.Contains(filterQuery, StringComparison.InvariantCultureIgnoreCase))) &&
todo.Completed == completionStatus)
.OrderBy(todo => todo.Id)),
[context, filterQuery, completionStatus]);

return todosQuery;
}

internal static AsyncValue<List<Todo>> TodoListCapsule(ICapsuleHandle use)
{
var todosQuery = use.Invoke(TodoQueryCapsule);

var todosObservable = use.Memo(
() => Observable
.FromEventPattern(
(EventHandler<NotifyCollectionChangedEventArgs> ev) => new NotifyCollectionChangedEventHandler(ev),
ev => todosQuery.CollectionChanged += ev,
ev => todosQuery.CollectionChanged -= ev)
.Select(e => (e.Sender as IEnumerable<Todo>)!)
.StartWith(todosQuery),
[todosQuery]);
var todosState = use.Observable(todosObservable);
return todosState.Select(todos => todos.ToList());
}

/// <summary>
/// Represents the length of the <see cref="TodoListCapsule"/>.
/// </summary>
/// <param name="use">Capsule handle.</param>
/// <returns>Length of todos.</returns>
internal static AsyncValue<int> TodoListCountCapsule(ICapsuleHandle use) =>
use.Invoke(TodoListCapsule).Select(todos => todos.Count);
}
51 changes: 51 additions & 0 deletions examples/Rearch.Reactor.Example/Components/Body.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
using MauiReactor;
using Rearch.Reactor.Example.Models;
using Rearch.Reactor.Components;
using static Rearch.Reactor.Example.Capsules.TodoCapsules;
using Rearch.Reactor.Example.Pages;
using System;

namespace Rearch.Reactor.Example.Components;

partial class Body : CapsuleConsumer
{
public override VisualNode Render(ICapsuleHandle use)
{
var (isSearching, setIsSearching) = use.State(false);

var (AddTodo, _, _) = use.Invoke(TodoItemsManagerCapsule);

var (
filter,
_,
toggleCompletionStatus) =
use.Invoke(FilterCapsule);
var completionStatus = filter.CompletionStatus;

return NavigationPage(
ContentPage(
ToolbarItem(completionStatus ?
"Complete" :
"Incomplete")
.OnClicked(toggleCompletionStatus),

ToolbarItem("Search")
.OnClicked(() => setIsSearching(!isSearching)),

ToolbarItem("Create")
.OnClicked(() => ShowCreateTodoDialogAsync(
ContainerPage, AddTodo)),

new TodoList()
)
.Title("rearch todos")
);

void ShowCreateTodoDialogAsync(
MauiControls.Page? containerPage,
Action<Todo> todoCreator)
{
containerPage?.Navigation.PushModalAsync<CreateTodoPage, CreateTodoPageProps>(p => p.TodoCreator = todoCreator);
}
}
}
29 changes: 29 additions & 0 deletions examples/Rearch.Reactor.Example/Components/GlobalWarmUps.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using MauiReactor;
using ReactorData;
using System.Collections.Generic;
using System.Linq;
using Rearch.Reactor.Components;
using Rearch.Types;
using static Rearch.Reactor.Example.Capsules.ContextCapsules;

namespace Rearch.Reactor.Example.Components;

partial class GlobalWarmUps(VisualNode child) : CapsuleConsumer
{
public override VisualNode Render(ICapsuleHandle use)
{
return new List<AsyncValue<IModelContext>>
{
use.Invoke(ContextWarmUpCapsule)
}
.ToWarmUpComponent(
child: child,
loading: ContentPage(Label("Loading...").Center()),
errorBuilder: errors =>
ContentPage(VStack(
children: errors
.Select(error => Label(error.Error.ToString()))
.ToArray())
.Center()));
}
}
59 changes: 59 additions & 0 deletions examples/Rearch.Reactor.Example/Components/TodoItem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
using MauiReactor;
using System;
using Rearch.Reactor.Example.Models;
using Rearch.Reactor.Components;
using Microsoft.Maui.Controls;
using static Rearch.Reactor.Example.Capsules.TodoCapsules;

namespace Rearch.Reactor.Example.Components;

internal class TodoItem(Todo item) : CapsuleConsumer
{
public override VisualNode Render(ICapsuleHandle use)
{
var title = item.Title;
var description = item.Description;
var id = item.Id;
var completed = item.Completed;

var (_, UpdateTodo, DeleteTodo) = use.Invoke(TodoItemsManagerCapsule);

void Delete() => DeleteTodo(item);
void ToggleCompletionStatus()
{
item.Title = title;
item.Description = description;
item.Id = id;
item.Completed = !completed;

UpdateTodo(item);
}

return Frame(
Grid("27, 27", "Auto, *",
CheckBox()
.IsChecked(item.Completed)
.OnCheckedChanged(ToggleCompletionStatus)
.GridRowSpan(2),
Label(item.Title)
.FormattedText(() =>
{
FormattedString formattedString = new FormattedString();
formattedString.Spans.Add(new Span
{
Text = item.Title,
FontAttributes = FontAttributes.Bold,
});
return formattedString;
})
.VCenter()
.GridColumn(1),
Label(item.Description)
.VCenter()
.GridRow(1)
.GridColumn(1)),
TapGestureRecognizer(ToggleCompletionStatus, 1),
TapGestureRecognizer(Delete, 2)
);
}
}
43 changes: 43 additions & 0 deletions examples/Rearch.Reactor.Example/Components/TodoList.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
using System.Linq;
using MauiReactor;
using Rearch.Reactor.Example.Models;
using Rearch.Reactor.Components;
using static Rearch.Reactor.Example.Capsules.TodoCapsules;
using Rearch.Types;

namespace Rearch.Reactor.Example.Components;

partial class TodoList : CapsuleConsumer
{
public override VisualNode Render(ICapsuleHandle use)
{
var completionFilter = use.Invoke(FilterCapsule).Filter.CompletionStatus;
var completionText = completionFilter ? "completed" : "incomplete";

var todoListCount = use.Invoke(TodoListCountCapsule);
var todoQuery = use.Invoke(TodoQueryCapsule);

var todoListWidget = todoListCount.GetData().Select(_ =>
{
return CollectionView().ItemsSource(
todoQuery,
i => new TodoItem(i));
}).AsNullable();

var infoWidget = todoListCount.Match(
onLoading: _ => Label("Loading..."),
onError: (error, _) => Label(error.ToString()),
onData: count => count == 0 ?
Label($"No {completionText} todos found") :
null);

return StackLayout(
children: (todoListWidget != null ?
[todoListWidget] :
Enumerable.Empty<VisualNode>())
.Concat(infoWidget != null ?
[Frame(infoWidget)] :
Enumerable.Empty<VisualNode>())
.ToArray());
}
}
Loading

0 comments on commit 344771d

Please sign in to comment.