Compare commits

...

2 Commits

Author SHA1 Message Date
fde8931dbd complete basic report page 2022-03-09 17:26:56 +08:00
abffc0627e complete ranking page 2022-03-09 16:21:03 +08:00
15 changed files with 703 additions and 88 deletions

View File

@ -77,6 +77,7 @@
<Compile Include="$(MSBuildThisFileDirectory)UI\OptionsCells.cs" /> <Compile Include="$(MSBuildThisFileDirectory)UI\OptionsCells.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Store\StoreHelper.cs" /> <Compile Include="$(MSBuildThisFileDirectory)Store\StoreHelper.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Models\Bill.cs" /> <Compile Include="$(MSBuildThisFileDirectory)Models\Bill.cs" />
<Compile Include="$(MSBuildThisFileDirectory)UI\SegmentedControl.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<EmbeddedResource Include="$(MSBuildThisFileDirectory)MainShell.xaml"> <EmbeddedResource Include="$(MSBuildThisFileDirectory)MainShell.xaml">

View File

@ -92,4 +92,5 @@
<TrackingChart>Tracking Chart</TrackingChart> <TrackingChart>Tracking Chart</TrackingChart>
<NoResult>(no results)</NoResult> <NoResult>(no results)</NoResult>
<Top10>Top 10</Top10> <Top10>Top 10</Top10>
<CategoryRank>Category Ranking</CategoryRank>
</root> </root>

View File

@ -92,4 +92,5 @@
<TrackingChart>跟踪图表</TrackingChart> <TrackingChart>跟踪图表</TrackingChart>
<NoResult>(无记录)</NoResult> <NoResult>(无记录)</NoResult>
<Top10>Top 10</Top10> <Top10>Top 10</Top10>
<CategoryRank>分类排行</CategoryRank>
</root> </root>

View File

@ -111,10 +111,16 @@ namespace Billing.UI
public class TimeConverter : IValueConverter public class TimeConverter : IValueConverter
{ {
public bool IncludeDate { get; set; }
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{ {
if (value is DateTime time) if (value is DateTime time)
{ {
if (IncludeDate)
{
return time.ToString("MM-dd HH:mm");
}
return time.ToString("HH:mm"); return time.ToString("HH:mm");
} }
return string.Empty; return string.Empty;

View File

@ -62,4 +62,6 @@ namespace Billing.UI
} }
} }
} }
public class BlurryPanel : ContentView { }
} }

View File

@ -0,0 +1,70 @@
using System;
using System.Collections.Generic;
using Xamarin.Forms;
namespace Billing.UI
{
public class SegmentedControl : View, IViewContainer<SegmentedControlOption>
{
public IList<SegmentedControlOption> Children { get; set; }
public SegmentedControl()
{
Children = new List<SegmentedControlOption>();
}
public static readonly BindableProperty TintColorProperty = Helper.Create<Color, SegmentedControl>(nameof(TintColor));
public static readonly BindableProperty DisabledColorProperty = Helper.Create<Color, SegmentedControl>(nameof(DisabledColor));
public static readonly BindableProperty SelectedTextColorProperty = Helper.Create<Color, SegmentedControl>(nameof(SelectedTextColor));
public static readonly BindableProperty SelectedSegmentIndexProperty = Helper.Create<int, SegmentedControl>(nameof(SelectedSegmentIndex));
public Color TintColor
{
get => (Color)GetValue(TintColorProperty);
set => SetValue(TintColorProperty, value);
}
public Color DisabledColor
{
get => (Color)GetValue(DisabledColorProperty);
set => SetValue(DisabledColorProperty, value);
}
public Color SelectedTextColor
{
get => (Color)GetValue(SelectedTextColorProperty);
set => SetValue(SelectedTextColorProperty, value);
}
public int SelectedSegmentIndex
{
get => (int)GetValue(SelectedSegmentIndexProperty);
set => SetValue(SelectedSegmentIndexProperty, value);
}
public SegmentedControlOption SelectedSegment => Children[SelectedSegmentIndex];
public event EventHandler<ValueChangedEventArgs> ValueChanged;
//[EditorBrowsable(EditorBrowsableState.Never)]
public void SendValueChanged()
{
ValueChanged?.Invoke(this, new ValueChangedEventArgs { NewValue = SelectedSegmentIndex });
}
}
public class SegmentedControlOption : View
{
public static readonly BindableProperty TextProperty = Helper.Create<string, SegmentedControlOption>(nameof(Text));
public string Text
{
get => (string)GetValue(TextProperty);
set => SetValue(TextProperty, value);
}
public object Value { get; set; }
}
public class ValueChangedEventArgs : EventArgs
{
public int NewValue { get; set; }
}
}

