Compare commits
29 Commits
fde8931dbd
...
master
Author | SHA1 | Date | |
---|---|---|---|
776cc7da49 | |||
1072a1a15c | |||
469b1a8627 | |||
ba289b6087 | |||
b7affae8ab | |||
7ca377b8c2 | |||
f27b0a2564 | |||
a214110c8c | |||
4067bc2768 | |||
ba7b3e7389 | |||
5cbcfbcd56 | |||
ef5e91aad1 | |||
60f7824cb5 | |||
b46b150f6a | |||
cac4735bc4 | |||
d3af69b31e | |||
8ba6f4bf85 | |||
5b209cc19c | |||
77b4e54734 | |||
9a8f1289ed | |||
c43bfb51be | |||
51ac42b9fc | |||
6d2e0624ab | |||
28897c96ea | |||
5ec4119025 | |||
f5f16d43f4 | |||
71c1a7f0f1 | |||
74053a328e | |||
84ec2df987 |
@ -1,9 +1,11 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Billing.Languages;
|
using Billing.Languages;
|
||||||
using Billing.Models;
|
using Billing.Models;
|
||||||
using Billing.Store;
|
using Billing.Store;
|
||||||
using Billing.Themes;
|
using Billing.Themes;
|
||||||
|
using Billing.UI;
|
||||||
using Xamarin.Essentials;
|
using Xamarin.Essentials;
|
||||||
using Xamarin.Forms;
|
using Xamarin.Forms;
|
||||||
|
|
||||||
@ -11,41 +13,55 @@ namespace Billing
|
|||||||
{
|
{
|
||||||
public class App : Application
|
public class App : Application
|
||||||
{
|
{
|
||||||
|
internal const string NewBillAction = "/newbill";
|
||||||
|
|
||||||
public static AppTheme CurrentTheme { get; private set; }
|
public static AppTheme CurrentTheme { get; private set; }
|
||||||
public static PlatformCulture CurrentCulture { get; private set; }
|
public static PlatformCulture CurrentCulture { get; private set; }
|
||||||
public static List<Bill> Bills => bills ??= new List<Bill>();
|
public static List<Bill> Bills => bills ??= new List<Bill>();
|
||||||
public static List<Account> Accounts => accounts ??= new List<Account>();
|
public static List<Account> Accounts => accounts ??= new List<Account>();
|
||||||
public static List<Category> Categories => categories ??= new List<Category>();
|
public static List<Category> Categories => categories ??= new List<Category>();
|
||||||
|
public static bool SaveLocation => saveLocation;
|
||||||
|
public static string MainRoute => mainRoute;
|
||||||
|
|
||||||
private static List<Bill> bills;
|
private static List<Bill> bills;
|
||||||
private static List<Account> accounts;
|
private static List<Account> accounts;
|
||||||
private static List<Category> categories;
|
private static List<Category> categories;
|
||||||
|
private static bool saveLocation;
|
||||||
|
private static string mainRoute;
|
||||||
|
|
||||||
public App()
|
private string initialUrl;
|
||||||
|
|
||||||
|
public App(string url = null)
|
||||||
{
|
{
|
||||||
|
if (url == NewBillAction)
|
||||||
|
{
|
||||||
|
#if __ANDROID__
|
||||||
|
mainRoute = "//Bills/Details";
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mainRoute = "//Bills";
|
||||||
|
initialUrl = url;
|
||||||
|
}
|
||||||
CurrentCulture = new PlatformCulture();
|
CurrentCulture = new PlatformCulture();
|
||||||
|
saveLocation = Preferences.Get(Definition.SaveLocationKey, false);
|
||||||
InitResources();
|
InitResources();
|
||||||
|
|
||||||
MainPage = new MainShell();
|
MainPage = new MainShell();
|
||||||
Shell.Current.GoToAsync("//Settings");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void WriteAccounts() => StoreHelper.WriteAccounts(accounts);
|
|
||||||
|
|
||||||
public static void WriteBills() => StoreHelper.WriteBills(bills);
|
|
||||||
|
|
||||||
public static void WriteCategories() => StoreHelper.WriteCategories(categories);
|
|
||||||
|
|
||||||
protected override void OnStart()
|
protected override void OnStart()
|
||||||
{
|
{
|
||||||
Helper.Debug($"personal folder: {StoreHelper.PersonalFolder}");
|
Helper.Debug($"personal folder: {StoreHelper.PersonalFolder}");
|
||||||
Helper.Debug($"cache folder: {StoreHelper.CacheFolder}");
|
Helper.Debug($"cache folder: {StoreHelper.CacheFolder}");
|
||||||
|
|
||||||
accounts = StoreHelper.GetAccounts();
|
|
||||||
categories = StoreHelper.GetCategories();
|
|
||||||
bills = StoreHelper.GetBills();
|
|
||||||
|
|
||||||
Shell.Current.GoToAsync("//Bills");
|
if (initialUrl != null)
|
||||||
|
{
|
||||||
|
var url = initialUrl;
|
||||||
|
initialUrl = null;
|
||||||
|
_ = OpenUrl(url);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnResume()
|
protected override void OnResume()
|
||||||
@ -83,5 +99,56 @@ namespace Billing
|
|||||||
// TODO: status bar
|
// TODO: status bar
|
||||||
Resources = instance;
|
Resources = instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void SetSaveLocation(bool flag)
|
||||||
|
{
|
||||||
|
saveLocation = flag;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task InitializeData()
|
||||||
|
{
|
||||||
|
var instance = await StoreHelper.Instance;
|
||||||
|
await Task.WhenAll(
|
||||||
|
Task.Run(async () => accounts = await instance.GetListAsync<Account>()),
|
||||||
|
Task.Run(async () => categories = await instance.GetListAsync<Category>()),
|
||||||
|
Task.Run(async () => bills = await instance.GetListAsync<Bill>()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#if __ANDROID__
|
||||||
|
public static async Task<bool> OpenUrl(string url)
|
||||||
|
#elif __IOS__
|
||||||
|
public static bool OpenUrl(string url)
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(url))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (File.Exists(url))
|
||||||
|
{
|
||||||
|
#if __ANDROID__
|
||||||
|
var status = await Helper.CheckAndRequestPermissionAsync<Permissions.StorageRead>();
|
||||||
|
if (status != PermissionStatus.Granted)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
_ = Task.Run(async () =>
|
||||||
|
{
|
||||||
|
var result = await StoreHelper.ReloadDatabase(url);
|
||||||
|
if (result)
|
||||||
|
{
|
||||||
|
await InitializeData();
|
||||||
|
|
||||||
|
var current = Shell.Current.CurrentPage;
|
||||||
|
if (current is BillingPage page)
|
||||||
|
{
|
||||||
|
MainThread.BeginInvokeOnMainThread(() => page.TriggerRefresh());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -11,6 +11,10 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<EmbeddedResource Include="$(MSBuildThisFileDirectory)Languages\en.xml" />
|
<EmbeddedResource Include="$(MSBuildThisFileDirectory)Languages\en.xml" />
|
||||||
<EmbeddedResource Include="$(MSBuildThisFileDirectory)Languages\zh-CN.xml" />
|
<EmbeddedResource Include="$(MSBuildThisFileDirectory)Languages\zh-CN.xml" />
|
||||||
|
<EmbeddedResource Include="$(MSBuildThisFileDirectory)SplashPage.xaml">
|
||||||
|
<SubType>Designer</SubType>
|
||||||
|
<Generator>MSBuild:UpdateDesignTimeXaml</Generator>
|
||||||
|
</EmbeddedResource>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="$(MSBuildThisFileDirectory)App.cs" />
|
<Compile Include="$(MSBuildThisFileDirectory)App.cs" />
|
||||||
@ -18,10 +22,11 @@
|
|||||||
<Compile Include="$(MSBuildThisFileDirectory)Helper.cs" />
|
<Compile Include="$(MSBuildThisFileDirectory)Helper.cs" />
|
||||||
<Compile Include="$(MSBuildThisFileDirectory)Languages\PlatformCulture.cs" />
|
<Compile Include="$(MSBuildThisFileDirectory)Languages\PlatformCulture.cs" />
|
||||||
<Compile Include="$(MSBuildThisFileDirectory)Languages\Resource.cs" />
|
<Compile Include="$(MSBuildThisFileDirectory)Languages\Resource.cs" />
|
||||||
|
<Compile Include="$(MSBuildThisFileDirectory)LocationExtension.cs" />
|
||||||
<Compile Include="$(MSBuildThisFileDirectory)MainShell.xaml.cs">
|
<Compile Include="$(MSBuildThisFileDirectory)MainShell.xaml.cs">
|
||||||
<DependentUpon>MainShell.xaml</DependentUpon>
|
<DependentUpon>MainShell.xaml</DependentUpon>
|
||||||
|
<SubType>Code</SubType>
|
||||||
</Compile>
|
</Compile>
|
||||||
<Compile Include="$(MSBuildThisFileDirectory)Models\BaseModel.cs" />
|
|
||||||
<Compile Include="$(MSBuildThisFileDirectory)Models\Category.cs" />
|
<Compile Include="$(MSBuildThisFileDirectory)Models\Category.cs" />
|
||||||
<Compile Include="$(MSBuildThisFileDirectory)Models\Account.cs" />
|
<Compile Include="$(MSBuildThisFileDirectory)Models\Account.cs" />
|
||||||
<Compile Include="$(MSBuildThisFileDirectory)Themes\BaseTheme.cs" />
|
<Compile Include="$(MSBuildThisFileDirectory)Themes\BaseTheme.cs" />
|
||||||
@ -29,6 +34,7 @@
|
|||||||
<Compile Include="$(MSBuildThisFileDirectory)Themes\Light.cs" />
|
<Compile Include="$(MSBuildThisFileDirectory)Themes\Light.cs" />
|
||||||
<Compile Include="$(MSBuildThisFileDirectory)UI\BillingDate.xaml.cs">
|
<Compile Include="$(MSBuildThisFileDirectory)UI\BillingDate.xaml.cs">
|
||||||
<DependentUpon>BillingDate.xaml</DependentUpon>
|
<DependentUpon>BillingDate.xaml</DependentUpon>
|
||||||
|
<SubType>Code</SubType>
|
||||||
</Compile>
|
</Compile>
|
||||||
<Compile Include="$(MSBuildThisFileDirectory)UI\BillingPage.cs" />
|
<Compile Include="$(MSBuildThisFileDirectory)UI\BillingPage.cs" />
|
||||||
<Compile Include="$(MSBuildThisFileDirectory)UI\ColorPicker.cs" />
|
<Compile Include="$(MSBuildThisFileDirectory)UI\ColorPicker.cs" />
|
||||||
@ -41,12 +47,15 @@
|
|||||||
<Compile Include="$(MSBuildThisFileDirectory)UI\WrapLayout.cs" />
|
<Compile Include="$(MSBuildThisFileDirectory)UI\WrapLayout.cs" />
|
||||||
<Compile Include="$(MSBuildThisFileDirectory)Views\AccountPage.xaml.cs">
|
<Compile Include="$(MSBuildThisFileDirectory)Views\AccountPage.xaml.cs">
|
||||||
<DependentUpon>AccountPage.xaml</DependentUpon>
|
<DependentUpon>AccountPage.xaml</DependentUpon>
|
||||||
|
<SubType>Code</SubType>
|
||||||
</Compile>
|
</Compile>
|
||||||
<Compile Include="$(MSBuildThisFileDirectory)Views\AddAccountPage.xaml.cs">
|
<Compile Include="$(MSBuildThisFileDirectory)Views\AddAccountPage.xaml.cs">
|
||||||
<DependentUpon>AddAccountPage.xaml</DependentUpon>
|
<DependentUpon>AddAccountPage.xaml</DependentUpon>
|
||||||
|
<SubType>Code</SubType>
|
||||||
</Compile>
|
</Compile>
|
||||||
<Compile Include="$(MSBuildThisFileDirectory)Views\AddBillPage.xaml.cs">
|
<Compile Include="$(MSBuildThisFileDirectory)Views\AddBillPage.xaml.cs">
|
||||||
<DependentUpon>AddBillPage.xaml</DependentUpon>
|
<DependentUpon>AddBillPage.xaml</DependentUpon>
|
||||||
|
<SubType>Code</SubType>
|
||||||
</Compile>
|
</Compile>
|
||||||
<Compile Include="$(MSBuildThisFileDirectory)Views\AddCategoryPage.xaml.cs">
|
<Compile Include="$(MSBuildThisFileDirectory)Views\AddCategoryPage.xaml.cs">
|
||||||
<DependentUpon>AddCategoryPage.xaml</DependentUpon>
|
<DependentUpon>AddCategoryPage.xaml</DependentUpon>
|
||||||
@ -54,6 +63,7 @@
|
|||||||
</Compile>
|
</Compile>
|
||||||
<Compile Include="$(MSBuildThisFileDirectory)Views\BillPage.xaml.cs">
|
<Compile Include="$(MSBuildThisFileDirectory)Views\BillPage.xaml.cs">
|
||||||
<DependentUpon>BillPage.xaml</DependentUpon>
|
<DependentUpon>BillPage.xaml</DependentUpon>
|
||||||
|
<SubType>Code</SubType>
|
||||||
</Compile>
|
</Compile>
|
||||||
<Compile Include="$(MSBuildThisFileDirectory)Views\CategoryPage.xaml.cs">
|
<Compile Include="$(MSBuildThisFileDirectory)Views\CategoryPage.xaml.cs">
|
||||||
<DependentUpon>CategoryPage.xaml</DependentUpon>
|
<DependentUpon>CategoryPage.xaml</DependentUpon>
|
||||||
@ -73,11 +83,19 @@
|
|||||||
</Compile>
|
</Compile>
|
||||||
<Compile Include="$(MSBuildThisFileDirectory)Views\SettingPage.xaml.cs">
|
<Compile Include="$(MSBuildThisFileDirectory)Views\SettingPage.xaml.cs">
|
||||||
<DependentUpon>SettingPage.xaml</DependentUpon>
|
<DependentUpon>SettingPage.xaml</DependentUpon>
|
||||||
|
<SubType>Code</SubType>
|
||||||
</Compile>
|
</Compile>
|
||||||
<Compile Include="$(MSBuildThisFileDirectory)UI\OptionsCells.cs" />
|
<Compile Include="$(MSBuildThisFileDirectory)UI\OptionsCells.cs" />
|
||||||
<Compile Include="$(MSBuildThisFileDirectory)Store\StoreHelper.cs" />
|
<Compile Include="$(MSBuildThisFileDirectory)Store\StoreHelper.cs" />
|
||||||
<Compile Include="$(MSBuildThisFileDirectory)Models\Bill.cs" />
|
<Compile Include="$(MSBuildThisFileDirectory)Models\Bill.cs" />
|
||||||
<Compile Include="$(MSBuildThisFileDirectory)UI\SegmentedControl.cs" />
|
<Compile Include="$(MSBuildThisFileDirectory)UI\SegmentedControl.cs" />
|
||||||
|
<Compile Include="$(MSBuildThisFileDirectory)Models\IIdItem.cs" />
|
||||||
|
<Compile Include="$(MSBuildThisFileDirectory)SplashPage.xaml.cs">
|
||||||
|
<DependentUpon>SplashPage.xaml</DependentUpon>
|
||||||
|
<SubType>Code</SubType>
|
||||||
|
</Compile>
|
||||||
|
<Compile Include="$(MSBuildThisFileDirectory)Models\Logs.cs" />
|
||||||
|
<Compile Include="$(MSBuildThisFileDirectory)Views\ViewLocationPage.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<EmbeddedResource Include="$(MSBuildThisFileDirectory)MainShell.xaml">
|
<EmbeddedResource Include="$(MSBuildThisFileDirectory)MainShell.xaml">
|
||||||
@ -111,6 +129,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<EmbeddedResource Include="$(MSBuildThisFileDirectory)Views\IconSelectPage.xaml">
|
<EmbeddedResource Include="$(MSBuildThisFileDirectory)Views\IconSelectPage.xaml">
|
||||||
|
<SubType>Designer</SubType>
|
||||||
<Generator>MSBuild:UpdateDesignTimeXaml</Generator>
|
<Generator>MSBuild:UpdateDesignTimeXaml</Generator>
|
||||||
</EmbeddedResource>
|
</EmbeddedResource>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
@ -119,21 +138,25 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<EmbeddedResource Include="$(MSBuildThisFileDirectory)Views\CategoryPage.xaml">
|
<EmbeddedResource Include="$(MSBuildThisFileDirectory)Views\CategoryPage.xaml">
|
||||||
|
<SubType>Designer</SubType>
|
||||||
<Generator>MSBuild:UpdateDesignTimeXaml</Generator>
|
<Generator>MSBuild:UpdateDesignTimeXaml</Generator>
|
||||||
</EmbeddedResource>
|
</EmbeddedResource>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<EmbeddedResource Include="$(MSBuildThisFileDirectory)Views\AddCategoryPage.xaml">
|
<EmbeddedResource Include="$(MSBuildThisFileDirectory)Views\AddCategoryPage.xaml">
|
||||||
|
<SubType>Designer</SubType>
|
||||||
<Generator>MSBuild:UpdateDesignTimeXaml</Generator>
|
<Generator>MSBuild:UpdateDesignTimeXaml</Generator>
|
||||||
</EmbeddedResource>
|
</EmbeddedResource>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<EmbeddedResource Include="$(MSBuildThisFileDirectory)Views\CategorySelectPage.xaml">
|
<EmbeddedResource Include="$(MSBuildThisFileDirectory)Views\CategorySelectPage.xaml">
|
||||||
|
<SubType>Designer</SubType>
|
||||||
<Generator>MSBuild:UpdateDesignTimeXaml</Generator>
|
<Generator>MSBuild:UpdateDesignTimeXaml</Generator>
|
||||||
</EmbeddedResource>
|
</EmbeddedResource>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<EmbeddedResource Include="$(MSBuildThisFileDirectory)Views\RankPage.xaml">
|
<EmbeddedResource Include="$(MSBuildThisFileDirectory)Views\RankPage.xaml">
|
||||||
|
<SubType>Designer</SubType>
|
||||||
<Generator>MSBuild:UpdateDesignTimeXaml</Generator>
|
<Generator>MSBuild:UpdateDesignTimeXaml</Generator>
|
||||||
</EmbeddedResource>
|
</EmbeddedResource>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
using Billing.Models;
|
using Billing.Models;
|
||||||
|
using Billing.UI;
|
||||||
using Billing.Views;
|
using Billing.Views;
|
||||||
using System;
|
using System;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Xamarin.Essentials;
|
using Xamarin.Essentials;
|
||||||
using Xamarin.Forms;
|
using Xamarin.Forms;
|
||||||
|
|
||||||
@ -16,33 +19,35 @@ namespace Billing
|
|||||||
var time = DateTime.Now.ToString("HH:mm:ss.fff");
|
var time = DateTime.Now.ToString("HH:mm:ss.fff");
|
||||||
System.Diagnostics.Debug.WriteLine($"[{time}] - {message}");
|
System.Diagnostics.Debug.WriteLine($"[{time}] - {message}");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void Error(string category, Exception ex)
|
|
||||||
{
|
|
||||||
Error(category, ex?.Message ?? "unknown error");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void Error(string category, string message)
|
|
||||||
{
|
|
||||||
var time = DateTime.Now.ToString("HH:mm:ss.fff");
|
|
||||||
System.Diagnostics.Debug.Fail($"[{time}] - {category}", message);
|
|
||||||
}
|
|
||||||
#else
|
#else
|
||||||
#pragma warning disable IDE0060 // Remove unused parameter
|
#pragma warning disable IDE0060 // Remove unused parameter
|
||||||
public static void Debug(string message)
|
public static void Debug(string message)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
#pragma warning restore IDE0060 // Remove unused parameter
|
||||||
|
#endif
|
||||||
|
|
||||||
public static void Error(string category, Exception ex)
|
public static void Error(string category, Exception ex)
|
||||||
{
|
{
|
||||||
|
Error(category, ex?.ToString() ?? "unknown error");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void Error(string category, string message)
|
public static void Error(string category, string message)
|
||||||
{
|
{
|
||||||
}
|
#if DEBUG
|
||||||
#pragma warning restore IDE0060 // Remove unused parameter
|
var time = DateTime.Now.ToString("HH:mm:ss.fff");
|
||||||
|
System.Diagnostics.Debug.WriteLine($"[{time}] - {category}", message);
|
||||||
|
MainThread.BeginInvokeOnMainThread(async () => await Shell.Current.DisplayAlert(category, message, "Ok"));
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
_ = Store.StoreHelper.SaveLogItemAsync(new Logs()
|
||||||
|
{
|
||||||
|
LogTime = DateTime.Now,
|
||||||
|
Category = category,
|
||||||
|
Detail = message
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public static bool NetworkAvailable
|
public static bool NetworkAvailable
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
@ -80,9 +85,11 @@ namespace Billing
|
|||||||
|
|
||||||
public static UIBill WrapBill(Bill b)
|
public static UIBill WrapBill(Bill b)
|
||||||
{
|
{
|
||||||
|
var category = App.Categories.FirstOrDefault(c => c.Id == b.CategoryId);
|
||||||
return new UIBill(b)
|
return new UIBill(b)
|
||||||
{
|
{
|
||||||
Icon = App.Categories.FirstOrDefault(c => c.Id == b.CategoryId)?.Icon ?? BaseModel.ICON_DEFAULT,
|
Icon = category?.Icon ?? Definition.DefaultIcon,
|
||||||
|
TintColor = category?.TintColor ?? Definition.TransparentColor,
|
||||||
Name = b.Name,
|
Name = b.Name,
|
||||||
DateCreation = b.CreateTime,
|
DateCreation = b.CreateTime,
|
||||||
Amount = b.Amount,
|
Amount = b.Amount,
|
||||||
@ -129,5 +136,26 @@ namespace Billing
|
|||||||
}
|
}
|
||||||
|
|
||||||
public delegate void PropertyValueChanged<TResult, TOwner>(TOwner obj, TResult old, TResult @new);
|
public delegate void PropertyValueChanged<TResult, TOwner>(TOwner obj, TResult old, TResult @new);
|
||||||
|
|
||||||
|
public static async Task<PermissionStatus> CheckAndRequestPermissionAsync<T>() where T : Permissions.BasePermission, new()
|
||||||
|
{
|
||||||
|
var status = await Permissions.CheckStatusAsync<T>();
|
||||||
|
if (status != PermissionStatus.Disabled &&
|
||||||
|
status != PermissionStatus.Granted)
|
||||||
|
{
|
||||||
|
status = await Permissions.RequestAsync<T>();
|
||||||
|
}
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class AsyncLazy<T> : Lazy<Task<T>>
|
||||||
|
{
|
||||||
|
public AsyncLazy(Func<Task<T>> factory) : base(() => Task.Run(factory)) { }
|
||||||
|
|
||||||
|
public TaskAwaiter<T> GetAwaiter()
|
||||||
|
{
|
||||||
|
return Value.GetAwaiter();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -10,12 +10,22 @@ namespace Billing.Languages
|
|||||||
internal class Resource
|
internal class Resource
|
||||||
{
|
{
|
||||||
public static string Ok => Text(nameof(Ok));
|
public static string Ok => Text(nameof(Ok));
|
||||||
|
public static string Cancel => Text(nameof(Cancel));
|
||||||
public static string Yes => Text(nameof(Yes));
|
public static string Yes => Text(nameof(Yes));
|
||||||
public static string No => Text(nameof(No));
|
public static string No => Text(nameof(No));
|
||||||
public static string ConfirmDeleteAccount => Text(nameof(ConfirmDeleteAccount));
|
public static string ConfirmDeleteAccount => Text(nameof(ConfirmDeleteAccount));
|
||||||
public static string ConfirmDeleteBill => Text(nameof(ConfirmDeleteBill));
|
public static string ConfirmDeleteBill => Text(nameof(ConfirmDeleteBill));
|
||||||
public static string TitleDateFormat => Text(nameof(TitleDateFormat));
|
public static string TitleDateFormat => Text(nameof(TitleDateFormat));
|
||||||
|
public static string TitleShortDateFormat => Text(nameof(TitleShortDateFormat));
|
||||||
public static string DateRangeFormat => Text(nameof(DateRangeFormat));
|
public static string DateRangeFormat => Text(nameof(DateRangeFormat));
|
||||||
|
public static string Custom => Text(nameof(Custom));
|
||||||
|
public static string Monthly => Text(nameof(Monthly));
|
||||||
|
public static string Today => Text(nameof(Today));
|
||||||
|
public static string PastMonth => Text(nameof(PastMonth));
|
||||||
|
public static string PastQuarter => Text(nameof(PastQuarter));
|
||||||
|
public static string PastSixMonths => Text(nameof(PastSixMonths));
|
||||||
|
public static string PastYear => Text(nameof(PastYear));
|
||||||
|
public static string Total => Text(nameof(Total));
|
||||||
public static string Cash => Text(nameof(Cash));
|
public static string Cash => Text(nameof(Cash));
|
||||||
public static string CreditCard => Text(nameof(CreditCard));
|
public static string CreditCard => Text(nameof(CreditCard));
|
||||||
public static string DebitCard => Text(nameof(DebitCard));
|
public static string DebitCard => Text(nameof(DebitCard));
|
||||||
@ -29,9 +39,15 @@ namespace Billing.Languages
|
|||||||
public static string AmountRequired => Text(nameof(AmountRequired));
|
public static string AmountRequired => Text(nameof(AmountRequired));
|
||||||
public static string Income => Text(nameof(Income));
|
public static string Income => Text(nameof(Income));
|
||||||
public static string Spending => Text(nameof(Spending));
|
public static string Spending => Text(nameof(Spending));
|
||||||
|
public static string LastSelected => Text(nameof(LastSelected));
|
||||||
|
public static string Recent => Text(nameof(Recent));
|
||||||
public static string CategoryManage => Text(nameof(CategoryManage));
|
public static string CategoryManage => Text(nameof(CategoryManage));
|
||||||
public static string AddCategory => Text(nameof(AddCategory));
|
public static string AddCategory => Text(nameof(AddCategory));
|
||||||
public static string ConfirmDeleteCategory => Text(nameof(ConfirmDeleteCategory));
|
public static string ConfirmDeleteCategory => Text(nameof(ConfirmDeleteCategory));
|
||||||
|
public static string ShareLogs => Text(nameof(ShareLogs));
|
||||||
|
public static string ManyRecords => Text(nameof(ManyRecords));
|
||||||
|
public static string SendEmail => Text(nameof(SendEmail));
|
||||||
|
public static string HowToShareDiagnostic => Text(nameof(HowToShareDiagnostic));
|
||||||
|
|
||||||
#region Categories
|
#region Categories
|
||||||
public static string Clothing => Text(nameof(Clothing));
|
public static string Clothing => Text(nameof(Clothing));
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<root>
|
<root>
|
||||||
<Ok>OK</Ok>
|
<Ok>OK</Ok>
|
||||||
|
<Cancel>Cancel</Cancel>
|
||||||
<About>About</About>
|
<About>About</About>
|
||||||
<Version>Version</Version>
|
<Version>Version</Version>
|
||||||
<Preference>Preference</Preference>
|
<Preference>Preference</Preference>
|
||||||
@ -19,7 +20,19 @@
|
|||||||
<NoRecords>Bills not yet generated</NoRecords>
|
<NoRecords>Bills not yet generated</NoRecords>
|
||||||
<TapToMemo>Click here to record</TapToMemo>
|
<TapToMemo>Click here to record</TapToMemo>
|
||||||
<TitleDateFormat>MM/dd/yyyy</TitleDateFormat>
|
<TitleDateFormat>MM/dd/yyyy</TitleDateFormat>
|
||||||
|
<TitleShortDateFormat>MM/dd/yyyy</TitleShortDateFormat>
|
||||||
<DateRangeFormat>MM/dd</DateRangeFormat>
|
<DateRangeFormat>MM/dd</DateRangeFormat>
|
||||||
|
<To>To</To>
|
||||||
|
<Type>Type</Type>
|
||||||
|
<Preset>Preset</Preset>
|
||||||
|
<Custom>Custom</Custom>
|
||||||
|
<Monthly>Monthly</Monthly>
|
||||||
|
<Today>Today</Today>
|
||||||
|
<PastMonth>Past Month</PastMonth>
|
||||||
|
<PastQuarter>Past Quarter</PastQuarter>
|
||||||
|
<PastSixMonths>Past Six Months</PastSixMonths>
|
||||||
|
<PastYear>Past Year</PastYear>
|
||||||
|
<Total>Total</Total>
|
||||||
<Balance>Balance</Balance>
|
<Balance>Balance</Balance>
|
||||||
<Assets>Assets</Assets>
|
<Assets>Assets</Assets>
|
||||||
<Liability>Liability</Liability>
|
<Liability>Liability</Liability>
|
||||||
@ -52,6 +65,8 @@
|
|||||||
<AmountRequired>Please enter the amount.</AmountRequired>
|
<AmountRequired>Please enter the amount.</AmountRequired>
|
||||||
<Income>Income</Income>
|
<Income>Income</Income>
|
||||||
<Spending>Spending</Spending>
|
<Spending>Spending</Spending>
|
||||||
|
<LastSelected>Last Selected</LastSelected>
|
||||||
|
<Recent>Recent</Recent>
|
||||||
<Clothing>Clothing</Clothing>
|
<Clothing>Clothing</Clothing>
|
||||||
<Food>Food</Food>
|
<Food>Food</Food>
|
||||||
<Drinks>Drinks</Drinks>
|
<Drinks>Drinks</Drinks>
|
||||||
@ -86,6 +101,7 @@
|
|||||||
<Feature>Feature</Feature>
|
<Feature>Feature</Feature>
|
||||||
<CategoryManage>Category Management</CategoryManage>
|
<CategoryManage>Category Management</CategoryManage>
|
||||||
<Detail>Detail</Detail>
|
<Detail>Detail</Detail>
|
||||||
|
<SaveLocation>Save Location</SaveLocation>
|
||||||
<AddCategory>Add Category</AddCategory>
|
<AddCategory>Add Category</AddCategory>
|
||||||
<ConfirmDeleteCategory>Are you sure you want to delete the category: {0}?</ConfirmDeleteCategory>
|
<ConfirmDeleteCategory>Are you sure you want to delete the category: {0}?</ConfirmDeleteCategory>
|
||||||
<SelectCategory>Select Category</SelectCategory>
|
<SelectCategory>Select Category</SelectCategory>
|
||||||
@ -93,4 +109,10 @@
|
|||||||
<NoResult>(no results)</NoResult>
|
<NoResult>(no results)</NoResult>
|
||||||
<Top10>Top 10</Top10>
|
<Top10>Top 10</Top10>
|
||||||
<CategoryRank>Category Ranking</CategoryRank>
|
<CategoryRank>Category Ranking</CategoryRank>
|
||||||
|
<Diagnostic>Diagnostic</Diagnostic>
|
||||||
|
<ShareLogs>Share Logs</ShareLogs>
|
||||||
|
<ManyRecords>{0} record(s)</ManyRecords>
|
||||||
|
<SendEmail>Send Eamil</SendEmail>
|
||||||
|
<HowToShareDiagnostic>How would you like to share diagnostic logs?</HowToShareDiagnostic>
|
||||||
|
<ViewLocation>View Location</ViewLocation>
|
||||||
</root>
|
</root>
|
@ -1,6 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<root>
|
<root>
|
||||||
<Ok>确定</Ok>
|
<Ok>确定</Ok>
|
||||||
|
<Cancel>取消</Cancel>
|
||||||
<About>关于</About>
|
<About>关于</About>
|
||||||
<Version>版本号</Version>
|
<Version>版本号</Version>
|
||||||
<Preference>偏好</Preference>
|
<Preference>偏好</Preference>
|
||||||
@ -19,7 +20,19 @@
|
|||||||
<NoRecords>还未产生账单</NoRecords>
|
<NoRecords>还未产生账单</NoRecords>
|
||||||
<TapToMemo>点此记录</TapToMemo>
|
<TapToMemo>点此记录</TapToMemo>
|
||||||
<TitleDateFormat>yyyy年MM月dd日</TitleDateFormat>
|
<TitleDateFormat>yyyy年MM月dd日</TitleDateFormat>
|
||||||
|
<TitleShortDateFormat>yyyy/MM/dd</TitleShortDateFormat>
|
||||||
<DateRangeFormat>MM月dd日</DateRangeFormat>
|
<DateRangeFormat>MM月dd日</DateRangeFormat>
|
||||||
|
<To>至</To>
|
||||||
|
<Type>类型</Type>
|
||||||
|
<Preset>预设</Preset>
|
||||||
|
<Custom>自定义</Custom>
|
||||||
|
<Monthly>当月</Monthly>
|
||||||
|
<Today>今日</Today>
|
||||||
|
<PastMonth>一个月</PastMonth>
|
||||||
|
<PastQuarter>一个季度</PastQuarter>
|
||||||
|
<PastSixMonths>六个月</PastSixMonths>
|
||||||
|
<PastYear>一年</PastYear>
|
||||||
|
<Total>全部</Total>
|
||||||
<Balance>余额</Balance>
|
<Balance>余额</Balance>
|
||||||
<Assets>资产</Assets>
|
<Assets>资产</Assets>
|
||||||
<Liability>负债</Liability>
|
<Liability>负债</Liability>
|
||||||
@ -52,6 +65,8 @@
|
|||||||
<AmountRequired>请输入金额。</AmountRequired>
|
<AmountRequired>请输入金额。</AmountRequired>
|
||||||
<Income>收入</Income>
|
<Income>收入</Income>
|
||||||
<Spending>支出</Spending>
|
<Spending>支出</Spending>
|
||||||
|
<LastSelected>最后选择</LastSelected>
|
||||||
|
<Recent>最近</Recent>
|
||||||
<Clothing>衣物</Clothing>
|
<Clothing>衣物</Clothing>
|
||||||
<Food>食品</Food>
|
<Food>食品</Food>
|
||||||
<Drinks>饮料</Drinks>
|
<Drinks>饮料</Drinks>
|
||||||
@ -86,6 +101,7 @@
|
|||||||
<Feature>功能</Feature>
|
<Feature>功能</Feature>
|
||||||
<CategoryManage>分类管理</CategoryManage>
|
<CategoryManage>分类管理</CategoryManage>
|
||||||
<Detail>详细</Detail>
|
<Detail>详细</Detail>
|
||||||
|
<SaveLocation>保存位置</SaveLocation>
|
||||||
<AddCategory>新建分类</AddCategory>
|
<AddCategory>新建分类</AddCategory>
|
||||||
<ConfirmDeleteCategory>是否确认删除该分类:{0}?</ConfirmDeleteCategory>
|
<ConfirmDeleteCategory>是否确认删除该分类:{0}?</ConfirmDeleteCategory>
|
||||||
<SelectCategory>选择类别</SelectCategory>
|
<SelectCategory>选择类别</SelectCategory>
|
||||||
@ -93,4 +109,10 @@
|
|||||||
<NoResult>(无记录)</NoResult>
|
<NoResult>(无记录)</NoResult>
|
||||||
<Top10>Top 10</Top10>
|
<Top10>Top 10</Top10>
|
||||||
<CategoryRank>分类排行</CategoryRank>
|
<CategoryRank>分类排行</CategoryRank>
|
||||||
|
<Diagnostic>诊断</Diagnostic>
|
||||||
|
<ShareLogs>发送日志</ShareLogs>
|
||||||
|
<ManyRecords>{0} 条记录</ManyRecords>
|
||||||
|
<SendEmail>发送邮件</SendEmail>
|
||||||
|
<HowToShareDiagnostic>您想以哪种方式分享诊断日志?</HowToShareDiagnostic>
|
||||||
|
<ViewLocation>查看位置</ViewLocation>
|
||||||
</root>
|
</root>
|
88
Billing.Shared/LocationExtension.cs
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Billing
|
||||||
|
{
|
||||||
|
public static class LocationExtension
|
||||||
|
{
|
||||||
|
private const double PI = 3.1415926535897931;
|
||||||
|
private const double A = 6378245d;
|
||||||
|
private const double EE = 0.00669342162296594323;
|
||||||
|
|
||||||
|
public static (double longitude, double latitude) Wgs84ToGcj02(this (double lon, double lat) position)
|
||||||
|
{
|
||||||
|
return Transform(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static (double longitude, double latitude) Gcj02ToWgs84(this (double lon, double lat) position)
|
||||||
|
{
|
||||||
|
(double longitude, double latitude) = Transform(position);
|
||||||
|
longitude = position.lon * 2d - longitude;
|
||||||
|
latitude = position.lat * 2d - latitude;
|
||||||
|
return (longitude, latitude);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static (double longitude, double latitude) Gcj02ToBd09(this (double lon, double lat) position)
|
||||||
|
{
|
||||||
|
double x = position.lon;
|
||||||
|
double y = position.lat;
|
||||||
|
double z = Math.Sqrt(x * x + y * y) + 0.00002 * Math.Sin(y * PI);
|
||||||
|
double theta = Math.Atan2(y, x) + 0.000003 * Math.Cos(x * PI);
|
||||||
|
double longitude = z * Math.Cos(theta) + 0.0065;
|
||||||
|
double latitude = z * Math.Sin(theta) + 0.006;
|
||||||
|
return (longitude, latitude);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static (double longitude, double latitude) Bd09ToGcj02(this (double lon, double lat) position)
|
||||||
|
{
|
||||||
|
double x = position.lon - 0.0065;
|
||||||
|
double y = position.lat - 0.006;
|
||||||
|
double z = Math.Sqrt(x * x + y * y) - 0.00002 * Math.Sin(y * PI);
|
||||||
|
double theta = Math.Atan2(y, x) - 0.000003 * Math.Cos(x * PI);
|
||||||
|
double longitude = z * Math.Cos(theta);
|
||||||
|
double latitude = z * Math.Sin(theta);
|
||||||
|
return (longitude, latitude);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static (double longitude, double latitude) Transform(this (double lon, double lat) position)
|
||||||
|
{
|
||||||
|
if (IsOutOfChina(position.lon, position.lat))
|
||||||
|
{
|
||||||
|
return position;
|
||||||
|
}
|
||||||
|
|
||||||
|
var offsetLatitude = TransformLatitude(position.lon - 105d, position.lat - 35d);
|
||||||
|
var offsetLongitude = TransformLongitude(position.lon - 105d, position.lat - 35d);
|
||||||
|
var radiusLatitude = position.lat / 180d * PI;
|
||||||
|
var magic = 1d - EE * Math.Pow(Math.Sin(radiusLatitude), 2);
|
||||||
|
var sqrtMagic = Math.Sqrt(magic);
|
||||||
|
offsetLatitude = offsetLatitude * 180d / (A * (1d - EE) / (magic * sqrtMagic) * PI);
|
||||||
|
offsetLongitude = offsetLongitude * 180d / (A / sqrtMagic * Math.Cos(radiusLatitude) * PI);
|
||||||
|
return (position.lon + offsetLongitude, position.lat + offsetLatitude);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsOutOfChina(double lon, double lat)
|
||||||
|
{
|
||||||
|
return lon < 72.004 || lon > 137.8347
|
||||||
|
|| lat < 0.8293 || lat > 55.8171;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static double TransformLatitude(double x, double y)
|
||||||
|
{
|
||||||
|
var ret = -100d + 2d * x + 3d * y + 0.2 * y * y + 0.1 * x * y + 0.2 * Math.Sqrt(Math.Abs(x));
|
||||||
|
ret += (20d * Math.Sin(6d * x * PI) + 20d * Math.Sin(2d * x * PI)) * 2d / 3d;
|
||||||
|
ret += (20d * Math.Sin(y * PI) + 40d * Math.Sin(y / 3d * PI)) * 2d / 3d;
|
||||||
|
ret += (160d * Math.Sin(y / 12d * PI) + 320d * Math.Sin(y * PI / 30d)) * 2d / 3d;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static double TransformLongitude(double x, double y)
|
||||||
|
{
|
||||||
|
var ret = 300d + x + 2d * y + 0.1 * x * x + 0.1 * x * y + 0.1 * Math.Sqrt(Math.Abs(x));
|
||||||
|
ret += (20d * Math.Sin(6d * x * PI) + 20d * Math.Sin(2d * x * PI)) * 2d / 3d;
|
||||||
|
ret += (20d * Math.Sin(x * PI) + 40d * Math.Sin(x / 3d * PI)) * 2d / 3d;
|
||||||
|
ret += (150d * Math.Sin(x / 12d * PI) + 300d * Math.Sin(x / 30d * PI)) * 2d / 3d;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="utf-8" ?>
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
<Shell xmlns="http://xamarin.com/schemas/2014/forms"
|
<Shell xmlns="http://xamarin.com/schemas/2014/forms"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||||
|
xmlns:local="clr-namespace:Billing"
|
||||||
xmlns:v="clr-namespace:Billing.Views"
|
xmlns:v="clr-namespace:Billing.Views"
|
||||||
xmlns:r="clr-namespace:Billing.Languages"
|
xmlns:r="clr-namespace:Billing.Languages"
|
||||||
x:Class="Billing.MainShell"
|
x:Class="Billing.MainShell"
|
||||||
@ -9,11 +10,14 @@
|
|||||||
TitleColor="{DynamicResource PrimaryColor}"
|
TitleColor="{DynamicResource PrimaryColor}"
|
||||||
Shell.NavBarHasShadow="True">
|
Shell.NavBarHasShadow="True">
|
||||||
|
|
||||||
|
<FlyoutItem>
|
||||||
|
<ShellContent ContentTemplate="{DataTemplate local:SplashPage}" Route="Splash"/>
|
||||||
|
</FlyoutItem>
|
||||||
|
|
||||||
<TabBar>
|
<TabBar>
|
||||||
<ShellContent ContentTemplate="{DataTemplate v:AccountPage}" Route="Accounts" Title="{r:Text Accounts}" Icon="wallet.png"/>
|
<ShellContent ContentTemplate="{DataTemplate v:AccountPage}" Route="Accounts" Title="{r:Text Accounts}" Icon="wallet.png"/>
|
||||||
<ShellContent ContentTemplate="{DataTemplate v:BillPage}" Route="Bills" Title="{r:Text Bills}" Icon="bill.png"/>
|
<ShellContent ContentTemplate="{DataTemplate v:BillPage}" Route="Bills" Title="{r:Text Bills}" Icon="bill.png"/>
|
||||||
<ShellContent ContentTemplate="{DataTemplate v:RankPage}" Route="Ranks" Title="{r:Text Report}" Icon="rank.png"/>
|
<ShellContent ContentTemplate="{DataTemplate v:RankPage}" Route="Ranks" Title="{r:Text Report}" Icon="rank.png"/>
|
||||||
<ShellContent ContentTemplate="{DataTemplate v:SettingPage}" Route="Settings" Title="{r:Text Settings}" Icon="settings.png"/>
|
<ShellContent ContentTemplate="{DataTemplate v:SettingPage}" Route="Settings" Title="{r:Text Settings}" Icon="settings.png"/>
|
||||||
</TabBar>
|
</TabBar>
|
||||||
|
|
||||||
</Shell>
|
</Shell>
|
@ -1,3 +1,4 @@
|
|||||||
|
using Billing.Views;
|
||||||
using Xamarin.Forms;
|
using Xamarin.Forms;
|
||||||
|
|
||||||
namespace Billing
|
namespace Billing
|
||||||
@ -6,6 +7,8 @@ namespace Billing
|
|||||||
{
|
{
|
||||||
public MainShell()
|
public MainShell()
|
||||||
{
|
{
|
||||||
|
Routing.RegisterRoute("Bills/Details", typeof(AddBillPage));
|
||||||
|
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,15 @@
|
|||||||
using System.Xml.Linq;
|
using SQLite;
|
||||||
|
|
||||||
namespace Billing.Models
|
namespace Billing.Models
|
||||||
{
|
{
|
||||||
public class Account : BaseModel
|
public class Account : IIdItem
|
||||||
{
|
{
|
||||||
|
private const string ICON_DEFAULT = "ic_default";
|
||||||
|
|
||||||
|
private static Account empty;
|
||||||
|
public static Account Empty => empty ??= new() { Id = -1 };
|
||||||
|
|
||||||
|
[PrimaryKey, AutoIncrement]
|
||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
public string Icon { get; set; } = ICON_DEFAULT;
|
public string Icon { get; set; } = ICON_DEFAULT;
|
||||||
public AccountCategory Category { get; set; }
|
public AccountCategory Category { get; set; }
|
||||||
@ -11,28 +17,6 @@ namespace Billing.Models
|
|||||||
public decimal Initial { get; set; }
|
public decimal Initial { get; set; }
|
||||||
public decimal Balance { get; set; }
|
public decimal Balance { get; set; }
|
||||||
public string Memo { get; set; }
|
public string Memo { get; set; }
|
||||||
|
|
||||||
public override void OnXmlDeserialize(XElement node)
|
|
||||||
{
|
|
||||||
Id = Read(node, nameof(Id), 0);
|
|
||||||
Icon = Read(node, nameof(Icon), ICON_DEFAULT);
|
|
||||||
Category = (AccountCategory)Read(node, nameof(Category), 0);
|
|
||||||
Name = Read(node, nameof(Name), string.Empty);
|
|
||||||
Initial = Read(node, nameof(Initial), 0m);
|
|
||||||
Balance = Read(node, nameof(Balance), 0m);
|
|
||||||
Memo = Read(node, nameof(Memo), null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void OnXmlSerialize(XElement node)
|
|
||||||
{
|
|
||||||
Write(node, nameof(Id), Id);
|
|
||||||
Write(node, nameof(Icon), Icon);
|
|
||||||
Write(node, nameof(Category), (int)Category);
|
|
||||||
Write(node, nameof(Name), Name);
|
|
||||||
Write(node, nameof(Initial), Initial);
|
|
||||||
Write(node, nameof(Balance), Balance);
|
|
||||||
Write(node, nameof(Memo), Memo);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum AccountCategory
|
public enum AccountCategory
|
||||||
|
@ -1,186 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Globalization;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Xml.Linq;
|
|
||||||
|
|
||||||
namespace Billing.Models
|
|
||||||
{
|
|
||||||
public interface IModel
|
|
||||||
{
|
|
||||||
void OnXmlSerialize(XElement node);
|
|
||||||
|
|
||||||
void OnXmlDeserialize(XElement node);
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract class BaseModel : IModel, IDisposable
|
|
||||||
{
|
|
||||||
public const string ICON_DEFAULT = "ic_default";
|
|
||||||
|
|
||||||
private bool disposed = false;
|
|
||||||
|
|
||||||
public static T ParseXml<T>(string xml) where T : BaseModel, new()
|
|
||||||
{
|
|
||||||
XDocument doc = XDocument.Parse(xml);
|
|
||||||
T model = new();
|
|
||||||
model.OnXmlDeserialize(doc.Root);
|
|
||||||
return model;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static string ToString(IFormattable v) => v.ToString(null, CultureInfo.InvariantCulture);
|
|
||||||
protected static double ParseDouble(string s) => double.Parse(s, CultureInfo.InvariantCulture);
|
|
||||||
protected static decimal ParseDecimal(string s) => decimal.Parse(s, CultureInfo.InvariantCulture);
|
|
||||||
protected static int ParseInt(string s) => int.Parse(s, CultureInfo.InvariantCulture);
|
|
||||||
protected static long ParseLong(string s) => long.Parse(s, CultureInfo.InvariantCulture);
|
|
||||||
|
|
||||||
protected static bool IsTrue(string s) =>
|
|
||||||
string.Equals(s, "true", StringComparison.OrdinalIgnoreCase) ||
|
|
||||||
string.Equals(s, "yes", StringComparison.OrdinalIgnoreCase) ||
|
|
||||||
s == "1";
|
|
||||||
|
|
||||||
#region Private Methods
|
|
||||||
|
|
||||||
private static XElement WriteString(XElement parent, string name, string val, Action<XElement> action = null)
|
|
||||||
{
|
|
||||||
XElement ele;
|
|
||||||
if (val == null)
|
|
||||||
{
|
|
||||||
ele = new XElement(name);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ele = new XElement(name, val);
|
|
||||||
}
|
|
||||||
action?.Invoke(ele);
|
|
||||||
parent.Add(ele);
|
|
||||||
return ele;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static T ReadSubnode<T>(XElement node, string subname, Func<XElement, T> func)
|
|
||||||
{
|
|
||||||
var ele = node.Elements().FirstOrDefault(e => string.Equals(e.Name.ToString(), subname, StringComparison.OrdinalIgnoreCase));
|
|
||||||
return func(ele);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static T ReadObject<T>(XElement node) where T : IModel, new()
|
|
||||||
{
|
|
||||||
if (IsTrue(node.Attribute("null")?.Value))
|
|
||||||
{
|
|
||||||
return default;
|
|
||||||
}
|
|
||||||
T value = new();
|
|
||||||
value.OnXmlDeserialize(node);
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static T[] ReadArray<T>(XElement node) where T : IModel, new()
|
|
||||||
{
|
|
||||||
if (IsTrue(node.Attribute("null")?.Value))
|
|
||||||
{
|
|
||||||
return default;
|
|
||||||
}
|
|
||||||
int count = ParseInt(node.Attribute("count").Value);
|
|
||||||
T[] array = new T[count];
|
|
||||||
foreach (XElement ele in node.Elements("item"))
|
|
||||||
{
|
|
||||||
int index = ParseInt(ele.Attribute("index").Value);
|
|
||||||
array[index] = ReadObject<T>(ele);
|
|
||||||
}
|
|
||||||
return array;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Read/Write Helper
|
|
||||||
|
|
||||||
protected static XElement Write(XElement parent, string name, string val) => WriteString(parent, name, val);
|
|
||||||
protected static XElement Write(XElement parent, string name, DateTime date) => WriteString(parent, name, ToString(date.Ticks));
|
|
||||||
protected static XElement Write<T>(XElement parent, string name, T value) where T : IFormattable => WriteString(parent, name, ToString(value));
|
|
||||||
protected static XElement WriteObject<T>(XElement parent, string name, T value) where T : IModel => WriteString(parent, name, null,
|
|
||||||
action: ele =>
|
|
||||||
{
|
|
||||||
if (value == null)
|
|
||||||
{
|
|
||||||
ele.Add(new XAttribute("null", 1));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
value.OnXmlSerialize(ele);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
protected static XElement WriteArray<T>(XElement parent, string name, T[] value) where T : IModel => WriteString(parent, name, null,
|
|
||||||
action: ele =>
|
|
||||||
{
|
|
||||||
if (value == null)
|
|
||||||
{
|
|
||||||
ele.Add(new XAttribute("null", 1));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ele.Add(new XAttribute("count", value.Length));
|
|
||||||
for (var i = 0; i < value.Length; i++)
|
|
||||||
{
|
|
||||||
XElement item = WriteObject(ele, "item", value[i]);
|
|
||||||
item.Add(new XAttribute("index", i));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
protected static string Read(XElement node, string subname, string def) => ReadSubnode(node, subname, e => e?.Value ?? def);
|
|
||||||
protected static int Read(XElement node, string subname, int def) => ReadSubnode(node, subname, e => e == null ? def : ParseInt(e.Value));
|
|
||||||
protected static long Read(XElement node, string subname, long def) => ReadSubnode(node, subname, e => e == null ? def : ParseLong(e.Value));
|
|
||||||
protected static double Read(XElement node, string subname, double def) => ReadSubnode(node, subname, e => e == null ? def : ParseDouble(e.Value));
|
|
||||||
protected static decimal Read(XElement node, string subname, decimal def) => ReadSubnode(node, subname, e => e == null ? def : ParseDecimal(e.Value));
|
|
||||||
protected static DateTime Read(XElement node, string subname, DateTime def) => ReadSubnode(node, subname, e => e == null ? def : new DateTime(ParseLong(e.Value)));
|
|
||||||
protected static T ReadObject<T>(XElement node, string subname, T def = default) where T : IModel, new() => ReadSubnode(node, subname, e => e == null ? def : ReadObject<T>(e));
|
|
||||||
protected static T[] ReadArray<T>(XElement node, string subname, T[] def = null) where T : IModel, new() => ReadSubnode(node, subname, e => e == null ? def : ReadArray<T>(e));
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
public XDocument ToXml()
|
|
||||||
{
|
|
||||||
XDocument xdoc = new(new XDeclaration("1.0", "utf-8", "yes"), new XElement("root"));
|
|
||||||
ToXml(xdoc.Root);
|
|
||||||
return xdoc;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ToXml(XElement node)
|
|
||||||
{
|
|
||||||
OnXmlSerialize(node);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SaveToStream(Stream stream)
|
|
||||||
{
|
|
||||||
ToXml().Save(stream, SaveOptions.DisableFormatting);
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract void OnXmlSerialize(XElement node);
|
|
||||||
public abstract void OnXmlDeserialize(XElement node);
|
|
||||||
|
|
||||||
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
using MemoryStream ms = new();
|
|
||||||
//using StreamWriter writer = new(ms, Encoding.UTF8);
|
|
||||||
//XDocument xdoc = ToXml();
|
|
||||||
//xdoc.Save(writer, SaveOptions.DisableFormatting);
|
|
||||||
//writer.Flush();
|
|
||||||
SaveToStream(ms);
|
|
||||||
ms.Seek(0, SeekOrigin.Begin);
|
|
||||||
using StreamReader reader = new(ms, Encoding.UTF8);
|
|
||||||
return reader.ReadToEnd();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected virtual void Dispose(bool dispose) { }
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
if (!disposed)
|
|
||||||
{
|
|
||||||
Dispose(true);
|
|
||||||
disposed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,10 +1,11 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Xml.Linq;
|
using SQLite;
|
||||||
|
|
||||||
namespace Billing.Models
|
namespace Billing.Models
|
||||||
{
|
{
|
||||||
public class Bill : BaseModel
|
public class Bill : IIdItem
|
||||||
{
|
{
|
||||||
|
[PrimaryKey, AutoIncrement]
|
||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
public decimal Amount { get; set; }
|
public decimal Amount { get; set; }
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
@ -13,29 +14,10 @@ namespace Billing.Models
|
|||||||
public string Store { get; set; }
|
public string Store { get; set; }
|
||||||
public DateTime CreateTime { get; set; }
|
public DateTime CreateTime { get; set; }
|
||||||
public string Note { get; set; }
|
public string Note { get; set; }
|
||||||
|
public double? Latitude { get; set; }
|
||||||
public override void OnXmlDeserialize(XElement node)
|
public double? Longitude { get; set; }
|
||||||
{
|
public double? Altitude { get; set; }
|
||||||
Id = Read(node, nameof(Id), 0);
|
public double? Accuracy { get; set; }
|
||||||
Amount = Read(node, nameof(Amount), 0m);
|
public bool? IsGps { get; set; }
|
||||||
Name = Read(node, nameof(Name), string.Empty);
|
|
||||||
CategoryId = Read(node, nameof(CategoryId), -1);
|
|
||||||
WalletId = Read(node, nameof(WalletId), -1);
|
|
||||||
Store = Read(node, nameof(Store), string.Empty);
|
|
||||||
CreateTime = Read(node, nameof(CreateTime), default(DateTime));
|
|
||||||
Note = Read(node, nameof(Note), string.Empty);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void OnXmlSerialize(XElement node)
|
|
||||||
{
|
|
||||||
Write(node, nameof(Id), Id);
|
|
||||||
Write(node, nameof(Amount), Amount);
|
|
||||||
Write(node, nameof(Name), Name);
|
|
||||||
Write(node, nameof(CategoryId), CategoryId);
|
|
||||||
Write(node, nameof(WalletId), WalletId);
|
|
||||||
Write(node, nameof(Store), Store);
|
|
||||||
Write(node, nameof(CreateTime), CreateTime);
|
|
||||||
Write(node, nameof(Note), Note);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,50 +1,25 @@
|
|||||||
using Xamarin.Forms;
|
using SQLite;
|
||||||
using System.Xml.Linq;
|
using System;
|
||||||
|
|
||||||
namespace Billing.Models
|
namespace Billing.Models
|
||||||
{
|
{
|
||||||
public class Category : BaseModel
|
public class Category : IIdItem
|
||||||
{
|
{
|
||||||
|
private const string ICON_DEFAULT = "ic_default";
|
||||||
|
private const long TRANSPARENT_COLOR = 0x00ffffffL;
|
||||||
|
|
||||||
|
private static Category empty;
|
||||||
|
public static Category Empty => empty ??= new() { Id = -1 };
|
||||||
|
|
||||||
|
[PrimaryKey, AutoIncrement]
|
||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
public CategoryType Type { get; set; }
|
public CategoryType Type { get; set; }
|
||||||
public string Icon { get; set; } = ICON_DEFAULT;
|
public string Icon { get; set; } = ICON_DEFAULT;
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
public Color TintColor { get; set; } = Color.Transparent;
|
public long TintColor { get; set; } = TRANSPARENT_COLOR;
|
||||||
public int? ParentId { get; set; }
|
public int? ParentId { get; set; }
|
||||||
|
public DateTime? LastUsed { get; set; }
|
||||||
public override void OnXmlDeserialize(XElement node)
|
public int? LastAccountId { get; set; }
|
||||||
{
|
|
||||||
Id = Read(node, nameof(Id), 0);
|
|
||||||
Type = (CategoryType)Read(node, nameof(Type), 0);
|
|
||||||
Icon = Read(node, nameof(Icon), ICON_DEFAULT);
|
|
||||||
Name = Read(node, nameof(Name), string.Empty);
|
|
||||||
var color = Read(node, nameof(TintColor), string.Empty);
|
|
||||||
if (!string.IsNullOrEmpty(color))
|
|
||||||
{
|
|
||||||
TintColor = Color.FromHex(color);
|
|
||||||
}
|
|
||||||
var parentId = Read(node, nameof(ParentId), -1);
|
|
||||||
if (parentId >= 0)
|
|
||||||
{
|
|
||||||
ParentId = parentId;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void OnXmlSerialize(XElement node)
|
|
||||||
{
|
|
||||||
Write(node, nameof(Id), Id);
|
|
||||||
Write(node, nameof(Type), (int)Type);
|
|
||||||
Write(node, nameof(Icon), Icon);
|
|
||||||
Write(node, nameof(Name), Name);
|
|
||||||
if (TintColor != Color.Transparent)
|
|
||||||
{
|
|
||||||
Write(node, nameof(TintColor), TintColor.ToHex());
|
|
||||||
}
|
|
||||||
if (ParentId != null)
|
|
||||||
{
|
|
||||||
Write(node, nameof(ParentId), ParentId.Value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum CategoryType
|
public enum CategoryType
|
||||||
|
7
Billing.Shared/Models/IIdItem.cs
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
namespace Billing.Models
|
||||||
|
{
|
||||||
|
public interface IIdItem
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
}
|
||||||
|
}
|
14
Billing.Shared/Models/Logs.cs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
using System;
|
||||||
|
using SQLite;
|
||||||
|
|
||||||
|
namespace Billing.Models
|
||||||
|
{
|
||||||
|
public class Logs : IIdItem
|
||||||
|
{
|
||||||
|
[PrimaryKey, AutoIncrement]
|
||||||
|
public int Id { get; set; }
|
||||||
|
public DateTime LogTime { get; set; }
|
||||||
|
public string Category { get; set; }
|
||||||
|
public string Detail { get; set; }
|
||||||
|
}
|
||||||
|
}
|
9
Billing.Shared/SplashPage.xaml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<ui:BillingPage xmlns="http://xamarin.com/schemas/2014/forms"
|
||||||
|
xmlns:ui="clr-namespace:Billing.UI"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||||
|
x:Class="Billing.SplashPage"
|
||||||
|
Shell.NavBarIsVisible="False"
|
||||||
|
Shell.TabBarIsVisible="False">
|
||||||
|
<!--<Label Text="Loading..." HorizontalOptions="Center" VerticalOptions="Center"/>-->
|
||||||
|
</ui:BillingPage>
|
23
Billing.Shared/SplashPage.xaml.cs
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
using Billing.UI;
|
||||||
|
using Xamarin.Forms;
|
||||||
|
|
||||||
|
namespace Billing
|
||||||
|
{
|
||||||
|
public partial class SplashPage : BillingPage
|
||||||
|
{
|
||||||
|
public SplashPage()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async void OnLoaded()
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(App.MainRoute))
|
||||||
|
{
|
||||||
|
await App.InitializeData();
|
||||||
|
|
||||||
|
await Shell.Current.GoToAsync(App.MainRoute);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -3,7 +3,7 @@ using System.Collections.Generic;
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Billing.Models;
|
using Billing.Models;
|
||||||
using Billing.UI;
|
using SQLite;
|
||||||
using Xamarin.Essentials;
|
using Xamarin.Essentials;
|
||||||
using Resource = Billing.Languages.Resource;
|
using Resource = Billing.Languages.Resource;
|
||||||
|
|
||||||
@ -14,130 +14,309 @@ namespace Billing.Store
|
|||||||
public static readonly string PersonalFolder = Environment.GetFolderPath(Environment.SpecialFolder.Personal);
|
public static readonly string PersonalFolder = Environment.GetFolderPath(Environment.SpecialFolder.Personal);
|
||||||
public static readonly string CacheFolder = FileSystem.CacheDirectory;
|
public static readonly string CacheFolder = FileSystem.CacheDirectory;
|
||||||
|
|
||||||
private const string accountFile = "accounts.xml";
|
public static string DatabasePath => Path.Combine(PersonalFolder, dbfile);
|
||||||
private const string billFile = "bills.xml";
|
|
||||||
private const string categoryFile = "categories.xml";
|
|
||||||
|
|
||||||
private static StoreHelper instance;
|
#region Sqlite3
|
||||||
private static StoreHelper Instance => instance ??= new StoreHelper();
|
private const string dbfile = ".master.db3";
|
||||||
|
private static SQLiteAsyncConnection database;
|
||||||
|
#endregion
|
||||||
|
|
||||||
public static List<Account> GetAccounts() => Instance.GetAccountsInternal();
|
public static async Task<bool> ReloadDatabase(string file)
|
||||||
public static void WriteAccounts(IEnumerable<Account> accounts) => Instance.WriteAccountsInternal(accounts);
|
|
||||||
public static List<Bill> GetBills() => Instance.GetBillsInternal();
|
|
||||||
public static void WriteBills(IEnumerable<Bill> bills) => Instance.WriteBillsInternal(bills);
|
|
||||||
public static List<Category> GetCategories() => Instance.GetCategoriesInternal();
|
|
||||||
public static void WriteCategories(IEnumerable<Category> categories) => Instance.WriteCategoriesInternal(categories);
|
|
||||||
|
|
||||||
private StoreHelper() { }
|
|
||||||
|
|
||||||
private List<Account> GetAccountsInternal()
|
|
||||||
{
|
{
|
||||||
return GetList<Account>(Path.Combine(PersonalFolder, accountFile));
|
var path = DatabasePath;
|
||||||
}
|
if (string.Equals(file, path, StringComparison.OrdinalIgnoreCase))
|
||||||
|
|
||||||
private void WriteAccountsInternal(IEnumerable<Account> accounts)
|
|
||||||
{
|
|
||||||
var filename = Path.Combine(PersonalFolder, accountFile);
|
|
||||||
WriteList(filename, accounts);
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<Bill> GetBillsInternal()
|
|
||||||
{
|
|
||||||
return GetList<Bill>(Path.Combine(PersonalFolder, billFile));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void WriteBillsInternal(IEnumerable<Bill> bills)
|
|
||||||
{
|
|
||||||
var filename = Path.Combine(PersonalFolder, billFile);
|
|
||||||
WriteList(filename, bills);
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<Category> GetCategoriesInternal()
|
|
||||||
{
|
|
||||||
var list = GetList<Category>(Path.Combine(PersonalFolder, categoryFile));
|
|
||||||
if (list == null || list.Count == 0)
|
|
||||||
{
|
{
|
||||||
list = new List<Category>
|
return false;
|
||||||
{
|
|
||||||
// sample categories
|
|
||||||
new() { Id = 1, Name = Resource.Clothing, Icon = "clothes" },
|
|
||||||
new() { Id = 2, Name = Resource.Food, Icon = "food" },
|
|
||||||
new() { Id = 4, Name = Resource.Daily, Icon = "daily" },
|
|
||||||
new() { Id = 5, Name = Resource.Trans, Icon = "trans" },
|
|
||||||
new() { Id = 6, Name = Resource.Entertainment, Icon = "face" },
|
|
||||||
new() { Id = 7, Name = Resource.Learn, Icon = "learn" },
|
|
||||||
new() { Id = 8, Name = Resource.Medical, Icon = "medical" },
|
|
||||||
new() { Id = 9, Name = Resource.OtherSpending, Icon = "plus" },
|
|
||||||
|
|
||||||
new() { Id = 10, Type = CategoryType.Income, Name = Resource.Earnings, Icon = "#brand#btc" },
|
|
||||||
new() { Id = 20, Type = CategoryType.Income, Name = Resource.OtherIncome, Icon = "plus" },
|
|
||||||
|
|
||||||
// sub-categories
|
|
||||||
new() { Id = 100, ParentId = 1, Name = Resource.Jewellery, Icon = "gem" },
|
|
||||||
new() { Id = 101, ParentId = 1, Name = Resource.Cosmetics, Icon = "makeup" },
|
|
||||||
new() { Id = 102, ParentId = 2, Name = Resource.Brunch, Icon = "brunch" },
|
|
||||||
new() { Id = 103, ParentId = 2, Name = Resource.Dinner, Icon = "dinner" },
|
|
||||||
new() { Id = 104, ParentId = 2, Name = Resource.Drinks, Icon = "drink" },
|
|
||||||
new() { Id = 105, ParentId = 2, Name = Resource.Fruit, Icon = "fruit" },
|
|
||||||
new() { Id = 106, ParentId = 4, Name = Resource.UtilityBill, Icon = "bill" },
|
|
||||||
new() { Id = 107, ParentId = 4, Name = Resource.PropertyFee, Icon = "fee" },
|
|
||||||
new() { Id = 108, ParentId = 4, Name = Resource.Rent, Icon = "rent" },
|
|
||||||
new() { Id = 109, ParentId = 4, Name = Resource.Maintenance, Icon = "maintenance" },
|
|
||||||
new() { Id = 110, ParentId = 5, Name = Resource.LightRail, Icon = "rail" },
|
|
||||||
new() { Id = 111, ParentId = 5, Name = Resource.Taxi, Icon = "taxi" },
|
|
||||||
new() { Id = 112, ParentId = 6, Name = Resource.Fitness, Icon = "fitness" },
|
|
||||||
new() { Id = 113, ParentId = 6, Name = Resource.Party, Icon = "party" },
|
|
||||||
new() { Id = 200, ParentId = 10, Type = CategoryType.Income, Name = Resource.Salary, Icon = "#brand#buffer" },
|
|
||||||
new() { Id = 201, ParentId = 10, Type = CategoryType.Income, Name = Resource.Bonus, Icon = "dollar" },
|
|
||||||
};
|
|
||||||
Task.Run(() => WriteCategoriesInternal(list));
|
|
||||||
}
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void WriteCategoriesInternal(IEnumerable<Category> categories)
|
|
||||||
{
|
|
||||||
var filename = Path.Combine(PersonalFolder, categoryFile);
|
|
||||||
WriteList(filename, categories);
|
|
||||||
}
|
|
||||||
|
|
||||||
#region Helper
|
|
||||||
|
|
||||||
private void WriteList<T>(string filename, IEnumerable<T> list) where T : IModel, new()
|
|
||||||
{
|
|
||||||
if (list == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using var stream = File.Open(filename, FileMode.Create);
|
if (database != null)
|
||||||
list.ToStream(stream);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Helper.Error("file.write", $"failed to write file: {filename}, error: {ex.Message}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<T> GetList<T>(string file) where T : IModel, new()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (File.Exists(file))
|
|
||||||
{
|
{
|
||||||
using var stream = File.OpenRead(file);
|
await database.CloseAsync();
|
||||||
var list = ModelExtensionHelper.FromStream<T>(stream);
|
|
||||||
return list;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Helper.Error("file.read", $"failed to read file: {file}, error: {ex.Message}");
|
Helper.Error("database.close", ex);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
try
|
||||||
|
{
|
||||||
|
File.Copy(file, path, true);
|
||||||
|
File.Delete(file);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Helper.Error("file.import", ex);
|
||||||
|
}
|
||||||
|
try
|
||||||
|
{
|
||||||
|
database = new SQLiteAsyncConnection(path,
|
||||||
|
SQLiteOpenFlags.ReadWrite |
|
||||||
|
SQLiteOpenFlags.Create |
|
||||||
|
SQLiteOpenFlags.SharedCache);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Helper.Error("database.connect", ex);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static readonly AsyncLazy<StoreHelper> Instance = new(async () =>
|
||||||
|
{
|
||||||
|
var instance = new StoreHelper();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await database.CreateTablesAsync<Category, Account, Bill, Logs>();
|
||||||
|
} catch (Exception ex)
|
||||||
|
{
|
||||||
|
Helper.Error("database.init.table", ex);
|
||||||
|
}
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var count = await database.ExecuteScalarAsync<int>("SELECT COUNT(Id) FROM [Account]");
|
||||||
|
if (count <= 0)
|
||||||
|
{
|
||||||
|
await database.InsertAsync(new Account { Name = Resource.Cash, Icon = "wallet" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Helper.Error("database.init.account", ex);
|
||||||
|
}
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var count = await database.ExecuteScalarAsync<int>("SELECT COUNT(Id) FROM [Category]");
|
||||||
|
if (count <= 0)
|
||||||
|
{
|
||||||
|
// init categories
|
||||||
|
await database.InsertAllAsync(new List<Category>
|
||||||
|
{
|
||||||
|
// sample categories
|
||||||
|
new() { Name = Resource.Clothing, Icon = "clothes" },
|
||||||
|
new() { Name = Resource.Food, Icon = "food" },
|
||||||
|
new() { Name = Resource.Daily, Icon = "daily" },
|
||||||
|
new() { Name = Resource.Trans, Icon = "trans" },
|
||||||
|
new() { Name = Resource.Entertainment, Icon = "face" },
|
||||||
|
new() { Name = Resource.Learn, Icon = "learn" },
|
||||||
|
new() { Name = Resource.Medical, Icon = "medical" },
|
||||||
|
new() { Name = Resource.OtherSpending, Icon = "plus" },
|
||||||
|
|
||||||
|
new() { Type = CategoryType.Income, Name = Resource.Earnings, Icon = "#brand#btc" },
|
||||||
|
new() { Type = CategoryType.Income, Name = Resource.OtherIncome, Icon = "plus" },
|
||||||
|
|
||||||
|
// sub-categories
|
||||||
|
new() { ParentId = 1, Name = Resource.Jewellery, Icon = "gem" },
|
||||||
|
new() { ParentId = 1, Name = Resource.Cosmetics, Icon = "makeup" },
|
||||||
|
new() { ParentId = 2, Name = Resource.Brunch, Icon = "brunch" },
|
||||||
|
new() { ParentId = 2, Name = Resource.Dinner, Icon = "dinner" },
|
||||||
|
new() { ParentId = 2, Name = Resource.Drinks, Icon = "drink" },
|
||||||
|
new() { ParentId = 2, Name = Resource.Fruit, Icon = "fruit" },
|
||||||
|
new() { ParentId = 3, Name = Resource.UtilityBill, Icon = "bill" },
|
||||||
|
new() { ParentId = 3, Name = Resource.PropertyFee, Icon = "fee" },
|
||||||
|
new() { ParentId = 3, Name = Resource.Rent, Icon = "rent" },
|
||||||
|
new() { ParentId = 3, Name = Resource.Maintenance, Icon = "maintenance" },
|
||||||
|
new() { ParentId = 4, Name = Resource.LightRail, Icon = "rail" },
|
||||||
|
new() { ParentId = 4, Name = Resource.Taxi, Icon = "taxi" },
|
||||||
|
new() { ParentId = 5, Name = Resource.Fitness, Icon = "fitness" },
|
||||||
|
new() { ParentId = 5, Name = Resource.Party, Icon = "party" },
|
||||||
|
new() { ParentId = 9, Type = CategoryType.Income, Name = Resource.Salary, Icon = "#brand#buffer" },
|
||||||
|
new() { ParentId = 9, Type = CategoryType.Income, Name = Resource.Bonus, Icon = "dollar" },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Helper.Error("database.init.category", ex);
|
||||||
|
}
|
||||||
|
return instance;
|
||||||
|
});
|
||||||
|
|
||||||
|
public static async Task<int> SaveAccountItemAsync(Account account)
|
||||||
|
{
|
||||||
|
var instance = await Instance;
|
||||||
|
return await instance.SaveItemAsync(account);
|
||||||
|
}
|
||||||
|
public static async Task<int> DeleteAccountItemAsync(Account account)
|
||||||
|
{
|
||||||
|
var instance = await Instance;
|
||||||
|
return await instance.DeleteItemAsync(account);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<int> SaveBillItemAsync(Bill bill)
|
||||||
|
{
|
||||||
|
var instance = await Instance;
|
||||||
|
return await instance.SaveItemAsync(bill);
|
||||||
|
}
|
||||||
|
public static async Task<int> DeleteBillItemAsync(Bill bill)
|
||||||
|
{
|
||||||
|
var instance = await Instance;
|
||||||
|
return await instance.DeleteItemAsync(bill);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<int> SaveCategoryItemAsync(Category category)
|
||||||
|
{
|
||||||
|
var instance = await Instance;
|
||||||
|
return await instance.SaveItemAsync(category);
|
||||||
|
}
|
||||||
|
public static async Task<int> DeleteCategoryItemAsync(Category category)
|
||||||
|
{
|
||||||
|
var instance = await Instance;
|
||||||
|
return await instance.DeleteItemAsync(category);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<int> GetLogsCount()
|
||||||
|
{
|
||||||
|
await Instance;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return await database.ExecuteScalarAsync<int>("SELECT COUNT(Id) FROM [Logs]");
|
||||||
|
}
|
||||||
|
catch (SQLiteException)
|
||||||
|
{
|
||||||
|
await database.CreateTableAsync<Logs>();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public static async Task<int> SaveLogItemAsync(Logs log)
|
||||||
|
{
|
||||||
|
var instance = await Instance;
|
||||||
|
return await instance.SaveItemAsync(log);
|
||||||
|
}
|
||||||
|
public static string GetLogFile()
|
||||||
|
{
|
||||||
|
return Path.Combine(CacheFolder, "logs.csv");
|
||||||
|
}
|
||||||
|
public static async Task<string> ExportLogs()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var instance = await Instance;
|
||||||
|
var logs = await instance.GetListAsync<Logs>();
|
||||||
|
var file = GetLogFile();
|
||||||
|
using var writer = new StreamWriter(File.Open(file, FileMode.Create, FileAccess.Write));
|
||||||
|
writer.WriteLine("Id,DateTime,Category,Detail");
|
||||||
|
foreach (var log in logs)
|
||||||
|
{
|
||||||
|
var category = log.Category?.Replace("\n", " \\n ");
|
||||||
|
var detail = log.Detail?.Replace("\n", " \\n ");
|
||||||
|
writer.WriteLine($"{log.Id},{log.LogTime},{category},{detail}");
|
||||||
|
}
|
||||||
|
writer.Flush();
|
||||||
|
|
||||||
|
await database.RunInTransactionAsync(conn =>
|
||||||
|
{
|
||||||
|
conn.Execute("DELETE FROM [Logs]");
|
||||||
|
conn.Execute("DELETE FROM [sqlite_sequence] WHERE [name] = 'Logs'");
|
||||||
|
});
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private StoreHelper()
|
||||||
|
{
|
||||||
|
if (database == null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
database = new SQLiteAsyncConnection(DatabasePath,
|
||||||
|
SQLiteOpenFlags.ReadWrite |
|
||||||
|
SQLiteOpenFlags.Create |
|
||||||
|
SQLiteOpenFlags.SharedCache);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Helper.Error("database.ctor.connect", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<List<T>> GetListAsync<T>(string query, params object[] args) where T : new()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return database.QueryAsync<T>(query, args);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Helper.Error("db.read", $"failed to read db, query: {string.Format(query, args)}, error: {ex.Message}");
|
||||||
}
|
}
|
||||||
return default;
|
return default;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Task<T> GetItemAsync<T>(int id) where T : IIdItem, new()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var source = new TaskCompletionSource<T>();
|
||||||
|
Task.Run(async () =>
|
||||||
|
{
|
||||||
|
var item = await database.FindWithQueryAsync<T>($"SELECT * FROM [{typeof(T).Name}] WHERE [Id] = ? LIMIT 1", id);
|
||||||
|
source.SetResult(item);
|
||||||
|
});
|
||||||
|
return source.Task;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Helper.Error("db.read", $"failed to get item, table: {typeof(T)}, id: {id}, error: {ex.Message}");
|
||||||
|
}
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Helper
|
||||||
|
|
||||||
|
public Task<List<T>> GetListAsync<T>() where T : new()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return database.Table<T>().ToListAsync();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Helper.Error("db.read", $"failed to read db, error: {ex.Message}");
|
||||||
|
}
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<int> SaveItemAsync<T>(T item) where T : IIdItem
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (item.Id > 0)
|
||||||
|
{
|
||||||
|
return database.UpdateAsync(item);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return database.InsertAsync(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Helper.Error("db.write", $"failed to insert/update item, table: {typeof(T)}, id: {item.Id}, item: {item}, error: {ex.Message}");
|
||||||
|
}
|
||||||
|
return Task.FromResult(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<int> DeleteItemAsync<T>(T item) where T : IIdItem
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return database.DeleteAsync(item);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Helper.Error("db.delete", $"failed to delete item, table: {typeof(T)}, id: {item.Id}, item: {item}, error: {ex.Message}");
|
||||||
|
}
|
||||||
|
return Task.FromResult(0);
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,8 @@ namespace Billing.Themes
|
|||||||
{
|
{
|
||||||
public abstract class BaseTheme : ResourceDictionary
|
public abstract class BaseTheme : ResourceDictionary
|
||||||
{
|
{
|
||||||
|
public const double DefaultFontSize = 15.0;
|
||||||
|
|
||||||
public static Color CurrentPrimaryColor => (Color)Application.Current.Resources[PrimaryColor];
|
public static Color CurrentPrimaryColor => (Color)Application.Current.Resources[PrimaryColor];
|
||||||
public static Color CurrentTextColor => (Color)Application.Current.Resources[TextColor];
|
public static Color CurrentTextColor => (Color)Application.Current.Resources[TextColor];
|
||||||
public static Color CurrentSecondaryTextColor => (Color)Application.Current.Resources[SecondaryTextColor];
|
public static Color CurrentSecondaryTextColor => (Color)Application.Current.Resources[SecondaryTextColor];
|
||||||
@ -31,9 +33,8 @@ namespace Billing.Themes
|
|||||||
|
|
||||||
protected void InitResources()
|
protected void InitResources()
|
||||||
{
|
{
|
||||||
var regularFontFamily = Definition.GetRegularFontFamily();
|
Add(FontSemiBold, Definition.SemiBoldFontFamily);
|
||||||
Add(FontSemiBold, Definition.GetSemiBoldFontFamily());
|
Add(FontBold, Definition.BoldFontFamily);
|
||||||
Add(FontBold, Definition.GetBoldFontFamily());
|
|
||||||
|
|
||||||
Add(PrimaryColor, PrimaryMauiColor);
|
Add(PrimaryColor, PrimaryMauiColor);
|
||||||
Add(SecondaryColor, SecondaryMauiColor);
|
Add(SecondaryColor, SecondaryMauiColor);
|
||||||
@ -43,41 +44,49 @@ namespace Billing.Themes
|
|||||||
{
|
{
|
||||||
Setters =
|
Setters =
|
||||||
{
|
{
|
||||||
new Setter { Property = Label.FontSizeProperty, Value = Device.GetNamedSize(NamedSize.Small, typeof(Label)) },
|
new Setter { Property = Label.FontSizeProperty, Value = DefaultFontSize },
|
||||||
new Setter { Property = Label.TextColorProperty, Value = PrimaryMauiColor },
|
new Setter { Property = Label.TextColorProperty, Value = PrimaryMauiColor },
|
||||||
new Setter { Property = Label.FontFamilyProperty, Value = regularFontFamily }
|
new Setter { Property = Label.FontFamilyProperty, Value = Definition.RegularFontFamily }
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
Add(new Style(typeof(OptionEntry))
|
Add(new Style(typeof(OptionEntry))
|
||||||
{
|
{
|
||||||
Setters =
|
Setters =
|
||||||
{
|
{
|
||||||
new Setter { Property = Entry.FontSizeProperty, Value = Device.GetNamedSize(NamedSize.Small, typeof(Entry)) },
|
new Setter { Property = Entry.FontSizeProperty, Value = DefaultFontSize },
|
||||||
new Setter { Property = Entry.FontFamilyProperty, Value = regularFontFamily }
|
new Setter { Property = Entry.FontFamilyProperty, Value = Definition.RegularFontFamily }
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
Add(new Style(typeof(OptionEditor))
|
Add(new Style(typeof(OptionEditor))
|
||||||
{
|
{
|
||||||
Setters =
|
Setters =
|
||||||
{
|
{
|
||||||
new Setter { Property = Editor.FontSizeProperty, Value = Device.GetNamedSize(NamedSize.Small, typeof(Editor)) },
|
new Setter { Property = Editor.FontSizeProperty, Value = DefaultFontSize },
|
||||||
new Setter { Property = Editor.FontFamilyProperty, Value = regularFontFamily }
|
new Setter { Property = Editor.FontFamilyProperty, Value = Definition.RegularFontFamily }
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
Add(new Style(typeof(OptionDatePicker))
|
Add(new Style(typeof(OptionDatePicker))
|
||||||
{
|
{
|
||||||
Setters =
|
Setters =
|
||||||
{
|
{
|
||||||
new Setter { Property = DatePicker.FontSizeProperty, Value = Device.GetNamedSize(NamedSize.Small, typeof(DatePicker)) },
|
new Setter { Property = DatePicker.FontSizeProperty, Value = DefaultFontSize },
|
||||||
new Setter { Property = DatePicker.FontFamilyProperty, Value = regularFontFamily }
|
new Setter { Property = DatePicker.FontFamilyProperty, Value = Definition.RegularFontFamily }
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
Add(new Style(typeof(OptionTimePicker))
|
Add(new Style(typeof(OptionTimePicker))
|
||||||
{
|
{
|
||||||
Setters =
|
Setters =
|
||||||
{
|
{
|
||||||
new Setter { Property = TimePicker.FontSizeProperty, Value = Device.GetNamedSize(NamedSize.Small, typeof(TimePicker)) },
|
new Setter { Property = TimePicker.FontSizeProperty, Value = DefaultFontSize },
|
||||||
new Setter { Property = TimePicker.FontFamilyProperty, Value = regularFontFamily }
|
new Setter { Property = TimePicker.FontFamilyProperty, Value = Definition.RegularFontFamily }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Add(new Style(typeof(OptionPicker))
|
||||||
|
{
|
||||||
|
Setters =
|
||||||
|
{
|
||||||
|
new Setter { Property = Picker.FontSizeProperty, Value = DefaultFontSize },
|
||||||
|
new Setter { Property = Picker.FontFamilyProperty, Value = Definition.RegularFontFamily }
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
Add(new Style(typeof(TintImage))
|
Add(new Style(typeof(TintImage))
|
||||||
|
@ -230,7 +230,7 @@ namespace Billing.UI
|
|||||||
{
|
{
|
||||||
public static readonly BindableProperty DateProperty = Helper.Create<DateTime, BillingDay>(nameof(Date), propertyChanged: OnDatePropertyChanged);
|
public static readonly BindableProperty DateProperty = Helper.Create<DateTime, BillingDay>(nameof(Date), propertyChanged: OnDatePropertyChanged);
|
||||||
public static readonly BindableProperty TextProperty = Helper.Create<string, BillingDay>(nameof(Text));
|
public static readonly BindableProperty TextProperty = Helper.Create<string, BillingDay>(nameof(Text));
|
||||||
public static readonly BindableProperty FontFamilyProperty = Helper.Create<string, BillingDay>(nameof(FontFamily), defaultValue: Definition.GetRegularFontFamily());
|
public static readonly BindableProperty FontFamilyProperty = Helper.Create<string, BillingDay>(nameof(FontFamily), defaultValue: Definition.RegularFontFamily);
|
||||||
public static readonly BindableProperty IsSelectedProperty = Helper.Create<bool, BillingDay>(nameof(IsSelected), defaultValue: false);
|
public static readonly BindableProperty IsSelectedProperty = Helper.Create<bool, BillingDay>(nameof(IsSelected), defaultValue: false);
|
||||||
public static readonly BindableProperty OpacityProperty = Helper.Create<double, BillingDay>(nameof(Opacity), defaultValue: 1.0);
|
public static readonly BindableProperty OpacityProperty = Helper.Create<double, BillingDay>(nameof(Opacity), defaultValue: 1.0);
|
||||||
public static readonly BindableProperty TextOpacityProperty = Helper.Create<double, BillingDay>(nameof(TextOpacity), defaultValue: 1.0);
|
public static readonly BindableProperty TextOpacityProperty = Helper.Create<double, BillingDay>(nameof(TextOpacity), defaultValue: 1.0);
|
||||||
@ -273,11 +273,11 @@ namespace Billing.UI
|
|||||||
if (Helper.IsSameDay(date, selected))
|
if (Helper.IsSameDay(date, selected))
|
||||||
{
|
{
|
||||||
IsSelected = true;
|
IsSelected = true;
|
||||||
SetValue(FontFamilyProperty, Definition.GetBoldFontFamily());
|
SetValue(FontFamilyProperty, Definition.BoldFontFamily);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
SetValue(FontFamilyProperty, Definition.GetRegularFontFamily());
|
SetValue(FontFamilyProperty, Definition.RegularFontFamily);
|
||||||
}
|
}
|
||||||
if (date.Year == selected.Year && date.Month == selected.Month)
|
if (date.Year == selected.Year && date.Month == selected.Month)
|
||||||
{
|
{
|
||||||
|
@ -7,6 +7,9 @@ namespace Billing.UI
|
|||||||
public abstract class BillingPage : ContentPage
|
public abstract class BillingPage : ContentPage
|
||||||
{
|
{
|
||||||
public event EventHandler Loaded;
|
public event EventHandler Loaded;
|
||||||
|
public event EventHandler Refreshed;
|
||||||
|
|
||||||
|
private bool loaded;
|
||||||
|
|
||||||
public BillingPage()
|
public BillingPage()
|
||||||
{
|
{
|
||||||
@ -14,9 +17,28 @@ namespace Billing.UI
|
|||||||
Shell.SetTabBarIsVisible(this, false);
|
Shell.SetTabBarIsVisible(this, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual void OnLoaded()
|
protected virtual void OnLoaded()
|
||||||
{
|
{
|
||||||
Loaded?.Invoke(this, EventArgs.Empty);
|
Loaded?.Invoke(this, EventArgs.Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected virtual void OnRefresh()
|
||||||
|
{
|
||||||
|
Refreshed?.Invoke(this, EventArgs.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void TriggerLoad()
|
||||||
|
{
|
||||||
|
if (!loaded)
|
||||||
|
{
|
||||||
|
loaded = true;
|
||||||
|
OnLoaded();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void TriggerRefresh()
|
||||||
|
{
|
||||||
|
OnRefresh();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -8,15 +8,19 @@ namespace Billing.UI
|
|||||||
{
|
{
|
||||||
public class ColorPicker : SKCanvasView
|
public class ColorPicker : SKCanvasView
|
||||||
{
|
{
|
||||||
public static readonly BindableProperty ColorProperty = BindableProperty.Create(nameof(Color), typeof(Color), typeof(ColorPicker));
|
public static readonly BindableProperty ColorProperty = Helper.Create<Color, ColorPicker>(nameof(Color));
|
||||||
|
public static readonly BindableProperty CommandProperty = Helper.Create<Command, ColorPicker>(nameof(Command));
|
||||||
|
|
||||||
public Color Color
|
public Color Color
|
||||||
{
|
{
|
||||||
get => (Color)GetValue(ColorProperty);
|
get => (Color)GetValue(ColorProperty);
|
||||||
set => SetValue(ColorProperty, value);
|
set => SetValue(ColorProperty, value);
|
||||||
}
|
}
|
||||||
|
public Command Command
|
||||||
public event EventHandler<Color> ColorChanged;
|
{
|
||||||
|
get => (Command)GetValue(CommandProperty);
|
||||||
|
set => SetValue(CommandProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
private SKPoint? lastTouch;
|
private SKPoint? lastTouch;
|
||||||
|
|
||||||
@ -116,7 +120,7 @@ namespace Billing.UI
|
|||||||
|
|
||||||
var color = touchColor.ToFormsColor();
|
var color = touchColor.ToFormsColor();
|
||||||
Color = color;
|
Color = color;
|
||||||
ColorChanged?.Invoke(this, color);
|
Command?.Execute(color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -126,8 +130,8 @@ namespace Billing.UI
|
|||||||
lastTouch = e.Location;
|
lastTouch = e.Location;
|
||||||
|
|
||||||
var size = CanvasSize;
|
var size = CanvasSize;
|
||||||
if ((e.Location.X > 0 && e.Location.X < size.Width) &&
|
if (e.Location.X > 0 && e.Location.X < size.Width &&
|
||||||
(e.Location.Y > 0 && e.Location.Y < size.Height))
|
e.Location.Y > 0 && e.Location.Y < size.Height)
|
||||||
{
|
{
|
||||||
e.Handled = true;
|
e.Handled = true;
|
||||||
InvalidateSurface();
|
InvalidateSurface();
|
||||||
|
@ -205,13 +205,13 @@ namespace Billing.UI
|
|||||||
{
|
{
|
||||||
if (!int.TryParse(key, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out int i))
|
if (!int.TryParse(key, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out int i))
|
||||||
{
|
{
|
||||||
return ImageSource.FromFile(BaseModel.ICON_DEFAULT);
|
return ImageSource.FromFile(Definition.DefaultIcon);
|
||||||
}
|
}
|
||||||
glyph = char.ConvertFromUtf32(i);
|
glyph = char.ConvertFromUtf32(i);
|
||||||
}
|
}
|
||||||
return new FontImageSource
|
return new FontImageSource
|
||||||
{
|
{
|
||||||
FontFamily = Definition.GetBrandsFontFamily(),
|
FontFamily = Definition.BrandsFontFamily,
|
||||||
Size = 20,
|
Size = 20,
|
||||||
Glyph = glyph,
|
Glyph = glyph,
|
||||||
Color = Color.Black
|
Color = Color.Black
|
||||||
@ -244,4 +244,23 @@ namespace Billing.UI
|
|||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class TintColorConverter : IValueConverter
|
||||||
|
{
|
||||||
|
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
if (value is long l)
|
||||||
|
{
|
||||||
|
return l.IsTransparent() ?
|
||||||
|
BaseTheme.CurrentPrimaryColor :
|
||||||
|
l.ToColor();
|
||||||
|
}
|
||||||
|
return Color.Transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@ -34,8 +34,8 @@ namespace Billing.UI
|
|||||||
|
|
||||||
public class LongPressGrid : Grid
|
public class LongPressGrid : Grid
|
||||||
{
|
{
|
||||||
public static readonly BindableProperty LongCommandProperty = BindableProperty.Create(nameof(LongCommand), typeof(Command), typeof(LongPressGrid));
|
public static readonly BindableProperty LongCommandProperty = Helper.Create<Command, LongPressGrid>(nameof(LongCommand));
|
||||||
public static readonly BindableProperty LongCommandParameterProperty = BindableProperty.Create(nameof(LongCommandParameter), typeof(object), typeof(LongPressGrid));
|
public static readonly BindableProperty LongCommandParameterProperty = Helper.Create<object, LongPressGrid>(nameof(LongCommandParameter));
|
||||||
|
|
||||||
public Command LongCommand
|
public Command LongCommand
|
||||||
{
|
{
|
||||||
|
@ -1,22 +1,16 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Xml.Linq;
|
|
||||||
using Billing.Languages;
|
using Billing.Languages;
|
||||||
using Billing.Models;
|
|
||||||
using Xamarin.Forms;
|
using Xamarin.Forms;
|
||||||
|
|
||||||
namespace Billing.UI
|
namespace Billing.UI
|
||||||
{
|
{
|
||||||
public static partial class Definition
|
public static partial class Definition
|
||||||
{
|
{
|
||||||
public static string PrimaryColorKey = "PrimaryColor";
|
public const string SaveLocationKey = "SaveLocationKey";
|
||||||
public static partial (string main, long build) GetVersion();
|
public const string PrimaryColorKey = "PrimaryColor";
|
||||||
public static partial string GetRegularFontFamily();
|
public const string DefaultIcon = "ic_default";
|
||||||
public static partial string GetSemiBoldFontFamily();
|
public const long TransparentColor = 0x00ffffffL;
|
||||||
public static partial string GetBoldFontFamily();
|
|
||||||
public static partial string GetBrandsFontFamily();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class ExtensionHelper
|
public static class ExtensionHelper
|
||||||
@ -92,50 +86,39 @@ namespace Billing.UI
|
|||||||
var result = await page.DisplayActionSheet(message, Resource.No, yes);
|
var result = await page.DisplayActionSheet(message, Resource.No, yes);
|
||||||
return result == yes;
|
return result == yes;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public static class ModelExtensionHelper
|
public static DateTime LastMoment(this DateTime date)
|
||||||
{
|
|
||||||
public static List<T> FromStream<T>(Stream stream) where T : IModel, new()
|
|
||||||
{
|
{
|
||||||
XDocument doc = XDocument.Load(stream);
|
// add 23:59:59.999...
|
||||||
var root = doc.Root;
|
return date.AddTicks(863999999999);
|
||||||
var list = new List<T>();
|
|
||||||
foreach (XElement ele in root.Elements("item"))
|
|
||||||
{
|
|
||||||
if (ele.Attribute("null")?.Value == "1")
|
|
||||||
{
|
|
||||||
list.Add(default);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
T value = new();
|
|
||||||
value.OnXmlDeserialize(ele);
|
|
||||||
list.Add(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return list;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void ToStream<T>(this IEnumerable<T> list, Stream stream) where T : IModel
|
public static bool IsTransparent(this long color)
|
||||||
{
|
{
|
||||||
XElement root = new("root");
|
return (color & 0xff000000L) == 0x00000000L;
|
||||||
foreach (var t in list)
|
}
|
||||||
{
|
|
||||||
XElement item = new("item");
|
|
||||||
if (t == null)
|
|
||||||
{
|
|
||||||
item.Add(new XAttribute("null", 1));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
t.OnXmlSerialize(item);
|
|
||||||
}
|
|
||||||
root.Add(item);
|
|
||||||
}
|
|
||||||
|
|
||||||
XDocument doc = new(new XDeclaration("1.0", "utf-8", "yes"), root);
|
public static Color ToColor(this long color)
|
||||||
doc.Save(stream, SaveOptions.DisableFormatting);
|
{
|
||||||
|
ulong c = (ulong)color;
|
||||||
|
int r = (int)(c & 0xff);
|
||||||
|
c >>= 8;
|
||||||
|
int g = (int)(c & 0xff);
|
||||||
|
c >>= 8;
|
||||||
|
int b = (int)(c & 0xff);
|
||||||
|
c >>= 8;
|
||||||
|
int a = (int)(c & 0xff);
|
||||||
|
return Color.FromRgba(r, g, b, a);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static long ToLong(this Color color)
|
||||||
|
{
|
||||||
|
long l =
|
||||||
|
(uint)(color.A * 255) << 24 |
|
||||||
|
(uint)(color.B * 255) << 16 |
|
||||||
|
(uint)(color.G * 255) << 8 |
|
||||||
|
(uint)(color.R * 255);
|
||||||
|
return l;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,12 +5,12 @@ namespace Billing.UI
|
|||||||
{
|
{
|
||||||
public class GroupStackLayout : Layout<View>
|
public class GroupStackLayout : Layout<View>
|
||||||
{
|
{
|
||||||
public static readonly BindableProperty GroupHeaderTemplateProperty = BindableProperty.Create(nameof(GroupHeaderTemplate), typeof(DataTemplate), typeof(GroupStackLayout));
|
public static readonly BindableProperty GroupHeaderTemplateProperty = Helper.Create<DataTemplate, GroupStackLayout>(nameof(GroupHeaderTemplate));
|
||||||
public static readonly BindableProperty ItemTemplateProperty = BindableProperty.Create(nameof(ItemTemplate), typeof(DataTemplate), typeof(GroupStackLayout));
|
public static readonly BindableProperty ItemTemplateProperty = Helper.Create<DataTemplate, GroupStackLayout>(nameof(ItemTemplate));
|
||||||
public static readonly BindableProperty ItemsSourceProperty = BindableProperty.Create(nameof(ItemsSource), typeof(IList), typeof(GroupStackLayout), propertyChanged: OnItemsSourcePropertyChanged);
|
public static readonly BindableProperty ItemsSourceProperty = Helper.Create<IList, GroupStackLayout>(nameof(ItemsSource), propertyChanged: OnItemsSourcePropertyChanged);
|
||||||
public static readonly BindableProperty SpacingProperty = BindableProperty.Create(nameof(Spacing), typeof(double), typeof(GroupStackLayout), defaultValue: 4d);
|
public static readonly BindableProperty SpacingProperty = Helper.Create<double, GroupStackLayout>(nameof(Spacing), defaultValue: 4d);
|
||||||
public static readonly BindableProperty RowHeightProperty = BindableProperty.Create(nameof(RowHeight), typeof(double), typeof(GroupStackLayout), defaultValue: 32d);
|
public static readonly BindableProperty RowHeightProperty = Helper.Create<double, GroupStackLayout>(nameof(RowHeight), defaultValue: 32d);
|
||||||
public static readonly BindableProperty GroupHeightProperty = BindableProperty.Create(nameof(GroupHeight), typeof(double), typeof(GroupStackLayout), defaultValue: 24d);
|
public static readonly BindableProperty GroupHeightProperty = Helper.Create<double, GroupStackLayout>(nameof(GroupHeight), defaultValue: 24d);
|
||||||
|
|
||||||
public DataTemplate GroupHeaderTemplate
|
public DataTemplate GroupHeaderTemplate
|
||||||
{
|
{
|
||||||
@ -43,17 +43,16 @@ namespace Billing.UI
|
|||||||
set => SetValue(GroupHeightProperty, value);
|
set => SetValue(GroupHeightProperty, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void OnItemsSourcePropertyChanged(BindableObject obj, object old, object @new)
|
private static void OnItemsSourcePropertyChanged(GroupStackLayout stack, IList old, IList list)
|
||||||
{
|
{
|
||||||
var stack = (GroupStackLayout)obj;
|
|
||||||
stack.lastWidth = -1;
|
stack.lastWidth = -1;
|
||||||
if (@new == null)
|
if (list == null)
|
||||||
{
|
{
|
||||||
//stack.cachedLayout.Clear();
|
//stack.cachedLayout.Clear();
|
||||||
stack.Children.Clear();
|
stack.Children.Clear();
|
||||||
stack.InvalidateLayout();
|
stack.InvalidateLayout();
|
||||||
}
|
}
|
||||||
else if (@new is IList list)
|
else
|
||||||
{
|
{
|
||||||
stack.freezed = true;
|
stack.freezed = true;
|
||||||
//stack.cachedLayout.Clear();
|
//stack.cachedLayout.Clear();
|
||||||
|
@ -4,16 +4,32 @@ using Xamarin.Forms;
|
|||||||
|
|
||||||
namespace Billing.UI
|
namespace Billing.UI
|
||||||
{
|
{
|
||||||
|
public enum BorderStyle
|
||||||
|
{
|
||||||
|
None = 0,
|
||||||
|
RoundedRect
|
||||||
|
}
|
||||||
|
|
||||||
public class OptionEntry : Entry { }
|
public class OptionEntry : Entry { }
|
||||||
public class OptionEditor : Editor { }
|
public class OptionEditor : Editor { }
|
||||||
|
public class OptionPicker : Picker
|
||||||
|
{
|
||||||
|
public static readonly BindableProperty BorderStyleProperty = Helper.Create<BorderStyle, OptionPicker>(nameof(BorderStyle));
|
||||||
|
|
||||||
|
public BorderStyle BorderStyle
|
||||||
|
{
|
||||||
|
get => (BorderStyle)GetValue(BorderStyleProperty);
|
||||||
|
set => SetValue(BorderStyleProperty, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
public class OptionDatePicker : DatePicker { }
|
public class OptionDatePicker : DatePicker { }
|
||||||
public class OptionTimePicker : TimePicker { }
|
public class OptionTimePicker : TimePicker { }
|
||||||
|
|
||||||
public abstract class OptionCell : ViewCell
|
public abstract class OptionCell : ViewCell
|
||||||
{
|
{
|
||||||
public static readonly BindableProperty TitleProperty = BindableProperty.Create(nameof(Title), typeof(string), typeof(OptionCell));
|
public static readonly BindableProperty TitleProperty = Helper.Create<string, OptionCell>(nameof(Title));
|
||||||
public static readonly BindableProperty BackgroundColorProperty = BindableProperty.Create(nameof(BackgroundColor), typeof(Color), typeof(OptionCell));
|
public static readonly BindableProperty BackgroundColorProperty = Helper.Create<Color, OptionCell>(nameof(BackgroundColor));
|
||||||
public static readonly BindableProperty IconProperty = BindableProperty.Create(nameof(Icon), typeof(ImageSource), typeof(OptionCell));
|
public static readonly BindableProperty IconProperty = Helper.Create<ImageSource, OptionCell>(nameof(Icon));
|
||||||
|
|
||||||
public string Title
|
public string Title
|
||||||
{
|
{
|
||||||
@ -124,7 +140,7 @@ namespace Billing.UI
|
|||||||
|
|
||||||
public class OptionTextCell : OptionCell
|
public class OptionTextCell : OptionCell
|
||||||
{
|
{
|
||||||
public static readonly BindableProperty DetailProperty = BindableProperty.Create(nameof(Detail), typeof(string), typeof(OptionTextCell));
|
public static readonly BindableProperty DetailProperty = Helper.Create<string, OptionTextCell>(nameof(Detail));
|
||||||
|
|
||||||
public string Detail
|
public string Detail
|
||||||
{
|
{
|
||||||
@ -143,8 +159,8 @@ namespace Billing.UI
|
|||||||
|
|
||||||
public class OptionSelectCell : OptionTextCell
|
public class OptionSelectCell : OptionTextCell
|
||||||
{
|
{
|
||||||
public static readonly BindableProperty CommandProperty = BindableProperty.Create(nameof(Command), typeof(Command), typeof(OptionSelectCell));
|
public static readonly BindableProperty CommandProperty = Helper.Create<Command, OptionSelectCell>(nameof(Command));
|
||||||
public static readonly BindableProperty CommandParameterProperty = BindableProperty.Create(nameof(CommandParameter), typeof(object), typeof(OptionSelectCell));
|
public static readonly BindableProperty CommandParameterProperty = Helper.Create<object, OptionSelectCell>(nameof(CommandParameter));
|
||||||
|
|
||||||
public Command Command
|
public Command Command
|
||||||
{
|
{
|
||||||
@ -189,8 +205,8 @@ namespace Billing.UI
|
|||||||
|
|
||||||
public class OptionImageCell : OptionSelectCell
|
public class OptionImageCell : OptionSelectCell
|
||||||
{
|
{
|
||||||
public static readonly BindableProperty ImageSourceProperty = BindableProperty.Create(nameof(ImageSource), typeof(ImageSource), typeof(OptionImageCell));
|
public static readonly BindableProperty ImageSourceProperty = Helper.Create<ImageSource, OptionImageCell>(nameof(ImageSource));
|
||||||
public static readonly BindableProperty TintColorProperty = BindableProperty.Create(nameof(TintColor), typeof(Color?), typeof(OptionImageCell));
|
public static readonly BindableProperty TintColorProperty = Helper.Create<Color?, OptionImageCell>(nameof(TintColor));
|
||||||
|
|
||||||
[TypeConverter(typeof(ImageSourceConverter))]
|
[TypeConverter(typeof(ImageSourceConverter))]
|
||||||
public ImageSource ImageSource
|
public ImageSource ImageSource
|
||||||
@ -221,6 +237,13 @@ namespace Billing.UI
|
|||||||
.Binding(Image.SourceProperty, nameof(ImageSource))
|
.Binding(Image.SourceProperty, nameof(ImageSource))
|
||||||
.Binding(TintHelper.TintColorProperty, nameof(TintColor)),
|
.Binding(TintHelper.TintColorProperty, nameof(TintColor)),
|
||||||
|
|
||||||
|
new Label
|
||||||
|
{
|
||||||
|
VerticalOptions = LayoutOptions.Center
|
||||||
|
}
|
||||||
|
.Binding(Label.TextProperty, nameof(Detail))
|
||||||
|
.DynamicResource(Label.TextColorProperty, BaseTheme.SecondaryTextColor),
|
||||||
|
|
||||||
new TintImage
|
new TintImage
|
||||||
{
|
{
|
||||||
HeightRequest = 20,
|
HeightRequest = 20,
|
||||||
@ -240,7 +263,7 @@ namespace Billing.UI
|
|||||||
|
|
||||||
public class OptionSwitchCell : OptionCell
|
public class OptionSwitchCell : OptionCell
|
||||||
{
|
{
|
||||||
public static readonly BindableProperty IsToggledProperty = BindableProperty.Create(nameof(IsToggled), typeof(bool), typeof(OptionSwitchCell));
|
public static readonly BindableProperty IsToggledProperty = Helper.Create<bool, OptionSwitchCell>(nameof(IsToggled));
|
||||||
|
|
||||||
public bool IsToggled
|
public bool IsToggled
|
||||||
{
|
{
|
||||||
@ -258,7 +281,7 @@ namespace Billing.UI
|
|||||||
|
|
||||||
public class OptionDatePickerCell : OptionCell
|
public class OptionDatePickerCell : OptionCell
|
||||||
{
|
{
|
||||||
public static readonly BindableProperty DateProperty = BindableProperty.Create(nameof(Date), typeof(DateTime), typeof(OptionDatePickerCell));
|
public static readonly BindableProperty DateProperty = Helper.Create<DateTime, OptionDatePickerCell>(nameof(Date));
|
||||||
|
|
||||||
public DateTime Date
|
public DateTime Date
|
||||||
{
|
{
|
||||||
@ -278,7 +301,7 @@ namespace Billing.UI
|
|||||||
|
|
||||||
public class OptionTimePickerCell : OptionCell
|
public class OptionTimePickerCell : OptionCell
|
||||||
{
|
{
|
||||||
public static readonly BindableProperty TimeProperty = BindableProperty.Create(nameof(Time), typeof(TimeSpan), typeof(OptionTimePickerCell));
|
public static readonly BindableProperty TimeProperty = Helper.Create<TimeSpan, OptionTimePickerCell>(nameof(Time));
|
||||||
|
|
||||||
public TimeSpan Time
|
public TimeSpan Time
|
||||||
{
|
{
|
||||||
@ -298,9 +321,9 @@ namespace Billing.UI
|
|||||||
|
|
||||||
public class OptionEntryCell : OptionCell
|
public class OptionEntryCell : OptionCell
|
||||||
{
|
{
|
||||||
public static readonly BindableProperty TextProperty = BindableProperty.Create(nameof(Text), typeof(string), typeof(OptionEntryCell));
|
public static readonly BindableProperty TextProperty = Helper.Create<string, OptionEntryCell>(nameof(Text));
|
||||||
public static readonly BindableProperty KeyboardProperty = BindableProperty.Create(nameof(Keyboard), typeof(Keyboard), typeof(OptionEntryCell));
|
public static readonly BindableProperty KeyboardProperty = Helper.Create<Keyboard, OptionEntryCell>(nameof(Keyboard), defaultValue: Keyboard.Default);
|
||||||
public static readonly BindableProperty PlaceholderProperty = BindableProperty.Create(nameof(Placeholder), typeof(string), typeof(OptionEntryCell));
|
public static readonly BindableProperty PlaceholderProperty = Helper.Create<string, OptionEntryCell>(nameof(Placeholder));
|
||||||
|
|
||||||
public string Text
|
public string Text
|
||||||
{
|
{
|
||||||
@ -351,10 +374,10 @@ namespace Billing.UI
|
|||||||
|
|
||||||
public class OptionEditorCell : OptionVerticalCell
|
public class OptionEditorCell : OptionVerticalCell
|
||||||
{
|
{
|
||||||
public static readonly BindableProperty TextProperty = BindableProperty.Create(nameof(Text), typeof(string), typeof(OptionEditorCell));
|
public static readonly BindableProperty TextProperty = Helper.Create<string, OptionEditorCell>(nameof(Text));
|
||||||
public static readonly BindableProperty FontSizeProperty = BindableProperty.Create(nameof(FontSize), typeof(double), typeof(OptionEditorCell), defaultValue: Device.GetNamedSize(NamedSize.Default, typeof(Editor)));
|
public static readonly BindableProperty FontSizeProperty = Helper.Create<double, OptionEditorCell>(nameof(FontSize), defaultValue: Device.GetNamedSize(NamedSize.Default, typeof(Editor)));
|
||||||
public static readonly BindableProperty KeyboardProperty = BindableProperty.Create(nameof(Keyboard), typeof(Keyboard), typeof(OptionEditorCell), defaultValue: Keyboard.Default);
|
public static readonly BindableProperty KeyboardProperty = Helper.Create<Keyboard, OptionEditorCell>(nameof(Keyboard), defaultValue: Keyboard.Default);
|
||||||
public static readonly BindableProperty PlaceholderProperty = BindableProperty.Create(nameof(Placeholder), typeof(string), typeof(OptionEditorCell));
|
public static readonly BindableProperty PlaceholderProperty = Helper.Create<string, OptionEditorCell>(nameof(Placeholder));
|
||||||
|
|
||||||
public string Text
|
public string Text
|
||||||
{
|
{
|
||||||
|
@ -7,24 +7,23 @@ namespace Billing.UI
|
|||||||
{
|
{
|
||||||
public class WrapLayout : Layout<View>
|
public class WrapLayout : Layout<View>
|
||||||
{
|
{
|
||||||
public static readonly BindableProperty ItemsSourceProperty = BindableProperty.Create(nameof(ItemsSource), typeof(IList), typeof(WrapLayout), propertyChanged: OnItemsSourcePropertyChanged);
|
public static readonly BindableProperty ItemsSourceProperty = Helper.Create<IList, WrapLayout>(nameof(ItemsSource), propertyChanged: OnItemsSourcePropertyChanged);
|
||||||
public static readonly BindableProperty ColumnSpacingProperty = BindableProperty.Create(nameof(ColumnSpacing), typeof(double), typeof(WrapLayout), defaultValue: 4d, propertyChanged: (obj, _, _) => ((WrapLayout)obj).InvalidateLayout());
|
public static readonly BindableProperty ColumnSpacingProperty = Helper.Create<double, WrapLayout>(nameof(ColumnSpacing), defaultValue: 4d, propertyChanged: (layout, _, _) => layout.InvalidateLayout());
|
||||||
public static readonly BindableProperty RowSpacingProperty = BindableProperty.Create(nameof(RowSpacing), typeof(double), typeof(WrapLayout), defaultValue: 4d, propertyChanged: (obj, _, _) => ((WrapLayout)obj).InvalidateLayout());
|
public static readonly BindableProperty RowSpacingProperty = Helper.Create<double, WrapLayout>(nameof(RowSpacing), defaultValue: 4d, propertyChanged: (layout, _, _) => layout.InvalidateLayout());
|
||||||
|
|
||||||
private static void OnItemsSourcePropertyChanged(BindableObject obj, object old, object @new)
|
private static void OnItemsSourcePropertyChanged(WrapLayout layout, IList old, IList list)
|
||||||
{
|
{
|
||||||
var itemTemplate = BindableLayout.GetItemTemplate(obj);
|
var itemTemplate = BindableLayout.GetItemTemplate(layout);
|
||||||
if (itemTemplate == null)
|
if (itemTemplate == null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var layout = (WrapLayout)obj;
|
if (list == null)
|
||||||
if (@new == null)
|
|
||||||
{
|
{
|
||||||
layout.Children.Clear();
|
layout.Children.Clear();
|
||||||
layout.InvalidateLayout();
|
layout.InvalidateLayout();
|
||||||
}
|
}
|
||||||
else if (@new is IList list)
|
else
|
||||||
{
|
{
|
||||||
layout.freezed = true;
|
layout.freezed = true;
|
||||||
layout.Children.Clear();
|
layout.Children.Clear();
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Billing.Languages;
|
using Billing.Languages;
|
||||||
using Billing.Models;
|
using Billing.Models;
|
||||||
|
using Billing.Store;
|
||||||
using Billing.UI;
|
using Billing.UI;
|
||||||
using Xamarin.Forms;
|
using Xamarin.Forms;
|
||||||
|
|
||||||
@ -10,10 +10,10 @@ namespace Billing.Views
|
|||||||
{
|
{
|
||||||
public partial class AccountPage : BillingPage
|
public partial class AccountPage : BillingPage
|
||||||
{
|
{
|
||||||
private static readonly BindableProperty BalanceProperty = BindableProperty.Create(nameof(Balance), typeof(decimal), typeof(AccountPage));
|
private static readonly BindableProperty BalanceProperty = Helper.Create<decimal, AccountPage>(nameof(Balance));
|
||||||
private static readonly BindableProperty AssetProperty = BindableProperty.Create(nameof(Asset), typeof(decimal), typeof(AccountPage));
|
private static readonly BindableProperty AssetProperty = Helper.Create<decimal, AccountPage>(nameof(Asset));
|
||||||
private static readonly BindableProperty LiabilityProperty = BindableProperty.Create(nameof(Liability), typeof(decimal), typeof(AccountPage));
|
private static readonly BindableProperty LiabilityProperty = Helper.Create<decimal, AccountPage>(nameof(Liability));
|
||||||
private static readonly BindableProperty AccountsProperty = BindableProperty.Create(nameof(Accounts), typeof(List<AccountGrouping>), typeof(AccountPage));
|
private static readonly BindableProperty AccountsProperty = Helper.Create<List<AccountGrouping>, AccountPage>(nameof(Accounts));
|
||||||
|
|
||||||
public decimal Balance => (decimal)GetValue(BalanceProperty);
|
public decimal Balance => (decimal)GetValue(BalanceProperty);
|
||||||
public decimal Asset => (decimal)GetValue(AssetProperty);
|
public decimal Asset => (decimal)GetValue(AssetProperty);
|
||||||
@ -50,6 +50,17 @@ namespace Billing.Views
|
|||||||
groupLayout.Refresh(accounts);
|
groupLayout.Refresh(accounts);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void OnRefresh()
|
||||||
|
{
|
||||||
|
accounts.Clear();
|
||||||
|
foreach (var account in App.Accounts)
|
||||||
|
{
|
||||||
|
AddToAccountGroup(account);
|
||||||
|
}
|
||||||
|
RefreshBalance(true);
|
||||||
|
groupLayout.Refresh(accounts);
|
||||||
|
}
|
||||||
|
|
||||||
private void RefreshBalance(bool calc = false)
|
private void RefreshBalance(bool calc = false)
|
||||||
{
|
{
|
||||||
if (calc)
|
if (calc)
|
||||||
@ -64,17 +75,6 @@ namespace Billing.Views
|
|||||||
|
|
||||||
private void AddToAccountGroup(Account account)
|
private void AddToAccountGroup(Account account)
|
||||||
{
|
{
|
||||||
int maxId;
|
|
||||||
if (accounts.Count > 0)
|
|
||||||
{
|
|
||||||
maxId = accounts.Max(g => g.Max(a => a.Id));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
maxId = -1;
|
|
||||||
}
|
|
||||||
account.Id = maxId + 1;
|
|
||||||
|
|
||||||
var group = accounts.FirstOrDefault(g => g.Key == account.Category);
|
var group = accounts.FirstOrDefault(g => g.Key == account.Category);
|
||||||
if (group == null)
|
if (group == null)
|
||||||
{
|
{
|
||||||
@ -120,25 +120,29 @@ namespace Billing.Views
|
|||||||
if (group == null)
|
if (group == null)
|
||||||
{
|
{
|
||||||
Helper.Error("account.delete", "unexpected deleting account, cannot find the current category");
|
Helper.Error("account.delete", "unexpected deleting account, cannot find the current category");
|
||||||
return;
|
}
|
||||||
}
|
else
|
||||||
group.Remove(account);
|
|
||||||
if (group.Count == 0)
|
|
||||||
{
|
{
|
||||||
accounts.Remove(group);
|
group.Remove(account);
|
||||||
|
if (group.Count == 0)
|
||||||
|
{
|
||||||
|
accounts.Remove(group);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
RefreshBalance();
|
RefreshBalance();
|
||||||
groupLayout.Refresh(accounts);
|
groupLayout.Refresh(accounts);
|
||||||
|
|
||||||
|
RankPage.Instance?.SetNeedRefresh();
|
||||||
|
|
||||||
_ = Task.Run(App.WriteAccounts);
|
await StoreHelper.DeleteAccountItemAsync(account);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AccountChecked(object sender, AccountEventArgs e)
|
private async void AccountChecked(object sender, AccountEventArgs e)
|
||||||
{
|
{
|
||||||
var add = e.Account.Id < 0;
|
var add = e.Account.Id <= 0;
|
||||||
if (add)
|
if (add)
|
||||||
{
|
{
|
||||||
App.Accounts.Add(e.Account);
|
App.Accounts.Add(e.Account);
|
||||||
@ -147,7 +151,9 @@ namespace Billing.Views
|
|||||||
RefreshBalance(!add);
|
RefreshBalance(!add);
|
||||||
groupLayout.Refresh(accounts);
|
groupLayout.Refresh(accounts);
|
||||||
|
|
||||||
Task.Run(App.WriteAccounts);
|
RankPage.Instance?.SetNeedRefresh();
|
||||||
|
|
||||||
|
await StoreHelper.SaveAccountItemAsync(e.Account);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,11 +9,11 @@ namespace Billing.Views
|
|||||||
{
|
{
|
||||||
public partial class AddAccountPage : BillingPage
|
public partial class AddAccountPage : BillingPage
|
||||||
{
|
{
|
||||||
private static readonly BindableProperty AccountNameProperty = BindableProperty.Create(nameof(AccountName), typeof(string), typeof(AddAccountPage));
|
private static readonly BindableProperty AccountNameProperty = Helper.Create<string, AddAccountPage>(nameof(AccountName));
|
||||||
private static readonly BindableProperty AccountIconProperty = BindableProperty.Create(nameof(AccountIcon), typeof(string), typeof(AddAccountPage));
|
private static readonly BindableProperty AccountIconProperty = Helper.Create<string, AddAccountPage>(nameof(AccountIcon));
|
||||||
private static readonly BindableProperty CategoryProperty = BindableProperty.Create(nameof(Category), typeof(AccountCategory), typeof(AddAccountPage));
|
private static readonly BindableProperty CategoryProperty = Helper.Create<AccountCategory, AddAccountPage>(nameof(Category));
|
||||||
private static readonly BindableProperty InitialProperty = BindableProperty.Create(nameof(Initial), typeof(string), typeof(AddAccountPage));
|
private static readonly BindableProperty InitialProperty = Helper.Create<string, AddAccountPage>(nameof(Initial));
|
||||||
private static readonly BindableProperty MemoProperty = BindableProperty.Create(nameof(Memo), typeof(string), typeof(AddAccountPage));
|
private static readonly BindableProperty MemoProperty = Helper.Create<string, AddAccountPage>(nameof(Memo));
|
||||||
|
|
||||||
public string AccountName
|
public string AccountName
|
||||||
{
|
{
|
||||||
@ -59,7 +59,7 @@ namespace Billing.Views
|
|||||||
this.account = account;
|
this.account = account;
|
||||||
if (account == null)
|
if (account == null)
|
||||||
{
|
{
|
||||||
AccountIcon = BaseModel.ICON_DEFAULT;
|
AccountIcon = Definition.DefaultIcon;
|
||||||
Category = AccountCategory.Cash;
|
Category = AccountCategory.Cash;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -73,15 +73,9 @@ namespace Billing.Views
|
|||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool focused;
|
protected override void OnLoaded()
|
||||||
|
|
||||||
public override void OnLoaded()
|
|
||||||
{
|
{
|
||||||
if (!focused)
|
editorName.SetFocus();
|
||||||
{
|
|
||||||
focused = true;
|
|
||||||
editorName.SetFocus();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void OnCheckAccount()
|
private async void OnCheckAccount()
|
||||||
@ -111,7 +105,6 @@ namespace Billing.Views
|
|||||||
{
|
{
|
||||||
Account = account ?? new Account
|
Account = account ?? new Account
|
||||||
{
|
{
|
||||||
Id = -1,
|
|
||||||
Name = AccountName,
|
Name = AccountName,
|
||||||
Icon = AccountIcon,
|
Icon = AccountIcon,
|
||||||
Category = Category,
|
Category = Category,
|
||||||
|
@ -10,9 +10,15 @@
|
|||||||
BindingContext="{x:Reference billPage}">
|
BindingContext="{x:Reference billPage}">
|
||||||
|
|
||||||
<ContentPage.ToolbarItems>
|
<ContentPage.ToolbarItems>
|
||||||
|
<ToolbarItem Order="Primary" IconImageSource="pin.png" Command="{Binding ViewLocation}"/>
|
||||||
<ToolbarItem Order="Primary" IconImageSource="check.png" Command="{Binding CheckBill}"/>
|
<ToolbarItem Order="Primary" IconImageSource="check.png" Command="{Binding CheckBill}"/>
|
||||||
</ContentPage.ToolbarItems>
|
</ContentPage.ToolbarItems>
|
||||||
|
|
||||||
|
<ContentPage.Resources>
|
||||||
|
<ui:IconConverter x:Key="iconConverter"/>
|
||||||
|
<ui:TintColorConverter x:Key="tintColorConverter"/>
|
||||||
|
</ContentPage.Resources>
|
||||||
|
|
||||||
<ContentPage.Content>
|
<ContentPage.Content>
|
||||||
<TableView Intent="Settings" HasUnevenRows="True">
|
<TableView Intent="Settings" HasUnevenRows="True">
|
||||||
<TableSection Title=" ">
|
<TableSection Title=" ">
|
||||||
@ -29,14 +35,18 @@
|
|||||||
Title="{r:Text Name}"
|
Title="{r:Text Name}"
|
||||||
Text="{Binding Name, Mode=TwoWay}"
|
Text="{Binding Name, Mode=TwoWay}"
|
||||||
Placeholder="{r:Text NamePlaceholder}"/>
|
Placeholder="{r:Text NamePlaceholder}"/>
|
||||||
<ui:OptionSelectCell Height="44" Icon="project.png"
|
<ui:OptionImageCell Height="44" Icon="project.png"
|
||||||
Title="{r:Text Category}"
|
Title="{r:Text Category}"
|
||||||
Detail="{Binding CategoryName}"
|
Detail="{Binding Category.Name}"
|
||||||
Command="{Binding SelectCategory}"/>
|
ImageSource="{Binding Category.Icon, Converter={StaticResource iconConverter}}"
|
||||||
<ui:OptionSelectCell Height="44" Icon="wallet.png"
|
TintColor="{Binding Category.TintColor, Converter={StaticResource tintColorConverter}}"
|
||||||
Title="{r:Text Account}"
|
Command="{Binding SelectCategory}"/>
|
||||||
Detail="{Binding WalletName}"
|
<ui:OptionImageCell Height="44" Icon="wallet.png"
|
||||||
Command="{Binding SelectWallet}"/>
|
Title="{r:Text Account}"
|
||||||
|
Detail="{Binding Wallet.Name}"
|
||||||
|
ImageSource="{Binding Wallet.Icon, Converter={StaticResource iconConverter}}"
|
||||||
|
TintColor="{DynamicResource PrimaryColor}"
|
||||||
|
Command="{Binding SelectWallet}"/>
|
||||||
<ui:OptionEntryCell Height="44" Icon="online.png"
|
<ui:OptionEntryCell Height="44" Icon="online.png"
|
||||||
Title="{r:Text Store}"
|
Title="{r:Text Store}"
|
||||||
Text="{Binding Store, Mode=TwoWay}"/>
|
Text="{Binding Store, Mode=TwoWay}"/>
|
||||||
|
@ -1,24 +1,31 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Billing.Languages;
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Billing.Models;
|
using Billing.Models;
|
||||||
|
using Billing.Store;
|
||||||
using Billing.UI;
|
using Billing.UI;
|
||||||
|
using Xamarin.Essentials;
|
||||||
using Xamarin.Forms;
|
using Xamarin.Forms;
|
||||||
|
using Resource = Billing.Languages.Resource;
|
||||||
|
|
||||||
namespace Billing.Views
|
namespace Billing.Views
|
||||||
{
|
{
|
||||||
public partial class AddBillPage : BillingPage
|
public partial class AddBillPage : BillingPage
|
||||||
{
|
{
|
||||||
private static readonly BindableProperty AmountProperty = BindableProperty.Create(nameof(Amount), typeof(string), typeof(AddBillPage));
|
private static readonly BindableProperty CheckBillProperty = Helper.Create<Command, AddBillPage>(nameof(CheckBill), defaultValue: new Command(() => { }, () => false));
|
||||||
private static readonly BindableProperty NameProperty = BindableProperty.Create(nameof(Name), typeof(string), typeof(AddBillPage));
|
private static readonly BindableProperty AmountProperty = Helper.Create<string, AddBillPage>(nameof(Amount));
|
||||||
private static readonly BindableProperty CategoryNameProperty = BindableProperty.Create(nameof(CategoryName), typeof(string), typeof(AddBillPage));
|
private static readonly BindableProperty NameProperty = Helper.Create<string, AddBillPage>(nameof(Name), defaultValue: string.Empty);
|
||||||
private static readonly BindableProperty WalletNameProperty = BindableProperty.Create(nameof(WalletName), typeof(string), typeof(AddBillPage));
|
private static readonly BindableProperty CategoryProperty = Helper.Create<Category, AddBillPage>(nameof(Category));
|
||||||
private static readonly BindableProperty StoreProperty = BindableProperty.Create(nameof(Store), typeof(string), typeof(AddBillPage));
|
private static readonly BindableProperty WalletProperty = Helper.Create<Account, AddBillPage>(nameof(Wallet));
|
||||||
private static readonly BindableProperty CreatedDateProperty = BindableProperty.Create(nameof(CreatedDate), typeof(DateTime), typeof(AddBillPage));
|
private static readonly BindableProperty StoreProperty = Helper.Create<string, AddBillPage>(nameof(Store));
|
||||||
private static readonly BindableProperty CreatedTimeProperty = BindableProperty.Create(nameof(CreatedTime), typeof(TimeSpan), typeof(AddBillPage));
|
private static readonly BindableProperty CreatedDateProperty = Helper.Create<DateTime, AddBillPage>(nameof(CreatedDate));
|
||||||
private static readonly BindableProperty NoteProperty = BindableProperty.Create(nameof(Note), typeof(string), typeof(AddBillPage));
|
private static readonly BindableProperty CreatedTimeProperty = Helper.Create<TimeSpan, AddBillPage>(nameof(CreatedTime));
|
||||||
|
private static readonly BindableProperty NoteProperty = Helper.Create<string, AddBillPage>(nameof(Note));
|
||||||
|
private static readonly BindableProperty ViewLocationProperty = Helper.Create<Command, AddBillPage>(nameof(ViewLocation), defaultValue: new Command(() => { }, () => false));
|
||||||
|
|
||||||
|
public Command CheckBill => (Command)GetValue(CheckBillProperty);
|
||||||
public string Amount
|
public string Amount
|
||||||
{
|
{
|
||||||
get => (string)GetValue(AmountProperty);
|
get => (string)GetValue(AmountProperty);
|
||||||
@ -29,8 +36,8 @@ namespace Billing.Views
|
|||||||
get => (string)GetValue(NameProperty);
|
get => (string)GetValue(NameProperty);
|
||||||
set => SetValue(NameProperty, value);
|
set => SetValue(NameProperty, value);
|
||||||
}
|
}
|
||||||
public string CategoryName => (string)GetValue(CategoryNameProperty);
|
public Category Category => (Category)GetValue(CategoryProperty);
|
||||||
public string WalletName => (string)GetValue(WalletNameProperty);
|
public Account Wallet => (Account)GetValue(WalletProperty);
|
||||||
public string Store
|
public string Store
|
||||||
{
|
{
|
||||||
get => (string)GetValue(StoreProperty);
|
get => (string)GetValue(StoreProperty);
|
||||||
@ -52,22 +59,23 @@ namespace Billing.Views
|
|||||||
set => SetValue(NoteProperty, value);
|
set => SetValue(NoteProperty, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Command CheckBill { get; }
|
|
||||||
public Command SelectCategory { get; }
|
public Command SelectCategory { get; }
|
||||||
public Command SelectWallet { get; }
|
public Command SelectWallet { get; }
|
||||||
|
public Command ViewLocation => (Command)GetValue(ViewLocationProperty);
|
||||||
|
|
||||||
public event EventHandler<Bill> BillChecked;
|
public event EventHandler<Bill> BillChecked;
|
||||||
|
|
||||||
private readonly Bill bill;
|
private readonly Bill bill;
|
||||||
private readonly DateTime createDate;
|
private readonly DateTime createDate;
|
||||||
|
|
||||||
private int walletId;
|
private bool categoryChanged;
|
||||||
private int categoryId;
|
private CancellationTokenSource tokenSource;
|
||||||
|
private Location location;
|
||||||
|
|
||||||
|
public AddBillPage() : this(DateTime.Today) { }
|
||||||
public AddBillPage(DateTime date)
|
public AddBillPage(DateTime date)
|
||||||
{
|
{
|
||||||
createDate = date;
|
createDate = date;
|
||||||
CheckBill = new Command(OnCheckBill);
|
|
||||||
SelectCategory = new Command(OnSelectCategory);
|
SelectCategory = new Command(OnSelectCategory);
|
||||||
SelectWallet = new Command(OnSelectWallet);
|
SelectWallet = new Command(OnSelectWallet);
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
@ -79,25 +87,38 @@ namespace Billing.Views
|
|||||||
public AddBillPage(Bill bill)
|
public AddBillPage(Bill bill)
|
||||||
{
|
{
|
||||||
this.bill = bill;
|
this.bill = bill;
|
||||||
CheckBill = new Command(OnCheckBill);
|
|
||||||
SelectCategory = new Command(OnSelectCategory);
|
SelectCategory = new Command(OnSelectCategory);
|
||||||
SelectWallet = new Command(OnSelectWallet);
|
SelectWallet = new Command(OnSelectWallet);
|
||||||
|
#if __IOS__
|
||||||
|
if (bill != null && bill.Latitude != null && bill.Longitude != null)
|
||||||
|
{
|
||||||
|
SetValue(ViewLocationProperty, new Command(OnViewLocation));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
Title = Resource.EditBill;
|
Title = Resource.EditBill;
|
||||||
|
|
||||||
Initial();
|
Initial();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void OnDisappearing()
|
||||||
|
{
|
||||||
|
if (tokenSource != null && !tokenSource.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
tokenSource.Cancel();
|
||||||
|
}
|
||||||
|
base.OnDisappearing();
|
||||||
|
}
|
||||||
|
|
||||||
private void Initial()
|
private void Initial()
|
||||||
{
|
{
|
||||||
if (bill != null)
|
if (bill != null)
|
||||||
{
|
{
|
||||||
Amount = Math.Abs(bill.Amount).ToString(CultureInfo.InvariantCulture);
|
Amount = Math.Abs(bill.Amount).ToString(CultureInfo.InvariantCulture);
|
||||||
Name = bill.Name;
|
Name = bill.Name;
|
||||||
walletId = bill.WalletId;
|
SetValue(WalletProperty, App.Accounts.FirstOrDefault(a => a.Id == bill.WalletId) ?? Account.Empty);
|
||||||
categoryId = bill.CategoryId;
|
SetValue(CategoryProperty, App.Categories.FirstOrDefault(c => c.Id == bill.CategoryId) ?? Category.Empty);
|
||||||
SetValue(WalletNameProperty, App.Accounts.FirstOrDefault(a => a.Id == walletId)?.Name);
|
categoryChanged = true;
|
||||||
SetValue(CategoryNameProperty, App.Categories.FirstOrDefault(c => c.Id == categoryId)?.Name);
|
|
||||||
Store = bill.Store;
|
Store = bill.Store;
|
||||||
CreatedDate = bill.CreateTime.Date;
|
CreatedDate = bill.CreateTime.Date;
|
||||||
CreatedTime = bill.CreateTime.TimeOfDay;
|
CreatedTime = bill.CreateTime.TimeOfDay;
|
||||||
@ -105,26 +126,59 @@ namespace Billing.Views
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var first = App.Accounts.First();
|
SetValue(WalletProperty, App.Accounts.FirstOrDefault() ?? Account.Empty);
|
||||||
walletId = first.Id;
|
SetValue(CategoryProperty, App.Categories.FirstOrDefault() ?? Category.Empty);
|
||||||
SetValue(WalletNameProperty, first.Name);
|
|
||||||
var firstCategory = App.Categories.First();
|
|
||||||
categoryId = firstCategory.Id;
|
|
||||||
SetValue(CategoryNameProperty, firstCategory.Name);
|
|
||||||
CreatedDate = createDate.Date;
|
CreatedDate = createDate.Date;
|
||||||
CreatedTime = DateTime.Now.TimeOfDay;
|
CreatedTime = DateTime.Now.TimeOfDay;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool focused;
|
protected override void OnLoaded()
|
||||||
|
|
||||||
public override void OnLoaded()
|
|
||||||
{
|
{
|
||||||
if (!focused)
|
if (bill == null)
|
||||||
{
|
{
|
||||||
focused = true;
|
|
||||||
editorAmount.SetFocus();
|
editorAmount.SetFocus();
|
||||||
}
|
}
|
||||||
|
if (bill == null && App.SaveLocation)
|
||||||
|
{
|
||||||
|
_ = GetCurrentLocation();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SetValue(CheckBillProperty, new Command(OnCheckBill));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task GetCurrentLocation()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var request = new GeolocationRequest(GeolocationAccuracy.Best, TimeSpan.FromSeconds(10));
|
||||||
|
tokenSource = new CancellationTokenSource();
|
||||||
|
var status = await Helper.CheckAndRequestPermissionAsync<Permissions.LocationWhenInUse>();
|
||||||
|
if (status != PermissionStatus.Granted)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
location = await Geolocation.GetLocationAsync(request, tokenSource.Token);
|
||||||
|
#if __IOS__
|
||||||
|
if (bill == null)
|
||||||
|
{
|
||||||
|
SetValue(ViewLocationProperty, new Command(OnViewLocation));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
catch (FeatureNotSupportedException) { }
|
||||||
|
catch (FeatureNotEnabledException) { }
|
||||||
|
catch (PermissionException) { }
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Helper.Error("location.get", ex);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
SetValue(CheckBillProperty, new Command(OnCheckBill));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void OnCheckBill()
|
private async void OnCheckBill()
|
||||||
@ -144,8 +198,9 @@ namespace Billing.Views
|
|||||||
await this.ShowMessage(Resource.AmountRequired);
|
await this.ShowMessage(Resource.AmountRequired);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
var category = Category;
|
||||||
|
var wallet = Wallet;
|
||||||
amount = Math.Abs(amount);
|
amount = Math.Abs(amount);
|
||||||
var category = App.Categories.FirstOrDefault(c => c.Id == categoryId);
|
|
||||||
if (category.Type == CategoryType.Spending)
|
if (category.Type == CategoryType.Spending)
|
||||||
{
|
{
|
||||||
amount *= -1;
|
amount *= -1;
|
||||||
@ -156,27 +211,42 @@ namespace Billing.Views
|
|||||||
{
|
{
|
||||||
name = category.Name;
|
name = category.Name;
|
||||||
}
|
}
|
||||||
if (bill != null)
|
Bill b = bill;
|
||||||
{
|
if (b == null)
|
||||||
bill.Amount = amount;
|
{
|
||||||
bill.Name = name;
|
b = new Bill();
|
||||||
bill.CategoryId = categoryId;
|
}
|
||||||
bill.WalletId = walletId;
|
b.Amount = amount;
|
||||||
bill.CreateTime = CreatedDate.Date.Add(CreatedTime);
|
b.Name = name;
|
||||||
bill.Store = Store;
|
b.CategoryId = category.Id;
|
||||||
bill.Note = Note;
|
b.WalletId = wallet.Id;
|
||||||
|
b.CreateTime = CreatedDate.Date.Add(CreatedTime);
|
||||||
|
b.Store = Store;
|
||||||
|
b.Note = Note;
|
||||||
|
if (location != null)
|
||||||
|
{
|
||||||
|
b.Latitude = location.Latitude;
|
||||||
|
b.Longitude = location.Longitude;
|
||||||
|
b.Altitude = location.Altitude;
|
||||||
|
b.Accuracy = location.Accuracy;
|
||||||
|
b.IsGps = location.IsFromMockProvider;
|
||||||
|
}
|
||||||
|
BillChecked?.Invoke(this, b);
|
||||||
|
|
||||||
|
category.LastAccountId = wallet.Id;
|
||||||
|
category.LastUsed = DateTime.Now;
|
||||||
|
|
||||||
|
await StoreHelper.SaveCategoryItemAsync(category);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
HapticFeedback.Perform();
|
||||||
|
}
|
||||||
|
catch (FeatureNotSupportedException) { }
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Helper.Error("haptic.feedback", ex);
|
||||||
}
|
}
|
||||||
BillChecked?.Invoke(this, bill ?? new Bill
|
|
||||||
{
|
|
||||||
Id = -1,
|
|
||||||
Amount = amount,
|
|
||||||
Name = name,
|
|
||||||
CategoryId = categoryId,
|
|
||||||
WalletId = walletId,
|
|
||||||
CreateTime = CreatedDate.Date.Add(CreatedTime),
|
|
||||||
Store = Store,
|
|
||||||
Note = Note
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -188,7 +258,7 @@ namespace Billing.Views
|
|||||||
}
|
}
|
||||||
using (Tap.Start())
|
using (Tap.Start())
|
||||||
{
|
{
|
||||||
var page = new CategorySelectPage(categoryId);
|
var page = new CategorySelectPage(categoryChanged ? Category.Id : -1);
|
||||||
page.CategoryTapped += CategorySelectPage_Tapped;
|
page.CategoryTapped += CategorySelectPage_Tapped;
|
||||||
await Navigation.PushAsync(page);
|
await Navigation.PushAsync(page);
|
||||||
}
|
}
|
||||||
@ -196,8 +266,16 @@ namespace Billing.Views
|
|||||||
|
|
||||||
private void CategorySelectPage_Tapped(object sender, UICategory e)
|
private void CategorySelectPage_Tapped(object sender, UICategory e)
|
||||||
{
|
{
|
||||||
categoryId = e.Category.Id;
|
SetValue(CategoryProperty, e.Category);
|
||||||
SetValue(CategoryNameProperty, e.Name);
|
categoryChanged = true;
|
||||||
|
if (e.Category.LastAccountId != null)
|
||||||
|
{
|
||||||
|
var wallet = App.Accounts.FirstOrDefault(a => a.Id == e.Category.LastAccountId.Value);
|
||||||
|
if (wallet != null)
|
||||||
|
{
|
||||||
|
SetValue(WalletProperty, wallet);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void OnSelectWallet()
|
private async void OnSelectWallet()
|
||||||
@ -208,22 +286,39 @@ namespace Billing.Views
|
|||||||
}
|
}
|
||||||
using (Tap.Start())
|
using (Tap.Start())
|
||||||
{
|
{
|
||||||
var source = App.Accounts.Select(a => new SelectItem<int>
|
var page = new ItemSelectPage<Account>(App.Accounts);
|
||||||
{
|
|
||||||
Value = a.Id,
|
|
||||||
Name = a.Name,
|
|
||||||
Icon = a.Icon
|
|
||||||
});
|
|
||||||
var page = new ItemSelectPage<SelectItem<int>>(source);
|
|
||||||
page.ItemTapped += Wallet_ItemTapped;
|
page.ItemTapped += Wallet_ItemTapped;
|
||||||
await Navigation.PushAsync(page);
|
await Navigation.PushAsync(page);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Wallet_ItemTapped(object sender, SelectItem<int> account)
|
private void Wallet_ItemTapped(object sender, Account account)
|
||||||
{
|
{
|
||||||
walletId = account.Value;
|
SetValue(WalletProperty, account);
|
||||||
SetValue(WalletNameProperty, account.Name);
|
}
|
||||||
|
|
||||||
|
private async void OnViewLocation()
|
||||||
|
{
|
||||||
|
if (bill == null && location == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (Tap.IsBusy)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
using (Tap.Start())
|
||||||
|
{
|
||||||
|
var page = new ViewLocationPage(bill ?? new Bill
|
||||||
|
{
|
||||||
|
Name = Name,
|
||||||
|
Store = Store,
|
||||||
|
Longitude = location.Longitude,
|
||||||
|
Latitude = location.Latitude
|
||||||
|
});
|
||||||
|
page.Synced += (sender, loc) => location = loc;
|
||||||
|
await Navigation.PushAsync(page);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -41,7 +41,7 @@
|
|||||||
<ViewCell Height="120">
|
<ViewCell Height="120">
|
||||||
<Grid BackgroundColor="{DynamicResource OptionTintColor}"
|
<Grid BackgroundColor="{DynamicResource OptionTintColor}"
|
||||||
ColumnDefinitions=".35*, .65*" Padding="10">
|
ColumnDefinitions=".35*, .65*" Padding="10">
|
||||||
<ui:ColorPicker Grid.Column="1" ColorChanged="ColorPicker_ColorChanged"/>
|
<ui:ColorPicker Grid.Column="1" Command="{Binding ColorPickerCommand}"/>
|
||||||
</Grid>
|
</Grid>
|
||||||
</ViewCell>
|
</ViewCell>
|
||||||
</TableSection>
|
</TableSection>
|
||||||
|
@ -10,10 +10,10 @@ namespace Billing.Views
|
|||||||
{
|
{
|
||||||
public partial class AddCategoryPage : BillingPage
|
public partial class AddCategoryPage : BillingPage
|
||||||
{
|
{
|
||||||
private static readonly BindableProperty CategoryNameProperty = BindableProperty.Create(nameof(CategoryName), typeof(string), typeof(AddCategoryPage));
|
private static readonly BindableProperty CategoryNameProperty = Helper.Create<string, AddCategoryPage>(nameof(CategoryName));
|
||||||
private static readonly BindableProperty CategoryIconProperty = BindableProperty.Create(nameof(CategoryIcon), typeof(string), typeof(AddCategoryPage));
|
private static readonly BindableProperty CategoryIconProperty = Helper.Create<string, AddCategoryPage>(nameof(CategoryIcon));
|
||||||
private static readonly BindableProperty TintColorProperty = BindableProperty.Create(nameof(TintColor), typeof(Color), typeof(AddCategoryPage));
|
private static readonly BindableProperty TintColorProperty = Helper.Create<Color, AddCategoryPage>(nameof(TintColor));
|
||||||
private static readonly BindableProperty TintColorStringProperty = BindableProperty.Create(nameof(TintColorString), typeof(string), typeof(AddCategoryPage));
|
private static readonly BindableProperty TintColorStringProperty = Helper.Create<string, AddCategoryPage>(nameof(TintColorString));
|
||||||
|
|
||||||
public string CategoryName
|
public string CategoryName
|
||||||
{
|
{
|
||||||
@ -38,6 +38,7 @@ namespace Billing.Views
|
|||||||
|
|
||||||
public Command CheckCategory { get; }
|
public Command CheckCategory { get; }
|
||||||
public Command SelectIcon { get; }
|
public Command SelectIcon { get; }
|
||||||
|
public Command ColorPickerCommand { get; }
|
||||||
|
|
||||||
public event EventHandler<Category> CategoryChecked;
|
public event EventHandler<Category> CategoryChecked;
|
||||||
|
|
||||||
@ -55,14 +56,13 @@ namespace Billing.Views
|
|||||||
{
|
{
|
||||||
CategoryName = category.Name;
|
CategoryName = category.Name;
|
||||||
CategoryIcon = category.Icon;
|
CategoryIcon = category.Icon;
|
||||||
if (category.TintColor == Color.Transparent ||
|
if (category.TintColor.IsTransparent())
|
||||||
category.TintColor == default)
|
|
||||||
{
|
{
|
||||||
TintColor = BaseTheme.CurrentPrimaryColor;
|
TintColor = BaseTheme.CurrentPrimaryColor;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
TintColor = category.TintColor;
|
TintColor = category.TintColor.ToColor();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -73,25 +73,23 @@ namespace Billing.Views
|
|||||||
|
|
||||||
CheckCategory = new Command(OnCheckCategory);
|
CheckCategory = new Command(OnCheckCategory);
|
||||||
SelectIcon = new Command(OnSelectIcon);
|
SelectIcon = new Command(OnSelectIcon);
|
||||||
|
ColorPickerCommand = new Command(OnColorPickerCommand);
|
||||||
|
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool focused;
|
protected override void OnLoaded()
|
||||||
|
|
||||||
public override void OnLoaded()
|
|
||||||
{
|
{
|
||||||
if (!focused)
|
editorName.SetFocus();
|
||||||
{
|
|
||||||
focused = true;
|
|
||||||
editorName.SetFocus();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ColorPicker_ColorChanged(object sender, Color e)
|
private void OnColorPickerCommand(object o)
|
||||||
{
|
{
|
||||||
TintColor = e;
|
if (o is Color color)
|
||||||
TintColorString = Helper.WrapColorString(e.ToHex());
|
{
|
||||||
|
TintColor = color;
|
||||||
|
TintColorString = Helper.WrapColorString(color.ToHex());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void OnCheckCategory()
|
private async void OnCheckCategory()
|
||||||
@ -104,15 +102,15 @@ namespace Billing.Views
|
|||||||
{
|
{
|
||||||
var currentColor = BaseTheme.CurrentPrimaryColor;
|
var currentColor = BaseTheme.CurrentPrimaryColor;
|
||||||
var tintColor = TintColor;
|
var tintColor = TintColor;
|
||||||
|
var color = (tintColor == currentColor ? Color.Transparent : tintColor).ToLong();
|
||||||
var category = App.Categories.FirstOrDefault(c => c.Id == categoryId);
|
var category = App.Categories.FirstOrDefault(c => c.Id == categoryId);
|
||||||
if (category == null)
|
if (category == null)
|
||||||
{
|
{
|
||||||
CategoryChecked?.Invoke(this, new Category
|
CategoryChecked?.Invoke(this, new Category
|
||||||
{
|
{
|
||||||
Id = -1,
|
|
||||||
Name = CategoryName,
|
Name = CategoryName,
|
||||||
Icon = CategoryIcon,
|
Icon = CategoryIcon,
|
||||||
TintColor = tintColor == currentColor ? Color.Transparent : tintColor,
|
TintColor = color,
|
||||||
ParentId = parent?.Id,
|
ParentId = parent?.Id,
|
||||||
Type = parent?.Type ?? CategoryType.Spending
|
Type = parent?.Type ?? CategoryType.Spending
|
||||||
});
|
});
|
||||||
@ -121,7 +119,7 @@ namespace Billing.Views
|
|||||||
{
|
{
|
||||||
category.Name = CategoryName;
|
category.Name = CategoryName;
|
||||||
category.Icon = CategoryIcon;
|
category.Icon = CategoryIcon;
|
||||||
category.TintColor = tintColor == currentColor ? Color.Transparent : tintColor;
|
category.TintColor = color;
|
||||||
CategoryChecked?.Invoke(this, category);
|
CategoryChecked?.Invoke(this, category);
|
||||||
}
|
}
|
||||||
await Navigation.PopAsync();
|
await Navigation.PopAsync();
|
||||||
|
@ -20,6 +20,7 @@
|
|||||||
<ui:BalanceColorConverter x:Key="colorConverter"/>
|
<ui:BalanceColorConverter x:Key="colorConverter"/>
|
||||||
<ui:TimeConverter x:Key="timeConverter"/>
|
<ui:TimeConverter x:Key="timeConverter"/>
|
||||||
<ui:IconConverter x:Key="iconConverter"/>
|
<ui:IconConverter x:Key="iconConverter"/>
|
||||||
|
<ui:TintColorConverter x:Key="tintColorConverter"/>
|
||||||
</ContentPage.Resources>
|
</ContentPage.Resources>
|
||||||
|
|
||||||
<Shell.TitleView>
|
<Shell.TitleView>
|
||||||
@ -31,13 +32,8 @@
|
|||||||
VerticalOptions="Center" LongCommand="{Binding TitleLongPressed}">
|
VerticalOptions="Center" LongCommand="{Binding TitleLongPressed}">
|
||||||
<Label Text="{Binding SelectedDate, Converter={StaticResource titleDateConverter}}"
|
<Label Text="{Binding SelectedDate, Converter={StaticResource titleDateConverter}}"
|
||||||
TextColor="{DynamicResource PrimaryColor}"
|
TextColor="{DynamicResource PrimaryColor}"
|
||||||
FontSize="{OnPlatform Android=20, iOS=18}">
|
FontFamily="{x:Static ui:Definition.SemiBoldFontFamily}"
|
||||||
<Label.FontFamily>
|
FontSize="{OnPlatform Android=20, iOS=18}"/>
|
||||||
<OnPlatform x:TypeArguments="x:String"
|
|
||||||
Android="OpenSans-SemiBold.ttf#OpenSans-SemiBold"
|
|
||||||
iOS="OpenSans-Bold"/>
|
|
||||||
</Label.FontFamily>
|
|
||||||
</Label>
|
|
||||||
</ui:LongPressGrid>
|
</ui:LongPressGrid>
|
||||||
<ui:TintImageButton Source="calendar.png" WidthRequest="20" HeightRequest="20"
|
<ui:TintImageButton Source="calendar.png" WidthRequest="20" HeightRequest="20"
|
||||||
VerticalOptions="Center" HorizontalOptions="Start"
|
VerticalOptions="Center" HorizontalOptions="Start"
|
||||||
@ -101,6 +97,7 @@
|
|||||||
CommandParameter="{Binding .}"/>
|
CommandParameter="{Binding .}"/>
|
||||||
</Grid.GestureRecognizers>
|
</Grid.GestureRecognizers>
|
||||||
<ui:TintImage Source="{Binding Icon, Converter={StaticResource iconConverter}}"
|
<ui:TintImage Source="{Binding Icon, Converter={StaticResource iconConverter}}"
|
||||||
|
ui:TintHelper.TintColor="{Binding TintColor, Converter={StaticResource tintColorConverter}}"
|
||||||
WidthRequest="26" HeightRequest="20" VerticalOptions="Center"/>
|
WidthRequest="26" HeightRequest="20" VerticalOptions="Center"/>
|
||||||
<Label Grid.Column="1" Text="{Binding Name}" TextColor="{DynamicResource TextColor}"
|
<Label Grid.Column="1" Text="{Binding Name}" TextColor="{DynamicResource TextColor}"
|
||||||
VerticalOptions="Center"
|
VerticalOptions="Center"
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using Billing.Models;
|
using Billing.Models;
|
||||||
|
using Billing.Store;
|
||||||
using Billing.UI;
|
using Billing.UI;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
@ -44,8 +45,6 @@ namespace Billing.Views
|
|||||||
public Command EditBilling { get; }
|
public Command EditBilling { get; }
|
||||||
public Command DeleteBilling { get; }
|
public Command DeleteBilling { get; }
|
||||||
|
|
||||||
private bool initialized;
|
|
||||||
|
|
||||||
public BillPage()
|
public BillPage()
|
||||||
{
|
{
|
||||||
TitleDateTap = new Command(OnTitleDateTapped);
|
TitleDateTap = new Command(OnTitleDateTapped);
|
||||||
@ -56,26 +55,26 @@ namespace Billing.Views
|
|||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void OnLoaded()
|
protected override void OnLoaded()
|
||||||
{
|
{
|
||||||
if (!initialized)
|
billingDate.SetDateTime(DateTime.Today);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnRefresh()
|
||||||
|
{
|
||||||
|
Task.Run(() =>
|
||||||
{
|
{
|
||||||
initialized = true;
|
var bills = App.Bills.Where(b => Helper.IsSameDay(b.CreateTime, SelectedDate));
|
||||||
billingDate.SetDateTime(DateTime.Today);
|
Bills = new List<UIBill>(bills.OrderBy(b => b.CreateTime).Select(b => Helper.WrapBill(b)));
|
||||||
}
|
RefreshBalance(Bills);
|
||||||
|
MainThread.BeginInvokeOnMainThread(async () => await scrollView.ScrollToAsync(0, 0, true));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnDateSelected(object sender, DateEventArgs e)
|
private void OnDateSelected(object sender, DateEventArgs e)
|
||||||
{
|
{
|
||||||
SelectedDate = e.Date;
|
SelectedDate = e.Date;
|
||||||
|
OnRefresh();
|
||||||
Task.Run(() =>
|
|
||||||
{
|
|
||||||
var bills = App.Bills.Where(b => Helper.IsSameDay(b.CreateTime, e.Date));
|
|
||||||
Bills = new List<UIBill>(bills.OrderBy(b => b.CreateTime).Select(b => Helper.WrapBill(b)));
|
|
||||||
RefreshBalance(Bills);
|
|
||||||
MainThread.BeginInvokeOnMainThread(async () => await scrollView.ScrollToAsync(0, 0, true));
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RefreshBalance(List<UIBill> bills)
|
private void RefreshBalance(List<UIBill> bills)
|
||||||
@ -90,7 +89,9 @@ namespace Billing.Views
|
|||||||
|
|
||||||
private void UpdateBill(UIBill bill)
|
private void UpdateBill(UIBill bill)
|
||||||
{
|
{
|
||||||
bill.Icon = App.Categories.FirstOrDefault(c => c.Id == bill.Bill.CategoryId)?.Icon ?? BaseModel.ICON_DEFAULT;
|
var category = App.Categories.FirstOrDefault(c => c.Id == bill.Bill.CategoryId);
|
||||||
|
bill.Icon = category?.Icon ?? Definition.DefaultIcon;
|
||||||
|
bill.TintColor = category?.TintColor ?? Definition.TransparentColor;
|
||||||
bill.Name = bill.Bill.Name;
|
bill.Name = bill.Bill.Name;
|
||||||
bill.DateCreation = bill.Bill.CreateTime;
|
bill.DateCreation = bill.Bill.CreateTime;
|
||||||
bill.Amount = bill.Bill.Amount;
|
bill.Amount = bill.Bill.Amount;
|
||||||
@ -113,6 +114,16 @@ namespace Billing.Views
|
|||||||
private void OnTitleDateLongPressed()
|
private void OnTitleDateLongPressed()
|
||||||
{
|
{
|
||||||
billingDate.SetDateTime(DateTime.Today);
|
billingDate.SetDateTime(DateTime.Today);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
HapticFeedback.Perform();
|
||||||
|
}
|
||||||
|
catch (FeatureNotSupportedException) { }
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Helper.Error("haptic.feedback", ex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void OnEditBilling(object o)
|
private async void OnEditBilling(object o)
|
||||||
@ -161,27 +172,19 @@ namespace Billing.Views
|
|||||||
App.Bills.Remove(bill.Bill);
|
App.Bills.Remove(bill.Bill);
|
||||||
billsLayout.Refresh(bills);
|
billsLayout.Refresh(bills);
|
||||||
RefreshBalance(bills);
|
RefreshBalance(bills);
|
||||||
|
|
||||||
|
RankPage.Instance?.SetNeedRefresh();
|
||||||
|
|
||||||
_ = Task.Run(App.WriteBills);
|
await StoreHelper.DeleteBillItemAsync(bill.Bill);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnBillChecked(object sender, Bill e)
|
private async void OnBillChecked(object sender, Bill e)
|
||||||
{
|
{
|
||||||
if (e.Id < 0)
|
if (e.Id <= 0)
|
||||||
{
|
{
|
||||||
int maxId;
|
|
||||||
if (App.Bills.Count > 0)
|
|
||||||
{
|
|
||||||
maxId = App.Bills.Max(b => b.Id);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
maxId = -1;
|
|
||||||
}
|
|
||||||
e.Id = maxId + 1;
|
|
||||||
App.Bills.Add(e);
|
App.Bills.Add(e);
|
||||||
var bills = Bills;
|
var bills = Bills;
|
||||||
bills.Add(Helper.WrapBill(e));
|
bills.Add(Helper.WrapBill(e));
|
||||||
@ -192,28 +195,50 @@ namespace Billing.Views
|
|||||||
var bill = Bills.FirstOrDefault(b => b.Bill == e);
|
var bill = Bills.FirstOrDefault(b => b.Bill == e);
|
||||||
if (bill != null)
|
if (bill != null)
|
||||||
{
|
{
|
||||||
UpdateBill(bill);
|
if (bill.DateCreation != e.CreateTime)
|
||||||
|
{
|
||||||
|
var bills = App.Bills.Where(b => Helper.IsSameDay(b.CreateTime, SelectedDate));
|
||||||
|
Bills = new List<UIBill>(bills.OrderBy(b => b.CreateTime).Select(b => Helper.WrapBill(b)));
|
||||||
|
RefreshBalance(Bills);
|
||||||
|
|
||||||
|
RankPage.Instance?.SetNeedRefresh();
|
||||||
|
|
||||||
|
await StoreHelper.SaveBillItemAsync(e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
UpdateBill(bill);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
RefreshBalance(Bills);
|
RefreshBalance(Bills);
|
||||||
|
|
||||||
Task.Run(App.WriteBills);
|
RankPage.Instance?.SetNeedRefresh();
|
||||||
|
|
||||||
|
await StoreHelper.SaveBillItemAsync(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class UIBill : BindableObject
|
public class UIBill : BindableObject
|
||||||
{
|
{
|
||||||
public static readonly BindableProperty IconProperty = BindableProperty.Create(nameof(Icon), typeof(string), typeof(UIBill));
|
public static readonly BindableProperty IconProperty = Helper.Create<string, UIBill>(nameof(Icon));
|
||||||
public static readonly BindableProperty NameProperty = BindableProperty.Create(nameof(Name), typeof(string), typeof(UIBill));
|
public static readonly BindableProperty TintColorProperty = Helper.Create<long, UIBill>(nameof(TintColor));
|
||||||
public static readonly BindableProperty DateCreationProperty = BindableProperty.Create(nameof(DateCreation), typeof(DateTime), typeof(UIBill));
|
public static readonly BindableProperty NameProperty = Helper.Create<string, UIBill>(nameof(Name));
|
||||||
public static readonly BindableProperty AmountProperty = BindableProperty.Create(nameof(Amount), typeof(decimal), typeof(UIBill));
|
public static readonly BindableProperty DateCreationProperty = Helper.Create<DateTime, UIBill>(nameof(DateCreation));
|
||||||
public static readonly BindableProperty WalletProperty = BindableProperty.Create(nameof(Wallet), typeof(string), typeof(UIBill));
|
public static readonly BindableProperty AmountProperty = Helper.Create<decimal, UIBill>(nameof(Amount));
|
||||||
|
public static readonly BindableProperty WalletProperty = Helper.Create<string, UIBill>(nameof(Wallet));
|
||||||
|
|
||||||
public string Icon
|
public string Icon
|
||||||
{
|
{
|
||||||
get => (string)GetValue(IconProperty);
|
get => (string)GetValue(IconProperty);
|
||||||
set => SetValue(IconProperty, value);
|
set => SetValue(IconProperty, value);
|
||||||
}
|
}
|
||||||
|
public long TintColor
|
||||||
|
{
|
||||||
|
get => (long)GetValue(TintColorProperty);
|
||||||
|
set => SetValue(TintColorProperty, value);
|
||||||
|
}
|
||||||
public string Name
|
public string Name
|
||||||
{
|
{
|
||||||
get => (string)GetValue(NameProperty);
|
get => (string)GetValue(NameProperty);
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
|
|
||||||
<ContentPage.Resources>
|
<ContentPage.Resources>
|
||||||
<ui:IconConverter x:Key="iconConverter"/>
|
<ui:IconConverter x:Key="iconConverter"/>
|
||||||
|
<ui:TintColorConverter x:Key="tintColorConverter"/>
|
||||||
</ContentPage.Resources>
|
</ContentPage.Resources>
|
||||||
|
|
||||||
<ScrollView>
|
<ScrollView>
|
||||||
@ -32,7 +33,7 @@
|
|||||||
CommandParameter="{Binding .}"/>
|
CommandParameter="{Binding .}"/>
|
||||||
</Grid.GestureRecognizers>
|
</Grid.GestureRecognizers>
|
||||||
<ui:TintImage Source="{Binding Icon, Converter={StaticResource iconConverter}}"
|
<ui:TintImage Source="{Binding Icon, Converter={StaticResource iconConverter}}"
|
||||||
ui:TintHelper.TintColor="{Binding TintColor}"
|
ui:TintHelper.TintColor="{Binding TintColor, Converter={StaticResource tintColorConverter}}"
|
||||||
WidthRequest="26" HeightRequest="20" VerticalOptions="Center"/>
|
WidthRequest="26" HeightRequest="20" VerticalOptions="Center"/>
|
||||||
<Label Grid.Column="1" Text="{Binding Name}" TextColor="{DynamicResource TextColor}"
|
<Label Grid.Column="1" Text="{Binding Name}" TextColor="{DynamicResource TextColor}"
|
||||||
HorizontalOptions="FillAndExpand" VerticalOptions="Center"
|
HorizontalOptions="FillAndExpand" VerticalOptions="Center"
|
||||||
|
@ -1,19 +1,18 @@
|
|||||||
using Billing.Languages;
|
using Billing.Languages;
|
||||||
using Billing.Models;
|
using Billing.Models;
|
||||||
using Billing.Themes;
|
using Billing.Store;
|
||||||
using Billing.UI;
|
using Billing.UI;
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Xamarin.Forms;
|
using Xamarin.Forms;
|
||||||
|
|
||||||
namespace Billing.Views
|
namespace Billing.Views
|
||||||
{
|
{
|
||||||
public partial class CategoryPage : BillingPage
|
public partial class CategoryPage : BillingPage
|
||||||
{
|
{
|
||||||
private static readonly BindableProperty CategoriesProperty = BindableProperty.Create(nameof(Categories), typeof(IList), typeof(CategoryPage));
|
private static readonly BindableProperty CategoriesProperty = Helper.Create<IList, CategoryPage>(nameof(Categories));
|
||||||
private static readonly BindableProperty IsTopCategoryProperty = BindableProperty.Create(nameof(IsTopCategory), typeof(bool), typeof(CategoryPage));
|
private static readonly BindableProperty IsTopCategoryProperty = Helper.Create<bool, CategoryPage>(nameof(IsTopCategory));
|
||||||
|
|
||||||
public IList Categories
|
public IList Categories
|
||||||
{
|
{
|
||||||
@ -68,9 +67,7 @@ namespace Billing.Views
|
|||||||
Icon = category.Icon,
|
Icon = category.Icon,
|
||||||
Name = category.Name,
|
Name = category.Name,
|
||||||
IsTopCategory = IsTopCategory,
|
IsTopCategory = IsTopCategory,
|
||||||
TintColor = category.TintColor == Color.Transparent || category.TintColor == default ?
|
TintColor = category.TintColor
|
||||||
BaseTheme.CurrentPrimaryColor :
|
|
||||||
category.TintColor
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -112,7 +109,7 @@ namespace Billing.Views
|
|||||||
Categories.Remove(c);
|
Categories.Remove(c);
|
||||||
groupLayout.Refresh(Categories);
|
groupLayout.Refresh(Categories);
|
||||||
App.Categories.Remove(c.Category);
|
App.Categories.Remove(c.Category);
|
||||||
_ = Task.Run(App.WriteCategories);
|
await StoreHelper.DeleteCategoryItemAsync(c.Category);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -144,21 +141,11 @@ namespace Billing.Views
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnCategoryChecked(object sender, Category category)
|
private async void OnCategoryChecked(object sender, Category category)
|
||||||
{
|
{
|
||||||
if (category.Id < 0)
|
if (category.Id <= 0)
|
||||||
{
|
{
|
||||||
// add
|
// add
|
||||||
int maxId;
|
|
||||||
if (App.Categories.Count > 0)
|
|
||||||
{
|
|
||||||
maxId = App.Categories.Max(b => b.Id);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
maxId = -1;
|
|
||||||
}
|
|
||||||
category.Id = maxId + 1;
|
|
||||||
App.Categories.Add(category);
|
App.Categories.Add(category);
|
||||||
Categories.Add(WrapCategory(category));
|
Categories.Add(WrapCategory(category));
|
||||||
}
|
}
|
||||||
@ -183,26 +170,24 @@ namespace Billing.Views
|
|||||||
}
|
}
|
||||||
groupLayout.Refresh(Categories);
|
groupLayout.Refresh(Categories);
|
||||||
|
|
||||||
Task.Run(App.WriteCategories);
|
await StoreHelper.SaveCategoryItemAsync(category);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateCategory(UICategory c)
|
private void UpdateCategory(UICategory c)
|
||||||
{
|
{
|
||||||
c.Name = c.Category.Name;
|
c.Name = c.Category.Name;
|
||||||
c.Icon = c.Category.Icon;
|
c.Icon = c.Category.Icon;
|
||||||
c.TintColor = c.Category.TintColor == Color.Transparent || c.Category.TintColor == default ?
|
c.TintColor = c.Category.TintColor;
|
||||||
BaseTheme.CurrentPrimaryColor :
|
|
||||||
c.Category.TintColor;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class UICategory : BindableObject
|
public class UICategory : BindableObject
|
||||||
{
|
{
|
||||||
public static readonly BindableProperty IsCheckedProperty = BindableProperty.Create(nameof(IsChecked), typeof(bool), typeof(UICategory));
|
public static readonly BindableProperty IsCheckedProperty = Helper.Create<bool, UICategory>(nameof(IsChecked));
|
||||||
public static readonly BindableProperty IconProperty = BindableProperty.Create(nameof(Icon), typeof(string), typeof(UICategory));
|
public static readonly BindableProperty IconProperty = Helper.Create<string, UICategory>(nameof(Icon));
|
||||||
public static readonly BindableProperty NameProperty = BindableProperty.Create(nameof(Name), typeof(string), typeof(UICategory));
|
public static readonly BindableProperty NameProperty = Helper.Create<string, UICategory>(nameof(Name));
|
||||||
public static readonly BindableProperty TintColorProperty = BindableProperty.Create(nameof(TintColor), typeof(Color), typeof(UICategory));
|
public static readonly BindableProperty TintColorProperty = Helper.Create<long, UICategory>(nameof(TintColor));
|
||||||
public static readonly BindableProperty IsTopCategoryProperty = BindableProperty.Create(nameof(IsTopCategory), typeof(bool), typeof(UICategory));
|
public static readonly BindableProperty IsTopCategoryProperty = Helper.Create<bool, UICategory>(nameof(IsTopCategory));
|
||||||
|
|
||||||
public bool IsChecked
|
public bool IsChecked
|
||||||
{
|
{
|
||||||
@ -219,9 +204,9 @@ namespace Billing.Views
|
|||||||
get => (string)GetValue(NameProperty);
|
get => (string)GetValue(NameProperty);
|
||||||
set => SetValue(NameProperty, value);
|
set => SetValue(NameProperty, value);
|
||||||
}
|
}
|
||||||
public Color TintColor
|
public long TintColor
|
||||||
{
|
{
|
||||||
get => (Color)GetValue(TintColorProperty);
|
get => (long)GetValue(TintColorProperty);
|
||||||
set => SetValue(TintColorProperty, value);
|
set => SetValue(TintColorProperty, value);
|
||||||
}
|
}
|
||||||
public bool IsTopCategory
|
public bool IsTopCategory
|
||||||
@ -242,6 +227,10 @@ namespace Billing.Views
|
|||||||
{
|
{
|
||||||
public string Key { get; }
|
public string Key { get; }
|
||||||
|
|
||||||
|
public CategoryGrouping(string key) : base()
|
||||||
|
{
|
||||||
|
Key = key;
|
||||||
|
}
|
||||||
public CategoryGrouping(string key, IEnumerable<UICategory> categories) : base(categories)
|
public CategoryGrouping(string key, IEnumerable<UICategory> categories) : base(categories)
|
||||||
{
|
{
|
||||||
Key = key;
|
Key = key;
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
<ContentPage.Resources>
|
<ContentPage.Resources>
|
||||||
<ui:IconConverter x:Key="iconConverter"/>
|
<ui:IconConverter x:Key="iconConverter"/>
|
||||||
<ui:SelectBackgroundColorConverter x:Key="backgroundConverter"/>
|
<ui:SelectBackgroundColorConverter x:Key="backgroundConverter"/>
|
||||||
|
<ui:TintColorConverter x:Key="tintColorConverter"/>
|
||||||
</ContentPage.Resources>
|
</ContentPage.Resources>
|
||||||
|
|
||||||
<Grid ColumnDefinitions=".5*, .5*">
|
<Grid ColumnDefinitions=".5*, .5*">
|
||||||
@ -36,7 +37,7 @@
|
|||||||
CommandParameter="{Binding .}"/>
|
CommandParameter="{Binding .}"/>
|
||||||
</Grid.GestureRecognizers>
|
</Grid.GestureRecognizers>
|
||||||
<ui:TintImage Source="{Binding Icon, Converter={StaticResource iconConverter}}"
|
<ui:TintImage Source="{Binding Icon, Converter={StaticResource iconConverter}}"
|
||||||
ui:TintHelper.TintColor="{Binding TintColor}"
|
ui:TintHelper.TintColor="{Binding TintColor, Converter={StaticResource tintColorConverter}}"
|
||||||
WidthRequest="26" HeightRequest="20" VerticalOptions="Center"/>
|
WidthRequest="26" HeightRequest="20" VerticalOptions="Center"/>
|
||||||
<Label Grid.Column="1" Text="{Binding Name}" TextColor="{DynamicResource TextColor}"
|
<Label Grid.Column="1" Text="{Binding Name}" TextColor="{DynamicResource TextColor}"
|
||||||
HorizontalOptions="FillAndExpand" VerticalOptions="Center"
|
HorizontalOptions="FillAndExpand" VerticalOptions="Center"
|
||||||
@ -58,7 +59,7 @@
|
|||||||
CommandParameter="{Binding .}"/>
|
CommandParameter="{Binding .}"/>
|
||||||
</Grid.GestureRecognizers>
|
</Grid.GestureRecognizers>
|
||||||
<ui:TintImage Source="{Binding Icon, Converter={StaticResource iconConverter}}"
|
<ui:TintImage Source="{Binding Icon, Converter={StaticResource iconConverter}}"
|
||||||
ui:TintHelper.TintColor="{Binding TintColor}"
|
ui:TintHelper.TintColor="{Binding TintColor, Converter={StaticResource tintColorConverter}}"
|
||||||
WidthRequest="26" HeightRequest="20" VerticalOptions="Center"/>
|
WidthRequest="26" HeightRequest="20" VerticalOptions="Center"/>
|
||||||
<Label Grid.Column="1" Text="{Binding Name}" TextColor="{DynamicResource TextColor}"
|
<Label Grid.Column="1" Text="{Binding Name}" TextColor="{DynamicResource TextColor}"
|
||||||
HorizontalOptions="FillAndExpand" VerticalOptions="Center"
|
HorizontalOptions="FillAndExpand" VerticalOptions="Center"
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
using Billing.Languages;
|
using Billing.Languages;
|
||||||
using Billing.Models;
|
using Billing.Models;
|
||||||
using Billing.Themes;
|
|
||||||
using Billing.UI;
|
using Billing.UI;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
@ -11,8 +10,8 @@ namespace Billing.Views
|
|||||||
{
|
{
|
||||||
public partial class CategorySelectPage : BillingPage
|
public partial class CategorySelectPage : BillingPage
|
||||||
{
|
{
|
||||||
private static readonly BindableProperty TopCategoriesProperty = BindableProperty.Create(nameof(TopCategories), typeof(List<CategoryGrouping>), typeof(CategorySelectPage));
|
private static readonly BindableProperty TopCategoriesProperty = Helper.Create<List<CategoryGrouping>, CategorySelectPage>(nameof(TopCategories));
|
||||||
private static readonly BindableProperty SubCategoriesProperty = BindableProperty.Create(nameof(SubCategories), typeof(List<UICategory>), typeof(CategorySelectPage));
|
private static readonly BindableProperty SubCategoriesProperty = Helper.Create<List<UICategory>, CategorySelectPage>(nameof(SubCategories));
|
||||||
|
|
||||||
public List<CategoryGrouping> TopCategories
|
public List<CategoryGrouping> TopCategories
|
||||||
{
|
{
|
||||||
@ -31,12 +30,10 @@ namespace Billing.Views
|
|||||||
public event EventHandler<UICategory> CategoryTapped;
|
public event EventHandler<UICategory> CategoryTapped;
|
||||||
|
|
||||||
private readonly int categoryId;
|
private readonly int categoryId;
|
||||||
private readonly Color defaultColor;
|
|
||||||
|
|
||||||
public CategorySelectPage(int id)
|
public CategorySelectPage(int id)
|
||||||
{
|
{
|
||||||
categoryId = id;
|
categoryId = id;
|
||||||
defaultColor = BaseTheme.CurrentPrimaryColor;
|
|
||||||
TapTopCategory = new Command(OnTopCategoryTapped);
|
TapTopCategory = new Command(OnTopCategoryTapped);
|
||||||
TapSubCategory = new Command(OnSubCategoryTapped);
|
TapSubCategory = new Command(OnSubCategoryTapped);
|
||||||
|
|
||||||
@ -45,6 +42,22 @@ namespace Billing.Views
|
|||||||
new(Resource.Spending, App.Categories.Where(c => c.Type == CategoryType.Spending && c.ParentId == null).Select(c => WrapCategory(c))),
|
new(Resource.Spending, App.Categories.Where(c => c.Type == CategoryType.Spending && c.ParentId == null).Select(c => WrapCategory(c))),
|
||||||
new(Resource.Income, App.Categories.Where(c => c.Type == CategoryType.Income && c.ParentId == null).Select(c => WrapCategory(c)))
|
new(Resource.Income, App.Categories.Where(c => c.Type == CategoryType.Income && c.ParentId == null).Select(c => WrapCategory(c)))
|
||||||
};
|
};
|
||||||
|
UICategory last;
|
||||||
|
if (App.Categories.Any(c => c.LastUsed != null))
|
||||||
|
{
|
||||||
|
last = new UICategory(null)
|
||||||
|
{
|
||||||
|
IsChecked = true,
|
||||||
|
Icon = "rank",
|
||||||
|
Name = Resource.LastSelected,
|
||||||
|
TintColor = Definition.TransparentColor
|
||||||
|
};
|
||||||
|
TopCategories.Insert(0, new(Resource.Recent) { last });
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
last = null;
|
||||||
|
}
|
||||||
|
|
||||||
UICategory cat;
|
UICategory cat;
|
||||||
var category = App.Categories.FirstOrDefault(c => c.Id == categoryId);
|
var category = App.Categories.FirstOrDefault(c => c.Id == categoryId);
|
||||||
@ -58,7 +71,7 @@ namespace Billing.Views
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
cat = TopCategories.SelectMany(g => g).FirstOrDefault(c => c.Category.Id == category.ParentId);
|
cat = TopCategories.SelectMany(g => g).FirstOrDefault(c => c.Category?.Id == category.ParentId) ?? last;
|
||||||
}
|
}
|
||||||
DoRefreshSubCategories(cat);
|
DoRefreshSubCategories(cat);
|
||||||
|
|
||||||
@ -72,7 +85,7 @@ namespace Billing.Views
|
|||||||
IsChecked = c.Id == categoryId,
|
IsChecked = c.Id == categoryId,
|
||||||
Icon = c.Icon,
|
Icon = c.Icon,
|
||||||
Name = c.Name,
|
Name = c.Name,
|
||||||
TintColor = c.TintColor == Color.Transparent || c.TintColor == default ? defaultColor : c.TintColor
|
TintColor = c.TintColor
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -83,7 +96,18 @@ namespace Billing.Views
|
|||||||
{
|
{
|
||||||
m.IsChecked = m == category;
|
m.IsChecked = m == category;
|
||||||
}
|
}
|
||||||
SubCategories = App.Categories.Where(c => c.ParentId == category.Category.Id).Select(c => WrapCategory(c)).ToList();
|
if (category == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (category.Category == null)
|
||||||
|
{
|
||||||
|
SubCategories = App.Categories.Where(c => c.ParentId != null && c.LastUsed != null).OrderByDescending(c => c.LastUsed).Take(10).Select(c => WrapCategory(c)).ToList();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SubCategories = App.Categories.Where(c => c.ParentId == category.Category.Id).Select(c => WrapCategory(c)).ToList();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void OnTopCategoryTapped(object o)
|
private async void OnTopCategoryTapped(object o)
|
||||||
@ -97,7 +121,7 @@ namespace Billing.Views
|
|||||||
if (o is UICategory category)
|
if (o is UICategory category)
|
||||||
{
|
{
|
||||||
DoRefreshSubCategories(category);
|
DoRefreshSubCategories(category);
|
||||||
if (SubCategories.Count == 0)
|
if (SubCategories?.Count == 0)
|
||||||
{
|
{
|
||||||
CategoryTapped?.Invoke(this, category);
|
CategoryTapped?.Invoke(this, category);
|
||||||
await Navigation.PopAsync();
|
await Navigation.PopAsync();
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
using Billing.Models;
|
using Billing.UI;
|
||||||
using Billing.UI;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@ -9,7 +8,7 @@ namespace Billing.Views
|
|||||||
{
|
{
|
||||||
public partial class IconSelectPage : BillingPage
|
public partial class IconSelectPage : BillingPage
|
||||||
{
|
{
|
||||||
public static readonly BindableProperty IconsSourceProperty = BindableProperty.Create(nameof(IconsSource), typeof(IList<BillingIcon>), typeof(IconSelectPage));
|
public static readonly BindableProperty IconsSourceProperty = Helper.Create<IList<BillingIcon>, IconSelectPage>(nameof(IconsSource));
|
||||||
|
|
||||||
public IList<BillingIcon> IconsSource
|
public IList<BillingIcon> IconsSource
|
||||||
{
|
{
|
||||||
@ -37,7 +36,7 @@ namespace Billing.Views
|
|||||||
{
|
{
|
||||||
var source = new List<BillingIcon>
|
var source = new List<BillingIcon>
|
||||||
{
|
{
|
||||||
new() { Icon = BaseModel.ICON_DEFAULT },
|
new() { Icon = Definition.DefaultIcon },
|
||||||
new() { Icon = "wallet" },
|
new() { Icon = "wallet" },
|
||||||
new() { Icon = "dollar" },
|
new() { Icon = "dollar" },
|
||||||
new() { Icon = "creditcard" },
|
new() { Icon = "creditcard" },
|
||||||
@ -67,6 +66,7 @@ namespace Billing.Views
|
|||||||
new() { Icon = "taxi" },
|
new() { Icon = "taxi" },
|
||||||
new() { Icon = "fitness" },
|
new() { Icon = "fitness" },
|
||||||
new() { Icon = "party" },
|
new() { Icon = "party" },
|
||||||
|
new() { Icon = "share" },
|
||||||
};
|
};
|
||||||
source.AddRange(IconConverter.IconPreset.Select(icon => new BillingIcon { Icon = $"#brand#{icon.Key}" }));
|
source.AddRange(IconConverter.IconPreset.Select(icon => new BillingIcon { Icon = $"#brand#{icon.Key}" }));
|
||||||
foreach (var icon in source)
|
foreach (var icon in source)
|
||||||
@ -103,7 +103,7 @@ namespace Billing.Views
|
|||||||
|
|
||||||
public class BillingIcon : BindableObject
|
public class BillingIcon : BindableObject
|
||||||
{
|
{
|
||||||
public static readonly BindableProperty IsCheckedProperty = BindableProperty.Create(nameof(IsChecked), typeof(bool), typeof(BillingIcon));
|
public static readonly BindableProperty IsCheckedProperty = Helper.Create<bool, BillingIcon>(nameof(IsChecked));
|
||||||
|
|
||||||
public bool IsChecked
|
public bool IsChecked
|
||||||
{
|
{
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
xmlns:ui="clr-namespace:Billing.UI"
|
xmlns:ui="clr-namespace:Billing.UI"
|
||||||
xmlns:v="clr-namespace:Billing.Views"
|
xmlns:v="clr-namespace:Billing.Views"
|
||||||
xmlns:chart="clr-namespace:Microcharts.Forms;assembly=Microcharts.Forms"
|
xmlns:chart="clr-namespace:Microcharts.Forms;assembly=Microcharts.Forms"
|
||||||
|
xmlns:ios="clr-namespace:Xamarin.Forms.PlatformConfiguration.iOSSpecific;assembly=Xamarin.Forms.Core"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||||
x:Class="Billing.Views.RankPage"
|
x:Class="Billing.Views.RankPage"
|
||||||
x:Name="rankPage"
|
x:Name="rankPage"
|
||||||
@ -12,31 +13,29 @@
|
|||||||
Shell.TabBarIsVisible="True">
|
Shell.TabBarIsVisible="True">
|
||||||
|
|
||||||
<Shell.TitleView>
|
<Shell.TitleView>
|
||||||
<Grid>
|
<Grid ColumnSpacing="10" ColumnDefinitions="30, *, 30">
|
||||||
<StackLayout Margin="30, 0, 0, 0" HorizontalOptions="Center" VerticalOptions="Center"
|
<ui:TintImageButton Source="left.png" WidthRequest="20" HeightRequest="20"
|
||||||
Orientation="Horizontal" Spacing="10">
|
VerticalOptions="Center" HorizontalOptions="Center"
|
||||||
<ui:TintImageButton Source="left.png" WidthRequest="20" HeightRequest="20"
|
Command="{Binding LeftCommand}"/>
|
||||||
VerticalOptions="Center" HorizontalOptions="Start"
|
<Label Grid.Column="1" Text="{Binding Title}"
|
||||||
Command="{Binding LeftCommand}"/>
|
TextColor="{DynamicResource PrimaryColor}"
|
||||||
<Label Text="{Binding Title}"
|
FontSize="{OnPlatform Android=20, iOS=18}"
|
||||||
TextColor="{DynamicResource PrimaryColor}"
|
FontFamily="{x:Static ui:Definition.SemiBoldFontFamily}"
|
||||||
FontSize="{OnPlatform Android=20, iOS=18}">
|
VerticalOptions="Center"
|
||||||
<Label.FontFamily>
|
HorizontalOptions="Center">
|
||||||
<OnPlatform x:TypeArguments="x:String"
|
<Label.GestureRecognizers>
|
||||||
Android="OpenSans-SemiBold.ttf#OpenSans-SemiBold"
|
<TapGestureRecognizer Command="{Binding FilterCommand}"/>
|
||||||
iOS="OpenSans-Bold"/>
|
</Label.GestureRecognizers>
|
||||||
</Label.FontFamily>
|
</Label>
|
||||||
</Label>
|
<ui:TintImageButton Grid.Column="2" Source="right.png" WidthRequest="20" HeightRequest="20"
|
||||||
<ui:TintImageButton Source="right.png" WidthRequest="20" HeightRequest="20"
|
VerticalOptions="Center" HorizontalOptions="Center"
|
||||||
VerticalOptions="Center" HorizontalOptions="End"
|
Command="{Binding RightCommand}"/>
|
||||||
Command="{Binding RightCommand}"/>
|
|
||||||
</StackLayout>
|
|
||||||
</Grid>
|
</Grid>
|
||||||
</Shell.TitleView>
|
</Shell.TitleView>
|
||||||
|
|
||||||
<ContentPage.ToolbarItems>
|
<!--<ContentPage.ToolbarItems>
|
||||||
<ToolbarItem Order="Primary" IconImageSource="filter.png" Command="{Binding FilterCommand}"/>
|
<ToolbarItem Order="Primary" IconImageSource="filter.png" Command="{Binding FilterCommand}"/>
|
||||||
</ContentPage.ToolbarItems>
|
</ContentPage.ToolbarItems>-->
|
||||||
|
|
||||||
<ContentPage.Resources>
|
<ContentPage.Resources>
|
||||||
<ui:NegativeConverter x:Key="negativeConverter"/>
|
<ui:NegativeConverter x:Key="negativeConverter"/>
|
||||||
@ -45,10 +44,11 @@
|
|||||||
<ui:BalanceColorConverter x:Key="colorConverter"/>
|
<ui:BalanceColorConverter x:Key="colorConverter"/>
|
||||||
<ui:TimeConverter x:Key="timeConverter" IncludeDate="True"/>
|
<ui:TimeConverter x:Key="timeConverter" IncludeDate="True"/>
|
||||||
<ui:IconConverter x:Key="iconConverter"/>
|
<ui:IconConverter x:Key="iconConverter"/>
|
||||||
|
<ui:TintColorConverter x:Key="tintColorConverter"/>
|
||||||
<Style x:Key="titleLabel" TargetType="Label">
|
<Style x:Key="titleLabel" TargetType="Label">
|
||||||
<Setter Property="FontSize" Value="16"/>
|
<Setter Property="FontSize" Value="16"/>
|
||||||
<Setter Property="Margin" Value="10, 20, 10, 10"/>
|
<Setter Property="Margin" Value="10, 20, 10, 10"/>
|
||||||
<!--<Setter Property="TextColor" Value="{DynamicResource SecondaryTextColor}"/>-->
|
<Setter Property="TextColor" Value="{DynamicResource TextColor}"/>
|
||||||
</Style>
|
</Style>
|
||||||
<Style x:Key="promptLabel" TargetType="Label">
|
<Style x:Key="promptLabel" TargetType="Label">
|
||||||
<Setter Property="HeightRequest" Value="240"/>
|
<Setter Property="HeightRequest" Value="240"/>
|
||||||
@ -58,12 +58,16 @@
|
|||||||
<Setter Property="VerticalTextAlignment" Value="Center"/>
|
<Setter Property="VerticalTextAlignment" Value="Center"/>
|
||||||
<Setter Property="TextColor" Value="{DynamicResource SecondaryTextColor}"/>
|
<Setter Property="TextColor" Value="{DynamicResource SecondaryTextColor}"/>
|
||||||
</Style>
|
</Style>
|
||||||
|
<Style TargetType="Button">
|
||||||
|
<Setter Property="TextColor" Value="{DynamicResource TextColor}"/>
|
||||||
|
<Setter Property="FontSize" Value="14"/>
|
||||||
|
</Style>
|
||||||
</ContentPage.Resources>
|
</ContentPage.Resources>
|
||||||
|
|
||||||
<Grid>
|
<Grid>
|
||||||
<ScrollView x:Name="scroller">
|
<ScrollView x:Name="scroller" Scrolled="Scroller_Scrolled">
|
||||||
<StackLayout>
|
<StackLayout>
|
||||||
<Grid Margin="0, 10, 0, 0" Padding="8" ColumnSpacing="8" ColumnDefinitions="*, Auto" HeightRequest="24"
|
<Grid Padding="8" ColumnSpacing="8" ColumnDefinitions="*, Auto" HeightRequest="24"
|
||||||
BackgroundColor="{DynamicResource PromptBackgroundColor}">
|
BackgroundColor="{DynamicResource PromptBackgroundColor}">
|
||||||
<StackLayout Grid.Column="1" Orientation="Horizontal" Spacing="6">
|
<StackLayout Grid.Column="1" Orientation="Horizontal" Spacing="6">
|
||||||
<Label Text="{r:Text Income}" TextColor="{DynamicResource GreenColor}"
|
<Label Text="{r:Text Income}" TextColor="{DynamicResource GreenColor}"
|
||||||
@ -108,6 +112,7 @@
|
|||||||
CommandParameter="{Binding .}"/>
|
CommandParameter="{Binding .}"/>
|
||||||
</Grid.GestureRecognizers>
|
</Grid.GestureRecognizers>
|
||||||
<ui:TintImage Source="{Binding Icon, Converter={StaticResource iconConverter}}"
|
<ui:TintImage Source="{Binding Icon, Converter={StaticResource iconConverter}}"
|
||||||
|
ui:TintHelper.TintColor="{Binding TintColor, Converter={StaticResource tintColorConverter}}"
|
||||||
WidthRequest="26" HeightRequest="20" VerticalOptions="Center"/>
|
WidthRequest="26" HeightRequest="20" VerticalOptions="Center"/>
|
||||||
<Label Grid.Column="1" Text="{Binding Name}" TextColor="{DynamicResource TextColor}"
|
<Label Grid.Column="1" Text="{Binding Name}" TextColor="{DynamicResource TextColor}"
|
||||||
VerticalOptions="Center"
|
VerticalOptions="Center"
|
||||||
@ -133,8 +138,8 @@
|
|||||||
<ui:BlurryPanel x:Name="panelFilter" VerticalOptions="Start" Opacity="0"
|
<ui:BlurryPanel x:Name="panelFilter" VerticalOptions="Start" Opacity="0"
|
||||||
BackgroundColor="{DynamicResource WindowBackgroundColor}"
|
BackgroundColor="{DynamicResource WindowBackgroundColor}"
|
||||||
HeightRequest="{Binding Height, Source={x:Reference gridFilter}}"/>
|
HeightRequest="{Binding Height, Source={x:Reference gridFilter}}"/>
|
||||||
<Grid x:Name="gridFilter" VerticalOptions="Start" Opacity="0" Padding="10">
|
<Grid x:Name="gridFilter" VerticalOptions="Start" Opacity="0" RowDefinitions="Auto, Auto, Auto, Auto">
|
||||||
<ui:SegmentedControl Margin="6, 6, 6, 3" VerticalOptions="Center"
|
<ui:SegmentedControl VerticalOptions="Center" Margin="10, 10, 10, 3"
|
||||||
SelectedSegmentIndex="{Binding SegmentType, Mode=TwoWay}"
|
SelectedSegmentIndex="{Binding SegmentType, Mode=TwoWay}"
|
||||||
SelectedTextColor="{DynamicResource TextColor}"
|
SelectedTextColor="{DynamicResource TextColor}"
|
||||||
TintColor="{DynamicResource PromptBackgroundColor}">
|
TintColor="{DynamicResource PromptBackgroundColor}">
|
||||||
@ -143,6 +148,26 @@
|
|||||||
<ui:SegmentedControlOption Text="{r:Text Income}"/>
|
<ui:SegmentedControlOption Text="{r:Text Income}"/>
|
||||||
</ui:SegmentedControl.Children>
|
</ui:SegmentedControl.Children>
|
||||||
</ui:SegmentedControl>
|
</ui:SegmentedControl>
|
||||||
|
<ui:OptionPicker Grid.Row="1" VerticalOptions="Center" Margin="10, 3"
|
||||||
|
HorizontalTextAlignment="Center" FontSize="16"
|
||||||
|
ItemsSource="{Binding DateTypes}"
|
||||||
|
SelectedIndex="{Binding SegmentDate, Mode=TwoWay}"
|
||||||
|
TextColor="{DynamicResource TextColor}"
|
||||||
|
ios:Picker.UpdateMode="WhenFinished"/>
|
||||||
|
<Grid Grid.Row="2" ColumnDefinitions="*, Auto, *" Margin="10, 3">
|
||||||
|
<ui:OptionDatePicker Date="{Binding StartPickerDate, Mode=TwoWay}"
|
||||||
|
FontSize="16" TextColor="{DynamicResource TextColor}"
|
||||||
|
VerticalOptions="Center"
|
||||||
|
ios:DatePicker.UpdateMode="WhenFinished"/>
|
||||||
|
<Label Grid.Column="1" Text="{r:Text To}" TextColor="{DynamicResource SecondaryTextColor}"
|
||||||
|
VerticalOptions="Center"/>
|
||||||
|
<ui:OptionDatePicker Grid.Column="2" Date="{Binding EndPickerDate, Mode=TwoWay}"
|
||||||
|
FontSize="16" TextColor="{DynamicResource TextColor}"
|
||||||
|
VerticalOptions="Center"
|
||||||
|
ios:DatePicker.UpdateMode="WhenFinished"/>
|
||||||
|
</Grid>
|
||||||
|
<Grid Grid.Row="3" HeightRequest="1" BackgroundColor="{DynamicResource PromptBackgroundColor}"
|
||||||
|
IsVisible="{OnPlatform iOS=False}"/>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
</ui:BillingPage>
|
</ui:BillingPage>
|
@ -1,4 +1,5 @@
|
|||||||
using Billing.Models;
|
using Billing.Models;
|
||||||
|
using Billing.Store;
|
||||||
using Billing.Themes;
|
using Billing.Themes;
|
||||||
using Billing.UI;
|
using Billing.UI;
|
||||||
using Microcharts;
|
using Microcharts;
|
||||||
@ -14,9 +15,39 @@ using Resource = Billing.Languages.Resource;
|
|||||||
|
|
||||||
namespace Billing.Views
|
namespace Billing.Views
|
||||||
{
|
{
|
||||||
|
public enum DateType : int
|
||||||
|
{
|
||||||
|
Custom = 0,
|
||||||
|
Monthly,
|
||||||
|
Today,
|
||||||
|
PastMonth,
|
||||||
|
PastQuarter,
|
||||||
|
PastSixMonths,
|
||||||
|
PastYear,
|
||||||
|
Total
|
||||||
|
}
|
||||||
|
|
||||||
public partial class RankPage : BillingPage
|
public partial class RankPage : BillingPage
|
||||||
{
|
{
|
||||||
|
private static RankPage instance;
|
||||||
|
public static RankPage Instance => instance;
|
||||||
|
|
||||||
|
private static readonly DateTime today = DateTime.Today;
|
||||||
|
|
||||||
private static readonly BindableProperty SegmentTypeProperty = Helper.Create<int, RankPage>(nameof(SegmentType), defaultValue: 0, propertyChanged: OnSegmentTypeChanged);
|
private static readonly BindableProperty SegmentTypeProperty = Helper.Create<int, RankPage>(nameof(SegmentType), defaultValue: 0, propertyChanged: OnSegmentTypeChanged);
|
||||||
|
private static readonly BindableProperty SegmentDateProperty = Helper.Create<int, RankPage>(nameof(SegmentDate), defaultValue: 1, propertyChanged: OnSegmentDateChanged);
|
||||||
|
private static readonly BindableProperty StartDateProperty = Helper.Create<DateTime, RankPage>(nameof(StartDate),
|
||||||
|
defaultValue: today.AddDays(1 - today.Day),
|
||||||
|
propertyChanged: OnDateChanged);
|
||||||
|
private static readonly BindableProperty EndDateProperty = Helper.Create<DateTime, RankPage>(nameof(EndDate),
|
||||||
|
defaultValue: today.AddDays(DateTime.DaysInMonth(today.Year, today.Month) - today.Day).LastMoment(),
|
||||||
|
propertyChanged: OnDateChanged);
|
||||||
|
private static readonly BindableProperty StartPickerDateProperty = Helper.Create<DateTime, RankPage>(nameof(StartPickerDate),
|
||||||
|
defaultValue: today.AddDays(1 - today.Day),
|
||||||
|
propertyChanged: OnPickerStartDateChanged);
|
||||||
|
private static readonly BindableProperty EndPickerDateProperty = Helper.Create<DateTime, RankPage>(nameof(EndPickerDate),
|
||||||
|
defaultValue: today.AddDays(DateTime.DaysInMonth(today.Year, today.Month) - today.Day),
|
||||||
|
propertyChanged: OnPickerEndDateChanged);
|
||||||
private static readonly BindableProperty ChartProperty = Helper.Create<Chart, RankPage>(nameof(Chart));
|
private static readonly BindableProperty ChartProperty = Helper.Create<Chart, RankPage>(nameof(Chart));
|
||||||
private static readonly BindableProperty CategoryChartProperty = Helper.Create<Chart, RankPage>(nameof(CategoryChart));
|
private static readonly BindableProperty CategoryChartProperty = Helper.Create<Chart, RankPage>(nameof(CategoryChart));
|
||||||
private static readonly BindableProperty TopBillsProperty = Helper.Create<IList<UIBill>, RankPage>(nameof(TopBills));
|
private static readonly BindableProperty TopBillsProperty = Helper.Create<IList<UIBill>, RankPage>(nameof(TopBills));
|
||||||
@ -34,8 +65,40 @@ namespace Billing.Views
|
|||||||
1 => CategoryType.Income,
|
1 => CategoryType.Income,
|
||||||
_ => CategoryType.Spending
|
_ => CategoryType.Spending
|
||||||
};
|
};
|
||||||
page.OnFilterCommand(false);
|
page.LoadData();
|
||||||
page.SetMonth(page.current);
|
}
|
||||||
|
private static void OnSegmentDateChanged(RankPage page, int old, int @new)
|
||||||
|
{
|
||||||
|
page.OnDateTypeCommand((DateType)@new);
|
||||||
|
}
|
||||||
|
private static void OnDateChanged(RankPage page, DateTime old = default, DateTime @new = default)
|
||||||
|
{
|
||||||
|
page.isLocked = true;
|
||||||
|
page.StartPickerDate = page.StartDate.Date;
|
||||||
|
page.EndPickerDate = page.EndDate.Date;
|
||||||
|
page.isLocked = false;
|
||||||
|
if (!page.isFreezed)
|
||||||
|
{
|
||||||
|
var format = Resource.TitleShortDateFormat;
|
||||||
|
page.Title = page.StartDate.ToString(format) + " ~ " + page.EndDate.ToString(format);
|
||||||
|
page.LoadData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private static void OnPickerStartDateChanged(RankPage page, DateTime _, DateTime @new)
|
||||||
|
{
|
||||||
|
if (!page.isLocked)
|
||||||
|
{
|
||||||
|
page.SegmentDate = 0;
|
||||||
|
page.StartDate = @new.Date;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private static void OnPickerEndDateChanged(RankPage page, DateTime _, DateTime @new)
|
||||||
|
{
|
||||||
|
if (!page.isLocked)
|
||||||
|
{
|
||||||
|
page.SegmentDate = 0;
|
||||||
|
page.EndDate = @new.Date.LastMoment();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public int SegmentType
|
public int SegmentType
|
||||||
@ -43,6 +106,31 @@ namespace Billing.Views
|
|||||||
get => (int)GetValue(SegmentTypeProperty);
|
get => (int)GetValue(SegmentTypeProperty);
|
||||||
set => SetValue(SegmentTypeProperty, value);
|
set => SetValue(SegmentTypeProperty, value);
|
||||||
}
|
}
|
||||||
|
public int SegmentDate
|
||||||
|
{
|
||||||
|
get => (int)GetValue(SegmentDateProperty);
|
||||||
|
set => SetValue(SegmentDateProperty, value);
|
||||||
|
}
|
||||||
|
public DateTime StartDate
|
||||||
|
{
|
||||||
|
get => (DateTime)GetValue(StartDateProperty);
|
||||||
|
set => SetValue(StartDateProperty, value);
|
||||||
|
}
|
||||||
|
public DateTime EndDate
|
||||||
|
{
|
||||||
|
get => (DateTime)GetValue(EndDateProperty);
|
||||||
|
set => SetValue(EndDateProperty, value);
|
||||||
|
}
|
||||||
|
public DateTime StartPickerDate
|
||||||
|
{
|
||||||
|
get => (DateTime)GetValue(StartPickerDateProperty);
|
||||||
|
set => SetValue(StartPickerDateProperty, value);
|
||||||
|
}
|
||||||
|
public DateTime EndPickerDate
|
||||||
|
{
|
||||||
|
get => (DateTime)GetValue(EndPickerDateProperty);
|
||||||
|
set => SetValue(EndPickerDateProperty, value);
|
||||||
|
}
|
||||||
public Chart Chart
|
public Chart Chart
|
||||||
{
|
{
|
||||||
get => (Chart)GetValue(ChartProperty);
|
get => (Chart)GetValue(ChartProperty);
|
||||||
@ -77,59 +165,262 @@ namespace Billing.Views
|
|||||||
public decimal Spending => (decimal)GetValue(SpendingProperty);
|
public decimal Spending => (decimal)GetValue(SpendingProperty);
|
||||||
public decimal Balance => (decimal)GetValue(BalanceProperty);
|
public decimal Balance => (decimal)GetValue(BalanceProperty);
|
||||||
|
|
||||||
|
public List<string> DateTypes { get; }
|
||||||
|
|
||||||
public Command LeftCommand { get; }
|
public Command LeftCommand { get; }
|
||||||
public Command RightCommand { get; }
|
public Command RightCommand { get; }
|
||||||
public Command FilterCommand { get; }
|
public Command FilterCommand { get; }
|
||||||
public Command EditBilling { get; }
|
public Command EditBilling { get; }
|
||||||
|
|
||||||
private DateTime current;
|
|
||||||
private DateTime end;
|
|
||||||
private IEnumerable<Bill> bills;
|
private IEnumerable<Bill> bills;
|
||||||
private CategoryType type = CategoryType.Spending;
|
private CategoryType type = CategoryType.Spending;
|
||||||
private bool isFilterToggled;
|
private bool isFilterToggled;
|
||||||
|
private bool isFreezed;
|
||||||
|
private bool isLocked;
|
||||||
|
private bool needRefresh = true;
|
||||||
|
|
||||||
|
private const int FILTER_HEIGHT = 100;
|
||||||
private readonly SKTypeface font;
|
private readonly SKTypeface font;
|
||||||
|
|
||||||
public RankPage()
|
public RankPage()
|
||||||
{
|
{
|
||||||
|
instance = this;
|
||||||
|
|
||||||
LeftCommand = new Command(OnLeftCommand);
|
LeftCommand = new Command(OnLeftCommand);
|
||||||
RightCommand = new Command(OnRightCommand);
|
RightCommand = new Command(OnRightCommand);
|
||||||
FilterCommand = new Command(OnFilterCommand);
|
FilterCommand = new Command(OnFilterCommand);
|
||||||
EditBilling = new Command(OnEditBilling);
|
EditBilling = new Command(OnEditBilling);
|
||||||
|
|
||||||
var style = SKFontManager.Default.GetFontStyles("PingFang SC");
|
#if __IOS__
|
||||||
|
var style = SKFontManager.Default.GetFontStyles("PingFang SC");
|
||||||
if (style != null)
|
if (style != null)
|
||||||
{
|
{
|
||||||
font = style.CreateTypeface(SKFontStyle.Normal);
|
font = style.CreateTypeface(SKFontStyle.Normal);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
#endif
|
||||||
|
font = SKFontManager.Default.MatchCharacter(0x4e00);
|
||||||
|
|
||||||
|
DateTypes = new List<string>
|
||||||
|
{
|
||||||
|
Resource.Custom,
|
||||||
|
Resource.Monthly,
|
||||||
|
Resource.Today,
|
||||||
|
Resource.PastMonth,
|
||||||
|
Resource.PastQuarter,
|
||||||
|
Resource.PastSixMonths,
|
||||||
|
Resource.PastYear,
|
||||||
|
Resource.Total
|
||||||
|
};
|
||||||
|
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
gridFilter.TranslationY = -60;
|
gridFilter.TranslationY = -FILTER_HEIGHT;
|
||||||
panelFilter.TranslationY = -60;
|
panelFilter.TranslationY = -FILTER_HEIGHT;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void OnLoaded()
|
public void SetNeedRefresh()
|
||||||
{
|
{
|
||||||
SetMonth(DateTime.Today);
|
needRefresh = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnAppearing()
|
||||||
|
{
|
||||||
|
if (needRefresh)
|
||||||
|
{
|
||||||
|
needRefresh = false;
|
||||||
|
OnDateChanged(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnRefresh()
|
||||||
|
{
|
||||||
|
OnDateChanged(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDateTypeCommand(DateType index)
|
||||||
|
{
|
||||||
|
if (index < DateType.Monthly || index > DateType.Total)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (scroller.ScrollY > 0)
|
||||||
|
{
|
||||||
|
scroller.ScrollToAsync(0, 0, true);
|
||||||
|
}
|
||||||
|
isFreezed = true;
|
||||||
|
var today = DateTime.Today;
|
||||||
|
switch (index)
|
||||||
|
{
|
||||||
|
case DateType.Monthly:
|
||||||
|
StartDate = today.AddDays(1 - today.Day);
|
||||||
|
EndDate = today.AddDays(DateTime.DaysInMonth(today.Year, today.Month) - today.Day).LastMoment();
|
||||||
|
break;
|
||||||
|
case DateType.Today:
|
||||||
|
StartDate = today;
|
||||||
|
EndDate = today.LastMoment();
|
||||||
|
break;
|
||||||
|
case DateType.PastMonth:
|
||||||
|
StartDate = today.AddMonths(-1).AddDays(1);
|
||||||
|
EndDate = today.LastMoment();
|
||||||
|
break;
|
||||||
|
case DateType.PastQuarter:
|
||||||
|
StartDate = today.AddMonths(-3).AddDays(1);
|
||||||
|
EndDate = today.LastMoment();
|
||||||
|
break;
|
||||||
|
case DateType.PastSixMonths:
|
||||||
|
StartDate = today.AddMonths(-6).AddDays(1);
|
||||||
|
EndDate = today.LastMoment();
|
||||||
|
break;
|
||||||
|
case DateType.PastYear:
|
||||||
|
StartDate = today.AddYears(-1).AddDays(1);
|
||||||
|
EndDate = today.LastMoment();
|
||||||
|
break;
|
||||||
|
case DateType.Total:
|
||||||
|
//StartDate = App.Bills.Min(b => b.CreateTime).Date;
|
||||||
|
//EndDate = App.Bills.Max(b => b.CreateTime).Date.LastMoment();
|
||||||
|
DateTime min = DateTime.MaxValue;
|
||||||
|
DateTime max = DateTime.MinValue;
|
||||||
|
App.Bills.ForEach(b =>
|
||||||
|
{
|
||||||
|
if (b.CreateTime < min)
|
||||||
|
{
|
||||||
|
min = b.CreateTime;
|
||||||
|
}
|
||||||
|
if (b.CreateTime > max)
|
||||||
|
{
|
||||||
|
max = b.CreateTime;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (min == DateTime.MaxValue && max == DateTime.MinValue)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
StartDate = min.Date;
|
||||||
|
EndDate = max.Date.LastMoment();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
isFreezed = false;
|
||||||
|
OnDateChanged(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsPreset(DateTime start, DateTime end)
|
||||||
|
{
|
||||||
|
return start.Month == end.Month &&
|
||||||
|
start.Day == 1 &&
|
||||||
|
end.Day == DateTime.DaysInMonth(end.Year, end.Month);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnLeftCommand()
|
private void OnLeftCommand()
|
||||||
{
|
{
|
||||||
|
var type = (DateType)SegmentDate;
|
||||||
|
if (type < DateType.Monthly || type >= DateType.Total)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (scroller.ScrollY > 0)
|
if (scroller.ScrollY > 0)
|
||||||
{
|
{
|
||||||
scroller.ScrollToAsync(0, 0, true);
|
scroller.ScrollToAsync(0, 0, true);
|
||||||
}
|
}
|
||||||
SetMonth(current.AddMonths(-1));
|
isFreezed = true;
|
||||||
|
var start = StartDate;
|
||||||
|
var end = EndDate;
|
||||||
|
if (type == DateType.Monthly || IsPreset(start, end))
|
||||||
|
{
|
||||||
|
start = start.AddMonths(-1);
|
||||||
|
end = start.AddDays(DateTime.DaysInMonth(start.Year, start.Month) - 1).LastMoment();
|
||||||
|
}
|
||||||
|
else if (type == DateType.PastMonth)
|
||||||
|
{
|
||||||
|
start = start.AddMonths(-1);
|
||||||
|
end = end.AddMonths(-1);
|
||||||
|
}
|
||||||
|
else if (type == DateType.PastQuarter)
|
||||||
|
{
|
||||||
|
start = start.AddMonths(-3);
|
||||||
|
end = end.AddMonths(-3);
|
||||||
|
}
|
||||||
|
else if (type == DateType.PastSixMonths)
|
||||||
|
{
|
||||||
|
start = start.AddMonths(-6);
|
||||||
|
end = end.AddMonths(-6);
|
||||||
|
}
|
||||||
|
else if (type == DateType.PastYear)
|
||||||
|
{
|
||||||
|
start = start.AddYears(-1);
|
||||||
|
end = end.AddYears(-1);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var days = (end.Date - start.Date).TotalDays + 1;
|
||||||
|
start = start.AddDays(-days);
|
||||||
|
end = end.AddDays(-days);
|
||||||
|
}
|
||||||
|
if (start.Year < 1900)
|
||||||
|
{
|
||||||
|
isFreezed = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
StartDate = start;
|
||||||
|
EndDate = end;
|
||||||
|
isFreezed = false;
|
||||||
|
OnDateChanged(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnRightCommand()
|
private void OnRightCommand()
|
||||||
{
|
{
|
||||||
|
var type = (DateType)SegmentDate;
|
||||||
|
if (type < DateType.Monthly || type >= DateType.Total)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (scroller.ScrollY > 0)
|
if (scroller.ScrollY > 0)
|
||||||
{
|
{
|
||||||
scroller.ScrollToAsync(0, 0, true);
|
scroller.ScrollToAsync(0, 0, true);
|
||||||
}
|
}
|
||||||
SetMonth(current.AddMonths(1));
|
isFreezed = true;
|
||||||
|
var start = StartDate;
|
||||||
|
var end = EndDate;
|
||||||
|
if (type == DateType.Monthly || IsPreset(start, end))
|
||||||
|
{
|
||||||
|
start = start.AddMonths(1);
|
||||||
|
end = start.AddDays(DateTime.DaysInMonth(start.Year, start.Month) - 1).LastMoment();
|
||||||
|
}
|
||||||
|
else if (type == DateType.PastMonth)
|
||||||
|
{
|
||||||
|
start = start.AddMonths(1);
|
||||||
|
end = end.AddMonths(1);
|
||||||
|
}
|
||||||
|
else if (type == DateType.PastQuarter)
|
||||||
|
{
|
||||||
|
start = start.AddMonths(3);
|
||||||
|
end = end.AddMonths(3);
|
||||||
|
}
|
||||||
|
else if (type == DateType.PastSixMonths)
|
||||||
|
{
|
||||||
|
start = start.AddMonths(6);
|
||||||
|
end = end.AddMonths(6);
|
||||||
|
}
|
||||||
|
else if (type == DateType.PastYear)
|
||||||
|
{
|
||||||
|
start = start.AddYears(1);
|
||||||
|
end = end.AddYears(1);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var days = (end.Date - start.Date).TotalDays + 1;
|
||||||
|
start = start.AddDays(days);
|
||||||
|
end = end.AddDays(days);
|
||||||
|
}
|
||||||
|
if (end.Year > DateTime.Today.Year + 100)
|
||||||
|
{
|
||||||
|
isFreezed = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
StartDate = start;
|
||||||
|
EndDate = end;
|
||||||
|
isFreezed = false;
|
||||||
|
OnDateChanged(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void OnFilterCommand(object o)
|
private async void OnFilterCommand(object o)
|
||||||
@ -146,6 +437,7 @@ namespace Billing.Views
|
|||||||
ViewExtensions.CancelAnimations(panelFilter);
|
ViewExtensions.CancelAnimations(panelFilter);
|
||||||
if (isFilterToggled)
|
if (isFilterToggled)
|
||||||
{
|
{
|
||||||
|
await scroller.ScrollToAsync(scroller.ScrollX, scroller.ScrollY, false);
|
||||||
await Task.WhenAll(
|
await Task.WhenAll(
|
||||||
gridFilter.TranslateTo(0, 0, easing: Easing.CubicOut),
|
gridFilter.TranslateTo(0, 0, easing: Easing.CubicOut),
|
||||||
gridFilter.FadeTo(1, easing: Easing.CubicOut),
|
gridFilter.FadeTo(1, easing: Easing.CubicOut),
|
||||||
@ -155,9 +447,9 @@ namespace Billing.Views
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
await Task.WhenAll(
|
await Task.WhenAll(
|
||||||
gridFilter.TranslateTo(0, -60, easing: Easing.CubicIn),
|
gridFilter.TranslateTo(0, -FILTER_HEIGHT, easing: Easing.CubicIn),
|
||||||
gridFilter.FadeTo(0, easing: Easing.CubicIn),
|
gridFilter.FadeTo(0, easing: Easing.CubicIn),
|
||||||
panelFilter.TranslateTo(0, -60, easing: Easing.CubicIn),
|
panelFilter.TranslateTo(0, -FILTER_HEIGHT, easing: Easing.CubicIn),
|
||||||
panelFilter.FadeTo(0, easing: Easing.CubicIn));
|
panelFilter.FadeTo(0, easing: Easing.CubicIn));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -179,18 +471,9 @@ namespace Billing.Views
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateBill(UIBill bill)
|
private async void RefreshBalance(DateTime start, DateTime end)
|
||||||
{
|
|
||||||
bill.Icon = App.Categories.FirstOrDefault(c => c.Id == bill.Bill.CategoryId)?.Icon ?? BaseModel.ICON_DEFAULT;
|
|
||||||
bill.Name = bill.Bill.Name;
|
|
||||||
bill.DateCreation = bill.Bill.CreateTime;
|
|
||||||
bill.Amount = bill.Bill.Amount;
|
|
||||||
bill.Wallet = App.Accounts.FirstOrDefault(a => a.Id == bill.Bill.WalletId)?.Name;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async void RefreshBalance()
|
|
||||||
{
|
{
|
||||||
var bills = await Task.Run(() => App.Bills.Where(b => b.CreateTime >= current && b.CreateTime <= end));
|
var bills = await Task.Run(() => App.Bills.Where(b => b.CreateTime >= start && b.CreateTime <= end));
|
||||||
var income = bills.Where(b => b.Amount > 0).Sum(b => b.Amount);
|
var income = bills.Where(b => b.Amount > 0).Sum(b => b.Amount);
|
||||||
var spending = -bills.Where(b => b.Amount < 0).Sum(b => b.Amount);
|
var spending = -bills.Where(b => b.Amount < 0).Sum(b => b.Amount);
|
||||||
SetValue(IncomeProperty, income);
|
SetValue(IncomeProperty, income);
|
||||||
@ -198,43 +481,33 @@ namespace Billing.Views
|
|||||||
SetValue(BalanceProperty, income - spending);
|
SetValue(BalanceProperty, income - spending);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnBillChecked(object sender, Bill e)
|
private async void OnBillChecked(object sender, Bill e)
|
||||||
{
|
{
|
||||||
var bill = TopBills.FirstOrDefault(b => b.Bill == e);
|
await StoreHelper.SaveBillItemAsync(e);
|
||||||
if (bill != null)
|
LoadData();
|
||||||
{
|
|
||||||
UpdateBill(bill);
|
|
||||||
}
|
|
||||||
RefreshBalance();
|
|
||||||
|
|
||||||
Task.Run(App.WriteBills);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void SetMonth(DateTime date)
|
private async void LoadData()
|
||||||
{
|
{
|
||||||
current = date.AddDays(1 - date.Day);
|
var start = StartDate;
|
||||||
end = current.AddDays(DateTime.DaysInMonth(current.Year, current.Month));
|
var end = EndDate;
|
||||||
|
|
||||||
var format = Resource.DateRangeFormat;
|
|
||||||
Title = current.ToString(format) + " ~ " + end.AddDays(-1).ToString(format);
|
|
||||||
|
|
||||||
var spending = type == CategoryType.Spending;
|
var spending = type == CategoryType.Spending;
|
||||||
bills = await Task.Run(() => App.Bills.Where(b => (b.Amount > 0 ^ spending) && b.CreateTime >= current && b.CreateTime <= end));
|
bills = await Task.Run(() => App.Bills.Where(b => (b.Amount > 0 ^ spending) && b.CreateTime >= start && b.CreateTime <= end));
|
||||||
|
|
||||||
var primaryColor = BaseTheme.CurrentPrimaryColor.ToSKColor();
|
var primaryColor = BaseTheme.CurrentPrimaryColor.ToSKColor();
|
||||||
var textColor = BaseTheme.CurrentSecondaryTextColor.ToSKColor();
|
var textColor = BaseTheme.CurrentSecondaryTextColor.ToSKColor();
|
||||||
|
|
||||||
_ = Task.Run(() => LoadReportChart(primaryColor, textColor));
|
_ = Task.Run(() => LoadReportChart(primaryColor, textColor, start, end));
|
||||||
_ = Task.Run(() => LoadCategoryChart(primaryColor, textColor));
|
_ = Task.Run(() => LoadCategoryChart(primaryColor, textColor));
|
||||||
_ = Task.Run(LoadTopBills);
|
_ = Task.Run(LoadTopBills);
|
||||||
|
|
||||||
RefreshBalance();
|
RefreshBalance(start, end);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void LoadReportChart(SKColor primaryColor, SKColor textColor)
|
private void LoadReportChart(SKColor primaryColor, SKColor textColor, DateTime start, DateTime end)
|
||||||
{
|
{
|
||||||
var entries = new List<ChartEntry>();
|
var entries = new List<ChartEntry>();
|
||||||
for (var day = current; day <= end; day = day.AddDays(1))
|
for (var day = start; day <= end; day = day.AddDays(1))
|
||||||
{
|
{
|
||||||
var daybills = bills.Where(b => Helper.IsSameDay(b.CreateTime, day));
|
var daybills = bills.Where(b => Helper.IsSameDay(b.CreateTime, day));
|
||||||
decimal amount = Math.Abs(daybills.Sum(b => b.Amount));
|
decimal amount = Math.Abs(daybills.Sum(b => b.Amount));
|
||||||
@ -350,5 +623,13 @@ namespace Billing.Views
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void Scroller_Scrolled(object sender, ScrolledEventArgs e)
|
||||||
|
{
|
||||||
|
if (isFilterToggled)
|
||||||
|
{
|
||||||
|
OnFilterCommand(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -11,18 +11,24 @@
|
|||||||
BindingContext="{x:Reference settingPage}"
|
BindingContext="{x:Reference settingPage}"
|
||||||
Shell.TabBarIsVisible="True">
|
Shell.TabBarIsVisible="True">
|
||||||
|
|
||||||
|
<ContentPage.ToolbarItems>
|
||||||
|
<ToolbarItem Order="Primary" IconImageSource="share.png" Command="{Binding ShareCommand}"/>
|
||||||
|
</ContentPage.ToolbarItems>
|
||||||
|
|
||||||
<TableView Intent="Settings" HasUnevenRows="True">
|
<TableView Intent="Settings" HasUnevenRows="True">
|
||||||
<TableSection Title="{r:Text About}">
|
<TableSection Title="{r:Text About}">
|
||||||
<ui:OptionTextCell Height="36" Title="{r:Text Version}"
|
<ui:OptionTextCell Height="44" Title="{r:Text Version}"
|
||||||
Detail="{Binding Version}"/>
|
Detail="{Binding Version}"/>
|
||||||
</TableSection>
|
</TableSection>
|
||||||
<TableSection Title="{r:Text Feature}">
|
<TableSection Title="{r:Text Feature}">
|
||||||
<ui:OptionSelectCell Height="36" Title="{r:Text CategoryManage}"
|
<ui:OptionSelectCell Height="44" Title="{r:Text CategoryManage}"
|
||||||
Detail="{r:Text Detail}"
|
Detail="{r:Text Detail}"
|
||||||
Command="{Binding CategoryCommand}"/>
|
Command="{Binding CategoryCommand}"/>
|
||||||
|
<ui:OptionSwitchCell Height="44" Title="{r:Text SaveLocation}"
|
||||||
|
IsToggled="{Binding SaveLocation, Mode=TwoWay}"/>
|
||||||
</TableSection>
|
</TableSection>
|
||||||
<TableSection Title="{r:Text Preference}">
|
<TableSection Title="{r:Text Preference}">
|
||||||
<ui:OptionEntryCell Height="36" Title="{r:Text PrimaryColor}"
|
<ui:OptionEntryCell Height="44" Title="{r:Text PrimaryColor}"
|
||||||
Text="{Binding PrimaryColor, Mode=TwoWay}"
|
Text="{Binding PrimaryColor, Mode=TwoWay}"
|
||||||
Keyboard="Text"/>
|
Keyboard="Text"/>
|
||||||
<ViewCell Height="120">
|
<ViewCell Height="120">
|
||||||
@ -31,9 +37,14 @@
|
|||||||
<!--<Label Text="" LineBreakMode="TailTruncation"
|
<!--<Label Text="" LineBreakMode="TailTruncation"
|
||||||
VerticalOptions="Center"
|
VerticalOptions="Center"
|
||||||
TextColor="{DynamicResource TextColor}"/>-->
|
TextColor="{DynamicResource TextColor}"/>-->
|
||||||
<ui:ColorPicker Grid.Column="1" ColorChanged="ColorPicker_ColorChanged"/>
|
<ui:ColorPicker Grid.Column="1" Command="{Binding ColorPickerCommand}"/>
|
||||||
</Grid>
|
</Grid>
|
||||||
</ViewCell>
|
</ViewCell>
|
||||||
</TableSection>
|
</TableSection>
|
||||||
|
<TableSection Title="{r:Text Diagnostic}">
|
||||||
|
<ui:OptionSelectCell Height="44" Title="{r:Text ShareLogs}"
|
||||||
|
Detail="{Binding ManyRecords}"
|
||||||
|
Command="{Binding ShareLogsCommand}"/>
|
||||||
|
</TableSection>
|
||||||
</TableView>
|
</TableView>
|
||||||
</ui:BillingPage>
|
</ui:BillingPage>
|
@ -1,50 +1,89 @@
|
|||||||
|
using System.IO;
|
||||||
|
using Billing.Store;
|
||||||
using Billing.Themes;
|
using Billing.Themes;
|
||||||
using Billing.UI;
|
using Billing.UI;
|
||||||
using System.Globalization;
|
|
||||||
using Xamarin.Essentials;
|
using Xamarin.Essentials;
|
||||||
using Xamarin.Forms;
|
using Xamarin.Forms;
|
||||||
|
using Resource = Billing.Languages.Resource;
|
||||||
|
|
||||||
namespace Billing.Views
|
namespace Billing.Views
|
||||||
{
|
{
|
||||||
public partial class SettingPage : BillingPage
|
public partial class SettingPage : BillingPage
|
||||||
{
|
{
|
||||||
private static readonly BindableProperty VersionProperty = BindableProperty.Create(nameof(Version), typeof(string), typeof(SettingPage));
|
private static readonly BindableProperty VersionProperty = Helper.Create<string, SettingPage>(nameof(Version));
|
||||||
private static readonly BindableProperty PrimaryColorProperty = BindableProperty.Create(nameof(PrimaryColor), typeof(string), typeof(SettingPage));
|
private static readonly BindableProperty SaveLocationProperty = Helper.Create<bool, SettingPage>(nameof(SaveLocation));
|
||||||
|
private static readonly BindableProperty PrimaryColorProperty = Helper.Create<string, SettingPage>(nameof(PrimaryColor));
|
||||||
|
private static readonly BindableProperty ManyRecordsProperty = Helper.Create<string, SettingPage>(nameof(ManyRecords));
|
||||||
|
|
||||||
public string Version => (string)GetValue(VersionProperty);
|
public string Version => (string)GetValue(VersionProperty);
|
||||||
|
public bool SaveLocation
|
||||||
|
{
|
||||||
|
get => (bool)GetValue(SaveLocationProperty);
|
||||||
|
set => SetValue(SaveLocationProperty, value);
|
||||||
|
}
|
||||||
public string PrimaryColor
|
public string PrimaryColor
|
||||||
{
|
{
|
||||||
get => (string)GetValue(PrimaryColorProperty);
|
get => (string)GetValue(PrimaryColorProperty);
|
||||||
set => SetValue(PrimaryColorProperty, value);
|
set => SetValue(PrimaryColorProperty, value);
|
||||||
}
|
}
|
||||||
|
public string ManyRecords => (string)GetValue(ManyRecordsProperty);
|
||||||
|
|
||||||
|
public Command ShareCommand { get; }
|
||||||
public Command CategoryCommand { get; }
|
public Command CategoryCommand { get; }
|
||||||
|
public Command ColorPickerCommand { get; }
|
||||||
|
public Command ShareLogsCommand { get; }
|
||||||
|
|
||||||
public SettingPage()
|
public SettingPage()
|
||||||
{
|
{
|
||||||
|
ShareCommand = new Command(OnShareCommand);
|
||||||
CategoryCommand = new Command(OnCategoryCommand);
|
CategoryCommand = new Command(OnCategoryCommand);
|
||||||
|
ColorPickerCommand = new Command(OnColorPickerCommand);
|
||||||
|
ShareLogsCommand = new Command(OnShareLogsCommand);
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
var (main, build) = Definition.GetVersion();
|
SetValue(VersionProperty, $"{AppInfo.VersionString} ({AppInfo.BuildString})");
|
||||||
SetValue(VersionProperty, $"{main} ({build})");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnAppearing()
|
protected override async void OnAppearing()
|
||||||
{
|
{
|
||||||
base.OnAppearing();
|
base.OnAppearing();
|
||||||
|
|
||||||
//SetValue(VersionProperty, $"{AppInfo.VersionString} ({AppInfo.BuildString})");
|
SaveLocation = App.SaveLocation;
|
||||||
var colorString = Preferences.Get(Definition.PrimaryColorKey, Helper.DEFAULT_COLOR);
|
var colorString = Preferences.Get(Definition.PrimaryColorKey, Helper.DEFAULT_COLOR);
|
||||||
PrimaryColor = Helper.WrapColorString(colorString);
|
PrimaryColor = Helper.WrapColorString(colorString);
|
||||||
|
|
||||||
|
var count = await StoreHelper.GetLogsCount();
|
||||||
|
SetValue(ManyRecordsProperty, string.Format(Resource.ManyRecords, count));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnDisappearing()
|
protected override void OnDisappearing()
|
||||||
{
|
{
|
||||||
base.OnDisappearing();
|
base.OnDisappearing();
|
||||||
|
|
||||||
var color = PrimaryColor;
|
App.SetSaveLocation(SaveLocation);
|
||||||
Preferences.Set(Definition.PrimaryColorKey, color);
|
Preferences.Set(Definition.SaveLocationKey, SaveLocation);
|
||||||
Light.Instance.RefreshColor(Color.FromHex(color));
|
Preferences.Set(Definition.PrimaryColorKey, PrimaryColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async void OnRefresh()
|
||||||
|
{
|
||||||
|
var count = await StoreHelper.GetLogsCount();
|
||||||
|
SetValue(ManyRecordsProperty, string.Format(Resource.ManyRecords, count));
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void OnShareCommand()
|
||||||
|
{
|
||||||
|
if (Tap.IsBusy)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
using (Tap.Start())
|
||||||
|
{
|
||||||
|
await Share.RequestAsync(new ShareFileRequest
|
||||||
|
{
|
||||||
|
File = new ShareFile(StoreHelper.DatabasePath, "application/vnd.sqlite3")
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void OnCategoryCommand()
|
private async void OnCategoryCommand()
|
||||||
@ -60,9 +99,73 @@ namespace Billing.Views
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ColorPicker_ColorChanged(object sender, Color e)
|
private void OnColorPickerCommand(object o)
|
||||||
{
|
{
|
||||||
PrimaryColor = Helper.WrapColorString(e.ToHex());
|
if (o is Color color)
|
||||||
|
{
|
||||||
|
PrimaryColor = Helper.WrapColorString(color.ToHex());
|
||||||
|
Light.Instance.RefreshColor(color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void OnShareLogsCommand()
|
||||||
|
{
|
||||||
|
if (Tap.IsBusy)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
using (Tap.Start())
|
||||||
|
{
|
||||||
|
string file;
|
||||||
|
var count = await StoreHelper.GetLogsCount();
|
||||||
|
if (count > 0)
|
||||||
|
{
|
||||||
|
file = await StoreHelper.ExportLogs();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
file = StoreHelper.GetLogFile();
|
||||||
|
}
|
||||||
|
if (file != null && File.Exists(file))
|
||||||
|
{
|
||||||
|
#if __IOS__
|
||||||
|
var sendEmail = Resource.SendEmail;
|
||||||
|
var shareLogs = Resource.ShareLogs;
|
||||||
|
var result = await DisplayActionSheet(Resource.HowToShareDiagnostic, Resource.Cancel, null, sendEmail, shareLogs);
|
||||||
|
if (result == sendEmail)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await Email.ComposeAsync(new EmailMessage
|
||||||
|
{
|
||||||
|
To = { "tsorgy@gmail.com " },
|
||||||
|
Subject = Resource.ShareLogs,
|
||||||
|
Attachments =
|
||||||
|
{
|
||||||
|
new(file, "text/csv")
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (System.Exception ex)
|
||||||
|
{
|
||||||
|
Helper.Error("email.send", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (result == shareLogs)
|
||||||
|
{
|
||||||
|
await Share.RequestAsync(new ShareFileRequest
|
||||||
|
{
|
||||||
|
File = new ShareFile(file, "text/csv")
|
||||||
|
});
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
await Share.RequestAsync(new ShareFileRequest
|
||||||
|
{
|
||||||
|
File = new ShareFile(file, "text/csv")
|
||||||
|
});
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
129
Billing.Shared/Views/ViewLocationPage.cs
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
using System;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Billing.Models;
|
||||||
|
using Billing.UI;
|
||||||
|
using Xamarin.Essentials;
|
||||||
|
using Xamarin.Forms;
|
||||||
|
using Xamarin.Forms.Maps;
|
||||||
|
using Xamarin.Forms.PlatformConfiguration.iOSSpecific;
|
||||||
|
using Map = Xamarin.Forms.Maps.Map;
|
||||||
|
|
||||||
|
namespace Billing.Views
|
||||||
|
{
|
||||||
|
public class ViewLocationPage : BillingPage
|
||||||
|
{
|
||||||
|
public event EventHandler<Location> Synced;
|
||||||
|
|
||||||
|
private readonly Bill bill;
|
||||||
|
|
||||||
|
private CancellationTokenSource tokenSource;
|
||||||
|
|
||||||
|
public ViewLocationPage(Bill bill)
|
||||||
|
{
|
||||||
|
On<Xamarin.Forms.PlatformConfiguration.iOS>().SetUseSafeArea(false);
|
||||||
|
this.bill = bill;
|
||||||
|
Title = bill.Name;
|
||||||
|
|
||||||
|
ToolbarItems.Add(new ToolbarItem
|
||||||
|
{
|
||||||
|
IconImageSource = "location.png",
|
||||||
|
Order = ToolbarItemOrder.Primary,
|
||||||
|
Command = new Command(OnSynced)
|
||||||
|
});
|
||||||
|
|
||||||
|
if (bill.Latitude != null && bill.Longitude != null)
|
||||||
|
{
|
||||||
|
var (longitude, latitude) = (bill.Longitude.Value, bill.Latitude.Value).Wgs84ToGcj02();
|
||||||
|
var position = new Position(latitude, longitude);
|
||||||
|
var mapSpan = new MapSpan(position, 0.01, 0.01);
|
||||||
|
Content = new Map(mapSpan)
|
||||||
|
{
|
||||||
|
Pins =
|
||||||
|
{
|
||||||
|
new Pin
|
||||||
|
{
|
||||||
|
Label = bill.Name,
|
||||||
|
Type = PinType.Generic,
|
||||||
|
Position = position,
|
||||||
|
Address = bill.Store
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnDisappearing()
|
||||||
|
{
|
||||||
|
if (tokenSource != null && !tokenSource.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
tokenSource.Cancel();
|
||||||
|
}
|
||||||
|
base.OnDisappearing();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void OnSynced()
|
||||||
|
{
|
||||||
|
if (Tap.IsBusy)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
using (Tap.Start())
|
||||||
|
{
|
||||||
|
if (tokenSource != null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var location = await GetCurrentLocation();
|
||||||
|
if (location != null)
|
||||||
|
{
|
||||||
|
Synced?.Invoke(this, location);
|
||||||
|
|
||||||
|
MainThread.BeginInvokeOnMainThread(() =>
|
||||||
|
{
|
||||||
|
var (longitude, latitude) = (location.Longitude, location.Latitude).Wgs84ToGcj02();
|
||||||
|
var position = new Position(latitude, longitude);
|
||||||
|
var mapSpan = new MapSpan(position, 0.01, 0.01);
|
||||||
|
Content = new Map(mapSpan)
|
||||||
|
{
|
||||||
|
Pins =
|
||||||
|
{
|
||||||
|
new Pin
|
||||||
|
{
|
||||||
|
Label = bill.Name,
|
||||||
|
Type = PinType.Generic,
|
||||||
|
Position = position,
|
||||||
|
Address = bill.Store
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<Location> GetCurrentLocation()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var request = new GeolocationRequest(GeolocationAccuracy.Best, TimeSpan.FromSeconds(10));
|
||||||
|
tokenSource = new CancellationTokenSource();
|
||||||
|
var status = await Helper.CheckAndRequestPermissionAsync<Permissions.LocationWhenInUse>();
|
||||||
|
if (status != PermissionStatus.Granted)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return await Geolocation.GetLocationAsync(request, tokenSource.Token);
|
||||||
|
}
|
||||||
|
catch (FeatureNotSupportedException) { }
|
||||||
|
catch (FeatureNotEnabledException) { }
|
||||||
|
catch (PermissionException) { }
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Helper.Error("location.get", ex);
|
||||||
|
}
|
||||||
|
tokenSource = null;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
26
Billing.sln
@ -9,7 +9,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Billing.iOS", "Billing\Bill
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Billing.Shared", "Billing.Shared\Billing.Shared.shproj", "{6AC75D01-70D6-4A07-8685-BC52AFD97A7A}"
|
Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Billing.Shared", "Billing.Shared\Billing.Shared.shproj", "{6AC75D01-70D6-4A07-8685-BC52AFD97A7A}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Svg2Png", "Svg2Png\Svg2Png.csproj", "{6A012FCA-3B1C-4593-ADD7-0751E5815C67}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Svg2Png", "Svg2Png\Svg2Png.csproj", "{43BB5B21-61E0-42BB-ADF1-DBCD662E61E1}"
|
||||||
EndProject
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SharedMSBuildProjectFiles) = preSolution
|
GlobalSection(SharedMSBuildProjectFiles) = preSolution
|
||||||
@ -62,18 +62,18 @@ Global
|
|||||||
{5C4F1C35-6F66-4063-9605-A9F37FCABBA8}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator
|
{5C4F1C35-6F66-4063-9605-A9F37FCABBA8}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator
|
||||||
{5C4F1C35-6F66-4063-9605-A9F37FCABBA8}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator
|
{5C4F1C35-6F66-4063-9605-A9F37FCABBA8}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator
|
||||||
{5C4F1C35-6F66-4063-9605-A9F37FCABBA8}.Release|iPhoneSimulator.Deploy.0 = Release|iPhoneSimulator
|
{5C4F1C35-6F66-4063-9605-A9F37FCABBA8}.Release|iPhoneSimulator.Deploy.0 = Release|iPhoneSimulator
|
||||||
{6A012FCA-3B1C-4593-ADD7-0751E5815C67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{43BB5B21-61E0-42BB-ADF1-DBCD662E61E1}.Debug|Any CPU.ActiveCfg = Debug|iPhoneSimulator
|
||||||
{6A012FCA-3B1C-4593-ADD7-0751E5815C67}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{43BB5B21-61E0-42BB-ADF1-DBCD662E61E1}.Debug|Any CPU.Build.0 = Debug|iPhoneSimulator
|
||||||
{6A012FCA-3B1C-4593-ADD7-0751E5815C67}.Debug|iPhone.ActiveCfg = Debug|Any CPU
|
{43BB5B21-61E0-42BB-ADF1-DBCD662E61E1}.Debug|iPhone.ActiveCfg = Debug|iPhoneSimulator
|
||||||
{6A012FCA-3B1C-4593-ADD7-0751E5815C67}.Debug|iPhone.Build.0 = Debug|Any CPU
|
{43BB5B21-61E0-42BB-ADF1-DBCD662E61E1}.Debug|iPhone.Build.0 = Debug|iPhoneSimulator
|
||||||
{6A012FCA-3B1C-4593-ADD7-0751E5815C67}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
{43BB5B21-61E0-42BB-ADF1-DBCD662E61E1}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator
|
||||||
{6A012FCA-3B1C-4593-ADD7-0751E5815C67}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
|
{43BB5B21-61E0-42BB-ADF1-DBCD662E61E1}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator
|
||||||
{6A012FCA-3B1C-4593-ADD7-0751E5815C67}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{43BB5B21-61E0-42BB-ADF1-DBCD662E61E1}.Release|Any CPU.ActiveCfg = Release|iPhoneSimulator
|
||||||
{6A012FCA-3B1C-4593-ADD7-0751E5815C67}.Release|Any CPU.Build.0 = Release|Any CPU
|
{43BB5B21-61E0-42BB-ADF1-DBCD662E61E1}.Release|Any CPU.Build.0 = Release|iPhoneSimulator
|
||||||
{6A012FCA-3B1C-4593-ADD7-0751E5815C67}.Release|iPhone.ActiveCfg = Release|Any CPU
|
{43BB5B21-61E0-42BB-ADF1-DBCD662E61E1}.Release|iPhone.ActiveCfg = Release|iPhoneSimulator
|
||||||
{6A012FCA-3B1C-4593-ADD7-0751E5815C67}.Release|iPhone.Build.0 = Release|Any CPU
|
{43BB5B21-61E0-42BB-ADF1-DBCD662E61E1}.Release|iPhone.Build.0 = Release|iPhoneSimulator
|
||||||
{6A012FCA-3B1C-4593-ADD7-0751E5815C67}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
{43BB5B21-61E0-42BB-ADF1-DBCD662E61E1}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator
|
||||||
{6A012FCA-3B1C-4593-ADD7-0751E5815C67}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
|
{43BB5B21-61E0-42BB-ADF1-DBCD662E61E1}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
@ -16,12 +16,12 @@
|
|||||||
<AndroidManifest>Properties\AndroidManifest.xml</AndroidManifest>
|
<AndroidManifest>Properties\AndroidManifest.xml</AndroidManifest>
|
||||||
<MonoAndroidResourcePrefix>Resources</MonoAndroidResourcePrefix>
|
<MonoAndroidResourcePrefix>Resources</MonoAndroidResourcePrefix>
|
||||||
<MonoAndroidAssetsPrefix>Assets</MonoAndroidAssetsPrefix>
|
<MonoAndroidAssetsPrefix>Assets</MonoAndroidAssetsPrefix>
|
||||||
<TargetFrameworkVersion>v12.0</TargetFrameworkVersion>
|
<TargetFrameworkVersion>v11.0</TargetFrameworkVersion>
|
||||||
<AndroidEnableSGenConcurrent>true</AndroidEnableSGenConcurrent>
|
<AndroidEnableSGenConcurrent>true</AndroidEnableSGenConcurrent>
|
||||||
<AndroidUseAapt2>true</AndroidUseAapt2>
|
<AndroidUseAapt2>true</AndroidUseAapt2>
|
||||||
<AndroidHttpClientHandlerType>Xamarin.Android.Net.AndroidClientHandler</AndroidHttpClientHandlerType>
|
<AndroidHttpClientHandlerType>Xamarin.Android.Net.AndroidClientHandler</AndroidHttpClientHandlerType>
|
||||||
<NuGetPackageImportStamp>
|
<NuGetPackageImportStamp></NuGetPackageImportStamp>
|
||||||
</NuGetPackageImportStamp>
|
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||||
<LangVersion>9.0</LangVersion>
|
<LangVersion>9.0</LangVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||||
@ -37,11 +37,12 @@
|
|||||||
<EnableLLVM>false</EnableLLVM>
|
<EnableLLVM>false</EnableLLVM>
|
||||||
<AndroidEnableProfiledAot>false</AndroidEnableProfiledAot>
|
<AndroidEnableProfiledAot>false</AndroidEnableProfiledAot>
|
||||||
<BundleAssemblies>false</BundleAssemblies>
|
<BundleAssemblies>false</BundleAssemblies>
|
||||||
<AndroidSupportedAbis>x86_64;x86</AndroidSupportedAbis>
|
<AndroidSupportedAbis>x86;x86_64;arm64-v8a</AndroidSupportedAbis>
|
||||||
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
|
<EmbedAssembliesIntoApk>false</EmbedAssembliesIntoApk>
|
||||||
|
<MandroidI18n />
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||||
<DebugSymbols>true</DebugSymbols>
|
<DebugSymbols>false</DebugSymbols>
|
||||||
<DebugType>portable</DebugType>
|
<DebugType>portable</DebugType>
|
||||||
<Optimize>true</Optimize>
|
<Optimize>true</Optimize>
|
||||||
<OutputPath>bin\Release</OutputPath>
|
<OutputPath>bin\Release</OutputPath>
|
||||||
@ -52,25 +53,28 @@
|
|||||||
<AotAssemblies>false</AotAssemblies>
|
<AotAssemblies>false</AotAssemblies>
|
||||||
<EnableLLVM>false</EnableLLVM>
|
<EnableLLVM>false</EnableLLVM>
|
||||||
<AndroidEnableProfiledAot>false</AndroidEnableProfiledAot>
|
<AndroidEnableProfiledAot>false</AndroidEnableProfiledAot>
|
||||||
<BundleAssemblies>false</BundleAssemblies>
|
<BundleAssemblies>true</BundleAssemblies>
|
||||||
<AndroidSupportedAbis>arm64-v8a</AndroidSupportedAbis>
|
<AndroidSupportedAbis>x86_64;arm64-v8a</AndroidSupportedAbis>
|
||||||
|
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
|
||||||
|
<AndroidCreatePackagePerAbi>true</AndroidCreatePackagePerAbi>
|
||||||
|
<AndroidLinkTool>r8</AndroidLinkTool>
|
||||||
|
<MandroidI18n />
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Reference Include="Mono.Android" />
|
<Reference Include="Mono.Android" />
|
||||||
<Reference Include="System" />
|
<Reference Include="System" />
|
||||||
<Reference Include="System.Core" />
|
<Reference Include="System.Core" />
|
||||||
<Reference Include="System.Numerics" />
|
<Reference Include="System.Numerics" />
|
||||||
<Reference Include="System.Numerics.Vectors" />
|
|
||||||
<Reference Include="System.Xml.Linq" />
|
<Reference Include="System.Xml.Linq" />
|
||||||
<Reference Include="System.Xml" />
|
<Reference Include="System.Xml" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microcharts.Forms">
|
<PackageReference Include="Microcharts.Forms" Version="0.9.5.9" />
|
||||||
<Version>0.9.5.9</Version>
|
|
||||||
</PackageReference>
|
|
||||||
<PackageReference Include="SkiaSharp.Views.Forms" Version="2.80.3" />
|
<PackageReference Include="SkiaSharp.Views.Forms" Version="2.80.3" />
|
||||||
<PackageReference Include="Xamarin.Forms" Version="5.0.0.2337" />
|
<PackageReference Include="sqlite-net-pcl" Version="1.8.116" />
|
||||||
<PackageReference Include="Xamarin.Essentials" Version="1.7.1" />
|
<PackageReference Include="Xamarin.Forms" Version="5.0.0.2401" />
|
||||||
|
<PackageReference Include="Xamarin.Essentials" Version="1.7.2" />
|
||||||
|
<PackageReference Include="Xamarin.Forms.Maps" Version="5.0.0.2401" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="Definition.cs" />
|
<Compile Include="Definition.cs" />
|
||||||
@ -90,6 +94,8 @@
|
|||||||
<Compile Include="Renderers\TintImageButtonRenderer.cs" />
|
<Compile Include="Renderers\TintImageButtonRenderer.cs" />
|
||||||
<Compile Include="Renderers\BillingPageRenderer.cs" />
|
<Compile Include="Renderers\BillingPageRenderer.cs" />
|
||||||
<Compile Include="Renderers\BlurryPanelRenderer.cs" />
|
<Compile Include="Renderers\BlurryPanelRenderer.cs" />
|
||||||
|
<Compile Include="Renderers\SegmentedControlRenderer.cs" />
|
||||||
|
<Compile Include="Renderers\OptionPickerRenderer.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<AndroidAsset Include="Assets\fa-brands-400.ttf" />
|
<AndroidAsset Include="Assets\fa-brands-400.ttf" />
|
||||||
@ -129,6 +135,66 @@
|
|||||||
<Generator>
|
<Generator>
|
||||||
</Generator>
|
</Generator>
|
||||||
</AndroidResource>
|
</AndroidResource>
|
||||||
|
<AndroidResource Include="Resources\layout\RadioGroup.xml">
|
||||||
|
<SubType>
|
||||||
|
</SubType>
|
||||||
|
<Generator>
|
||||||
|
</Generator>
|
||||||
|
</AndroidResource>
|
||||||
|
<AndroidResource Include="Resources\layout\RadioButton.xml">
|
||||||
|
<SubType>
|
||||||
|
</SubType>
|
||||||
|
<Generator>
|
||||||
|
</Generator>
|
||||||
|
</AndroidResource>
|
||||||
|
<AndroidResource Include="Resources\color\segmented_control_text.xml">
|
||||||
|
<SubType>
|
||||||
|
</SubType>
|
||||||
|
<Generator>
|
||||||
|
</Generator>
|
||||||
|
</AndroidResource>
|
||||||
|
<AndroidResource Include="Resources\drawable\segmented_control_background.xml">
|
||||||
|
<SubType>
|
||||||
|
</SubType>
|
||||||
|
<Generator>
|
||||||
|
</Generator>
|
||||||
|
</AndroidResource>
|
||||||
|
<AndroidResource Include="Resources\drawable\segmented_control_first_background.xml">
|
||||||
|
<SubType>
|
||||||
|
</SubType>
|
||||||
|
<Generator>
|
||||||
|
</Generator>
|
||||||
|
</AndroidResource>
|
||||||
|
<AndroidResource Include="Resources\drawable\segmented_control_last_background.xml">
|
||||||
|
<SubType>
|
||||||
|
</SubType>
|
||||||
|
<Generator>
|
||||||
|
</Generator>
|
||||||
|
</AndroidResource>
|
||||||
|
<AndroidResource Include="Resources\drawable\share.png">
|
||||||
|
<SubType>
|
||||||
|
</SubType>
|
||||||
|
<Generator>
|
||||||
|
</Generator>
|
||||||
|
</AndroidResource>
|
||||||
|
<AndroidResource Include="Resources\drawable-mdpi\share.png">
|
||||||
|
<SubType>
|
||||||
|
</SubType>
|
||||||
|
<Generator>
|
||||||
|
</Generator>
|
||||||
|
</AndroidResource>
|
||||||
|
<AndroidResource Include="Resources\drawable-xhdpi\share.png">
|
||||||
|
<SubType>
|
||||||
|
</SubType>
|
||||||
|
<Generator>
|
||||||
|
</Generator>
|
||||||
|
</AndroidResource>
|
||||||
|
<AndroidResource Include="Resources\drawable-xxhdpi\share.png">
|
||||||
|
<SubType>
|
||||||
|
</SubType>
|
||||||
|
<Generator>
|
||||||
|
</Generator>
|
||||||
|
</AndroidResource>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<AndroidResource Include="Resources\drawable\splash_logo.png" />
|
<AndroidResource Include="Resources\drawable\splash_logo.png" />
|
||||||
@ -543,6 +609,9 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<AndroidResource Include="Resources\drawable\left.png" />
|
<AndroidResource Include="Resources\drawable\left.png" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<AndroidResource Include="Resources\xml\shortcuts.xml" />
|
||||||
|
</ItemGroup>
|
||||||
<Import Project="..\..\Billing.Shared\Billing.Shared.projitems" Label="Shared" />
|
<Import Project="..\..\Billing.Shared\Billing.Shared.projitems" Label="Shared" />
|
||||||
<Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.CSharp.targets" />
|
<Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.CSharp.targets" />
|
||||||
</Project>
|
</Project>
|
@ -2,20 +2,9 @@
|
|||||||
{
|
{
|
||||||
public static partial class Definition
|
public static partial class Definition
|
||||||
{
|
{
|
||||||
public static partial (string main, long build) GetVersion()
|
public const string RegularFontFamily = "OpenSans-Regular.ttf#OpenSans-Regular";
|
||||||
{
|
public const string SemiBoldFontFamily = "OpenSans-SemiBold.ttf#OpenSans-SemiBold";
|
||||||
var context = Android.App.Application.Context;
|
public const string BoldFontFamily = "OpenSans-Bold.ttf#OpenSans-Bold";
|
||||||
var manager = context.PackageManager;
|
public const string BrandsFontFamily = "fa-brands-400.ttf#FontAwesome6Brands-Regular";
|
||||||
var info = manager.GetPackageInfo(context.PackageName, 0);
|
|
||||||
|
|
||||||
string version = info.VersionName;
|
|
||||||
long build = info.LongVersionCode;
|
|
||||||
|
|
||||||
return (version, build);
|
|
||||||
}
|
|
||||||
public static partial string GetRegularFontFamily() => "OpenSans-Regular.ttf#OpenSans-Regular";
|
|
||||||
public static partial string GetSemiBoldFontFamily() => "OpenSans-SemiBold.ttf#OpenSans-SemiBold";
|
|
||||||
public static partial string GetBoldFontFamily() => "OpenSans-Bold.ttf#OpenSans-Bold";
|
|
||||||
public static partial string GetBrandsFontFamily() => "fa-brands-400.ttf#FontAwesome6Brands-Regular";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,7 +1,11 @@
|
|||||||
using Android.App;
|
using Android.App;
|
||||||
|
using Android.Content;
|
||||||
using Android.Content.PM;
|
using Android.Content.PM;
|
||||||
using Android.Runtime;
|
using Android.Runtime;
|
||||||
using Android.OS;
|
using Android.OS;
|
||||||
|
using Android.Net;
|
||||||
|
using Android.Provider;
|
||||||
|
using Android.Database;
|
||||||
|
|
||||||
namespace Billing.Droid
|
namespace Billing.Droid
|
||||||
{
|
{
|
||||||
@ -18,9 +22,26 @@ namespace Billing.Droid
|
|||||||
{
|
{
|
||||||
base.OnCreate(savedInstanceState);
|
base.OnCreate(savedInstanceState);
|
||||||
|
|
||||||
|
string url;
|
||||||
|
if (Intent.ActionView.Equals(Intent.Action) && Intent.Data is Uri uri)
|
||||||
|
{
|
||||||
|
if (uri.Authority == "org.tsanie.billing.shortcuts")
|
||||||
|
{
|
||||||
|
url = uri.Path;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
url = GetFilePath(BaseContext, uri);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
url = null;
|
||||||
|
}
|
||||||
Xamarin.Essentials.Platform.Init(this, savedInstanceState);
|
Xamarin.Essentials.Platform.Init(this, savedInstanceState);
|
||||||
Xamarin.Forms.Forms.Init(this, savedInstanceState);
|
Xamarin.Forms.Forms.Init(this, savedInstanceState);
|
||||||
LoadApplication(new App());
|
Xamarin.FormsMaps.Init(this, savedInstanceState);
|
||||||
|
LoadApplication(new App(url));
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Permission[] grantResults)
|
public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Permission[] grantResults)
|
||||||
@ -29,5 +50,83 @@ namespace Billing.Droid
|
|||||||
|
|
||||||
base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
|
base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string GetFilePath(Context context, Uri uri)
|
||||||
|
{
|
||||||
|
if (DocumentsContract.IsDocumentUri(context, uri))
|
||||||
|
{
|
||||||
|
Uri contentUri;
|
||||||
|
string[] split;
|
||||||
|
switch (uri.Authority)
|
||||||
|
{
|
||||||
|
case "com.android.externalstorage.documents":
|
||||||
|
split = DocumentsContract.GetDocumentId(uri).Split(':');
|
||||||
|
if (split[0] == "primary")
|
||||||
|
{
|
||||||
|
var external = ExternalCacheDir.Path;
|
||||||
|
external = external[..external.IndexOf("/Android/")];
|
||||||
|
return external + "/" + split[1];
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "com.android.providers.downloads.documents":
|
||||||
|
contentUri = ContentUris.WithAppendedId(
|
||||||
|
Uri.Parse("content://downloads/public_downloads"),
|
||||||
|
long.Parse(DocumentsContract.GetDocumentId(uri)));
|
||||||
|
return GetDataColumn(context, contentUri, null, null);
|
||||||
|
|
||||||
|
case "com.android.providers.media.documents":
|
||||||
|
split = DocumentsContract.GetDocumentId(uri).Split(':');
|
||||||
|
contentUri = split[0] switch
|
||||||
|
{
|
||||||
|
"image" => MediaStore.Images.Media.ExternalContentUri,
|
||||||
|
"video" => MediaStore.Video.Media.ExternalContentUri,
|
||||||
|
"audio" => MediaStore.Audio.Media.ExternalContentUri,
|
||||||
|
_ => null
|
||||||
|
};
|
||||||
|
return GetDataColumn(context, contentUri, "_id=?", new[] { split[1] });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (uri.Scheme == "content")
|
||||||
|
{
|
||||||
|
if (uri.Authority == "com.speedsoftware.rootexplorer.fileprovider")
|
||||||
|
{
|
||||||
|
var path = uri.Path;
|
||||||
|
if (path.StartsWith("/root/"))
|
||||||
|
{
|
||||||
|
return path[5..];
|
||||||
|
}
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
return GetDataColumn(context, uri, null, null);
|
||||||
|
}
|
||||||
|
else if (uri.Scheme == "file")
|
||||||
|
{
|
||||||
|
return uri.Path;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetDataColumn(Context context, Uri uri, string selection, string[] selectionArgs)
|
||||||
|
{
|
||||||
|
ICursor cursor = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
cursor = context.ContentResolver.Query(uri, new[] { "_data" }, selection, selectionArgs, null);
|
||||||
|
if (cursor != null && cursor.MoveToFirst())
|
||||||
|
{
|
||||||
|
var index = cursor.GetColumnIndexOrThrow("_data");
|
||||||
|
return cursor.GetString(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (cursor != null)
|
||||||
|
{
|
||||||
|
cursor.Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,6 +1,12 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionName="0.8.309" package="org.tsanie.billing" android:installLocation="auto" android:versionCode="8">
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionName="1.2.411" package="org.tsanie.billing" android:installLocation="auto" android:versionCode="21s">
|
||||||
<uses-sdk android:minSdkVersion="24" android:targetSdkVersion="31" />
|
<uses-sdk android:minSdkVersion="24" android:targetSdkVersion="30" />
|
||||||
<application android:label="@string/applabel" android:theme="@style/MainTheme"></application>
|
<application android:label="@string/applabel" android:theme="@style/MainTheme" android:requestLegacyExternalStorage="true"></application>
|
||||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
|
<queries>
|
||||||
|
<intent>
|
||||||
|
<action android:name="android.intent.action.SENDTO" />
|
||||||
|
<data android:scheme="mailto" />
|
||||||
|
</intent>
|
||||||
|
</queries>
|
||||||
</manifest>
|
</manifest>
|
@ -27,3 +27,11 @@ using Android.App;
|
|||||||
// Add some common permissions, these can be removed if not needed
|
// Add some common permissions, these can be removed if not needed
|
||||||
[assembly: UsesPermission(Android.Manifest.Permission.Internet)]
|
[assembly: UsesPermission(Android.Manifest.Permission.Internet)]
|
||||||
[assembly: UsesPermission(Android.Manifest.Permission.WriteExternalStorage)]
|
[assembly: UsesPermission(Android.Manifest.Permission.WriteExternalStorage)]
|
||||||
|
[assembly: UsesPermission(Android.Manifest.Permission.ReadExternalStorage)]
|
||||||
|
[assembly: UsesPermission(Android.Manifest.Permission.ManageExternalStorage)]
|
||||||
|
[assembly: UsesPermission(Android.Manifest.Permission.Vibrate)]
|
||||||
|
[assembly: UsesPermission(Android.Manifest.Permission.AccessCoarseLocation)]
|
||||||
|
[assembly: UsesPermission(Android.Manifest.Permission.AccessFineLocation)]
|
||||||
|
[assembly: UsesFeature("android.hardware.location", Required = false)]
|
||||||
|
[assembly: UsesFeature("android.hardware.location.gps", Required = false)]
|
||||||
|
[assembly: UsesFeature("android.hardware.location.network", Required = false)]
|
||||||
|
@ -18,7 +18,7 @@ namespace Billing.Droid.Renderers
|
|||||||
base.OnAttachedToWindow();
|
base.OnAttachedToWindow();
|
||||||
if (Element is BillingPage page)
|
if (Element is BillingPage page)
|
||||||
{
|
{
|
||||||
page.OnLoaded();
|
page.TriggerLoad();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,10 +18,12 @@ namespace Billing.Droid.Renderers
|
|||||||
{
|
{
|
||||||
base.OnElementChanged(e);
|
base.OnElementChanged(e);
|
||||||
|
|
||||||
if (e.NewElement != null)
|
var control = Control;
|
||||||
|
if (e.NewElement != null && control != null)
|
||||||
{
|
{
|
||||||
var drawable = new ColorDrawable(e.NewElement.BackgroundColor.ToAndroid());
|
var drawable = new ColorDrawable(e.NewElement.BackgroundColor.ToAndroid());
|
||||||
Control.SetBackground(drawable);
|
control.SetBackground(drawable);
|
||||||
|
control.Gravity = Android.Views.GravityFlags.CenterHorizontal;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
29
Billing/Billing.Android/Renderers/OptionPickerRenderer.cs
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
using Android.Content;
|
||||||
|
using Android.Graphics.Drawables;
|
||||||
|
using Billing.Droid.Renderers;
|
||||||
|
using Billing.UI;
|
||||||
|
using Xamarin.Forms;
|
||||||
|
using Xamarin.Forms.Platform.Android;
|
||||||
|
|
||||||
|
[assembly: ExportRenderer(typeof(OptionPicker), typeof(OptionPickerRenderer))]
|
||||||
|
namespace Billing.Droid.Renderers
|
||||||
|
{
|
||||||
|
public class OptionPickerRenderer : PickerRenderer
|
||||||
|
{
|
||||||
|
public OptionPickerRenderer(Context context) : base(context)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnElementChanged(ElementChangedEventArgs<Picker> e)
|
||||||
|
{
|
||||||
|
base.OnElementChanged(e);
|
||||||
|
|
||||||
|
var control = Control;
|
||||||
|
if (e.NewElement != null && control != null)
|
||||||
|
{
|
||||||
|
var drawable = new ColorDrawable(e.NewElement.BackgroundColor.ToAndroid());
|
||||||
|
control.SetBackground(drawable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -18,10 +18,11 @@ namespace Billing.Droid.Renderers
|
|||||||
{
|
{
|
||||||
base.OnElementChanged(e);
|
base.OnElementChanged(e);
|
||||||
|
|
||||||
if (e.NewElement != null)
|
var control = Control;
|
||||||
|
if (e.NewElement != null && control != null)
|
||||||
{
|
{
|
||||||
var drawable = new ColorDrawable(e.NewElement.BackgroundColor.ToAndroid());
|
var drawable = new ColorDrawable(e.NewElement.BackgroundColor.ToAndroid());
|
||||||
Control.SetBackground(drawable);
|
control.SetBackground(drawable);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
245
Billing/Billing.Android/Renderers/SegmentedControlRenderer.cs
Normal file
@ -0,0 +1,245 @@
|
|||||||
|
using Android.Content;
|
||||||
|
using Android.Graphics.Drawables;
|
||||||
|
using Android.Views;
|
||||||
|
using Android.Widget;
|
||||||
|
using Billing.Droid.Renderers;
|
||||||
|
using Billing.UI;
|
||||||
|
using Xamarin.Forms;
|
||||||
|
using Xamarin.Forms.Platform.Android;
|
||||||
|
using RadioButton = Android.Widget.RadioButton;
|
||||||
|
|
||||||
|
[assembly: ExportRenderer(typeof(SegmentedControl), typeof(SegmentedControlRenderer))]
|
||||||
|
namespace Billing.Droid.Renderers
|
||||||
|
{
|
||||||
|
public class SegmentedControlRenderer : ViewRenderer<SegmentedControl, RadioGroup>
|
||||||
|
{
|
||||||
|
RadioGroup nativeControl;
|
||||||
|
RadioButton _rb;
|
||||||
|
readonly Context context;
|
||||||
|
|
||||||
|
public SegmentedControlRenderer(Context context) : base(context)
|
||||||
|
{
|
||||||
|
this.context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnElementChanged(ElementChangedEventArgs<SegmentedControl> e)
|
||||||
|
{
|
||||||
|
base.OnElementChanged(e);
|
||||||
|
|
||||||
|
if (Control == null)
|
||||||
|
{
|
||||||
|
// Instantiate the native control and assign it to the Control property with
|
||||||
|
// the SetNativeControl method
|
||||||
|
var layoutInflater = LayoutInflater.From(context);
|
||||||
|
|
||||||
|
var view = layoutInflater.Inflate(Resource.Layout.RadioGroup, null);
|
||||||
|
|
||||||
|
nativeControl = (RadioGroup)layoutInflater.Inflate(Resource.Layout.RadioGroup, null);
|
||||||
|
var density = context.Resources.DisplayMetrics.Density;
|
||||||
|
|
||||||
|
for (var i = 0; i < Element.Children.Count; i++)
|
||||||
|
{
|
||||||
|
var o = Element.Children[i];
|
||||||
|
var rb = (RadioButton)layoutInflater.Inflate(Resource.Layout.RadioButton, null);
|
||||||
|
|
||||||
|
var width = rb.Paint.MeasureText(o.Text) * density + 0.5f;
|
||||||
|
rb.LayoutParameters = new RadioGroup.LayoutParams((int)width, LayoutParams.WrapContent, 1f);
|
||||||
|
rb.Text = o.Text;
|
||||||
|
|
||||||
|
if (i == 0)
|
||||||
|
rb.SetBackgroundResource(Resource.Drawable.segmented_control_first_background);
|
||||||
|
else if (i == Element.Children.Count - 1)
|
||||||
|
rb.SetBackgroundResource(Resource.Drawable.segmented_control_last_background);
|
||||||
|
else
|
||||||
|
rb.SetBackgroundResource(Resource.Drawable.segmented_control_background);
|
||||||
|
|
||||||
|
ConfigureRadioButton(i, rb);
|
||||||
|
|
||||||
|
nativeControl.AddView(rb);
|
||||||
|
}
|
||||||
|
|
||||||
|
var option = (RadioButton)nativeControl.GetChildAt(Element.SelectedSegmentIndex);
|
||||||
|
|
||||||
|
if (option != null)
|
||||||
|
option.Checked = true;
|
||||||
|
|
||||||
|
SetNativeControl(nativeControl);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.OldElement != null)
|
||||||
|
{
|
||||||
|
// Unsubscribe from event handlers and cleanup any resources
|
||||||
|
if (nativeControl != null)
|
||||||
|
nativeControl.CheckedChange -= NativeControl_ValueChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.NewElement != null)
|
||||||
|
{
|
||||||
|
// Configure the control and subscribe to event handlers
|
||||||
|
nativeControl.CheckedChange += NativeControl_ValueChanged;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnElementPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
|
||||||
|
{
|
||||||
|
base.OnElementPropertyChanged(sender, e);
|
||||||
|
|
||||||
|
if (nativeControl == null || Element == null) return;
|
||||||
|
|
||||||
|
switch (e.PropertyName)
|
||||||
|
{
|
||||||
|
case "Renderer":
|
||||||
|
Element?.SendValueChanged();
|
||||||
|
break;
|
||||||
|
case nameof(SegmentedControl.SelectedSegmentIndex):
|
||||||
|
var option = (RadioButton)nativeControl.GetChildAt(Element.SelectedSegmentIndex);
|
||||||
|
|
||||||
|
if (option != null)
|
||||||
|
option.Checked = true;
|
||||||
|
|
||||||
|
if (Element.SelectedSegmentIndex < 0)
|
||||||
|
{
|
||||||
|
var layoutInflater = LayoutInflater.From(context);
|
||||||
|
|
||||||
|
nativeControl = (RadioGroup)layoutInflater.Inflate(Resource.Layout.RadioGroup, null);
|
||||||
|
|
||||||
|
for (var i = 0; i < Element.Children.Count; i++)
|
||||||
|
{
|
||||||
|
var o = Element.Children[i];
|
||||||
|
var rb = (RadioButton)layoutInflater.Inflate(Resource.Layout.RadioButton, null);
|
||||||
|
|
||||||
|
var width = rb.Paint.MeasureText(o.Text);
|
||||||
|
rb.LayoutParameters = new RadioGroup.LayoutParams((int)width, LayoutParams.WrapContent, 1f);
|
||||||
|
rb.Text = o.Text;
|
||||||
|
|
||||||
|
if (i == 0)
|
||||||
|
rb.SetBackgroundResource(Resource.Drawable.segmented_control_first_background);
|
||||||
|
else if (i == Element.Children.Count - 1)
|
||||||
|
rb.SetBackgroundResource(Resource.Drawable.segmented_control_last_background);
|
||||||
|
else
|
||||||
|
rb.SetBackgroundResource(Resource.Drawable.segmented_control_background);
|
||||||
|
|
||||||
|
ConfigureRadioButton(i, rb);
|
||||||
|
|
||||||
|
nativeControl.AddView(rb);
|
||||||
|
}
|
||||||
|
|
||||||
|
nativeControl.CheckedChange += NativeControl_ValueChanged;
|
||||||
|
|
||||||
|
SetNativeControl(nativeControl);
|
||||||
|
}
|
||||||
|
|
||||||
|
Element.SendValueChanged();
|
||||||
|
break;
|
||||||
|
case nameof(SegmentedControl.TintColor):
|
||||||
|
OnPropertyChanged();
|
||||||
|
break;
|
||||||
|
case nameof(SegmentedControl.IsEnabled):
|
||||||
|
OnPropertyChanged();
|
||||||
|
break;
|
||||||
|
case nameof(SegmentedControl.SelectedTextColor):
|
||||||
|
var v = (RadioButton)nativeControl.GetChildAt(Element.SelectedSegmentIndex);
|
||||||
|
v.SetTextColor(Element.SelectedTextColor.ToAndroid());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnPropertyChanged()
|
||||||
|
{
|
||||||
|
if (nativeControl != null && Element != null)
|
||||||
|
{
|
||||||
|
for (var i = 0; i < Element.Children.Count; i++)
|
||||||
|
{
|
||||||
|
var rb = (RadioButton)nativeControl.GetChildAt(i);
|
||||||
|
|
||||||
|
ConfigureRadioButton(i, rb);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConfigureRadioButton(int i, RadioButton rb)
|
||||||
|
{
|
||||||
|
var textColor = Element.SelectedTextColor;
|
||||||
|
if (i == Element.SelectedSegmentIndex)
|
||||||
|
{
|
||||||
|
rb.SetTextColor(textColor.ToAndroid());
|
||||||
|
rb.Paint.FakeBoldText = true;
|
||||||
|
_rb = rb;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var tColor = Element.IsEnabled ?
|
||||||
|
textColor.IsDefault ? Element.TintColor.ToAndroid() : textColor.MultiplyAlpha(.6).ToAndroid() :
|
||||||
|
Element.DisabledColor.ToAndroid();
|
||||||
|
rb.SetTextColor(tColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
GradientDrawable selectedShape;
|
||||||
|
GradientDrawable unselectedShape;
|
||||||
|
|
||||||
|
var gradientDrawable = (StateListDrawable)rb.Background;
|
||||||
|
var drawableContainerState = (DrawableContainer.DrawableContainerState)gradientDrawable.GetConstantState();
|
||||||
|
var children = drawableContainerState.GetChildren();
|
||||||
|
|
||||||
|
// Doesnt works on API < 18
|
||||||
|
selectedShape = children[0] is GradientDrawable selected ? selected : (GradientDrawable)((InsetDrawable)children[0]).Drawable;
|
||||||
|
unselectedShape = children[1] is GradientDrawable unselected ? unselected : (GradientDrawable)((InsetDrawable)children[1]).Drawable;
|
||||||
|
|
||||||
|
var color = Element.IsEnabled ? Element.TintColor.ToAndroid() : Element.DisabledColor.ToAndroid();
|
||||||
|
|
||||||
|
selectedShape.SetStroke(3, color);
|
||||||
|
selectedShape.SetColor(color);
|
||||||
|
unselectedShape.SetStroke(3, color);
|
||||||
|
|
||||||
|
rb.Enabled = Element.IsEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NativeControl_ValueChanged(object sender, RadioGroup.CheckedChangeEventArgs e)
|
||||||
|
{
|
||||||
|
var rg = (RadioGroup)sender;
|
||||||
|
if (rg.CheckedRadioButtonId != -1)
|
||||||
|
{
|
||||||
|
var id = rg.CheckedRadioButtonId;
|
||||||
|
var radioButton = rg.FindViewById(id);
|
||||||
|
var radioId = rg.IndexOfChild(radioButton);
|
||||||
|
|
||||||
|
var rb = (RadioButton)rg.GetChildAt(radioId);
|
||||||
|
|
||||||
|
var textColor = Element.SelectedTextColor;
|
||||||
|
var color = Element.IsEnabled ?
|
||||||
|
textColor.IsDefault ? Element.TintColor.ToAndroid() : textColor.MultiplyAlpha(.6).ToAndroid() :
|
||||||
|
Element.DisabledColor.ToAndroid();
|
||||||
|
if (_rb != null)
|
||||||
|
{
|
||||||
|
_rb.SetTextColor(color);
|
||||||
|
_rb.Paint.FakeBoldText = false;
|
||||||
|
}
|
||||||
|
rb.SetTextColor(Element.SelectedTextColor.ToAndroid());
|
||||||
|
rb.Paint.FakeBoldText = true;
|
||||||
|
_rb = rb;
|
||||||
|
|
||||||
|
Element.SelectedSegmentIndex = radioId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (nativeControl != null)
|
||||||
|
{
|
||||||
|
nativeControl.CheckedChange -= NativeControl_ValueChanged;
|
||||||
|
nativeControl.Dispose();
|
||||||
|
nativeControl = null;
|
||||||
|
_rb = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
base.Dispose(disposing);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
13026
Billing/Billing.Android/Resources/Resource.designer.cs
generated
@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:state_checked="true" android:color="@color/normal"/>
|
||||||
|
<item android:color="@color/selected" />
|
||||||
|
</selector>
|
Before Width: | Height: | Size: 508 B After Width: | Height: | Size: 540 B |
BIN
Billing/Billing.Android/Resources/drawable-mdpi/location.png
Normal file
After Width: | Height: | Size: 797 B |
BIN
Billing/Billing.Android/Resources/drawable-mdpi/pin.png
Normal file
After Width: | Height: | Size: 443 B |
BIN
Billing/Billing.Android/Resources/drawable-mdpi/share.png
Normal file
After Width: | Height: | Size: 403 B |
BIN
Billing/Billing.Android/Resources/drawable-mdpi/sync.png
Normal file
After Width: | Height: | Size: 799 B |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
BIN
Billing/Billing.Android/Resources/drawable-xhdpi/location.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
Billing/Billing.Android/Resources/drawable-xhdpi/pin.png
Normal file
After Width: | Height: | Size: 874 B |
BIN
Billing/Billing.Android/Resources/drawable-xhdpi/share.png
Normal file
After Width: | Height: | Size: 774 B |
BIN
Billing/Billing.Android/Resources/drawable-xhdpi/sync.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
BIN
Billing/Billing.Android/Resources/drawable-xxhdpi/location.png
Normal file
After Width: | Height: | Size: 2.8 KiB |
BIN
Billing/Billing.Android/Resources/drawable-xxhdpi/pin.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
Billing/Billing.Android/Resources/drawable-xxhdpi/share.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
Billing/Billing.Android/Resources/drawable-xxhdpi/sync.png
Normal file
After Width: | Height: | Size: 2.7 KiB |
Before Width: | Height: | Size: 975 B After Width: | Height: | Size: 975 B |
BIN
Billing/Billing.Android/Resources/drawable/location.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
Billing/Billing.Android/Resources/drawable/pin.png
Normal file
After Width: | Height: | Size: 662 B |
@ -0,0 +1,19 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:state_checked="true">
|
||||||
|
<inset android:insetRight="-1dp">
|
||||||
|
<shape android:id="@+id/shape_id" xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
|
||||||
|
<solid android:color="@color/selected" />
|
||||||
|
<stroke android:width="1dp" android:color="@color/selected" />
|
||||||
|
</shape>
|
||||||
|
</inset>
|
||||||
|
</item>
|
||||||
|
<item android:state_checked="false">
|
||||||
|
<inset android:insetRight="-1dp">
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
|
||||||
|
<solid android:color="@color/normal" />
|
||||||
|
<stroke android:width="1dp" android:color="@color/selected" />
|
||||||
|
</shape>
|
||||||
|
</inset>
|
||||||
|
</item>
|
||||||
|
</selector>
|
@ -0,0 +1,21 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:state_checked="true">
|
||||||
|
<inset android:insetRight="-1dp">
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
|
||||||
|
<solid android:color="@color/selected" />
|
||||||
|
<stroke android:width="1dp" android:color="@color/selected" />
|
||||||
|
<corners android:topLeftRadius="2sp" android:bottomLeftRadius="2sp" />
|
||||||
|
</shape>
|
||||||
|
</inset>
|
||||||
|
</item>
|
||||||
|
<item android:state_checked="false">
|
||||||
|
<inset android:insetRight="-1dp">
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
|
||||||
|
<solid android:color="@color/normal" />
|
||||||
|
<stroke android:width="1dp" android:color="@color/selected" />
|
||||||
|
<corners android:topLeftRadius="2sp" android:bottomLeftRadius="2sp" />
|
||||||
|
</shape>
|
||||||
|
</inset>
|
||||||
|
</item>
|
||||||
|
</selector>
|
@ -0,0 +1,17 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:state_checked="true">
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
|
||||||
|
<solid android:color="@color/selected" />
|
||||||
|
<stroke android:width="1dp" android:color="@color/selected" />
|
||||||
|
<corners android:topRightRadius="2sp" android:bottomRightRadius="2sp" />
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
<item android:state_checked="false">
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
|
||||||
|
<solid android:color="@color/normal" />
|
||||||
|
<stroke android:width="1dp" android:color="@color/selected" />
|
||||||
|
<corners android:topRightRadius="2sp" android:bottomRightRadius="2sp" />
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
</selector>
|
BIN
Billing/Billing.Android/Resources/drawable/share.png
Normal file
After Width: | Height: | Size: 647 B |
BIN
Billing/Billing.Android/Resources/drawable/sync.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
13
Billing/Billing.Android/Resources/layout/RadioButton.xml
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<RadioButton xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:button="@null"
|
||||||
|
android:gravity="center"
|
||||||
|
android:background="@drawable/segmented_control_background"
|
||||||
|
android:textColor="@color/segmented_control_text"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:paddingLeft="10dp"
|
||||||
|
android:paddingRight="10dp"
|
||||||
|
android:minHeight="30dp"
|
||||||
|
android:textSize="12sp" />
|
6
Billing/Billing.Android/Resources/layout/RadioGroup.xml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<RadioGroup xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:id="@+id/SegControl" />
|
@ -1,4 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" ?>
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
<resources>
|
<resources>
|
||||||
<string name="applabel">记账本</string>
|
<string name="applabel">记账本</string>
|
||||||
|
<string name="newbill">记录一条</string>
|
||||||
</resources>
|
</resources>
|
@ -3,5 +3,7 @@
|
|||||||
<color name="colorPrimary">#183153</color>
|
<color name="colorPrimary">#183153</color>
|
||||||
<color name="colorPrimaryDark">#2B0B98</color>
|
<color name="colorPrimaryDark">#2B0B98</color>
|
||||||
<color name="colorAccent">#2B0B98</color>
|
<color name="colorAccent">#2B0B98</color>
|
||||||
|
<color name="normal">@android:color/transparent</color>
|
||||||
|
<color name="selected">#007AFF</color>
|
||||||
<color name="splash_background">?android:attr/colorBackground</color>
|
<color name="splash_background">?android:attr/colorBackground</color>
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" ?>
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
<resources>
|
<resources>
|
||||||
<string name="applabel">Billing</string>
|
<string name="applabel">Billing</string>
|
||||||
|
<string name="newbill">Write a bill</string>
|
||||||
</resources>
|
</resources>
|
6
Billing/Billing.Android/Resources/xml/shortcuts.xml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
|
<shortcuts xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<shortcut android:shortcutId="newbill" android:enabled="true" android:icon="@drawable/daily" android:shortcutShortLabel="@string/newbill" android:shortcutLongLabel="@string/newbill" android:shortcutDisabledMessage="@string/newbill">
|
||||||
|
<intent android:action="android.intent.action.VIEW" android:targetPackage="org.tsanie.billing" android:targetClass="org.tsanie.billing.SplashScreen" android:data="content://org.tsanie.billing.shortcuts/newbill" />
|
||||||
|
</shortcut>
|
||||||
|
</shortcuts>
|
@ -1,5 +1,6 @@
|
|||||||
using Android.App;
|
using Android.App;
|
||||||
using Android.Content;
|
using Android.Content;
|
||||||
|
using Android.Net;
|
||||||
using Android.OS;
|
using Android.OS;
|
||||||
using AndroidX.AppCompat.App;
|
using AndroidX.AppCompat.App;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@ -11,6 +12,15 @@ namespace Billing.Droid
|
|||||||
NoHistory = true,
|
NoHistory = true,
|
||||||
Theme = "@style/MainTheme.Splash",
|
Theme = "@style/MainTheme.Splash",
|
||||||
Name = "org.tsanie.billing.SplashScreen")]
|
Name = "org.tsanie.billing.SplashScreen")]
|
||||||
|
[IntentFilter(
|
||||||
|
new[] { Intent.ActionView },
|
||||||
|
Categories = new[] { Intent.CategoryDefault, Intent.CategoryBrowsable },
|
||||||
|
//DataScheme = "file",
|
||||||
|
DataMimeType = "*/*",
|
||||||
|
DataPathPattern = ".*\\.db3")]
|
||||||
|
[MetaData(
|
||||||
|
"android.app.shortcuts",
|
||||||
|
Resource = "@xml/shortcuts")]
|
||||||
public class SplashActivity : AppCompatActivity
|
public class SplashActivity : AppCompatActivity
|
||||||
{
|
{
|
||||||
public override void OnCreate(Bundle savedInstanceState, PersistableBundle persistentState)
|
public override void OnCreate(Bundle savedInstanceState, PersistableBundle persistentState)
|
||||||
@ -23,7 +33,16 @@ namespace Billing.Droid
|
|||||||
protected override void OnResume()
|
protected override void OnResume()
|
||||||
{
|
{
|
||||||
base.OnResume();
|
base.OnResume();
|
||||||
Task.Run(() => StartActivity(new Intent(Application.Context, typeof(MainActivity))));
|
Task.Run(() =>
|
||||||
|
{
|
||||||
|
var intent = new Intent(Application.Context, typeof(MainActivity));
|
||||||
|
if (Intent?.Data is Uri uri)
|
||||||
|
{
|
||||||
|
intent.SetAction(Intent.ActionView);
|
||||||
|
intent.SetData(uri);
|
||||||
|
}
|
||||||
|
StartActivity(intent);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -19,9 +19,55 @@ namespace Billing.iOS
|
|||||||
public override bool FinishedLaunching(UIApplication app, NSDictionary options)
|
public override bool FinishedLaunching(UIApplication app, NSDictionary options)
|
||||||
{
|
{
|
||||||
Xamarin.Forms.Forms.Init();
|
Xamarin.Forms.Forms.Init();
|
||||||
LoadApplication(new App());
|
Xamarin.FormsMaps.Init();
|
||||||
|
string action;
|
||||||
|
if (options != null && options.TryGetValue(UIApplication.LaunchOptionsShortcutItemKey, out var obj) && obj is UIApplicationShortcutItem shortcut)
|
||||||
|
{
|
||||||
|
action = "/" + shortcut.Type;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
action = null;
|
||||||
|
}
|
||||||
|
LoadApplication(new App(action));
|
||||||
|
|
||||||
return base.FinishedLaunching(app, options);
|
return base.FinishedLaunching(app, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override bool OpenUrl(UIApplication app, NSUrl url, NSDictionary options)
|
||||||
|
{
|
||||||
|
if (url?.IsFileUrl == true)
|
||||||
|
{
|
||||||
|
return App.OpenUrl(url.Path);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async void PerformActionForShortcutItem(UIApplication application, UIApplicationShortcutItem shortcutItem, UIOperationHandler completionHandler)
|
||||||
|
{
|
||||||
|
if (shortcutItem == null || string.IsNullOrEmpty(shortcutItem.Type))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (App.Accounts.Count == 0)
|
||||||
|
{
|
||||||
|
await App.InitializeData();
|
||||||
|
}
|
||||||
|
Xamarin.Essentials.MainThread.BeginInvokeOnMainThread(async () =>
|
||||||
|
{
|
||||||
|
var state = Xamarin.Forms.Shell.Current.CurrentState.Location;
|
||||||
|
var route = state.OriginalString;
|
||||||
|
var current = Xamarin.Forms.Shell.Current;
|
||||||
|
if (!route.StartsWith("//Bills"))
|
||||||
|
{
|
||||||
|
await current.GoToAsync("//Bills");
|
||||||
|
}
|
||||||
|
else if (route.EndsWith("/Details") || route.StartsWith("//Bills/D_FAULT_AddBillPage"))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await current.GoToAsync("Details");
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1 +1,3 @@
|
|||||||
"CFBundleDisplayName" = "Billing";
|
"CFBundleDisplayName" = "Billing";
|
||||||
|
"BillingShortcutNew" = "Write a bill";
|
||||||
|
"NSLocationWhenInUseUsageDescription" = "When "Save Location" is checked, the Billing app needs to access the location.";
|
@ -14,8 +14,9 @@
|
|||||||
<AssemblyName>Billing.iOS</AssemblyName>
|
<AssemblyName>Billing.iOS</AssemblyName>
|
||||||
<MtouchEnableSGenConc>true</MtouchEnableSGenConc>
|
<MtouchEnableSGenConc>true</MtouchEnableSGenConc>
|
||||||
<MtouchHttpClientHandler>NSUrlSessionHandler</MtouchHttpClientHandler>
|
<MtouchHttpClientHandler>NSUrlSessionHandler</MtouchHttpClientHandler>
|
||||||
<ProvisioningType>automatic</ProvisioningType>
|
<ProvisioningType>manual</ProvisioningType>
|
||||||
<LangVersion>9.0</LangVersion>
|
<LangVersion>9.0</LangVersion>
|
||||||
|
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|iPhoneSimulator' ">
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|iPhoneSimulator' ">
|
||||||
<DebugSymbols>true</DebugSymbols>
|
<DebugSymbols>true</DebugSymbols>
|
||||||
@ -38,6 +39,7 @@
|
|||||||
<MtouchLink>None</MtouchLink>
|
<MtouchLink>None</MtouchLink>
|
||||||
<MtouchArch>x86_64</MtouchArch>
|
<MtouchArch>x86_64</MtouchArch>
|
||||||
<CodesignKey>iPhone Distribution</CodesignKey>
|
<CodesignKey>iPhone Distribution</CodesignKey>
|
||||||
|
<MtouchUseLlvm>true</MtouchUseLlvm>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|iPhone' ">
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|iPhone' ">
|
||||||
<DebugSymbols>true</DebugSymbols>
|
<DebugSymbols>true</DebugSymbols>
|
||||||
@ -64,6 +66,9 @@
|
|||||||
<CodesignKey>iPhone Distribution</CodesignKey>
|
<CodesignKey>iPhone Distribution</CodesignKey>
|
||||||
<CodesignEntitlements>Entitlements.plist</CodesignEntitlements>
|
<CodesignEntitlements>Entitlements.plist</CodesignEntitlements>
|
||||||
<MtouchLink>SdkOnly</MtouchLink>
|
<MtouchLink>SdkOnly</MtouchLink>
|
||||||
|
<MtouchInterpreter>-all</MtouchInterpreter>
|
||||||
|
<MtouchUseLlvm>true</MtouchUseLlvm>
|
||||||
|
<OptimizePNGs>true</OptimizePNGs>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="Definition.cs" />
|
<Compile Include="Definition.cs" />
|
||||||
@ -91,6 +96,19 @@
|
|||||||
<BundleResource Include="Resources\OpenSans-SemiBold.ttf" />
|
<BundleResource Include="Resources\OpenSans-SemiBold.ttf" />
|
||||||
<Compile Include="Renderers\BlurryPanelRenderer.cs" />
|
<Compile Include="Renderers\BlurryPanelRenderer.cs" />
|
||||||
<Compile Include="Renderers\SegmentedControlRenderer.cs" />
|
<Compile Include="Renderers\SegmentedControlRenderer.cs" />
|
||||||
|
<Compile Include="Renderers\OptionPickerRenderer.cs" />
|
||||||
|
<BundleResource Include="Resources\share.png" />
|
||||||
|
<BundleResource Include="Resources\share%402x.png" />
|
||||||
|
<BundleResource Include="Resources\share%403x.png" />
|
||||||
|
<BundleResource Include="Resources\sync.png" />
|
||||||
|
<BundleResource Include="Resources\sync%402x.png" />
|
||||||
|
<BundleResource Include="Resources\sync%403x.png" />
|
||||||
|
<BundleResource Include="Resources\location.png" />
|
||||||
|
<BundleResource Include="Resources\location%402x.png" />
|
||||||
|
<BundleResource Include="Resources\location%403x.png" />
|
||||||
|
<BundleResource Include="Resources\pin.png" />
|
||||||
|
<BundleResource Include="Resources\pin%402x.png" />
|
||||||
|
<BundleResource Include="Resources\pin%403x.png" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ImageAsset Include="Assets.xcassets\AppIcon.appiconset\Contents.json">
|
<ImageAsset Include="Assets.xcassets\AppIcon.appiconset\Contents.json">
|
||||||
@ -165,15 +183,14 @@
|
|||||||
<Reference Include="System.Xml.Linq" />
|
<Reference Include="System.Xml.Linq" />
|
||||||
<Reference Include="Xamarin.iOS" />
|
<Reference Include="Xamarin.iOS" />
|
||||||
<Reference Include="System.Numerics" />
|
<Reference Include="System.Numerics" />
|
||||||
<Reference Include="System.Numerics.Vectors" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microcharts.Forms">
|
<PackageReference Include="Microcharts.Forms" Version="0.9.5.9" />
|
||||||
<Version>0.9.5.9</Version>
|
|
||||||
</PackageReference>
|
|
||||||
<PackageReference Include="SkiaSharp.Views.Forms" Version="2.80.3" />
|
<PackageReference Include="SkiaSharp.Views.Forms" Version="2.80.3" />
|
||||||
<PackageReference Include="Xamarin.Forms" Version="5.0.0.2337" />
|
<PackageReference Include="sqlite-net-pcl" Version="1.8.116" />
|
||||||
<PackageReference Include="Xamarin.Essentials" Version="1.7.1" />
|
<PackageReference Include="Xamarin.Forms" Version="5.0.0.2401" />
|
||||||
|
<PackageReference Include="Xamarin.Essentials" Version="1.7.2" />
|
||||||
|
<PackageReference Include="Xamarin.Forms.Maps" Version="5.0.0.2401" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<BundleResource Include="Resources\dollar.png" />
|
<BundleResource Include="Resources\dollar.png" />
|
||||||
|
@ -2,13 +2,9 @@
|
|||||||
{
|
{
|
||||||
public static partial class Definition
|
public static partial class Definition
|
||||||
{
|
{
|
||||||
public static partial (string main, long build) GetVersion() => (
|
public const string RegularFontFamily = "OpenSans-Regular";
|
||||||
Foundation.NSBundle.MainBundle.ObjectForInfoDictionary("CFBundleShortVersionString").ToString(),
|
public const string SemiBoldFontFamily = "OpenSans-SemiBold";
|
||||||
int.Parse(Foundation.NSBundle.MainBundle.ObjectForInfoDictionary("CFBundleVersion").ToString())
|
public const string BoldFontFamily = "OpenSans-Bold";
|
||||||
);
|
public const string BrandsFontFamily = "FontAwesome6Brands-Regular";
|
||||||
public static partial string GetRegularFontFamily() => "OpenSans-Regular";
|
|
||||||
public static partial string GetSemiBoldFontFamily() => "OpenSans-SemiBold";
|
|
||||||
public static partial string GetBoldFontFamily() => "OpenSans-Bold";
|
|
||||||
public static partial string GetBrandsFontFamily() => "FontAwesome6Brands-Regular";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -41,11 +41,68 @@
|
|||||||
<string>OpenSans-Regular.ttf</string>
|
<string>OpenSans-Regular.ttf</string>
|
||||||
<string>OpenSans-SemiBold.ttf</string>
|
<string>OpenSans-SemiBold.ttf</string>
|
||||||
</array>
|
</array>
|
||||||
|
<key>CFBundleDocumentTypes</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleTypeName</key>
|
||||||
|
<string>SQLite Database</string>
|
||||||
|
<key>LSItemContentTypes</key>
|
||||||
|
<array>
|
||||||
|
<string>org.tsanie.billing.db3</string>
|
||||||
|
<string>public.database</string>
|
||||||
|
</array>
|
||||||
|
<key>CFBundleTypeIconFiles</key>
|
||||||
|
<array>
|
||||||
|
<string>Assets.xcassets/AppIcon.appiconset/Icon180</string>
|
||||||
|
</array>
|
||||||
|
<key>LSHandlerRank</key>
|
||||||
|
<string>Alternate</string>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
|
<key>UTExportedTypeDeclarations</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>UTTypeConformsTo</key>
|
||||||
|
<array>
|
||||||
|
<string>public.data</string>
|
||||||
|
</array>
|
||||||
|
<key>UTTypeDescription</key>
|
||||||
|
<string>SQLite Database</string>
|
||||||
|
<key>UTTypeIdentifier</key>
|
||||||
|
<string>org.tsanie.billing.db3</string>
|
||||||
|
<key>UTTypeTagSpecification</key>
|
||||||
|
<dict>
|
||||||
|
<key>public.filename-extension</key>
|
||||||
|
<string>db3</string>
|
||||||
|
<key>public.mime-type</key>
|
||||||
|
<string>application/vnd.sqlite3</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
|
<key>CFBundleVersion</key>
|
||||||
|
<string>21</string>
|
||||||
|
<key>CFBundleShortVersionString</key>
|
||||||
|
<string>1.2.411</string>
|
||||||
|
<key>LSApplicationQueriesSchemes</key>
|
||||||
|
<array>
|
||||||
|
<string>mailto</string>
|
||||||
|
</array>
|
||||||
<key>UIFileSharingEnabled</key>
|
<key>UIFileSharingEnabled</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>CFBundleVersion</key>
|
<key>LSSupportsOpeningDocumentsInPlace</key>
|
||||||
<string>8</string>
|
<true/>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>NSLocationWhenInUseUsageDescription</key>
|
||||||
<string>0.8.309</string>
|
<string>When "Save Location" is checked, the Billing app needs to access the location.</string>
|
||||||
|
<key>UIApplicationShortcutItems</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>UIApplicationShortcutItemIconType</key>
|
||||||
|
<string>UIApplicationShortcutIconTypeCompose</string>
|
||||||
|
<key>UIApplicationShortcutItemTitle</key>
|
||||||
|
<string>BillingShortcutNew</string>
|
||||||
|
<key>UIApplicationShortcutItemType</key>
|
||||||
|
<string>newbill</string>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
@ -55,7 +55,7 @@ namespace Billing.iOS.Renderers
|
|||||||
base.ViewDidAppear(animated);
|
base.ViewDidAppear(animated);
|
||||||
if (Element is BillingPage page)
|
if (Element is BillingPage page)
|
||||||
{
|
{
|
||||||
page.OnLoaded();
|
page.TriggerLoad();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,9 +14,10 @@ namespace Billing.iOS.Renderers
|
|||||||
base.OnElementChanged(e);
|
base.OnElementChanged(e);
|
||||||
|
|
||||||
var control = Control;
|
var control = Control;
|
||||||
if (control != null)
|
if (e.NewElement != null && control != null)
|
||||||
{
|
{
|
||||||
control.BorderStyle = UITextBorderStyle.None;
|
control.BorderStyle = UITextBorderStyle.None;
|
||||||
|
control.TextAlignment = UITextAlignment.Center;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
46
Billing/Billing.iOS/Renderers/OptionPickerRenderer.cs
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
using System.ComponentModel;
|
||||||
|
using Billing.iOS.Renderers;
|
||||||
|
using Billing.UI;
|
||||||
|
using UIKit;
|
||||||
|
using Xamarin.Forms;
|
||||||
|
using Xamarin.Forms.Platform.iOS;
|
||||||
|
|
||||||
|
[assembly: ExportRenderer(typeof(OptionPicker), typeof(OptionPickerRenderer))]
|
||||||
|
namespace Billing.iOS.Renderers
|
||||||
|
{
|
||||||
|
public class OptionPickerRenderer : PickerRenderer
|
||||||
|
{
|
||||||
|
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
|
||||||
|
{
|
||||||
|
base.OnElementPropertyChanged(sender, e);
|
||||||
|
|
||||||
|
if (e.PropertyName == nameof(OptionPicker.BorderStyle))
|
||||||
|
{
|
||||||
|
var control = Control;
|
||||||
|
if (control != null && Element is OptionPicker picker)
|
||||||
|
{
|
||||||
|
control.BorderStyle = picker.BorderStyle switch
|
||||||
|
{
|
||||||
|
BorderStyle.RoundedRect => UITextBorderStyle.RoundedRect,
|
||||||
|
_ => UITextBorderStyle.None
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnElementChanged(ElementChangedEventArgs<Picker> e)
|
||||||
|
{
|
||||||
|
base.OnElementChanged(e);
|
||||||
|
|
||||||
|
var control = Control;
|
||||||
|
if (control != null && e.NewElement is OptionPicker picker)
|
||||||
|
{
|
||||||
|
control.BorderStyle = picker.BorderStyle switch
|
||||||
|
{
|
||||||
|
BorderStyle.RoundedRect => UITextBorderStyle.RoundedRect,
|
||||||
|
_ => UITextBorderStyle.None
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -92,7 +92,7 @@ namespace Billing.iOS.Renderers
|
|||||||
{
|
{
|
||||||
//var color = Element.SelectedTextColor;
|
//var color = Element.SelectedTextColor;
|
||||||
//UIColor c = color == default ? UIColor.LabelColor : color.ToUIColor();
|
//UIColor c = color == default ? UIColor.LabelColor : color.ToUIColor();
|
||||||
UIColor c = UIColor.LabelColor;
|
UIColor c = UIColor.Label;
|
||||||
var attribute = new UITextAttributes
|
var attribute = new UITextAttributes
|
||||||
{
|
{
|
||||||
TextColor = c
|
TextColor = c
|
||||||
@ -112,7 +112,7 @@ namespace Billing.iOS.Renderers
|
|||||||
//var tintColor = element.TintColor;
|
//var tintColor = element.TintColor;
|
||||||
//if (tintColor == default)
|
//if (tintColor == default)
|
||||||
//{
|
//{
|
||||||
return UIColor.SystemGray6Color;
|
return UIColor.SystemGray6;
|
||||||
//}
|
//}
|
||||||
//else
|
//else
|
||||||
//{
|
//{
|
||||||
@ -124,7 +124,7 @@ namespace Billing.iOS.Renderers
|
|||||||
//var disabledColor = element.DisabledColor;
|
//var disabledColor = element.DisabledColor;
|
||||||
//if (disabledColor == default)
|
//if (disabledColor == default)
|
||||||
//{
|
//{
|
||||||
return UIColor.SecondaryLabelColor;
|
return UIColor.SecondaryLabel;
|
||||||
//}
|
//}
|
||||||
//else
|
//else
|
||||||
//{
|
//{
|
||||||
|
Before Width: | Height: | Size: 508 B After Width: | Height: | Size: 540 B |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |