commit 353dde03c56882a88cb0b288cfe58099c19805f5 Author: Julia Date: Thu Mar 13 11:23:08 2025 +0300 first commit 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