diff --git a/FlowerApp/AppShell.xaml b/FlowerApp/AppShell.xaml
index 9ff868e..aaa8eff 100644
--- a/FlowerApp/AppShell.xaml
+++ b/FlowerApp/AppShell.xaml
@@ -3,13 +3,13 @@
x:Class="Blahblah.FlowerApp.AppShell"
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
- xmlns:local="clr-namespace:Blahblah.FlowerApp"
+ xmlns:l="clr-namespace:Blahblah.FlowerApp"
Shell.FlyoutBehavior="Disabled"
Title="Flower Story">
+ Title="{l:Lang home, Default=Garden Square}"
+ ContentTemplate="{DataTemplate l:HomePage}"
+ Route="HomePage" />
diff --git a/FlowerApp/Controls/AppContentPage.cs b/FlowerApp/Controls/AppContentPage.cs
new file mode 100644
index 0000000..1ca619e
--- /dev/null
+++ b/FlowerApp/Controls/AppContentPage.cs
@@ -0,0 +1,59 @@
+namespace Blahblah.FlowerApp;
+
+public class AppContentPage : ContentPage
+{
+ protected static string L(string key, string defaultValue = "")
+ {
+ return LocalizationResource.GetText(key, defaultValue);
+ }
+
+ protected static Task FetchAsync(string url, CancellationToken cancellation = default)
+ {
+ return Extensions.FetchAsync(url, cancellation);
+ }
+
+ protected T GetValue(BindableProperty property)
+ {
+ return (T)GetValue(property);
+ }
+
+ protected Task AlertError(string error)
+ {
+ return Alert(LocalizationResource.GetText("error", "Error"), error);
+ }
+
+ protected Task Alert(string title, string message, string? cancel = null)
+ {
+ cancel ??= LocalizationResource.GetText("ok", "Ok");
+
+ if (MainThread.IsMainThread)
+ {
+ return DisplayAlert(title, message, cancel);
+ }
+ var taskSource = new TaskCompletionSource();
+ MainThread.BeginInvokeOnMainThread(async () =>
+ {
+ await DisplayAlert(title, message, cancel);
+ taskSource.TrySetResult();
+ });
+ return taskSource.Task;
+ }
+
+ protected Task Confirm(string title, string question, string? accept = null, string? cancel = null)
+ {
+ accept ??= LocalizationResource.GetText("yes", "Yes");
+ cancel ??= LocalizationResource.GetText("no", "No");
+
+ if (MainThread.IsMainThread)
+ {
+ return DisplayAlert(title, question, accept, cancel);
+ }
+ var taskSource = new TaskCompletionSource();
+ MainThread.BeginInvokeOnMainThread(async () =>
+ {
+ var result = await DisplayAlert(title, question, accept, cancel);
+ taskSource.TrySetResult(result);
+ });
+ return taskSource.Task;
+ }
+}
diff --git a/FlowerApp/Controls/AppResources.cs b/FlowerApp/Controls/AppResources.cs
new file mode 100644
index 0000000..d3458cb
--- /dev/null
+++ b/FlowerApp/Controls/AppResources.cs
@@ -0,0 +1,8 @@
+namespace Blahblah.FlowerApp;
+
+internal class AppResources
+{
+ public const string EmptyCover = "empty_flower.jpg";
+
+ public static readonly Size EmptySize = new(512, 339);
+}
diff --git a/FlowerApp/Controls/FlowerClientItem.cs b/FlowerApp/Controls/FlowerClientItem.cs
new file mode 100644
index 0000000..211b7e4
--- /dev/null
+++ b/FlowerApp/Controls/FlowerClientItem.cs
@@ -0,0 +1,57 @@
+using Blahblah.FlowerApp.Data.Model;
+using static Blahblah.FlowerApp.PropertyExtension;
+
+namespace Blahblah.FlowerApp.Controls;
+
+public class FlowerClientItem : BindableObject
+{
+ static readonly BindableProperty NameProperty = CreateProperty(nameof(Name));
+ static readonly BindableProperty CategoryProperty = CreateProperty(nameof(Category));
+ static readonly BindableProperty CoverProperty = CreateProperty(nameof(Cover));
+ static readonly BindableProperty BoundsProperty = CreateProperty(nameof(Bounds));
+
+ public int Id { get; }
+ public FlowerItem? FlowerItem { get; }
+
+ public string Name
+ {
+ get => (string)GetValue(NameProperty);
+ set => SetValue(NameProperty, value);
+ }
+ public int Category
+ {
+ get => (int)GetValue(CategoryProperty);
+ set => SetValue(CategoryProperty, value);
+ }
+ public ImageSource? Cover
+ {
+ get => (ImageSource?)GetValue(CoverProperty);
+ set => SetValue(CoverProperty, value);
+ }
+ public Rect Bounds
+ {
+ get => (Rect)GetValue(BoundsProperty);
+ set => SetValue(BoundsProperty, value);
+ }
+
+ public int? Width { get; set; }
+ public int? Height { get; set; }
+
+ public FlowerClientItem(int id)
+ {
+ Id = id;
+ }
+
+ public FlowerClientItem(FlowerItem item) : this(item.Id)
+ {
+ FlowerItem = item;
+ Name = item.Name;
+ Category = item.Category;
+
+ if (item.Photos?.Length > 0 && item.Photos[0] is PhotoItem cover)
+ {
+ Width = cover.Width;
+ Height = cover.Height;
+ }
+ }
+}
diff --git a/FlowerApp/Controls/PropertyExtension.cs b/FlowerApp/Controls/PropertyExtension.cs
new file mode 100644
index 0000000..e5b52c2
--- /dev/null
+++ b/FlowerApp/Controls/PropertyExtension.cs
@@ -0,0 +1,9 @@
+namespace Blahblah.FlowerApp;
+
+internal sealed class PropertyExtension
+{
+ public static BindableProperty CreateProperty(string propertyName, T? defaultValue = default)
+ {
+ return BindableProperty.Create(propertyName, typeof(T), typeof(V), defaultValue);
+ }
+}
diff --git a/FlowerApp/Controls/WaterfallLayout.cs b/FlowerApp/Controls/WaterfallLayout.cs
deleted file mode 100644
index e771ad5..0000000
--- a/FlowerApp/Controls/WaterfallLayout.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-namespace Blahblah.FlowerApp.Controls;
-
-public class WaterfallLayout : View
-{
- public static readonly BindableProperty ColumnsProperty = BindableProperty.Create(nameof(Columns), typeof(int), typeof(WaterfallLayout), defaultValue: 2);
-
- public int Columns
- {
- get => (int)GetValue(ColumnsProperty);
- set => SetValue(ColumnsProperty, value);
- }
-}
diff --git a/FlowerApp/Data/Constants.cs b/FlowerApp/Data/Constants.cs
index e42125e..277f166 100644
--- a/FlowerApp/Data/Constants.cs
+++ b/FlowerApp/Data/Constants.cs
@@ -1,19 +1,76 @@
-using SQLite;
+using Microsoft.Extensions.Logging;
+using SQLite;
namespace Blahblah.FlowerApp.Data;
-public sealed class Constants
+internal sealed class Constants
{
public const string CategoryOther = "other";
public const string EventUnknown = "unknown";
- public const string BaseUrl = "https://flower.tsanie.org";
+ public const string ApiVersionName = "api_version";
+ public const string LastTokenName = "last_token";
+
+ public const string BaseUrl = "https://app.blahblaho.com";
public const SQLiteOpenFlags SQLiteFlags =
SQLiteOpenFlags.ReadWrite |
SQLiteOpenFlags.Create |
SQLiteOpenFlags.SharedCache;
- private const string dbFilename = "flowerstory.db3";
+ const string dbFilename = "flowerstory.db3";
public static string DatabasePath => Path.Combine(FileSystem.AppDataDirectory, dbFilename);
+
+ static string? apiVersion;
+ public static string? ApiVersion => apiVersion;
+
+ static string? authorization;
+ public static string? Authorization => authorization;
+
+ public static void SetAuthorization(string auth)
+ {
+ authorization = auth;
+ }
+
+ public static async Task Initialize(ILogger logger, string? version, CancellationToken cancellation = default)
+ {
+ try
+ {
+ var v = await Extensions.FetchAsync("api/version", cancellation);
+ apiVersion = v;
+
+ if (v != version)
+ {
+ var definition = await Extensions.FetchAsync($"api/consts?{v}", cancellation);
+ return definition;
+ }
+ }
+ catch (Exception ex)
+ {
+ logger.LogError("error occurs on fetching version and definitions, {error}", ex);
+ }
+
+ return null;
+ }
}
+
+internal record Definitions
+{
+ public required string ApiVersion { get; init; }
+
+ public required Dictionary Categories { get; init; }
+
+ public required Dictionary Events { get; init; }
+}
+
+internal record NamedItem(string Name, string? Description)
+{
+ public string Name { get; init; } = Name;
+
+ public string? Description { get; init; } = Description;
+}
+
+internal record EventItem(string Name, string? Description, bool Unique) : NamedItem(Name, Description)
+{
+ public bool Unique { get; init; } = Unique;
+}
\ No newline at end of file
diff --git a/FlowerApp/Data/FlowerDatabase.cs b/FlowerApp/Data/FlowerDatabase.cs
index fa4990c..0a4ffec 100644
--- a/FlowerApp/Data/FlowerDatabase.cs
+++ b/FlowerApp/Data/FlowerDatabase.cs
@@ -13,6 +13,130 @@ public class FlowerDatabase
public FlowerDatabase(ILogger logger)
{
this.logger = logger;
+
+ Task.Run(async () =>
+ {
+ try
+ {
+ await Setup();
+ }
+ catch (Exception ex)
+ {
+ logger.LogError("error occurs on setup, {error}", ex);
+ }
+ });
+ }
+
+ private Dictionary? categories;
+
+ private Dictionary? events;
+
+ public string Category(int categoryId)
+ {
+ if (categories?.TryGetValue(categoryId, out var category) == true)
+ {
+ return category.Name;
+ }
+ return Constants.CategoryOther;
+ }
+
+ public string Event(int eventId)
+ {
+ if (events?.TryGetValue(eventId, out var @event) == true)
+ {
+ return @event.Name;
+ }
+ return Constants.EventUnknown;
+ }
+
+ private async Task Setup()
+ {
+ await Init();
+
+#if DEBUG
+ Constants.SetAuthorization("RF4mfoUur0vHtWzHwD42ka0FhIfGaPnBxoQgrXOYEDg=");
+#else
+ var token = await database.Table().FirstOrDefaultAsync(p => p.Code == Constants.LastTokenName);
+ if (token != null)
+ {
+ Constants.SetAuthorization(token.Value);
+ }
+#endif
+
+ var version = await database.Table().FirstOrDefaultAsync(p => p.Code == Constants.ApiVersionName);
+ var definition = await Constants.Initialize(logger, version?.Value);
+
+ if (definition != null)
+ {
+ categories = definition.Categories;
+ events = definition.Events;
+
+ logger.LogInformation("new version founded, from ({from}) to ({to})", version?.Value, definition.ApiVersion);
+
+ if (version == null)
+ {
+ version = new ParamItem
+ {
+ Code = Constants.ApiVersionName,
+ Value = definition.ApiVersion
+ };
+ }
+ else
+ {
+ version.Value = definition.ApiVersion;
+ }
+ await database.InsertOrReplaceAsync(version);
+
+ // replace local definitions
+ await database.DeleteAllAsync();
+
+ var defs = new List();
+ foreach (var category in definition.Categories)
+ {
+ defs.Add(new DefinitionItem
+ {
+ DefinitionType = 0,
+ DefinitionId = category.Key,
+ Name = category.Value.Name,
+ Description = category.Value.Description
+ });
+ }
+ foreach (var @event in definition.Events)
+ {
+ defs.Add(new DefinitionItem
+ {
+ DefinitionType = 1,
+ DefinitionId = @event.Key,
+ Name = @event.Value.Name,
+ Description = @event.Value.Description,
+ Unique = @event.Value.Unique
+ });
+ }
+ var rows = await database.InsertAllAsync(defs);
+ logger.LogInformation("{count} definitions, {rows} rows inserted", defs.Count, rows);
+ }
+ else
+ {
+ // use local definitions
+ var defs = await database.Table().ToListAsync();
+ var cates = new Dictionary();
+ var evts = new Dictionary();
+ foreach (var d in defs)
+ {
+ if (d.DefinitionType == 0)
+ {
+ // category
+ cates[d.DefinitionId] = new NamedItem(d.Name, d.Description);
+ }
+ else if (d.DefinitionType == 1)
+ {
+ // event
+ evts[d.DefinitionId] = new EventItem(d.Name, d.Description, d.Unique ?? false);
+ }
+ }
+ categories = cates;
+ events = evts;
+ }
}
private async Task Init()
@@ -27,7 +151,13 @@ public class FlowerDatabase
#if DEBUG
var result =
#endif
- await database.CreateTablesAsync();
+ await database.CreateTablesAsync(CreateFlags.None,
+ typeof(FlowerItem),
+ typeof(RecordItem),
+ typeof(PhotoItem),
+ typeof(UserItem),
+ typeof(DefinitionItem),
+ typeof(ParamItem));
#if DEBUG
foreach (var item in result.Results)
diff --git a/FlowerApp/Data/Model/DefinitionItem.cs b/FlowerApp/Data/Model/DefinitionItem.cs
new file mode 100644
index 0000000..8a5102d
--- /dev/null
+++ b/FlowerApp/Data/Model/DefinitionItem.cs
@@ -0,0 +1,29 @@
+using SQLite;
+
+namespace Blahblah.FlowerApp.Data.Model;
+
+[Table("definitions")]
+public class DefinitionItem
+{
+ [Column("did"), PrimaryKey, AutoIncrement]
+ public int Id { get; set; }
+
+ ///
+ /// - 0: category
+ /// - 1: event
+ ///
+ [Column("type"), NotNull]
+ public int DefinitionType { get; set; }
+
+ [Column("id"), NotNull]
+ public int DefinitionId { get; set; }
+
+ [Column("name"), NotNull]
+ public string Name { get; set; } = null!;
+
+ [Column("description")]
+ public string? Description { get; set; }
+
+ [Column("unique")]
+ public bool? Unique { get; set; }
+}
diff --git a/FlowerApp/Data/Model/FlowerItem.cs b/FlowerApp/Data/Model/FlowerItem.cs
index 9152d7c..bc00eda 100644
--- a/FlowerApp/Data/Model/FlowerItem.cs
+++ b/FlowerApp/Data/Model/FlowerItem.cs
@@ -20,8 +20,6 @@ public class FlowerItem
[Column("datebuy"), NotNull]
public long DateBuyUnixTime { get; set; }
- public DateTimeOffset DateBuy => DateTimeOffset.FromUnixTimeMilliseconds(DateBuyUnixTime);
-
[Column("cost")]
public decimal? Cost { get; set; }
@@ -37,5 +35,9 @@ public class FlowerItem
[Column("longitude")]
public double? Longitude { get; set; }
+ [Ignore]
+ public PhotoItem[]? Photos { get; set; }
+
+ [Ignore]
public int? Distance { get; set; }
}
diff --git a/FlowerApp/Data/Model/ParamItem.cs b/FlowerApp/Data/Model/ParamItem.cs
new file mode 100644
index 0000000..38fe911
--- /dev/null
+++ b/FlowerApp/Data/Model/ParamItem.cs
@@ -0,0 +1,22 @@
+using SQLite;
+
+namespace Blahblah.FlowerApp.Data.Model;
+
+[Table("params")]
+public class ParamItem
+{
+ [Column("pid"), PrimaryKey, AutoIncrement]
+ public int Id { get; set; }
+
+ [Column("uid")]
+ public int? OwnerId { get; set; }
+
+ [Column("code"), NotNull]
+ public string Code { get; set; } = null!;
+
+ [Column("value"), NotNull]
+ public string Value { get; set; } = null!;
+
+ [Column("description")]
+ public string? Description { get; set; }
+}
diff --git a/FlowerApp/Data/Model/PhotoItem.cs b/FlowerApp/Data/Model/PhotoItem.cs
index 6785db8..cff3a4d 100644
--- a/FlowerApp/Data/Model/PhotoItem.cs
+++ b/FlowerApp/Data/Model/PhotoItem.cs
@@ -29,8 +29,12 @@ public class PhotoItem
[Column("dateupload"), NotNull]
public long DateUploadUnixTime { get; set; }
- public DateTimeOffset DateUpload => DateTimeOffset.FromUnixTimeMilliseconds(DateUploadUnixTime);
-
[Column("url")]
public string Url { get; set; } = null!;
+
+ [Column("width")]
+ public int? Width { get; set; }
+
+ [Column("height")]
+ public int? Height { get; set; }
}
diff --git a/FlowerApp/Data/Model/RecordItem.cs b/FlowerApp/Data/Model/RecordItem.cs
index 63d7a8e..f2c3095 100644
--- a/FlowerApp/Data/Model/RecordItem.cs
+++ b/FlowerApp/Data/Model/RecordItem.cs
@@ -19,8 +19,6 @@ public class RecordItem
[Column("date"), NotNull]
public long DateUnixTime { get; set; }
- public DateTimeOffset Date => DateTimeOffset.FromUnixTimeMilliseconds(DateUnixTime);
-
[Column("byuid")]
public int? ByUserId { get; set; }
diff --git a/FlowerApp/Data/Model/UserItem.cs b/FlowerApp/Data/Model/UserItem.cs
index fe78138..0a0e497 100644
--- a/FlowerApp/Data/Model/UserItem.cs
+++ b/FlowerApp/Data/Model/UserItem.cs
@@ -4,15 +4,15 @@ namespace Blahblah.FlowerApp.Data.Model;
public class UserItem
{
- [Column("uid"), PrimaryKey, AutoIncrement]
+ [Column("uid"), PrimaryKey, NotNull]
public int Id { get; set; }
+ [Column("token"), Indexed, NotNull]
+ public string Token { get; set; } = null!;
+
[Column("id"), NotNull]
public string UserId { get; set; } = null!;
- [Column("token"), NotNull]
- public string Token { get; set; } = null!;
-
[Column("name"), NotNull]
public string Name { get; set; } = null!;
@@ -22,8 +22,6 @@ public class UserItem
[Column("regdate"), NotNull]
public long RegisterDateUnixTime { get; set; }
- public DateTimeOffset RegisterDate => DateTimeOffset.FromUnixTimeMilliseconds(RegisterDateUnixTime);
-
//[Column("activedate")]
//public long? ActiveDateUnixTime { get; set; }
diff --git a/FlowerApp/Extensions.cs b/FlowerApp/Extensions.cs
new file mode 100644
index 0000000..83619c3
--- /dev/null
+++ b/FlowerApp/Extensions.cs
@@ -0,0 +1,18 @@
+using Blahblah.FlowerApp.Data;
+using System.Net.Http.Json;
+
+namespace Blahblah.FlowerApp;
+
+internal sealed class Extensions
+{
+ public static async Task FetchAsync(string url, CancellationToken cancellation = default)
+ {
+ using var client = new HttpClient();
+ var authorization = Constants.Authorization;
+ if (authorization != null)
+ {
+ client.DefaultRequestHeaders.TryAddWithoutValidation("Authorization", authorization);
+ }
+ return await client.GetFromJsonAsync($"{Constants.BaseUrl}/{url}", cancellation);
+ }
+}
diff --git a/FlowerApp/FlowerApp.csproj b/FlowerApp/FlowerApp.csproj
index b23a472..33fa655 100644
--- a/FlowerApp/FlowerApp.csproj
+++ b/FlowerApp/FlowerApp.csproj
@@ -68,6 +68,7 @@
+
@@ -92,9 +93,14 @@
+
+
+
+
+
diff --git a/FlowerApp/Handlers/WaterfallLayoutHandler.cs b/FlowerApp/Handlers/WaterfallLayoutHandler.cs
deleted file mode 100644
index 9c06c96..0000000
--- a/FlowerApp/Handlers/WaterfallLayoutHandler.cs
+++ /dev/null
@@ -1,20 +0,0 @@
-using Blahblah.FlowerApp.Controls;
-
-namespace Blahblah.FlowerApp.Handlers;
-
-public partial class WaterfallLayoutHandler
-{
- static readonly IPropertyMapper PropertyMapper = new PropertyMapper(ViewMapper)
- {
- [nameof(WaterfallLayout.Columns)] = MapColumns
- };
-
- static readonly CommandMapper CommandMapper = new(ViewCommandMapper)
- {
-
- };
-
- public WaterfallLayoutHandler() : base(PropertyMapper, CommandMapper)
- {
- }
-}
diff --git a/FlowerApp/Localizations.Designer.cs b/FlowerApp/Localizations.Designer.cs
index 6ba343a..8785540 100644
--- a/FlowerApp/Localizations.Designer.cs
+++ b/FlowerApp/Localizations.Designer.cs
@@ -60,6 +60,60 @@ namespace Blahblah.FlowerApp {
}
}
+ ///
+ /// Looks up a localized string similar to Error.
+ ///
+ internal static string error {
+ get {
+ return ResourceManager.GetString("error", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Failed to get flowers, please try again..
+ ///
+ internal static string failedGetFlowers {
+ get {
+ return ResourceManager.GetString("failedGetFlowers", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Garden Square.
+ ///
+ internal static string home {
+ get {
+ return ResourceManager.GetString("home", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to No.
+ ///
+ internal static string no {
+ get {
+ return ResourceManager.GetString("no", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Nothing here....
+ ///
+ internal static string nothing {
+ get {
+ return ResourceManager.GetString("nothing", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Ok.
+ ///
+ internal static string ok {
+ get {
+ return ResourceManager.GetString("ok", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Unknown.
///
@@ -68,5 +122,14 @@ namespace Blahblah.FlowerApp {
return ResourceManager.GetString("unknown", resourceCulture);
}
}
+
+ ///
+ /// Looks up a localized string similar to Yes.
+ ///
+ internal static string yes {
+ get {
+ return ResourceManager.GetString("yes", resourceCulture);
+ }
+ }
}
}
diff --git a/FlowerApp/Localizations.resx b/FlowerApp/Localizations.resx
index 769ce41..b330f1c 100644
--- a/FlowerApp/Localizations.resx
+++ b/FlowerApp/Localizations.resx
@@ -117,7 +117,28 @@
System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+ Error
+
+
+ Failed to get flowers, please try again.
+
+
+ Garden Square
+
+
+ No
+
+
+ Nothing here...
+
+
+ Ok
+
Unknown
+
+ Yes
+
\ No newline at end of file
diff --git a/FlowerApp/Localizations.zh-CN.resx b/FlowerApp/Localizations.zh-CN.resx
index e094abf..399af99 100644
--- a/FlowerApp/Localizations.zh-CN.resx
+++ b/FlowerApp/Localizations.zh-CN.resx
@@ -117,7 +117,28 @@
System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+ 错误
+
+
+ 获取花草失败,请重试。
+
+
+ 花园广场
+
+
+ 否
+
+
+ 没有任何东西...
+
+
+ 好
+
未知
+
+ 是
+
\ No newline at end of file
diff --git a/FlowerApp/MainPage.xaml b/FlowerApp/MainPage.xaml
index bfbd785..c570e60 100644
--- a/FlowerApp/MainPage.xaml
+++ b/FlowerApp/MainPage.xaml
@@ -1,46 +1,51 @@
-
+ xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
+ x:Class="Blahblah.FlowerApp.HomePage"
+ x:Name="homePage"
+ x:DataType="l:HomePage">
-
-
-
-
-
-
+
diff --git a/FlowerApp/MainPage.xaml.cs b/FlowerApp/MainPage.xaml.cs
index 26dd0fb..f43961b 100644
--- a/FlowerApp/MainPage.xaml.cs
+++ b/FlowerApp/MainPage.xaml.cs
@@ -1,15 +1,41 @@
-using Blahblah.FlowerApp.Data;
+using Blahblah.FlowerApp.Controls;
+using Blahblah.FlowerApp.Data;
+using Blahblah.FlowerApp.Data.Model;
using Microsoft.Extensions.Logging;
+using static Blahblah.FlowerApp.PropertyExtension;
namespace Blahblah.FlowerApp;
-public partial class MainPage : ContentPage
+public partial class HomePage : AppContentPage
{
- int count = 0;
+ static readonly BindableProperty FlowersProperty = CreateProperty(nameof(Flowers));
+ static readonly BindableProperty IsRefreshingProperty = CreateProperty(nameof(IsRefreshing));
+
+ public FlowerClientItem[] Flowers
+ {
+ get => GetValue(FlowersProperty);
+ set => SetValue(FlowersProperty, value);
+ }
+ public bool IsRefreshing
+ {
+ get => GetValue(IsRefreshingProperty);
+ set => SetValue(IsRefreshingProperty, value);
+ }
+
readonly FlowerDatabase database;
readonly ILogger logger;
- public MainPage(FlowerDatabase database, ILogger logger)
+ bool loaded = false;
+ double pageWidth;
+
+ const int margin = 12;
+ const int cols = 2;
+ double[] ys = null!;
+ int yIndex;
+ int itemWidth;
+ int emptyHeight;
+
+ public HomePage(FlowerDatabase database, ILogger logger)
{
this.database = database;
this.logger = logger;
@@ -17,33 +43,115 @@ public partial class MainPage : ContentPage
InitializeComponent();
}
- protected override void OnAppearing()
+ protected override void OnSizeAllocated(double width, double height)
{
- base.OnAppearing();
+ base.OnSizeAllocated(width, height);
- Task.Run(async () =>
+ pageWidth = width - margin * 2;
+ if (!loaded)
{
- try
+ loaded = true;
+ IsRefreshing = true;
+ }
+ else if (Flowers?.Length > 0)
+ {
+ DoInitSize();
+ foreach (var item in Flowers)
{
- var list = await database.GetFlowers();
- logger.LogInformation("got {count} flowers.", list.Length);
+ DoResizeItem(item);
}
- catch (Exception ex)
- {
- logger.LogError("error occurs in MainPage, {exception}", ex);
- }
- });
+ }
}
- //private void OnCounterClicked(object sender, EventArgs e)
- //{
- // count++;
+ private void DoInitSize()
+ {
+ ys = new double[cols];
+ yIndex = 0;
+ itemWidth = (int)(pageWidth / cols) - margin * (cols - 1) / 2;
+ emptyHeight = (int)(itemWidth * AppResources.EmptySize.Height / AppResources.EmptySize.Width);
+ }
- // if (count == 1)
- // CounterBtn.Text = $"Clicked {count} time";
- // else
- // CounterBtn.Text = $"Clicked {count} times";
+ private void DoResizeItem(FlowerClientItem item)
+ {
+ int height;
+ if (item.Width > 0 && item.Height > 0)
+ {
+ height = itemWidth * item.Height.Value / item.Width.Value;
+ }
+ else
+ {
+ height = emptyHeight;
+ }
+ height += 36;
+ double yMin = double.MaxValue;
+ for (var i = 0; i < cols; i++)
+ {
+ if (ys[i] < yMin)
+ {
+ yMin = ys[i];
+ yIndex = i;
+ }
+ }
+ ys[yIndex] += height + margin;
+ item.Bounds = new Rect(
+ yIndex,
+ yMin,
+ itemWidth,
+ height);
+ }
- // SemanticScreenReader.Announce(CounterBtn.Text);
- //}
+ private async void DoRefreshSquare()
+ {
+ try
+ {
+ var result = await FetchAsync("api/flower/latest?photo=true");
+ if (result?.Count > 0)
+ {
+ DoInitSize();
+ var flowers = result.Flowers.Select(f =>
+ {
+ var item = new FlowerClientItem(f);
+ if (f.Photos?.Length > 0 && f.Photos[0] is PhotoItem cover)
+ {
+ item.Cover = new UriImageSource { Uri = new Uri($"{Constants.BaseUrl}/{cover.Url}") };
+ }
+ else
+ {
+ item.Cover = "empty_flower.jpg";
+ }
+ DoResizeItem(item);
+ return item;
+ });
+ logger.LogInformation("got {count} flowers.", result.Count);
+
+ Flowers = flowers.ToArray();
+ }
+ else
+ {
+ Flowers = Array.Empty();
+ logger.LogInformation("no flowers.");
+ }
+ }
+ catch (Exception ex)
+ {
+ await AlertError(L("failedGetFlowers", "Failed to get flowers, please try again."));
+ logger.LogError("error occurs in HomePage, {exception}", ex);
+ }
+ finally
+ {
+ IsRefreshing = false;
+ }
+ }
+
+ private void RefreshView_Refreshing(object sender, EventArgs e)
+ {
+ Task.Run(DoRefreshSquare);
+ }
+}
+
+public record FlowerResult
+{
+ public FlowerItem[] Flowers { get; init; } = null!;
+
+ public int Count { get; init; }
}
\ No newline at end of file
diff --git a/FlowerApp/MauiProgram.cs b/FlowerApp/MauiProgram.cs
index df174ed..8b1018f 100644
--- a/FlowerApp/MauiProgram.cs
+++ b/FlowerApp/MauiProgram.cs
@@ -1,6 +1,5 @@
-using Blahblah.FlowerApp.Controls;
-using Blahblah.FlowerApp.Data;
-using Blahblah.FlowerApp.Handlers;
+using Blahblah.FlowerApp.Data;
+using CommunityToolkit.Maui;
#if DEBUG
using Microsoft.Extensions.Logging;
#endif
@@ -14,6 +13,7 @@ public static class MauiProgram
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp()
+ .UseMauiCommunityToolkit()
.ConfigureFonts(fonts =>
{
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
@@ -21,14 +21,13 @@ public static class MauiProgram
})
.ConfigureMauiHandlers(handlers =>
{
- handlers.AddHandler();
});
#if DEBUG
builder.Logging.AddDebug();
#endif
- builder.Services.AddSingleton();
+ builder.Services.AddSingleton();
builder.Services.AddSingleton();
builder.Services.AddLocalization();
diff --git a/FlowerApp/Platforms/iOS/Controls/FlowLayout.cs b/FlowerApp/Platforms/iOS/Controls/FlowLayout.cs
deleted file mode 100644
index 7a7f864..0000000
--- a/FlowerApp/Platforms/iOS/Controls/FlowLayout.cs
+++ /dev/null
@@ -1,125 +0,0 @@
-using CoreGraphics;
-using Foundation;
-using UIKit;
-
-namespace Blahblah.FlowerApp.Platforms.iOS.Controls;
-
-public class FlowLayout : UICollectionViewFlowLayout
-{
- public CalculateCellHeightHandler? CalculateCellHeight { get; set; }
-
- private readonly List layoutAttributes = new();
-
- private CGSize oldBounds;
- private bool dirty;
- private int cols;
- private nfloat[]? yArray;
- private nfloat maxHeight;
-
- public FlowLayout(int cols = 2)
- {
- SetCols(cols);
- }
-
- public void SetCols(int cols)
- {
- if (this.cols != cols)
- {
- dirty = true;
- this.cols = cols;
- }
- }
-
- public void Invalidate()
- {
- dirty = true;
- }
-
- private void Clean()
- {
- dirty = false;
- yArray = new nfloat[cols];
- maxHeight = 0f;
- layoutAttributes.Clear();
- }
-
- public override CGSize CollectionViewContentSize => new(CollectionView.Bounds.Width, maxHeight);
-
- public override bool ShouldInvalidateLayoutForBoundsChange(CGRect newBounds)
- {
- return newBounds.Width != CollectionView.Bounds.Width;
- }
-
- public override void PrepareLayout()
- {
- base.PrepareLayout();
-
- var bounds = CollectionView.Bounds.Size;
- if (dirty || oldBounds.Width != bounds.Width)
- {
- oldBounds = bounds;
- Clean();
- }
-
- var sectionLeft = SectionInset.Left;
- var sectionTop = SectionInset.Top;
- var minSpacing = MinimumInteritemSpacing;
- var itemWidth = (bounds.Width - sectionLeft - SectionInset.Right - minSpacing * (cols - 1)) / cols;
- var itemCount = CollectionView.NumberOfItemsInSection(0);
- for (nint i = layoutAttributes.Count; i < itemCount; i++)
- {
- var indexPath = NSIndexPath.FromItemSection(i, 0);
- var attr = UICollectionViewLayoutAttributes.CreateForCell(indexPath);
- var itemHeight = CalculateCellHeight?.Invoke(indexPath, itemWidth) ?? 20;
- nfloat value = nfloat.MaxValue;
- int minHeightIndex = 0;
- if (yArray?.Length >= cols)
- {
- for (var n = 0; n < cols; n++)
- {
- if (yArray[n] < value)
- {
- value = yArray[n];
- minHeightIndex = n;
- }
- }
- }
- var itemY = value;
- if (itemY < sectionTop)
- {
- itemY = sectionTop;
- }
- if (i >= cols)
- {
- itemY += minSpacing;
- }
-
- var itemX = sectionLeft + (itemWidth + minSpacing) * minHeightIndex;
- attr.Frame = new CGRect(itemX, itemY, itemWidth, itemHeight);
- layoutAttributes.Add(attr);
- if (yArray != null)
- {
- yArray[minHeightIndex] = itemY + itemHeight;
- }
- }
- nfloat y = 0f;
- if (yArray != null)
- {
- for (var i = 0; i < yArray.Length; i++)
- {
- if (yArray[i] > y)
- {
- y = yArray[i];
- }
- }
- }
- maxHeight = y + SectionInset.Bottom;
- }
-
- public override UICollectionViewLayoutAttributes[] LayoutAttributesForElementsInRect(CGRect rect)
- {
- return layoutAttributes.Where(a => a.Frame.IntersectsWith(rect)).ToArray();
- }
-}
-
-public delegate nfloat CalculateCellHeightHandler(NSIndexPath indexPath, nfloat itemWidth);
\ No newline at end of file
diff --git a/FlowerApp/Platforms/iOS/Controls/MauiWaterfallLayout.cs b/FlowerApp/Platforms/iOS/Controls/MauiWaterfallLayout.cs
deleted file mode 100644
index b70bb45..0000000
--- a/FlowerApp/Platforms/iOS/Controls/MauiWaterfallLayout.cs
+++ /dev/null
@@ -1,86 +0,0 @@
-using Blahblah.FlowerApp.Controls;
-using Foundation;
-using UIKit;
-
-namespace Blahblah.FlowerApp.Platforms.iOS.Controls;
-
-public class MauiWaterfallLayout : UIView, IUICollectionViewDataSource, IUICollectionViewDelegate
-{
- WaterfallLayout? layout;
- UICollectionView? collectionView;
-
- public MauiWaterfallLayout(WaterfallLayout layout)
- {
- this.layout = layout;
-
- var flow = new FlowLayout(layout.Columns)
- {
- //CalculateCellHeight =
- MinimumInteritemSpacing = 12f,
- SectionInset = new UIEdgeInsets(12f, 12f, 12f, 12f)
- };
-
- var refreshControl = new UIRefreshControl
- {
- TintColor = UIColor.SystemFill
- };
- refreshControl.ValueChanged += (sender, e) =>
- {
- if (sender is UIRefreshControl control)
- {
- control.EndRefreshing();
- }
- };
-
- collectionView = new UICollectionView(Bounds, flow)
- {
- DataSource = this,
- Delegate = this,
- RefreshControl = refreshControl,
- TranslatesAutoresizingMaskIntoConstraints = false
- };
- AddSubview(collectionView);
-
- var safe = SafeAreaLayoutGuide;
- AddConstraints(new[]
- {
- NSLayoutConstraint.Create(collectionView, NSLayoutAttribute.Leading, NSLayoutRelation.Equal, safe, NSLayoutAttribute.Leading, 1f, 0f),
- NSLayoutConstraint.Create(collectionView, NSLayoutAttribute.Trailing, NSLayoutRelation.Equal, safe, NSLayoutAttribute.Trailing, 1f, 0f),
- NSLayoutConstraint.Create(collectionView, NSLayoutAttribute.Top, NSLayoutRelation.Equal, safe, NSLayoutAttribute.Top, 1f, 0f),
- NSLayoutConstraint.Create(collectionView, NSLayoutAttribute.Bottom, NSLayoutRelation.Equal, safe, NSLayoutAttribute.Bottom, 1f, 0f),
- });
- }
-
- public UICollectionViewCell GetCell(UICollectionView collectionView, NSIndexPath indexPath)
- {
- return new UICollectionViewCell();
- }
-
- public nint GetItemsCount(UICollectionView collectionView, nint section)
- {
- return 5;
- }
-
- public void UpdateColumns()
- {
- if (layout != null && collectionView?.CollectionViewLayout is FlowLayout flow)
- {
- flow.SetCols(layout.Columns);
- }
- }
-
- protected override void Dispose(bool disposing)
- {
- if (disposing)
- {
- if (collectionView != null)
- {
- collectionView.Dispose();
- collectionView = null;
- }
- layout = null;
- }
-
- base.Dispose(disposing);
- }
-}
diff --git a/FlowerApp/Platforms/iOS/Handlers/WaterfallLayoutHandler.cs b/FlowerApp/Platforms/iOS/Handlers/WaterfallLayoutHandler.cs
deleted file mode 100644
index 92af691..0000000
--- a/FlowerApp/Platforms/iOS/Handlers/WaterfallLayoutHandler.cs
+++ /dev/null
@@ -1,26 +0,0 @@
-using Blahblah.FlowerApp.Controls;
-using Blahblah.FlowerApp.Platforms.iOS.Controls;
-using Microsoft.Maui.Handlers;
-
-namespace Blahblah.FlowerApp.Handlers;
-
-partial class WaterfallLayoutHandler : ViewHandler
-{
- static void MapColumns(WaterfallLayoutHandler handler, WaterfallLayout layout)
- {
- handler.PlatformView?.UpdateColumns();
- }
-
- protected override MauiWaterfallLayout CreatePlatformView() => new MauiWaterfallLayout(VirtualView);
-
- protected override void ConnectHandler(MauiWaterfallLayout platformView)
- {
- base.ConnectHandler(platformView);
- }
-
- protected override void DisconnectHandler(MauiWaterfallLayout platformView)
- {
- platformView.Dispose();
- base.DisconnectHandler(platformView);
- }
-}
diff --git a/FlowerApp/Resources/Images/empty_flower.jpg b/FlowerApp/Resources/Images/empty_flower.jpg
new file mode 100644
index 0000000..b0d7dae
Binary files /dev/null and b/FlowerApp/Resources/Images/empty_flower.jpg differ
diff --git a/Server/Constants.cs b/Server/Constants.cs
index a920deb..9323b20 100644
--- a/Server/Constants.cs
+++ b/Server/Constants.cs
@@ -103,10 +103,10 @@ public enum EventTypes
///
/// 事件类
///
-/// 名称
+/// 名称
/// 描述
/// 是否唯一
-public record Event(string Key, string? Description = null, bool Unique = false) : NamedItem(Key, Description)
+public record Event(string Name, string? Description = null, bool Unique = false) : NamedItem(Name, Description)
{
///
/// 是否唯一
@@ -118,14 +118,14 @@ public record Event(string Key, string? Description = null, bool Unique = false)
///
/// 命名对象类
///
-/// 名称
+/// 名称
/// 描述
-public record NamedItem(string Key, string? Description = null)
+public record NamedItem(string Name, string? Description = null)
{
///
/// 名称
///
- public string Key { get; init; } = Key;
+ public string Name { get; init; } = Name;
///
/// 描述
diff --git a/Server/Controller/ApiController.cs b/Server/Controller/ApiController.cs
index e05a82a..aa16506 100644
--- a/Server/Controller/ApiController.cs
+++ b/Server/Controller/ApiController.cs
@@ -52,6 +52,7 @@ public partial class ApiController : BaseController
{
return Ok(new DefinitionResult
{
+ ApiVersion = Program.Version,
Categories = Constants.Categories,
Events = Constants.Events,
});
diff --git a/Server/Controller/ApiController.structs.cs b/Server/Controller/ApiController.structs.cs
index c6f8711..8e8b44c 100644
--- a/Server/Controller/ApiController.structs.cs
+++ b/Server/Controller/ApiController.structs.cs
@@ -5,6 +5,11 @@
///
public record DefinitionResult
{
+ ///
+ /// API 版本号
+ ///
+ public required string ApiVersion { get; init; }
+
///
/// 花草分类
///
diff --git a/Server/Controller/BaseController.cs b/Server/Controller/BaseController.cs
index 742b99e..fb6cdf0 100644
--- a/Server/Controller/BaseController.cs
+++ b/Server/Controller/BaseController.cs
@@ -218,6 +218,7 @@ public abstract partial class BaseController : ControllerBase
using var ms = new MemoryStream();
ms.Write(headers, 0, count);
+#if __OLD_READER__
// reading
const int size = 16384;
var buffer = new byte[size];
@@ -225,17 +226,24 @@ public abstract partial class BaseController : ControllerBase
{
ms.Write(buffer, 0, count);
}
+#else
+ stream.CopyTo(ms);
+#endif
var data = ms.ToArray();
var name = file.FileName;
var ext = Path.GetExtension(name);
var path = $"{WebUtility.UrlEncode(Path.GetFileNameWithoutExtension(name))}_{DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()}{ext}";
+ var image = SkiaSharp.SKImage.FromEncodedData(data);
+
return new FileResult
{
Filename = name,
FileType = ext,
Path = path,
- Content = data
+ Content = data,
+ Width = image.Width,
+ Height = image.Height
};
}
}
@@ -364,30 +372,20 @@ public record FileResult
///
/// 储存路径
///
- public required string Path { get; set; }
+ public required string Path { get; init; }
///
/// 文件内容
///
- [Required]
public required byte[] Content { get; init; }
+
+ ///
+ /// 照片宽度
+ ///
+ public required int Width { get; init; }
+
+ ///
+ /// 照片高度
+ ///
+ public required int Height { get; init; }
}
-
-
-///
-/// 照片参数
-///
-public record PhotoParameter
-{
- ///
- /// 花草 id
- ///
- [Required]
- public int Id { get; set; }
-
- ///
- /// 封面照片
- ///
- [Required]
- public required IFormFile Photo { get; set; }
-}
\ No newline at end of file
diff --git a/Server/Controller/BaseController.sqlite.cs b/Server/Controller/BaseController.sqlite.cs
index a200d18..fe9bb7c 100644
--- a/Server/Controller/BaseController.sqlite.cs
+++ b/Server/Controller/BaseController.sqlite.cs
@@ -88,6 +88,6 @@ partial class BaseController
///
protected int AddPhotoItem(PhotoItem item)
{
- return database.Database.ExecuteSql($"INSERT INTO \"photos\"(\"fid\",\"rid\",\"filetype\",\"filename\",\"path\",\"dateupload\") VALUES({item.FlowerId},{item.RecordId},{item.FileType},{item.FileName},{item.Path},{item.DateUploadUnixTime})");
+ return database.Database.ExecuteSql($"INSERT INTO \"photos\"(\"fid\",\"uid\",\"rid\",\"filetype\",\"filename\",\"path\",\"dateupload\",\"width\",\"height\") VALUES({item.FlowerId},{item.OwnerId},{item.RecordId},{item.FileType},{item.FileName},{item.Path},{item.DateUploadUnixTime},{item.Width},{item.Height})");
}
}
diff --git a/Server/Controller/EventApiController.cs b/Server/Controller/EventApiController.cs
index 2330794..07c7411 100644
--- a/Server/Controller/EventApiController.cs
+++ b/Server/Controller/EventApiController.cs
@@ -279,7 +279,9 @@ public class EventApiController : BaseController
FileType = file.FileType,
FileName = file.Filename,
Path = file.Path,
- DateUploadUnixTime = now
+ DateUploadUnixTime = now,
+ Width = file.Width,
+ Height = file.Height
};
AddPhotoItem(p);
@@ -402,7 +404,9 @@ public class EventApiController : BaseController
FileType = file.FileType,
FileName = file.Filename,
Path = file.Path,
- DateUploadUnixTime = now
+ DateUploadUnixTime = now,
+ Width = file.Width,
+ Height = file.Height
};
AddPhotoItem(cover);
@@ -498,7 +502,9 @@ public class EventApiController : BaseController
FileType = file.FileType,
FileName = file.Filename,
Path = file.Path,
- DateUploadUnixTime = user.ActiveDateUnixTime ?? DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()
+ DateUploadUnixTime = user.ActiveDateUnixTime ?? DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
+ Width = file.Width,
+ Height = file.Height
};
AddPhotoItem(p);
@@ -594,7 +600,9 @@ public class EventApiController : BaseController
FileType = file.FileType,
FileName = file.Filename,
Path = file.Path,
- DateUploadUnixTime = user.ActiveDateUnixTime ?? DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()
+ DateUploadUnixTime = user.ActiveDateUnixTime ?? DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
+ Width = file.Width,
+ Height = file.Height
};
AddPhotoItem(p);
diff --git a/Server/Controller/FlowerApiController.cs b/Server/Controller/FlowerApiController.cs
index 2ced828..dc65d1e 100644
--- a/Server/Controller/FlowerApiController.cs
+++ b/Server/Controller/FlowerApiController.cs
@@ -518,7 +518,9 @@ public class FlowerApiController : BaseController
FileType = file.FileType,
FileName = file.Filename,
Path = file.Path,
- DateUploadUnixTime = now
+ DateUploadUnixTime = now,
+ Width = file.Width,
+ Height = file.Height
};
AddPhotoItem(cover);
@@ -639,7 +641,9 @@ public class FlowerApiController : BaseController
FileType = file.FileType,
FileName = file.Filename,
Path = file.Path,
- DateUploadUnixTime = now
+ DateUploadUnixTime = now,
+ Width = file.Width,
+ Height = file.Height
};
database.Photos.Add(item);
SaveDatabase();
@@ -776,7 +780,9 @@ public class FlowerApiController : BaseController
FileType = file.FileType,
FileName = file.Filename,
Path = file.Path,
- DateUploadUnixTime = now
+ DateUploadUnixTime = now,
+ Width = file.Width,
+ Height = file.Height
};
AddPhotoItem(cover);
@@ -893,7 +899,9 @@ public class FlowerApiController : BaseController
FileType = file.FileType,
FileName = file.Filename,
Path = file.Path,
- DateUploadUnixTime = now
+ DateUploadUnixTime = now,
+ Width = file.Width,
+ Height = file.Height
};
AddPhotoItem(cover);
diff --git a/Server/Data/Model/PhotoItem.cs b/Server/Data/Model/PhotoItem.cs
index 0c716de..6d1dc27 100644
--- a/Server/Data/Model/PhotoItem.cs
+++ b/Server/Data/Model/PhotoItem.cs
@@ -94,6 +94,18 @@ public class PhotoItem
[JsonIgnore]
public DateTimeOffset DateUpload => DateTimeOffset.FromUnixTimeMilliseconds(DateUploadUnixTime);
+ ///
+ /// 图片宽度
+ ///
+ [Column("width")]
+ public int? Width { get; set; }
+
+ ///
+ /// 图片高度
+ ///
+ [Column("height")]
+ public int? Height { get; set; }
+
///
/// 前端显示的 URL
///
diff --git a/Server/Migrations/20230724084100_Add-Photo-Size.Designer.cs b/Server/Migrations/20230724084100_Add-Photo-Size.Designer.cs
new file mode 100644
index 0000000..5bdbace
--- /dev/null
+++ b/Server/Migrations/20230724084100_Add-Photo-Size.Designer.cs
@@ -0,0 +1,353 @@
+//
+using System;
+using Blahblah.FlowerStory.Server.Data;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+
+#nullable disable
+
+namespace Blahblah.FlowerStory.Server.Migrations
+{
+ [DbContext(typeof(FlowerDatabase))]
+ [Migration("20230724084100_Add-Photo-Size")]
+ partial class AddPhotoSize
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder.HasAnnotation("ProductVersion", "7.0.9");
+
+ modelBuilder.Entity("Blahblah.FlowerStory.Server.Data.Model.FlowerItem", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("fid");
+
+ b.Property("CategoryId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("categoryid");
+
+ b.Property("Cost")
+ .HasColumnType("real")
+ .HasColumnName("cost");
+
+ b.Property("DateBuyUnixTime")
+ .HasColumnType("INTEGER")
+ .HasColumnName("datebuy")
+ .HasAnnotation("Relational:JsonPropertyName", "dateBuy");
+
+ b.Property("Latitude")
+ .HasColumnType("REAL")
+ .HasColumnName("latitude");
+
+ b.Property("Longitude")
+ .HasColumnType("REAL")
+ .HasColumnName("longitude");
+
+ b.Property("Memo")
+ .HasColumnType("TEXT")
+ .HasColumnName("memo");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("name");
+
+ b.Property("OwnerId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("uid");
+
+ b.Property("Purchase")
+ .HasColumnType("TEXT")
+ .HasColumnName("purchase");
+
+ b.HasKey("Id");
+
+ b.HasIndex("OwnerId");
+
+ b.ToTable("flowers");
+ });
+
+ modelBuilder.Entity("Blahblah.FlowerStory.Server.Data.Model.PhotoItem", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("pid");
+
+ b.Property("DateUploadUnixTime")
+ .HasColumnType("INTEGER")
+ .HasColumnName("dateupload")
+ .HasAnnotation("Relational:JsonPropertyName", "dateUpload");
+
+ b.Property("FileName")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("filename");
+
+ b.Property("FileType")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("filetype");
+
+ b.Property("FlowerId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("fid");
+
+ b.Property("Height")
+ .HasColumnType("INTEGER")
+ .HasColumnName("height");
+
+ b.Property("OwnerId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("uid");
+
+ b.Property("Path")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("path");
+
+ b.Property("RecordId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("rid");
+
+ b.Property("Width")
+ .HasColumnType("INTEGER")
+ .HasColumnName("width");
+
+ b.HasKey("Id");
+
+ b.HasIndex("FlowerId");
+
+ b.HasIndex("OwnerId");
+
+ b.HasIndex("RecordId");
+
+ b.ToTable("photos");
+ });
+
+ modelBuilder.Entity("Blahblah.FlowerStory.Server.Data.Model.RecordItem", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("rid");
+
+ b.Property("ByUserId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("byuid");
+
+ b.Property("ByUserName")
+ .HasColumnType("TEXT")
+ .HasColumnName("byname");
+
+ b.Property("DateUnixTime")
+ .HasColumnType("INTEGER")
+ .HasColumnName("date")
+ .HasAnnotation("Relational:JsonPropertyName", "date");
+
+ b.Property("EventId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("eid");
+
+ b.Property("FlowerId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("fid");
+
+ b.Property("Latitude")
+ .HasColumnType("REAL")
+ .HasColumnName("latitude");
+
+ b.Property("Longitude")
+ .HasColumnType("REAL")
+ .HasColumnName("longitude");
+
+ b.Property("Memo")
+ .HasColumnType("TEXT")
+ .HasColumnName("memo");
+
+ b.Property("OwnerId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("uid");
+
+ b.HasKey("Id");
+
+ b.HasIndex("FlowerId");
+
+ b.HasIndex("OwnerId");
+
+ b.ToTable("records");
+ });
+
+ modelBuilder.Entity("Blahblah.FlowerStory.Server.Data.Model.TokenItem", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("TEXT")
+ .HasColumnName("tid");
+
+ b.Property("ActiveDateUnixTime")
+ .HasColumnType("INTEGER")
+ .HasColumnName("activedate")
+ .HasAnnotation("Relational:JsonPropertyName", "activeDate");
+
+ b.Property("ClientAgent")
+ .HasColumnType("TEXT")
+ .HasColumnName("clientagent");
+
+ b.Property("ClientApp")
+ .HasColumnType("TEXT")
+ .HasColumnName("clientapp");
+
+ b.Property("DeviceId")
+ .HasColumnType("TEXT")
+ .HasColumnName("deviceid");
+
+ b.Property("ExpireDateUnixTime")
+ .HasColumnType("INTEGER")
+ .HasColumnName("expiredate")
+ .HasAnnotation("Relational:JsonPropertyName", "expireDate");
+
+ b.Property("ExpireSeconds")
+ .HasColumnType("INTEGER")
+ .HasColumnName("expiresecs");
+
+ b.Property("LogonDateUnixTime")
+ .HasColumnType("INTEGER")
+ .HasColumnName("logondate")
+ .HasAnnotation("Relational:JsonPropertyName", "logonDate");
+
+ b.Property("UserId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("uid");
+
+ b.Property("VerifyCode")
+ .HasColumnType("TEXT")
+ .HasColumnName("verifycode");
+
+ b.HasKey("Id");
+
+ b.ToTable("tokens");
+ });
+
+ modelBuilder.Entity("Blahblah.FlowerStory.Server.Data.Model.UserItem", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("uid");
+
+ b.Property("ActiveDateUnixTime")
+ .HasColumnType("INTEGER")
+ .HasColumnName("activedate");
+
+ b.Property("Avatar")
+ .HasColumnType("BLOB")
+ .HasColumnName("avatar");
+
+ b.Property("Email")
+ .HasColumnType("TEXT")
+ .HasColumnName("email");
+
+ b.Property("Level")
+ .HasColumnType("INTEGER")
+ .HasColumnName("level");
+
+ b.Property("Mobile")
+ .HasColumnType("TEXT")
+ .HasColumnName("mobile");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("name");
+
+ b.Property("Password")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("password");
+
+ b.Property("RegisterDateUnixTime")
+ .HasColumnType("INTEGER")
+ .HasColumnName("regdate")
+ .HasAnnotation("Relational:JsonPropertyName", "registerDate");
+
+ b.Property("UserId")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("id");
+
+ b.HasKey("Id");
+
+ b.ToTable("users");
+ });
+
+ modelBuilder.Entity("Blahblah.FlowerStory.Server.Data.Model.FlowerItem", b =>
+ {
+ b.HasOne("Blahblah.FlowerStory.Server.Data.Model.UserItem", "Owner")
+ .WithMany()
+ .HasForeignKey("OwnerId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("Owner");
+ });
+
+ modelBuilder.Entity("Blahblah.FlowerStory.Server.Data.Model.PhotoItem", b =>
+ {
+ b.HasOne("Blahblah.FlowerStory.Server.Data.Model.FlowerItem", "Flower")
+ .WithMany("Photos")
+ .HasForeignKey("FlowerId");
+
+ b.HasOne("Blahblah.FlowerStory.Server.Data.Model.UserItem", "Owner")
+ .WithMany()
+ .HasForeignKey("OwnerId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("Blahblah.FlowerStory.Server.Data.Model.RecordItem", "Record")
+ .WithMany("Photos")
+ .HasForeignKey("RecordId");
+
+ b.Navigation("Flower");
+
+ b.Navigation("Owner");
+
+ b.Navigation("Record");
+ });
+
+ modelBuilder.Entity("Blahblah.FlowerStory.Server.Data.Model.RecordItem", b =>
+ {
+ b.HasOne("Blahblah.FlowerStory.Server.Data.Model.FlowerItem", "Flower")
+ .WithMany()
+ .HasForeignKey("FlowerId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("Blahblah.FlowerStory.Server.Data.Model.UserItem", "Owner")
+ .WithMany()
+ .HasForeignKey("OwnerId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("Flower");
+
+ b.Navigation("Owner");
+ });
+
+ modelBuilder.Entity("Blahblah.FlowerStory.Server.Data.Model.FlowerItem", b =>
+ {
+ b.Navigation("Photos");
+ });
+
+ modelBuilder.Entity("Blahblah.FlowerStory.Server.Data.Model.RecordItem", b =>
+ {
+ b.Navigation("Photos");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/Server/Migrations/20230724084100_Add-Photo-Size.cs b/Server/Migrations/20230724084100_Add-Photo-Size.cs
new file mode 100644
index 0000000..82135a3
--- /dev/null
+++ b/Server/Migrations/20230724084100_Add-Photo-Size.cs
@@ -0,0 +1,38 @@
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+namespace Blahblah.FlowerStory.Server.Migrations
+{
+ ///
+ public partial class AddPhotoSize : Migration
+ {
+ ///
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.AddColumn(
+ name: "height",
+ table: "photos",
+ type: "INTEGER",
+ nullable: true);
+
+ migrationBuilder.AddColumn(
+ name: "width",
+ table: "photos",
+ type: "INTEGER",
+ nullable: true);
+ }
+
+ ///
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropColumn(
+ name: "height",
+ table: "photos");
+
+ migrationBuilder.DropColumn(
+ name: "width",
+ table: "photos");
+ }
+ }
+}
diff --git a/Server/Migrations/FlowerDatabaseModelSnapshot.cs b/Server/Migrations/FlowerDatabaseModelSnapshot.cs
index 6d33685..44cd4fb 100644
--- a/Server/Migrations/FlowerDatabaseModelSnapshot.cs
+++ b/Server/Migrations/FlowerDatabaseModelSnapshot.cs
@@ -15,7 +15,7 @@ namespace Blahblah.FlowerStory.Server.Migrations
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
- modelBuilder.HasAnnotation("ProductVersion", "7.0.5");
+ modelBuilder.HasAnnotation("ProductVersion", "7.0.9");
modelBuilder.Entity("Blahblah.FlowerStory.Server.Data.Model.FlowerItem", b =>
{
@@ -95,6 +95,10 @@ namespace Blahblah.FlowerStory.Server.Migrations
.HasColumnType("INTEGER")
.HasColumnName("fid");
+ b.Property("Height")
+ .HasColumnType("INTEGER")
+ .HasColumnName("height");
+
b.Property("OwnerId")
.HasColumnType("INTEGER")
.HasColumnName("uid");
@@ -108,6 +112,10 @@ namespace Blahblah.FlowerStory.Server.Migrations
.HasColumnType("INTEGER")
.HasColumnName("rid");
+ b.Property("Width")
+ .HasColumnType("INTEGER")
+ .HasColumnName("width");
+
b.HasKey("Id");
b.HasIndex("FlowerId");
diff --git a/Server/Program.cs b/Server/Program.cs
index eb8c403..f53950d 100644
--- a/Server/Program.cs
+++ b/Server/Program.cs
@@ -11,7 +11,7 @@ public class Program
///
public const string ProjectName = "Flower Story";
///
- public const string Version = "0.6.713";
+ public const string Version = "0.7.727";
///
public static void Main(string[] args)
diff --git a/Server/Server.csproj b/Server/Server.csproj
index 6a39827..577db82 100644
--- a/Server/Server.csproj
+++ b/Server/Server.csproj
@@ -11,12 +11,13 @@
-
-
-
+
+
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
+