switch to sqlite
This commit is contained in:
		| @@ -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> | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user