diff --git a/Billing.Shared/Billing.Shared.projitems b/Billing.Shared/Billing.Shared.projitems
index 0afc34f..4694a9c 100644
--- a/Billing.Shared/Billing.Shared.projitems
+++ b/Billing.Shared/Billing.Shared.projitems
@@ -85,6 +85,7 @@
SplashPage.xaml
+
diff --git a/Billing.Shared/Helper.cs b/Billing.Shared/Helper.cs
index 3ee9136..c698ef5 100644
--- a/Billing.Shared/Helper.cs
+++ b/Billing.Shared/Helper.cs
@@ -1,5 +1,4 @@
using Billing.Models;
-using Billing.Themes;
using Billing.UI;
using Billing.Views;
using System;
@@ -20,34 +19,35 @@ namespace Billing
var time = DateTime.Now.ToString("HH:mm:ss.fff");
System.Diagnostics.Debug.WriteLine($"[{time}] - {message}");
}
-
- public static void Error(string category, Exception ex)
- {
- Error(category, ex?.Message ?? "unknown error");
- }
-
- public static void Error(string category, string message)
- {
- var time = DateTime.Now.ToString("HH:mm:ss.fff");
- System.Diagnostics.Debug.Fail($"[{time}] - {category}", message);
- }
#else
#pragma warning disable IDE0060 // Remove unused parameter
public static void Debug(string message)
{
}
+#pragma warning restore IDE0060 // Remove unused parameter
+#endif
public static void Error(string category, Exception ex)
{
- MainThread.BeginInvokeOnMainThread(async () => await Shell.Current.DisplayAlert(category, ex.ToString(), "Ok"));
+ Error(category, ex?.ToString() ?? "unknown error");
}
public static void Error(string category, string message)
{
- }
-#pragma warning restore IDE0060 // Remove unused parameter
+#if DEBUG
+ var time = DateTime.Now.ToString("HH:mm:ss.fff");
+ System.Diagnostics.Debug.WriteLine($"[{time}] - {category}", message);
+ MainThread.BeginInvokeOnMainThread(async () => await Shell.Current.DisplayAlert(category, message, "Ok"));
#endif
+ _ = Store.StoreHelper.SaveLogItemAsync(new Logs()
+ {
+ LogTime = DateTime.Now,
+ Category = category,
+ Detail = message
+ });
+ }
+
public static bool NetworkAvailable
{
get
@@ -89,9 +89,7 @@ namespace Billing
return new UIBill(b)
{
Icon = category?.Icon ?? Definition.DefaultIcon,
- TintColor = category?.TintColor.IsTransparent() == false ?
- category.TintColor.ToColor() :
- BaseTheme.CurrentPrimaryColor,
+ TintColor = category?.TintColor ?? Definition.TransparentColor,
Name = b.Name,
DateCreation = b.CreateTime,
Amount = b.Amount,
diff --git a/Billing.Shared/Languages/Resource.cs b/Billing.Shared/Languages/Resource.cs
index 4eec6cf..fd191a2 100644
--- a/Billing.Shared/Languages/Resource.cs
+++ b/Billing.Shared/Languages/Resource.cs
@@ -10,6 +10,7 @@ namespace Billing.Languages
internal class Resource
{
public static string Ok => Text(nameof(Ok));
+ public static string Cancel => Text(nameof(Cancel));
public static string Yes => Text(nameof(Yes));
public static string No => Text(nameof(No));
public static string ConfirmDeleteAccount => Text(nameof(ConfirmDeleteAccount));
@@ -37,9 +38,15 @@ namespace Billing.Languages
public static string AmountRequired => Text(nameof(AmountRequired));
public static string Income => Text(nameof(Income));
public static string Spending => Text(nameof(Spending));
+ public static string LastSelected => Text(nameof(LastSelected));
+ public static string Recent => Text(nameof(Recent));
public static string CategoryManage => Text(nameof(CategoryManage));
public static string AddCategory => Text(nameof(AddCategory));
public static string ConfirmDeleteCategory => Text(nameof(ConfirmDeleteCategory));
+ public static string ShareLogs => Text(nameof(ShareLogs));
+ public static string ManyRecords => Text(nameof(ManyRecords));
+ public static string SendEmail => Text(nameof(SendEmail));
+ public static string HowToShareDiagnostic => Text(nameof(HowToShareDiagnostic));
#region Categories
public static string Clothing => Text(nameof(Clothing));
diff --git a/Billing.Shared/Languages/en.xml b/Billing.Shared/Languages/en.xml
index 942e2e3..3bfc7df 100644
--- a/Billing.Shared/Languages/en.xml
+++ b/Billing.Shared/Languages/en.xml
@@ -1,6 +1,7 @@
OK
+ Cancel
About
Version
Preference
@@ -63,6 +64,8 @@
Please enter the amount.
Income
Spending
+ Last Selected
+ Recent
Clothing
Food
Drinks
@@ -104,4 +107,9 @@
(no results)
Top 10
Category Ranking
+ Diagnostic
+ Share Logs
+ {0} record(s)
+ Send Eamil
+ How would you like to share diagnostic logs?
\ No newline at end of file
diff --git a/Billing.Shared/Languages/zh-CN.xml b/Billing.Shared/Languages/zh-CN.xml
index a786a4b..db8dfea 100644
--- a/Billing.Shared/Languages/zh-CN.xml
+++ b/Billing.Shared/Languages/zh-CN.xml
@@ -1,6 +1,7 @@
确定
+ 取消
关于
版本号
偏好
@@ -63,6 +64,8 @@
请输入金额。
收入
支出
+ 最后选择
+ 最近
衣物
食品
饮料
@@ -104,4 +107,9 @@
(无记录)
Top 10
分类排行
+ 诊断
+ 发送日志
+ {0} 条记录
+ 发送邮件
+ 您想以哪种方式分享诊断日志?
\ No newline at end of file
diff --git a/Billing.Shared/Models/Account.cs b/Billing.Shared/Models/Account.cs
index 51e6ae2..750f23e 100644
--- a/Billing.Shared/Models/Account.cs
+++ b/Billing.Shared/Models/Account.cs
@@ -6,6 +6,9 @@ namespace Billing.Models
{
private const string ICON_DEFAULT = "ic_default";
+ private static Account empty;
+ public static Account Empty => empty ??= new() { Id = -1 };
+
[PrimaryKey, AutoIncrement]
public int Id { get; set; }
public string Icon { get; set; } = ICON_DEFAULT;
diff --git a/Billing.Shared/Models/Category.cs b/Billing.Shared/Models/Category.cs
index 03b9cb4..e2d550f 100644
--- a/Billing.Shared/Models/Category.cs
+++ b/Billing.Shared/Models/Category.cs
@@ -8,6 +8,9 @@ namespace Billing.Models
private const string ICON_DEFAULT = "ic_default";
private const long TRANSPARENT_COLOR = 0x00ffffffL;
+ private static Category empty;
+ public static Category Empty => empty ??= new() { Id = -1 };
+
[PrimaryKey, AutoIncrement]
public int Id { get; set; }
public CategoryType Type { get; set; }
diff --git a/Billing.Shared/Models/Logs.cs b/Billing.Shared/Models/Logs.cs
new file mode 100644
index 0000000..7c96ea6
--- /dev/null
+++ b/Billing.Shared/Models/Logs.cs
@@ -0,0 +1,14 @@
+using System;
+using SQLite;
+
+namespace Billing.Models
+{
+ public class Logs : IIdItem
+ {
+ [PrimaryKey, AutoIncrement]
+ public int Id { get; set; }
+ public DateTime LogTime { get; set; }
+ public string Category { get; set; }
+ public string Detail { get; set; }
+ }
+}
diff --git a/Billing.Shared/Store/StoreHelper.cs b/Billing.Shared/Store/StoreHelper.cs
index a864f65..9fdc98b 100644
--- a/Billing.Shared/Store/StoreHelper.cs
+++ b/Billing.Shared/Store/StoreHelper.cs
@@ -66,12 +66,24 @@ namespace Billing.Store
var instance = new StoreHelper();
try
{
- await database.CreateTablesAsync();
+ await database.CreateTablesAsync();
} catch (Exception ex)
{
Helper.Error("database.init.table", ex);
}
try
+ {
+ var count = await database.ExecuteScalarAsync("SELECT COUNT(Id) FROM [Category]");
+ if (count <= 0)
+ {
+ await database.InsertAsync(new Account { Name = Resource.Cash, Icon = "wallet" });
+ }
+ }
+ catch (Exception ex)
+ {
+ Helper.Error("database.init.account", ex);
+ }
+ try
{
var count = await database.ExecuteScalarAsync("SELECT COUNT(Id) FROM [Category]");
if (count <= 0)
@@ -152,6 +164,46 @@ namespace Billing.Store
return await instance.DeleteItemAsync(category);
}
+ public static async Task GetLogsCount()
+ {
+ await Instance;
+ return await database.ExecuteScalarAsync("SELECT COUNT(Id) FROM [Logs]");
+ }
+ public static async Task SaveLogItemAsync(Logs log)
+ {
+ var instance = await Instance;
+ return await instance.SaveItemAsync(log);
+ }
+ public static string GetLogFile()
+ {
+ return Path.Combine(CacheFolder, "logs.csv");
+ }
+ public static async Task ExportLogs()
+ {
+ try
+ {
+ var instance = await Instance;
+ var logs = await instance.GetListAsync();
+ var file = GetLogFile();
+ using var writer = new StreamWriter(File.Open(file, FileMode.Create, FileAccess.Write));
+ writer.WriteLine("Id,DateTime,Category,Detail");
+ foreach (var log in logs)
+ {
+ var category = log.Category?.Replace("\n", " \\n ");
+ var detail = log.Detail?.Replace("\n", " \\n ");
+ writer.WriteLine($"{log.Id},{log.LogTime},{category},{detail}");
+ }
+ writer.Flush();
+
+ await database.ExecuteAsync("DELETE FROM [Logs]; DELETE FROM [sqlite_sequence] WHERE [name] = 'Logs'");
+ return file;
+ }
+ catch
+ {
+ return null;
+ }
+ }
+
private StoreHelper()
{
if (database == null)
diff --git a/Billing.Shared/UI/Converters.cs b/Billing.Shared/UI/Converters.cs
index 58eeee1..c54f2d4 100644
--- a/Billing.Shared/UI/Converters.cs
+++ b/Billing.Shared/UI/Converters.cs
@@ -244,4 +244,23 @@ namespace Billing.UI
throw new NotImplementedException();
}
}
+
+ public class TintColorConverter : IValueConverter
+ {
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ if (value is long l)
+ {
+ return l.IsTransparent() ?
+ BaseTheme.CurrentPrimaryColor :
+ l.ToColor();
+ }
+ return Color.Transparent;
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ throw new NotImplementedException();
+ }
+ }
}
\ No newline at end of file
diff --git a/Billing.Shared/UI/Definition.cs b/Billing.Shared/UI/Definition.cs
index c132632..7e97113 100644
--- a/Billing.Shared/UI/Definition.cs
+++ b/Billing.Shared/UI/Definition.cs
@@ -9,6 +9,7 @@ namespace Billing.UI
{
public const string PrimaryColorKey = "PrimaryColor";
public const string DefaultIcon = "ic_default";
+ public const long TransparentColor = 0x00ffffffL;
public static partial (string main, long build) GetVersion();
public static partial string GetRegularFontFamily();
diff --git a/Billing.Shared/UI/OptionsCells.cs b/Billing.Shared/UI/OptionsCells.cs
index 35766b4..6d3d977 100644
--- a/Billing.Shared/UI/OptionsCells.cs
+++ b/Billing.Shared/UI/OptionsCells.cs
@@ -237,6 +237,13 @@ namespace Billing.UI
.Binding(Image.SourceProperty, nameof(ImageSource))
.Binding(TintHelper.TintColorProperty, nameof(TintColor)),
+ new Label
+ {
+ VerticalOptions = LayoutOptions.Center
+ }
+ .Binding(Label.TextProperty, nameof(Detail))
+ .DynamicResource(Label.TextColorProperty, BaseTheme.SecondaryTextColor),
+
new TintImage
{
HeightRequest = 20,
diff --git a/Billing.Shared/Views/AddBillPage.xaml b/Billing.Shared/Views/AddBillPage.xaml
index 8739774..8fd180d 100644
--- a/Billing.Shared/Views/AddBillPage.xaml
+++ b/Billing.Shared/Views/AddBillPage.xaml
@@ -13,6 +13,11 @@
+
+
+
+
+
@@ -29,14 +34,18 @@
Title="{r:Text Name}"
Text="{Binding Name, Mode=TwoWay}"
Placeholder="{r:Text NamePlaceholder}"/>
-
-
+
+
diff --git a/Billing.Shared/Views/AddBillPage.xaml.cs b/Billing.Shared/Views/AddBillPage.xaml.cs
index 01b6ce8..2caf82f 100644
--- a/Billing.Shared/Views/AddBillPage.xaml.cs
+++ b/Billing.Shared/Views/AddBillPage.xaml.cs
@@ -3,6 +3,7 @@ using System.Globalization;
using System.Linq;
using Billing.Languages;
using Billing.Models;
+using Billing.Store;
using Billing.UI;
using Xamarin.Forms;
@@ -12,8 +13,8 @@ namespace Billing.Views
{
private static readonly BindableProperty AmountProperty = Helper.Create(nameof(Amount));
private static readonly BindableProperty NameProperty = Helper.Create(nameof(Name));
- private static readonly BindableProperty CategoryNameProperty = Helper.Create(nameof(CategoryName));
- private static readonly BindableProperty WalletNameProperty = Helper.Create(nameof(WalletName));
+ private static readonly BindableProperty CategoryProperty = Helper.Create(nameof(Category));
+ private static readonly BindableProperty WalletProperty = Helper.Create(nameof(Wallet));
private static readonly BindableProperty StoreProperty = Helper.Create(nameof(Store));
private static readonly BindableProperty CreatedDateProperty = Helper.Create(nameof(CreatedDate));
private static readonly BindableProperty CreatedTimeProperty = Helper.Create(nameof(CreatedTime));
@@ -29,8 +30,8 @@ namespace Billing.Views
get => (string)GetValue(NameProperty);
set => SetValue(NameProperty, value);
}
- public string CategoryName => (string)GetValue(CategoryNameProperty);
- public string WalletName => (string)GetValue(WalletNameProperty);
+ public Category Category => (Category)GetValue(CategoryProperty);
+ public Account Wallet => (Account)GetValue(WalletProperty);
public string Store
{
get => (string)GetValue(StoreProperty);
@@ -61,8 +62,7 @@ namespace Billing.Views
private readonly Bill bill;
private readonly DateTime createDate;
- private int walletId;
- private int categoryId;
+ private bool categoryChanged;
public AddBillPage(DateTime date)
{
@@ -94,10 +94,9 @@ namespace Billing.Views
{
Amount = Math.Abs(bill.Amount).ToString(CultureInfo.InvariantCulture);
Name = bill.Name;
- walletId = bill.WalletId;
- categoryId = bill.CategoryId;
- SetValue(WalletNameProperty, App.Accounts.FirstOrDefault(a => a.Id == walletId)?.Name);
- SetValue(CategoryNameProperty, App.Categories.FirstOrDefault(c => c.Id == categoryId)?.Name);
+ SetValue(WalletProperty, App.Accounts.FirstOrDefault(a => a.Id == bill.WalletId) ?? Account.Empty);
+ SetValue(CategoryProperty, App.Categories.FirstOrDefault(c => c.Id == bill.CategoryId) ?? Category.Empty);
+ categoryChanged = true;
Store = bill.Store;
CreatedDate = bill.CreateTime.Date;
CreatedTime = bill.CreateTime.TimeOfDay;
@@ -105,12 +104,8 @@ namespace Billing.Views
}
else
{
- var first = App.Accounts.First();
- walletId = first.Id;
- SetValue(WalletNameProperty, first.Name);
- var firstCategory = App.Categories.First();
- categoryId = firstCategory.Id;
- SetValue(CategoryNameProperty, firstCategory.Name);
+ SetValue(WalletProperty, App.Accounts.FirstOrDefault() ?? Account.Empty);
+ SetValue(CategoryProperty, App.Categories.FirstOrDefault() ?? Category.Empty);
CreatedDate = createDate.Date;
CreatedTime = DateTime.Now.TimeOfDay;
}
@@ -138,8 +133,9 @@ namespace Billing.Views
await this.ShowMessage(Resource.AmountRequired);
return;
}
+ var category = Category;
+ var wallet = Wallet;
amount = Math.Abs(amount);
- var category = App.Categories.FirstOrDefault(c => c.Id == categoryId);
if (category.Type == CategoryType.Spending)
{
amount *= -1;
@@ -154,8 +150,8 @@ namespace Billing.Views
{
bill.Amount = amount;
bill.Name = name;
- bill.CategoryId = categoryId;
- bill.WalletId = walletId;
+ bill.CategoryId = category.Id;
+ bill.WalletId = wallet.Id;
bill.CreateTime = CreatedDate.Date.Add(CreatedTime);
bill.Store = Store;
bill.Note = Note;
@@ -164,15 +160,17 @@ namespace Billing.Views
{
Amount = amount,
Name = name,
- CategoryId = categoryId,
- WalletId = walletId,
+ CategoryId = category.Id,
+ WalletId = wallet.Id,
CreateTime = CreatedDate.Date.Add(CreatedTime),
Store = Store,
Note = Note
});
- category.LastAccountId = walletId;
+ category.LastAccountId = wallet.Id;
category.LastUsed = DateTime.Now;
+
+ await StoreHelper.SaveCategoryItemAsync(category);
}
}
@@ -184,7 +182,7 @@ namespace Billing.Views
}
using (Tap.Start())
{
- var page = new CategorySelectPage(categoryId);
+ var page = new CategorySelectPage(categoryChanged ? Category.Id : -1);
page.CategoryTapped += CategorySelectPage_Tapped;
await Navigation.PushAsync(page);
}
@@ -192,16 +190,16 @@ namespace Billing.Views
private void CategorySelectPage_Tapped(object sender, UICategory e)
{
- categoryId = e.Category.Id;
+ SetValue(CategoryProperty, e.Category);
+ categoryChanged = true;
if (e.Category.LastAccountId != null)
{
var wallet = App.Accounts.FirstOrDefault(a => a.Id == e.Category.LastAccountId.Value);
if (wallet != null)
{
- SetValue(WalletNameProperty, wallet.Name);
+ SetValue(WalletProperty, wallet);
}
}
- SetValue(CategoryNameProperty, e.Name);
}
private async void OnSelectWallet()
@@ -212,22 +210,15 @@ namespace Billing.Views
}
using (Tap.Start())
{
- var source = App.Accounts.Select(a => new SelectItem
- {
- Value = a.Id,
- Name = a.Name,
- Icon = a.Icon
- });
- var page = new ItemSelectPage>(source);
+ var page = new ItemSelectPage(App.Accounts);
page.ItemTapped += Wallet_ItemTapped;
await Navigation.PushAsync(page);
}
}
- private void Wallet_ItemTapped(object sender, SelectItem account)
+ private void Wallet_ItemTapped(object sender, Account account)
{
- walletId = account.Value;
- SetValue(WalletNameProperty, account.Name);
+ SetValue(WalletProperty, account);
}
}
}
\ No newline at end of file
diff --git a/Billing.Shared/Views/BillPage.xaml b/Billing.Shared/Views/BillPage.xaml
index a33e2c1..bbf62e7 100644
--- a/Billing.Shared/Views/BillPage.xaml
+++ b/Billing.Shared/Views/BillPage.xaml
@@ -20,6 +20,7 @@
+
@@ -101,7 +102,7 @@
CommandParameter="{Binding .}"/>