category management
This commit is contained in:
@ -9,7 +9,8 @@
|
||||
x:Name="accountPage"
|
||||
x:DataType="v:AccountPage"
|
||||
Title="{r:Text Accounts}"
|
||||
BindingContext="{x:Reference accountPage}">
|
||||
BindingContext="{x:Reference accountPage}"
|
||||
Shell.TabBarIsVisible="True">
|
||||
|
||||
<ContentPage.Resources>
|
||||
<ui:MoneyConverter x:Key="moneyConverter"/>
|
||||
|
@ -177,22 +177,16 @@ namespace Billing.Views
|
||||
}
|
||||
using (Tap.Start())
|
||||
{
|
||||
var source = App.Categories.Select(c => new SelectItem<int>
|
||||
{
|
||||
Value = c.Id,
|
||||
Name = c.Name,
|
||||
Icon = c.Icon
|
||||
});
|
||||
var page = new ItemSelectPage<SelectItem<int>>(source);
|
||||
page.ItemTapped += Category_ItemTapped;
|
||||
var page = new CategorySelectPage(categoryId);
|
||||
page.CategoryTapped += CategorySelectPage_Tapped;
|
||||
await Navigation.PushAsync(page);
|
||||
}
|
||||
}
|
||||
|
||||
private void Category_ItemTapped(object sender, SelectItem<int> category)
|
||||
private void CategorySelectPage_Tapped(object sender, UICategory e)
|
||||
{
|
||||
categoryId = category.Value;
|
||||
SetValue(CategoryNameProperty, category.Name);
|
||||
categoryId = e.Category.Id;
|
||||
SetValue(CategoryNameProperty, e.Name);
|
||||
}
|
||||
|
||||
private async void OnSelectWallet()
|
||||
|
49
Billing.Shared/Views/AddCategoryPage.xaml
Normal file
49
Billing.Shared/Views/AddCategoryPage.xaml
Normal file
@ -0,0 +1,49 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<ui:BillingPage xmlns="http://xamarin.com/schemas/2014/forms"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
xmlns:r="clr-namespace:Billing.Languages"
|
||||
xmlns:ui="clr-namespace:Billing.UI"
|
||||
xmlns:v="clr-namespace:Billing.Views"
|
||||
x:Class="Billing.Views.AddCategoryPage"
|
||||
x:Name="addCategoryPage"
|
||||
x:DataType="v:AddCategoryPage"
|
||||
BindingContext="{x:Reference addCategoryPage}">
|
||||
|
||||
<ContentPage.Resources>
|
||||
<ui:IconConverter x:Key="iconConverter"/>
|
||||
</ContentPage.Resources>
|
||||
|
||||
<ContentPage.ToolbarItems>
|
||||
<ToolbarItem Order="Primary" IconImageSource="check.png" Command="{Binding CheckCategory}"/>
|
||||
</ContentPage.ToolbarItems>
|
||||
|
||||
<ContentPage.Content>
|
||||
<TableView Intent="Settings" HasUnevenRows="True">
|
||||
<TableSection Title=" ">
|
||||
<ui:OptionEditorCell Height="120" Icon="pencil.png" FontSize="20" Keyboard="Text"
|
||||
Title="{r:Text Name}"
|
||||
Text="{Binding CategoryName, Mode=TwoWay}"
|
||||
Placeholder="{r:Text NamePlaceholder}"/>
|
||||
<ui:OptionImageCell Height="44" Icon="face.png"
|
||||
Title="{r:Text Icon}"
|
||||
ImageSource="{Binding CategoryIcon, Converter={StaticResource iconConverter}}"
|
||||
TintColor="{Binding TintColor}"
|
||||
Command="{Binding SelectIcon}"/>
|
||||
</TableSection>
|
||||
<TableSection>
|
||||
<TableSection.Title>
|
||||
<OnPlatform x:TypeArguments="x:String" Android=" "/>
|
||||
</TableSection.Title>
|
||||
<ui:OptionEntryCell Height="44" Icon="color.png" Keyboard="Numeric"
|
||||
Title="{r:Text PrimaryColor}"
|
||||
Text="{Binding TintColorString, Mode=TwoWay}"/>
|
||||
<ViewCell Height="120">
|
||||
<Grid BackgroundColor="{DynamicResource OptionTintColor}"
|
||||
ColumnDefinitions=".3*, .7*" Padding="10">
|
||||
<ui:ColorPicker Grid.Column="1" ColorChanged="ColorPicker_ColorChanged"/>
|
||||
</Grid>
|
||||
</ViewCell>
|
||||
</TableSection>
|
||||
</TableView>
|
||||
</ContentPage.Content>
|
||||
</ui:BillingPage>
|
137
Billing.Shared/Views/AddCategoryPage.xaml.cs
Normal file
137
Billing.Shared/Views/AddCategoryPage.xaml.cs
Normal file
@ -0,0 +1,137 @@
|
||||
using Billing.Languages;
|
||||
using Billing.Models;
|
||||
using Billing.Themes;
|
||||
using Billing.UI;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Billing.Views
|
||||
{
|
||||
public partial class AddCategoryPage : BillingPage
|
||||
{
|
||||
private static readonly BindableProperty CategoryNameProperty = BindableProperty.Create(nameof(CategoryName), typeof(string), typeof(AddCategoryPage));
|
||||
private static readonly BindableProperty CategoryIconProperty = BindableProperty.Create(nameof(CategoryIcon), typeof(string), typeof(AddCategoryPage));
|
||||
private static readonly BindableProperty TintColorProperty = BindableProperty.Create(nameof(TintColor), typeof(Color), typeof(AddCategoryPage));
|
||||
private static readonly BindableProperty TintColorStringProperty = BindableProperty.Create(nameof(TintColorString), typeof(string), typeof(AddCategoryPage));
|
||||
|
||||
public string CategoryName
|
||||
{
|
||||
get => (string)GetValue(CategoryNameProperty);
|
||||
set => SetValue(CategoryNameProperty, value);
|
||||
}
|
||||
public string CategoryIcon
|
||||
{
|
||||
get => (string)GetValue(CategoryIconProperty);
|
||||
set => SetValue(CategoryIconProperty, value);
|
||||
}
|
||||
public Color TintColor
|
||||
{
|
||||
get => (Color)GetValue(TintColorProperty);
|
||||
set => SetValue(TintColorProperty, value);
|
||||
}
|
||||
public string TintColorString
|
||||
{
|
||||
get => (string)GetValue(TintColorStringProperty);
|
||||
set => SetValue(TintColorStringProperty, value);
|
||||
}
|
||||
|
||||
public Command CheckCategory { get; }
|
||||
public Command SelectIcon { get; }
|
||||
|
||||
public event EventHandler<Category> CategoryChecked;
|
||||
|
||||
private readonly int categoryId;
|
||||
private readonly Category parent;
|
||||
|
||||
public AddCategoryPage(int id = -1, Category parent = null)
|
||||
{
|
||||
categoryId = id;
|
||||
this.parent = parent;
|
||||
var category = App.Categories.FirstOrDefault(c => c.Id == id);
|
||||
Title = category?.Name ?? Resource.AddCategory;
|
||||
|
||||
if (category != null)
|
||||
{
|
||||
CategoryName = category.Name;
|
||||
CategoryIcon = category.Icon;
|
||||
if (category.TintColor == Color.Transparent ||
|
||||
category.TintColor == default)
|
||||
{
|
||||
TintColor = (Color)Application.Current.Resources[BaseTheme.PrimaryColor];
|
||||
}
|
||||
else
|
||||
{
|
||||
TintColor = category.TintColor;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
TintColor = (Color)Application.Current.Resources[BaseTheme.PrimaryColor];
|
||||
}
|
||||
TintColorString = Helper.WrapColorString(TintColor.ToHex());
|
||||
|
||||
CheckCategory = new Command(OnCheckCategory);
|
||||
SelectIcon = new Command(OnSelectIcon);
|
||||
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void ColorPicker_ColorChanged(object sender, Color e)
|
||||
{
|
||||
TintColor = e;
|
||||
TintColorString = Helper.WrapColorString(e.ToHex());
|
||||
}
|
||||
|
||||
private async void OnCheckCategory()
|
||||
{
|
||||
if (Tap.IsBusy)
|
||||
{
|
||||
return;
|
||||
}
|
||||
using (Tap.Start())
|
||||
{
|
||||
var category = App.Categories.FirstOrDefault(c => c.Id == categoryId);
|
||||
if (category == null)
|
||||
{
|
||||
CategoryChecked?.Invoke(this, new Category
|
||||
{
|
||||
Id = -1,
|
||||
Name = CategoryName,
|
||||
Icon = CategoryIcon,
|
||||
TintColor = TintColor,
|
||||
ParentId = parent?.Id ?? -1,
|
||||
Type = parent?.Type ?? CategoryType.Spending
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
category.Name = CategoryName;
|
||||
category.Icon = CategoryIcon;
|
||||
category.TintColor = TintColor;
|
||||
CategoryChecked?.Invoke(this, category);
|
||||
}
|
||||
await Navigation.PopAsync();
|
||||
}
|
||||
}
|
||||
|
||||
private async void OnSelectIcon()
|
||||
{
|
||||
if (Tap.IsBusy)
|
||||
{
|
||||
return;
|
||||
}
|
||||
using (Tap.Start())
|
||||
{
|
||||
var page = new IconSelectPage(CategoryIcon);
|
||||
page.IconChecked += Category_IconChecked;
|
||||
await Navigation.PushAsync(page);
|
||||
}
|
||||
}
|
||||
|
||||
private void Category_IconChecked(object sender, string icon)
|
||||
{
|
||||
CategoryIcon = icon;
|
||||
}
|
||||
}
|
||||
}
|
@ -8,7 +8,8 @@
|
||||
x:DataType="v:BillPage"
|
||||
x:Name="billPage"
|
||||
BindingContext="{x:Reference billPage}"
|
||||
Title="{r:Text Bills}">
|
||||
Title="{r:Text Bills}"
|
||||
Shell.TabBarIsVisible="True">
|
||||
|
||||
<ContentPage.Resources>
|
||||
<ui:TitleDateConverter x:Key="titleDateConverter"/>
|
||||
|
@ -7,6 +7,7 @@ using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Xamarin.Essentials;
|
||||
using Xamarin.Forms;
|
||||
using Resource = Billing.Languages.Resource;
|
||||
|
||||
namespace Billing.Views
|
||||
{
|
||||
@ -170,8 +171,9 @@ namespace Billing.Views
|
||||
}
|
||||
e.Id = maxId + 1;
|
||||
App.Bills.Add(e);
|
||||
Bills.Add(WrapBill(e));
|
||||
billsLayout.Refresh(Bills);
|
||||
var bills = Bills;
|
||||
bills.Add(WrapBill(e));
|
||||
Bills = bills.OrderBy(b => b.DateCreation).ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
47
Billing.Shared/Views/CategoryPage.xaml
Normal file
47
Billing.Shared/Views/CategoryPage.xaml
Normal file
@ -0,0 +1,47 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<ui:BillingPage xmlns="http://xamarin.com/schemas/2014/forms"
|
||||
xmlns:ui="clr-namespace:Billing.UI"
|
||||
xmlns:v="clr-namespace:Billing.Views"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
x:Class="Billing.Views.CategoryPage"
|
||||
x:Name="categoryPage"
|
||||
x:DataType="v:CategoryPage"
|
||||
BindingContext="{x:Reference categoryPage}">
|
||||
|
||||
<ContentPage.Resources>
|
||||
<ui:IconConverter x:Key="iconConverter"/>
|
||||
</ContentPage.Resources>
|
||||
|
||||
<ScrollView>
|
||||
<ui:GroupStackLayout x:Name="groupLayout" ItemsSource="{Binding Categories}" Margin="0, 10, 0, 0"
|
||||
GroupHeight="36" RowHeight="44">
|
||||
<ui:GroupStackLayout.GroupHeaderTemplate>
|
||||
<DataTemplate x:DataType="v:CategoryGrouping">
|
||||
<StackLayout Orientation="Horizontal" Padding="10, 0" VerticalOptions="End">
|
||||
<Label Text="{Binding Key}" TextColor="{DynamicResource SecondaryTextColor}"/>
|
||||
</StackLayout>
|
||||
</DataTemplate>
|
||||
</ui:GroupStackLayout.GroupHeaderTemplate>
|
||||
<ui:GroupStackLayout.ItemTemplate>
|
||||
<DataTemplate x:DataType="v:UICategory">
|
||||
<ui:LongPressGrid Padding="20, 0" ColumnSpacing="10" ColumnDefinitions="Auto, *, Auto"
|
||||
LongCommand="{Binding LongPressed, Source={x:Reference categoryPage}}"
|
||||
LongCommandParameter="{Binding .}">
|
||||
<Grid.GestureRecognizers>
|
||||
<TapGestureRecognizer Command="{Binding Tapped, Source={x:Reference categoryPage}}"
|
||||
CommandParameter="{Binding .}"/>
|
||||
</Grid.GestureRecognizers>
|
||||
<ui:TintImage Source="{Binding Icon, Converter={StaticResource iconConverter}}"
|
||||
PrimaryColor="{Binding TintColor}"
|
||||
WidthRequest="26" HeightRequest="20" VerticalOptions="Center"/>
|
||||
<Label Grid.Column="1" Text="{Binding Name}" TextColor="{DynamicResource TextColor}"
|
||||
HorizontalOptions="FillAndExpand" VerticalOptions="Center"
|
||||
FontSize="Default" FontAttributes="Bold"/>
|
||||
<ui:TintImage Grid.Column="2" Source="right.png" HeightRequest="20"
|
||||
IsVisible="{Binding IsTopCategory}"/>
|
||||
</ui:LongPressGrid>
|
||||
</DataTemplate>
|
||||
</ui:GroupStackLayout.ItemTemplate>
|
||||
</ui:GroupStackLayout>
|
||||
</ScrollView>
|
||||
</ui:BillingPage>
|
247
Billing.Shared/Views/CategoryPage.xaml.cs
Normal file
247
Billing.Shared/Views/CategoryPage.xaml.cs
Normal file
@ -0,0 +1,247 @@
|
||||
using Billing.Languages;
|
||||
using Billing.Models;
|
||||
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
|
||||
{
|
||||
public partial class CategoryPage : BillingPage
|
||||
{
|
||||
private static readonly BindableProperty CategoriesProperty = BindableProperty.Create(nameof(Categories), typeof(IList), typeof(CategoryPage));
|
||||
private static readonly BindableProperty IsTopCategoryProperty = BindableProperty.Create(nameof(IsTopCategory), typeof(bool), typeof(CategoryPage));
|
||||
|
||||
public IList Categories
|
||||
{
|
||||
get => (IList)GetValue(CategoriesProperty);
|
||||
set => SetValue(CategoriesProperty, value);
|
||||
}
|
||||
public bool IsTopCategory => (bool)GetValue(IsTopCategoryProperty);
|
||||
|
||||
public Command LongPressed { get; }
|
||||
public Command Tapped { get; }
|
||||
|
||||
private readonly int parentId;
|
||||
|
||||
public CategoryPage(int pid = -1)
|
||||
{
|
||||
parentId = pid;
|
||||
var category = App.Categories.FirstOrDefault(c => c.Id == pid);
|
||||
Title = category?.Name ?? Resource.CategoryManage;
|
||||
|
||||
if (category != null)
|
||||
{
|
||||
SetValue(IsTopCategoryProperty, false);
|
||||
Categories = App.Categories.Where(c => c.ParentId == pid).Select(c => WrapCategory(c)).ToList();
|
||||
ToolbarItems.Add(new ToolbarItem
|
||||
{
|
||||
Order = ToolbarItemOrder.Primary,
|
||||
IconImageSource = "plus.png",
|
||||
Command = new Command(OnAddCategory)
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
SetValue(IsTopCategoryProperty, true);
|
||||
Categories = new List<CategoryGrouping>
|
||||
{
|
||||
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)))
|
||||
};
|
||||
}
|
||||
|
||||
LongPressed = new Command(OnLongPressed);
|
||||
Tapped = new Command(OnTapped);
|
||||
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private UICategory WrapCategory(Category category)
|
||||
{
|
||||
return new UICategory(category)
|
||||
{
|
||||
Icon = category.Icon,
|
||||
Name = category.Name,
|
||||
IsTopCategory = IsTopCategory,
|
||||
TintColor = category.TintColor == Color.Transparent || category.TintColor == default ?
|
||||
(Color)Application.Current.Resources[BaseTheme.PrimaryColor] :
|
||||
category.TintColor
|
||||
};
|
||||
}
|
||||
|
||||
private async void OnAddCategory()
|
||||
{
|
||||
if (Tap.IsBusy)
|
||||
{
|
||||
return;
|
||||
}
|
||||
using (Tap.Start())
|
||||
{
|
||||
var page = new AddCategoryPage();
|
||||
page.CategoryChecked += OnCategoryChecked;
|
||||
await Navigation.PushAsync(page);
|
||||
}
|
||||
}
|
||||
|
||||
private async void OnLongPressed(object o)
|
||||
{
|
||||
if (Tap.IsBusy)
|
||||
{
|
||||
return;
|
||||
}
|
||||
using (Tap.Start())
|
||||
{
|
||||
if (o is UICategory c)
|
||||
{
|
||||
if (parentId < 0)
|
||||
{
|
||||
var page = new AddCategoryPage(c.Category.Id);
|
||||
page.CategoryChecked += OnCategoryChecked;
|
||||
await Navigation.PushAsync(page);
|
||||
}
|
||||
else
|
||||
{
|
||||
var result = await this.ShowConfirm(string.Format(Resource.ConfirmDeleteCategory, c.Category.Name));
|
||||
if (result)
|
||||
{
|
||||
Categories.Remove(c);
|
||||
groupLayout.Refresh(Categories);
|
||||
App.Categories.Remove(c.Category);
|
||||
_ = Task.Run(App.WriteCategories);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async void OnTapped(object o)
|
||||
{
|
||||
if (Tap.IsBusy)
|
||||
{
|
||||
return;
|
||||
}
|
||||
using (Tap.Start())
|
||||
{
|
||||
if (o is UICategory c)
|
||||
{
|
||||
if (parentId < 0)
|
||||
{
|
||||
var page = new CategoryPage(c.Category.Id);
|
||||
await Navigation.PushAsync(page);
|
||||
}
|
||||
else
|
||||
{
|
||||
var page = new AddCategoryPage(c.Category.Id);
|
||||
page.CategoryChecked += OnCategoryChecked;
|
||||
await Navigation.PushAsync(page);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnCategoryChecked(object sender, Category category)
|
||||
{
|
||||
if (category.Id < 0)
|
||||
{
|
||||
// add
|
||||
int maxId;
|
||||
if (App.Categories.Count > 0)
|
||||
{
|
||||
maxId = App.Categories.Max(b => b.Id);
|
||||
}
|
||||
else
|
||||
{
|
||||
maxId = -1;
|
||||
}
|
||||
category.Id = maxId + 1;
|
||||
App.Categories.Add(category);
|
||||
Categories.Add(WrapCategory(category));
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var o in Categories)
|
||||
{
|
||||
if (o is CategoryGrouping grouping)
|
||||
{
|
||||
var c = grouping.FirstOrDefault(c => c.Category == category);
|
||||
if (c != null)
|
||||
{
|
||||
UpdateCategory(c);
|
||||
}
|
||||
}
|
||||
else if (o is UICategory c && c.Category == category)
|
||||
{
|
||||
UpdateCategory(c);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
groupLayout.Refresh(Categories);
|
||||
|
||||
Task.Run(App.WriteCategories);
|
||||
}
|
||||
|
||||
private void UpdateCategory(UICategory c)
|
||||
{
|
||||
c.Name = c.Category.Name;
|
||||
c.Icon = c.Category.Icon;
|
||||
c.TintColor = c.Category.TintColor;
|
||||
}
|
||||
}
|
||||
|
||||
public class UICategory : BindableObject
|
||||
{
|
||||
public static readonly BindableProperty IsCheckedProperty = BindableProperty.Create(nameof(IsChecked), typeof(bool), typeof(UICategory));
|
||||
public static readonly BindableProperty IconProperty = BindableProperty.Create(nameof(Icon), typeof(string), typeof(UICategory));
|
||||
public static readonly BindableProperty NameProperty = BindableProperty.Create(nameof(Name), typeof(string), typeof(UICategory));
|
||||
public static readonly BindableProperty TintColorProperty = BindableProperty.Create(nameof(TintColor), typeof(Color), typeof(UICategory));
|
||||
public static readonly BindableProperty IsTopCategoryProperty = BindableProperty.Create(nameof(IsTopCategory), typeof(bool), typeof(UICategory));
|
||||
|
||||
public bool IsChecked
|
||||
{
|
||||
get => (bool)GetValue(IsCheckedProperty);
|
||||
set => SetValue(IsCheckedProperty, value);
|
||||
}
|
||||
public string Icon
|
||||
{
|
||||
get => (string)GetValue(IconProperty);
|
||||
set => SetValue(IconProperty, value);
|
||||
}
|
||||
public string Name
|
||||
{
|
||||
get => (string)GetValue(NameProperty);
|
||||
set => SetValue(NameProperty, value);
|
||||
}
|
||||
public Color TintColor
|
||||
{
|
||||
get => (Color)GetValue(TintColorProperty);
|
||||
set => SetValue(TintColorProperty, value);
|
||||
}
|
||||
public bool IsTopCategory
|
||||
{
|
||||
get => (bool)GetValue(IsTopCategoryProperty);
|
||||
set => SetValue(IsTopCategoryProperty, value);
|
||||
}
|
||||
|
||||
public Category Category { get; }
|
||||
|
||||
public UICategory(Category category)
|
||||
{
|
||||
Category = category;
|
||||
}
|
||||
}
|
||||
|
||||
public class CategoryGrouping : List<UICategory>, IGrouping<string, UICategory>
|
||||
{
|
||||
public string Key { get; }
|
||||
|
||||
public CategoryGrouping(string key, IEnumerable<UICategory> categories) : base(categories)
|
||||
{
|
||||
Key = key;
|
||||
}
|
||||
}
|
||||
}
|
72
Billing.Shared/Views/CategorySelectPage.xaml
Normal file
72
Billing.Shared/Views/CategorySelectPage.xaml
Normal file
@ -0,0 +1,72 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<ui:BillingPage xmlns="http://xamarin.com/schemas/2014/forms"
|
||||
xmlns:r="clr-namespace:Billing.Languages"
|
||||
xmlns:ui="clr-namespace:Billing.UI"
|
||||
xmlns:v="clr-namespace:Billing.Views"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
x:Class="Billing.Views.CategorySelectPage"
|
||||
x:Name="categorySelectPage"
|
||||
x:DataType="v:CategorySelectPage"
|
||||
BindingContext="{x:Reference categorySelectPage}"
|
||||
Title="{r:Text SelectCategory}">
|
||||
|
||||
<ContentPage.Resources>
|
||||
<ui:IconConverter x:Key="iconConverter"/>
|
||||
<ui:SelectBackgroundColorConverter x:Key="backgroundConverter"/>
|
||||
</ContentPage.Resources>
|
||||
|
||||
<Grid ColumnDefinitions=".5*, .5*">
|
||||
<ScrollView>
|
||||
<ui:GroupStackLayout ItemsSource="{Binding TopCategories}" Margin="0, 10, 0, 0"
|
||||
GroupHeight="36" RowHeight="44">
|
||||
<ui:GroupStackLayout.GroupHeaderTemplate>
|
||||
<DataTemplate x:DataType="v:CategoryGrouping">
|
||||
<StackLayout Orientation="Horizontal" Padding="10, 0" VerticalOptions="End">
|
||||
<Label Text="{Binding Key}"
|
||||
TextColor="{DynamicResource SecondaryTextColor}"/>
|
||||
</StackLayout>
|
||||
</DataTemplate>
|
||||
</ui:GroupStackLayout.GroupHeaderTemplate>
|
||||
<ui:GroupStackLayout.ItemTemplate>
|
||||
<DataTemplate x:DataType="v:UICategory">
|
||||
<Grid Padding="20, 0, 10, 0" ColumnSpacing="10" ColumnDefinitions="Auto, *"
|
||||
BackgroundColor="{Binding IsChecked, Converter={StaticResource backgroundConverter}}">
|
||||
<Grid.GestureRecognizers>
|
||||
<TapGestureRecognizer Command="{Binding TapTopCategory, Source={x:Reference categorySelectPage}}"
|
||||
CommandParameter="{Binding .}"/>
|
||||
</Grid.GestureRecognizers>
|
||||
<ui:TintImage Source="{Binding Icon, Converter={StaticResource iconConverter}}"
|
||||
PrimaryColor="{Binding TintColor}"
|
||||
WidthRequest="26" HeightRequest="20" VerticalOptions="Center"/>
|
||||
<Label Grid.Column="1" Text="{Binding Name}" TextColor="{DynamicResource TextColor}"
|
||||
HorizontalOptions="FillAndExpand" VerticalOptions="Center"
|
||||
FontSize="Default" FontAttributes="Bold"/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ui:GroupStackLayout.ItemTemplate>
|
||||
</ui:GroupStackLayout>
|
||||
</ScrollView>
|
||||
|
||||
<ScrollView Grid.Column="1">
|
||||
<ui:GroupStackLayout ItemsSource="{Binding SubCategories}" Margin="0, 10, 0, 0" RowHeight="44" Padding="0, 36, 0, 0">
|
||||
<ui:GroupStackLayout.ItemTemplate>
|
||||
<DataTemplate x:DataType="v:UICategory">
|
||||
<Grid Padding="20, 0, 10, 0" ColumnSpacing="10" ColumnDefinitions="Auto, *"
|
||||
BackgroundColor="{Binding IsChecked, Converter={StaticResource backgroundConverter}}">
|
||||
<Grid.GestureRecognizers>
|
||||
<TapGestureRecognizer Command="{Binding TapSubCategory, Source={x:Reference categorySelectPage}}"
|
||||
CommandParameter="{Binding .}"/>
|
||||
</Grid.GestureRecognizers>
|
||||
<ui:TintImage Source="{Binding Icon, Converter={StaticResource iconConverter}}"
|
||||
PrimaryColor="{Binding TintColor}"
|
||||
WidthRequest="26" HeightRequest="20" VerticalOptions="Center"/>
|
||||
<Label Grid.Column="1" Text="{Binding Name}" TextColor="{DynamicResource TextColor}"
|
||||
HorizontalOptions="FillAndExpand" VerticalOptions="Center"
|
||||
FontSize="Default" FontAttributes="Bold"/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ui:GroupStackLayout.ItemTemplate>
|
||||
</ui:GroupStackLayout>
|
||||
</ScrollView>
|
||||
</Grid>
|
||||
</ui:BillingPage>
|
120
Billing.Shared/Views/CategorySelectPage.xaml.cs
Normal file
120
Billing.Shared/Views/CategorySelectPage.xaml.cs
Normal file
@ -0,0 +1,120 @@
|
||||
using Billing.Languages;
|
||||
using Billing.Models;
|
||||
using Billing.Themes;
|
||||
using Billing.UI;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Billing.Views
|
||||
{
|
||||
public partial class CategorySelectPage : BillingPage
|
||||
{
|
||||
private static readonly BindableProperty TopCategoriesProperty = BindableProperty.Create(nameof(TopCategories), typeof(List<CategoryGrouping>), typeof(CategorySelectPage));
|
||||
private static readonly BindableProperty SubCategoriesProperty = BindableProperty.Create(nameof(SubCategories), typeof(List<UICategory>), typeof(CategorySelectPage));
|
||||
|
||||
public List<CategoryGrouping> TopCategories
|
||||
{
|
||||
get => (List<CategoryGrouping>)GetValue(TopCategoriesProperty);
|
||||
set => SetValue(TopCategoriesProperty, value);
|
||||
}
|
||||
public List<UICategory> SubCategories
|
||||
{
|
||||
get => (List<UICategory>)GetValue(SubCategoriesProperty);
|
||||
set => SetValue(SubCategoriesProperty, value);
|
||||
}
|
||||
|
||||
public Command TapTopCategory { get; }
|
||||
public Command TapSubCategory { get; }
|
||||
|
||||
public event EventHandler<UICategory> CategoryTapped;
|
||||
|
||||
private readonly int categoryId;
|
||||
private readonly Color defaultColor;
|
||||
|
||||
public CategorySelectPage(int id)
|
||||
{
|
||||
categoryId = id;
|
||||
defaultColor = (Color)Application.Current.Resources[BaseTheme.PrimaryColor];
|
||||
TapTopCategory = new Command(OnTopCategoryTapped);
|
||||
TapSubCategory = new Command(OnSubCategoryTapped);
|
||||
|
||||
TopCategories = new List<CategoryGrouping>
|
||||
{
|
||||
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 cat;
|
||||
var category = App.Categories.FirstOrDefault(c => c.Id == categoryId);
|
||||
if (category == null)
|
||||
{
|
||||
cat = TopCategories.Where(g => g.Count > 0).Select(g => g.First()).FirstOrDefault();
|
||||
}
|
||||
else if (category.ParentId == null)
|
||||
{
|
||||
cat = TopCategories.SelectMany(g => g).FirstOrDefault(c => c.Category == category);
|
||||
}
|
||||
else
|
||||
{
|
||||
cat = TopCategories.SelectMany(g => g).FirstOrDefault(c => c.Category.Id == category.ParentId);
|
||||
}
|
||||
OnTopCategoryTapped(cat);
|
||||
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private UICategory WrapCategory(Category c)
|
||||
{
|
||||
return new UICategory(c)
|
||||
{
|
||||
IsChecked = c.Id == categoryId,
|
||||
Icon = c.Icon,
|
||||
Name = c.Name,
|
||||
TintColor = c.TintColor == Color.Transparent || c.TintColor == default ? defaultColor : c.TintColor
|
||||
};
|
||||
}
|
||||
|
||||
private async void OnTopCategoryTapped(object o)
|
||||
{
|
||||
if (Tap.IsBusy)
|
||||
{
|
||||
return;
|
||||
}
|
||||
using (Tap.Start())
|
||||
{
|
||||
if (o is UICategory category)
|
||||
{
|
||||
var many = TopCategories.SelectMany(g => g);
|
||||
foreach (var m in many)
|
||||
{
|
||||
m.IsChecked = m == category;
|
||||
}
|
||||
SubCategories = App.Categories.Where(c => c.ParentId == category.Category.Id).Select(c => WrapCategory(c)).ToList();
|
||||
if (SubCategories.Count == 0)
|
||||
{
|
||||
CategoryTapped?.Invoke(this, category);
|
||||
await Navigation.PopAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async void OnSubCategoryTapped(object o)
|
||||
{
|
||||
if (Tap.IsBusy)
|
||||
{
|
||||
return;
|
||||
}
|
||||
using (Tap.Start())
|
||||
{
|
||||
if (o is UICategory category)
|
||||
{
|
||||
CategoryTapped?.Invoke(this, category);
|
||||
await Navigation.PopAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -39,12 +39,34 @@ namespace Billing.Views
|
||||
{
|
||||
new() { Icon = BaseModel.ICON_DEFAULT },
|
||||
new() { Icon = "wallet" },
|
||||
new() { Icon = "dollar" },
|
||||
new() { Icon = "creditcard" },
|
||||
new() { Icon = "debitcard" },
|
||||
new() { Icon = "cmb" },
|
||||
new() { Icon = "rcb" },
|
||||
new() { Icon = "yuebao" },
|
||||
new() { Icon = "zhaozhaoying" }
|
||||
new() { Icon = "zhaozhaoying" },
|
||||
new() { Icon = "clothes" },
|
||||
new() { Icon = "food" },
|
||||
new() { Icon = "drink" },
|
||||
new() { Icon = "daily" },
|
||||
new() { Icon = "trans" },
|
||||
new() { Icon = "face" },
|
||||
new() { Icon = "learn" },
|
||||
new() { Icon = "medical" },
|
||||
new() { Icon = "gem" },
|
||||
new() { Icon = "makeup" },
|
||||
new() { Icon = "brunch" },
|
||||
new() { Icon = "dinner" },
|
||||
new() { Icon = "fruit" },
|
||||
new() { Icon = "bill" },
|
||||
new() { Icon = "fee" },
|
||||
new() { Icon = "rent" },
|
||||
new() { Icon = "maintenance" },
|
||||
new() { Icon = "rail" },
|
||||
new() { Icon = "taxi" },
|
||||
new() { Icon = "fitness" },
|
||||
new() { Icon = "party" },
|
||||
};
|
||||
source.AddRange(IconConverter.IconPreset.Select(icon => new BillingIcon { Icon = $"#brand#{icon.Key}" }));
|
||||
foreach (var icon in source)
|
||||
@ -59,15 +81,22 @@ namespace Billing.Views
|
||||
|
||||
private async void OnIconCheck(object o)
|
||||
{
|
||||
if (o is string icon)
|
||||
if (Tap.IsBusy)
|
||||
{
|
||||
foreach (var ic in IconsSource)
|
||||
return;
|
||||
}
|
||||
using (Tap.Start())
|
||||
{
|
||||
if (o is string icon)
|
||||
{
|
||||
ic.IsChecked = ic.Icon == icon;
|
||||
foreach (var ic in IconsSource)
|
||||
{
|
||||
ic.IsChecked = ic.Icon == icon;
|
||||
}
|
||||
iconChecked = icon;
|
||||
IconChecked?.Invoke(this, icon);
|
||||
await Navigation.PopAsync();
|
||||
}
|
||||
iconChecked = icon;
|
||||
IconChecked?.Invoke(this, icon);
|
||||
await Navigation.PopAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,21 +8,32 @@
|
||||
x:Name="settingPage"
|
||||
x:DataType="v:SettingPage"
|
||||
Title="{r:Text Settings}"
|
||||
BindingContext="{x:Reference settingPage}">
|
||||
BindingContext="{x:Reference settingPage}"
|
||||
Shell.TabBarIsVisible="True">
|
||||
|
||||
<TableView Intent="Settings" RowHeight="36">
|
||||
<TableView Intent="Settings" HasUnevenRows="True">
|
||||
<TableSection Title="{r:Text About}">
|
||||
<ui:OptionTextCell Title="{r:Text Version}"
|
||||
<ui:OptionTextCell Height="36" Title="{r:Text Version}"
|
||||
Detail="{Binding Version}"/>
|
||||
</TableSection>
|
||||
<TableSection Title="{r:Text Feature}">
|
||||
<ui:OptionSelectCell Height="36" Title="{r:Text CategoryManage}"
|
||||
Detail="{r:Text Detail}"
|
||||
Command="{Binding CategoryCommand}"/>
|
||||
</TableSection>
|
||||
<TableSection Title="{r:Text Preference}">
|
||||
<ui:OptionEntryCell Title="{r:Text PrimaryColor}"
|
||||
Text="{Binding Red, Mode=TwoWay}"
|
||||
Keyboard="Numeric"/>
|
||||
<ui:OptionEntryCell Text="{Binding Green, Mode=TwoWay}"
|
||||
Keyboard="Numeric"/>
|
||||
<ui:OptionEntryCell Text="{Binding Blue, Mode=TwoWay}"
|
||||
Keyboard="Numeric"/>
|
||||
<ui:OptionEntryCell Height="36" Title="{r:Text PrimaryColor}"
|
||||
Text="{Binding PrimaryColor, Mode=TwoWay}"
|
||||
Keyboard="Text"/>
|
||||
<ViewCell Height="120">
|
||||
<Grid BackgroundColor="{DynamicResource OptionTintColor}"
|
||||
ColumnDefinitions=".3*, .7*" Padding="10">
|
||||
<!--<Label Text="" LineBreakMode="TailTruncation"
|
||||
VerticalOptions="Center"
|
||||
TextColor="{DynamicResource TextColor}"/>-->
|
||||
<ui:ColorPicker Grid.Column="1" ColorChanged="ColorPicker_ColorChanged"/>
|
||||
</Grid>
|
||||
</ViewCell>
|
||||
</TableSection>
|
||||
</TableView>
|
||||
</ui:BillingPage>
|
@ -1,5 +1,6 @@
|
||||
using Billing.Themes;
|
||||
using Billing.UI;
|
||||
using System.Globalization;
|
||||
using Xamarin.Essentials;
|
||||
using Xamarin.Forms;
|
||||
|
||||
@ -8,29 +9,20 @@ namespace Billing.Views
|
||||
public partial class SettingPage : BillingPage
|
||||
{
|
||||
private static readonly BindableProperty VersionProperty = BindableProperty.Create(nameof(Version), typeof(string), typeof(SettingPage));
|
||||
private static readonly BindableProperty RedProperty = BindableProperty.Create(nameof(Red), typeof(byte), typeof(SettingPage));
|
||||
private static readonly BindableProperty GreenProperty = BindableProperty.Create(nameof(Green), typeof(byte), typeof(SettingPage));
|
||||
private static readonly BindableProperty BlueProperty = BindableProperty.Create(nameof(Blue), typeof(byte), typeof(SettingPage));
|
||||
private static readonly BindableProperty PrimaryColorProperty = BindableProperty.Create(nameof(PrimaryColor), typeof(string), typeof(SettingPage));
|
||||
|
||||
public string Version => (string)GetValue(VersionProperty);
|
||||
public byte Red
|
||||
public string PrimaryColor
|
||||
{
|
||||
get => (byte)GetValue(RedProperty);
|
||||
set => SetValue(RedProperty, value);
|
||||
}
|
||||
public byte Green
|
||||
{
|
||||
get => (byte)GetValue(GreenProperty);
|
||||
set => SetValue(GreenProperty, value);
|
||||
}
|
||||
public byte Blue
|
||||
{
|
||||
get => (byte)GetValue(BlueProperty);
|
||||
set => SetValue(BlueProperty, value);
|
||||
get => (string)GetValue(PrimaryColorProperty);
|
||||
set => SetValue(PrimaryColorProperty, value);
|
||||
}
|
||||
|
||||
public Command CategoryCommand { get; }
|
||||
|
||||
public SettingPage()
|
||||
{
|
||||
CategoryCommand = new Command(OnCategoryCommand);
|
||||
InitializeComponent();
|
||||
|
||||
var (main, build) = Definition.GetVersion();
|
||||
@ -42,20 +34,35 @@ namespace Billing.Views
|
||||
base.OnAppearing();
|
||||
|
||||
//SetValue(VersionProperty, $"{AppInfo.VersionString} ({AppInfo.BuildString})");
|
||||
var colorString = Preferences.Get(Definition.PrimaryColorKey, "#183153");
|
||||
var color = Color.FromHex(colorString);
|
||||
Red = (byte)(color.R * 255);
|
||||
Green = (byte)(color.G * 255);
|
||||
Blue = (byte)(color.B * 255);
|
||||
var colorString = Preferences.Get(Definition.PrimaryColorKey, Helper.DEFAULT_COLOR);
|
||||
PrimaryColor = Helper.WrapColorString(colorString);
|
||||
}
|
||||
|
||||
protected override void OnDisappearing()
|
||||
{
|
||||
base.OnDisappearing();
|
||||
|
||||
var color = Color.FromRgb(Red, Green, Blue);
|
||||
Preferences.Set(Definition.PrimaryColorKey, color.ToHex());
|
||||
Light.Instance.RefreshColor(color);
|
||||
var color = PrimaryColor;
|
||||
Preferences.Set(Definition.PrimaryColorKey, color);
|
||||
Light.Instance.RefreshColor(Color.FromHex(color));
|
||||
}
|
||||
|
||||
private async void OnCategoryCommand()
|
||||
{
|
||||
if (Tap.IsBusy)
|
||||
{
|
||||
return;
|
||||
}
|
||||
using (Tap.Start())
|
||||
{
|
||||
var page = new CategoryPage();
|
||||
await Navigation.PushAsync(page);
|
||||
}
|
||||
}
|
||||
|
||||
private void ColorPicker_ColorChanged(object sender, Color e)
|
||||
{
|
||||
PrimaryColor = Helper.WrapColorString(e.ToHex());
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user