optimized and add diagnostic feature

This commit is contained in:
2022-03-15 15:17:02 +08:00
parent 77b4e54734
commit 5b209cc19c
45 changed files with 380 additions and 122 deletions

View File

@@ -85,6 +85,7 @@
<Compile Include="$(MSBuildThisFileDirectory)SplashPage.xaml.cs">
<DependentUpon>SplashPage.xaml</DependentUpon>
</Compile>
<Compile Include="$(MSBuildThisFileDirectory)Models\Logs.cs" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="$(MSBuildThisFileDirectory)MainShell.xaml">

View File

@@ -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,

View File

@@ -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));

View File

@@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<Ok>OK</Ok>
<Cancel>Cancel</Cancel>
<About>About</About>
<Version>Version</Version>
<Preference>Preference</Preference>
@@ -63,6 +64,8 @@
<AmountRequired>Please enter the amount.</AmountRequired>
<Income>Income</Income>
<Spending>Spending</Spending>
<LastSelected>Last Selected</LastSelected>
<Recent>Recent</Recent>
<Clothing>Clothing</Clothing>
<Food>Food</Food>
<Drinks>Drinks</Drinks>
@@ -104,4 +107,9 @@
<NoResult>(no results)</NoResult>
<Top10>Top 10</Top10>
<CategoryRank>Category Ranking</CategoryRank>
<Diagnostic>Diagnostic</Diagnostic>
<ShareLogs>Share Logs</ShareLogs>
<ManyRecords>{0} record(s)</ManyRecords>
<SendEmail>Send Eamil</SendEmail>
<HowToShareDiagnostic>How would you like to share diagnostic logs?</HowToShareDiagnostic>
</root>

View File

@@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<Ok>确定</Ok>
<Cancel>取消</Cancel>
<About>关于</About>
<Version>版本号</Version>
<Preference>偏好</Preference>
@@ -63,6 +64,8 @@
<AmountRequired>请输入金额。</AmountRequired>
<Income>收入</Income>
<Spending>支出</Spending>
<LastSelected>最后选择</LastSelected>
<Recent>最近</Recent>
<Clothing>衣物</Clothing>
<Food>食品</Food>
<Drinks>饮料</Drinks>
@@ -104,4 +107,9 @@
<NoResult>(无记录)</NoResult>
<Top10>Top 10</Top10>
<CategoryRank>分类排行</CategoryRank>
<Diagnostic>诊断</Diagnostic>
<ShareLogs>发送日志</ShareLogs>
<ManyRecords>{0} 条记录</ManyRecords>
<SendEmail>发送邮件</SendEmail>
<HowToShareDiagnostic>您想以哪种方式分享诊断日志?</HowToShareDiagnostic>
</root>

View File

@@ -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;

View File

@@ -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; }

View File

@@ -0,0 +1,14 @@
using System;
using SQLite;
namespace Billing.Models
{
public class Logs : IIdItem
{
[PrimaryKey, AutoIncrement]
public int Id { get; set; }
public DateTime LogTime { get; set; }
public string Category { get; set; }
public string Detail { get; set; }
}
}

View File

@@ -66,12 +66,24 @@ namespace Billing.Store
var instance = new StoreHelper();
try
{
await database.CreateTablesAsync<Category, Account, Bill>();
await database.CreateTablesAsync<Category, Account, Bill, Logs>();
} catch (Exception ex)
{
Helper.Error("database.init.table", ex);
}
try
{
var count = await database.ExecuteScalarAsync<int>("SELECT COUNT(Id) FROM [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<int>("SELECT COUNT(Id) FROM [Category]");
if (count <= 0)
@@ -152,6 +164,46 @@ namespace Billing.Store
return await instance.DeleteItemAsync(category);
}
public static async Task<int> GetLogsCount()
{
await Instance;
return await database.ExecuteScalarAsync<int>("SELECT COUNT(Id) FROM [Logs]");
}
public static async Task<int> SaveLogItemAsync(Logs log)
{
var instance = await Instance;
return await instance.SaveItemAsync(log);
}
public static string GetLogFile()
{
return Path.Combine(CacheFolder, "logs.csv");
}
public static async Task<string> ExportLogs()
{
try
{
var instance = await Instance;
var logs = await instance.GetListAsync<Logs>();
var file = GetLogFile();
using var writer = new StreamWriter(File.Open(file, FileMode.Create, FileAccess.Write));
writer.WriteLine("Id,DateTime,Category,Detail");
foreach (var log in logs)
{
var category = log.Category?.Replace("\n", " \\n ");
var detail = log.Detail?.Replace("\n", " \\n ");
writer.WriteLine($"{log.Id},{log.LogTime},{category},{detail}");
}
writer.Flush();
await database.ExecuteAsync("DELETE FROM [Logs]; DELETE FROM [sqlite_sequence] WHERE [name] = 'Logs'");
return file;
}
catch
{
return null;
}
}
private StoreHelper()
{
if (database == null)

View File

@@ -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();
}
}
}