View File

@ -41,8 +41,9 @@
<ContentPage.Resources> <ContentPage.Resources>
<ui:NegativeConverter x:Key="negativeConverter"/> <ui:NegativeConverter x:Key="negativeConverter"/>
<ui:MoneyConverter x:Key="moneyConverter" Absolute="True"/> <ui:MoneyConverter x:Key="moneyConverter" Absolute="True"/>
<ui:MoneyConverter x:Key="moneyRawConverter"/>
<ui:BalanceColorConverter x:Key="colorConverter"/> <ui:BalanceColorConverter x:Key="colorConverter"/>
<ui:TimeConverter x:Key="timeConverter"/> <ui:TimeConverter x:Key="timeConverter" IncludeDate="True"/>
<ui:IconConverter x:Key="iconConverter"/> <ui:IconConverter x:Key="iconConverter"/>
<Style x:Key="titleLabel" TargetType="Label"> <Style x:Key="titleLabel" TargetType="Label">
<Setter Property="FontSize" Value="16"/> <Setter Property="FontSize" Value="16"/>
@ -59,22 +60,53 @@
</Style> </Style>
</ContentPage.Resources> </ContentPage.Resources>
<Grid>
<ScrollView x:Name="scroller"> <ScrollView x:Name="scroller">
<StackLayout> <StackLayout>
<Grid Margin="0, 10, 0, 0" Padding="8" ColumnSpacing="8" ColumnDefinitions="*, Auto" HeightRequest="24"
BackgroundColor="{DynamicResource PromptBackgroundColor}">
<StackLayout Grid.Column="1" Orientation="Horizontal" Spacing="6">
<Label Text="{r:Text Income}" TextColor="{DynamicResource GreenColor}"
VerticalOptions="Center" FontSize="12"/>
<Label Text="{Binding Income, Converter={StaticResource moneyConverter}}"
TextColor="{DynamicResource TextColor}"
VerticalOptions="Center" FontSize="12"/>
<Label Text="{r:Text Spending}" TextColor="{DynamicResource RedColor}"
VerticalOptions="Center" FontSize="12" Margin="10, 0, 0, 0"/>
<Label Text="{Binding Spending, Converter={StaticResource moneyConverter}}"
TextColor="{DynamicResource TextColor}"
VerticalOptions="Center" FontSize="12"/>
<Label Text="{r:Text Balance}"
VerticalOptions="Center" FontSize="12" Margin="10, 0, 0, 0"/>
<Label Text="{Binding Balance, Converter={StaticResource moneyRawConverter}}"
TextColor="{DynamicResource TextColor}"
VerticalOptions="Center" FontSize="12"/>
</StackLayout>
</Grid>
<Label Text="{r:Text TrackingChart}" Style="{StaticResource titleLabel}"/> <Label Text="{r:Text TrackingChart}" Style="{StaticResource titleLabel}"/>
<chart:ChartView HeightRequest="240" Chart="{Binding Chart}" <chart:ChartView HeightRequest="240" Chart="{Binding Chart}"
IsVisible="{Binding NoResult, Converter={StaticResource negativeConverter}}"/> IsVisible="{Binding NoResultChart, Converter={StaticResource negativeConverter}}"/>
<Label Text="{r:Text NoResult}" Style="{StaticResource promptLabel}" <Label Text="{r:Text NoResult}" Style="{StaticResource promptLabel}"
IsVisible="{Binding NoResult}"/> IsVisible="{Binding NoResultChart}"/>
<Label Text="{r:Text CategoryRank}" Style="{StaticResource titleLabel}"/>
<chart:ChartView HeightRequest="240" Chart="{Binding CategoryChart}"
IsVisible="{Binding NoResultCategoryChart, Converter={StaticResource negativeConverter}}"/>
<Label Text="{r:Text NoResult}" Style="{StaticResource promptLabel}"
IsVisible="{Binding NoResultCategoryChart}"/>
<Label Text="{r:Text Top10}" Style="{StaticResource titleLabel}"/> <Label Text="{r:Text Top10}" Style="{StaticResource titleLabel}"/>
<ui:GroupStackLayout IsVisible="{Binding NoResult, Converter={StaticResource negativeConverter}}" <ui:GroupStackLayout IsVisible="{Binding NoResultTopBills, Converter={StaticResource negativeConverter}}"
ItemsSource="{Binding TopBills}" RowHeight="50"> ItemsSource="{Binding TopBills}" RowHeight="50">
<ui:GroupStackLayout.ItemTemplate> <ui:GroupStackLayout.ItemTemplate>
<DataTemplate x:DataType="v:UIBill"> <DataTemplate x:DataType="v:UIBill">
<Grid Padding="20, 0" ColumnSpacing="10" <Grid Padding="20, 0" ColumnSpacing="10"
ColumnDefinitions="Auto, *, Auto" RowDefinitions="Auto, Auto"> ColumnDefinitions="Auto, *, Auto" RowDefinitions="Auto, Auto">
<Grid.GestureRecognizers>
<TapGestureRecognizer Command="{Binding EditBilling, Source={x:Reference rankPage}}"
CommandParameter="{Binding .}"/>
</Grid.GestureRecognizers>
<ui:TintImage Source="{Binding Icon, Converter={StaticResource iconConverter}}" <ui:TintImage Source="{Binding Icon, Converter={StaticResource iconConverter}}"
WidthRequest="26" HeightRequest="20" VerticalOptions="Center"/> WidthRequest="26" HeightRequest="20" VerticalOptions="Center"/>
<Label Grid.Column="1" Text="{Binding Name}" TextColor="{DynamicResource TextColor}" <Label Grid.Column="1" Text="{Binding Name}" TextColor="{DynamicResource TextColor}"
@ -94,7 +126,23 @@
</ui:GroupStackLayout.ItemTemplate> </ui:GroupStackLayout.ItemTemplate>
</ui:GroupStackLayout> </ui:GroupStackLayout>
<Label Text="{r:Text NoResult}" Style="{StaticResource promptLabel}" <Label Text="{r:Text NoResult}" Style="{StaticResource promptLabel}"
IsVisible="{Binding NoResult}"/> IsVisible="{Binding NoResultTopBills}"/>
</StackLayout> </StackLayout>
</ScrollView> </ScrollView>
<ui:BlurryPanel x:Name="panelFilter" VerticalOptions="Start" Opacity="0"
BackgroundColor="{DynamicResource WindowBackgroundColor}"
HeightRequest="{Binding Height, Source={x:Reference gridFilter}}"/>
<Grid x:Name="gridFilter" VerticalOptions="Start" Opacity="0" Padding="10">
<ui:SegmentedControl Margin="6, 6, 6, 3" VerticalOptions="Center"
SelectedSegmentIndex="{Binding SegmentType, Mode=TwoWay}"
SelectedTextColor="{DynamicResource TextColor}"
TintColor="{DynamicResource PromptBackgroundColor}">
<ui:SegmentedControl.Children>
<ui:SegmentedControlOption Text="{r:Text Spending}"/>
<ui:SegmentedControlOption Text="{r:Text Income}"/>
</ui:SegmentedControl.Children>
</ui:SegmentedControl>
</Grid>
</Grid>
</ui:BillingPage> </ui:BillingPage>

