From 353dde03c56882a88cb0b288cfe58099c19805f5 Mon Sep 17 00:00:00 2001 From: Julia Date: Thu, 13 Mar 2025 11:23:08 +0300 Subject: [PATCH] first commit --- .gitignore | 3 + App.xaml | 32 +++++++++ App.xaml.cs | 32 +++++++++ AppShell.xaml | 136 ++++++++++++++++++++++++++++++++++++++ AppShell.xaml.cs | 23 +++++++ AssemblyInfo.cs | 3 + GettingStarted.txt | 34 ++++++++++ MobApp.csproj | 13 ++++ Models/Item.cs | 11 +++ Services/IDataStore.cs | 15 +++++ Services/MockDataStore.cs | 60 +++++++++++++++++ ViewModels/AboutViewModel.cs | 18 +++++ ViewModels/BaseViewModel.cs | 54 +++++++++++++++ ViewModels/ItemDetailViewModel.cs | 57 ++++++++++++++++ ViewModels/ItemsViewModel.cs | 84 +++++++++++++++++++++++ ViewModels/LoginViewModel.cs | 24 +++++++ ViewModels/NewItemViewModel.cs | 65 ++++++++++++++++++ Views/AboutPage.xaml | 52 +++++++++++++++ Views/AboutPage.xaml.cs | 15 +++++ Views/ItemDetailPage.xaml | 14 ++++ Views/ItemDetailPage.xaml.cs | 15 +++++ Views/ItemsPage.xaml | 44 ++++++++++++ Views/ItemsPage.xaml.cs | 32 +++++++++ Views/LoginPage.xaml | 14 ++++ Views/LoginPage.xaml.cs | 21 ++++++ Views/NewItemPage.xaml | 22 ++++++ Views/NewItemPage.xaml.cs | 21 ++++++ 27 files changed, 914 insertions(+) create mode 100644 .gitignore create mode 100644 App.xaml create mode 100644 App.xaml.cs create mode 100644 AppShell.xaml create mode 100644 AppShell.xaml.cs create mode 100644 AssemblyInfo.cs create mode 100644 GettingStarted.txt create mode 100644 MobApp.csproj create mode 100644 Models/Item.cs create mode 100644 Services/IDataStore.cs create mode 100644 Services/MockDataStore.cs create mode 100644 ViewModels/AboutViewModel.cs create mode 100644 ViewModels/BaseViewModel.cs create mode 100644 ViewModels/ItemDetailViewModel.cs create mode 100644 ViewModels/ItemsViewModel.cs create mode 100644 ViewModels/LoginViewModel.cs create mode 100644 ViewModels/NewItemViewModel.cs create mode 100644 Views/AboutPage.xaml create mode 100644 Views/AboutPage.xaml.cs create mode 100644 Views/ItemDetailPage.xaml create mode 100644 Views/ItemDetailPage.xaml.cs create mode 100644 Views/ItemsPage.xaml create mode 100644 Views/ItemsPage.xaml.cs create mode 100644 Views/LoginPage.xaml create mode 100644 Views/LoginPage.xaml.cs create mode 100644 Views/NewItemPage.xaml create mode 100644 Views/NewItemPage.xaml.cs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5df6da6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/.vs +/bin +/obj \ No newline at end of file diff --git a/App.xaml b/App.xaml new file mode 100644 index 0000000..2ff79a3 --- /dev/null +++ b/App.xaml @@ -0,0 +1,32 @@ + + + + + + #2196F3 + + + + diff --git a/App.xaml.cs b/App.xaml.cs new file mode 100644 index 0000000..1dd14b2 --- /dev/null +++ b/App.xaml.cs @@ -0,0 +1,32 @@ +using MobApp.Services; +using MobApp.Views; +using System; +using Xamarin.Forms; +using Xamarin.Forms.Xaml; + +namespace MobApp +{ + public partial class App : Application + { + + public App() + { + InitializeComponent(); + + DependencyService.Register(); + MainPage = new AppShell(); + } + + protected override void OnStart() + { + } + + protected override void OnSleep() + { + } + + protected override void OnResume() + { + } + } +} diff --git a/AppShell.xaml b/AppShell.xaml new file mode 100644 index 0000000..dfe2358 --- /dev/null +++ b/AppShell.xaml @@ -0,0 +1,136 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/AppShell.xaml.cs b/AppShell.xaml.cs new file mode 100644 index 0000000..0a82ef0 --- /dev/null +++ b/AppShell.xaml.cs @@ -0,0 +1,23 @@ +using MobApp.ViewModels; +using MobApp.Views; +using System; +using System.Collections.Generic; +using Xamarin.Forms; + +namespace MobApp +{ + public partial class AppShell : Xamarin.Forms.Shell + { + public AppShell() + { + InitializeComponent(); + Routing.RegisterRoute(nameof(ItemDetailPage), typeof(ItemDetailPage)); + Routing.RegisterRoute(nameof(NewItemPage), typeof(NewItemPage)); + } + + private async void OnMenuItemClicked(object sender, EventArgs e) + { + await Shell.Current.GoToAsync("//LoginPage"); + } + } +} diff --git a/AssemblyInfo.cs b/AssemblyInfo.cs new file mode 100644 index 0000000..c859952 --- /dev/null +++ b/AssemblyInfo.cs @@ -0,0 +1,3 @@ +using Xamarin.Forms.Xaml; + +[assembly: XamlCompilation(XamlCompilationOptions.Compile)] \ No newline at end of file diff --git a/GettingStarted.txt b/GettingStarted.txt new file mode 100644 index 0000000..411f64b --- /dev/null +++ b/GettingStarted.txt @@ -0,0 +1,34 @@ +Welcome to Xamarin.Forms! Here are some tips to get started building your app. + +Building Your App UI +-------------------- + +XAML Hot Reload quickly applies UI changes as you make them to your running app. +This is the most productive way to preview and iteratively create your UI. + +Try it out: + +1. Run the app by clicking the Start Debugging (play) button in the above toolbar. +2. Open \Views\AboutPage.xaml. + Don't stop the app - keep it running while making changes. +3. Change something! Hint: change the Accent color on line 14 from "#96d1ff" to "Pink". +4. Watch the About screen update on the device or emulator, with the logo background now pink. + +Keep going and try more changes! + +QuickStart Guide +---------------- + +Learn more of the fundamentals for building apps with Xamarin here: https://aka.ms/xamarin-quickstart + +Your App Shell +-------------- + +This template uses Shell, an app container that reduces the complexity of your apps by providing fundamental features including: + +- A single place to describe the app's visual hierarchy. +- Common navigation such as a flyout menu and tabs. +- A URI-based navigation scheme that permits navigation to any page in the application. +- An integrated search handler. + +Open AppShell.xaml to begin exploring. To learn more about Shell visit: https://docs.microsoft.com/xamarin/xamarin-forms/app-fundamentals/shell/introduction diff --git a/MobApp.csproj b/MobApp.csproj new file mode 100644 index 0000000..8288144 --- /dev/null +++ b/MobApp.csproj @@ -0,0 +1,13 @@ + + + + netstandard2.0 + true + true + + + + + + + \ No newline at end of file diff --git a/Models/Item.cs b/Models/Item.cs new file mode 100644 index 0000000..b1839fa --- /dev/null +++ b/Models/Item.cs @@ -0,0 +1,11 @@ +using System; + +namespace MobApp.Models +{ + public class Item + { + public string Id { get; set; } + public string Text { get; set; } + public string Description { get; set; } + } +} \ No newline at end of file diff --git a/Services/IDataStore.cs b/Services/IDataStore.cs new file mode 100644 index 0000000..3ac3938 --- /dev/null +++ b/Services/IDataStore.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace MobApp.Services +{ + public interface IDataStore + { + Task AddItemAsync(T item); + Task UpdateItemAsync(T item); + Task DeleteItemAsync(string id); + Task GetItemAsync(string id); + Task> GetItemsAsync(bool forceRefresh = false); + } +} diff --git a/Services/MockDataStore.cs b/Services/MockDataStore.cs new file mode 100644 index 0000000..af36486 --- /dev/null +++ b/Services/MockDataStore.cs @@ -0,0 +1,60 @@ +using MobApp.Models; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace MobApp.Services +{ + public class MockDataStore : IDataStore + { + readonly List items; + + public MockDataStore() + { + items = new List() + { + new Item { Id = Guid.NewGuid().ToString(), Text = "First item", Description="This is an item description." }, + new Item { Id = Guid.NewGuid().ToString(), Text = "Second item", Description="This is an item description." }, + new Item { Id = Guid.NewGuid().ToString(), Text = "Third item", Description="This is an item description." }, + new Item { Id = Guid.NewGuid().ToString(), Text = "Fourth item", Description="This is an item description." }, + new Item { Id = Guid.NewGuid().ToString(), Text = "Fifth item", Description="This is an item description." }, + new Item { Id = Guid.NewGuid().ToString(), Text = "Sixth item", Description="This is an item description." } + }; + } + + public async Task AddItemAsync(Item item) + { + items.Add(item); + + return await Task.FromResult(true); + } + + public async Task UpdateItemAsync(Item item) + { + var oldItem = items.Where((Item arg) => arg.Id == item.Id).FirstOrDefault(); + items.Remove(oldItem); + items.Add(item); + + return await Task.FromResult(true); + } + + public async Task DeleteItemAsync(string id) + { + var oldItem = items.Where((Item arg) => arg.Id == id).FirstOrDefault(); + items.Remove(oldItem); + + return await Task.FromResult(true); + } + + public async Task GetItemAsync(string id) + { + return await Task.FromResult(items.FirstOrDefault(s => s.Id == id)); + } + + public async Task> GetItemsAsync(bool forceRefresh = false) + { + return await Task.FromResult(items); + } + } +} \ No newline at end of file diff --git a/ViewModels/AboutViewModel.cs b/ViewModels/AboutViewModel.cs new file mode 100644 index 0000000..e94f66f --- /dev/null +++ b/ViewModels/AboutViewModel.cs @@ -0,0 +1,18 @@ +using System; +using System.Windows.Input; +using Xamarin.Essentials; +using Xamarin.Forms; + +namespace MobApp.ViewModels +{ + public class AboutViewModel : BaseViewModel + { + public AboutViewModel() + { + Title = "About"; + OpenWebCommand = new Command(async () => await Browser.OpenAsync("https://aka.ms/xamarin-quickstart")); + } + + public ICommand OpenWebCommand { get; } + } +} \ No newline at end of file diff --git a/ViewModels/BaseViewModel.cs b/ViewModels/BaseViewModel.cs new file mode 100644 index 0000000..6d351f8 --- /dev/null +++ b/ViewModels/BaseViewModel.cs @@ -0,0 +1,54 @@ +using MobApp.Models; +using MobApp.Services; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Runtime.CompilerServices; +using Xamarin.Forms; + +namespace MobApp.ViewModels +{ + public class BaseViewModel : INotifyPropertyChanged + { + public IDataStore DataStore => DependencyService.Get>(); + + bool isBusy = false; + public bool IsBusy + { + get { return isBusy; } + set { SetProperty(ref isBusy, value); } + } + + string title = string.Empty; + public string Title + { + get { return title; } + set { SetProperty(ref title, value); } + } + + protected bool SetProperty(ref T backingStore, T value, + [CallerMemberName] string propertyName = "", + Action onChanged = null) + { + if (EqualityComparer.Default.Equals(backingStore, value)) + return false; + + backingStore = value; + onChanged?.Invoke(); + OnPropertyChanged(propertyName); + return true; + } + + #region INotifyPropertyChanged + public event PropertyChangedEventHandler PropertyChanged; + protected void OnPropertyChanged([CallerMemberName] string propertyName = "") + { + var changed = PropertyChanged; + if (changed == null) + return; + + changed.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + #endregion + } +} diff --git a/ViewModels/ItemDetailViewModel.cs b/ViewModels/ItemDetailViewModel.cs new file mode 100644 index 0000000..15bcbbc --- /dev/null +++ b/ViewModels/ItemDetailViewModel.cs @@ -0,0 +1,57 @@ +using MobApp.Models; +using System; +using System.Diagnostics; +using System.Threading.Tasks; +using Xamarin.Forms; + +namespace MobApp.ViewModels +{ + [QueryProperty(nameof(ItemId), nameof(ItemId))] + public class ItemDetailViewModel : BaseViewModel + { + private string itemId; + private string text; + private string description; + public string Id { get; set; } + + public string Text + { + get => text; + set => SetProperty(ref text, value); + } + + public string Description + { + get => description; + set => SetProperty(ref description, value); + } + + public string ItemId + { + get + { + return itemId; + } + set + { + itemId = value; + LoadItemId(value); + } + } + + public async void LoadItemId(string itemId) + { + try + { + var item = await DataStore.GetItemAsync(itemId); + Id = item.Id; + Text = item.Text; + Description = item.Description; + } + catch (Exception) + { + Debug.WriteLine("Failed to Load Item"); + } + } + } +} diff --git a/ViewModels/ItemsViewModel.cs b/ViewModels/ItemsViewModel.cs new file mode 100644 index 0000000..4587308 --- /dev/null +++ b/ViewModels/ItemsViewModel.cs @@ -0,0 +1,84 @@ +using MobApp.Models; +using MobApp.Views; +using System; +using System.Collections.ObjectModel; +using System.Diagnostics; +using System.Threading.Tasks; +using Xamarin.Forms; + +namespace MobApp.ViewModels +{ + public class ItemsViewModel : BaseViewModel + { + private Item _selectedItem; + + public ObservableCollection Items { get; } + public Command LoadItemsCommand { get; } + public Command AddItemCommand { get; } + public Command ItemTapped { get; } + + public ItemsViewModel() + { + Title = "Browse"; + Items = new ObservableCollection(); + LoadItemsCommand = new Command(async () => await ExecuteLoadItemsCommand()); + + ItemTapped = new Command(OnItemSelected); + + AddItemCommand = new Command(OnAddItem); + } + + async Task ExecuteLoadItemsCommand() + { + IsBusy = true; + + try + { + Items.Clear(); + var items = await DataStore.GetItemsAsync(true); + foreach (var item in items) + { + Items.Add(item); + } + } + catch (Exception ex) + { + Debug.WriteLine(ex); + } + finally + { + IsBusy = false; + } + } + + public void OnAppearing() + { + IsBusy = true; + SelectedItem = null; + } + + public Item SelectedItem + { + get => _selectedItem; + set + { + SetProperty(ref _selectedItem, value); + OnItemSelected(value); + } + } + + private async void OnAddItem(object obj) + { + await Shell.Current.GoToAsync(nameof(NewItemPage)); + } + + async void OnItemSelected(Item item) + { + if (item == null) + return; + + // This will push the ItemDetailPage onto the navigation stack + await Shell.Current.GoToAsync($"{nameof(ItemDetailPage)}?{nameof(ItemDetailViewModel.ItemId)}={item.Id}"); + } + } +} \ No newline at end of file diff --git a/ViewModels/LoginViewModel.cs b/ViewModels/LoginViewModel.cs new file mode 100644 index 0000000..8ac4ec1 --- /dev/null +++ b/ViewModels/LoginViewModel.cs @@ -0,0 +1,24 @@ +using MobApp.Views; +using System; +using System.Collections.Generic; +using System.Text; +using Xamarin.Forms; + +namespace MobApp.ViewModels +{ + public class LoginViewModel : BaseViewModel + { + public Command LoginCommand { get; } + + public LoginViewModel() + { + LoginCommand = new Command(OnLoginClicked); + } + + private async void OnLoginClicked(object obj) + { + // Prefixing with `//` switches to a different navigation stack instead of pushing to the active one + await Shell.Current.GoToAsync($"//{nameof(AboutPage)}"); + } + } +} diff --git a/ViewModels/NewItemViewModel.cs b/ViewModels/NewItemViewModel.cs new file mode 100644 index 0000000..603537d --- /dev/null +++ b/ViewModels/NewItemViewModel.cs @@ -0,0 +1,65 @@ +using MobApp.Models; +using System; +using System.Collections.Generic; +using System.Text; +using System.Windows.Input; +using Xamarin.Forms; + +namespace MobApp.ViewModels +{ + public class NewItemViewModel : BaseViewModel + { + private string text; + private string description; + + public NewItemViewModel() + { + SaveCommand = new Command(OnSave, ValidateSave); + CancelCommand = new Command(OnCancel); + this.PropertyChanged += + (_, __) => SaveCommand.ChangeCanExecute(); + } + + private bool ValidateSave() + { + return !String.IsNullOrWhiteSpace(text) + && !String.IsNullOrWhiteSpace(description); + } + + public string Text + { + get => text; + set => SetProperty(ref text, value); + } + + public string Description + { + get => description; + set => SetProperty(ref description, value); + } + + public Command SaveCommand { get; } + public Command CancelCommand { get; } + + private async void OnCancel() + { + // This will pop the current page off the navigation stack + await Shell.Current.GoToAsync(".."); + } + + private async void OnSave() + { + Item newItem = new Item() + { + Id = Guid.NewGuid().ToString(), + Text = Text, + Description = Description + }; + + await DataStore.AddItemAsync(newItem); + + // This will pop the current page off the navigation stack + await Shell.Current.GoToAsync(".."); + } + } +} diff --git a/Views/AboutPage.xaml b/Views/AboutPage.xaml new file mode 100644 index 0000000..07fac84 --- /dev/null +++ b/Views/AboutPage.xaml @@ -0,0 +1,52 @@ + + + + + + + + + + #96d1ff + + + + + + + + + + + + + + + + + + + + + + diff --git a/Views/NewItemPage.xaml.cs b/Views/NewItemPage.xaml.cs new file mode 100644 index 0000000..82e9b78 --- /dev/null +++ b/Views/NewItemPage.xaml.cs @@ -0,0 +1,21 @@ +using MobApp.Models; +using MobApp.ViewModels; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using Xamarin.Forms; +using Xamarin.Forms.Xaml; + +namespace MobApp.Views +{ + public partial class NewItemPage : ContentPage + { + public Item Item { get; set; } + + public NewItemPage() + { + InitializeComponent(); + BindingContext = new NewItemViewModel(); + } + } +} \ No newline at end of file