View File

@@ -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();

View File

@@ -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,

View File

@@ -13,6 +13,11 @@
<ToolbarItem Order="Primary" IconImageSource="check.png" Command="{Binding CheckBill}"/>
</ContentPage.ToolbarItems>
<ContentPage.Resources>
<ui:IconConverter x:Key="iconConverter"/>
<ui:TintColorConverter x:Key="tintColorConverter"/>
</ContentPage.Resources>
<ContentPage.Content>
<TableView Intent="Settings" HasUnevenRows="True">
<TableSection Title=" ">
@@ -29,14 +34,18 @@
Title="{r:Text Name}"
Text="{Binding Name, Mode=TwoWay}"
Placeholder="{r:Text NamePlaceholder}"/>
<ui:OptionSelectCell Height="44" Icon="project.png"
Title="{r:Text Category}"
Detail="{Binding CategoryName}"
Command="{Binding SelectCategory}"/>
<ui:OptionSelectCell Height="44" Icon="wallet.png"
Title="{r:Text Account}"
Detail="{Binding WalletName}"
Command="{Binding SelectWallet}"/>
<ui:OptionImageCell Height="44" Icon="project.png"
Title="{r:Text Category}"
Detail="{Binding Category.Name}"
ImageSource="{Binding Category.Icon, Converter={StaticResource iconConverter}}"
TintColor="{Binding Category.TintColor, Converter={StaticResource tintColorConverter}}"
Command="{Binding SelectCategory}"/>
<ui:OptionImageCell Height="44" Icon="wallet.png"
Title="{r:Text Account}"
Detail="{Binding Wallet.Name}"
ImageSource="{Binding Wallet.Icon, Converter={StaticResource iconConverter}}"
TintColor="{DynamicResource PrimaryColor}"
Command="{Binding SelectWallet}"/>
<ui:OptionEntryCell Height="44" Icon="online.png"
Title="{r:Text Store}"
Text="{Binding Store, Mode=TwoWay}"/>

View File

@@ -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<string, AddBillPage>(nameof(Amount));
private static readonly BindableProperty NameProperty = Helper.Create<string, AddBillPage>(nameof(Name));
private static readonly BindableProperty CategoryNameProperty = Helper.Create<string, AddBillPage>(nameof(CategoryName));
private static readonly BindableProperty WalletNameProperty = Helper.Create<string, AddBillPage>(nameof(WalletName));
private static readonly BindableProperty CategoryProperty = Helper.Create<Category, AddBillPage>(nameof(Category));
private static readonly BindableProperty WalletProperty = Helper.Create<Account, AddBillPage>(nameof(Wallet));
private static readonly BindableProperty StoreProperty = Helper.Create<string, AddBillPage>(nameof(Store));
private static readonly BindableProperty CreatedDateProperty = Helper.Create<DateTime, AddBillPage>(nameof(CreatedDate));
private static readonly BindableProperty CreatedTimeProperty = Helper.Create<TimeSpan, AddBillPage>(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<int>
{
Value = a.Id,
Name = a.Name,
Icon = a.Icon
});
var page = new ItemSelectPage<SelectItem<int>>(source);
var page = new ItemSelectPage<Account>(App.Accounts);
page.ItemTapped += Wallet_ItemTapped;
await Navigation.PushAsync(page);
}
}
private void Wallet_ItemTapped(object sender, SelectItem<int> account)
private void Wallet_ItemTapped(object sender, Account account)
{
walletId = account.Value;
SetValue(WalletNameProperty, account.Name);
SetValue(WalletProperty, account);
}
}
}

View File

