switch to sqlite
This commit is contained in:
parent
f5f16d43f4
commit
5ec4119025
@ -27,25 +27,21 @@ namespace Billing
|
|||||||
InitResources();
|
InitResources();
|
||||||
|
|
||||||
MainPage = new MainShell();
|
MainPage = new MainShell();
|
||||||
Shell.Current.GoToAsync("//Settings");
|
Shell.Current.GoToAsync("//Splash");
|
||||||
}
|
}
|
||||||
|
|
||||||
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();
|
internal static async Task InitilalizeData()
|
||||||
categories = StoreHelper.GetCategories();
|
{
|
||||||
bills = StoreHelper.GetBills();
|
await Task.WhenAll(
|
||||||
|
Task.Run(async () => accounts = await StoreHelper.GetAccountsAsync()),
|
||||||
Shell.Current.GoToAsync("//Bills");
|
Task.Run(async () => categories = await StoreHelper.GetCategoriesAsync()),
|
||||||
|
Task.Run(async () => bills = await StoreHelper.GetBillsAsync()));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnResume()
|
protected override void OnResume()
|
||||||
|
@ -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" />
|
||||||
@ -21,7 +25,6 @@
|
|||||||
<Compile Include="$(MSBuildThisFileDirectory)MainShell.xaml.cs">
|
<Compile Include="$(MSBuildThisFileDirectory)MainShell.xaml.cs">
|
||||||
<DependentUpon>MainShell.xaml</DependentUpon>
|
<DependentUpon>MainShell.xaml</DependentUpon>
|
||||||
</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" />
|
||||||
@ -78,6 +81,10 @@
|
|||||||
<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>
|
||||||
|
</Compile>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<EmbeddedResource Include="$(MSBuildThisFileDirectory)MainShell.xaml">
|
<EmbeddedResource Include="$(MSBuildThisFileDirectory)MainShell.xaml">
|
||||||
|
@ -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;
|
||||||
|
|
||||||
@ -82,7 +85,7 @@ namespace Billing
|
|||||||
{
|
{
|
||||||
return new UIBill(b)
|
return new UIBill(b)
|
||||||
{
|
{
|
||||||
Icon = App.Categories.FirstOrDefault(c => c.Id == b.CategoryId)?.Icon ?? BaseModel.ICON_DEFAULT,
|
Icon = App.Categories.FirstOrDefault(c => c.Id == b.CategoryId)?.Icon ?? Definition.DefaultIcon,
|
||||||
Name = b.Name,
|
Name = b.Name,
|
||||||
DateCreation = b.CreateTime,
|
DateCreation = b.CreateTime,
|
||||||
Amount = b.Amount,
|
Amount = b.Amount,
|
||||||
@ -130,4 +133,24 @@ 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal class AsyncLazy<T>
|
||||||
|
{
|
||||||
|
private readonly Lazy<Task<T>> instance;
|
||||||
|
|
||||||
|
public AsyncLazy(Func<T> factory)
|
||||||
|
{
|
||||||
|
instance = new Lazy<Task<T>>(() => Task.Run(factory));
|
||||||
|
}
|
||||||
|
|
||||||
|
public AsyncLazy(Func<Task<T>> factory)
|
||||||
|
{
|
||||||
|
instance = new Lazy<Task<T>>(() => Task.Run(factory));
|
||||||
|
}
|
||||||
|
|
||||||
|
public TaskAwaiter<T> GetAwaiter()
|
||||||
|
{
|
||||||
|
return instance.Value.GetAwaiter();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@ -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"
|
||||||
@ -16,4 +17,8 @@
|
|||||||
<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>
|
||||||
|
|
||||||
|
<Tab>
|
||||||
|
<ShellContent ContentTemplate="{DataTemplate local:SplashPage}" Route="Splash"/>
|
||||||
|
</Tab>
|
||||||
|
|
||||||
</Shell>
|
</Shell>
|
@ -1,9 +1,12 @@
|
|||||||
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";
|
||||||
|
|
||||||
|
[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 +14,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,5 @@ 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 override void OnXmlDeserialize(XElement node)
|
|
||||||
{
|
|
||||||
Id = Read(node, nameof(Id), 0);
|
|
||||||
Amount = Read(node, nameof(Amount), 0m);
|
|
||||||
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,19 @@
|
|||||||
using Xamarin.Forms;
|
using SQLite;
|
||||||
using System.Xml.Linq;
|
|
||||||
|
|
||||||
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;
|
||||||
|
|
||||||
|
[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 override void OnXmlDeserialize(XElement node)
|
|
||||||
{
|
|
||||||
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
7
Billing.Shared/Models/IIdItem.cs
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
namespace Billing.Models
|
||||||
|
{
|
||||||
|
public interface IIdItem
|
||||||
|
{
|
||||||
|
public int Id { get; }
|
||||||
|
}
|
||||||
|
}
|
9
Billing.Shared/SplashPage.xaml
Normal file
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>
|
20
Billing.Shared/SplashPage.xaml.cs
Normal file
20
Billing.Shared/SplashPage.xaml.cs
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
using Billing.UI;
|
||||||
|
using Xamarin.Forms;
|
||||||
|
|
||||||
|
namespace Billing
|
||||||
|
{
|
||||||
|
public partial class SplashPage : BillingPage
|
||||||
|
{
|
||||||
|
public SplashPage()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async void OnLoaded()
|
||||||
|
{
|
||||||
|
await App.InitilalizeData();
|
||||||
|
|
||||||
|
await Shell.Current.GoToAsync("//Bills");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,9 +1,10 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
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,50 +15,20 @@ 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";
|
#region Sqlite3
|
||||||
private const string billFile = "bills.xml";
|
private const string dbfile = "data.db3";
|
||||||
private const string categoryFile = "categories.xml";
|
private static SQLiteAsyncConnection database;
|
||||||
|
#endregion
|
||||||
|
|
||||||
private static StoreHelper instance;
|
private static readonly AsyncLazy<StoreHelper> Instance = new(async () =>
|
||||||
private static StoreHelper Instance => instance ??= new StoreHelper();
|
|
||||||
|
|
||||||
public static List<Account> GetAccounts() => Instance.GetAccountsInternal();
|
|
||||||
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 instance = new StoreHelper();
|
||||||
}
|
await database.CreateTablesAsync<Category, Account, Bill>();
|
||||||
|
var count = await database.ExecuteScalarAsync<int>("SELECT COUNT(Id) FROM [Category]");
|
||||||
private void WriteAccountsInternal(IEnumerable<Account> accounts)
|
if (count <= 0)
|
||||||
{
|
{
|
||||||
var filename = Path.Combine(PersonalFolder, accountFile);
|
// init categories
|
||||||
WriteList(filename, accounts);
|
await database.InsertAllAsync(new List<Category>
|
||||||
}
|
|
||||||
|
|
||||||
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>
|
|
||||||
{
|
{
|
||||||
// sample categories
|
// sample categories
|
||||||
new() { Id = 1, Name = Resource.Clothing, Icon = "clothes" },
|
new() { Id = 1, Name = Resource.Clothing, Icon = "clothes" },
|
||||||
@ -89,55 +60,147 @@ namespace Billing.Store
|
|||||||
new() { Id = 113, ParentId = 6, Name = Resource.Party, Icon = "party" },
|
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 = 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" },
|
new() { Id = 201, ParentId = 10, Type = CategoryType.Income, Name = Resource.Bonus, Icon = "dollar" },
|
||||||
};
|
});
|
||||||
Task.Run(() => WriteCategoriesInternal(list));
|
|
||||||
}
|
}
|
||||||
return list;
|
return instance;
|
||||||
|
});
|
||||||
|
|
||||||
|
public static async Task<List<Account>> GetAccountsAsync()
|
||||||
|
{
|
||||||
|
var instance = await Instance;
|
||||||
|
return await instance.GetListAsync<Account>();
|
||||||
|
}
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void WriteCategoriesInternal(IEnumerable<Category> categories)
|
public static async Task<List<Bill>> GetBillsAsync()
|
||||||
{
|
{
|
||||||
var filename = Path.Combine(PersonalFolder, categoryFile);
|
var instance = await Instance;
|
||||||
WriteList(filename, categories);
|
return await instance.GetListAsync<Bill>();
|
||||||
|
}
|
||||||
|
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<List<Category>> GetCategoriesAsync()
|
||||||
|
{
|
||||||
|
var instance = await Instance;
|
||||||
|
return await instance.GetListAsync<Category>();
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
private StoreHelper()
|
||||||
|
{
|
||||||
|
database = new SQLiteAsyncConnection(Path.Combine(PersonalFolder, dbfile),
|
||||||
|
SQLiteOpenFlags.ReadWrite |
|
||||||
|
SQLiteOpenFlags.Create |
|
||||||
|
SQLiteOpenFlags.SharedCache);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<T> GetItemAsync<T>(int id) where T : IIdItem, new()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var source = new TaskCompletionSource<T>();
|
||||||
|
Task.Run(async () =>
|
||||||
|
{
|
||||||
|
var list = await database.QueryAsync<T>($"SELECT * FROM [{typeof(T).Name}] WHERE [Id] = ?", id);
|
||||||
|
source.SetResult(list.FirstOrDefault());
|
||||||
|
});
|
||||||
|
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
|
#region Helper
|
||||||
|
|
||||||
private void WriteList<T>(string filename, IEnumerable<T> list) where T : IModel, new()
|
private Task<List<T>> GetListAsync<T>() where T : new()
|
||||||
{
|
|
||||||
if (list == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try
|
|
||||||
{
|
|
||||||
using var stream = File.Open(filename, FileMode.Create);
|
|
||||||
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
|
try
|
||||||
{
|
{
|
||||||
if (File.Exists(file))
|
return database.Table<T>().ToListAsync();
|
||||||
{
|
|
||||||
using var stream = File.OpenRead(file);
|
|
||||||
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("db.read", $"failed to read db, error: {ex.Message}");
|
||||||
}
|
}
|
||||||
return default;
|
return default;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Task<int> SaveItemAsync<T>(T item) where T : IIdItem
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (item.Id < 0)
|
||||||
|
{
|
||||||
|
return database.InsertAsync(item);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return database.UpdateAsync(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(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private 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(-1);
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -205,7 +205,7 @@ 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);
|
||||||
}
|
}
|
||||||
|
@ -1,17 +1,15 @@
|
|||||||
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 PrimaryColorKey = "PrimaryColor";
|
||||||
|
public const string DefaultIcon = "ic_default";
|
||||||
|
|
||||||
public static partial (string main, long build) GetVersion();
|
public static partial (string main, long build) GetVersion();
|
||||||
public static partial string GetRegularFontFamily();
|
public static partial string GetRegularFontFamily();
|
||||||
public static partial string GetSemiBoldFontFamily();
|
public static partial string GetSemiBoldFontFamily();
|
||||||
@ -98,50 +96,33 @@ namespace Billing.UI
|
|||||||
// add 23:59:59.999...
|
// add 23:59:59.999...
|
||||||
return date.AddTicks(863999999999);
|
return date.AddTicks(863999999999);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static bool IsTransparent(this long color)
|
||||||
|
{
|
||||||
|
return (color & 0xff000000L) == 0x00000000L;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class ModelExtensionHelper
|
public static Color ToColor(this long color)
|
||||||
{
|
{
|
||||||
public static List<T> FromStream<T>(Stream stream) where T : IModel, new()
|
ulong c = (ulong)color;
|
||||||
{
|
int r = (int)(c & 0xff);
|
||||||
XDocument doc = XDocument.Load(stream);
|
c >>= 8;
|
||||||
var root = doc.Root;
|
int g = (int)(c & 0xff);
|
||||||
var list = new List<T>();
|
c >>= 8;
|
||||||
foreach (XElement ele in root.Elements("item"))
|
int b = (int)(c & 0xff);
|
||||||
{
|
c >>= 8;
|
||||||
if (ele.Attribute("null")?.Value == "1")
|
int a = (int)(c & 0xff);
|
||||||
{
|
return Color.FromRgba(r, g, b, a);
|
||||||
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 long ToLong(this Color color)
|
||||||
{
|
{
|
||||||
XElement root = new("root");
|
long l =
|
||||||
foreach (var t in list)
|
(uint)(color.A * 255) << 24 |
|
||||||
{
|
(uint)(color.B * 255) << 16 |
|
||||||
XElement item = new("item");
|
(uint)(color.G * 255) << 8 |
|
||||||
if (t == null)
|
(uint)(color.R * 255);
|
||||||
{
|
return l;
|
||||||
item.Add(new XAttribute("null", 1));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
t.OnXmlSerialize(item);
|
|
||||||
}
|
|
||||||
root.Add(item);
|
|
||||||
}
|
|
||||||
|
|
||||||
XDocument doc = new(new XDeclaration("1.0", "utf-8", "yes"), root);
|
|
||||||
doc.Save(stream, SaveOptions.DisableFormatting);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
|
||||||
@ -120,25 +120,27 @@ 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);
|
group.Remove(account);
|
||||||
if (group.Count == 0)
|
if (group.Count == 0)
|
||||||
{
|
{
|
||||||
accounts.Remove(group);
|
accounts.Remove(group);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
RefreshBalance();
|
RefreshBalance();
|
||||||
groupLayout.Refresh(accounts);
|
groupLayout.Refresh(accounts);
|
||||||
|
|
||||||
RankPage.Instance?.SetNeedRefresh();
|
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)
|
||||||
@ -151,7 +153,7 @@ namespace Billing.Views
|
|||||||
|
|
||||||
RankPage.Instance?.SetNeedRefresh();
|
RankPage.Instance?.SetNeedRefresh();
|
||||||
|
|
||||||
Task.Run(App.WriteAccounts);
|
await StoreHelper.SaveAccountItemAsync(e.Account);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -56,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
|
||||||
@ -103,6 +102,7 @@ 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)
|
||||||
{
|
{
|
||||||
@ -111,7 +111,7 @@ namespace Billing.Views
|
|||||||
Id = -1,
|
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
|
||||||
});
|
});
|
||||||
@ -120,7 +120,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();
|
||||||
|
@ -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;
|
||||||
@ -84,7 +85,7 @@ 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;
|
bill.Icon = App.Categories.FirstOrDefault(c => c.Id == bill.Bill.CategoryId)?.Icon ?? Definition.DefaultIcon;
|
||||||
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;
|
||||||
@ -158,13 +159,13 @@ namespace Billing.Views
|
|||||||
|
|
||||||
RankPage.Instance?.SetNeedRefresh();
|
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)
|
||||||
{
|
{
|
||||||
@ -195,7 +196,7 @@ namespace Billing.Views
|
|||||||
|
|
||||||
RankPage.Instance?.SetNeedRefresh();
|
RankPage.Instance?.SetNeedRefresh();
|
||||||
|
|
||||||
Task.Run(App.WriteBills);
|
await StoreHelper.SaveBillItemAsync(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
using Billing.Languages;
|
using Billing.Languages;
|
||||||
using Billing.Models;
|
using Billing.Models;
|
||||||
|
using Billing.Store;
|
||||||
using Billing.Themes;
|
using Billing.Themes;
|
||||||
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
|
||||||
@ -68,9 +68,9 @@ 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.IsTransparent() ?
|
||||||
BaseTheme.CurrentPrimaryColor :
|
BaseTheme.CurrentPrimaryColor :
|
||||||
category.TintColor
|
category.TintColor.ToColor()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -112,7 +112,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,7 +144,7 @@ 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)
|
||||||
{
|
{
|
||||||
@ -183,16 +183,16 @@ 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.IsTransparent() ?
|
||||||
BaseTheme.CurrentPrimaryColor :
|
BaseTheme.CurrentPrimaryColor :
|
||||||
c.Category.TintColor;
|
c.Category.TintColor.ToColor();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -72,7 +72,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.IsTransparent() ? defaultColor : c.TintColor.ToColor()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
@ -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" },
|
||||||
|
@ -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;
|
||||||
@ -473,7 +474,7 @@ namespace Billing.Views
|
|||||||
|
|
||||||
private async void OnBillChecked(object sender, Bill e)
|
private async void OnBillChecked(object sender, Bill e)
|
||||||
{
|
{
|
||||||
await Task.Run(App.WriteBills);
|
await StoreHelper.SaveBillItemAsync(e);
|
||||||
LoadData();
|
LoadData();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,12 +65,11 @@
|
|||||||
<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="Xamarin.Forms" Version="5.0.0.2337" />
|
||||||
<PackageReference Include="Xamarin.Essentials" Version="1.7.1" />
|
<PackageReference Include="Xamarin.Essentials" Version="1.7.1" />
|
||||||
|
<PackageReference Include="sqlite-net-pcl" Version="1.8.116" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="Definition.cs" />
|
<Compile Include="Definition.cs" />
|
||||||
|
@ -169,12 +169,11 @@
|
|||||||
<Reference Include="System.Numerics.Vectors" />
|
<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="Xamarin.Forms" Version="5.0.0.2337" />
|
||||||
<PackageReference Include="Xamarin.Essentials" Version="1.7.1" />
|
<PackageReference Include="Xamarin.Essentials" Version="1.7.1" />
|
||||||
|
<PackageReference Include="sqlite-net-pcl" Version="1.8.116" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<BundleResource Include="Resources\dollar.png" />
|
<BundleResource Include="Resources\dollar.png" />
|
||||||
|
@ -41,8 +41,6 @@
|
|||||||
<string>OpenSans-Regular.ttf</string>
|
<string>OpenSans-Regular.ttf</string>
|
||||||
<string>OpenSans-SemiBold.ttf</string>
|
<string>OpenSans-SemiBold.ttf</string>
|
||||||
</array>
|
</array>
|
||||||
<key>UIFileSharingEnabled</key>
|
|
||||||
<true/>
|
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>9</string>
|
<string>9</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user