flower-story/FlowerApp/Views/Garden/AddFlowerPage.xaml.cs
2023-08-02 23:45:04 +08:00

390 lines
13 KiB
C#

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<Location?, AddFlowerPage>(nameof(CurrentLocation), propertyChanged: OnCurrentLocationPropertyChanged);
static readonly BindableProperty CurrentLocationStringProperty = CreateProperty<string?, AddFlowerPage>(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<GeoResult>($"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<Location?>(CurrentLocationProperty);
set => SetValue(CurrentLocationProperty, value);
}
public string? CurrentLocationString
{
get => GetValue<string?>(CurrentLocationStringProperty);
set => SetValue(CurrentLocationStringProperty, value);
}
static readonly BindableProperty CoverProperty = CreateProperty<string?, AddFlowerPage>(nameof(Cover));
static readonly BindableProperty FlowerNameProperty = CreateProperty<string, AddFlowerPage>(nameof(FlowerName));
static readonly BindableProperty FlowerLocationProperty = CreateProperty<string?, AddFlowerPage>(nameof(FlowerLocation), defaultValue: L("selectFlowerLocation", "Please select the location"));
static readonly BindableProperty CategoryProperty = CreateProperty<string, AddFlowerPage>(nameof(Category), defaultValue: L("selectFlowerCategory", "Please select the flower category"));
static readonly BindableProperty PurchaseDateProperty = CreateProperty<DateTime, AddFlowerPage>(nameof(PurchaseDate));
static readonly BindableProperty PurchaseTimeProperty = CreateProperty<TimeSpan, AddFlowerPage>(nameof(PurchaseTime));
static readonly BindableProperty PurchaseFromProperty = CreateProperty<string?, AddFlowerPage>(nameof(PurchaseFrom), defaultValue: L("selectPurchaseFrom", "Please select where are you purchase from"));
static readonly BindableProperty CostProperty = CreateProperty<string?, AddFlowerPage>(nameof(Cost));
static readonly BindableProperty MemoProperty = CreateProperty<string?, AddFlowerPage>(nameof(Memo));
public string? Cover
{
get => GetValue<string?>(CoverProperty);
set => SetValue(CoverProperty, value);
}
public string FlowerName
{
get => GetValue<string>(FlowerNameProperty);
set => SetValue(FlowerNameProperty, value);
}
public string? FlowerLocation
{
get => GetValue<string?>(FlowerLocationProperty);
set => SetValue(FlowerLocationProperty, value);
}
public string Category
{
get => GetValue<string>(CategoryProperty);
set => SetValue(CategoryProperty, value);
}
public DateTime PurchaseDate
{
get => GetValue<DateTime>(PurchaseDateProperty);
set => SetValue(PurchaseDateProperty, value);
}
public TimeSpan PurchaseTime
{
get => GetValue<TimeSpan>(PurchaseTimeProperty);
set => SetValue(PurchaseTimeProperty, value);
}
public string? PurchaseFrom
{
get => GetValue<string>(PurchaseFromProperty);
set => SetValue(PurchaseFromProperty, value);
}
public string? Cost
{
get => GetValue<string?>(CostProperty);
set => SetValue(CostProperty, value);
}
public string? Memo
{
get => GetValue<string>(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<int, IdTextItem<int>>(
L("flowerCategory", "Flower category"),
categories.Select(c => new IdTextItem<int>
{
Id = c.Key,
Text = c.Value.Name,
Detail = c.Value.Description
}).ToArray(),
selected: selectedCategoryId != null ?
new[] { selectedCategoryId.Value } :
Array.Empty<int>(),
detail: nameof(IdTextItem<int>.Detail));
page.Selected += FlowerCategory_Selected;
await Navigation.PushAsync(page);
}
private void FlowerCategory_Selected(object? sender, IdTextItem<int> 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<FlowerItem>("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; }
}