@@ -20,6 +20,7 @@
<ui:BalanceColorConverter x:Key="colorConverter"/>
<ui:TimeConverter x:Key="timeConverter"/>
<ui:IconConverter x:Key="iconConverter"/>
<ui:TintColorConverter x:Key="tintColorConverter"/>
</ContentPage.Resources>
<Shell.TitleView>
@@ -101,7 +102,7 @@
CommandParameter="{Binding .}"/>
</Grid.GestureRecognizers>
<ui:TintImage Source="{Binding Icon, Converter={StaticResource iconConverter}}"
ui:TintHelper.TintColor="{Binding TintColor}"
ui:TintHelper.TintColor="{Binding TintColor, Converter={StaticResource tintColorConverter}}"
WidthRequest="26" HeightRequest="20" VerticalOptions="Center"/>
<Label Grid.Column="1" Text="{Binding Name}" TextColor="{DynamicResource TextColor}"
VerticalOptions="Center"

View File

@@ -85,7 +85,9 @@ namespace Billing.Views
private void UpdateBill(UIBill bill)
{
bill.Icon = App.Categories.FirstOrDefault(c => c.Id == bill.Bill.CategoryId)?.Icon ?? Definition.DefaultIcon;
var category = App.Categories.FirstOrDefault(c => c.Id == bill.Bill.CategoryId);
bill.Icon = category?.Icon ?? Definition.DefaultIcon;
bill.TintColor = category?.TintColor ?? Definition.TransparentColor;
bill.Name = bill.Bill.Name;
bill.DateCreation = bill.Bill.CreateTime;
bill.Amount = bill.Bill.Amount;
@@ -207,7 +209,7 @@ namespace Billing.Views
public class UIBill : BindableObject
{
public static readonly BindableProperty IconProperty = Helper.Create<string, UIBill>(nameof(Icon));
public static readonly BindableProperty TintColorProperty = Helper.Create<Color, UIBill>(nameof(TintColor));
public static readonly BindableProperty TintColorProperty = Helper.Create<long, UIBill>(nameof(TintColor));
public static readonly BindableProperty NameProperty = Helper.Create<string, UIBill>(nameof(Name));
public static readonly BindableProperty DateCreationProperty = Helper.Create<DateTime, UIBill>(nameof(DateCreation));
public static readonly BindableProperty AmountProperty = Helper.Create<decimal, UIBill>(nameof(Amount));
@@ -218,9 +220,9 @@ namespace Billing.Views
get => (string)GetValue(IconProperty);
set => SetValue(IconProperty, value);
}
public Color TintColor
public long TintColor
{
get => (Color)GetValue(TintColorProperty);
get => (long)GetValue(TintColorProperty);
set => SetValue(TintColorProperty, value);
}
public string Name

View File

@@ -10,6 +10,7 @@
<ContentPage.Resources>
<ui:IconConverter x:Key="iconConverter"/>
<ui:TintColorConverter x:Key="tintColorConverter"/>
</ContentPage.Resources>
<ScrollView>
@@ -32,7 +33,7 @@
CommandParameter="{Binding .}"/>
</Grid.GestureRecognizers>
<ui:TintImage Source="{Binding Icon, Converter={StaticResource iconConverter}}"
ui:TintHelper.TintColor="{Binding TintColor}"
ui:TintHelper.TintColor="{Binding TintColor, Converter={StaticResource tintColorConverter}}"
WidthRequest="26" HeightRequest="20" VerticalOptions="Center"/>
<Label Grid.Column="1" Text="{Binding Name}" TextColor="{DynamicResource TextColor}"
HorizontalOptions="FillAndExpand" VerticalOptions="Center"

View File

@@ -1,7 +1,6 @@
using Billing.Languages;
using Billing.Models;
using Billing.Store;
using Billing.Themes;
using Billing.UI;
using System.Collections;
using System.Collections.Generic;
@@ -68,9 +67,7 @@ namespace Billing.Views
Icon = category.Icon,
Name = category.Name,
IsTopCategory = IsTopCategory,
TintColor = category.TintColor.IsTransparent() ?
BaseTheme.CurrentPrimaryColor :
category.TintColor.ToColor()
TintColor = category.TintColor
};
}
@@ -180,9 +177,7 @@ namespace Billing.Views
{
c.Name = c.Category.Name;
c.Icon = c.Category.Icon;
c.TintColor = c.Category.TintColor.IsTransparent() ?
BaseTheme.CurrentPrimaryColor :
c.Category.TintColor.ToColor();
c.TintColor = c.Category.TintColor;
}
}
@@ -191,7 +186,7 @@ namespace Billing.Views
public static readonly BindableProperty IsCheckedProperty = Helper.Create<bool, UICategory>(nameof(IsChecked));
public static readonly BindableProperty IconProperty = Helper.Create<string, UICategory>(nameof(Icon));
public static readonly BindableProperty NameProperty = Helper.Create<string, UICategory>(nameof(Name));
public static readonly BindableProperty TintColorProperty = Helper.Create<Color, UICategory>(nameof(TintColor));
public static readonly BindableProperty TintColorProperty = Helper.Create<long, UICategory>(nameof(TintColor));
public static readonly BindableProperty IsTopCategoryProperty = Helper.Create<bool, UICategory>(nameof(IsTopCategory));
public bool IsChecked
@@ -209,9 +204,9 @@ namespace Billing.Views
get => (string)GetValue(NameProperty);
set => SetValue(NameProperty, value);
}
public Color TintColor
public long TintColor
{
get => (Color)GetValue(TintColorProperty);
get => (long)GetValue(TintColorProperty);
set => SetValue(TintColorProperty, value);
}
public bool IsTopCategory
@@ -232,6 +227,10 @@ namespace Billing.Views
{
public string Key { get; }
public CategoryGrouping(string key) : base()
{
Key = key;
}
public CategoryGrouping(string key, IEnumerable<UICategory> categories) : base(categories)
{
Key = key;

View File

@@ -13,6 +13,7 @@
<ContentPage.Resources>
<ui:IconConverter x:Key="iconConverter"/>
<ui:SelectBackgroundColorConverter x:Key="backgroundConverter"/>
<ui:TintColorConverter x:Key="tintColorConverter"/>
</ContentPage.Resources>
<Grid ColumnDefinitions=".5*, .5*">
@@ -36,7 +37,7 @@
CommandParameter="{Binding .}"/>
</Grid.GestureRecognizers>
<ui:TintImage Source="{Binding Icon, Converter={StaticResource iconConverter}}"
ui:TintHelper.TintColor="{Binding TintColor}"
ui:TintHelper.TintColor="{Binding TintColor, Converter={StaticResource tintColorConverter}}"
WidthRequest="26" HeightRequest="20" VerticalOptions="Center"/>
<Label Grid.Column="1" Text="{Binding Name}" TextColor="{DynamicResource TextColor}"
HorizontalOptions="FillAndExpand" VerticalOptions="Center"
@@ -58,7 +59,7 @@
CommandParameter="{Binding .}"/>
</Grid.GestureRecognizers>
<ui:TintImage Source="{Binding Icon, Converter={StaticResource iconConverter}}"
ui:TintHelper.TintColor="{Binding TintColor}"
ui:TintHelper.TintColor="{Binding TintColor, Converter={StaticResource tintColorConverter}}"
WidthRequest="26" HeightRequest="20" VerticalOptions="Center"/>
<Label Grid.Column="1" Text="{Binding Name}" TextColor="{DynamicResource TextColor}"
HorizontalOptions="FillAndExpand" VerticalOptions="Center"

View File

@@ -1,6 +1,5 @@
using Billing.Languages;
using Billing.Models;
using Billing.Themes;
using Billing.UI;
using System;
using System.Collections.Generic;
@@ -31,12 +30,10 @@ namespace Billing.Views
public event EventHandler<UICategory> CategoryTapped;
private readonly int categoryId;
private readonly Color defaultColor;
public CategorySelectPage(int id)
{
categoryId = id;
defaultColor = BaseTheme.CurrentPrimaryColor;
TapTopCategory = new Command(OnTopCategoryTapped);
TapSubCategory = new Command(OnSubCategoryTapped);
@@ -45,6 +42,22 @@ namespace Billing.Views
new(Resource.Spending, App.Categories.Where(c => c.Type == CategoryType.Spending && c.ParentId == null).Select(c => WrapCategory(c))),
new(Resource.Income, App.Categories.Where(c => c.Type == CategoryType.Income && c.ParentId == null).Select(c => WrapCategory(c)))
};
UICategory last;
if (App.Categories.Any(c => c.LastUsed != null))
{
last = new UICategory(null)
{
IsChecked = true,
Icon = "rank",
Name = Resource.LastSelected,
TintColor = Definition.TransparentColor
};
TopCategories.Insert(0, new(Resource.Recent) { last });
}
else
{
last = null;
}
UICategory cat;
var category = App.Categories.FirstOrDefault(c => c.Id == categoryId);
@@ -58,7 +71,7 @@ namespace Billing.Views
}
else
{
cat = TopCategories.SelectMany(g => g).FirstOrDefault(c => c.Category.Id == category.ParentId);
cat = TopCategories.SelectMany(g => g).FirstOrDefault(c => c.Category?.Id == category.ParentId) ?? last;
}
DoRefreshSubCategories(cat);
@@ -72,7 +85,7 @@ namespace Billing.Views
IsChecked = c.Id == categoryId,
Icon = c.Icon,
Name = c.Name,
TintColor = c.TintColor.IsTransparent() ? defaultColor : c.TintColor.ToColor()
TintColor = c.TintColor
};
}
@@ -83,7 +96,18 @@ namespace Billing.Views
{
m.IsChecked = m == category;
}
SubCategories = App.Categories.Where(c => c.ParentId == category.Category.Id).Select(c => WrapCategory(c)).ToList();
if (category == null)
{
return;
}
if (category.Category == null)
{
SubCategories = App.Categories.Where(c => c.ParentId != null && c.LastUsed != null).OrderByDescending(c => c.LastUsed).Take(10).Select(c => WrapCategory(c)).ToList();
}
else
{
SubCategories = App.Categories.Where(c => c.ParentId == category.Category.Id).Select(c => WrapCategory(c)).ToList();
}
}
private async void OnTopCategoryTapped(object o)
@@ -97,7 +121,7 @@ namespace Billing.Views
if (o is UICategory category)
{
DoRefreshSubCategories(category);
if (SubCategories.Count == 0)
if (SubCategories?.Count == 0)
{
CategoryTapped?.Invoke(this, category);
await Navigation.PopAsync();

View File

@@ -66,6 +66,7 @@ namespace Billing.Views
new() { Icon = "taxi" },
new() { Icon = "fitness" },
new() { Icon = "party" },
new() { Icon = "share" },
};
source.AddRange(IconConverter.IconPreset.Select(icon => new BillingIcon { Icon = $"#brand#{icon.Key}" }));
foreach (var icon in source)

View File

@@ -49,6 +49,7 @@
<ui:BalanceColorConverter x:Key="colorConverter"/>
<ui:TimeConverter x:Key="timeConverter" IncludeDate="True"/>
<ui:IconConverter x:Key="iconConverter"/>
<ui:TintColorConverter x:Key="tintColorConverter"/>
<Style x:Key="titleLabel" TargetType="Label">
<Setter Property="FontSize" Value="16"/>
<Setter Property="Margin" Value="10, 20, 10, 10"/>
@@ -116,7 +117,7 @@
CommandParameter="{Binding .}"/>
</Grid.GestureRecognizers>
<ui:TintImage Source="{Binding Icon, Converter={StaticResource iconConverter}}"
ui:TintHelper.TintColor="{Binding TintColor}"
ui:TintHelper.TintColor="{Binding TintColor, Converter={StaticResource tintColorConverter}}"
WidthRequest="26" HeightRequest="20" VerticalOptions="Center"/>
<Label Grid.Column="1" Text="{Binding Name}" TextColor="{DynamicResource TextColor}"
VerticalOptions="Center"

View File

@@ -12,7 +12,7 @@
Shell.TabBarIsVisible="True">
<ContentPage.ToolbarItems>
<ToolbarItem Order="Primary" IconImageSource="project.png" Command="{Binding ShareCommand}"/>
<ToolbarItem Order="Primary" IconImageSource="share.png" Command="{Binding ShareCommand}"/>
</ContentPage.ToolbarItems>
<TableView Intent="Settings" HasUnevenRows="True">
@@ -39,5 +39,10 @@
</Grid>
</ViewCell>
</TableSection>
<TableSection Title="{r:Text Diagnostic}">
<ui:OptionSelectCell Height="36" Title="{r:Text ShareLogs}"
Detail="{Binding ManyRecords}"
Command="{Binding ShareLogsCommand}"/>
</TableSection>
</TableView>
</ui:BillingPage>

View File

@@ -1,8 +1,10 @@
using System.IO;
using Billing.Store;
using Billing.Themes;
using Billing.UI;
using Xamarin.Essentials;
using Xamarin.Forms;
using Resource = Billing.Languages.Resource;
namespace Billing.Views
{
@@ -10,6 +12,7 @@ namespace Billing.Views
{
private static readonly BindableProperty VersionProperty = Helper.Create<string, SettingPage>(nameof(Version));
private static readonly BindableProperty PrimaryColorProperty = Helper.Create<string, SettingPage>(nameof(PrimaryColor));
private static readonly BindableProperty ManyRecordsProperty = Helper.Create<string, SettingPage>(nameof(ManyRecords));
public string Version => (string)GetValue(VersionProperty);
public string PrimaryColor
@@ -17,38 +20,43 @@ namespace Billing.Views
get => (string)GetValue(PrimaryColorProperty);
set => SetValue(PrimaryColorProperty, value);
}
public string ManyRecords => (string)GetValue(ManyRecordsProperty);
public Command ShareCommand { get; }
public Command CategoryCommand { get; }
public Command ColorPickerCommand { get; }
public Command ShareLogsCommand { get; }
public SettingPage()
{
ShareCommand = new Command(OnShareCommand);
CategoryCommand = new Command(OnCategoryCommand);
ColorPickerCommand = new Command(OnColorPickerCommand);
ShareLogsCommand = new Command(OnShareLogsCommand);
InitializeComponent();
var (main, build) = Definition.GetVersion();
SetValue(VersionProperty, $"{main} ({build})");
}
protected override void OnAppearing()
protected override async void OnAppearing()
{
base.OnAppearing();
//SetValue(VersionProperty, $"{AppInfo.VersionString} ({AppInfo.BuildString})");
var colorString = Preferences.Get(Definition.PrimaryColorKey, Helper.DEFAULT_COLOR);
PrimaryColor = Helper.WrapColorString(colorString);
var count = await StoreHelper.GetLogsCount();
SetValue(ManyRecordsProperty, string.Format(Resource.ManyRecords, count));
}
protected override void OnDisappearing()
{
base.OnDisappearing();
var color = PrimaryColor;
Preferences.Set(Definition.PrimaryColorKey, color);
Light.Instance.RefreshColor(Color.FromHex(color));
Preferences.Set(Definition.PrimaryColorKey, PrimaryColor);
//Light.Instance.RefreshColor(Color.FromHex(color));
}
private async void OnShareCommand()
@@ -61,7 +69,7 @@ namespace Billing.Views
{
await Share.RequestAsync(new ShareFileRequest
{
File = new ShareFile(StoreHelper.DatabasePath)
File = new ShareFile(StoreHelper.DatabasePath, "application/vnd.sqlite3")
});
}
}
@@ -84,6 +92,67 @@ namespace Billing.Views
if (o is Color color)
{
PrimaryColor = Helper.WrapColorString(color.ToHex());
Light.Instance.RefreshColor(color);
}
}
private async void OnShareLogsCommand()
{
if (Tap.IsBusy)
{
return;
}
using (Tap.Start())
{
string file;
var count = await StoreHelper.GetLogsCount();
if (count > 0)
{
file = await StoreHelper.ExportLogs();
}
else
{
file = StoreHelper.GetLogFile();
}
if (file != null && File.Exists(file))
{
#if __IOS__
var sendEmail = Resource.SendEmail;
var shareLogs = Resource.ShareLogs;
var result = await DisplayActionSheet(Resource.HowToShareDiagnostic, Resource.Cancel, null, sendEmail, shareLogs);
if (result == sendEmail)
{
try
{
await Email.ComposeAsync(new EmailMessage
{
To = { "tsorgy@gmail.com " },
Subject = Resource.ShareLogs,
Attachments =
{
new(file, "text/csv")
}
});
}
catch (System.Exception ex)
{
Helper.Error("email.send", ex);
}
}
else if (result == shareLogs)
{
await Share.RequestAsync(new ShareFileRequest
{
File = new ShareFile(file, "text/csv")
});
}
#else
await Share.RequestAsync(new ShareFileRequest
{
File = new ShareFile(file, "text/csv")
});
#endif
}
}
}
}