using Blahblah.FlowerApp.Controls; using Blahblah.FlowerApp.Data; using Blahblah.FlowerApp.Data.Model; using Microsoft.Extensions.Logging; using System.Globalization; using System.Net; using System.Net.Http.Json; using System.Text.Json.Serialization; using static Blahblah.FlowerApp.Extensions; namespace Blahblah.FlowerApp.Views.Garden; public partial class AddFlowerPage : AppContentPage { static readonly BindableProperty CurrentLocationProperty = CreateProperty(nameof(CurrentLocation), propertyChanged: OnCurrentLocationPropertyChanged); static readonly BindableProperty CurrentLocationStringProperty = CreateProperty(nameof(CurrentLocationString), defaultValue: L("locating", "Locating...")); static void OnCurrentLocationPropertyChanged(BindableObject bindable, object old, object @new) { if (bindable is AddFlowerPage page) { if (@new is Location loc) { Task.Run(async () => { string? city = null; try { var location = WebUtility.UrlEncode($"{{\"x\":{loc.Longitude},\"y\":{loc.Latitude},\"spatialReference\":{{\"wkid\":4326}}}}"); using var client = new HttpClient(); var result = await client.GetFromJsonAsync($"https://geocode.arcgis.com/arcgis/rest/services/World/GeocodeServer/reverseGeocode?location={location}&distance=100&f=json"); if (result != null) { if (result.Address == null) { page.LogWarning($"failed to query geo location, with message: {result.Error?.Message}"); } else { city = result.Address.City; } } } catch (Exception ex) { city = L("unknown", "Unknown"); page.LogError(ex, $"error occurs when quering geo location: {ex.Message}"); } page.SetValue(CurrentLocationStringProperty, city ?? L("unknown", "Unknown")); }); } else { page.SetValue(CurrentLocationStringProperty, L("unknown", "Unknown")); } } } public Location? CurrentLocation { get => GetValue(CurrentLocationProperty); set => SetValue(CurrentLocationProperty, value); } public string? CurrentLocationString { get => GetValue(CurrentLocationStringProperty); set => SetValue(CurrentLocationStringProperty, value); } static readonly BindableProperty CoverProperty = CreateProperty(nameof(Cover)); static readonly BindableProperty FlowerNameProperty = CreateProperty(nameof(FlowerName)); static readonly BindableProperty FlowerLocationProperty = CreateProperty(nameof(FlowerLocation), defaultValue: L("selectFlowerLocation", "Please select the location")); static readonly BindableProperty CategoryProperty = CreateProperty(nameof(Category), defaultValue: L("selectFlowerCategory", "Please select the flower category")); static readonly BindableProperty PurchaseDateProperty = CreateProperty(nameof(PurchaseDate)); static readonly BindableProperty PurchaseTimeProperty = CreateProperty(nameof(PurchaseTime)); static readonly BindableProperty PurchaseFromProperty = CreateProperty(nameof(PurchaseFrom), defaultValue: L("selectPurchaseFrom", "Please select where are you purchase from")); static readonly BindableProperty CostProperty = CreateProperty(nameof(Cost)); static readonly BindableProperty MemoProperty = CreateProperty(nameof(Memo)); public string? Cover { get => GetValue(CoverProperty); set => SetValue(CoverProperty, value); } public string FlowerName { get => GetValue(FlowerNameProperty); set => SetValue(FlowerNameProperty, value); } public string? FlowerLocation { get => GetValue(FlowerLocationProperty); set => SetValue(FlowerLocationProperty, value); } public string Category { get => GetValue(CategoryProperty); set => SetValue(CategoryProperty, value); } public DateTime PurchaseDate { get => GetValue(PurchaseDateProperty); set => SetValue(PurchaseDateProperty, value); } public TimeSpan PurchaseTime { get => GetValue(PurchaseTimeProperty); set => SetValue(PurchaseTimeProperty, value); } public string? PurchaseFrom { get => GetValue(PurchaseFromProperty); set => SetValue(PurchaseFromProperty, value); } public string? Cost { get => GetValue(CostProperty); set => SetValue(CostProperty, value); } public string? Memo { get => GetValue(MemoProperty); set => SetValue(MemoProperty, value); } string? selectedLocation; int? selectedCategoryId; public AddFlowerPage(FlowerDatabase database, ILogger logger) : base(database, logger) { var now = DateTime.Now; PurchaseDate = now.Date; PurchaseTime = now.TimeOfDay; InitializeComponent(); } protected override void OnAppearing() { base.OnAppearing(); #if DEBUG MainThread.BeginInvokeOnMainThread(() => CurrentLocation = new Location(29.56128954116272, 106.5447724580102)); #else MainThread.BeginInvokeOnMainThread(GetLocation); } bool accuracyLocation = false; async void GetLocation() { var location = await GetLastLocationAsync(); CurrentLocation = location; if (!accuracyLocation) { location = await GetCurrentLocationAsync(); CurrentLocation = location; accuracyLocation = location != null; } #endif } private async void ButtonTakePhoto_Clicked(object sender, EventArgs e) { if (MediaPicker.Default.IsCaptureSupported) { var photo = await TakePhoto(); if (photo != null) { string cache = await CacheFileAsync(photo); Cover = cache; } } else { await this.AlertError(L("notSupportedCapture", "Your device does not support taking photos.")); } } private async void ButtonSelectPhoto_Clicked(object sender, EventArgs e) { var photo = await MediaPicker.Default.PickPhotoAsync(); if (photo != null) { string cache = await CacheFileAsync(photo); Cover = cache; } } private void ButtonClearCover_Clicked(object sender, EventArgs e) { Cover = null; } private async void FlowerCategory_Tapped(object sender, EventArgs e) { var categories = Database.Categories; if (categories == null) { return; } var page = new ItemSelectorPage>( L("flowerCategory", "Flower category"), categories.Select(c => new IdTextItem { Id = c.Key, Text = c.Value.Name, Detail = c.Value.Description }).ToArray(), selected: selectedCategoryId != null ? new[] { selectedCategoryId.Value } : Array.Empty(), detail: nameof(IdTextItem.Detail)); page.Selected += FlowerCategory_Selected; await Navigation.PushAsync(page); } private void FlowerCategory_Selected(object? sender, IdTextItem category) { selectedCategoryId = category.Id; Category = category.Text; } private async void Save_Clicked(object sender, EventArgs e) { var name = FlowerName; if (string.IsNullOrEmpty(name)) { await this.Alert(Title, L("flowerNameRequired", "Flower name is required.")); return; } // TODO: selectedLocation var location = FlowerLocation; if (string.IsNullOrEmpty(location)) { await this.Alert(Title, L("locationRequired", "Location is required.")); return; } if (selectedCategoryId == null) { await this.Alert(Title, L("flowerCategoryRequired", "Flower category is required.")); return; } var purchaseDate = new DateTimeOffset((PurchaseDate + PurchaseTime).ToUniversalTime()); var purchaseFrom = PurchaseFrom; if (!decimal.TryParse(Cost, out decimal cost) || cost < 0) { await this.Alert(Title, L("costInvalid", "Cost must be a positive number.")); return; } var memo = Memo; var item = new FlowerItem { Name = name, CategoryId = selectedCategoryId.Value, DateBuyUnixTime = purchaseDate.ToUnixTimeMilliseconds(), Purchase = purchaseFrom, Cost = cost, Memo = memo, OwnerId = AppResources.User.Id }; var loc = CurrentLocation; if (loc != null) { item.Latitude = loc.Latitude; item.Longitude = loc.Longitude; } var cover = Cover; if (cover != null) { item.Photos = new[] { new PhotoItem { Url = cover } }; } await Loading(true); _ = Task.Run(async () => { try { await DoAddFlowerAsync(item); } catch (Exception ex) { this.LogError(ex, $"error occurs while adding flower, {item}"); await this.AlertError(L("failedAddFlower", "Failed to add flower, {error}, please try again later.").Replace("{error}", ex.Message)); } }); } async Task DoAddFlowerAsync(FlowerItem item) { var data = new MultipartFormDataContent { { new StringContent(item.CategoryId.ToString()), "categoryId" }, { new StringContent(item.Name), "name" }, { new StringContent(item.DateBuyUnixTime.ToString()), "dateBuy" } }; if (item.Cost != null) { data.Add(new StringContent($"{item.Cost}"), "cost"); } if (item.Purchase != null) { data.Add(new StringContent(item.Purchase), "purchase"); } if (item.Memo != null) { data.Add(new StringContent(item.Memo), "memo"); } if (item.Latitude != null && item.Longitude != null) { data.Add(new StringContent($"{item.Latitude}"), "lat"); data.Add(new StringContent($"{item.Longitude}"), "lon"); } if (item.Photos?.Length > 0) { data.Add(new StreamContent(File.OpenRead(item.Photos[0].Url)), "cover"); } var result = await UploadAsync("api/flower/add", data); this.LogInformation($"upload successfully, {result}"); } } class CoverConverter : IValueConverter { public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) { if (value is string s && !string.IsNullOrEmpty(s)) { return s; } return "empty_flower.jpg"; } public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) { throw new NotImplementedException(); } } record GeoResult { [JsonPropertyName("address")] public AddressResult? Address { get; init; } [JsonPropertyName("error")] public ErrorResult? Error { get; init; } } record ErrorResult { [JsonPropertyName("code")] public int Code { get; init; } [JsonPropertyName("message")] public string Message { get; init; } = null!; } record AddressResult { [JsonPropertyName("City")] public string? City { get; init; } [JsonPropertyName("District")] public string? District { get; init; } [JsonPropertyName("CntryName")] public string? CountryName { get; init; } [JsonPropertyName("CountryCode")] public string? CountryCode { get; init; } [JsonPropertyName("Region")] public string? Region { get; init; } [JsonPropertyName("RegionAbbr")] public string? RegionAbbr { get; init; } }