Tutorial 2 - Implement the MVVM pattern in the notes app (#1128)
* Init for diff * Upgrade * viewmodel article done * fix full snippets * updates after doing full run of article * fix highlighted lines * restructure; add notifications * updates * dates * fix image paths * Move to shared snippet; remove notification * checkpoint * missing file * more tweaks * more tweaks * more fixes * final part * tweaks * tweak * Remove file * tweak * add links to samples * outro * Update TOC.yml * Update index.yml * Update index.yml * Apply suggestions from code review Co-authored-by: David Britch <davidbritch@users.noreply.github.com> * feedback * Apply suggestions from code review Co-authored-by: David Britch <davidbritch@users.noreply.github.com> * feedback * Apply suggestions from code review Co-authored-by: David Britch <davidbritch@users.noreply.github.com> * add mvvm article * image * fix link * Fix interface * update first tutorial with ios icons * feedback * add link from 1st tutorial to 2nd Co-authored-by: David Britch <davidbritch@users.noreply.github.com>
|
@ -64,7 +64,9 @@
|
|||
- name: Tutorials
|
||||
items:
|
||||
- name: Create a .NET MAUI app
|
||||
href: tutorials/notes-app/index.yml
|
||||
href: tutorials/notes-app/index.yml?
|
||||
- name: Upgrade an app with MVVM principles
|
||||
href: tutorials/notes-mvvm/index.yml?
|
||||
- name: XAML
|
||||
items:
|
||||
- name: Overview
|
||||
|
|
|
@ -71,12 +71,12 @@ The _AppShell.xaml_ defines two tabs, one for the `NotesPage` and another for `A
|
|||
<ShellContent
|
||||
Title="Notes"
|
||||
ContentTemplate="{DataTemplate local:NotePage}"
|
||||
Icon="icon_notes" />
|
||||
Icon="{OnPlatform 'icon_notes.png', iOS='icon_notes_ios.png', MacCatalyst='icon_notes_ios.png'}" />
|
||||
|
||||
<ShellContent
|
||||
Title="About"
|
||||
ContentTemplate="{DataTemplate local:AboutPage}"
|
||||
Icon="icon_about" />
|
||||
Icon="{OnPlatform 'icon_about.png', iOS='icon_about_ios.png', MacCatalyst='icon_about_ios.png'}" />
|
||||
</TabBar>
|
||||
|
||||
</Shell>
|
||||
|
@ -107,12 +107,12 @@ The `local` XML namespace was used by the `ShellContent.ContentTemplate` propert
|
|||
<ShellContent
|
||||
Title="Notes"
|
||||
ContentTemplate="{DataTemplate views:NotePage}"
|
||||
Icon="icon_notes" />
|
||||
Icon="{OnPlatform 'icon_notes.png', iOS='icon_notes_ios.png', MacCatalyst='icon_notes_ios.png'}" />
|
||||
|
||||
<ShellContent
|
||||
Title="About"
|
||||
ContentTemplate="{DataTemplate views:AboutPage}"
|
||||
Icon="icon_about" />
|
||||
Icon="{OnPlatform 'icon_about.png', iOS='icon_about_ios.png', MacCatalyst='icon_about_ios.png'}" />
|
||||
</TabBar>
|
||||
|
||||
</Shell>
|
||||
|
|
|
@ -88,9 +88,9 @@ Now that the XAML and code-behind of the `AboutPage` is complete, you'll need to
|
|||
|
||||
## Add image resources
|
||||
|
||||
Some controls can use images, which enhances how users interact with your app. In this section you'll download and add two images for to your app.
|
||||
Some controls can use images, which enhances how users interact with your app. In this section you'll download two images you'll use in your app, along with two alternative images for use with iOS.
|
||||
|
||||
Download these two images:
|
||||
Download the following images:
|
||||
|
||||
- [Icon: About](https://raw.githubusercontent.com/dotnet/docs-maui/main/docs/tutorials/notes-app/snippets/shell/csharp/Notes/Resources/Images/icon_about.png)\
|
||||
This image is used as an icon for the about page you created earlier.
|
||||
|
@ -98,8 +98,14 @@ Download these two images:
|
|||
- [Icon: Notes](https://raw.githubusercontent.com/dotnet/docs-maui/main/docs/tutorials/notes-app/snippets/shell/csharp/Notes/Resources/Images/icon_notes.png)\
|
||||
This image is used as an icon for the notes page you'll create in the next part of this tutorial.
|
||||
|
||||
- [Icon: About (iOS)](https://raw.githubusercontent.com/dotnet/docs-maui/main/docs/tutorials/notes-app/snippets/shell/csharp/Notes/Resources/Images/icon_about_ios.png)
|
||||
- [Icon: Notes (iOS)](https://raw.githubusercontent.com/dotnet/docs-maui/main/docs/tutorials/notes-app/snippets/shell/csharp/Notes/Resources/Images/icon_notes_ios.png)
|
||||
|
||||
After you've downloaded the images, you can move them with File Explorer to the _Resources\Images_ folder of the project. Any file in this folder is automatically included in the project as a **MauiImage** resource. You can also use Visual Studio to add the images to your project. If you move the images by hand, skip the following procedure.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Don't skip downloading the iOS-specific images, they're required to complete this tutorial.
|
||||
|
||||
### Move the images with Visual Studio
|
||||
|
||||
01. In the **Solution Explorer** pane of Visual Studio, expand the **Resources** folder, which reveals the **Images** folder.
|
||||
|
@ -110,9 +116,9 @@ After you've downloaded the images, you can move them with File Explorer to the
|
|||
01. Right-click on **Images** and select **Add** > **Existing Item...**.
|
||||
01. Navigate to the folder that contains the downloaded images.
|
||||
01. Change the filter to file type filter to **Image Files**.
|
||||
01. Hold down <kbd>CTRL</kbd> and click on each of the three images you downloaded, then press **Add**
|
||||
01. Hold down <kbd>CTRL</kbd> and click on each of the images you downloaded, then press **Add**
|
||||
|
||||
:::image type="content" source="../media/shell/vs-add-image.png" alt-text="Add three icon images to .NET MAUI project.":::
|
||||
:::image type="content" source="../media/shell/vs-add-image.png" alt-text="Add four icon images to .NET MAUI project.":::
|
||||
|
||||
## Modify the app Shell
|
||||
|
||||
|
@ -130,7 +136,7 @@ Let's break down the key parts of the XAML:
|
|||
- `<TabBar>` is the content of the <xref:Microsoft.Maui.Controls.Shell>.
|
||||
- Two `<ShellContent>` objects inside of the `<TabBar>`. Before you replaced the template code, there was a single `<ShellContent>` object, pointing to the `MainPage` page.
|
||||
|
||||
The <xref:Microsoft.Maui.Controls.TabBar> and its children don't represent any user interface elements, but rather the organization of the app's visual hierarchy. Shell will take these objects and produce the user interface for the content.
|
||||
The `TabBar` and its children don't represent any user interface elements, but rather the organization of the app's visual hierarchy. Shell takes these objects and produces the user interface for the content, with a bar at the top representing each page. The `ShellContent.Icon` property for each page uses special syntax: `{OnPlatform ...}`. This syntax is processed when the XAML pages are compiled for each platform, and with it you can specify a property value for each platform. In this case, every platform uses the `icon_about.png` icon by default, but iOS and MacCatalyst will use `icon_about_ios.png`.
|
||||
|
||||
Each `<ShellContent>` object is pointing to a page to display. This is set by the `ContentTemplate` property.
|
||||
|
||||
|
|
|
@ -35,6 +35,10 @@ items:
|
|||
|
||||
## Next steps
|
||||
|
||||
In the next part of the tutorial series you'll learn how to implement model-view-viewmodel (MVVM) patterns in your project.
|
||||
|
||||
- [Upgrade an app with MVVM principles](../notes-mvvm/index.yml)
|
||||
|
||||
The following links provide more information related to some of the concepts you learned in this tutorial:
|
||||
|
||||
- [.NET MAUI Shell overview](../../fundamentals/shell/index.md)
|
||||
|
|
Двоичные данные
docs/tutorials/notes-app/media/intro/final-note-small.pdn
Двоичные данные
docs/tutorials/notes-app/media/intro/final-note-small.png
До Ширина: | Высота: | Размер: 58 KiB После Ширина: | Высота: | Размер: 59 KiB |
Двоичные данные
docs/tutorials/notes-app/media/intro/final-note.png
До Ширина: | Высота: | Размер: 82 KiB После Ширина: | Высота: | Размер: 84 KiB |
Двоичные данные
docs/tutorials/notes-app/media/intro/final-notelist-small.pdn
Двоичные данные
docs/tutorials/notes-app/media/intro/final-notelist-small.png
До Ширина: | Высота: | Размер: 78 KiB После Ширина: | Высота: | Размер: 78 KiB |
Двоичные данные
docs/tutorials/notes-app/media/intro/final-notelist.png
До Ширина: | Высота: | Размер: 104 KiB После Ширина: | Высота: | Размер: 106 KiB |
Двоичные данные
docs/tutorials/notes-app/media/shell/final.png
До Ширина: | Высота: | Размер: 69 KiB После Ширина: | Высота: | Размер: 70 KiB |
Двоичные данные
docs/tutorials/notes-app/media/shell/vs-add-image.pdn
Двоичные данные
docs/tutorials/notes-app/media/shell/vs-add-image.png
До Ширина: | Высота: | Размер: 28 KiB После Ширина: | Высота: | Размер: 30 KiB |
|
@ -10,12 +10,12 @@
|
|||
<ShellContent
|
||||
Title="Notes"
|
||||
ContentTemplate="{DataTemplate views:AllNotesPage}"
|
||||
Icon="icon_notes" />
|
||||
Icon="{OnPlatform 'icon_notes.png', iOS='icon_notes_ios.png', MacCatalyst='icon_notes_ios.png'}" />
|
||||
|
||||
<ShellContent
|
||||
Title="About"
|
||||
ContentTemplate="{DataTemplate views:AboutPage}"
|
||||
Icon="icon_about" />
|
||||
Icon="{OnPlatform 'icon_about.png', iOS='icon_about_ios.png', MacCatalyst='icon_about_ios.png'}" />
|
||||
</TabBar>
|
||||
|
||||
</Shell>
|
||||
|
|
Двоичные данные
docs/tutorials/notes-app/snippets/navigation/csharp/Notes/Resources/Images/icon_about_ios.png
Normal file
После Ширина: | Высота: | Размер: 2.0 KiB |
Двоичные данные
docs/tutorials/notes-app/snippets/navigation/csharp/Notes/Resources/Images/icon_notes_ios.png
Normal file
После Ширина: | Высота: | Размер: 2.1 KiB |
|
@ -10,12 +10,12 @@
|
|||
<ShellContent
|
||||
Title="Notes"
|
||||
ContentTemplate="{DataTemplate local:NotePage}"
|
||||
Icon="icon_notes" />
|
||||
Icon="{OnPlatform 'icon_notes.png', iOS='icon_notes_ios.png', MacCatalyst='icon_notes_ios.png'}" />
|
||||
|
||||
<ShellContent
|
||||
Title="About"
|
||||
ContentTemplate="{DataTemplate local:AboutPage}"
|
||||
Icon="icon_about" />
|
||||
Icon="{OnPlatform 'icon_about.png', iOS='icon_about_ios.png', MacCatalyst='icon_about_ios.png'}" />
|
||||
</TabBar>
|
||||
|
||||
</Shell>
|
||||
|
|
Двоичные данные
docs/tutorials/notes-app/snippets/note/csharp/Notes/Resources/Images/icon_about_ios.png
Normal file
После Ширина: | Высота: | Размер: 2.0 KiB |
Двоичные данные
docs/tutorials/notes-app/snippets/note/csharp/Notes/Resources/Images/icon_notes_ios.png
Normal file
После Ширина: | Высота: | Размер: 2.1 KiB |
|
@ -10,12 +10,12 @@
|
|||
<ShellContent
|
||||
Title="Notes"
|
||||
ContentTemplate="{DataTemplate local:MainPage}"
|
||||
Icon="icon_notes" />
|
||||
Icon="{OnPlatform 'icon_notes.png', iOS='icon_notes_ios.png', MacCatalyst='icon_notes_ios.png'}" />
|
||||
|
||||
<ShellContent
|
||||
Title="About"
|
||||
ContentTemplate="{DataTemplate local:AboutPage}"
|
||||
Icon="icon_about" />
|
||||
Icon="{OnPlatform 'icon_about.png', iOS='icon_about_ios.png', MacCatalyst='icon_about_ios.png'}" />
|
||||
</TabBar>
|
||||
|
||||
</Shell>
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<ActiveDebugFramework>net6.0-android</ActiveDebugFramework>
|
||||
<ActiveDebugFramework>net6.0-windows10.0.19041.0</ActiveDebugFramework>
|
||||
<IsFirstTimeProjectOpen>False</IsFirstTimeProjectOpen>
|
||||
<ActiveDebugProfile>Pixel 2 XL - API 33 (Android 13.0 - API 33)</ActiveDebugProfile>
|
||||
<ActiveDebugProfile>Windows Machine</ActiveDebugProfile>
|
||||
<SelectedPlatformGroup>Emulator</SelectedPlatformGroup>
|
||||
<DefaultDevice>pixel_2_xl_-_api_33</DefaultDevice>
|
||||
</PropertyGroup>
|
||||
|
|
Двоичные данные
docs/tutorials/notes-app/snippets/shell/csharp/Notes/Resources/Images/icon_about_ios.png
Normal file
После Ширина: | Высота: | Размер: 2.0 KiB |
Двоичные данные
docs/tutorials/notes-app/snippets/shell/csharp/Notes/Resources/Images/icon_notes_ios.png
Normal file
После Ширина: | Высота: | Размер: 2.1 KiB |
|
@ -0,0 +1,55 @@
|
|||
---
|
||||
author: adegeo
|
||||
ms.author: adegeo
|
||||
ms.date: 01/05/2023
|
||||
ms.topic: include
|
||||
---
|
||||
|
||||
Now that the app code can compile and run, you'll likely have noticed that there are two flaws with how the app behaves. The app doesn't let you reselect a note that's already selected, and the list of notes isn't reordered after a note is created or changed.
|
||||
|
||||
## Get notes to the top of the list
|
||||
|
||||
First, fix the reordering problem with the notes list. In the _ViewModels\\NotesViewModel.cs_ file, the `AllNotes` collection contains all of the notes to be presented to the user. Unfortunately, the downside to using an `ObservableCollection` is that it must be manually sorted. To get the new or updated items to the top of the list, perform the following steps:
|
||||
|
||||
01. In the **Solution Explorer** pane of Visual Studio, double-click on **ViewModels\\NotesViewModel.cs**.
|
||||
01. In the `ApplyQueryAttributes` method, look at the logic for the **saved** query string key.
|
||||
01. When the `matchedNote` isn't `null`, the note is being updated. Use the `AllNotes.Move` method to move the `matchedNote` to index 0, which is the top of the list.
|
||||
|
||||
:::code language="csharp" source="../snippets/bugs/csharp/ViewModels/NotesViewModel.cs" id="move" highlight="8":::
|
||||
|
||||
The `AllNotes.Move` method takes two parameters to move an object's position in the collection. The first parameter is the index of the object that to move, and the second parameters is the index of where to move the object. The `AllNotes.IndexOf` method retrieves the index of the note.
|
||||
|
||||
01. When the `matchedNote` is `null`, the note is new and is being added to the list. Instead of adding it, which appends the note to the end of the list, insert the note at index 0, which is the top of the list. Change the `AllNotes.Add` method to `AllNotes.Insert`.
|
||||
|
||||
:::code language="csharp" source="../snippets/bugs/csharp/ViewModels/NotesViewModel.cs" id="insert" highlight="12":::
|
||||
|
||||
The `ApplyQueryAttributes` method should look like the following code snippet:
|
||||
|
||||
:::code language="csharp" source="../snippets/bugs/csharp/ViewModels/NotesViewModel.cs" id="query":::
|
||||
|
||||
## Allow selecting a note twice
|
||||
|
||||
In the **AllNotes view**, the `CollectionView` lists all of the notes, but doesn't allow you to select the same note twice. There are two ways the item remains selected: when the user changes an existing note, and when the user forcibly navigates backwards. The case where the user saves a note is fixed with the code change in previous section that uses `AllNotes.Move`, so you don't have to worry about that case.
|
||||
|
||||
The problem you have to solve now is related to navigation. No matter how the **Allnotes view** is navigated to, the `NavigatedTo` event is raised for the page. This event is a perfect place to forcibly unselect the selected item in the `CollectionView`.
|
||||
|
||||
However, with the MVVM pattern being applied here, the viewmodel can't trigger something directly on the view, such as clearing the selected item after the note is saved. So how do you get that to happen? A good implementation of the MVVM pattern minimizes the code-behind in the view. There are a few different ways to solve this problem to support the MVVM separation pattern. However, it's also OK to put code in the code-behind of the view, especially when it's directly tied to the view. MVVM has many great designs and concepts that help you compartmentalize your app, improving maintainability and making it easier for you to add new features. However, in some cases, you may find that MVVM encourages overengineering.
|
||||
|
||||
Don't overengineer a solution for this problem, and just use the `NavigatedTo` event to clear the selected item from the `CollectionView`.
|
||||
|
||||
01. In the **Solution Explorer** pane of Visual Studio, double-click on **Views\\AllNotesPage.xaml**.
|
||||
01. In the XAML for the `<ContentPage>`, add the `NavigatedTo` event:
|
||||
|
||||
:::code language="xaml" source="../snippets/bugs/csharp/Views/AllNotesPage.xaml" range="1-9" highlight="6":::
|
||||
|
||||
01. You can add a default event handler by right-clicking on the event method name, `ContentPage_NavigatedTo`, and selecting **Go To Definition**. This action opens the _Views\\AllNotesPage.xaml.cs_ in the code editor.
|
||||
|
||||
01. Replace the event handler code with the following snippet:
|
||||
|
||||
:::code language="csharp" source="../snippets/bugs/csharp/Views/AllNotesPage.xaml.cs" id="event":::
|
||||
|
||||
In the XAML, the `CollectionView` was given the name of `notesCollection`. This code uses that name to access the `CollectionView`, and set `SelectedItem` to `null`. The selected item is cleared every time the page is navigated to.
|
||||
|
||||
Now, run your app. Try to navigate to a note, press the back button, and select the same note a second time. The app behavior is fixed!
|
||||
|
||||
[![Explore the code.](~/media/code-sample.png) Explore the code for this step of the tutorial.](https://github.com/dotnet/maui-samples/tree/main/7.0/Tutorials/ConvertToMvvm/step6_bugs)
|
|
@ -0,0 +1,16 @@
|
|||
---
|
||||
author: adegeo
|
||||
ms.author: adegeo
|
||||
ms.date: 01/05/2023
|
||||
ms.topic: include
|
||||
---
|
||||
|
||||
This tutorial series is designed to continue the [Create a .NET MAUI app](../../notes-app/index.yml) tutorial, which created a note taking app. In this part of the series you'll learn how to:
|
||||
|
||||
> [!div class="checklist"]
|
||||
>
|
||||
> - Upgrade your app to .NET 7, if it's currently .NET 6.
|
||||
> - Implement the model-view-viewmodel (MVVM) pattern.
|
||||
> - Use an additional style of query string for passing data during navigation.
|
||||
|
||||
We highly suggest that you first follow the [Create a .NET MAUI app](../../notes-app/index.yml) tutorial, as the code created in that tutorial is the basis for this tutorial. If you lost the code, or want to start fresh, download [this project](https://github.com/dotnet/maui-samples/raw/main/7.0/Tutorials/ConvertToMvvm/step1_upgrade.zip).
|
|
@ -0,0 +1,82 @@
|
|||
---
|
||||
author: adegeo
|
||||
ms.author: adegeo
|
||||
ms.date: 01/05/2023
|
||||
ms.topic: include
|
||||
---
|
||||
|
||||
In this first part of the tutorial, you'll implement the model-view-viewmodel (MVVM) pattern. To start, open the _Notes.sln_ solution in Visual Studio.
|
||||
|
||||
## Clean up the model
|
||||
|
||||
In the previous tutorial, the model types were acting both as the model (data) and as a view model (data preparation), which was mapped directly to a view. The following table describes the model:
|
||||
|
||||
| Code file | Description |
|
||||
|----------------------|-----------------------------------------------------------------------------------------------|
|
||||
| _Models/About.cs_ | The `About` model. Contains read-only fields that describe the app itself, such as the app title and version. |
|
||||
| _Models/Note.cs_ | The `Note` model. Represents a note. |
|
||||
| _Models/AllNotes.cs_ | The `AllNotes` model. Loads all of the notes on the device into a collection. |
|
||||
|
||||
Thinking about the app itself, there is only one piece of data that is used by the app, the `Note`. Notes are loaded from the device, saved to the device, and edited through the app UI. There really isn't a need for the `About` and `AllNotes` models. Remove these models from the project:
|
||||
|
||||
01. Find the **Solution Explorer** pane of Visual Studio.
|
||||
01. Right-click on the **Models\\About.cs** file and select **Delete**. Press **OK** to delete the file.
|
||||
01. Right-click on the **Models\\AllNotes.cs** file and select **Delete**. Press **OK** to delete the file.
|
||||
|
||||
The only model file remaining is the **Models\\Note.cs** file.
|
||||
|
||||
## Update the model
|
||||
|
||||
The `Note` model contains:
|
||||
|
||||
- A unique identifier, which is the file name of the note as stored on the device.
|
||||
- The text of the note.
|
||||
- A date to indicate when the note was created or last updated.
|
||||
|
||||
Currently, loading and saving the model was done through the views, and in some cases, by the other model types that you just removed. The code you have for the `Note` type should be the following:
|
||||
|
||||
```csharp
|
||||
namespace Notes.Models;
|
||||
|
||||
internal class Note
|
||||
{
|
||||
public string Filename { get; set; }
|
||||
public string Text { get; set; }
|
||||
public DateTime Date { get; set; }
|
||||
}
|
||||
```
|
||||
|
||||
The `Note` model is going to be expanded to handle loading, saving, and deleting notes.
|
||||
|
||||
01. In the **Solution Explorer** pane of Visual Studio, double-click on **Models\\Note.cs**.
|
||||
01. In the code editor, add the following two methods to the `Note` class. These methods are instance-based and handle saving or deleting the current note to or from the device, respectively:
|
||||
|
||||
:::code language="csharp" source="../snippets/model/csharp/Notes/Models/Note.cs" id="save_delete":::
|
||||
|
||||
01. The app needs to load notes in two ways, loading an individual note from a file and loading all notes on the device. The code to handle loading can be `static` members, not requiring a class instance to run.
|
||||
|
||||
Add the following code to the class to load a note by file name:
|
||||
|
||||
:::code language="csharp" source="../snippets/model/csharp/Notes/Models/Note.cs" id="load_single":::
|
||||
|
||||
This code takes the file name as a parameter, builds the path to where the notes are stored on the device, and attempts to load the file if it exists.
|
||||
|
||||
01. The second way to load notes is to enumerate all notes on the device and load them into a collection.
|
||||
|
||||
Add the following code to the class:
|
||||
|
||||
:::code language="csharp" source="../snippets/model/csharp/Notes/Models/Note.cs" id="load_all":::
|
||||
|
||||
This code returns an enumerable collection of `Note` model types by retrieving the files on the device that match the notes file pattern: _*.notes.txt_. Each file name is passed to the `Load` method, loading an individual note. Finally, the collection of notes is ordered by the date of each note and returned to the caller.
|
||||
|
||||
01. Lastly, add a constructor to the class which sets the default values for the properties, including a random file name:
|
||||
|
||||
:::code language="csharp" source="../snippets/model/csharp/Notes/Models/Note.cs" id="ctor":::
|
||||
|
||||
The `Note` class code should look like the following:
|
||||
|
||||
:::code language="csharp" source="../snippets/model/csharp/Notes/Models/Note.cs" id="full":::
|
||||
|
||||
Now that the `Note` model is complete, the view models can be created.
|
||||
|
||||
[![Explore the code.](~/media/code-sample.png) Explore the code for this step of the tutorial.](https://github.com/dotnet/maui-samples/tree/main/7.0/Tutorials/ConvertToMvvm/step2_model)
|
|
@ -0,0 +1,36 @@
|
|||
---
|
||||
author: adegeo
|
||||
ms.author: adegeo
|
||||
ms.date: 01/05/2023
|
||||
ms.topic: include
|
||||
---
|
||||
|
||||
The .NET MAUI developer experience typically involves creating a user interface in XAML, and then adding code-behind that operates on the user interface. Complex maintenance issues can arise as apps are modified and grow in size and scope. These issues include the tight coupling between the UI controls and the business logic, which increases the cost of making UI modifications, and the difficulty of unit testing such code.
|
||||
|
||||
The model-view-viewmodel (MVVM) pattern helps cleanly separate an app's business and presentation logic from its user interface (UI). Maintaining a clean separation between app logic and the UI helps address numerous development issues and makes an app easier to test, maintain, and evolve. It can also significantly improve code reuse opportunities and allows developers and UI designers to collaborate more easily when developing their respective parts of an app.
|
||||
|
||||
## The pattern
|
||||
|
||||
There are three core components in the MVVM pattern: the model, the view, and the view model. Each serves a distinct purpose. The following diagram shows the relationships between the three components.
|
||||
|
||||
:::image type="content" source="../media/mvvm/mvvm-pattern.png" alt-text="A diagram demonstrating the parts of an MVVM-modeled application":::
|
||||
|
||||
In addition to understanding the responsibilities of each component, it's also important to understand how they interact. At a high level, the view "knows about" the view model, and the view model "knows about" the model, but the model is unaware of the view model, and the view model is unaware of the view. Therefore, the view model isolates the view from the model, and allows the model to evolve independently of the view.
|
||||
|
||||
The key to using MVVM effectively lies in understanding how to factor app code into the correct classes and how the classes interact.
|
||||
|
||||
## View
|
||||
|
||||
The view is responsible for defining the structure, layout, and appearance of what the user sees on screen. Ideally, each view is defined in XAML, with a limited code-behind that doesn't contain business logic. However, in some cases, the code-behind might contain UI logic that implements visual behavior that is difficult to express in XAML, such as animations.
|
||||
|
||||
## ViewModel
|
||||
|
||||
The view model implements properties and commands to which the view can data bind to, and notifies the view of any state changes through change notification events. The properties and commands that the view model provides define the functionality to be offered by the UI, but the view determines how that functionality is to be displayed.
|
||||
|
||||
The view model is also responsible for coordinating the view's interactions with any model classes that are required. There's typically a one-to-many relationship between the view model and the model classes.
|
||||
|
||||
Each view model provides data from a model in a form that the view can easily consume. To accomplish this, the view model sometimes performs data conversion. Placing this data conversion in the view model is a good idea because it provides properties that the view can bind to. For example, the view model might combine the values of two properties to make it easier to display by the view.
|
||||
|
||||
## Model
|
||||
|
||||
Model classes are non-visual classes that encapsulate the app's data. Therefore, the model can be thought of as representing the app's domain model, which usually includes a data model along with business and validation logic.
|
|
@ -0,0 +1,39 @@
|
|||
---
|
||||
author: adegeo
|
||||
ms.author: adegeo
|
||||
ms.date: 01/05/2023
|
||||
ms.topic: include
|
||||
---
|
||||
|
||||
Skip this step if:
|
||||
|
||||
- You downloaded the starter project in the previous step.
|
||||
- Completed the [previous tutorial series](../../notes-app/index.yml) before 2023.
|
||||
|
||||
To upgrade your app to .NET 7:
|
||||
|
||||
01. Open the _Notes.csproj_ file in Visual Studio, Visual Studio Code, or any other text editor.
|
||||
01. Find the `Project/PropertyGroup/TargetFrameworks` element. If it matches the snippet below, you can skip this tutorial step, otherwise, change it to the following:
|
||||
|
||||
```xml
|
||||
<TargetFrameworks>net7.0-android;net7.0-ios;net7.0-maccatalyst</TargetFrameworks>
|
||||
```
|
||||
|
||||
01. Find the `Project/PropertyGroup/TargetFrameworks` element with a `condition` specifying Windows, and change it to the following:
|
||||
|
||||
```xml
|
||||
<TargetFrameworks Condition="$([MSBuild]::IsOSPlatform('windows'))">$(TargetFrameworks);net7.0-windows10.0.19041.0</TargetFrameworks>
|
||||
```
|
||||
|
||||
01. Change the minimum operating system versions required for iOS and Mac. Replace the two `SupportedOSPlatformVersion` elements with the following:
|
||||
|
||||
```xml
|
||||
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">11.0</SupportedOSPlatformVersion>
|
||||
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'maccatalyst'">13.1</SupportedOSPlatformVersion>
|
||||
```
|
||||
|
||||
01. Save the project file.
|
||||
01. Close Visual Studio if you opened the project with Visual Studio.
|
||||
01. Delete the _./bin_ and _./obj_ folders in the same folder as the _Notes.csproj_ file.
|
||||
|
||||
[![Explore the code.](~/media/code-sample.png) Explore the code for this step of the tutorial.](https://github.com/dotnet/maui-samples/tree/main/7.0/Tutorials/ConvertToMvvm/step1_upgrade)
|
|
@ -0,0 +1,91 @@
|
|||
---
|
||||
author: adegeo
|
||||
ms.author: adegeo
|
||||
ms.date: 01/05/2023
|
||||
ms.topic: include
|
||||
no-loc: ["communitytoolkit", "CommunityToolkit.Mvvm", "AllNotes", "Notes", "About"]
|
||||
---
|
||||
|
||||
Before adding view models to the project, add a reference to the MVVM Community Toolkit. This library is available on NuGet, and provides types and systems that help implement the MVVM pattern.
|
||||
|
||||
01. In the **Solution Explorer** pane of Visual Studio, right-click on the **Notes** project > **Manage NuGet Packages**.
|
||||
01. Select the **Browse** tab.
|
||||
01. Search for **communitytoolkit mvvm** and select the `CommunityToolkit.Mvvm` package, which should be the first result.
|
||||
01. Make sure that at least version 8 is selected. This tutorial was written using version **8.0.0**.
|
||||
01. Next, select **Install** and accept any prompts that are displayed.
|
||||
|
||||
:::image type="content" source="../media/viewmodel-about/nuget.png" alt-text="Searching for the CommunityToolkit.Mvvm package in NuGet.":::
|
||||
|
||||
Now you're ready to start updating the project by adding view models.
|
||||
|
||||
## Decouple with view models
|
||||
|
||||
The view-to-viewmodel relationship relies heavily on the binding system provided by .NET Multi-platform App UI (.NET MAUI). The app is already using binding in the views to display a list of notes and to present the text and date of a single note. The app logic is currently provided by the view's code-behind and is directly tied to the view. For example, when a user is editing a note and presses the **Save** button, the `Clicked` event for the button is raised. Then, the code-behind for the event handler saves the note text to a file and navigates to the previous screen.
|
||||
|
||||
Having app logic in the code-behind of a view can become an issue when the view changes. For example if the button is replaced with a different input control, or the name of a control is changed, event handlers may become invalid. Regardless of how the view is designed, the purpose of the view is to invoke some sort of app logic and to present information to the user. For this app, the `Save` button is saving the note and then navigating back to the previous screen.
|
||||
|
||||
The viewmodel gives the app a specific place to put the app logic regardless of how the UI is designed or how the data is being loaded or saved. The viewmodel is the glue that represents and interacts with the data model on behalf of the view.
|
||||
|
||||
The view models are stored in a _ViewModels_ folder.
|
||||
|
||||
01. Find the **Solution Explorer** pane of Visual Studio.
|
||||
01. Right-click on the **Notes** project and select **Add** > **New Folder**. Name the folder **ViewModels**.
|
||||
01. Right-click on the **ViewModels** folder > **Add** > **Class** and name it **AboutViewModel.cs**.
|
||||
01. Repeat the previous step and create two more view models:
|
||||
- **NoteViewModel.cs**
|
||||
- **NotesViewModel.cs**
|
||||
|
||||
Your project structure should look like the following image:
|
||||
|
||||
:::image type="content" source="../media/viewmodel-about/class-structure-1.png" alt-text="Solution explorer showing MVVM folders.":::
|
||||
|
||||
## About viewmodel and About view
|
||||
|
||||
The **About view** displays some data on the screen and optionally navigates to a website with more information. Since this view doesn't have any data to change, like with a text entry control or selecting items from a list, it's a good candidate to demonstrate adding a viewmodel. For the **About viewmodel**, there isn't a backing model.
|
||||
|
||||
Create the **About viewmodel**:
|
||||
|
||||
01. In the **Solution Explorer** pane of Visual Studio, double-click on **ViewModels\\AboutViewModel.cs**.
|
||||
01. Paste in the following code:
|
||||
|
||||
:::code language="csharp" source="../snippets/viewmodel-shared/csharp/Notes/ViewModels/AboutViewModel.cs":::
|
||||
|
||||
The previous code snippet contains some properties that represent information about the app, such as the name and version. This snippet is exactly the same as the **About model** you deleted earlier. However, this viewmodel contains a new concept, the `ShowMoreInfoCommand` command property.
|
||||
|
||||
Commands are bindable actions that invoke code, and are a great place to put app logic. In this example, the `ShowMoreInfoCommand` points to the `ShowMoreInfo` method, which opens the web browser to a specific page. You'll learn more about the command system in the next section.
|
||||
|
||||
### About view
|
||||
|
||||
The **About view** needs to be changed slightly to hook it up to the viewmodel that was created in the previous section. In the _Views\\AboutPage.xaml_ file, apply the following changes:
|
||||
|
||||
- Update the `xmlns:models` XML namespace to `xmlns:viewModels` and target the `Notes.ViewModels` .NET namespace.
|
||||
- Change the `ContentPage.BindingContext` property to a new instance of the `About` viewmodel.
|
||||
- Remove the button's `Clicked` event handler and use the `Command` property.
|
||||
|
||||
Update the **About view**:
|
||||
|
||||
01. In the **Solution Explorer** pane of Visual Studio, double-click on **Views\\AboutPage.xaml**.
|
||||
01. Paste in the following code:
|
||||
|
||||
:::code language="xaml" source="../snippets/viewmodel-shared/csharp/Notes/Views/AboutPage.xaml" highlight="4,7,19":::
|
||||
|
||||
The previous code snippet highlights the lines that have changed in this version of the view.
|
||||
|
||||
Notice that the button is using the `Command` property. Many controls have a `Command` property that is invoked when the user interacts with the control. When used with a button, the command is invoked when a user presses the button, similar to how the `Clicked` event handler is invoked, except that you can bind `Command` to a property in the viewmodel.
|
||||
|
||||
In this view, when the user presses the button, the `Command` is invoked. The `Command` is bound to the `ShowMoreInfoCommand` property in the viewmodel, and when invoked, runs the code in the `ShowMoreInfo` method, which opens the web browser to a specific page.
|
||||
|
||||
### Clean up the About code-behind
|
||||
|
||||
The `ShowMoreInfo` button isn't using the event handler, so the `LearnMore_Clicked` code should be removed from the _Views\\AboutPage.xaml.cs_ file. Delete that code, the class should only contain the constructor:
|
||||
|
||||
01. In the **Solution Explorer** pane of Visual Studio, double-click on **Views\\AboutPage.xaml.cs**.
|
||||
|
||||
> [!TIP]
|
||||
> You may need to expand **Views\\AboutPage.xaml** to show the file.
|
||||
|
||||
01. Replace the code with the following snippet:
|
||||
|
||||
:::code language="csharp" source="../snippets/viewmodel-shared/csharp/Notes/Views/AboutPage.xaml.cs":::
|
||||
|
||||
[![Explore the code.](~/media/code-sample.png) Explore the code for this step of the tutorial.](https://github.com/dotnet/maui-samples/tree/main/7.0/Tutorials/ConvertToMvvm/step3_viewmodel_about)
|
|
@ -0,0 +1,130 @@
|
|||
---
|
||||
author: adegeo
|
||||
ms.author: adegeo
|
||||
ms.date: 01/05/2023
|
||||
ms.topic: include
|
||||
no-loc: ["communitytoolkit", "CommunityToolkit.Mvvm", "AllNotes", "Notes", "About"]
|
||||
---
|
||||
|
||||
The goal of updating the **Note view** is to move as much functionality as possible out of the XAML code-behind and put it in the **Note viewmodel**.
|
||||
|
||||
## Note viewmodel
|
||||
|
||||
Based on what the **Note view** requires, the **Note viewmodel** needs to provide the following items:
|
||||
|
||||
- The text of the note.
|
||||
- The date/time the note was created or last updated.
|
||||
- A command that saves the note.
|
||||
- A command that deletes the note.
|
||||
|
||||
Create the **Note viewmodel**:
|
||||
|
||||
01. In the **Solution Explorer** pane of Visual Studio, double-click on **ViewModels\\NoteViewModel.cs**.
|
||||
01. Replace the code in this file with the following snippet:
|
||||
|
||||
```csharp
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace Notes.ViewModels;
|
||||
|
||||
internal class NoteViewModel : ObservableObject, IQueryAttributable
|
||||
{
|
||||
private Models.Note _note;
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
This code is the blank `Note` viewmodel where you'll add properties and commands to support the `Note` view. Notice that the `CommunityToolkit.Mvvm.ComponentModel` namespace is being imported. This namespace provides the `ObservableObject` used as the base class. You'll learn more about `ObservableObject` in the next step. The `CommunityToolkit.Mvvm.Input` namespace is also imported. This namespace provides some command-types that invoke methods asynchronously.
|
||||
|
||||
The `Models.Note` model is being stored as a private field. The properties and methods of this class will use this field.
|
||||
|
||||
01. Add the following properties to the class:
|
||||
|
||||
:::code language="csharp" source="../snippets/viewmodel-shared/csharp/Notes/ViewModels/NoteViewModel.cs" id="properties":::
|
||||
|
||||
The `Date` and `Identifier` properties are simple properties that just retrieve the corresponding values from the model.
|
||||
|
||||
> [!TIP]
|
||||
> For properties, the `=>` syntax creates a get-only property where the statement to the right of `=>` must evaluate to a value to return.
|
||||
|
||||
The `Text` property first checks if the value being set is a different value. If the value is different, that value is passed on to the model's property, and the `OnPropertyChanged` method is called.
|
||||
|
||||
The `OnPropertyChanged` method is provided by the `ObservableObject` base class. This method uses the name of the calling code, in this case, the property name of **Text**, and raises the `ObservableObject.PropertyChanged` event. This event supplies the name of the property to any event subscribers. The binding system provided by .NET MAUI recognizes this event, and updates any related bindings in the UI. For the **Note viewmodel**, when the `Text` property changes, the event is raised, and any UI element that is bound to the `Text` property is notified that the property changed.
|
||||
|
||||
01. Add the following command-properties to the class, which are the commands that the view can bind to:
|
||||
|
||||
:::code language="csharp" source="../snippets/viewmodel-shared/csharp/Notes/ViewModels/NoteViewModel.cs" id="commands":::
|
||||
|
||||
01. Add the following constructors to the class:
|
||||
|
||||
:::code language="csharp" source="../snippets/viewmodel-shared/csharp/Notes/ViewModels/NoteViewModel.cs" id="ctor":::
|
||||
|
||||
These two constructors are used to either create the viewmodel with a new backing model, which is an empty note, or to create a viewmodel that uses the specified model instance.
|
||||
|
||||
The constructors also setup the commands for the viewmodel. Next, add the code for these commands.
|
||||
|
||||
01. Add the `Save` and `Load` methods:
|
||||
|
||||
:::code language="csharp" source="../snippets/viewmodel-shared/csharp/Notes/ViewModels/NoteViewModel.cs" id="command_methods":::
|
||||
|
||||
These methods are invoked by associated commands. They perform the related actions on the model and make the app navigate to the previous page. A query string parameter is added to the `..` navigation path, indicating which action was taken and the note's unique identifier.
|
||||
|
||||
01. Next, add the `ApplyQueryAttributes` method to the class, which satisfies the requirements of the `IQueryAttributable` interface:
|
||||
|
||||
:::code language="csharp" source="../snippets/viewmodel-shared/csharp/Notes/ViewModels/NoteViewModel.cs" id="iquery":::
|
||||
|
||||
When a page, or the binding context of a page, implements this interface, the query string parameters used in navigation are passed to the `ApplyQueryAttributes` method. This viewmodel is used as the binding context for the **Note view**. When the **Note view** is navigated to, the view's binding context (this viewmodel) is passed the query string parameters used during navigation.
|
||||
|
||||
This code checks if the `load` key was provided in the `query` dictionary. If this key is found, the value should be the identifier (the file name) of the note to load. That note is loaded and set as the underlying model object of this viewmodel instance.
|
||||
|
||||
01. Finally, add these two helper methods to the class:
|
||||
|
||||
:::code language="csharp" source="../snippets/viewmodel-shared/csharp/Notes/ViewModels/NoteViewModel.cs" id="helpers":::
|
||||
|
||||
The `Reload` method is a helper method that refreshes the backing model object, reloading it from device storage
|
||||
|
||||
The `RefreshProperties` method is another helper method to ensure that any subscribers bound to this object are notified that the `Text` and `Date` properties have changed. Since the underlying model (the `_note` field) is changed when the note is loaded during navigation, the `Text` and `Date` properties aren't actually set to new values. Since these properties aren't directly set, any bindings attached to those properties wouldn't be notified because `OnPropertyChanged` isn't called for each property. `RefreshProperties` ensures bindings to these properties are refreshed.
|
||||
|
||||
The code for the class should look like the following snippet:
|
||||
|
||||
:::code language="csharp" source="../snippets/viewmodel-shared/csharp/Notes/ViewModels/NoteViewModel.cs" id="full":::
|
||||
|
||||
## Note view
|
||||
|
||||
Now that the viewmodel has been created, update the **Note view**. In the _Views\\NotePage.xaml_ file, apply the following changes:
|
||||
|
||||
- Add the `xmlns:viewModels` XML namespace that targets the `Notes.ViewModels` .NET namespace.
|
||||
- Add a `BindingContext` to the page.
|
||||
- Remove the delete and save button `Clicked` event handlers and replace them with commands.
|
||||
|
||||
Update the **Note view**:
|
||||
|
||||
01. In the **Solution Explorer** pane of Visual Studio, double-click on **Views\\NotePage.xaml** to open the XAML editor.
|
||||
01. Paste in the following code:
|
||||
|
||||
:::code language="xaml" source="../snippets/viewmodel-shared/csharp/Notes/Views/NotePage.xaml" highlight="4,7-9,18,22":::
|
||||
|
||||
Previously, this view didn't declare a binding context, as it was supplied by the code-behind of the page itself. Setting the binding context directly in the XAML provides two things:
|
||||
|
||||
- At run-time, when the page is navigated to, it displays a blank note. This is because the parameterless constructor for the binding context, the viewmodel, is invoked. If you remember correctly, the parameterless constructor for the **Note viewmodel** creates a blank note.
|
||||
|
||||
- The intellisense in the XAML editor shows the available properties as soon as you start typing `{Binding` syntax. The syntax is also validated and alerts you of an invalid value. Try changing the binding syntax for the `SaveCommand` to `Save123Command`. If you hover the mouse cursor over the text, you'll notice that a tooltip is displayed informing you that **Save123Command** isn't found. This notification isn't considered an error because bindings are dynamic, it's really a small warning that may help you notice when you typed the wrong property.
|
||||
|
||||
If you changed the **SaveCommand** to a different value, restore it now.
|
||||
|
||||
## Clean up the Note code-behind
|
||||
|
||||
Now that the interaction with the view has changed from event handlers to commands, open the _Views\\NotePage.xaml.cs_ file and replace all the code with a class that only contains the constructor:
|
||||
|
||||
01. In the **Solution Explorer** pane of Visual Studio, double-click on **Views\\NotePage.xaml.cs**.
|
||||
|
||||
> [!TIP]
|
||||
> You may need to expand **Views\\NotePage.xaml** to show the file.
|
||||
|
||||
01. Replace the code with the following snippet:
|
||||
|
||||
:::code language="csharp" source="../snippets/viewmodel-shared/csharp/Notes/Views/NotePage.xaml.cs":::
|
||||
|
||||
[![Explore the code.](~/media/code-sample.png) Explore the code for this step of the tutorial.](https://github.com/dotnet/maui-samples/tree/main/7.0/Tutorials/ConvertToMvvm/step4_viewmodel_note)
|
|
@ -0,0 +1,119 @@
|
|||
---
|
||||
author: adegeo
|
||||
ms.author: adegeo
|
||||
ms.date: 01/05/2023
|
||||
ms.topic: include
|
||||
no-loc: ["communitytoolkit", "CommunityToolkit.Mvvm", "AllNotes", "Notes", "About"]
|
||||
---
|
||||
|
||||
The final viewmodel-view pair is the **Notes viewmodel** and **AllNotes view**. Currently though, the view is binding directly to the model, which was deleted at the start of this tutorial. The goal in updating the **AllNotes view** is to move as much functionality as possible out of the XAML code-behind and put it in the viewmodel. Again, the benefit being that the view can change its design with little effect to your code.
|
||||
|
||||
## Notes viewmodel
|
||||
|
||||
Based on what the **AllNotes view** is going to display and what interactions the user will do, the **Notes viewmodel** must provide the following items:
|
||||
|
||||
- A collection of notes.
|
||||
- A command to handle navigating to a note.
|
||||
- A command to create a new note.
|
||||
- Update the list of notes when one is created, deleted, or changed.
|
||||
|
||||
Create the **Notes viewmodel**:
|
||||
|
||||
01. In the **Solution Explorer** pane of Visual Studio, double-click on **ViewModels\\NotesViewModel.cs**.
|
||||
01. Replace the code in this file with the following code:
|
||||
|
||||
```csharp
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace Notes.ViewModels;
|
||||
|
||||
internal class NotesViewModel: IQueryAttributable
|
||||
{
|
||||
}
|
||||
```
|
||||
|
||||
This code is the blank `NotesViewModel` where you'll add properties and commands to support the `AllNotes` view.
|
||||
|
||||
01. In the `NotesViewModel` class code, add the following properties:
|
||||
|
||||
:::code language="csharp" source="../snippets/viewmodel-shared/csharp/Notes/ViewModels/NotesViewModel.cs" id="properties":::
|
||||
|
||||
The `AllNotes` property is an `ObservableCollection` that stores all of the notes loaded from the device. The two commands will be used by the view to trigger the actions of creating a note or selecting an existing note.
|
||||
|
||||
01. Add a parameterless constructor to the class, which initializes the commands and loads the notes from the model:
|
||||
|
||||
:::code language="csharp" source="../snippets/viewmodel-shared/csharp/Notes/ViewModels/NotesViewModel.cs" id="ctor":::
|
||||
|
||||
Notice that the `AllNotes` collection uses the `Models.Note.LoadAll` method to fill the observable collection with notes. The `LoadAll` method returns the notes as the `Models.Note` type, but the observable collection is a collection of `ViewModels.NoteViewModel` types. The code uses the `Select` Linq extension to create viewmodel instances from the note models returned from `LoadAll`.
|
||||
|
||||
01. Create the methods targeted by the commands:
|
||||
|
||||
:::code language="csharp" source="../snippets/viewmodel-shared/csharp/Notes/ViewModels/NotesViewModel.cs" id="commands":::
|
||||
|
||||
Notice that the `NewNoteAsync` method doesn't take a parameter while the `SelectNoteAsync` does. Commands can optionally have a single parameter that is provided when the command is invoked. For the `SelectNoteAsync` method, the parameter represents the note that's being selected.
|
||||
|
||||
01. Finally, implement the `IQueryAttributable.ApplyQueryAttributes` method:
|
||||
|
||||
:::code language="csharp" source="../snippets/viewmodel-shared/csharp/Notes/ViewModels/NotesViewModel.cs" id="query":::
|
||||
|
||||
The **Note viewmodel** created in the previous tutorial step, used navigation when the note was saved or deleted. The viewmodel navigated back to the **AllNotes view**, which this viewmodel is associated with. This code detects if the query string contains either the `deleted` or `saved` key. The value of the key is the unique identifier of the note.
|
||||
|
||||
If the note was **deleted**, that note is matched in the `AllNotes` collection by the provided identifier, and removed.
|
||||
|
||||
There are two possible reasons a note is **saved**. The note was either just created or an existing note was changed. If the note is already in the `AllNotes` collection, it's a note that was updated. In this case, the note instance in the collection just needs to be refreshed. If the note is missing from the collection, it's a new note and must be added to the collection.
|
||||
|
||||
The code for the class should look like the following snippet:
|
||||
|
||||
:::code language="csharp" source="../snippets/viewmodel-shared/csharp/Notes/ViewModels/NotesViewModel.cs" id="full":::
|
||||
|
||||
## AllNotes view
|
||||
|
||||
Now that the viewmodel has been created, update the **AllNotes view** to point to the viewmodel properties. In the _Views\\AllNotesPage.xaml_ file, apply the following changes:
|
||||
|
||||
- Add the `xmlns:viewModels` XML namespace that targets the `Notes.ViewModels` .NET namespace.
|
||||
- Add a `BindingContext` to the page.
|
||||
- Remove the toolbar button's `Clicked` event and use the `Command` property.
|
||||
- Change the `CollectionView` to use commanding to react to when the selected item changes.
|
||||
|
||||
Update the **AllNotes view**:
|
||||
|
||||
01. In the **Solution Explorer** pane of Visual Studio, double-click on **Views\\AllNotesPage.xaml**.
|
||||
01. Paste in the following code:
|
||||
|
||||
:::code language="xaml" source="../snippets/viewmodel-shared/csharp/Notes/Views/AllNotesPage.xaml" id="full" highlight="4,8,13,21-22":::
|
||||
|
||||
The toolbar no longer uses the `Clicked` event and instead uses a command.
|
||||
|
||||
The `CollectionView` supports commanding with the `SelectionChangedCommand` and `SelectionChangedCommandParameter` properties. In the updated XAML, the `SelectionChangedCommand` property is bound to the viewmodel's `SelectNodeCommand`, which means the command is invoked when the selected item changes. When the command is invoked, the `SelectionChangedCommandParameter` property value is passed to the command.
|
||||
|
||||
Look at the binding used for the `CollectionView`:
|
||||
|
||||
:::code language="xaml" source="../snippets/viewmodel-shared/csharp/Notes/Views/AllNotesPage.xaml" id="CollectionView" highlight="5-6":::
|
||||
|
||||
The `SelectionChangedCommandParameter` property uses `Source={RelativeSource Self}` binding. The `Self` references the current object, which is the `CollectionView`. Notice that the binding path is the `SelectedItem` property. When the command is invoked by changing the selected item, the `SelectNoteCommand` command is invoked and the selected item is passed to the command as a parameter.
|
||||
|
||||
## Clean up the AllNotes code-behind
|
||||
|
||||
Now that the interaction with the view has changed from event handlers to commands, open the _Views\\AllNotesPage.xaml.cs_ file and replace all the code with a class that only contains the constructor:
|
||||
|
||||
01. In the **Solution Explorer** pane of Visual Studio, double-click on **Views\\AllNotesPage.xaml.cs**.
|
||||
|
||||
> [!TIP]
|
||||
> You may need to expand **Views\\AllNotesPage.xaml** to show the file.
|
||||
|
||||
01. Replace the code with the following snippet:
|
||||
|
||||
:::code language="csharp" source="../snippets/viewmodel-shared/csharp/Notes/Views/AllNotesPage.xaml.cs":::
|
||||
|
||||
## Run the app
|
||||
|
||||
You can now run the app, and everything is working. However, there are two problems with how the app behaves:
|
||||
|
||||
- If you select a note, which opens the editor, press **Save**, and then try to select the same note, it doesn't work.
|
||||
- Whenever a note is changed or added, the list of notes isn't reordered to show the latest notes at the top.
|
||||
|
||||
These two problems are fixed in the next tutorial step.
|
||||
|
||||
[![Explore the code.](~/media/code-sample.png) Explore the code for this step of the tutorial.](https://github.com/dotnet/maui-samples/tree/main/7.0/Tutorials/ConvertToMvvm/step5_viewmodel_notes)
|
|
@ -0,0 +1,62 @@
|
|||
### YamlMime:Tutorial
|
||||
title: "Upgrade your app with MVVM concepts"
|
||||
metadata:
|
||||
title: "Tutorial: Use the MVVM toolkit to update the Notes app"
|
||||
description: Upgrade the Notes app from the previous tutorial with the MVVM Community Toolkit.
|
||||
audience: Developer
|
||||
level: Beginner
|
||||
ms.topic: interactive-tutorial
|
||||
ms.date: 12/21/2022
|
||||
author: adegeo
|
||||
ms.author: adegeo
|
||||
items:
|
||||
- durationInMinutes: 1
|
||||
content: |
|
||||
[!INCLUDE [](includes/intro.md)]
|
||||
|
||||
- title: Upgrade the app from .NET 6 to .NET 7
|
||||
durationInMinutes: 3
|
||||
content: |
|
||||
[!INCLUDE [](includes/upgrade.md)]
|
||||
|
||||
- title: Understand MVVM
|
||||
durationInMinutes: 3
|
||||
content: |
|
||||
[!INCLUDE [](includes/mvvm.md)]
|
||||
|
||||
- title: Update the model
|
||||
durationInMinutes: 7
|
||||
content: |
|
||||
[!INCLUDE [](includes/model.md)]
|
||||
|
||||
- title: Create the About viewmodel
|
||||
durationInMinutes: 10
|
||||
content: |
|
||||
[!INCLUDE [](includes/viewmodel-about.md)]
|
||||
|
||||
- title: Create the Note viewmodel
|
||||
durationInMinutes: 15
|
||||
content: |
|
||||
[!INCLUDE [](includes/viewmodel-note.md)]
|
||||
|
||||
- title: Create the Notes viewmodel
|
||||
durationInMinutes: 10
|
||||
content: |
|
||||
[!INCLUDE [](includes/viewmodel-notes.md)]
|
||||
|
||||
- title: Fix the app behavior
|
||||
durationInMinutes: 5
|
||||
content: |
|
||||
[!INCLUDE [](includes/bugs.md)]
|
||||
|
||||
- content: |
|
||||
Your app is now using MVVM patterns!
|
||||
|
||||
## Next steps
|
||||
|
||||
The following links provide more information related to some of the concepts you learned in this tutorial:
|
||||
|
||||
- [Enterprise Application Patterns Using .NET MAUI - MVVM](/architecture/maui/mvvm)
|
||||
- [Data binding and MVVM](../../xaml/fundamentals/mvvm.md)
|
||||
- [ApplyQueryAttributes - Process navigation data using a single method](../../fundamentals/shell/navigation.md#process-navigation-data-using-a-single-method)
|
||||
- [ObservableObject - How it works](/dotnet/communitytoolkit/mvvm/observableobject)
|
После Ширина: | Высота: | Размер: 13 KiB |
После Ширина: | Высота: | Размер: 14 KiB |
После Ширина: | Высота: | Размер: 22 KiB |
|
@ -0,0 +1,14 @@
|
|||
<?xml version = "1.0" encoding = "UTF-8" ?>
|
||||
<Application xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
xmlns:local="clr-namespace:Notes"
|
||||
x:Class="Notes.App">
|
||||
<Application.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="Resources/Styles/Colors.xaml" />
|
||||
<ResourceDictionary Source="Resources/Styles/Styles.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
</Application>
|
|
@ -0,0 +1,11 @@
|
|||
namespace Notes;
|
||||
|
||||
public partial class App : Application
|
||||
{
|
||||
public App()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
MainPage = new AppShell();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<Shell
|
||||
x:Class="Notes.AppShell"
|
||||
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
xmlns:views="clr-namespace:Notes.Views"
|
||||
Shell.FlyoutBehavior="Disabled">
|
||||
|
||||
<TabBar>
|
||||
<ShellContent
|
||||
Title="Notes"
|
||||
ContentTemplate="{DataTemplate views:AllNotesPage}"
|
||||
Icon="{OnPlatform 'icon_notes.png', iOS='icon_notes_ios.png', MacCatalyst='icon_notes_ios.png'}" />
|
||||
|
||||
<ShellContent
|
||||
Title="About"
|
||||
ContentTemplate="{DataTemplate views:AboutPage}"
|
||||
Icon="{OnPlatform 'icon_about.png', iOS='icon_about_ios.png', MacCatalyst='icon_about_ios.png'}" />
|
||||
</TabBar>
|
||||
|
||||
</Shell>
|
|
@ -0,0 +1,11 @@
|
|||
namespace Notes;
|
||||
|
||||
public partial class AppShell : Shell
|
||||
{
|
||||
public AppShell()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
Routing.RegisterRoute(nameof(Views.NotePage), typeof(Views.NotePage));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
namespace Notes;
|
||||
|
||||
public static class MauiProgram
|
||||
{
|
||||
public static MauiApp CreateMauiApp()
|
||||
{
|
||||
var builder = MauiApp.CreateBuilder();
|
||||
builder
|
||||
.UseMauiApp<App>()
|
||||
.ConfigureFonts(fonts =>
|
||||
{
|
||||
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
|
||||
fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
|
||||
});
|
||||
|
||||
return builder.Build();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
namespace Notes.Models;
|
||||
|
||||
internal class Note
|
||||
{
|
||||
public string Filename { get; set; }
|
||||
public string Text { get; set; }
|
||||
public DateTime Date { get; set; }
|
||||
|
||||
public void Save() =>
|
||||
File.WriteAllText(System.IO.Path.Combine(FileSystem.AppDataDirectory, Filename), Text);
|
||||
|
||||
public void Delete() =>
|
||||
File.Delete(System.IO.Path.Combine(FileSystem.AppDataDirectory, Filename));
|
||||
|
||||
public Note()
|
||||
{
|
||||
Filename = $"{Path.GetRandomFileName()}.notes.txt";
|
||||
Date = DateTime.Now;
|
||||
Text = "";
|
||||
}
|
||||
|
||||
public static Note Load(string filename)
|
||||
{
|
||||
filename = System.IO.Path.Combine(FileSystem.AppDataDirectory, filename);
|
||||
|
||||
if (!File.Exists(filename))
|
||||
throw new FileNotFoundException("Unable to find file on local storage.", filename);
|
||||
|
||||
return
|
||||
new()
|
||||
{
|
||||
Filename = Path.GetFileName(filename),
|
||||
Text = File.ReadAllText(filename),
|
||||
Date = File.GetLastWriteTime(filename)
|
||||
};
|
||||
}
|
||||
|
||||
public static IEnumerable<Note> LoadAll()
|
||||
{
|
||||
// Get the folder where the notes are stored.
|
||||
string appDataPath = FileSystem.AppDataDirectory;
|
||||
|
||||
// Use Linq extensions to load the *.notes.txt files.
|
||||
return Directory
|
||||
|
||||
// Select the file names from the directory
|
||||
.EnumerateFiles(appDataPath, "*.notes.txt")
|
||||
|
||||
// Each file name is used to load a note
|
||||
.Select(filename => Note.Load(Path.GetFileName(filename)))
|
||||
|
||||
// With the final collection of notes, order them by date
|
||||
.OrderByDescending(note => note.Date);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net7.0-android;net7.0-ios;net7.0-maccatalyst</TargetFrameworks>
|
||||
<TargetFrameworks Condition="$([MSBuild]::IsOSPlatform('windows'))">$(TargetFrameworks);net7.0-windows10.0.19041.0</TargetFrameworks>
|
||||
<!-- Uncomment to also build the tizen app. You will need to install tizen by following this: https://github.com/Samsung/Tizen.NET -->
|
||||
<!-- <TargetFrameworks>$(TargetFrameworks);net6.0-tizen</TargetFrameworks> -->
|
||||
<OutputType>Exe</OutputType>
|
||||
<RootNamespace>Notes</RootNamespace>
|
||||
<UseMaui>true</UseMaui>
|
||||
<SingleProject>true</SingleProject>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
|
||||
<!-- Display name -->
|
||||
<ApplicationTitle>Notes</ApplicationTitle>
|
||||
|
||||
<!-- App Identifier -->
|
||||
<ApplicationId>com.companyname.notes</ApplicationId>
|
||||
<ApplicationIdGuid>d656cfd3-d70c-439a-bc43-b90ff2fd6523</ApplicationIdGuid>
|
||||
|
||||
<!-- Versions -->
|
||||
<ApplicationDisplayVersion>1.0</ApplicationDisplayVersion>
|
||||
<ApplicationVersion>1</ApplicationVersion>
|
||||
|
||||
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">11.0</SupportedOSPlatformVersion>
|
||||
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'maccatalyst'">13.1</SupportedOSPlatformVersion>
|
||||
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">21.0</SupportedOSPlatformVersion>
|
||||
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">10.0.17763.0</SupportedOSPlatformVersion>
|
||||
<TargetPlatformMinVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">10.0.17763.0</TargetPlatformMinVersion>
|
||||
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'tizen'">6.5</SupportedOSPlatformVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<!-- App Icon -->
|
||||
<MauiIcon Include="Resources\AppIcon\appicon.svg" ForegroundFile="Resources\AppIcon\appiconfg.svg" Color="#512BD4" />
|
||||
|
||||
<!-- Splash Screen -->
|
||||
<MauiSplashScreen Include="Resources\Splash\splash.svg" Color="#512BD4" BaseSize="128,128" />
|
||||
|
||||
<!-- Images -->
|
||||
<MauiImage Include="Resources\Images\*" />
|
||||
<MauiImage Update="Resources\Images\dotnet_bot.svg" BaseSize="168,208" />
|
||||
|
||||
<!-- Custom Fonts -->
|
||||
<MauiFont Include="Resources\Fonts\*" />
|
||||
|
||||
<!-- Raw Assets (also remove the "Resources\Raw" prefix) -->
|
||||
<MauiAsset Include="Resources\Raw\**" LogicalName="%(RecursiveDir)%(Filename)%(Extension)" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<MauiXaml Update="Views\AboutPage.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</MauiXaml>
|
||||
<MauiXaml Update="Views\AllNotesPage.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</MauiXaml>
|
||||
<MauiXaml Update="Views\NotePage.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</MauiXaml>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
|
@ -0,0 +1,23 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<ActiveDebugFramework>net7.0-windows10.0.19041.0</ActiveDebugFramework>
|
||||
<IsFirstTimeProjectOpen>False</IsFirstTimeProjectOpen>
|
||||
<ActiveDebugProfile>Windows Machine</ActiveDebugProfile>
|
||||
<SelectedPlatformGroup>Emulator</SelectedPlatformGroup>
|
||||
<DefaultDevice>pixel_4_-_api_33</DefaultDevice>
|
||||
<UapAppxPackageBuildMode>SideloadOnly</UapAppxPackageBuildMode>
|
||||
<AppxShowAllApps>False</AppxShowAllApps>
|
||||
<_LastSelectedProfileId>MSIX-win10-x64.pubxml</_LastSelectedProfileId>
|
||||
<PackageOptionalProjectsInIdeBuilds>False</PackageOptionalProjectsInIdeBuilds>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|net6.0-android|AnyCPU'">
|
||||
<DebuggerFlavor>ProjectDebugger</DebuggerFlavor>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|net6.0-android|AnyCPU'">
|
||||
<DebuggerFlavor>ProjectDebugger</DebuggerFlavor>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|net7.0-android|AnyCPU'">
|
||||
<DebuggerFlavor>ProjectDebugger</DebuggerFlavor>
|
||||
</PropertyGroup>
|
||||
</Project>
|
|
@ -0,0 +1,27 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.4.32916.344
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Notes", "Notes.csproj", "{0CE9FCF7-16AA-467F-8F04-FF175FBC288D}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{0CE9FCF7-16AA-467F-8F04-FF175FBC288D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{0CE9FCF7-16AA-467F-8F04-FF175FBC288D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{0CE9FCF7-16AA-467F-8F04-FF175FBC288D}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
|
||||
{0CE9FCF7-16AA-467F-8F04-FF175FBC288D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{0CE9FCF7-16AA-467F-8F04-FF175FBC288D}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{0CE9FCF7-16AA-467F-8F04-FF175FBC288D}.Release|Any CPU.Deploy.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {C177C5C5-C67F-4457-BD0A-DF85476DC946}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<application android:allowBackup="true" android:icon="@mipmap/appicon" android:roundIcon="@mipmap/appicon_round" android:supportsRtl="true"></application>
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
</manifest>
|
|
@ -0,0 +1,10 @@
|
|||
using Android.App;
|
||||
using Android.Content.PM;
|
||||
using Android.OS;
|
||||
|
||||
namespace Notes;
|
||||
|
||||
[Activity(Theme = "@style/Maui.SplashTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize | ConfigChanges.Density)]
|
||||
public class MainActivity : MauiAppCompatActivity
|
||||
{
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
using Android.App;
|
||||
using Android.Runtime;
|
||||
|
||||
namespace Notes;
|
||||
|
||||
[Application]
|
||||
public class MainApplication : MauiApplication
|
||||
{
|
||||
public MainApplication(IntPtr handle, JniHandleOwnership ownership)
|
||||
: base(handle, ownership)
|
||||
{
|
||||
}
|
||||
|
||||
protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="colorPrimary">#512BD4</color>
|
||||
<color name="colorPrimaryDark">#2B0B98</color>
|
||||
<color name="colorAccent">#2B0B98</color>
|
||||
</resources>
|
|
@ -0,0 +1,9 @@
|
|||
using Foundation;
|
||||
|
||||
namespace Notes;
|
||||
|
||||
[Register("AppDelegate")]
|
||||
public class AppDelegate : MauiUIApplicationDelegate
|
||||
{
|
||||
protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>UIDeviceFamily</key>
|
||||
<array>
|
||||
<integer>1</integer>
|
||||
<integer>2</integer>
|
||||
</array>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
<array>
|
||||
<string>arm64</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>XSAppIconAssets</key>
|
||||
<string>Assets.xcassets/appicon.appiconset</string>
|
||||
</dict>
|
||||
</plist>
|
|
@ -0,0 +1,15 @@
|
|||
using ObjCRuntime;
|
||||
using UIKit;
|
||||
|
||||
namespace Notes;
|
||||
|
||||
public class Program
|
||||
{
|
||||
// This is the main entry point of the application.
|
||||
static void Main(string[] args)
|
||||
{
|
||||
// if you want to use a different Application Delegate class from "AppDelegate"
|
||||
// you can specify it here.
|
||||
UIApplication.Main(args, null, typeof(AppDelegate));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
using System;
|
||||
using Microsoft.Maui;
|
||||
using Microsoft.Maui.Hosting;
|
||||
|
||||
namespace Notes;
|
||||
|
||||
class Program : MauiApplication
|
||||
{
|
||||
protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
|
||||
|
||||
static void Main(string[] args)
|
||||
{
|
||||
var app = new Program();
|
||||
app.Run(args);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest package="maui-application-id-placeholder" version="0.0.0" api-version="7" xmlns="http://tizen.org/ns/packages">
|
||||
<profile name="common" />
|
||||
<ui-application appid="maui-application-id-placeholder" exec="Notes.dll" multiple="false" nodisplay="false" taskmanage="true" type="dotnet" launch_mode="single">
|
||||
<label>maui-application-title-placeholder</label>
|
||||
<icon>maui-appicon-placeholder</icon>
|
||||
<metadata key="http://tizen.org/metadata/prefer_dotnet_aot" value="true" />
|
||||
</ui-application>
|
||||
<shortcut-list />
|
||||
<privileges>
|
||||
<privilege>http://tizen.org/privilege/internet</privilege>
|
||||
</privileges>
|
||||
<dependencies />
|
||||
<provides-appdefined-privileges />
|
||||
</manifest>
|
|
@ -0,0 +1,8 @@
|
|||
<maui:MauiWinUIApplication
|
||||
x:Class="Notes.WinUI.App"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:maui="using:Microsoft.Maui"
|
||||
xmlns:local="using:Notes.WinUI">
|
||||
|
||||
</maui:MauiWinUIApplication>
|
|
@ -0,0 +1,24 @@
|
|||
using Microsoft.UI.Xaml;
|
||||
|
||||
// To learn more about WinUI, the WinUI project structure,
|
||||
// and more about our project templates, see: http://aka.ms/winui-project-info.
|
||||
|
||||
namespace Notes.WinUI;
|
||||
|
||||
/// <summary>
|
||||
/// Provides application-specific behavior to supplement the default Application class.
|
||||
/// </summary>
|
||||
public partial class App : MauiWinUIApplication
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes the singleton application object. This is the first line of authored code
|
||||
/// executed, and as such is the logical equivalent of main() or WinMain().
|
||||
/// </summary>
|
||||
public App()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
}
|
||||
|
||||
protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Package
|
||||
xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
|
||||
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
|
||||
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
|
||||
IgnorableNamespaces="uap rescap">
|
||||
|
||||
<Identity Name="maui-package-name-placeholder" Publisher="CN=User Name" Version="0.0.0.0" />
|
||||
|
||||
<Properties>
|
||||
<DisplayName>$placeholder$</DisplayName>
|
||||
<PublisherDisplayName>User Name</PublisherDisplayName>
|
||||
<Logo>$placeholder$.png</Logo>
|
||||
</Properties>
|
||||
|
||||
<Dependencies>
|
||||
<TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />
|
||||
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />
|
||||
</Dependencies>
|
||||
|
||||
<Resources>
|
||||
<Resource Language="x-generate" />
|
||||
</Resources>
|
||||
|
||||
<Applications>
|
||||
<Application Id="App" Executable="$targetnametoken$.exe" EntryPoint="$targetentrypoint$">
|
||||
<uap:VisualElements
|
||||
DisplayName="$placeholder$"
|
||||
Description="$placeholder$"
|
||||
Square150x150Logo="$placeholder$.png"
|
||||
Square44x44Logo="$placeholder$.png"
|
||||
BackgroundColor="transparent">
|
||||
<uap:DefaultTile Square71x71Logo="$placeholder$.png" Wide310x150Logo="$placeholder$.png" Square310x310Logo="$placeholder$.png" />
|
||||
<uap:SplashScreen Image="$placeholder$.png" />
|
||||
</uap:VisualElements>
|
||||
</Application>
|
||||
</Applications>
|
||||
|
||||
<Capabilities>
|
||||
<rescap:Capability Name="runFullTrust" />
|
||||
</Capabilities>
|
||||
|
||||
</Package>
|
|
@ -0,0 +1,15 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<assemblyIdentity version="1.0.0.0" name="Notes.WinUI.app"/>
|
||||
|
||||
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<windowsSettings>
|
||||
<!-- The combination of below two tags have the following effect:
|
||||
1) Per-Monitor for >= Windows 10 Anniversary Update
|
||||
2) System < Windows 10 Anniversary Update
|
||||
-->
|
||||
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/PM</dpiAware>
|
||||
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2, PerMonitor</dpiAwareness>
|
||||
</windowsSettings>
|
||||
</application>
|
||||
</assembly>
|
|
@ -0,0 +1,9 @@
|
|||
using Foundation;
|
||||
|
||||
namespace Notes;
|
||||
|
||||
[Register("AppDelegate")]
|
||||
public class AppDelegate : MauiUIApplicationDelegate
|
||||
{
|
||||
protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>UIDeviceFamily</key>
|
||||
<array>
|
||||
<integer>1</integer>
|
||||
<integer>2</integer>
|
||||
</array>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
<array>
|
||||
<string>arm64</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>XSAppIconAssets</key>
|
||||
<string>Assets.xcassets/appicon.appiconset</string>
|
||||
</dict>
|
||||
</plist>
|
|
@ -0,0 +1,15 @@
|
|||
using ObjCRuntime;
|
||||
using UIKit;
|
||||
|
||||
namespace Notes;
|
||||
|
||||
public class Program
|
||||
{
|
||||
// This is the main entry point of the application.
|
||||
static void Main(string[] args)
|
||||
{
|
||||
// if you want to use a different Application Delegate class from "AppDelegate"
|
||||
// you can specify it here.
|
||||
UIApplication.Main(args, null, typeof(AppDelegate));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<PublishDir>bin\Release\net6.0-android\publish\</PublishDir>
|
||||
<PublishProtocol>FileSystem</PublishProtocol>
|
||||
<RuntimeIdentifier>win10-x64</RuntimeIdentifier>
|
||||
<Platform>Any CPU</Platform>
|
||||
<Configuration>Release</Configuration>
|
||||
<TargetFramework>net6.0-windows10.0.19041.0</TargetFramework>
|
||||
<PublishSingleFile>false</PublishSingleFile>
|
||||
<PublishReadyToRun>false</PublishReadyToRun>
|
||||
<SelfContained>True</SelfContained>
|
||||
<PublishAppxPackage>true</PublishAppxPackage>
|
||||
<AppxPackageDir>bin\Debug\net6.0-windows10.0.19041.0\win10-x64\AppPackages\</AppxPackageDir>
|
||||
</PropertyGroup>
|
||||
</Project>
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"profiles": {
|
||||
"Windows Machine": {
|
||||
"commandName": "MsixPackage",
|
||||
"nativeDebugging": false
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg width="456" height="456" viewBox="0 0 456 456" version="1.1" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="0" y="0" width="456" height="456" fill="#512BD4" />
|
||||
</svg>
|
После Ширина: | Высота: | Размер: 228 B |
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="456" height="456" viewBox="0 0 456 456" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||
<path d="m 105.50037,281.60863 c -2.70293,0 -5.00091,-0.90042 -6.893127,-2.70209 -1.892214,-1.84778 -2.837901,-4.04181 -2.837901,-6.58209 0,-2.58722 0.945687,-4.80389 2.837901,-6.65167 1.892217,-1.84778 4.190197,-2.77167 6.893127,-2.77167 2.74819,0 5.06798,0.92389 6.96019,2.77167 1.93749,1.84778 2.90581,4.06445 2.90581,6.65167 0,2.54028 -0.96832,4.73431 -2.90581,6.58209 -1.89221,1.80167 -4.212,2.70209 -6.96019,2.70209 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />
|
||||
<path d="M 213.56111,280.08446 H 195.99044 L 149.69953,207.0544 c -1.17121,-1.84778 -2.14037,-3.76515 -2.90581,-5.75126 h -0.40578 c 0.36051,2.12528 0.54076,6.67515 0.54076,13.6496 v 65.13172 h -15.54349 v -99.36009 h 18.71925 l 44.7374,71.29798 c 1.89222,2.95695 3.1087,4.98917 3.64945,6.09751 h 0.26996 c -0.45021,-2.6325 -0.67573,-7.09015 -0.67573,-13.37293 v -64.02256 h 15.47557 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />
|
||||
<path d="m 289.25134,280.08446 h -54.40052 v -99.36009 h 52.23835 v 13.99669 h -36.15411 v 28.13085 h 33.31621 v 13.9271 h -33.31621 v 29.37835 h 38.31628 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />
|
||||
<path d="M 366.56466,194.72106 H 338.7222 v 85.3634 h -16.08423 v -85.3634 h -27.77455 v -13.99669 h 71.70124 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />
|
||||
</svg>
|
После Ширина: | Высота: | Размер: 1.8 KiB |
Двоичные данные
docs/tutorials/notes-mvvm/snippets/bugs/csharp/Resources/Fonts/OpenSans-Regular.ttf
Normal file
Двоичные данные
docs/tutorials/notes-mvvm/snippets/bugs/csharp/Resources/Fonts/OpenSans-Semibold.ttf
Normal file
|
@ -0,0 +1,93 @@
|
|||
<svg width="419" height="519" viewBox="0 0 419 519" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M284.432 247.568L284.004 221.881C316.359 221.335 340.356 211.735 355.308 193.336C382.408 159.996 372.893 108.183 372.786 107.659L398.013 102.831C398.505 105.432 409.797 167.017 375.237 209.53C355.276 234.093 324.719 246.894 284.432 247.568Z" fill="#8A6FE8"/>
|
||||
<path d="M331.954 109.36L361.826 134.245C367.145 138.676 375.055 137.959 379.497 132.639C383.928 127.32 383.211 119.41 377.891 114.969L348.019 90.0842C342.7 85.6531 334.79 86.3702 330.348 91.6896C325.917 97.0197 326.634 104.929 331.954 109.36Z" fill="#8A6FE8"/>
|
||||
<path d="M407.175 118.062L417.92 94.2263C420.735 87.858 417.856 80.4087 411.488 77.5831C405.12 74.7682 397.67 77.6473 394.845 84.0156L383.831 108.461L407.175 118.062Z" fill="#8A6FE8"/>
|
||||
<path d="M401.363 105.175L401.234 69.117C401.181 62.1493 395.498 56.541 388.53 56.5945C381.562 56.648 375.954 62.3313 376.007 69.2989L376.018 96.11L401.363 105.175Z" fill="#8A6FE8"/>
|
||||
<path d="M386.453 109.071L378.137 73.9548C376.543 67.169 369.757 62.9628 362.971 64.5575C356.185 66.1523 351.979 72.938 353.574 79.7237L362.04 115.482L386.453 109.071Z" fill="#8A6FE8"/>
|
||||
<path d="M381.776 142.261C396.359 142.261 408.181 130.44 408.181 115.857C408.181 101.274 396.359 89.4527 381.776 89.4527C367.194 89.4527 355.372 101.274 355.372 115.857C355.372 130.44 367.194 142.261 381.776 142.261Z" fill="url(#paint0_radial)"/>
|
||||
<path d="M248.267 406.979C248.513 384.727 245.345 339.561 222.376 301.736L199.922 315.372C220.76 349.675 222.323 389.715 221.841 407.182C221.798 408.627 235.263 409.933 248.267 406.979Z" fill="url(#paint1_linear)"/>
|
||||
<path d="M221.841 406.936L242.637 406.84L262.052 518.065L220.311 518.258C217.132 518.269 214.724 515.711 214.938 512.532L221.841 406.936Z" fill="#522CD5"/>
|
||||
<path d="M306.566 488.814C310.173 491.661 310.109 495.782 309.831 500.127L308.964 513.452C308.803 515.839 306.727 517.798 304.34 517.809L260.832 518.012C258.125 518.023 256.08 515.839 256.262 513.142L256.551 499.335C256.883 494.315 255.192 492.474 251.307 487.744C244.649 479.663 224.967 435.62 226.84 406.925L248.256 406.829C249.691 423.858 272.167 461.682 306.566 488.814Z" fill="url(#paint2_linear)"/>
|
||||
<path d="M309.82 500.127C310.023 497.088 310.077 494.176 308.889 491.715L254.635 491.961C256.134 494.166 256.765 496.092 256.562 499.314L256.273 513.121C256.091 515.828 258.146 518.012 260.843 517.99L304.34 517.798C306.727 517.787 308.803 515.828 308.964 513.442L309.82 500.127Z" fill="url(#paint3_radial)"/>
|
||||
<path d="M133.552 407.471C133.103 385.22 135.864 340.021 158.49 301.993L181.073 315.425C160.545 349.921 159.346 389.972 159.989 407.428C160.042 408.884 146.578 410.318 133.552 407.471Z" fill="url(#paint4_linear)"/>
|
||||
<path d="M110.798 497.152C110.765 494.187 111.204 491.575 112.457 487.23C131.882 434.132 133.52 407.364 133.52 407.364L159.999 407.246C159.999 407.246 161.819 433.512 181.716 486.427C183.289 490.195 183.471 493.641 183.674 496.831L183.792 513.816C183.803 516.374 181.716 518.483 179.158 518.494L177.873 518.504L116.781 518.782L115.496 518.793C112.927 518.804 110.83 516.728 110.819 514.159L110.798 497.152Z" fill="url(#paint5_linear)"/>
|
||||
<path d="M110.798 497.152C110.798 496.67 110.808 496.199 110.83 495.739C110.969 494.262 111.643 492.603 114.875 492.582L180.207 492.282C182.561 492.367 183.343 494.176 183.589 495.311C183.621 495.814 183.664 496.328 183.696 496.82L183.813 513.806C183.824 515.411 183.011 516.824 181.769 517.669C181.031 518.172 180.132 518.472 179.179 518.483L177.895 518.494L116.802 518.772L115.528 518.782C114.244 518.793 113.077 518.269 112.232 517.434C111.386 516.599 110.862 515.432 110.851 514.148L110.798 497.152Z" fill="url(#paint6_radial)"/>
|
||||
<path d="M314.979 246.348C324.162 210.407 318.008 181.777 318.008 181.777L326.452 181.734L326.656 181.574C314.262 115.75 256.326 66.0987 186.949 66.4198C108.796 66.773 45.7233 130.424 46.0765 208.577C46.4297 286.731 110.08 349.803 188.234 349.45C249.905 349.172 302.178 309.474 321.304 254.343C321.872 251.999 321.797 247.804 314.979 246.348Z" fill="url(#paint7_radial)"/>
|
||||
<path d="M310.237 279.035L65.877 280.148C71.3998 289.428 77.95 298.012 85.3672 305.761L290.972 304.829C298.336 297.005 304.8 288.368 310.237 279.035Z" fill="#D8CFF7"/>
|
||||
<path d="M235.062 312.794L280.924 312.585L280.74 272.021L234.877 272.23L235.062 312.794Z" fill="#512BD4"/>
|
||||
<path d="M243.001 297.626C242.691 297.626 242.434 297.53 242.22 297.327C242.006 297.123 241.899 296.866 241.899 296.588C241.899 296.299 242.006 296.042 242.22 295.839C242.434 295.625 242.691 295.528 243.001 295.528C243.312 295.528 243.568 295.635 243.782 295.839C243.996 296.042 244.114 296.299 244.114 296.588C244.114 296.877 244.007 297.123 243.793 297.327C243.568 297.519 243.312 297.626 243.001 297.626Z" fill="white"/>
|
||||
<path d="M255.192 297.434H253.212L247.967 289.203C247.839 289 247.721 288.775 247.636 288.55H247.593C247.636 288.786 247.657 289.299 247.657 290.091L247.668 297.444H245.912L245.891 286.228H247.999L253.062 294.265C253.276 294.597 253.415 294.833 253.479 294.95H253.511C253.458 294.651 253.437 294.148 253.437 293.441L253.426 286.217H255.17L255.192 297.434Z" fill="white"/>
|
||||
<path d="M263.733 297.412L257.589 297.423L257.568 286.206L263.465 286.195V287.779L259.387 287.79L259.398 290.969L263.155 290.958V292.532L259.398 292.542L259.409 295.86L263.733 295.85V297.412Z" fill="white"/>
|
||||
<path d="M272.445 287.758L269.298 287.769L269.32 297.401H267.5L267.479 287.769L264.343 287.779V286.195L272.434 286.174L272.445 287.758Z" fill="white"/>
|
||||
<path d="M315.279 246.337C324.355 210.836 318.457 182.483 318.308 181.798L171.484 182.462C171.484 182.462 162.226 181.563 162.268 190.018C162.311 198.463 162.761 222.341 162.878 248.746C162.9 254.172 167.363 256.773 170.863 256.751C170.874 256.751 311.618 252.213 315.279 246.337Z" fill="url(#paint8_radial)"/>
|
||||
<path d="M227.685 246.798C227.685 246.798 250.183 228.827 254.571 225.499C258.959 222.17 262.812 221.977 266.869 225.445C270.925 228.913 293.616 246.498 293.616 246.498L227.685 246.798Z" fill="#A08BE8"/>
|
||||
<path d="M320.748 256.141C320.748 256.141 324.943 248.414 315.279 246.348C315.289 246.305 170.927 246.894 170.927 246.894C167.566 246.905 163.232 244.925 162.846 241.671C162.857 244.004 162.878 246.369 162.889 248.756C162.91 253.68 166.582 256.27 169.878 256.698C170.21 256.73 170.542 256.773 170.874 256.773L180.742 256.73L320.748 256.141Z" fill="#512BD4"/>
|
||||
<path d="M206.4 233.214C212.511 233.095 217.302 224.667 217.102 214.39C216.901 204.112 211.785 195.878 205.674 195.997C199.563 196.116 194.772 204.544 194.973 214.821C195.173 225.099 200.289 233.333 206.4 233.214Z" fill="#512BD4"/>
|
||||
<path d="M306.249 214.267C306.356 203.989 301.488 195.605 295.377 195.541C289.266 195.478 284.225 203.758 284.118 214.037C284.011 224.315 288.878 232.699 294.99 232.763C301.101 232.826 306.142 224.545 306.249 214.267Z" fill="#512BD4"/>
|
||||
<path d="M205.905 205.291C208.152 203.022 211.192 202.016 214.157 202.262C215.912 205.495 217.014 209.733 217.111 214.389C217.164 217.3 216.811 220.04 216.158 222.513C212.669 223.519 208.752 222.662 205.979 219.922C201.912 215.909 201.88 209.348 205.905 205.291Z" fill="#8065E0"/>
|
||||
<path d="M294.996 204.285C297.255 202.016 300.294 200.999 303.259 201.256C305.164 204.628 306.309 209.209 306.256 214.239C306.224 216.808 305.892 219.259 305.303 221.485C301.793 222.523 297.843 221.678 295.061 218.916C291.004 214.892 290.972 208.342 294.996 204.285Z" fill="#8065E0"/>
|
||||
<path d="M11.6342 357.017C10.9171 354.716 -5.72611 300.141 21.3204 258.903C36.9468 235.078 63.3083 221.035 99.6664 217.15L102.449 243.276C74.3431 246.273 54.4676 256.345 43.3579 273.202C23.0971 303.941 36.5722 348.733 36.7113 349.183L11.6342 357.017Z" fill="url(#paint9_linear)"/>
|
||||
<path d="M95.1498 252.802C109.502 252.802 121.137 241.167 121.137 226.815C121.137 212.463 109.502 200.828 95.1498 200.828C80.7976 200.828 69.1628 212.463 69.1628 226.815C69.1628 241.167 80.7976 252.802 95.1498 252.802Z" fill="url(#paint10_radial)"/>
|
||||
<path d="M72.0098 334.434L33.4683 329.307C26.597 328.397 20.2929 333.214 19.3725 340.085C18.4627 346.956 23.279 353.26 30.1504 354.181L68.6919 359.308C75.5632 360.217 81.8673 355.401 82.7878 348.53C83.6975 341.658 78.8705 335.344 72.0098 334.434Z" fill="#8A6FE8"/>
|
||||
<path d="M3.73535 367.185L7.35297 393.076C8.36975 399.968 14.7702 404.731 21.6629 403.725C28.5556 402.708 33.3185 396.308 32.3124 389.415L28.5984 362.861L3.73535 367.185Z" fill="#8A6FE8"/>
|
||||
<path d="M15.5194 374.988L34.849 405.427C38.6058 411.292 46.4082 413.005 52.2735 409.248C58.1387 405.491 59.8512 397.689 56.0945 391.823L41.7953 369.144L15.5194 374.988Z" fill="#8A6FE8"/>
|
||||
<path d="M26.0511 363.739L51.8026 389.019C56.7688 393.911 64.7532 393.846 69.6445 388.88C74.5358 383.914 74.4715 375.929 69.516 371.038L43.2937 345.297L26.0511 363.739Z" fill="#8A6FE8"/>
|
||||
<path d="M26.4043 381.912C40.987 381.912 52.8086 370.091 52.8086 355.508C52.8086 340.925 40.987 329.104 26.4043 329.104C11.8216 329.104 0 340.925 0 355.508C0 370.091 11.8216 381.912 26.4043 381.912Z" fill="url(#paint11_radial)"/>
|
||||
<path d="M184.73 63.6308L157.819 66.5892L158.561 38.5412L177.888 36.4178L184.73 63.6308Z" fill="#8A6FE8"/>
|
||||
<path d="M170.018 41.647C180.455 39.521 187.193 29.3363 185.067 18.8988C182.941 8.46126 172.757 1.72345 162.319 3.84944C151.882 5.97543 145.144 16.1601 147.27 26.5976C149.396 37.0351 159.58 43.773 170.018 41.647Z" fill="#D8CFF7"/>
|
||||
<path d="M196.885 79.385C198.102 79.2464 198.948 78.091 198.684 76.8997C195.851 64.2818 183.923 55.5375 170.773 56.9926C157.622 58.4371 147.886 69.5735 147.865 82.4995C147.863 83.7232 148.949 84.6597 150.168 84.5316L196.885 79.385Z" fill="url(#paint12_radial)"/>
|
||||
<defs>
|
||||
<radialGradient id="paint0_radial" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(382.004 103.457) scale(26.4058)">
|
||||
<stop stop-color="#8065E0"/>
|
||||
<stop offset="1" stop-color="#512BD4"/>
|
||||
</radialGradient>
|
||||
<linearGradient id="paint1_linear" x1="214.439" y1="303.482" x2="236.702" y2="409.505" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#522CD5"/>
|
||||
<stop offset="0.4397" stop-color="#8A6FE8"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint2_linear" x1="231.673" y1="404.144" x2="297.805" y2="522.048" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#522CD5"/>
|
||||
<stop offset="0.4397" stop-color="#8A6FE8"/>
|
||||
</linearGradient>
|
||||
<radialGradient id="paint3_radial" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(280.957 469.555) rotate(-0.260742) scale(45.8326)">
|
||||
<stop offset="0.034" stop-color="#522CD5"/>
|
||||
<stop offset="0.9955" stop-color="#8A6FE8"/>
|
||||
</radialGradient>
|
||||
<linearGradient id="paint4_linear" x1="166.061" y1="303.491" x2="144.763" y2="409.709" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#522CD5"/>
|
||||
<stop offset="0.4397" stop-color="#8A6FE8"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint5_linear" x1="146.739" y1="407.302" x2="147.246" y2="518.627" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#522CD5"/>
|
||||
<stop offset="0.4397" stop-color="#8A6FE8"/>
|
||||
</linearGradient>
|
||||
<radialGradient id="paint6_radial" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(148.63 470.023) rotate(179.739) scale(50.2476)">
|
||||
<stop offset="0.034" stop-color="#522CD5"/>
|
||||
<stop offset="0.9955" stop-color="#8A6FE8"/>
|
||||
</radialGradient>
|
||||
<radialGradient id="paint7_radial" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(219.219 153.929) rotate(179.739) scale(140.935)">
|
||||
<stop offset="0.4744" stop-color="#A08BE8"/>
|
||||
<stop offset="0.8618" stop-color="#8065E0"/>
|
||||
</radialGradient>
|
||||
<radialGradient id="paint8_radial" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(314.861 158.738) rotate(179.739) scale(146.053)">
|
||||
<stop offset="0.0933" stop-color="#E1DFDD"/>
|
||||
<stop offset="0.6573" stop-color="white"/>
|
||||
</radialGradient>
|
||||
<linearGradient id="paint9_linear" x1="54.1846" y1="217.159" x2="54.1846" y2="357.022" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0.3344" stop-color="#9780E6"/>
|
||||
<stop offset="0.8488" stop-color="#8A6FE8"/>
|
||||
</linearGradient>
|
||||
<radialGradient id="paint10_radial" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(90.3494 218.071) rotate(-0.260742) scale(25.9924)">
|
||||
<stop stop-color="#8065E0"/>
|
||||
<stop offset="1" stop-color="#512BD4"/>
|
||||
</radialGradient>
|
||||
<radialGradient id="paint11_radial" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(25.805 345.043) scale(26.4106)">
|
||||
<stop stop-color="#8065E0"/>
|
||||
<stop offset="1" stop-color="#512BD4"/>
|
||||
</radialGradient>
|
||||
<radialGradient id="paint12_radial" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(169.113 67.3662) rotate(-32.2025) scale(21.0773)">
|
||||
<stop stop-color="#8065E0"/>
|
||||
<stop offset="1" stop-color="#512BD4"/>
|
||||
</radialGradient>
|
||||
</defs>
|
||||
</svg>
|
После Ширина: | Высота: | Размер: 12 KiB |
Двоичные данные
docs/tutorials/notes-mvvm/snippets/bugs/csharp/Resources/Images/icon_about.png
Normal file
После Ширина: | Высота: | Размер: 2.0 KiB |
Двоичные данные
docs/tutorials/notes-mvvm/snippets/bugs/csharp/Resources/Images/icon_about_ios.png
Normal file
После Ширина: | Высота: | Размер: 2.0 KiB |
Двоичные данные
docs/tutorials/notes-mvvm/snippets/bugs/csharp/Resources/Images/icon_notes.png
Normal file
После Ширина: | Высота: | Размер: 1.4 KiB |
Двоичные данные
docs/tutorials/notes-mvvm/snippets/bugs/csharp/Resources/Images/icon_notes_ios.png
Normal file
После Ширина: | Высота: | Размер: 2.1 KiB |
|
@ -0,0 +1,15 @@
|
|||
Any raw assets you want to be deployed with your application can be placed in
|
||||
this directory (and child directories). Deployment of the asset to your application
|
||||
is automatically handled by the following `MauiAsset` Build Action within your `.csproj`.
|
||||
|
||||
<MauiAsset Include="Resources\Raw\**" LogicalName="%(RecursiveDir)%(Filename)%(Extension)" />
|
||||
|
||||
These files will be deployed with you package and will be accessible using Essentials:
|
||||
|
||||
async Task LoadMauiAsset()
|
||||
{
|
||||
using var stream = await FileSystem.OpenAppPackageFileAsync("AboutAssets.txt");
|
||||
using var reader = new StreamReader(stream);
|
||||
|
||||
var contents = reader.ReadToEnd();
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="456" height="456" viewBox="0 0 456 456" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||
<path d="m 105.50037,281.60863 c -2.70293,0 -5.00091,-0.90042 -6.893127,-2.70209 -1.892214,-1.84778 -2.837901,-4.04181 -2.837901,-6.58209 0,-2.58722 0.945687,-4.80389 2.837901,-6.65167 1.892217,-1.84778 4.190197,-2.77167 6.893127,-2.77167 2.74819,0 5.06798,0.92389 6.96019,2.77167 1.93749,1.84778 2.90581,4.06445 2.90581,6.65167 0,2.54028 -0.96832,4.73431 -2.90581,6.58209 -1.89221,1.80167 -4.212,2.70209 -6.96019,2.70209 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />
|
||||
<path d="M 213.56111,280.08446 H 195.99044 L 149.69953,207.0544 c -1.17121,-1.84778 -2.14037,-3.76515 -2.90581,-5.75126 h -0.40578 c 0.36051,2.12528 0.54076,6.67515 0.54076,13.6496 v 65.13172 h -15.54349 v -99.36009 h 18.71925 l 44.7374,71.29798 c 1.89222,2.95695 3.1087,4.98917 3.64945,6.09751 h 0.26996 c -0.45021,-2.6325 -0.67573,-7.09015 -0.67573,-13.37293 v -64.02256 h 15.47557 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />
|
||||
<path d="m 289.25134,280.08446 h -54.40052 v -99.36009 h 52.23835 v 13.99669 h -36.15411 v 28.13085 h 33.31621 v 13.9271 h -33.31621 v 29.37835 h 38.31628 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />
|
||||
<path d="M 366.56466,194.72106 H 338.7222 v 85.3634 h -16.08423 v -85.3634 h -27.77455 v -13.99669 h 71.70124 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />
|
||||
</svg>
|
После Ширина: | Высота: | Размер: 1.8 KiB |
|
@ -0,0 +1,44 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<?xaml-comp compile="true" ?>
|
||||
<ResourceDictionary
|
||||
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml">
|
||||
|
||||
<Color x:Key="Primary">#512BD4</Color>
|
||||
<Color x:Key="Secondary">#DFD8F7</Color>
|
||||
<Color x:Key="Tertiary">#2B0B98</Color>
|
||||
<Color x:Key="White">White</Color>
|
||||
<Color x:Key="Black">Black</Color>
|
||||
<Color x:Key="Gray100">#E1E1E1</Color>
|
||||
<Color x:Key="Gray200">#C8C8C8</Color>
|
||||
<Color x:Key="Gray300">#ACACAC</Color>
|
||||
<Color x:Key="Gray400">#919191</Color>
|
||||
<Color x:Key="Gray500">#6E6E6E</Color>
|
||||
<Color x:Key="Gray600">#404040</Color>
|
||||
<Color x:Key="Gray900">#212121</Color>
|
||||
<Color x:Key="Gray950">#141414</Color>
|
||||
<SolidColorBrush x:Key="PrimaryBrush" Color="{StaticResource Primary}"/>
|
||||
<SolidColorBrush x:Key="SecondaryBrush" Color="{StaticResource Secondary}"/>
|
||||
<SolidColorBrush x:Key="TertiaryBrush" Color="{StaticResource Tertiary}"/>
|
||||
<SolidColorBrush x:Key="WhiteBrush" Color="{StaticResource White}"/>
|
||||
<SolidColorBrush x:Key="BlackBrush" Color="{StaticResource Black}"/>
|
||||
<SolidColorBrush x:Key="Gray100Brush" Color="{StaticResource Gray100}"/>
|
||||
<SolidColorBrush x:Key="Gray200Brush" Color="{StaticResource Gray200}"/>
|
||||
<SolidColorBrush x:Key="Gray300Brush" Color="{StaticResource Gray300}"/>
|
||||
<SolidColorBrush x:Key="Gray400Brush" Color="{StaticResource Gray400}"/>
|
||||
<SolidColorBrush x:Key="Gray500Brush" Color="{StaticResource Gray500}"/>
|
||||
<SolidColorBrush x:Key="Gray600Brush" Color="{StaticResource Gray600}"/>
|
||||
<SolidColorBrush x:Key="Gray900Brush" Color="{StaticResource Gray900}"/>
|
||||
<SolidColorBrush x:Key="Gray950Brush" Color="{StaticResource Gray950}"/>
|
||||
|
||||
<Color x:Key="Yellow100Accent">#F7B548</Color>
|
||||
<Color x:Key="Yellow200Accent">#FFD590</Color>
|
||||
<Color x:Key="Yellow300Accent">#FFE5B9</Color>
|
||||
<Color x:Key="Cyan100Accent">#28C2D1</Color>
|
||||
<Color x:Key="Cyan200Accent">#7BDDEF</Color>
|
||||
<Color x:Key="Cyan300Accent">#C3F2F4</Color>
|
||||
<Color x:Key="Blue100Accent">#3E8EED</Color>
|
||||
<Color x:Key="Blue200Accent">#72ACF1</Color>
|
||||
<Color x:Key="Blue300Accent">#A7CBF6</Color>
|
||||
|
||||
</ResourceDictionary>
|
|
@ -0,0 +1,384 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<?xaml-comp compile="true" ?>
|
||||
<ResourceDictionary
|
||||
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml">
|
||||
|
||||
<Style TargetType="ActivityIndicator">
|
||||
<Setter Property="Color" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" />
|
||||
</Style>
|
||||
|
||||
<Style TargetType="IndicatorView">
|
||||
<Setter Property="IndicatorColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray500}}"/>
|
||||
<Setter Property="SelectedIndicatorColor" Value="{AppThemeBinding Light={StaticResource Gray950}, Dark={StaticResource Gray100}}"/>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="Border">
|
||||
<Setter Property="Stroke" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray500}}" />
|
||||
<Setter Property="StrokeShape" Value="Rectangle"/>
|
||||
<Setter Property="StrokeThickness" Value="1"/>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="BoxView">
|
||||
<Setter Property="Color" Value="{AppThemeBinding Light={StaticResource Gray950}, Dark={StaticResource Gray200}}" />
|
||||
</Style>
|
||||
|
||||
<Style TargetType="Button">
|
||||
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource Primary}}" />
|
||||
<Setter Property="BackgroundColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" />
|
||||
<Setter Property="FontFamily" Value="OpenSansRegular"/>
|
||||
<Setter Property="FontSize" Value="14"/>
|
||||
<Setter Property="CornerRadius" Value="8"/>
|
||||
<Setter Property="Padding" Value="14,10"/>
|
||||
<Setter Property="VisualStateManager.VisualStateGroups">
|
||||
<VisualStateGroupList>
|
||||
<VisualStateGroup x:Name="CommonStates">
|
||||
<VisualState x:Name="Normal" />
|
||||
<VisualState x:Name="Disabled">
|
||||
<VisualState.Setters>
|
||||
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray950}, Dark={StaticResource Gray200}}" />
|
||||
<Setter Property="BackgroundColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray600}}" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateGroupList>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="CheckBox">
|
||||
<Setter Property="Color" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" />
|
||||
<Setter Property="VisualStateManager.VisualStateGroups">
|
||||
<VisualStateGroupList>
|
||||
<VisualStateGroup x:Name="CommonStates">
|
||||
<VisualState x:Name="Normal" />
|
||||
<VisualState x:Name="Disabled">
|
||||
<VisualState.Setters>
|
||||
<Setter Property="Color" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateGroupList>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="DatePicker">
|
||||
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource White}}" />
|
||||
<Setter Property="BackgroundColor" Value="Transparent" />
|
||||
<Setter Property="FontFamily" Value="OpenSansRegular"/>
|
||||
<Setter Property="FontSize" Value="14"/>
|
||||
<Setter Property="VisualStateManager.VisualStateGroups">
|
||||
<VisualStateGroupList>
|
||||
<VisualStateGroup x:Name="CommonStates">
|
||||
<VisualState x:Name="Normal" />
|
||||
<VisualState x:Name="Disabled">
|
||||
<VisualState.Setters>
|
||||
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray500}}" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateGroupList>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="Editor">
|
||||
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource White}}" />
|
||||
<Setter Property="BackgroundColor" Value="Transparent" />
|
||||
<Setter Property="FontFamily" Value="OpenSansRegular"/>
|
||||
<Setter Property="FontSize" Value="14" />
|
||||
<Setter Property="PlaceholderColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray500}}" />
|
||||
<Setter Property="VisualStateManager.VisualStateGroups">
|
||||
<VisualStateGroupList>
|
||||
<VisualStateGroup x:Name="CommonStates">
|
||||
<VisualState x:Name="Normal" />
|
||||
<VisualState x:Name="Disabled">
|
||||
<VisualState.Setters>
|
||||
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateGroupList>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="Entry">
|
||||
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource White}}" />
|
||||
<Setter Property="BackgroundColor" Value="Transparent" />
|
||||
<Setter Property="FontFamily" Value="OpenSansRegular"/>
|
||||
<Setter Property="FontSize" Value="14" />
|
||||
<Setter Property="PlaceholderColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray500}}" />
|
||||
<Setter Property="VisualStateManager.VisualStateGroups">
|
||||
<VisualStateGroupList>
|
||||
<VisualStateGroup x:Name="CommonStates">
|
||||
<VisualState x:Name="Normal" />
|
||||
<VisualState x:Name="Disabled">
|
||||
<VisualState.Setters>
|
||||
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateGroupList>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="Frame">
|
||||
<Setter Property="HasShadow" Value="False" />
|
||||
<Setter Property="BorderColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray950}}" />
|
||||
<Setter Property="CornerRadius" Value="8" />
|
||||
</Style>
|
||||
|
||||
<Style TargetType="ImageButton">
|
||||
<Setter Property="Opacity" Value="1" />
|
||||
<Setter Property="BorderColor" Value="Transparent"/>
|
||||
<Setter Property="BorderWidth" Value="0"/>
|
||||
<Setter Property="CornerRadius" Value="0"/>
|
||||
<Setter Property="VisualStateManager.VisualStateGroups">
|
||||
<VisualStateGroupList>
|
||||
<VisualStateGroup x:Name="CommonStates">
|
||||
<VisualState x:Name="Normal" />
|
||||
<VisualState x:Name="Disabled">
|
||||
<VisualState.Setters>
|
||||
<Setter Property="Opacity" Value="0.5" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateGroupList>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="Label">
|
||||
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource White}}" />
|
||||
<Setter Property="FontFamily" Value="OpenSansRegular" />
|
||||
<Setter Property="FontSize" Value="14" />
|
||||
<Setter Property="VisualStateManager.VisualStateGroups">
|
||||
<VisualStateGroupList>
|
||||
<VisualStateGroup x:Name="CommonStates">
|
||||
<VisualState x:Name="Normal" />
|
||||
<VisualState x:Name="Disabled">
|
||||
<VisualState.Setters>
|
||||
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateGroupList>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="ListView">
|
||||
<Setter Property="SeparatorColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray500}}" />
|
||||
<Setter Property="RefreshControlColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource Gray200}}" />
|
||||
</Style>
|
||||
|
||||
<Style TargetType="Picker">
|
||||
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource White}}" />
|
||||
<Setter Property="TitleColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource Gray200}}" />
|
||||
<Setter Property="BackgroundColor" Value="Transparent" />
|
||||
<Setter Property="FontFamily" Value="OpenSansRegular"/>
|
||||
<Setter Property="FontSize" Value="14"/>
|
||||
<Setter Property="VisualStateManager.VisualStateGroups">
|
||||
<VisualStateGroupList>
|
||||
<VisualStateGroup x:Name="CommonStates">
|
||||
<VisualState x:Name="Normal" />
|
||||
<VisualState x:Name="Disabled">
|
||||
<VisualState.Setters>
|
||||
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
|
||||
<Setter Property="TitleColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateGroupList>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="ProgressBar">
|
||||
<Setter Property="ProgressColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" />
|
||||
<Setter Property="VisualStateManager.VisualStateGroups">
|
||||
<VisualStateGroupList>
|
||||
<VisualStateGroup x:Name="CommonStates">
|
||||
<VisualState x:Name="Normal" />
|
||||
<VisualState x:Name="Disabled">
|
||||
<VisualState.Setters>
|
||||
<Setter Property="ProgressColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateGroupList>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="RadioButton">
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource White}}" />
|
||||
<Setter Property="FontFamily" Value="OpenSansRegular"/>
|
||||
<Setter Property="FontSize" Value="14"/>
|
||||
<Setter Property="VisualStateManager.VisualStateGroups">
|
||||
<VisualStateGroupList>
|
||||
<VisualStateGroup x:Name="CommonStates">
|
||||
<VisualState x:Name="Normal" />
|
||||
<VisualState x:Name="Disabled">
|
||||
<VisualState.Setters>
|
||||
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateGroupList>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="RefreshView">
|
||||
<Setter Property="RefreshColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource Gray200}}" />
|
||||
</Style>
|
||||
|
||||
<Style TargetType="SearchBar">
|
||||
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource White}}" />
|
||||
<Setter Property="PlaceholderColor" Value="{StaticResource Gray500}" />
|
||||
<Setter Property="CancelButtonColor" Value="{StaticResource Gray500}" />
|
||||
<Setter Property="BackgroundColor" Value="Transparent" />
|
||||
<Setter Property="FontFamily" Value="OpenSansRegular" />
|
||||
<Setter Property="FontSize" Value="14" />
|
||||
<Setter Property="VisualStateManager.VisualStateGroups">
|
||||
<VisualStateGroupList>
|
||||
<VisualStateGroup x:Name="CommonStates">
|
||||
<VisualState x:Name="Normal" />
|
||||
<VisualState x:Name="Disabled">
|
||||
<VisualState.Setters>
|
||||
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
|
||||
<Setter Property="PlaceholderColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateGroupList>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="SearchHandler">
|
||||
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource White}}" />
|
||||
<Setter Property="PlaceholderColor" Value="{StaticResource Gray500}" />
|
||||
<Setter Property="BackgroundColor" Value="Transparent" />
|
||||
<Setter Property="FontFamily" Value="OpenSansRegular" />
|
||||
<Setter Property="FontSize" Value="14" />
|
||||
<Setter Property="VisualStateManager.VisualStateGroups">
|
||||
<VisualStateGroupList>
|
||||
<VisualStateGroup x:Name="CommonStates">
|
||||
<VisualState x:Name="Normal" />
|
||||
<VisualState x:Name="Disabled">
|
||||
<VisualState.Setters>
|
||||
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
|
||||
<Setter Property="PlaceholderColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateGroupList>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="Shadow">
|
||||
<Setter Property="Radius" Value="15" />
|
||||
<Setter Property="Opacity" Value="0.5" />
|
||||
<Setter Property="Brush" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource White}}" />
|
||||
<Setter Property="Offset" Value="10,10" />
|
||||
</Style>
|
||||
|
||||
<Style TargetType="Slider">
|
||||
<Setter Property="MinimumTrackColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" />
|
||||
<Setter Property="MaximumTrackColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray600}}" />
|
||||
<Setter Property="ThumbColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" />
|
||||
<Setter Property="VisualStateManager.VisualStateGroups">
|
||||
<VisualStateGroupList>
|
||||
<VisualStateGroup x:Name="CommonStates">
|
||||
<VisualState x:Name="Normal" />
|
||||
<VisualState x:Name="Disabled">
|
||||
<VisualState.Setters>
|
||||
<Setter Property="MinimumTrackColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}"/>
|
||||
<Setter Property="MaximumTrackColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}"/>
|
||||
<Setter Property="ThumbColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}"/>
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateGroupList>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="SwipeItem">
|
||||
<Setter Property="BackgroundColor" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource Black}}" />
|
||||
</Style>
|
||||
|
||||
<Style TargetType="Switch">
|
||||
<Setter Property="OnColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" />
|
||||
<Setter Property="ThumbColor" Value="{StaticResource White}" />
|
||||
<Setter Property="VisualStateManager.VisualStateGroups">
|
||||
<VisualStateGroupList>
|
||||
<VisualStateGroup x:Name="CommonStates">
|
||||
<VisualState x:Name="Normal" />
|
||||
<VisualState x:Name="Disabled">
|
||||
<VisualState.Setters>
|
||||
<Setter Property="OnColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
|
||||
<Setter Property="ThumbColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
<VisualState x:Name="On">
|
||||
<VisualState.Setters>
|
||||
<Setter Property="OnColor" Value="{AppThemeBinding Light={StaticResource Secondary}, Dark={StaticResource Gray200}}" />
|
||||
<Setter Property="ThumbColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
<VisualState x:Name="Off">
|
||||
<VisualState.Setters>
|
||||
<Setter Property="ThumbColor" Value="{AppThemeBinding Light={StaticResource Gray400}, Dark={StaticResource Gray500}}" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateGroupList>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="TimePicker">
|
||||
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource White}}" />
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Setter Property="FontFamily" Value="OpenSansRegular"/>
|
||||
<Setter Property="FontSize" Value="14"/>
|
||||
<Setter Property="VisualStateManager.VisualStateGroups">
|
||||
<VisualStateGroupList>
|
||||
<VisualStateGroup x:Name="CommonStates">
|
||||
<VisualState x:Name="Normal" />
|
||||
<VisualState x:Name="Disabled">
|
||||
<VisualState.Setters>
|
||||
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateGroupList>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="Page" ApplyToDerivedTypes="True">
|
||||
<Setter Property="Padding" Value="0"/>
|
||||
<Setter Property="BackgroundColor" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource Black}}" />
|
||||
</Style>
|
||||
|
||||
<Style TargetType="Shell" ApplyToDerivedTypes="True">
|
||||
<Setter Property="Shell.BackgroundColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource Gray950}}" />
|
||||
<Setter Property="Shell.ForegroundColor" Value="{OnPlatform WinUI={StaticResource Primary}, Default={StaticResource White}}" />
|
||||
<Setter Property="Shell.TitleColor" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource White}}" />
|
||||
<Setter Property="Shell.DisabledColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray950}}" />
|
||||
<Setter Property="Shell.UnselectedColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray200}}" />
|
||||
<Setter Property="Shell.NavBarHasShadow" Value="False" />
|
||||
<Setter Property="Shell.TabBarBackgroundColor" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource Black}}" />
|
||||
<Setter Property="Shell.TabBarForegroundColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" />
|
||||
<Setter Property="Shell.TabBarTitleColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" />
|
||||
<Setter Property="Shell.TabBarUnselectedColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource Gray200}}" />
|
||||
</Style>
|
||||
|
||||
<Style TargetType="NavigationPage">
|
||||
<Setter Property="BarBackgroundColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource Gray950}}" />
|
||||
<Setter Property="BarTextColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource White}}" />
|
||||
<Setter Property="IconColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource White}}" />
|
||||
</Style>
|
||||
|
||||
<Style TargetType="TabbedPage">
|
||||
<Setter Property="BarBackgroundColor" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource Gray950}}" />
|
||||
<Setter Property="BarTextColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" />
|
||||
<Setter Property="UnselectedTabColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray950}}" />
|
||||
<Setter Property="SelectedTabColor" Value="{AppThemeBinding Light={StaticResource Gray950}, Dark={StaticResource Gray200}}" />
|
||||
</Style>
|
||||
|
||||
</ResourceDictionary>
|
|
@ -0,0 +1,21 @@
|
|||
using CommunityToolkit.Mvvm.Input;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace Notes.ViewModels;
|
||||
|
||||
internal class AboutViewModel
|
||||
{
|
||||
public string Title => AppInfo.Name;
|
||||
public string Version => AppInfo.VersionString;
|
||||
public string MoreInfoUrl => "https://aka.ms/maui";
|
||||
public string Message => "This app is written in XAML and C# with .NET MAUI.";
|
||||
public ICommand ShowMoreInfoCommand { get; }
|
||||
|
||||
public AboutViewModel()
|
||||
{
|
||||
ShowMoreInfoCommand = new AsyncRelayCommand(ShowMoreInfo);
|
||||
}
|
||||
|
||||
async Task ShowMoreInfo() =>
|
||||
await Launcher.Default.OpenAsync(MoreInfoUrl);
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
using CommunityToolkit.Mvvm.Input;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace Notes.ViewModels;
|
||||
|
||||
internal class NoteViewModel : ObservableObject, IQueryAttributable
|
||||
{
|
||||
private Models.Note _note;
|
||||
|
||||
public string Text
|
||||
{
|
||||
get => _note.Text;
|
||||
set
|
||||
{
|
||||
if (_note.Text != value)
|
||||
{
|
||||
_note.Text = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public DateTime Date => _note.Date;
|
||||
|
||||
public string Identifier => _note.Filename;
|
||||
|
||||
public ICommand SaveCommand { get; private set; }
|
||||
public ICommand DeleteCommand { get; private set; }
|
||||
|
||||
public NoteViewModel()
|
||||
{
|
||||
_note = new Models.Note();
|
||||
SetupCommands();
|
||||
}
|
||||
|
||||
public NoteViewModel(Models.Note note)
|
||||
{
|
||||
_note = note;
|
||||
SetupCommands();
|
||||
}
|
||||
|
||||
private void SetupCommands()
|
||||
{
|
||||
SaveCommand = new AsyncRelayCommand(Save);
|
||||
DeleteCommand = new AsyncRelayCommand(Delete);
|
||||
}
|
||||
|
||||
private async Task Save()
|
||||
{
|
||||
_note.Date = DateTime.Now;
|
||||
_note.Save();
|
||||
await Shell.Current.GoToAsync($"..?saved={_note.Filename}");
|
||||
}
|
||||
|
||||
private async Task Delete()
|
||||
{
|
||||
_note.Delete();
|
||||
await Shell.Current.GoToAsync($"..?deleted={_note.Filename}");
|
||||
}
|
||||
|
||||
void IQueryAttributable.ApplyQueryAttributes(IDictionary<string, object> query)
|
||||
{
|
||||
if (query.ContainsKey("load"))
|
||||
{
|
||||
_note = Models.Note.Load(query["load"].ToString());
|
||||
RefreshProperties();
|
||||
}
|
||||
}
|
||||
|
||||
public void Reload()
|
||||
{
|
||||
_note = Models.Note.Load(_note.Filename);
|
||||
RefreshProperties();
|
||||
}
|
||||
|
||||
private void RefreshProperties()
|
||||
{
|
||||
OnPropertyChanged(nameof(Text));
|
||||
OnPropertyChanged(nameof(Date));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
using CommunityToolkit.Mvvm.Input;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace Notes.ViewModels;
|
||||
|
||||
internal class NotesViewModel : IQueryAttributable
|
||||
{
|
||||
public ObservableCollection<NoteViewModel> AllNotes { get; private set; }
|
||||
public ICommand NewCommand { get; }
|
||||
public ICommand SelectNoteCommand { get; }
|
||||
|
||||
public NotesViewModel()
|
||||
{
|
||||
AllNotes = new ObservableCollection<NoteViewModel>(Models.Note.LoadAll().Select(n => new NoteViewModel(n)));
|
||||
NewCommand = new AsyncRelayCommand(NewNoteAsync);
|
||||
SelectNoteCommand = new AsyncRelayCommand<NoteViewModel>(SelectNoteAsync);
|
||||
}
|
||||
|
||||
private async Task NewNoteAsync()
|
||||
{
|
||||
await Shell.Current.GoToAsync(nameof(Views.NotePage));
|
||||
}
|
||||
|
||||
private async Task SelectNoteAsync(NoteViewModel note)
|
||||
{
|
||||
if (note != null)
|
||||
await Shell.Current.GoToAsync($"{nameof(Views.NotePage)}?load={note.Identifier}");
|
||||
}
|
||||
|
||||
//<query>
|
||||
void IQueryAttributable.ApplyQueryAttributes(IDictionary<string, object> query)
|
||||
{
|
||||
if (query.ContainsKey("deleted"))
|
||||
{
|
||||
string noteId = query["deleted"].ToString();
|
||||
NoteViewModel matchedNote = AllNotes.Where((n) => n.Identifier == noteId).FirstOrDefault();
|
||||
|
||||
// If note exists, delete it
|
||||
if (matchedNote != null)
|
||||
AllNotes.Remove(matchedNote);
|
||||
}
|
||||
else if (query.ContainsKey("saved"))
|
||||
{
|
||||
//<insert>
|
||||
//<move>
|
||||
string noteId = query["saved"].ToString();
|
||||
NoteViewModel matchedNote = AllNotes.Where((n) => n.Identifier == noteId).FirstOrDefault();
|
||||
|
||||
// If note is found, update it
|
||||
if (matchedNote != null)
|
||||
{
|
||||
matchedNote.Reload();
|
||||
AllNotes.Move(AllNotes.IndexOf(matchedNote), 0);
|
||||
}
|
||||
//</move>
|
||||
// If note isn't found, it's new; add it.
|
||||
else
|
||||
AllNotes.Insert(0, new NoteViewModel(Models.Note.Load(noteId)));
|
||||
//</insert>
|
||||
}
|
||||
}
|
||||
//</query>
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
xmlns:viewModels="clr-namespace:Notes.ViewModels"
|
||||
x:Class="Notes.Views.AboutPage">
|
||||
<ContentPage.BindingContext>
|
||||
<viewModels:AboutViewModel />
|
||||
</ContentPage.BindingContext>
|
||||
<VerticalStackLayout Spacing="10" Margin="10">
|
||||
<HorizontalStackLayout Spacing="10">
|
||||
<Image Source="dotnet_bot.png"
|
||||
SemanticProperties.Description="The dot net bot waving hello!"
|
||||
HeightRequest="64" />
|
||||
<Label FontSize="22" FontAttributes="Bold" Text="{Binding Title}" VerticalOptions="End" />
|
||||
<Label FontSize="22" Text="{Binding Version}" VerticalOptions="End" />
|
||||
</HorizontalStackLayout>
|
||||
|
||||
<Label Text="{Binding Message}" />
|
||||
<Button Text="Learn more..." Command="{Binding ShowMoreInfoCommand}" />
|
||||
</VerticalStackLayout>
|
||||
|
||||
</ContentPage>
|
|
@ -0,0 +1,9 @@
|
|||
namespace Notes.Views;
|
||||
|
||||
public partial class AboutPage : ContentPage
|
||||
{
|
||||
public AboutPage()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
xmlns:viewModels="clr-namespace:Notes.ViewModels"
|
||||
x:Class="Notes.Views.AllNotesPage"
|
||||
Title="Your Notes"
|
||||
NavigatedTo="ContentPage_NavigatedTo">
|
||||
<ContentPage.BindingContext>
|
||||
<viewModels:NotesViewModel />
|
||||
</ContentPage.BindingContext>
|
||||
|
||||
<!-- Add an item to the toolbar -->
|
||||
<ContentPage.ToolbarItems>
|
||||
<ToolbarItem Text="Add" Command="{Binding NewCommand}" IconImageSource="{FontImage Glyph='+', Color=White, Size=22}" />
|
||||
</ContentPage.ToolbarItems>
|
||||
|
||||
<!-- Display notes in a list -->
|
||||
<CollectionView x:Name="notesCollection" x:FieldModifier="internal"
|
||||
ItemsSource="{Binding AllNotes}"
|
||||
Margin="20"
|
||||
SelectionMode="Single"
|
||||
SelectionChangedCommand="{Binding SelectNoteCommand}"
|
||||
SelectionChangedCommandParameter="{Binding Source={RelativeSource Self}, Path=SelectedItem}">
|
||||
<!-- Designate how the collection of items are laid out -->
|
||||
<CollectionView.ItemsLayout>
|
||||
<LinearItemsLayout Orientation="Vertical" ItemSpacing="10" />
|
||||
</CollectionView.ItemsLayout>
|
||||
|
||||
<!-- Define the appearance of each item in the list -->
|
||||
<CollectionView.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<StackLayout>
|
||||
<Label Text="{Binding Text}" FontSize="22"/>
|
||||
<Label Text="{Binding Date}" FontSize="14" TextColor="Silver"/>
|
||||
</StackLayout>
|
||||
</DataTemplate>
|
||||
</CollectionView.ItemTemplate>
|
||||
</CollectionView>
|
||||
</ContentPage>
|
|
@ -0,0 +1,18 @@
|
|||
//<full>
|
||||
namespace Notes.Views;
|
||||
|
||||
public partial class AllNotesPage : ContentPage
|
||||
{
|
||||
public AllNotesPage()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
//<event>
|
||||
private void ContentPage_NavigatedTo(object sender, NavigatedToEventArgs e)
|
||||
{
|
||||
notesCollection.SelectedItem = null;
|
||||
}
|
||||
//</event>
|
||||
}
|
||||
//</full>
|
|
@ -0,0 +1,26 @@
|
|||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
xmlns:viewModels="clr-namespace:Notes.ViewModels"
|
||||
x:Class="Notes.Views.NotePage"
|
||||
Title="Note">
|
||||
<ContentPage.BindingContext>
|
||||
<viewModels:NoteViewModel />
|
||||
</ContentPage.BindingContext>
|
||||
<VerticalStackLayout Spacing="10" Margin="5">
|
||||
<Editor x:Name="TextEditor"
|
||||
Placeholder="Enter your note"
|
||||
Text="{Binding Text}"
|
||||
HeightRequest="100" />
|
||||
|
||||
<Grid ColumnDefinitions="*,*" ColumnSpacing="4">
|
||||
<Button Text="Save"
|
||||
Command="{Binding SaveCommand}"/>
|
||||
|
||||
<Button Grid.Column="1"
|
||||
Text="Delete"
|
||||
Command="{Binding DeleteCommand}"/>
|
||||
|
||||
</Grid>
|
||||
</VerticalStackLayout>
|
||||
</ContentPage>
|
|
@ -0,0 +1,9 @@
|
|||
namespace Notes.Views;
|
||||
|
||||
public partial class NotePage : ContentPage
|
||||
{
|
||||
public NotePage()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
<?xml version = "1.0" encoding = "UTF-8" ?>
|
||||
<Application xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
xmlns:local="clr-namespace:Notes"
|
||||
x:Class="Notes.App">
|
||||
<Application.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="Resources/Styles/Colors.xaml" />
|
||||
<ResourceDictionary Source="Resources/Styles/Styles.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
</Application>
|
|
@ -0,0 +1,11 @@
|
|||
namespace Notes;
|
||||
|
||||
public partial class App : Application
|
||||
{
|
||||
public App()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
MainPage = new AppShell();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<Shell
|
||||
x:Class="Notes.AppShell"
|
||||
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
xmlns:views="clr-namespace:Notes.Views"
|
||||
Shell.FlyoutBehavior="Disabled">
|
||||
|
||||
<TabBar>
|
||||
<ShellContent
|
||||
Title="Notes"
|
||||
ContentTemplate="{DataTemplate views:AllNotesPage}"
|
||||
Icon="{OnPlatform 'icon_notes.png', iOS='icon_notes_ios.png', MacCatalyst='icon_notes_ios.png'}" />
|
||||
|
||||
<ShellContent
|
||||
Title="About"
|
||||
ContentTemplate="{DataTemplate views:AboutPage}"
|
||||
Icon="{OnPlatform 'icon_about.png', iOS='icon_about_ios.png', MacCatalyst='icon_about_ios.png'}" />
|
||||
</TabBar>
|
||||
|
||||
</Shell>
|
|
@ -0,0 +1,11 @@
|
|||
namespace Notes;
|
||||
|
||||
public partial class AppShell : Shell
|
||||
{
|
||||
public AppShell()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
Routing.RegisterRoute(nameof(Views.NotePage), typeof(Views.NotePage));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
namespace Notes;
|
||||
|
||||
public static class MauiProgram
|
||||
{
|
||||
public static MauiApp CreateMauiApp()
|
||||
{
|
||||
var builder = MauiApp.CreateBuilder();
|
||||
builder
|
||||
.UseMauiApp<App>()
|
||||
.ConfigureFonts(fonts =>
|
||||
{
|
||||
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
|
||||
fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
|
||||
});
|
||||
|
||||
return builder.Build();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
//<full>
|
||||
namespace Notes.Models;
|
||||
|
||||
internal class Note
|
||||
{
|
||||
//<properties>
|
||||
public string Filename { get; set; }
|
||||
public string Text { get; set; }
|
||||
public DateTime Date { get; set; }
|
||||
//</properties>
|
||||
|
||||
//<ctor>
|
||||
public Note()
|
||||
{
|
||||
Filename = $"{Path.GetRandomFileName()}.notes.txt";
|
||||
Date = DateTime.Now;
|
||||
Text = "";
|
||||
}
|
||||
//</ctor>
|
||||
|
||||
//<save_delete>
|
||||
public void Save() =>
|
||||
File.WriteAllText(System.IO.Path.Combine(FileSystem.AppDataDirectory, Filename), Text);
|
||||
|
||||
public void Delete() =>
|
||||
File.Delete(System.IO.Path.Combine(FileSystem.AppDataDirectory, Filename));
|
||||
//</save_delete>
|
||||
|
||||
//<load_single>
|
||||
public static Note Load(string filename)
|
||||
{
|
||||
filename = System.IO.Path.Combine(FileSystem.AppDataDirectory, filename);
|
||||
|
||||
if (!File.Exists(filename))
|
||||
throw new FileNotFoundException("Unable to find file on local storage.", filename);
|
||||
|
||||
return
|
||||
new()
|
||||
{
|
||||
Filename = Path.GetFileName(filename),
|
||||
Text = File.ReadAllText(filename),
|
||||
Date = File.GetLastWriteTime(filename)
|
||||
};
|
||||
}
|
||||
//</load_single>
|
||||
|
||||
//<load_all>
|
||||
public static IEnumerable<Note> LoadAll()
|
||||
{
|
||||
// Get the folder where the notes are stored.
|
||||
string appDataPath = FileSystem.AppDataDirectory;
|
||||
|
||||
// Use Linq extensions to load the *.notes.txt files.
|
||||
return Directory
|
||||
|
||||
// Select the file names from the directory
|
||||
.EnumerateFiles(appDataPath, "*.notes.txt")
|
||||
|
||||
// Each file name is used to load a note
|
||||
.Select(filename => Note.Load(Path.GetFileName(filename)))
|
||||
|
||||
// With the final collection of notes, order them by date
|
||||
.OrderByDescending(note => note.Date);
|
||||
}
|
||||
//</load_all>
|
||||
}
|
||||
//</full>
|
|
@ -0,0 +1,67 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net6.0-android;net6.0-ios;net6.0-maccatalyst</TargetFrameworks>
|
||||
<TargetFrameworks Condition="$([MSBuild]::IsOSPlatform('windows'))">$(TargetFrameworks);net6.0-windows10.0.19041.0</TargetFrameworks>
|
||||
<!-- Uncomment to also build the tizen app. You will need to install tizen by following this: https://github.com/Samsung/Tizen.NET -->
|
||||
<!-- <TargetFrameworks>$(TargetFrameworks);net6.0-tizen</TargetFrameworks> -->
|
||||
<OutputType>Exe</OutputType>
|
||||
<RootNamespace>Notes</RootNamespace>
|
||||
<UseMaui>true</UseMaui>
|
||||
<SingleProject>true</SingleProject>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
|
||||
<!-- Display name -->
|
||||
<ApplicationTitle>Notes</ApplicationTitle>
|
||||
|
||||
<!-- App Identifier -->
|
||||
<ApplicationId>com.companyname.notes</ApplicationId>
|
||||
<ApplicationIdGuid>779582C2-BD7F-4701-A585-49B1A2FBDF30</ApplicationIdGuid>
|
||||
|
||||
<!-- Versions -->
|
||||
<ApplicationDisplayVersion>1.0</ApplicationDisplayVersion>
|
||||
<ApplicationVersion>1</ApplicationVersion>
|
||||
|
||||
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">14.2</SupportedOSPlatformVersion>
|
||||
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'maccatalyst'">14.0</SupportedOSPlatformVersion>
|
||||
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">21.0</SupportedOSPlatformVersion>
|
||||
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">10.0.17763.0</SupportedOSPlatformVersion>
|
||||
<TargetPlatformMinVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">10.0.17763.0</TargetPlatformMinVersion>
|
||||
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'tizen'">6.5</SupportedOSPlatformVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<!-- App Icon -->
|
||||
<MauiIcon Include="Resources\AppIcon\appicon.svg" ForegroundFile="Resources\AppIcon\appiconfg.svg" Color="#512BD4" />
|
||||
|
||||
<!-- Splash Screen -->
|
||||
<MauiSplashScreen Include="Resources\Splash\splash.svg" Color="#512BD4" BaseSize="128,128" />
|
||||
|
||||
<!-- Images -->
|
||||
<MauiImage Include="Resources\Images\*" />
|
||||
<MauiImage Update="Resources\Images\dotnet_bot.svg" BaseSize="168,208" />
|
||||
|
||||
<!-- Custom Fonts -->
|
||||
<MauiFont Include="Resources\Fonts\*" />
|
||||
|
||||
<!-- Raw Assets (also remove the "Resources\Raw" prefix) -->
|
||||
<MauiAsset Include="Resources\Raw\**" LogicalName="%(RecursiveDir)%(Filename)%(Extension)" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<MauiXaml Update="Views\AboutPage.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</MauiXaml>
|
||||
<MauiXaml Update="Views\AllNotesPage.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</MauiXaml>
|
||||
<MauiXaml Update="Views\NotePage.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</MauiXaml>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
|
@ -0,0 +1,24 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<ActiveDebugFramework>net6.0-windows10.0.19041.0</ActiveDebugFramework>
|
||||
<IsFirstTimeProjectOpen>False</IsFirstTimeProjectOpen>
|
||||
<ActiveDebugProfile>Windows Machine</ActiveDebugProfile>
|
||||
<SelectedPlatformGroup>Emulator</SelectedPlatformGroup>
|
||||
<DefaultDevice>pixel_2_xl_-_api_33</DefaultDevice>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|net6.0-android|AnyCPU'">
|
||||
<DebuggerFlavor>ProjectDebugger</DebuggerFlavor>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<MauiXaml Update="Views\AboutPage.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
</MauiXaml>
|
||||
<MauiXaml Update="Views\AllNotesPage.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
</MauiXaml>
|
||||
<MauiXaml Update="Views\NotePage.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
</MauiXaml>
|
||||
</ItemGroup>
|
||||
</Project>
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<application android:allowBackup="true" android:icon="@mipmap/appicon" android:roundIcon="@mipmap/appicon_round" android:supportsRtl="true"></application>
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
</manifest>
|
|
@ -0,0 +1,10 @@
|
|||
using Android.App;
|
||||
using Android.Content.PM;
|
||||
using Android.OS;
|
||||
|
||||
namespace Notes;
|
||||
|
||||
[Activity(Theme = "@style/Maui.SplashTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize | ConfigChanges.Density)]
|
||||
public class MainActivity : MauiAppCompatActivity
|
||||
{
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
using Android.App;
|
||||
using Android.Runtime;
|
||||
|
||||
namespace Notes;
|
||||
|
||||
[Application]
|
||||
public class MainApplication : MauiApplication
|
||||
{
|
||||
public MainApplication(IntPtr handle, JniHandleOwnership ownership)
|
||||
: base(handle, ownership)
|
||||
{
|
||||
}
|
||||
|
||||
protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="colorPrimary">#512BD4</color>
|
||||
<color name="colorPrimaryDark">#2B0B98</color>
|
||||
<color name="colorAccent">#2B0B98</color>
|
||||
</resources>
|
|
@ -0,0 +1,9 @@
|
|||
using Foundation;
|
||||
|
||||
namespace Notes;
|
||||
|
||||
[Register("AppDelegate")]
|
||||
public class AppDelegate : MauiUIApplicationDelegate
|
||||
{
|
||||
protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>UIDeviceFamily</key>
|
||||
<array>
|
||||
<integer>1</integer>
|
||||
<integer>2</integer>
|
||||
</array>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
<array>
|
||||
<string>arm64</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>XSAppIconAssets</key>
|
||||
<string>Assets.xcassets/appicon.appiconset</string>
|
||||
</dict>
|
||||
</plist>
|