View File

@ -16,89 +16,228 @@ namespace Billing.Views
{ {
public partial class RankPage : BillingPage public partial class RankPage : BillingPage
{ {
private static readonly BindableProperty SegmentTypeProperty = Helper.Create<int, RankPage>(nameof(SegmentType), defaultValue: 0, propertyChanged: OnSegmentTypeChanged);
private static readonly BindableProperty ChartProperty = Helper.Create<Chart, RankPage>(nameof(Chart)); private static readonly BindableProperty ChartProperty = Helper.Create<Chart, RankPage>(nameof(Chart));
private static readonly BindableProperty CategoryChartProperty = Helper.Create<Chart, RankPage>(nameof(CategoryChart));
private static readonly BindableProperty TopBillsProperty = Helper.Create<IList<UIBill>, RankPage>(nameof(TopBills)); private static readonly BindableProperty TopBillsProperty = Helper.Create<IList<UIBill>, RankPage>(nameof(TopBills));
private static readonly BindableProperty NoResultProperty = Helper.Create<bool, RankPage>(nameof(NoResult)); private static readonly BindableProperty NoResultChartProperty = Helper.Create<bool, RankPage>(nameof(NoResultChart));
private static readonly BindableProperty NoResultCategoryChartProperty = Helper.Create<bool, RankPage>(nameof(NoResultCategoryChart));
private static readonly BindableProperty NoResultTopBillsProperty = Helper.Create<bool, RankPage>(nameof(NoResultTopBills));
private static readonly BindableProperty IncomeProperty = Helper.Create<decimal, RankPage>(nameof(Income));
private static readonly BindableProperty SpendingProperty = Helper.Create<decimal, RankPage>(nameof(Spending));
private static readonly BindableProperty BalanceProperty = Helper.Create<decimal, RankPage>(nameof(Balance));
private static void OnSegmentTypeChanged(RankPage page, int old, int @new)
{
page.type = @new switch
{
1 => CategoryType.Income,
_ => CategoryType.Spending
};
page.OnFilterCommand(false);
page.SetMonth(page.current);
}
public int SegmentType
{
get => (int)GetValue(SegmentTypeProperty);
set => SetValue(SegmentTypeProperty, value);
}
public Chart Chart public Chart Chart
{ {
get => (Chart)GetValue(ChartProperty); get => (Chart)GetValue(ChartProperty);
set => SetValue(ChartProperty, value); set => SetValue(ChartProperty, value);
} }
public Chart CategoryChart
{
get => (Chart)GetValue(CategoryChartProperty);
set => SetValue(CategoryChartProperty, value);
}
public IList<UIBill> TopBills public IList<UIBill> TopBills
{ {
get => (IList<UIBill>)GetValue(TopBillsProperty); get => (IList<UIBill>)GetValue(TopBillsProperty);
set => SetValue(TopBillsProperty, value); set => SetValue(TopBillsProperty, value);
} }
public bool NoResult public bool NoResultChart
{ {
get => (bool)GetValue(NoResultProperty); get => (bool)GetValue(NoResultChartProperty);
set => SetValue(NoResultProperty, value); set => SetValue(NoResultChartProperty, value);
} }
public bool NoResultCategoryChart
{
get => (bool)GetValue(NoResultCategoryChartProperty);
set => SetValue(NoResultCategoryChartProperty, value);
}
public bool NoResultTopBills
{
get => (bool)GetValue(NoResultTopBillsProperty);
set => SetValue(NoResultTopBillsProperty, value);
}
public decimal Income => (decimal)GetValue(IncomeProperty);
public decimal Spending => (decimal)GetValue(SpendingProperty);
public decimal Balance => (decimal)GetValue(BalanceProperty);
public Command LeftCommand { get; } public Command LeftCommand { get; }
public Command RightCommand { get; } public Command RightCommand { get; }
public Command FilterCommand { get; } public Command FilterCommand { get; }
public Command EditBilling { get; }
private DateTime current; private DateTime current;
private DateTime end;
private IEnumerable<Bill> bills; private IEnumerable<Bill> bills;
private CategoryType type = CategoryType.Spending; private CategoryType type = CategoryType.Spending;
private bool isFilterToggled;
private readonly SKTypeface font;
public RankPage() public RankPage()
{ {
LeftCommand = new Command(OnLeftCommand); LeftCommand = new Command(OnLeftCommand);
RightCommand = new Command(OnRightCommand); RightCommand = new Command(OnRightCommand);
FilterCommand = new Command(OnFilterCommand);
EditBilling = new Command(OnEditBilling);
var style = SKFontManager.Default.GetFontStyles("PingFang SC");
if (style != null)
{
font = style.CreateTypeface(SKFontStyle.Normal);
}
InitializeComponent(); InitializeComponent();
gridFilter.TranslationY = -60;
panelFilter.TranslationY = -60;
} }
public override void OnLoaded() public override void OnLoaded()
{ {
Task.Run(() => SetMonth(DateTime.Today)); SetMonth(DateTime.Today);
} }
private void OnLeftCommand() private void OnLeftCommand()
{ {
Task.Run(() => if (scroller.ScrollY > 0)
{ {
MainThread.BeginInvokeOnMainThread(async () => await scroller.ScrollToAsync(0, 0, true)); scroller.ScrollToAsync(0, 0, true);
}
SetMonth(current.AddMonths(-1)); SetMonth(current.AddMonths(-1));
});
} }
private void OnRightCommand() private void OnRightCommand()
{ {
Task.Run(() => if (scroller.ScrollY > 0)
{ {
MainThread.BeginInvokeOnMainThread(async () => await scroller.ScrollToAsync(0, 0, true)); scroller.ScrollToAsync(0, 0, true);
}
SetMonth(current.AddMonths(1)); SetMonth(current.AddMonths(1));
});
} }
private void SetMonth(DateTime date) private async void OnFilterCommand(object o)
{
if (o is bool flag)
{
isFilterToggled = flag;
}
else
{
isFilterToggled = !isFilterToggled;
}
ViewExtensions.CancelAnimations(gridFilter);
ViewExtensions.CancelAnimations(panelFilter);
if (isFilterToggled)
{
await Task.WhenAll(
gridFilter.TranslateTo(0, 0, easing: Easing.CubicOut),
gridFilter.FadeTo(1, easing: Easing.CubicOut),
panelFilter.TranslateTo(0, 0, easing: Easing.CubicOut),
panelFilter.FadeTo(1, easing: Easing.CubicOut));
}
else
{
await Task.WhenAll(
gridFilter.TranslateTo(0, -60, easing: Easing.CubicIn),
gridFilter.FadeTo(0, easing: Easing.CubicIn),
panelFilter.TranslateTo(0, -60, easing: Easing.CubicIn),
panelFilter.FadeTo(0, easing: Easing.CubicIn));
}
}
private async void OnEditBilling(object o)
{
if (Tap.IsBusy)
{
return;
}
using (Tap.Start())
{
if (o is UIBill bill)
{
var page = new AddBillPage(bill.Bill);
page.BillChecked += OnBillChecked;
await Navigation.PushAsync(page);
}
}
}
private void UpdateBill(UIBill bill)
{
bill.Icon = App.Categories.FirstOrDefault(c => c.Id == bill.Bill.CategoryId)?.Icon ?? BaseModel.ICON_DEFAULT;
bill.Name = bill.Bill.Name;
bill.DateCreation = bill.Bill.CreateTime;
bill.Amount = bill.Bill.Amount;
bill.Wallet = App.Accounts.FirstOrDefault(a => a.Id == bill.Bill.WalletId)?.Name;
}
private async void RefreshBalance()
{
var bills = await Task.Run(() => App.Bills.Where(b => b.CreateTime >= current && b.CreateTime <= end));
var income = bills.Where(b => b.Amount > 0).Sum(b => b.Amount);
var spending = -bills.Where(b => b.Amount < 0).Sum(b => b.Amount);
SetValue(IncomeProperty, income);
SetValue(SpendingProperty, spending);
SetValue(BalanceProperty, income - spending);
}
private void OnBillChecked(object sender, Bill e)
{
var bill = TopBills.FirstOrDefault(b => b.Bill == e);
if (bill != null)
{
UpdateBill(bill);
}
RefreshBalance();
Task.Run(App.WriteBills);
}
private async void SetMonth(DateTime date)
{ {
current = date.AddDays(1 - date.Day); current = date.AddDays(1 - date.Day);
var end = current.AddDays(DateTime.DaysInMonth(current.Year, current.Month)); end = current.AddDays(DateTime.DaysInMonth(current.Year, current.Month));
var format = Resource.DateRangeFormat; var format = Resource.DateRangeFormat;
Title = current.ToString(format) + " ~ " + end.AddDays(-1).ToString(format); Title = current.ToString(format) + " ~ " + end.AddDays(-1).ToString(format);
bills = App.Bills.Where(b => b.CreateTime >= current && b.CreateTime <= end); var spending = type == CategoryType.Spending;
bills = await Task.Run(() => App.Bills.Where(b => (b.Amount > 0 ^ spending) && b.CreateTime >= current && b.CreateTime <= end));
var entries = new List<ChartEntry>();
var primaryColor = BaseTheme.CurrentPrimaryColor.ToSKColor(); var primaryColor = BaseTheme.CurrentPrimaryColor.ToSKColor();
var textColor = BaseTheme.CurrentSecondaryTextColor.ToSKColor(); var textColor = BaseTheme.CurrentSecondaryTextColor.ToSKColor();
_ = Task.Run(() => LoadReportChart(primaryColor, textColor));
_ = Task.Run(() => LoadCategoryChart(primaryColor, textColor));
_ = Task.Run(LoadTopBills);
RefreshBalance();
}
private void LoadReportChart(SKColor primaryColor, SKColor textColor)
{
var entries = new List<ChartEntry>();
for (var day = current; day <= end; day = day.AddDays(1)) for (var day = current; day <= end; day = day.AddDays(1))
{ {
var daybills = bills.Where(b => Helper.IsSameDay(b.CreateTime, day)); var daybills = bills.Where(b => Helper.IsSameDay(b.CreateTime, day));
decimal amount; decimal amount = Math.Abs(daybills.Sum(b => b.Amount));
if (type == CategoryType.Income)
{
amount = daybills.Where(b => b.Amount > 0).Sum(b => b.Amount);
}
else
{
amount = daybills.Where(b => b.Amount < 0).Sum(b => -b.Amount);
}
if (amount > 0) if (amount > 0)
{ {
entries.Add(new((float)amount) entries.Add(new((float)amount)
@ -112,30 +251,104 @@ namespace Billing.Views
} }
} }
MainThread.BeginInvokeOnMainThread(() =>
{
if (entries.Count > 0) if (entries.Count > 0)
{ {
NoResult = false; NoResultChart = false;
Chart = new LineChart Chart = new LineChart
{ {
BackgroundColor = SKColors.Transparent, BackgroundColor = SKColors.Transparent,
LabelTextSize = 24, LabelTextSize = 24,
Entries = entries Entries = entries
}; };
if (type == CategoryType.Income)
{
TopBills = bills.Where(b => b.Amount > 0).OrderByDescending(b => b.Amount).Take(10).Select(b => Helper.WrapBill(b)).ToList();
} }
else else
{ {
TopBills = bills.Where(b => b.Amount < 0).OrderBy(b => b.Amount).Take(10).Select(b => Helper.WrapBill(b)).ToList();
}
}
else
{
NoResult = true;
Chart = null; Chart = null;
TopBills = null; NoResultChart = true;
} }
});
}
private void LoadCategoryChart(SKColor primaryColor, SKColor textColor)
{
var entries = new List<ChartEntry>();
var groups = bills.GroupBy(b => b.CategoryId);
var dict = new Dictionary<string, decimal>();
decimal all = 0m;
foreach (var g in groups)
{
var categoryId = g.Key;
var category = App.Categories.FirstOrDefault(c => c.Id == categoryId);
if (category?.ParentId != null)
{
category = App.Categories.FirstOrDefault(c => c.Id == category.ParentId) ?? category;
}
if (category != null)
{
var total = Math.Abs(g.Sum(g => g.Amount));
all += total;
if (dict.ContainsKey(category.Name))
{
dict[category.Name] += total;
}
else
{
dict.Add(category.Name, total);
}
}
}
foreach (var kv in dict)
{
entries.Add(new((float)kv.Value)
{
Label = kv.Key,
ValueLabel = (kv.Value * 100 / all).ToString("0.#") + "%",
Color = primaryColor,
TextColor = textColor,
ValueLabelColor = textColor
});
}
MainThread.BeginInvokeOnMainThread(() =>
{
if (entries.Count > 0)
{
NoResultCategoryChart = false;
CategoryChart = new RadarChart
{
BackgroundColor = SKColors.Transparent,
LabelTextSize = 30,
Typeface = font,
Entries = entries
};
}
else
{
CategoryChart = null;
NoResultCategoryChart = true;
}
});
}
private void LoadTopBills()
{
List<UIBill> topBills = bills.OrderByDescending(b => Math.Abs(b.Amount)).Take(10).Select(b => Helper.WrapBill(b)).ToList();
MainThread.BeginInvokeOnMainThread(() =>
{
if (topBills.Count > 0)
{
NoResultTopBills = false;
TopBills = topBills;
}
else
{
TopBills = null;
NoResultTopBills = true;
}
});
} }
} }
} }

View File

@ -89,6 +89,7 @@
<Compile Include="SplashActivity.cs" /> <Compile Include="SplashActivity.cs" />
<Compile Include="Renderers\TintImageButtonRenderer.cs" /> <Compile Include="Renderers\TintImageButtonRenderer.cs" />
<Compile Include="Renderers\BillingPageRenderer.cs" /> <Compile Include="Renderers\BillingPageRenderer.cs" />
<Compile Include="Renderers\BlurryPanelRenderer.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<AndroidAsset Include="Assets\fa-brands-400.ttf" /> <AndroidAsset Include="Assets\fa-brands-400.ttf" />

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="7" android:versionName="0.7.308" package="org.tsanie.billing" android:installLocation="auto"> <manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionName="0.8.309" package="org.tsanie.billing" android:installLocation="auto" android:versionCode="8">
<uses-sdk android:minSdkVersion="24" android:targetSdkVersion="31" /> <uses-sdk android:minSdkVersion="24" android:targetSdkVersion="31" />
<application android:label="@string/applabel" android:theme="@style/MainTheme"></application> <application android:label="@string/applabel" android:theme="@style/MainTheme"></application>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

View File

@ -0,0 +1,30 @@
using Android.Content;
using Billing.Droid.Renderers;
using Billing.UI;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
[assembly: ExportRenderer(typeof(BlurryPanel), typeof(BlurryPanelRenderer))]
namespace Billing.Droid.Renderers
{
public class BlurryPanelRenderer : ViewRenderer
{
public BlurryPanelRenderer(Context context) : base(context)
{
}
protected override void OnElementChanged(ElementChangedEventArgs<View> e)
{
base.OnElementChanged(e);
if (e.NewElement != null)
{
var color = e.NewElement.BackgroundColor;
if (!color.IsDefault)
{
SetBackgroundColor(color.MultiplyAlpha(.94).ToAndroid());
}
}
}
}
}

View File

@ -89,6 +89,8 @@
<BundleResource Include="Resources\OpenSans-Regular.ttf" /> <BundleResource Include="Resources\OpenSans-Regular.ttf" />
<Compile Include="Renderers\TintImageButtonRenderer.cs" /> <Compile Include="Renderers\TintImageButtonRenderer.cs" />
<BundleResource Include="Resources\OpenSans-SemiBold.ttf" /> <BundleResource Include="Resources\OpenSans-SemiBold.ttf" />
<Compile Include="Renderers\BlurryPanelRenderer.cs" />
<Compile Include="Renderers\SegmentedControlRenderer.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ImageAsset Include="Assets.xcassets\AppIcon.appiconset\Contents.json"> <ImageAsset Include="Assets.xcassets\AppIcon.appiconset\Contents.json">

View File

@ -44,8 +44,8 @@
<key>UIFileSharingEnabled</key> <key>UIFileSharingEnabled</key>
<true/> <true/>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>7</string> <string>8</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>0.7.308</string> <string>0.8.309</string>
</dict> </dict>
</plist> </plist>

View File

@ -0,0 +1,87 @@
using Billing.iOS.Renderers;
using Billing.UI;
using CoreAnimation;
using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
[assembly: ExportRenderer(typeof(BlurryPanel), typeof(BlurryPanelRenderer))]
namespace Billing.iOS.Renderers
{
public class BlurryPanelRenderer : ViewRenderer<BlurryPanel, UIVisualEffectView>
{
private UIVisualEffectView nativeControl;
private CALayer bottom;
protected override void OnElementChanged(ElementChangedEventArgs<BlurryPanel> e)
{
base.OnElementChanged(e);
if (e.OldElement != null)
{
if (bottom != null)
{
if (bottom.SuperLayer != null)
{
bottom.RemoveFromSuperLayer();
}
bottom.Dispose();
bottom = null;
}
}
if (e.NewElement != null)
{
e.NewElement.BackgroundColor = Color.Default;
if (Control == null)
{
var blur = UIBlurEffect.FromStyle(UIBlurEffectStyle.SystemMaterial);
nativeControl = new UIVisualEffectView(blur)
{
Frame = Frame
};
SetNativeControl(nativeControl);
}
}
}
public override void LayoutSubviews()
{
base.LayoutSubviews();
if (nativeControl != null)
{
if (bottom == null)
{
bottom = new CALayer
{
BackgroundColor = UIColor.White.CGColor,
ShadowColor = UIColor.Black.CGColor,
ShadowOpacity = 1.0f
};
}
if (bottom.SuperLayer == null)
{
nativeControl.Layer.InsertSublayer(bottom, 0);
}
bottom.Frame = new CoreGraphics.CGRect(0, Frame.Height - 5, Frame.Width, 5);
nativeControl.Frame = Frame;
}
}
protected override void Dispose(bool disposing)
{
if (bottom != null)
{
if (bottom.SuperLayer != null)
{
bottom.RemoveFromSuperLayer();
}
bottom.Dispose();
bottom = null;
}
base.Dispose(disposing);
}
}
}

View File

@ -0,0 +1,153 @@
using System;
using System.ComponentModel;
using Billing.iOS.Renderers;
using Billing.UI;
using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
[assembly: ExportRenderer(typeof(SegmentedControl), typeof(SegmentedControlRenderer))]
namespace Billing.iOS.Renderers
{
public class SegmentedControlRenderer : ViewRenderer<SegmentedControl, UISegmentedControl>
{
private UISegmentedControl nativeControl;
protected override void OnElementChanged(ElementChangedEventArgs<SegmentedControl> e)
{
base.OnElementChanged(e);
var element = Element;
if (Control == null && element != null)
{
nativeControl = new UISegmentedControl();
for (var i = 0; i < element.Children.Count; i++)
{
nativeControl.InsertSegment(element.Children[i].Text, i, false);
}
nativeControl.Enabled = element.IsEnabled;
//nativeControl.BackgroundColor = element.BackgroundColor.ToUIColor();
nativeControl.SelectedSegmentTintColor = GetTintColor(element);
SetTextColor();
nativeControl.SelectedSegment = element.SelectedSegmentIndex;
SetNativeControl(nativeControl);
}
if (e.OldElement != null)
{
if (nativeControl != null)
{
nativeControl.ValueChanged -= NativeControl_ValueChanged;
}
}
if (e.NewElement != null)
{
nativeControl.ValueChanged += NativeControl_ValueChanged;
}
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
var element = Element;
if (nativeControl == null || element == null)
{
return;
}
switch (e.PropertyName)
{
case "Renderer":
element.SendValueChanged();
break;
//case nameof(element.BackgroundColor):
// nativeControl.BackgroundColor = element.BackgroundColor.ToUIColor();
// break;
case nameof(element.SelectedSegmentIndex):
nativeControl.SelectedSegment = element.SelectedSegmentIndex;
break;
case nameof(element.TintColor):
nativeControl.SelectedSegmentTintColor = GetTintColor(element);
break;
case nameof(element.IsEnabled):
nativeControl.Enabled = element.IsEnabled;
nativeControl.SelectedSegmentTintColor = GetTintColor(element);
break;
case nameof(element.SelectedTextColor):
SetTextColor();
break;
}
}
private void SetTextColor()
{
//var color = Element.SelectedTextColor;
//UIColor c = color == default ? UIColor.LabelColor : color.ToUIColor();
UIColor c = UIColor.LabelColor;
var attribute = new UITextAttributes
{
TextColor = c
};
nativeControl.SetTitleTextAttributes(attribute, UIControlState.Selected);
attribute = new UITextAttributes
{
TextColor = c.ColorWithAlpha(.6f)
};
nativeControl.SetTitleTextAttributes(attribute, UIControlState.Normal);
}
private UIColor GetTintColor(SegmentedControl element)
{
if (element.IsEnabled)
{
//var tintColor = element.TintColor;
//if (tintColor == default)
//{
return UIColor.SystemGray6Color;
//}
//else
//{
// return tintColor.ToUIColor().ColorWithAlpha(.3f);
//}
}
else
{
//var disabledColor = element.DisabledColor;
//if (disabledColor == default)
//{
return UIColor.SecondaryLabelColor;
//}
//else
//{
// return disabledColor.ToUIColor();
//}
}
}
private void NativeControl_ValueChanged(object sender, EventArgs e)
{
Element.SelectedSegmentIndex = (int)nativeControl.SelectedSegment;
}
protected override void Dispose(bool disposing)
{
if (nativeControl != null)
{
nativeControl.ValueChanged -= NativeControl_ValueChanged;
nativeControl.Dispose();
nativeControl = null;
}
base.Dispose(disposing);
}
}
}