diff --git a/Billing.Shared/App.cs b/Billing.Shared/App.cs
index 6442405..9282a5d 100644
--- a/Billing.Shared/App.cs
+++ b/Billing.Shared/App.cs
@@ -27,25 +27,21 @@ namespace Billing
InitResources();
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()
{
Helper.Debug($"personal folder: {StoreHelper.PersonalFolder}");
Helper.Debug($"cache folder: {StoreHelper.CacheFolder}");
-
- accounts = StoreHelper.GetAccounts();
- categories = StoreHelper.GetCategories();
- bills = StoreHelper.GetBills();
+ }
- Shell.Current.GoToAsync("//Bills");
+ internal static async Task InitilalizeData()
+ {
+ await Task.WhenAll(
+ Task.Run(async () => accounts = await StoreHelper.GetAccountsAsync()),
+ Task.Run(async () => categories = await StoreHelper.GetCategoriesAsync()),
+ Task.Run(async () => bills = await StoreHelper.GetBillsAsync()));
}
protected override void OnResume()
diff --git a/Billing.Shared/Billing.Shared.projitems b/Billing.Shared/Billing.Shared.projitems
index 5566fd7..0afc34f 100644
--- a/Billing.Shared/Billing.Shared.projitems
+++ b/Billing.Shared/Billing.Shared.projitems
@@ -11,6 +11,10 @@
+
+ Designer
+ MSBuild:UpdateDesignTimeXaml
+
@@ -21,7 +25,6 @@
MainShell.xaml
-
@@ -78,6 +81,10 @@
+
+
+ SplashPage.xaml
+
diff --git a/Billing.Shared/Helper.cs b/Billing.Shared/Helper.cs
index 53beef9..f0a5100 100644
--- a/Billing.Shared/Helper.cs
+++ b/Billing.Shared/Helper.cs
@@ -1,8 +1,11 @@
using Billing.Models;
+using Billing.UI;
using Billing.Views;
using System;
using System.Globalization;
using System.Linq;
+using System.Runtime.CompilerServices;
+using System.Threading.Tasks;
using Xamarin.Essentials;
using Xamarin.Forms;
@@ -82,7 +85,7 @@ namespace Billing
{
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,
DateCreation = b.CreateTime,
Amount = b.Amount,
@@ -130,4 +133,24 @@ namespace Billing
public delegate void PropertyValueChanged(TOwner obj, TResult old, TResult @new);
}
+
+ internal class AsyncLazy
+ {
+ private readonly Lazy> instance;
+
+ public AsyncLazy(Func factory)
+ {
+ instance = new Lazy>(() => Task.Run(factory));
+ }
+
+ public AsyncLazy(Func> factory)
+ {
+ instance = new Lazy>(() => Task.Run(factory));
+ }
+
+ public TaskAwaiter GetAwaiter()
+ {
+ return instance.Value.GetAwaiter();
+ }
+ }
}
\ No newline at end of file
diff --git a/Billing.Shared/MainShell.xaml b/Billing.Shared/MainShell.xaml
index 3da6c25..44b1d24 100644
--- a/Billing.Shared/MainShell.xaml
+++ b/Billing.Shared/MainShell.xaml
@@ -1,6 +1,7 @@
+
+
+
+
\ No newline at end of file
diff --git a/Billing.Shared/Models/Account.cs b/Billing.Shared/Models/Account.cs
index 3dadad5..51e6ae2 100644
--- a/Billing.Shared/Models/Account.cs
+++ b/Billing.Shared/Models/Account.cs
@@ -1,9 +1,12 @@
-using System.Xml.Linq;
+using SQLite;
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 string Icon { get; set; } = ICON_DEFAULT;
public AccountCategory Category { get; set; }
@@ -11,28 +14,6 @@ namespace Billing.Models
public decimal Initial { get; set; }
public decimal Balance { 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
diff --git a/Billing.Shared/Models/BaseModel.cs b/Billing.Shared/Models/BaseModel.cs
deleted file mode 100644
index 0dbdb62..0000000
--- a/Billing.Shared/Models/BaseModel.cs
+++ /dev/null
@@ -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(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 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(XElement node, string subname, Func func)
- {
- var ele = node.Elements().FirstOrDefault(e => string.Equals(e.Name.ToString(), subname, StringComparison.OrdinalIgnoreCase));
- return func(ele);
- }
-
- private static T ReadObject(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(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(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(XElement parent, string name, T value) where T : IFormattable => WriteString(parent, name, ToString(value));
- protected static XElement WriteObject(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(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(XElement node, string subname, T def = default) where T : IModel, new() => ReadSubnode(node, subname, e => e == null ? def : ReadObject(e));
- protected static T[] ReadArray(XElement node, string subname, T[] def = null) where T : IModel, new() => ReadSubnode(node, subname, e => e == null ? def : ReadArray(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;
- }
- }
- }
-}
\ No newline at end of file
diff --git a/Billing.Shared/Models/Bill.cs b/Billing.Shared/Models/Bill.cs
index 4ed804f..bd11615 100644
--- a/Billing.Shared/Models/Bill.cs
+++ b/Billing.Shared/Models/Bill.cs
@@ -1,10 +1,11 @@
using System;
-using System.Xml.Linq;
+using SQLite;
namespace Billing.Models
{
- public class Bill : BaseModel
+ public class Bill : IIdItem
{
+ [PrimaryKey, AutoIncrement]
public int Id { get; set; }
public decimal Amount { get; set; }
public string Name { get; set; }
@@ -13,29 +14,5 @@ namespace Billing.Models
public string Store { get; set; }
public DateTime CreateTime { 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);
- }
}
}
\ No newline at end of file
diff --git a/Billing.Shared/Models/Category.cs b/Billing.Shared/Models/Category.cs
index 8fd2e1d..9726509 100644
--- a/Billing.Shared/Models/Category.cs
+++ b/Billing.Shared/Models/Category.cs
@@ -1,50 +1,19 @@
-using Xamarin.Forms;
-using System.Xml.Linq;
+using SQLite;
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 CategoryType Type { get; set; }
public string Icon { get; set; } = ICON_DEFAULT;
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 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
diff --git a/Billing.Shared/Models/IIdItem.cs b/Billing.Shared/Models/IIdItem.cs
new file mode 100644
index 0000000..1b1b124
--- /dev/null
+++ b/Billing.Shared/Models/IIdItem.cs
@@ -0,0 +1,7 @@
+namespace Billing.Models
+{
+ public interface IIdItem
+ {
+ public int Id { get; }
+ }
+}
diff --git a/Billing.Shared/SplashPage.xaml b/Billing.Shared/SplashPage.xaml
new file mode 100644
index 0000000..5c15884
--- /dev/null
+++ b/Billing.Shared/SplashPage.xaml
@@ -0,0 +1,9 @@
+
+
+
+
diff --git a/Billing.Shared/SplashPage.xaml.cs b/Billing.Shared/SplashPage.xaml.cs
new file mode 100644
index 0000000..5b3c88b
--- /dev/null
+++ b/Billing.Shared/SplashPage.xaml.cs
@@ -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");
+ }
+ }
+}
diff --git a/Billing.Shared/Store/StoreHelper.cs b/Billing.Shared/Store/StoreHelper.cs
index 98c94fd..86454ed 100644
--- a/Billing.Shared/Store/StoreHelper.cs
+++ b/Billing.Shared/Store/StoreHelper.cs
@@ -1,9 +1,10 @@
using System;
using System.Collections.Generic;
using System.IO;
+using System.Linq;
using System.Threading.Tasks;
using Billing.Models;
-using Billing.UI;
+using SQLite;
using Xamarin.Essentials;
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 CacheFolder = FileSystem.CacheDirectory;
- private const string accountFile = "accounts.xml";
- private const string billFile = "bills.xml";
- private const string categoryFile = "categories.xml";
+ #region Sqlite3
+ private const string dbfile = "data.db3";
+ private static SQLiteAsyncConnection database;
+ #endregion
- private static StoreHelper instance;
- private static StoreHelper Instance => instance ??= new StoreHelper();
-
- public static List GetAccounts() => Instance.GetAccountsInternal();
- public static void WriteAccounts(IEnumerable accounts) => Instance.WriteAccountsInternal(accounts);
- public static List GetBills() => Instance.GetBillsInternal();
- public static void WriteBills(IEnumerable bills) => Instance.WriteBillsInternal(bills);
- public static List GetCategories() => Instance.GetCategoriesInternal();
- public static void WriteCategories(IEnumerable categories) => Instance.WriteCategoriesInternal(categories);
-
- private StoreHelper() { }
-
- private List GetAccountsInternal()
+ private static readonly AsyncLazy Instance = new(async () =>
{
- return GetList(Path.Combine(PersonalFolder, accountFile));
- }
-
- private void WriteAccountsInternal(IEnumerable accounts)
- {
- var filename = Path.Combine(PersonalFolder, accountFile);
- WriteList(filename, accounts);
- }
-
- private List GetBillsInternal()
- {
- return GetList(Path.Combine(PersonalFolder, billFile));
- }
-
- private void WriteBillsInternal(IEnumerable bills)
- {
- var filename = Path.Combine(PersonalFolder, billFile);
- WriteList(filename, bills);
- }
-
- private List GetCategoriesInternal()
- {
- var list = GetList(Path.Combine(PersonalFolder, categoryFile));
- if (list == null || list.Count == 0)
+ var instance = new StoreHelper();
+ await database.CreateTablesAsync();
+ var count = await database.ExecuteScalarAsync("SELECT COUNT(Id) FROM [Category]");
+ if (count <= 0)
{
- list = new List
+ // init categories
+ await database.InsertAllAsync(new List
{
// sample categories
new() { Id = 1, Name = Resource.Clothing, Icon = "clothes" },
@@ -89,53 +60,145 @@ namespace Billing.Store
new() { Id = 113, ParentId = 6, Name = Resource.Party, Icon = "party" },
new() { Id = 200, ParentId = 10, Type = CategoryType.Income, Name = Resource.Salary, Icon = "#brand#buffer" },
new() { Id = 201, ParentId = 10, Type = CategoryType.Income, Name = Resource.Bonus, Icon = "dollar" },
- };
- Task.Run(() => WriteCategoriesInternal(list));
+ });
}
- return list;
+ return instance;
+ });
+
+ public static async Task> GetAccountsAsync()
+ {
+ var instance = await Instance;
+ return await instance.GetListAsync();
+ }
+ public static async Task SaveAccountItemAsync(Account account)
+ {
+ var instance = await Instance;
+ return await instance.SaveItemAsync(account);
+ }
+ public static async Task DeleteAccountItemAsync(Account account)
+ {
+ var instance = await Instance;
+ return await instance.DeleteItemAsync(account);
}
- private void WriteCategoriesInternal(IEnumerable categories)
+ public static async Task> GetBillsAsync()
{
- var filename = Path.Combine(PersonalFolder, categoryFile);
- WriteList(filename, categories);
+ var instance = await Instance;
+ return await instance.GetListAsync();
+ }
+ public static async Task SaveBillItemAsync(Bill bill)
+ {
+ var instance = await Instance;
+ return await instance.SaveItemAsync(bill);
+ }
+ public static async Task DeleteBillItemAsync(Bill bill)
+ {
+ var instance = await Instance;
+ return await instance.DeleteItemAsync(bill);
+ }
+
+ public static async Task> GetCategoriesAsync()
+ {
+ var instance = await Instance;
+ return await instance.GetListAsync();
+ }
+ public static async Task SaveCategoryItemAsync(Category category)
+ {
+ var instance = await Instance;
+ return await instance.SaveItemAsync(category);
+ }
+ public static async Task 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> GetListAsync(string query, params object[] args) where T : new()
+ {
+ try
+ {
+ return database.QueryAsync(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 GetItemAsync(int id) where T : IIdItem, new()
+ {
+ try
+ {
+ var source = new TaskCompletionSource();
+ Task.Run(async () =>
+ {
+ var list = await database.QueryAsync($"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
- private void WriteList(string filename, IEnumerable list) where T : IModel, new()
+ private Task> GetListAsync() where T : new()
{
- if (list == null)
- {
- return;
- }
try
{
- using var stream = File.Open(filename, FileMode.Create);
- list.ToStream(stream);
+ return database.Table().ToListAsync();
}
catch (Exception ex)
{
- Helper.Error("file.write", $"failed to write file: {filename}, error: {ex.Message}");
+ Helper.Error("db.read", $"failed to read db, error: {ex.Message}");
}
+ return default;
}
- private List GetList(string file) where T : IModel, new()
+ private Task SaveItemAsync(T item) where T : IIdItem
{
try
{
- if (File.Exists(file))
+ if (item.Id < 0)
{
- using var stream = File.OpenRead(file);
- var list = ModelExtensionHelper.FromStream(stream);
- return list;
+ return database.InsertAsync(item);
+ }
+ else
+ {
+ return database.UpdateAsync(item);
}
}
catch (Exception ex)
{
- Helper.Error("file.read", $"failed to read file: {file}, error: {ex.Message}");
+ Helper.Error("db.write", $"failed to insert/update item, table: {typeof(T)}, id: {item.Id}, item: {item}, error: {ex.Message}");
}
- return default;
+ return Task.FromResult(-1);
+ }
+
+ private Task DeleteItemAsync(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
diff --git a/Billing.Shared/UI/Converters.cs b/Billing.Shared/UI/Converters.cs
index 7bd695b..58eeee1 100644
--- a/Billing.Shared/UI/Converters.cs
+++ b/Billing.Shared/UI/Converters.cs
@@ -205,7 +205,7 @@ namespace Billing.UI
{
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);
}
diff --git a/Billing.Shared/UI/Definition.cs b/Billing.Shared/UI/Definition.cs
index e3928c6..c132632 100644
--- a/Billing.Shared/UI/Definition.cs
+++ b/Billing.Shared/UI/Definition.cs
@@ -1,17 +1,15 @@
using System;
-using System.Collections.Generic;
-using System.IO;
using System.Threading.Tasks;
-using System.Xml.Linq;
using Billing.Languages;
-using Billing.Models;
using Xamarin.Forms;
namespace Billing.UI
{
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 GetRegularFontFamily();
public static partial string GetSemiBoldFontFamily();
@@ -98,50 +96,33 @@ namespace Billing.UI
// add 23:59:59.999...
return date.AddTicks(863999999999);
}
- }
- public static class ModelExtensionHelper
- {
- public static List FromStream(Stream stream) where T : IModel, new()
+ public static bool IsTransparent(this long color)
{
- XDocument doc = XDocument.Load(stream);
- var root = doc.Root;
- var list = new List();
- foreach (XElement ele in root.Elements("item"))
- {
- if (ele.Attribute("null")?.Value == "1")
- {
- list.Add(default);
- }
- else
- {
- T value = new();
- value.OnXmlDeserialize(ele);
- list.Add(value);
- }
- }
- return list;
+ return (color & 0xff000000L) == 0x00000000L;
}
- public static void ToStream(this IEnumerable list, Stream stream) where T : IModel
+ public static Color ToColor(this long color)
{
- XElement root = new("root");
- foreach (var t in list)
- {
- XElement item = new("item");
- if (t == null)
- {
- item.Add(new XAttribute("null", 1));
- }
- else
- {
- t.OnXmlSerialize(item);
- }
- root.Add(item);
- }
+ ulong c = (ulong)color;
+ int r = (int)(c & 0xff);
+ c >>= 8;
+ int g = (int)(c & 0xff);
+ c >>= 8;
+ int b = (int)(c & 0xff);
+ c >>= 8;
+ int a = (int)(c & 0xff);
+ return Color.FromRgba(r, g, b, a);
+ }
- XDocument doc = new(new XDeclaration("1.0", "utf-8", "yes"), root);
- doc.Save(stream, SaveOptions.DisableFormatting);
+ public static long ToLong(this Color color)
+ {
+ long l =
+ (uint)(color.A * 255) << 24 |
+ (uint)(color.B * 255) << 16 |
+ (uint)(color.G * 255) << 8 |
+ (uint)(color.R * 255);
+ return l;
}
}
diff --git a/Billing.Shared/Views/AccountPage.xaml.cs b/Billing.Shared/Views/AccountPage.xaml.cs
index 3604e71..77a136d 100644
--- a/Billing.Shared/Views/AccountPage.xaml.cs
+++ b/Billing.Shared/Views/AccountPage.xaml.cs
@@ -1,8 +1,8 @@
using System.Collections.Generic;
using System.Linq;
-using System.Threading.Tasks;
using Billing.Languages;
using Billing.Models;
+using Billing.Store;
using Billing.UI;
using Xamarin.Forms;
@@ -120,25 +120,27 @@ namespace Billing.Views
if (group == null)
{
Helper.Error("account.delete", "unexpected deleting account, cannot find the current category");
- return;
- }
- group.Remove(account);
- if (group.Count == 0)
+ }
+ else
{
- accounts.Remove(group);
+ group.Remove(account);
+ if (group.Count == 0)
+ {
+ accounts.Remove(group);
+ }
}
RefreshBalance();
groupLayout.Refresh(accounts);
RankPage.Instance?.SetNeedRefresh();
- _ = Task.Run(App.WriteAccounts);
+ await StoreHelper.DeleteAccountItemAsync(account);
}
}
}
}
- private void AccountChecked(object sender, AccountEventArgs e)
+ private async void AccountChecked(object sender, AccountEventArgs e)
{
var add = e.Account.Id < 0;
if (add)
@@ -151,7 +153,7 @@ namespace Billing.Views
RankPage.Instance?.SetNeedRefresh();
- Task.Run(App.WriteAccounts);
+ await StoreHelper.SaveAccountItemAsync(e.Account);
}
}
diff --git a/Billing.Shared/Views/AddAccountPage.xaml.cs b/Billing.Shared/Views/AddAccountPage.xaml.cs
index ddae6cd..8b04253 100644
--- a/Billing.Shared/Views/AddAccountPage.xaml.cs
+++ b/Billing.Shared/Views/AddAccountPage.xaml.cs
@@ -59,7 +59,7 @@ namespace Billing.Views
this.account = account;
if (account == null)
{
- AccountIcon = BaseModel.ICON_DEFAULT;
+ AccountIcon = Definition.DefaultIcon;
Category = AccountCategory.Cash;
}
else
diff --git a/Billing.Shared/Views/AddCategoryPage.xaml.cs b/Billing.Shared/Views/AddCategoryPage.xaml.cs
index f9d3430..a9cb66f 100644
--- a/Billing.Shared/Views/AddCategoryPage.xaml.cs
+++ b/Billing.Shared/Views/AddCategoryPage.xaml.cs
@@ -56,14 +56,13 @@ namespace Billing.Views
{
CategoryName = category.Name;
CategoryIcon = category.Icon;
- if (category.TintColor == Color.Transparent ||
- category.TintColor == default)
+ if (category.TintColor.IsTransparent())
{
TintColor = BaseTheme.CurrentPrimaryColor;
}
else
{
- TintColor = category.TintColor;
+ TintColor = category.TintColor.ToColor();
}
}
else
@@ -103,6 +102,7 @@ namespace Billing.Views
{
var currentColor = BaseTheme.CurrentPrimaryColor;
var tintColor = TintColor;
+ var color = (tintColor == currentColor ? Color.Transparent : tintColor).ToLong();
var category = App.Categories.FirstOrDefault(c => c.Id == categoryId);
if (category == null)
{
@@ -111,7 +111,7 @@ namespace Billing.Views
Id = -1,
Name = CategoryName,
Icon = CategoryIcon,
- TintColor = tintColor == currentColor ? Color.Transparent : tintColor,
+ TintColor = color,
ParentId = parent?.Id,
Type = parent?.Type ?? CategoryType.Spending
});
@@ -120,7 +120,7 @@ namespace Billing.Views
{
category.Name = CategoryName;
category.Icon = CategoryIcon;
- category.TintColor = tintColor == currentColor ? Color.Transparent : tintColor;
+ category.TintColor = color;
CategoryChecked?.Invoke(this, category);
}
await Navigation.PopAsync();
diff --git a/Billing.Shared/Views/BillPage.xaml.cs b/Billing.Shared/Views/BillPage.xaml.cs
index e5f9d1b..a6ef24f 100644
--- a/Billing.Shared/Views/BillPage.xaml.cs
+++ b/Billing.Shared/Views/BillPage.xaml.cs
@@ -1,4 +1,5 @@
using Billing.Models;
+using Billing.Store;
using Billing.UI;
using System;
using System.Collections.Generic;
@@ -84,7 +85,7 @@ namespace Billing.Views
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.DateCreation = bill.Bill.CreateTime;
bill.Amount = bill.Bill.Amount;
@@ -158,13 +159,13 @@ namespace Billing.Views
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)
{
@@ -195,7 +196,7 @@ namespace Billing.Views
RankPage.Instance?.SetNeedRefresh();
- Task.Run(App.WriteBills);
+ await StoreHelper.SaveBillItemAsync(e);
}
}
diff --git a/Billing.Shared/Views/CategoryPage.xaml.cs b/Billing.Shared/Views/CategoryPage.xaml.cs
index b71a978..d5f15bb 100644
--- a/Billing.Shared/Views/CategoryPage.xaml.cs
+++ b/Billing.Shared/Views/CategoryPage.xaml.cs
@@ -1,11 +1,11 @@
using Billing.Languages;
using Billing.Models;
+using Billing.Store;
using Billing.Themes;
using Billing.UI;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
-using System.Threading.Tasks;
using Xamarin.Forms;
namespace Billing.Views
@@ -68,9 +68,9 @@ namespace Billing.Views
Icon = category.Icon,
Name = category.Name,
IsTopCategory = IsTopCategory,
- TintColor = category.TintColor == Color.Transparent || category.TintColor == default ?
+ TintColor = category.TintColor.IsTransparent() ?
BaseTheme.CurrentPrimaryColor :
- category.TintColor
+ category.TintColor.ToColor()
};
}
@@ -112,7 +112,7 @@ namespace Billing.Views
Categories.Remove(c);
groupLayout.Refresh(Categories);
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)
{
@@ -183,16 +183,16 @@ namespace Billing.Views
}
groupLayout.Refresh(Categories);
- Task.Run(App.WriteCategories);
+ await StoreHelper.SaveCategoryItemAsync(category);
}
private void UpdateCategory(UICategory c)
{
c.Name = c.Category.Name;
c.Icon = c.Category.Icon;
- c.TintColor = c.Category.TintColor == Color.Transparent || c.Category.TintColor == default ?
+ c.TintColor = c.Category.TintColor.IsTransparent() ?
BaseTheme.CurrentPrimaryColor :
- c.Category.TintColor;
+ c.Category.TintColor.ToColor();
}
}
diff --git a/Billing.Shared/Views/CategorySelectPage.xaml.cs b/Billing.Shared/Views/CategorySelectPage.xaml.cs
index 2e6f9e5..21eebf0 100644
--- a/Billing.Shared/Views/CategorySelectPage.xaml.cs
+++ b/Billing.Shared/Views/CategorySelectPage.xaml.cs
@@ -72,7 +72,7 @@ namespace Billing.Views
IsChecked = c.Id == categoryId,
Icon = c.Icon,
Name = c.Name,
- TintColor = c.TintColor == Color.Transparent || c.TintColor == default ? defaultColor : c.TintColor
+ TintColor = c.TintColor.IsTransparent() ? defaultColor : c.TintColor.ToColor()
};
}
diff --git a/Billing.Shared/Views/IconSelectPage.xaml.cs b/Billing.Shared/Views/IconSelectPage.xaml.cs
index 9347e7f..cbc395d 100644
--- a/Billing.Shared/Views/IconSelectPage.xaml.cs
+++ b/Billing.Shared/Views/IconSelectPage.xaml.cs
@@ -1,5 +1,4 @@
-using Billing.Models;
-using Billing.UI;
+using Billing.UI;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -37,7 +36,7 @@ namespace Billing.Views
{
var source = new List
{
- new() { Icon = BaseModel.ICON_DEFAULT },
+ new() { Icon = Definition.DefaultIcon },
new() { Icon = "wallet" },
new() { Icon = "dollar" },
new() { Icon = "creditcard" },
diff --git a/Billing.Shared/Views/RankPage.xaml.cs b/Billing.Shared/Views/RankPage.xaml.cs
index 0dd3ebc..0852d22 100644
--- a/Billing.Shared/Views/RankPage.xaml.cs
+++ b/Billing.Shared/Views/RankPage.xaml.cs
@@ -1,4 +1,5 @@
using Billing.Models;
+using Billing.Store;
using Billing.Themes;
using Billing.UI;
using Microcharts;
@@ -473,7 +474,7 @@ namespace Billing.Views
private async void OnBillChecked(object sender, Bill e)
{
- await Task.Run(App.WriteBills);
+ await StoreHelper.SaveBillItemAsync(e);
LoadData();
}
diff --git a/Billing/Billing.Android/Billing.Android.csproj b/Billing/Billing.Android/Billing.Android.csproj
index 9c3a3be..83f151f 100644
--- a/Billing/Billing.Android/Billing.Android.csproj
+++ b/Billing/Billing.Android/Billing.Android.csproj
@@ -65,12 +65,11 @@
-
- 0.9.5.9
-
+
+
diff --git a/Billing/Billing.iOS/Billing.iOS.csproj b/Billing/Billing.iOS/Billing.iOS.csproj
index 9188daf..4097576 100644
--- a/Billing/Billing.iOS/Billing.iOS.csproj
+++ b/Billing/Billing.iOS/Billing.iOS.csproj
@@ -169,12 +169,11 @@
-
- 0.9.5.9
-
+
+
diff --git a/Billing/Billing.iOS/Info.plist b/Billing/Billing.iOS/Info.plist
index 13847c2..0a4fe63 100644
--- a/Billing/Billing.iOS/Info.plist
+++ b/Billing/Billing.iOS/Info.plist
@@ -41,8 +41,6 @@
OpenSans-Regular.ttf
OpenSans-SemiBold.ttf
- UIFileSharingEnabled
-
CFBundleVersion
9
CFBundleShortVersionString