change app directory
@ -1,12 +0,0 @@
|
||||
namespace Blahblah.FlowerStory
|
||||
{
|
||||
public partial class App : Application
|
||||
{
|
||||
public App()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
MainPage = new AppShell();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
namespace Blahblah.FlowerStory
|
||||
{
|
||||
public partial class AppShell : Shell
|
||||
{
|
||||
public AppShell()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,65 +0,0 @@
|
||||
namespace Blahblah.FlowerStory;
|
||||
|
||||
public sealed class Constants
|
||||
{
|
||||
public const string CategoryOther = "other";
|
||||
public const string EventUnknown = "unknown";
|
||||
|
||||
public const string BaseUrl = "https://flower.tsanie.org";
|
||||
|
||||
public const SQLite.SQLiteOpenFlags Flags =
|
||||
SQLite.SQLiteOpenFlags.ReadWrite |
|
||||
SQLite.SQLiteOpenFlags.Create |
|
||||
SQLite.SQLiteOpenFlags.SharedCache;
|
||||
|
||||
private const string databaseFilename = "flowerstory.db3";
|
||||
public static string DatabasePath => Path.Combine(FileSystem.AppDataDirectory, databaseFilename);
|
||||
|
||||
public static readonly Dictionary<int, string> Categories = new()
|
||||
{
|
||||
[0] = CategoryOther,
|
||||
[1] = "cactus", // 仙人球
|
||||
[2] = "hibiscus", // 扶桑花
|
||||
[3] = "bougainvillea", // 三角梅
|
||||
[4] = "sunflower", // 太阳花
|
||||
[5] = "milanflower", // 米兰花
|
||||
[6] = "jasmine", // 茉莉花
|
||||
[7] = "periwinkle", // 长春花
|
||||
[8] = "nasturtium", // 旱金莲
|
||||
[9] = "mirabilis", // 紫薇花
|
||||
[10] = "tigerthornplum", // 虎刺梅
|
||||
[11] = "geranium", // 天竺葵
|
||||
[12] = "ballorchid", // 球兰
|
||||
[13] = "medalchrysanthemum", // 勋章菊
|
||||
[14] = "dianthus", // 石竹
|
||||
[15] = "fivecolorplum", // 五色梅
|
||||
[16] = "fuchsia", // 倒挂金钟
|
||||
[17] = "bamboocrabapple", // 竹节海棠
|
||||
[18] = "impatiens", // 凤仙花
|
||||
[19] = "beautysakura", // 美女樱
|
||||
[20] = "petunias", // 矮牵牛
|
||||
[21] = "desertrose", // 沙漠玫瑰
|
||||
[22] = "trailingcampanula", // 蔓性风铃花
|
||||
[23] = "chineserose", // 月季花
|
||||
};
|
||||
|
||||
public static readonly Dictionary<int, Event> Events = new()
|
||||
{
|
||||
[0] = new(EventUnknown),
|
||||
[1] = new("buy", true), // 购买
|
||||
[2] = new("born", true), // 出生
|
||||
[3] = new("changebasin"), // 换盆
|
||||
[4] = new("watering"), // 浇水
|
||||
[5] = new("fertilize"), // 施肥
|
||||
[6] = new("germination"), // 发芽
|
||||
[7] = new("blossom"), // 开花
|
||||
[8] = new("fallenleaves"), // 落叶
|
||||
[9] = new("prune"), // 修剪
|
||||
[10] = new("sick"), // 生病
|
||||
[11] = new("death", true), // 死亡
|
||||
[12] = new("sell", true), // 出售
|
||||
[13] = new("share"), // 分享
|
||||
};
|
||||
}
|
||||
|
||||
public record Event(string Name, bool Unique = false);
|
@ -1,45 +0,0 @@
|
||||
using SQLite;
|
||||
|
||||
namespace Blahblah.FlowerStory.Data.Model;
|
||||
|
||||
[Table("flowers")]
|
||||
public class FlowerItem
|
||||
{
|
||||
[Column("fid"), PrimaryKey, AutoIncrement]
|
||||
public int Id { get; set; }
|
||||
|
||||
[Column("categoryid")]
|
||||
public int CategoryId { get; set; }
|
||||
|
||||
private string categoryName;
|
||||
public string CategoryName
|
||||
{
|
||||
get
|
||||
{
|
||||
if (categoryName == null)
|
||||
{
|
||||
if (!Constants.Categories.TryGetValue(CategoryId, out var name))
|
||||
{
|
||||
name = Constants.CategoryOther;
|
||||
}
|
||||
categoryName = LocalizationResource.GetText(name);
|
||||
}
|
||||
return categoryName;
|
||||
}
|
||||
}
|
||||
|
||||
[Column("name")]
|
||||
public string Name { get; set; }
|
||||
|
||||
[Column("datebuy")]
|
||||
public DateTimeOffset DateBuy { get; set; }
|
||||
|
||||
[Column("cost")]
|
||||
public decimal? Cost { get; set; }
|
||||
|
||||
[Column("purchase")]
|
||||
public string Purchase { get; set; }
|
||||
|
||||
[Column("memo")]
|
||||
public string Memo { get; set; }
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
using SQLite;
|
||||
|
||||
namespace Blahblah.FlowerStory.Data.Model;
|
||||
|
||||
[Table("photos")]
|
||||
public class PhotoItem
|
||||
{
|
||||
[Column("pid"), PrimaryKey, AutoIncrement]
|
||||
public int Id { get; set; }
|
||||
|
||||
[Column("fid")]
|
||||
public int FlowerId { get; set; }
|
||||
|
||||
[Column("rid")]
|
||||
public int RecordId { get; set; }
|
||||
|
||||
[Column("filetype")]
|
||||
public string FileType { get; set; }
|
||||
|
||||
[Column("filename")]
|
||||
public string FileName { get; set; }
|
||||
|
||||
[Column("path")]
|
||||
public string Path { get; set; }
|
||||
|
||||
[Column("dateupload")]
|
||||
public DateTimeOffset DateUpload { get; set; }
|
||||
}
|
@ -1,50 +0,0 @@
|
||||
using SQLite;
|
||||
|
||||
namespace Blahblah.FlowerStory.Data.Model;
|
||||
|
||||
[Table("records")]
|
||||
public class RecordItem
|
||||
{
|
||||
[Column("rid"), PrimaryKey, AutoIncrement]
|
||||
public int Id { get; set; }
|
||||
|
||||
[Column("fid")]
|
||||
public int FlowerId { get; set; }
|
||||
|
||||
[Column("eid")]
|
||||
public int EventId { get; set; }
|
||||
|
||||
private string eventName;
|
||||
public string EventName
|
||||
{
|
||||
get
|
||||
{
|
||||
if (eventName == null)
|
||||
{
|
||||
string evtkey;
|
||||
if (Constants.Events.TryGetValue(EventId, out var @event))
|
||||
{
|
||||
evtkey = @event.Name;
|
||||
}
|
||||
else
|
||||
{
|
||||
evtkey = Constants.EventUnknown;
|
||||
}
|
||||
eventName = LocalizationResource.GetText(evtkey);
|
||||
}
|
||||
return eventName;
|
||||
}
|
||||
}
|
||||
|
||||
[Column("date")]
|
||||
public DateTimeOffset Date { get; set; }
|
||||
|
||||
[Column("byuid")]
|
||||
public int? ByUserId { get; set; }
|
||||
|
||||
[Column("byname")]
|
||||
public string ByUserName { get; set; }
|
||||
|
||||
[Column("memo")]
|
||||
public string Memo { get; set; }
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
namespace Blahblah.FlowerStory.Data.Model;
|
||||
|
||||
public class UserItem
|
||||
{
|
||||
public int Id { get; set; }
|
||||
|
||||
public string UserId { get; set; }
|
||||
|
||||
public int Level { get; set; }
|
||||
|
||||
public DateTimeOffset RegisterDate { get; set; }
|
||||
|
||||
public string Name { get; set; }
|
||||
|
||||
public string Email { get; set; }
|
||||
|
||||
public string Mobile { get; set; }
|
||||
|
||||
public byte[] Avatar { get; set; }
|
||||
}
|
@ -1,36 +0,0 @@
|
||||
using Microsoft.Extensions.Localization;
|
||||
using System.Text;
|
||||
|
||||
namespace Blahblah.FlowerStory;
|
||||
|
||||
[ContentProperty(nameof(Key))]
|
||||
public class LocalizeExtension : IMarkupExtension
|
||||
{
|
||||
//private IStringLocalizer<Localizations> Localizer { get; }
|
||||
|
||||
public string Key { get; set; }
|
||||
|
||||
//public LocalizeExtension()
|
||||
//{
|
||||
// Localizer = MauiApplication.Current.Services.GetService<IStringLocalizer<Localizations>>();
|
||||
//}
|
||||
|
||||
public object ProvideValue(IServiceProvider _)
|
||||
{
|
||||
return LocalizationResource.Localizer.GetString(Key);
|
||||
}
|
||||
|
||||
object IMarkupExtension.ProvideValue(IServiceProvider serviceProvider) => ProvideValue(serviceProvider);
|
||||
}
|
||||
|
||||
sealed class LocalizationResource
|
||||
{
|
||||
private static IStringLocalizer<Localizations> localizer;
|
||||
|
||||
public static IStringLocalizer<Localizations> Localizer => localizer ??= MauiApplication.Current.Services.GetService<IStringLocalizer<Localizations>>();
|
||||
|
||||
public static string GetText(string key)
|
||||
{
|
||||
return Localizer.GetString(key);
|
||||
}
|
||||
}
|
@ -1,95 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net7.0-android</TargetFrameworks>
|
||||
|
||||
<OutputType>Exe</OutputType>
|
||||
<RootNamespace>Blahblah.FlowerStory</RootNamespace>
|
||||
<UseMaui>true</UseMaui>
|
||||
<SingleProject>true</SingleProject>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<!--<PublishAot>true</PublishAot>-->
|
||||
|
||||
<!-- Display name -->
|
||||
<ApplicationTitle>Flower Story</ApplicationTitle>
|
||||
|
||||
<!-- App Identifier -->
|
||||
<ApplicationId>org.blahblah.flowerstory</ApplicationId>
|
||||
<ApplicationIdGuid>4a6f7f22-fb2a-4771-b257-8b94ab57ce2f</ApplicationIdGuid>
|
||||
|
||||
<!-- Versions -->
|
||||
<ApplicationDisplayVersion>0.1.518</ApplicationDisplayVersion>
|
||||
<ApplicationVersion>1</ApplicationVersion>
|
||||
|
||||
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">13.0</SupportedOSPlatformVersion>
|
||||
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">23.0</SupportedOSPlatformVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|net7.0-android|AnyCPU'">
|
||||
<RuntimeIdentifiers>android-x64</RuntimeIdentifiers>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|net7.0-android|AnyCPU'">
|
||||
<AndroidPackageFormat>apk</AndroidPackageFormat>
|
||||
<RuntimeIdentifiers>android-arm64;android-x64</RuntimeIdentifiers>
|
||||
<AndroidCreatePackagePerAbi>True</AndroidCreatePackagePerAbi>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(TargetFramework)'=='net7.0-ios'">
|
||||
<CreatePackage>false</CreatePackage>
|
||||
<CodesignProvision>Automatic</CodesignProvision>
|
||||
<ProvisioningType>manual</ProvisioningType>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|net7.0-ios|AnyCPU'">
|
||||
<CodesignKey>iPhone Developer</CodesignKey>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|net7.0-ios|AnyCPU'">
|
||||
<CodesignKey>iPhone Distribution</CodesignKey>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<!-- App Icon -->
|
||||
<MauiIcon Include="Resources\AppIcon\appicon.svg" ForegroundFile="Resources\AppIcon\appiconfg.svg" Color="#512BD4" />
|
||||
|
||||
<!-- Splash Screen -->
|
||||
<MauiSplashScreen Include="Resources\Splash\splash.svg" Color="#512BD4" BaseSize="128,128" />
|
||||
|
||||
<!-- Images -->
|
||||
<MauiImage Include="Resources\Images\*" />
|
||||
<MauiImage Update="Resources\Images\dotnet_bot.svg" BaseSize="168,208" />
|
||||
|
||||
<!-- Custom Fonts -->
|
||||
<MauiFont Include="Resources\Fonts\*" />
|
||||
|
||||
<!-- Raw Assets (also remove the "Resources\Raw" prefix) -->
|
||||
<MauiAsset Include="Resources\Raw\**" LogicalName="%(RecursiveDir)%(Filename)%(Extension)" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<MauiAsset Remove="Resources\Raw\AboutAssets.txt" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Localization" Version="7.0.5" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="7.0.0" />
|
||||
<PackageReference Include="Microsoft.Maui.Graphics.Skia" Version="7.0.86" />
|
||||
<PackageReference Include="SkiaSharp.Views.Maui.Controls" Version="2.88.3" />
|
||||
<PackageReference Include="sqlite-net-pcl" Version="1.8.116" />
|
||||
<PackageReference Include="SQLitePCLRaw.bundle_green" Version="2.1.5" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Update="Localizations.Designer.cs">
|
||||
<DesignTime>True</DesignTime>
|
||||
<AutoGen>True</AutoGen>
|
||||
<DependentUpon>Localizations.resx</DependentUpon>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Update="Localizations.resx">
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
<LastGenOutput>Localizations.Designer.cs</LastGenOutput>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
</Project>
|
@ -1,55 +0,0 @@
|
||||
using Blahblah.FlowerStory.Data;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Blahblah.FlowerStory
|
||||
{
|
||||
public partial class MainPage : ContentPage
|
||||
{
|
||||
int count = 0;
|
||||
readonly FlowerDatabase database;
|
||||
|
||||
private readonly ILogger logger;
|
||||
public MainPage(FlowerDatabase database, ILogger<MainPage> logger)
|
||||
{
|
||||
this.logger = logger;
|
||||
|
||||
this.database = database;
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void OnCounterClicked(object sender, EventArgs e)
|
||||
{
|
||||
count++;
|
||||
|
||||
if (count == 1)
|
||||
CounterBtn.Text = $"Clicked {count} time";
|
||||
else
|
||||
CounterBtn.Text = $"Clicked {count} times";
|
||||
|
||||
SemanticScreenReader.Announce(CounterBtn.Text);
|
||||
}
|
||||
|
||||
protected override void OnAppearing()
|
||||
{
|
||||
base.OnAppearing();
|
||||
|
||||
Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var list = await database.GetFlowers();
|
||||
logger.LogInformation("got {count} flowers.", list.Count);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError("error occurs, {exception}", ex);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void canvasView_PaintSurface(object sender, SkiaSharp.Views.Maui.SKPaintSurfaceEventArgs e)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@ -1,36 +0,0 @@
|
||||
using Blahblah.FlowerStory.Data;
|
||||
#if DEBUG
|
||||
using Microsoft.Extensions.Logging;
|
||||
#endif
|
||||
|
||||
namespace Blahblah.FlowerStory
|
||||
{
|
||||
public static class MauiProgram
|
||||
{
|
||||
public static MauiApp CreateMauiApp()
|
||||
{
|
||||
var builder = MauiApp.CreateBuilder();
|
||||
builder
|
||||
.UseMauiApp<App>()
|
||||
.ConfigureFonts(fonts =>
|
||||
{
|
||||
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
|
||||
fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
|
||||
})
|
||||
.ConfigureMauiHandlers(handlers =>
|
||||
{
|
||||
});
|
||||
|
||||
#if DEBUG
|
||||
builder.Logging.AddDebug();
|
||||
#endif
|
||||
|
||||
builder.Services.AddSingleton<MainPage>();
|
||||
builder.Services.AddSingleton<FlowerDatabase>();
|
||||
|
||||
builder.Services.AddLocalization();
|
||||
|
||||
return builder.Build();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
using Android.App;
|
||||
using Android.Content.PM;
|
||||
using Android.OS;
|
||||
|
||||
namespace Blahblah.FlowerStory
|
||||
{
|
||||
[Activity(Theme = "@style/Maui.SplashTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize | ConfigChanges.Density)]
|
||||
public class MainActivity : MauiAppCompatActivity
|
||||
{
|
||||
}
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
using Android.App;
|
||||
using Android.Runtime;
|
||||
|
||||
namespace Blahblah.FlowerStory
|
||||
{
|
||||
[Application]
|
||||
public class MainApplication : MauiApplication
|
||||
{
|
||||
public MainApplication(IntPtr handle, JniHandleOwnership ownership)
|
||||
: base(handle, ownership)
|
||||
{
|
||||
}
|
||||
|
||||
protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
|
||||
}
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
using Foundation;
|
||||
|
||||
namespace Blahblah.FlowerStory
|
||||
{
|
||||
[Register("AppDelegate")]
|
||||
public class AppDelegate : MauiUIApplicationDelegate
|
||||
{
|
||||
protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
|
||||
}
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
using ObjCRuntime;
|
||||
using UIKit;
|
||||
|
||||
namespace Blahblah.FlowerStory
|
||||
{
|
||||
public class Program
|
||||
{
|
||||
// This is the main entry point of the application.
|
||||
static void Main(string[] args)
|
||||
{
|
||||
// if you want to use a different Application Delegate class from "AppDelegate"
|
||||
// you can specify it here.
|
||||
UIApplication.Main(args, null, typeof(AppDelegate));
|
||||
}
|
||||
}
|
||||
}
|
6
Directory.Build.props
Normal file
@ -0,0 +1,6 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<Nullable>enable</Nullable>
|
||||
<AvaloniaVersion>11.0.0</AvaloniaVersion>
|
||||
</PropertyGroup>
|
||||
</Project>
|
@ -1,8 +1,8 @@
|
||||
<?xml version = "1.0" encoding = "UTF-8" ?>
|
||||
<Application xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
xmlns:local="clr-namespace:Blahblah.FlowerStory"
|
||||
x:Class="Blahblah.FlowerStory.App">
|
||||
xmlns:local="clr-namespace:Blahblah.FlowerApp"
|
||||
x:Class="Blahblah.FlowerApp.App">
|
||||
<Application.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
11
FlowerApp/App.xaml.cs
Normal file
@ -0,0 +1,11 @@
|
||||
namespace Blahblah.FlowerApp;
|
||||
|
||||
public partial class App : Application
|
||||
{
|
||||
public App()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
MainPage = new AppShell();
|
||||
}
|
||||
}
|
@ -1,9 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<Shell
|
||||
x:Class="Blahblah.FlowerStory.AppShell"
|
||||
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.FlowerStory"
|
||||
xmlns:local="clr-namespace:Blahblah.FlowerApp"
|
||||
Shell.FlyoutBehavior="Disabled"
|
||||
Title="Flower Story">
|
||||
|
9
FlowerApp/AppShell.xaml.cs
Normal file
@ -0,0 +1,9 @@
|
||||
namespace Blahblah.FlowerApp;
|
||||
|
||||
public partial class AppShell : Shell
|
||||
{
|
||||
public AppShell()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
12
FlowerApp/Controls/WaterfallLayout.cs
Normal file
@ -0,0 +1,12 @@
|
||||
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);
|
||||
}
|
||||
}
|
19
FlowerApp/Data/Constants.cs
Normal file
@ -0,0 +1,19 @@
|
||||
using SQLite;
|
||||
|
||||
namespace Blahblah.FlowerApp.Data;
|
||||
|
||||
public sealed class Constants
|
||||
{
|
||||
public const string CategoryOther = "other";
|
||||
public const string EventUnknown = "unknown";
|
||||
|
||||
public const string BaseUrl = "https://flower.tsanie.org";
|
||||
|
||||
public const SQLiteOpenFlags SQLiteFlags =
|
||||
SQLiteOpenFlags.ReadWrite |
|
||||
SQLiteOpenFlags.Create |
|
||||
SQLiteOpenFlags.SharedCache;
|
||||
|
||||
private const string dbFilename = "flowerstory.db3";
|
||||
public static string DatabasePath => Path.Combine(FileSystem.AppDataDirectory, dbFilename);
|
||||
}
|
@ -1,14 +1,15 @@
|
||||
using Blahblah.FlowerStory.Data.Model;
|
||||
using Blahblah.FlowerApp.Data.Model;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using SQLite;
|
||||
|
||||
namespace Blahblah.FlowerStory.Data;
|
||||
namespace Blahblah.FlowerApp.Data;
|
||||
|
||||
public class FlowerDatabase
|
||||
{
|
||||
private SQLiteAsyncConnection database;
|
||||
private SQLiteAsyncConnection database = null!;
|
||||
|
||||
private readonly ILogger logger;
|
||||
|
||||
public FlowerDatabase(ILogger<FlowerDatabase> logger)
|
||||
{
|
||||
this.logger = logger;
|
||||
@ -21,12 +22,12 @@ public class FlowerDatabase
|
||||
return;
|
||||
}
|
||||
|
||||
database = new SQLiteAsyncConnection(Constants.DatabasePath, Constants.Flags);
|
||||
database = new SQLiteAsyncConnection(Constants.DatabasePath, Constants.SQLiteFlags);
|
||||
|
||||
#if DEBUG
|
||||
var result =
|
||||
#endif
|
||||
await database.CreateTablesAsync<FlowerItem, RecordItem, PhotoItem>();
|
||||
await database.CreateTablesAsync<FlowerItem, RecordItem, PhotoItem, UserItem>();
|
||||
|
||||
#if DEBUG
|
||||
foreach (var item in result.Results)
|
||||
@ -36,9 +37,9 @@ public class FlowerDatabase
|
||||
#endif
|
||||
}
|
||||
|
||||
public async Task<List<FlowerItem>> GetFlowers()
|
||||
public async Task<FlowerItem[]> GetFlowers()
|
||||
{
|
||||
await Init();
|
||||
return await database.Table<FlowerItem>().ToListAsync();
|
||||
return await database.Table<FlowerItem>().ToArrayAsync();
|
||||
}
|
||||
}
|
41
FlowerApp/Data/Model/FlowerItem.cs
Normal file
@ -0,0 +1,41 @@
|
||||
using SQLite;
|
||||
|
||||
namespace Blahblah.FlowerApp.Data.Model;
|
||||
|
||||
[Table("flowers")]
|
||||
public class FlowerItem
|
||||
{
|
||||
[Column("fid"), PrimaryKey, NotNull]
|
||||
public int Id { get; set; }
|
||||
|
||||
[Column("uid"), NotNull]
|
||||
public int OwnerId { get; set; }
|
||||
|
||||
[Column("category"), NotNull]
|
||||
public int Category { get; set; }
|
||||
|
||||
[Column("Name"), NotNull]
|
||||
public string Name { get; set; } = null!;
|
||||
|
||||
[Column("datebuy"), NotNull]
|
||||
public long DateBuyUnixTime { get; set; }
|
||||
|
||||
public DateTimeOffset DateBuy => DateTimeOffset.FromUnixTimeMilliseconds(DateBuyUnixTime);
|
||||
|
||||
[Column("cost")]
|
||||
public decimal? Cost { get; set; }
|
||||
|
||||
[Column("purchase")]
|
||||
public string? PurchaseFrom { get; set; }
|
||||
|
||||
[Column("memo")]
|
||||
public string? Memo { get; set; }
|
||||
|
||||
[Column("latitude")]
|
||||
public double? Latitude { get; set; }
|
||||
|
||||
[Column("longitude")]
|
||||
public double? Longitude { get; set; }
|
||||
|
||||
public int? Distance { get; set; }
|
||||
}
|
36
FlowerApp/Data/Model/PhotoItem.cs
Normal file
@ -0,0 +1,36 @@
|
||||
using SQLite;
|
||||
|
||||
namespace Blahblah.FlowerApp.Data.Model;
|
||||
|
||||
[Table("photos")]
|
||||
public class PhotoItem
|
||||
{
|
||||
[Column("pid"), PrimaryKey, NotNull]
|
||||
public int Id { get; set; }
|
||||
|
||||
[Column("uid"), NotNull]
|
||||
public int OwnerId { get; set; }
|
||||
|
||||
[Column("fid"), NotNull]
|
||||
public int FlowerId { get; set; }
|
||||
|
||||
[Column("rid"), NotNull]
|
||||
public int RecordId { get; set; }
|
||||
|
||||
[Column("filetype"), NotNull]
|
||||
public string FileType { get; set; } = null!;
|
||||
|
||||
[Column("filename"), NotNull]
|
||||
public string FileName { get; set; } = null!;
|
||||
|
||||
[Column("path"), NotNull]
|
||||
public string Path { get; set; } = null!;
|
||||
|
||||
[Column("dateupload"), NotNull]
|
||||
public long DateUploadUnixTime { get; set; }
|
||||
|
||||
public DateTimeOffset DateUpload => DateTimeOffset.FromUnixTimeMilliseconds(DateUploadUnixTime);
|
||||
|
||||
[Column("url")]
|
||||
public string Url { get; set; } = null!;
|
||||
}
|
38
FlowerApp/Data/Model/RecordItem.cs
Normal file
@ -0,0 +1,38 @@
|
||||
using SQLite;
|
||||
|
||||
namespace Blahblah.FlowerApp.Data.Model;
|
||||
|
||||
public class RecordItem
|
||||
{
|
||||
[Column("rid"), PrimaryKey, NotNull]
|
||||
public int Id { get; set; }
|
||||
|
||||
[Column("uid"), NotNull]
|
||||
public int OwnerId { get; set; }
|
||||
|
||||
[Column("fid"), NotNull]
|
||||
public int FlowerId { get; set; }
|
||||
|
||||
[Column("event"), NotNull]
|
||||
public int EventType { get; set; }
|
||||
|
||||
[Column("date"), NotNull]
|
||||
public long DateUnixTime { get; set; }
|
||||
|
||||
public DateTimeOffset Date => DateTimeOffset.FromUnixTimeMilliseconds(DateUnixTime);
|
||||
|
||||
[Column("byuid")]
|
||||
public int? ByUserId { get; set; }
|
||||
|
||||
[Column("byname")]
|
||||
public string? ByUserName { get; set; }
|
||||
|
||||
[Column("memo")]
|
||||
public string? Memo { get; set; }
|
||||
|
||||
[Column("latitude")]
|
||||
public double? Latitude { get; set; }
|
||||
|
||||
[Column("longitude")]
|
||||
public double? Longitude { get; set; }
|
||||
}
|
40
FlowerApp/Data/Model/UserItem.cs
Normal file
@ -0,0 +1,40 @@
|
||||
using SQLite;
|
||||
|
||||
namespace Blahblah.FlowerApp.Data.Model;
|
||||
|
||||
public class UserItem
|
||||
{
|
||||
[Column("uid"), PrimaryKey, AutoIncrement]
|
||||
public int Id { get; set; }
|
||||
|
||||
[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!;
|
||||
|
||||
[Column("level"), NotNull]
|
||||
public int Level { get; set; }
|
||||
|
||||
[Column("regdate"), NotNull]
|
||||
public long RegisterDateUnixTime { get; set; }
|
||||
|
||||
public DateTimeOffset RegisterDate => DateTimeOffset.FromUnixTimeMilliseconds(RegisterDateUnixTime);
|
||||
|
||||
//[Column("activedate")]
|
||||
//public long? ActiveDateUnixTime { get; set; }
|
||||
|
||||
//public DateTimeOffset? ActiveDate => ActiveDateUnixTime == null ? null : DateTimeOffset.FromUnixTimeMilliseconds(ActiveDateUnixTime.Value);
|
||||
|
||||
[Column("email")]
|
||||
public string? Email { get; set; }
|
||||
|
||||
[Column("mobile")]
|
||||
public string? Mobile { get; set; }
|
||||
|
||||
[Column("avatar")]
|
||||
public byte[]? Avatar { get; set; }
|
||||
}
|
101
FlowerApp/FlowerApp.csproj
Normal file
@ -0,0 +1,101 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net8.0-android;net8.0-ios</TargetFrameworks>
|
||||
|
||||
<OutputType>Exe</OutputType>
|
||||
<RootNamespace>Blahblah.FlowerApp</RootNamespace>
|
||||
<UseMaui>true</UseMaui>
|
||||
<SingleProject>true</SingleProject>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<!--<PublishAot>true</PublishAot>-->
|
||||
|
||||
<!-- Display name -->
|
||||
<ApplicationTitle>Flower Story</ApplicationTitle>
|
||||
|
||||
<!-- App Identifier -->
|
||||
<ApplicationId>org.blahblah.flowerstory</ApplicationId>
|
||||
|
||||
<!-- Versions -->
|
||||
<ApplicationDisplayVersion>0.1.719</ApplicationDisplayVersion>
|
||||
<ApplicationVersion>1</ApplicationVersion>
|
||||
|
||||
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">13.0</SupportedOSPlatformVersion>
|
||||
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">23.0</SupportedOSPlatformVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)' == 'Debug|net8.0-android'">
|
||||
<RuntimeIdentifiers>android-x64</RuntimeIdentifiers>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)' == 'Release|net8.0-android'">
|
||||
<RuntimeIdentifiers>android-x64;android-arm64</RuntimeIdentifiers>
|
||||
<AndroidPackageFormat>apk</AndroidPackageFormat>
|
||||
<AndroidCreatePackagePerAbi>true</AndroidCreatePackagePerAbi>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">
|
||||
<CreatePackage>false</CreatePackage>
|
||||
<ProvisioningType>manual</ProvisioningType>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)' == 'Debug|net8.0-ios'">
|
||||
<CodesignKey>Apple Development</CodesignKey>
|
||||
<CodesignProvision>Flower Story Development</CodesignProvision>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)' == 'Release|net8.0-ios'">
|
||||
<CodesignKey>Apple Distribution</CodesignKey>
|
||||
<CodesignProvision>Flower Story Ad-Hoc</CodesignProvision>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<!-- App Icon -->
|
||||
<MauiIcon Include="Resources\AppIcon\appicon.svg" ForegroundFile="Resources\AppIcon\appiconfg.svg" Color="#512BD4" />
|
||||
|
||||
<!-- Splash Screen -->
|
||||
<MauiSplashScreen Include="Resources\Splash\splash.svg" Color="#512BD4" BaseSize="128,128" />
|
||||
|
||||
<!-- Images -->
|
||||
<MauiImage Include="Resources\Images\*" />
|
||||
<MauiImage Update="Resources\Images\dotnet_bot.svg" BaseSize="168,208" />
|
||||
|
||||
<!-- Custom Fonts -->
|
||||
<MauiFont Include="Resources\Fonts\*" />
|
||||
|
||||
<!-- Raw Assets (also remove the "Resources\Raw" prefix) -->
|
||||
<MauiAsset Include="Resources\Raw\**" LogicalName="%(RecursiveDir)%(Filename)%(Extension)" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Localization" Version="7.0.9" />
|
||||
<PackageReference Include="Microsoft.Maui.Controls" Version="$(MauiVersion)" />
|
||||
<PackageReference Include="Microsoft.Maui.Controls.Compatibility" Version="$(MauiVersion)" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="8.0.0-preview.6.23329.7" />
|
||||
<PackageReference Include="sqlite-net-pcl" Version="1.8.116" />
|
||||
<PackageReference Include="SQLitePCLRaw.bundle_green" Version="2.1.5" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Update="Localizations.Designer.cs">
|
||||
<DesignTime>True</DesignTime>
|
||||
<AutoGen>True</AutoGen>
|
||||
<DependentUpon>Localizations.resx</DependentUpon>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Update="Localizations.resx">
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
<LastGenOutput>Localizations.Designer.cs</LastGenOutput>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Platforms\Android\Handlers\" />
|
||||
</ItemGroup>
|
||||
|
||||
<!--<ItemGroup>
|
||||
<Folder Include="Properties\" />
|
||||
</ItemGroup>-->
|
||||
</Project>
|
20
FlowerApp/Handlers/WaterfallLayoutHandler.cs
Normal file
@ -0,0 +1,20 @@
|
||||
using Blahblah.FlowerApp.Controls;
|
||||
|
||||
namespace Blahblah.FlowerApp.Handlers;
|
||||
|
||||
public partial class WaterfallLayoutHandler
|
||||
{
|
||||
static readonly IPropertyMapper<WaterfallLayout, WaterfallLayoutHandler> PropertyMapper = new PropertyMapper<WaterfallLayout, WaterfallLayoutHandler>(ViewMapper)
|
||||
{
|
||||
[nameof(WaterfallLayout.Columns)] = MapColumns
|
||||
};
|
||||
|
||||
static readonly CommandMapper<WaterfallLayout, WaterfallLayoutHandler> CommandMapper = new(ViewCommandMapper)
|
||||
{
|
||||
|
||||
};
|
||||
|
||||
public WaterfallLayoutHandler() : base(PropertyMapper, CommandMapper)
|
||||
{
|
||||
}
|
||||
}
|
36
FlowerApp/LocalizationResource.cs
Normal file
@ -0,0 +1,36 @@
|
||||
using Microsoft.Extensions.Localization;
|
||||
|
||||
namespace Blahblah.FlowerApp;
|
||||
|
||||
sealed class LocalizationResource
|
||||
{
|
||||
private static IStringLocalizer<Localizations>? localizer;
|
||||
|
||||
public static IStringLocalizer<Localizations>? Localizer => localizer ??=
|
||||
#if __ANDROID__
|
||||
MauiApplication
|
||||
#else
|
||||
MauiUIApplicationDelegate
|
||||
#endif
|
||||
.Current.Services.GetService<IStringLocalizer<Localizations>>();
|
||||
|
||||
public static string GetText(string key, string defaultValue = "")
|
||||
{
|
||||
return Localizer?.GetString(key) ?? defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
[ContentProperty(nameof(Key))]
|
||||
public class LangExtension : IMarkupExtension
|
||||
{
|
||||
public required string Key { get; set; }
|
||||
|
||||
public string Default { get; set; } = string.Empty;
|
||||
|
||||
public object ProvideValue(IServiceProvider _)
|
||||
{
|
||||
return LocalizationResource.GetText(Key, Default);
|
||||
}
|
||||
|
||||
object IMarkupExtension.ProvideValue(IServiceProvider serviceProvider) => ProvideValue(serviceProvider);
|
||||
}
|
@ -8,7 +8,7 @@
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace Blahblah.FlowerStory {
|
||||
namespace Blahblah.FlowerApp {
|
||||
using System;
|
||||
|
||||
|
||||
@ -39,7 +39,7 @@ namespace Blahblah.FlowerStory {
|
||||
internal static global::System.Resources.ResourceManager ResourceManager {
|
||||
get {
|
||||
if (object.ReferenceEquals(resourceMan, null)) {
|
||||
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Blahblah.FlowerStory.Localizations", typeof(Localizations).Assembly);
|
||||
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Blahblah.FlowerApp.Localizations", typeof(Localizations).Assembly);
|
||||
resourceMan = temp;
|
||||
}
|
||||
return resourceMan;
|
@ -1,12 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
xmlns:l="clr-namespace:Blahblah.FlowerStory"
|
||||
xmlns:forms="clr-namespace:SkiaSharp.Views.Maui.Controls;assembly=SkiaSharp.Views.Maui.Controls"
|
||||
x:Class="Blahblah.FlowerStory.MainPage">
|
||||
xmlns:l="clr-namespace:Blahblah.FlowerApp"
|
||||
xmlns:ctl="clr-namespace:Blahblah.FlowerApp.Controls"
|
||||
x:Class="Blahblah.FlowerApp.MainPage"
|
||||
x:Name="mainPage"
|
||||
x:DataType="l:MainPage">
|
||||
|
||||
<ScrollView>
|
||||
<VerticalStackLayout
|
||||
<ScrollView BindingContext="{x:Reference mainPage}">
|
||||
<!--<VerticalStackLayout
|
||||
Spacing="25"
|
||||
Padding="30,0"
|
||||
VerticalOptions="Center">
|
||||
@ -17,10 +19,8 @@
|
||||
HeightRequest="200"
|
||||
HorizontalOptions="Center" />
|
||||
|
||||
<forms:SKCanvasView x:Name="canvasView" PaintSurface="canvasView_PaintSurface"/>
|
||||
|
||||
<Label
|
||||
Text="{l:Localize unknown}"
|
||||
Text="{l:Lang unknown, Default=Unknown}"
|
||||
SemanticProperties.HeadingLevel="Level1"
|
||||
FontSize="32"
|
||||
HorizontalOptions="Center" />
|
||||
@ -39,7 +39,8 @@
|
||||
Clicked="OnCounterClicked"
|
||||
HorizontalOptions="Center" />
|
||||
|
||||
</VerticalStackLayout>
|
||||
</VerticalStackLayout>-->
|
||||
<ctl:WaterfallLayout/>
|
||||
</ScrollView>
|
||||
|
||||
</ContentPage>
|
49
FlowerApp/MainPage.xaml.cs
Normal file
@ -0,0 +1,49 @@
|
||||
using Blahblah.FlowerApp.Data;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Blahblah.FlowerApp;
|
||||
|
||||
public partial class MainPage : ContentPage
|
||||
{
|
||||
int count = 0;
|
||||
readonly FlowerDatabase database;
|
||||
readonly ILogger logger;
|
||||
|
||||
public MainPage(FlowerDatabase database, ILogger<MainPage> logger)
|
||||
{
|
||||
this.database = database;
|
||||
this.logger = logger;
|
||||
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
protected override void OnAppearing()
|
||||
{
|
||||
base.OnAppearing();
|
||||
|
||||
Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var list = await database.GetFlowers();
|
||||
logger.LogInformation("got {count} flowers.", list.Length);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError("error occurs in MainPage, {exception}", ex);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//private void OnCounterClicked(object sender, EventArgs e)
|
||||
//{
|
||||
// count++;
|
||||
|
||||
// if (count == 1)
|
||||
// CounterBtn.Text = $"Clicked {count} time";
|
||||
// else
|
||||
// CounterBtn.Text = $"Clicked {count} times";
|
||||
|
||||
// SemanticScreenReader.Announce(CounterBtn.Text);
|
||||
//}
|
||||
}
|
38
FlowerApp/MauiProgram.cs
Normal file
@ -0,0 +1,38 @@
|
||||
using Blahblah.FlowerApp.Controls;
|
||||
using Blahblah.FlowerApp.Data;
|
||||
using Blahblah.FlowerApp.Handlers;
|
||||
#if DEBUG
|
||||
using Microsoft.Extensions.Logging;
|
||||
#endif
|
||||
|
||||
namespace Blahblah.FlowerApp;
|
||||
|
||||
public static class MauiProgram
|
||||
{
|
||||
public static MauiApp CreateMauiApp()
|
||||
{
|
||||
var builder = MauiApp.CreateBuilder();
|
||||
builder
|
||||
.UseMauiApp<App>()
|
||||
.ConfigureFonts(fonts =>
|
||||
{
|
||||
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
|
||||
fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
|
||||
})
|
||||
.ConfigureMauiHandlers(handlers =>
|
||||
{
|
||||
handlers.AddHandler<WaterfallLayout, WaterfallLayoutHandler>();
|
||||
});
|
||||
|
||||
#if DEBUG
|
||||
builder.Logging.AddDebug();
|
||||
#endif
|
||||
|
||||
builder.Services.AddSingleton<MainPage>();
|
||||
builder.Services.AddSingleton<FlowerDatabase>();
|
||||
|
||||
builder.Services.AddLocalization();
|
||||
|
||||
return builder.Build();
|
||||
}
|
||||
}
|
18
FlowerApp/Platforms/Android/MainActivity.cs
Normal file
@ -0,0 +1,18 @@
|
||||
using Android.App;
|
||||
using Android.Content.PM;
|
||||
|
||||
namespace Blahblah.FlowerApp;
|
||||
|
||||
[Activity(
|
||||
Theme = "@style/Maui.SplashTheme",
|
||||
MainLauncher = true,
|
||||
ConfigurationChanges =
|
||||
ConfigChanges.ScreenSize |
|
||||
ConfigChanges.Orientation |
|
||||
ConfigChanges.UiMode |
|
||||
ConfigChanges.ScreenLayout |
|
||||
ConfigChanges.SmallestScreenSize |
|
||||
ConfigChanges.Density)]
|
||||
public class MainActivity : MauiAppCompatActivity
|
||||
{
|
||||
}
|
15
FlowerApp/Platforms/Android/MainApplication.cs
Normal file
@ -0,0 +1,15 @@
|
||||
using Android.App;
|
||||
using Android.Runtime;
|
||||
|
||||
namespace Blahblah.FlowerApp;
|
||||
|
||||
[Application]
|
||||
public class MainApplication : MauiApplication
|
||||
{
|
||||
public MainApplication(IntPtr handle, JniHandleOwnership ownership)
|
||||
: base(handle, ownership)
|
||||
{
|
||||
}
|
||||
|
||||
protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
|
||||
}
|
9
FlowerApp/Platforms/iOS/AppDelegate.cs
Normal file
@ -0,0 +1,9 @@
|
||||
using Foundation;
|
||||
|
||||
namespace Blahblah.FlowerApp;
|
||||
|
||||
[Register("AppDelegate")]
|
||||
public class AppDelegate : MauiUIApplicationDelegate
|
||||
{
|
||||
protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
|
||||
}
|
125
FlowerApp/Platforms/iOS/Controls/FlowLayout.cs
Normal file
@ -0,0 +1,125 @@
|
||||
using CoreGraphics;
|
||||
using Foundation;
|
||||
using UIKit;
|
||||
|
||||
namespace Blahblah.FlowerApp.Platforms.iOS.Controls;
|
||||
|
||||
public class FlowLayout : UICollectionViewFlowLayout
|
||||
{
|
||||
public CalculateCellHeightHandler? CalculateCellHeight { get; set; }
|
||||
|
||||
private readonly List<UICollectionViewLayoutAttributes> 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);
|
86
FlowerApp/Platforms/iOS/Controls/MauiWaterfallLayout.cs
Normal file
@ -0,0 +1,86 @@
|
||||
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);
|
||||
}
|
||||
}
|
26
FlowerApp/Platforms/iOS/Handlers/WaterfallLayoutHandler.cs
Normal file
@ -0,0 +1,26 @@
|
||||
using Blahblah.FlowerApp.Controls;
|
||||
using Blahblah.FlowerApp.Platforms.iOS.Controls;
|
||||
using Microsoft.Maui.Handlers;
|
||||
|
||||
namespace Blahblah.FlowerApp.Handlers;
|
||||
|
||||
partial class WaterfallLayoutHandler : ViewHandler<WaterfallLayout, MauiWaterfallLayout>
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
14
FlowerApp/Platforms/iOS/Program.cs
Normal file
@ -0,0 +1,14 @@
|
||||
using UIKit;
|
||||
|
||||
namespace Blahblah.FlowerApp;
|
||||
|
||||
public class Program
|
||||
{
|
||||
// This is the main entry point of the application.
|
||||
static void Main(string[] args)
|
||||
{
|
||||
// if you want to use a different Application Delegate class from "AppDelegate"
|
||||
// you can specify it here.
|
||||
UIApplication.Main(args, null, typeof(AppDelegate));
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 228 B After Width: | Height: | Size: 228 B |
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
@ -3,26 +3,26 @@ Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.7.33711.374
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FlowerStory", "App\FlowerStory.csproj", "{A2EB9F7A-8BB8-4D6D-989C-21C6CF10CE4C}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Server", "Server\Server.csproj", "{A551F94A-1997-4A20-A1E8-157050D92CEF}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FlowerApp", "FlowerApp\FlowerApp.csproj", "{FCBB0455-071E-407B-9CB6-553C6D283756}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{A2EB9F7A-8BB8-4D6D-989C-21C6CF10CE4C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{A2EB9F7A-8BB8-4D6D-989C-21C6CF10CE4C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{A2EB9F7A-8BB8-4D6D-989C-21C6CF10CE4C}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
|
||||
{A2EB9F7A-8BB8-4D6D-989C-21C6CF10CE4C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{A2EB9F7A-8BB8-4D6D-989C-21C6CF10CE4C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{A2EB9F7A-8BB8-4D6D-989C-21C6CF10CE4C}.Release|Any CPU.Deploy.0 = Release|Any CPU
|
||||
{A551F94A-1997-4A20-A1E8-157050D92CEF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{A551F94A-1997-4A20-A1E8-157050D92CEF}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{A551F94A-1997-4A20-A1E8-157050D92CEF}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{A551F94A-1997-4A20-A1E8-157050D92CEF}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{FCBB0455-071E-407B-9CB6-553C6D283756}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{FCBB0455-071E-407B-9CB6-553C6D283756}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{FCBB0455-071E-407B-9CB6-553C6D283756}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
|
||||
{FCBB0455-071E-407B-9CB6-553C6D283756}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{FCBB0455-071E-407B-9CB6-553C6D283756}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{FCBB0455-071E-407B-9CB6-553C6D283756}.Release|Any CPU.Deploy.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|