commit 11f4666b8ed2bfe43502ec868898cae7570c44e5 Author: Tsanie Lily Date: Wed Feb 23 16:09:24 2022 +0800 initial diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..58d4acc --- /dev/null +++ b/.gitignore @@ -0,0 +1,405 @@ +# globs +Makefile.in +*.userprefs +*.usertasks +config.make +config.status +aclocal.m4 +install-sh +autom4te.cache/ +*.tar.gz +tarballs/ +test-results/ + +# Mac bundle stuff +*.dmg +*.app + +# content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +# content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore +# Windows thumbnail cache files +Thumbs.db +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# content below from: https://github.com/github/gitignore/blob/master/VisualStudio.gitignore +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ \ No newline at end of file diff --git a/Billing.Shared/App.cs b/Billing.Shared/App.cs new file mode 100644 index 0000000..5cc2213 --- /dev/null +++ b/Billing.Shared/App.cs @@ -0,0 +1,61 @@ +using Billing.Languages; +using Billing.Themes; +using Xamarin.Essentials; +using Xamarin.Forms; + +namespace Billing; + +public class App : Application +{ + public static AppTheme CurrentTheme { get; private set; } + public static PlatformCulture CurrentCulture { get; private set; } + + public App() + { + CurrentCulture = new PlatformCulture(); + InitResources(); + + MainPage = new MainShell(); + Shell.Current.GoToAsync("//Bills"); + } + + protected override void OnStart() + { + } + + protected override void OnResume() + { + SetTheme(AppInfo.RequestedTheme); + } + + private void InitResources() + { + var theme = AppInfo.RequestedTheme; + SetTheme(theme, true); + } + + private void SetTheme(AppTheme theme, bool force = false) + { + if (force || theme != CurrentTheme) + { + CurrentTheme = theme; + } + else + { + return; + } + Helper.Debug($"application theme: {theme}"); + + BaseTheme instance; + if (theme == AppTheme.Dark) + { + instance = Dark.Instance; + } + else + { + instance = Light.Instance; + } + // TODO: status bar + Resources = instance; + } +} \ No newline at end of file diff --git a/Billing.Shared/AssemblyInfo.cs b/Billing.Shared/AssemblyInfo.cs new file mode 100644 index 0000000..5bcb232 --- /dev/null +++ b/Billing.Shared/AssemblyInfo.cs @@ -0,0 +1,3 @@ +using Xamarin.Forms.Xaml; + +[assembly: XamlCompilation(XamlCompilationOptions.Compile)] \ No newline at end of file diff --git a/Billing.Shared/Billing.Shared.projitems b/Billing.Shared/Billing.Shared.projitems new file mode 100644 index 0000000..55ce733 --- /dev/null +++ b/Billing.Shared/Billing.Shared.projitems @@ -0,0 +1,73 @@ + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + true + 6ac75d01-70d6-4a07-8685-bc52afd97a7a + + + Billing + + + + + + + + + + + + + MainShell.xaml + + + + + + BillingDate.xaml + + + + + + + AccountPage.xaml + + + AddBillPage.xaml + + + BillPage.xaml + + + SettingPage.xaml + + + + + Designer + MSBuild:UpdateDesignTimeXaml + + + Designer + MSBuild:UpdateDesignTimeXaml + + + Designer + MSBuild:UpdateDesignTimeXaml + + + Designer + MSBuild:UpdateDesignTimeXaml + + + Designer + MSBuild:UpdateDesignTimeXaml + + + Designer + MSBuild:UpdateDesignTimeXaml + + + \ No newline at end of file diff --git a/Billing.Shared/Billing.Shared.shproj b/Billing.Shared/Billing.Shared.shproj new file mode 100644 index 0000000..1182863 --- /dev/null +++ b/Billing.Shared/Billing.Shared.shproj @@ -0,0 +1,13 @@ + + + + 6ac75d01-70d6-4a07-8685-bc52afd97a7a + 14.0 + + + + + + + + diff --git a/Billing.Shared/Helper.cs b/Billing.Shared/Helper.cs new file mode 100644 index 0000000..e0b69b1 --- /dev/null +++ b/Billing.Shared/Helper.cs @@ -0,0 +1,23 @@ +using System; + +namespace Billing; + +internal static class Helper +{ + public static void Debug(string message) + { + 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); + } +} diff --git a/Billing.Shared/Languages/PlatformCulture.cs b/Billing.Shared/Languages/PlatformCulture.cs new file mode 100644 index 0000000..b8fc688 --- /dev/null +++ b/Billing.Shared/Languages/PlatformCulture.cs @@ -0,0 +1,61 @@ +namespace Billing.Languages +{ + public partial class PlatformCulture + { + public string PlatformString { get; set; } + public string LanguageCode { get; set; } + public string LocaleCode { get; set; } + + public string Language => string.IsNullOrEmpty(LocaleCode) ? LanguageCode : LanguageCode + "-" + LocaleCode; + + public partial string GetNamespace(); + public partial void Init(); + + public PlatformCulture() + { + Init(); + } + + private void Init(string cultureString) + { + if (string.IsNullOrEmpty(cultureString)) + { + cultureString = "zh-CN"; + } + + PlatformString = cultureString.Replace('_', '-'); + if (PlatformString.Contains('-')) + { + var parts = PlatformString.Split('-'); + LanguageCode = parts[0]; + LocaleCode = parts[^1]; + } + else + { + LanguageCode = PlatformString; + LocaleCode = string.Empty; + } + } + + private string ToDotnetFallbackLanguage() + { + string netLanguage = LanguageCode switch + { + // fallback to Portuguese (Portugal) + "pt" => "pt-PT", + // equivalent to German (Switzerland) for this app + "gsw" => "de-CH", + + // add more application-specific cases here (if required) + // ONLY use cultures that have been tested and known to work + + // use the first part of the identifier (two chars, usually); + _ => LanguageCode, + }; + Helper.Debug($".NET Fallback Language/Locale: {LanguageCode} to {netLanguage} (application-specific)"); + return netLanguage; + } + + public override string ToString() => PlatformString; + } +} diff --git a/Billing.Shared/Languages/Resource.cs b/Billing.Shared/Languages/Resource.cs new file mode 100644 index 0000000..5a59b23 --- /dev/null +++ b/Billing.Shared/Languages/Resource.cs @@ -0,0 +1,95 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Xml; +using Xamarin.Forms; +using Xamarin.Forms.Xaml; + +namespace Billing.Languages +{ + internal class Resource + { + public static string TitleDateFormat => Text(nameof(TitleDateFormat)); + + static readonly Dictionary dict = new(); + + public static string Text(string name, params object[] args) + { + string current = App.CurrentCulture.PlatformString; + if (!dict.TryGetValue(current, out LanguageResource lang)) + { + lang = new LanguageResource(App.CurrentCulture); + dict.Add(current, lang); + } + if (args == null || args.Length == 0) + { + return lang[name]; + } + return string.Format(lang[name], args); + } + + private class LanguageResource + { + private readonly Dictionary strings; + + public string this[string key] + { + get + { + if (strings?.TryGetValue(key, out string val) == true) + { + return val; + } + return key; + } + } + + public LanguageResource(PlatformCulture lang) + { + try + { + var ns = lang.GetNamespace(); + var langId = $"{ns}.Languages.{lang.Language}.xml"; + var langCodeId = $"{ns}.Languages.{lang.LanguageCode}.xml"; + var assembly = typeof(LanguageResource).Assembly; + var name = assembly.GetManifestResourceNames().FirstOrDefault(n => + string.Equals(n, langId, StringComparison.OrdinalIgnoreCase) || + string.Equals(n, langCodeId, StringComparison.OrdinalIgnoreCase)); + if (name == null) + { + name = $"{ns}.Languages.zh-CN.xml"; + } + var xml = new XmlDocument(); + using (var stream = assembly.GetManifestResourceStream(name)) + { + xml.Load(stream); + } + strings = new Dictionary(); + foreach (XmlElement ele in xml.DocumentElement) + { + strings[ele.Name] = ele.InnerText; + } + } + catch (Exception ex) + { + Helper.Error("language.ctor", $"failed to load json resource: {lang}, {ex.Message}"); + } + } + } + } + + [ContentProperty(nameof(Text))] + public class TextExtension : IMarkupExtension + { + public string Text { get; set; } + + public object ProvideValue(IServiceProvider serviceProvider) + { + if (Text == null) + { + return string.Empty; + } + return Resource.Text(Text); + } + } +} diff --git a/Billing.Shared/Languages/en.xml b/Billing.Shared/Languages/en.xml new file mode 100644 index 0000000..260deee --- /dev/null +++ b/Billing.Shared/Languages/en.xml @@ -0,0 +1,16 @@ + + + Accounts + Bills + Settings + Su + Mo + Tu + We + Th + Fr + Sa + Bills not yet generated + Click here to record + MM/dd/yyyy + \ No newline at end of file diff --git a/Billing.Shared/Languages/zh-CN.xml b/Billing.Shared/Languages/zh-CN.xml new file mode 100644 index 0000000..21fda81 --- /dev/null +++ b/Billing.Shared/Languages/zh-CN.xml @@ -0,0 +1,16 @@ + + + 账户 + 账单 + 设置 + 周日 + 周一 + 周二 + 周三 + 周四 + 周五 + 周六 + 还未产生账单 + 点此记录 + yyyy年MM月dd日 + \ No newline at end of file diff --git a/Billing.Shared/MainShell.xaml b/Billing.Shared/MainShell.xaml new file mode 100644 index 0000000..46bb476 --- /dev/null +++ b/Billing.Shared/MainShell.xaml @@ -0,0 +1,18 @@ + + + + + + + + + + \ No newline at end of file diff --git a/Billing.Shared/MainShell.xaml.cs b/Billing.Shared/MainShell.xaml.cs new file mode 100644 index 0000000..220a4d7 --- /dev/null +++ b/Billing.Shared/MainShell.xaml.cs @@ -0,0 +1,11 @@ +using Xamarin.Forms; + +namespace Billing; + +public partial class MainShell : Shell +{ + public MainShell() + { + InitializeComponent(); + } +} diff --git a/Billing.Shared/Themes/BaseTheme.cs b/Billing.Shared/Themes/BaseTheme.cs new file mode 100644 index 0000000..ccf8bc5 --- /dev/null +++ b/Billing.Shared/Themes/BaseTheme.cs @@ -0,0 +1,65 @@ +using Billing.UI; +using Xamarin.Forms; + +namespace Billing.Themes; + +public abstract class BaseTheme : ResourceDictionary +{ + public const string CascadiaFontRegular = nameof(CascadiaFontRegular); + public const string CascadiaFontBold = nameof(CascadiaFontBold); + public const string RobotoCondensedFontRegular = nameof(RobotoCondensedFontRegular); + public const string RobotoCondensedFontBold = nameof(RobotoCondensedFontBold); + public const string WindowBackgroundColor = nameof(WindowBackgroundColor); + public const string PromptBackgroundColor = nameof(PromptBackgroundColor); + public const string PrimaryColor = nameof(PrimaryColor); + public const string SecondaryColor = nameof(SecondaryColor); + public const string TabBarBackgroundColor = nameof(TabBarBackgroundColor); + public const string TabBarTitleColor = nameof(TabBarTitleColor); + public const string TabBarUnselectedColor = nameof(TabBarUnselectedColor); + public const string OutRangeDayColor = nameof(OutRangeDayColor); + public const string TextColor = nameof(TextColor); + public const string WeekendColor = nameof(WeekendColor); + + protected abstract Color PrimaryMauiColor { get; } + protected abstract Color SecondaryMauiColor { get; } + + protected void InitResources() + { + var robotoRegularFontFamily = Definition.GetRobotoCondensedRegularFontFamily(); + Add(CascadiaFontRegular, Definition.GetCascadiaRegularFontFamily()); + Add(CascadiaFontBold, Definition.GetCascadiaBoldFontFamily()); + Add(RobotoCondensedFontRegular, Definition.GetRobotoCondensedRegularFontFamily()); + Add(RobotoCondensedFontBold, Definition.GetRobotoCondensedBoldFontFamily()); + + Add(PrimaryColor, PrimaryMauiColor); + Add(SecondaryColor, SecondaryMauiColor); + Add(TabBarTitleColor, PrimaryMauiColor); + + Add(new Style(typeof(Label)) + { + Setters = + { + new Setter { Property = Label.FontSizeProperty, Value = Device.GetNamedSize(NamedSize.Small, typeof(Label)) }, + new Setter { Property = Label.TextColorProperty, Value = PrimaryMauiColor }, + new Setter { Property = Label.FontFamilyProperty, Value = robotoRegularFontFamily } + } + }); + Add(new Style(typeof(Button)) + { + Setters = + { + new Setter { Property = Button.TextColorProperty, Value = SecondaryMauiColor }, + new Setter { Property = Button.FontFamilyProperty, Value = robotoRegularFontFamily }, + new Setter { Property = VisualElement.BackgroundColorProperty, Value = PrimaryMauiColor }, + new Setter { Property = Button.PaddingProperty, Value = new Thickness(14, 10) } + } + }); + Add(new Style(typeof(TintImage)) + { + Setters = + { + new Setter { Property = TintImage.PrimaryColorProperty, Value = PrimaryMauiColor } + } + }); + } +} diff --git a/Billing.Shared/Themes/Dark.cs b/Billing.Shared/Themes/Dark.cs new file mode 100644 index 0000000..4e9f1d4 --- /dev/null +++ b/Billing.Shared/Themes/Dark.cs @@ -0,0 +1,40 @@ +using Xamarin.Forms; + +namespace Billing.Themes; + +public class Dark : BaseTheme +{ + private static Dark _instance; + + public static Dark Instance => _instance ??= new Dark(); + + protected override Color PrimaryMauiColor => Color.White; + protected override Color SecondaryMauiColor => Color.LightGray; + + public Dark() + { + InitColors(); + InitResources(); + } + + private void InitColors() + { + Add(WindowBackgroundColor, Color.Black); + Add(PromptBackgroundColor, Color.FromRgb(0x1f, 0x1f, 0x1f)); + Add(TabBarBackgroundColor, Color.Black); + Add(TabBarUnselectedColor, Color.FromRgb(0x82, 0x82, 0x82)); + Add(OutRangeDayColor, Color.DarkGray); + Add(TextColor, Color.FromRgb(0xcc, 0xcc, 0xcc)); + Add(WeekendColor, Color.FromRgb(211, 5, 5)); + + Add(new Style(typeof(TabBar)) + { + Setters = + { + new Setter { Property = Shell.TabBarBackgroundColorProperty, Value = Color.Black }, + new Setter { Property = Shell.TabBarTitleColorProperty, Value = PrimaryMauiColor }, + new Setter { Property = Shell.TabBarUnselectedColorProperty, Value = Color.FromRgb(0x82, 0x82, 0x82) } + } + }); + } +} diff --git a/Billing.Shared/Themes/Light.cs b/Billing.Shared/Themes/Light.cs new file mode 100644 index 0000000..4684a0e --- /dev/null +++ b/Billing.Shared/Themes/Light.cs @@ -0,0 +1,40 @@ +using Xamarin.Forms; + +namespace Billing.Themes; + +public class Light : BaseTheme +{ + private static Light _instance; + + public static Light Instance => _instance ??= new Light(); + + protected override Color PrimaryMauiColor => Color.FromRgb(0x18, 0x31, 0x53); + protected override Color SecondaryMauiColor => Color.White; + + public Light() + { + InitColors(); + InitResources(); + } + + private void InitColors() + { + Add(WindowBackgroundColor, Color.White); + Add(PromptBackgroundColor, Color.FromRgb(0xe0, 0xe0, 0xe0)); + Add(TabBarBackgroundColor, Color.White); + Add(TabBarUnselectedColor, Color.FromRgb(0x82, 0x82, 0x82)); + Add(OutRangeDayColor, Color.LightGray); + Add(TextColor, Color.FromRgb(0x33, 0x33, 0x33)); + Add(WeekendColor, Color.FromRgb(211, 64, 85)); + + Add(new Style(typeof(TabBar)) + { + Setters = + { + new Setter { Property = Shell.TabBarBackgroundColorProperty, Value = Color.White }, + new Setter { Property = Shell.TabBarTitleColorProperty, Value = PrimaryMauiColor }, + new Setter { Property = Shell.TabBarUnselectedColorProperty, Value = Color.FromRgb(0x82, 0x82, 0x82) } + } + }); + } +} diff --git a/Billing.Shared/UI/BillingDate.xaml b/Billing.Shared/UI/BillingDate.xaml new file mode 100644 index 0000000..4136f90 --- /dev/null +++ b/Billing.Shared/UI/BillingDate.xaml @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Billing.Shared/UI/BillingDate.xaml.cs b/Billing.Shared/UI/BillingDate.xaml.cs new file mode 100644 index 0000000..0424e8f --- /dev/null +++ b/Billing.Shared/UI/BillingDate.xaml.cs @@ -0,0 +1,290 @@ +using Billing.Themes; +using System; +using Xamarin.Forms; + +namespace Billing.UI; + +public partial class BillingDate : ContentView +{ + #region UI Properties + + private static readonly BindableProperty SundayProperty = BindableProperty.Create(nameof(Sunday), typeof(BillingDay), typeof(BillingDate)); + private static readonly BindableProperty MondayProperty = BindableProperty.Create(nameof(Monday), typeof(BillingDay), typeof(BillingDate)); + private static readonly BindableProperty TuesdayProperty = BindableProperty.Create(nameof(Tuesday), typeof(BillingDay), typeof(BillingDate)); + private static readonly BindableProperty WednesdayProperty = BindableProperty.Create(nameof(Wednesday), typeof(BillingDay), typeof(BillingDate)); + private static readonly BindableProperty ThursdayProperty = BindableProperty.Create(nameof(Thursday), typeof(BillingDay), typeof(BillingDate)); + private static readonly BindableProperty FridayProperty = BindableProperty.Create(nameof(Friday), typeof(BillingDay), typeof(BillingDate)); + private static readonly BindableProperty SaturdayProperty = BindableProperty.Create(nameof(Saturday), typeof(BillingDay), typeof(BillingDate)); + + public BillingDay Sunday => (BillingDay)GetValue(SundayProperty); + public BillingDay Monday => (BillingDay)GetValue(MondayProperty); + public BillingDay Tuesday => (BillingDay)GetValue(TuesdayProperty); + public BillingDay Wednesday => (BillingDay)GetValue(WednesdayProperty); + public BillingDay Thursday => (BillingDay)GetValue(ThursdayProperty); + public BillingDay Friday => (BillingDay)GetValue(FridayProperty); + public BillingDay Saturday => (BillingDay)GetValue(SaturdayProperty); + + #endregion + + private static BindableProperty GetWeekProperty(int week) + { + return (DayOfWeek)week switch + { + DayOfWeek.Monday => MondayProperty, + DayOfWeek.Tuesday => TuesdayProperty, + DayOfWeek.Wednesday => WednesdayProperty, + DayOfWeek.Thursday => ThursdayProperty, + DayOfWeek.Friday => FridayProperty, + DayOfWeek.Saturday => SaturdayProperty, + _ => SundayProperty + }; + } + + public static readonly BindableProperty LocatedDateProperty = BindableProperty.Create(nameof(LocatedDate), typeof(DateTime), typeof(BillingDate), propertyChanged: OnLocatedDatePropertyChanged); + public static readonly BindableProperty SelectedDateProperty = BindableProperty.Create(nameof(SelectedDate), typeof(DateTime), typeof(BillingDate), propertyChanged: OnSelectedDatePropertyChanged); + + private static void OnLocatedDatePropertyChanged(BindableObject obj, object old, object @new) + { + if (obj is BillingDate billingDate && @new is DateTime date) + { + var week = (int)date.DayOfWeek; + var tmpDate = date.AddDays(-week); + for (var i = 0; i < 7; i++) + { + var prop = GetWeekProperty(i); + var day = new BillingDay(tmpDate); + billingDate.SetValue(prop, day); + tmpDate = tmpDate.AddDays(1); + } + } + } + + private static void OnSelectedDatePropertyChanged(BindableObject obj, object old, object @new) + { + if (obj is BillingDate billingDate && @new is DateTime selected) + { + for (var i = 0; i < 7; i++) + { + var prop = GetWeekProperty(i); + var day = (BillingDay)billingDate.GetValue(prop); + day.Refresh(selected); + } + } + } + + public DateTime LocatedDate + { + get => (DateTime)GetValue(LocatedDateProperty); + set => SetValue(LocatedDateProperty, value); + } + public DateTime SelectedDate + { + get => (DateTime)GetValue(SelectedDateProperty); + set => SetValue(SelectedDateProperty, value); + } + + public Command OnDayTapped { get; } + + public event EventHandler DateSelected; + + private DateTime lastDate; + private double? x1; + private double x2; + + public BillingDate() + { + OnDayTapped = new Command(OnDayChanged); + + InitializeComponent(); + } + + public void SetDateTime(DateTime selectedDate, DateTime? locatedDate = null) + { + if (locatedDate != null) + { + LocatedDate = locatedDate.Value; + } + else + { + LocatedDate = selectedDate; + } + SelectedDate = selectedDate; + DateSelected?.Invoke(this, new DateEventArgs(selectedDate)); + } + + private void OnDayChanged(object o) + { + var selected = SelectedDate; + if (o is BillingDay day && (selected.Year != day.Date.Year || selected.DayOfYear != day.Date.DayOfYear)) + { + for (var i = 0; i < 7; i++) + { + var d = (BillingDay)GetValue(GetWeekProperty(i)); + if (d.IsSelected) + { + this.AbortAnimation("unselected"); + this.Animate("unselected", v => + { + d.Opacity = v; + }, + start: 1, end: 0, + easing: Easing.CubicOut, + finished: (v, b) => + { + d.Opacity = 0; + d.IsSelected = false; + }); + } + } + SelectedDate = day.Date; + this.AbortAnimation("selected"); + this.Animate("selected", v => + { + day.Opacity = v; + }, + start: 0, end: 1, + easing: Easing.CubicOut, + finished: (v, b) => + { + day.Opacity = 1; + }); + DateSelected?.Invoke(this, new DateEventArgs(day.Date)); + } + } + + private void OnPanUpdated(object sender, PanUpdatedEventArgs e) + { + if (e.StatusType == GestureStatus.Started) + { + lastDate = DateTime.Now; + x1 = null; + } + else if (e.StatusType == GestureStatus.Running) + { + if (x1 == null) + { + x1 = e.TotalX; + } + x2 = e.TotalX; + } + else if (e.StatusType == GestureStatus.Completed) + { + if (x1 == null) + { + return; + } + var totalX = x2 - x1.Value; + x1 = null; + var ms = (DateTime.Now - lastDate).TotalMilliseconds; + var speed = totalX / ms; + Helper.Debug($"completed, speed: {speed}"); + if (speed < -0.7) + { + LocatedDate = LocatedDate.AddDays(7); + } + else if (speed > 0.7) + { + LocatedDate = LocatedDate.AddDays(-7); + } + OnSelectedDatePropertyChanged(this, null, SelectedDate); + } + } +} + +public class DateEventArgs : EventArgs +{ + public DateTime Date { get; } + + public DateEventArgs(DateTime date) + { + Date = date; + } +} + +public class BillingDayView : ContentView +{ + public static readonly BindableProperty BillingDayProperty = BindableProperty.Create(nameof(BillingDay), typeof(BillingDay), typeof(BillingDayView)); + public static readonly BindableProperty CommandProperty = BindableProperty.Create(nameof(Command), typeof(Command), typeof(BillingDayView)); + + public BillingDay BillingDay + { + get => (BillingDay)GetValue(BillingDayProperty); + set => SetValue(BillingDayProperty, value); + } + public Command Command + { + get => (Command)GetValue(CommandProperty); + set => SetValue(CommandProperty, value); + } +} + +public class BillingDay : BindableObject +{ + private static readonly BindableProperty DateProperty = BindableProperty.Create(nameof(Date), typeof(DateTime), typeof(BillingDay), propertyChanged: OnDatePropertyChanged); + private static readonly BindableProperty TextProperty = BindableProperty.Create(nameof(Text), typeof(string), typeof(BillingDay)); + private static readonly BindableProperty FontFamilyProperty = BindableProperty.Create(nameof(FontFamily), typeof(string), typeof(BillingDay), defaultValue: Definition.GetCascadiaRegularFontFamily()); + private static readonly BindableProperty IsSelectedProperty = BindableProperty.Create(nameof(IsSelected), typeof(bool), typeof(BillingDay)); + private static readonly BindableProperty OpacityProperty = BindableProperty.Create(nameof(Opacity), typeof(double), typeof(BillingDay), defaultValue: 1.0); + private static readonly BindableProperty TextOpacityProperty = BindableProperty.Create(nameof(TextOpacity), typeof(double), typeof(BillingDay), defaultValue: 1.0); + + private static void OnDatePropertyChanged(BindableObject obj, object old, object @new) + { + if (obj is BillingDay day && @new is DateTime date) + { + if (date.Day == 1) + { + day.SetValue(TextProperty, date.ToString("MMM")); + } + else + { + day.SetValue(TextProperty, date.Day.ToString()); + } + } + } + + public DateTime Date + { + get => (DateTime)GetValue(DateProperty); + set => SetValue(DateProperty, value); + } + public string Text => (string)GetValue(TextProperty); + public string FontFamily => (string)GetValue(FontFamilyProperty); + public bool IsSelected + { + get => (bool)GetValue(IsSelectedProperty); + set => SetValue(IsSelectedProperty, value); + } + public double Opacity + { + get => (double)GetValue(OpacityProperty); + set => SetValue(OpacityProperty, value); + } + public double TextOpacity => (double)GetValue(TextOpacityProperty); + + public BillingDay(DateTime date) + { + Date = date; + } + + public void Refresh(DateTime selected) + { + var date = Date; + if (date.Year == selected.Year && date.DayOfYear == selected.DayOfYear) + { + SetValue(IsSelectedProperty, true); + SetValue(FontFamilyProperty, Definition.GetCascadiaBoldFontFamily()); + } + else + { + SetValue(FontFamilyProperty, Definition.GetCascadiaRegularFontFamily()); + } + if (date.Year == selected.Year && date.Month == selected.Month) + { + SetValue(TextOpacityProperty, 1.0); + } + else + { + SetValue(TextOpacityProperty, .4); + } + } +} diff --git a/Billing.Shared/UI/BillingPage.cs b/Billing.Shared/UI/BillingPage.cs new file mode 100644 index 0000000..0ebcdd7 --- /dev/null +++ b/Billing.Shared/UI/BillingPage.cs @@ -0,0 +1,12 @@ +using Billing.Themes; +using Xamarin.Forms; + +namespace Billing.UI; + +public abstract class BillingPage : ContentPage +{ + public BillingPage() + { + SetDynamicResource(BackgroundColorProperty, BaseTheme.WindowBackgroundColor); + } +} diff --git a/Billing.Shared/UI/Converters.cs b/Billing.Shared/UI/Converters.cs new file mode 100644 index 0000000..efb28ea --- /dev/null +++ b/Billing.Shared/UI/Converters.cs @@ -0,0 +1,23 @@ +using Billing.Languages; +using System; +using System.Globalization; +using Xamarin.Forms; + +namespace Billing.UI; + +public class TitleDateConverter : IValueConverter +{ + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value is DateTime date) + { + return date.ToString(Resource.TitleDateFormat); + } + return value; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + return value; + } +} diff --git a/Billing.Shared/UI/CustomControl.cs b/Billing.Shared/UI/CustomControl.cs new file mode 100644 index 0000000..496e596 --- /dev/null +++ b/Billing.Shared/UI/CustomControl.cs @@ -0,0 +1,31 @@ +using System; +using Xamarin.Forms; + +namespace Billing.UI; + +public class TintImage : Image +{ + public static readonly BindableProperty PrimaryColorProperty = BindableProperty.Create(nameof(PrimaryColor), typeof(Color?), typeof(TintImage)); + + public Color? PrimaryColor + { + get => (Color?)GetValue(PrimaryColorProperty); + set => SetValue(PrimaryColorProperty, value); + } +} + +public class LongPressButton : Button +{ + public event EventHandler LongPressed; + + public LongPressButton() + { + Padding = 0; + BackgroundColor = Color.Transparent; + } + + public void TriggerLongPress() + { + LongPressed?.Invoke(this, EventArgs.Empty); + } +} diff --git a/Billing.Shared/UI/Definition.cs b/Billing.Shared/UI/Definition.cs new file mode 100644 index 0000000..63b5571 --- /dev/null +++ b/Billing.Shared/UI/Definition.cs @@ -0,0 +1,9 @@ +namespace Billing.UI; + +public static partial class Definition +{ + public static partial string GetCascadiaRegularFontFamily(); + public static partial string GetCascadiaBoldFontFamily(); + public static partial string GetRobotoCondensedRegularFontFamily(); + public static partial string GetRobotoCondensedBoldFontFamily(); +} diff --git a/Billing.Shared/Views/AccountPage.xaml b/Billing.Shared/Views/AccountPage.xaml new file mode 100644 index 0000000..3c37d1c --- /dev/null +++ b/Billing.Shared/Views/AccountPage.xaml @@ -0,0 +1,13 @@ + + + + + \ No newline at end of file diff --git a/Billing.Shared/Views/AccountPage.xaml.cs b/Billing.Shared/Views/AccountPage.xaml.cs new file mode 100644 index 0000000..6711f02 --- /dev/null +++ b/Billing.Shared/Views/AccountPage.xaml.cs @@ -0,0 +1,11 @@ +using Billing.UI; + +namespace Billing.Views; + +public partial class AccountPage : BillingPage +{ + public AccountPage() + { + InitializeComponent(); + } +} diff --git a/Billing.Shared/Views/AddBillPage.xaml b/Billing.Shared/Views/AddBillPage.xaml new file mode 100644 index 0000000..b612592 --- /dev/null +++ b/Billing.Shared/Views/AddBillPage.xaml @@ -0,0 +1,14 @@ + + + + + + \ No newline at end of file diff --git a/Billing.Shared/Views/AddBillPage.xaml.cs b/Billing.Shared/Views/AddBillPage.xaml.cs new file mode 100644 index 0000000..436ceb2 --- /dev/null +++ b/Billing.Shared/Views/AddBillPage.xaml.cs @@ -0,0 +1,11 @@ +using Billing.UI; + +namespace Billing.Views; + +public partial class AddBillPage : BillingPage +{ + public AddBillPage() + { + InitializeComponent(); + } +} diff --git a/Billing.Shared/Views/BillPage.xaml b/Billing.Shared/Views/BillPage.xaml new file mode 100644 index 0000000..8bd0c66 --- /dev/null +++ b/Billing.Shared/Views/BillPage.xaml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Billing.Shared/Views/BillPage.xaml.cs b/Billing.Shared/Views/BillPage.xaml.cs new file mode 100644 index 0000000..b2d80e2 --- /dev/null +++ b/Billing.Shared/Views/BillPage.xaml.cs @@ -0,0 +1,44 @@ +using Billing.UI; +using System; +using Xamarin.Forms; + +namespace Billing.Views; + +public partial class BillPage : BillingPage +{ + private static readonly BindableProperty SelectedDateProperty = BindableProperty.Create(nameof(SelectedDate), typeof(DateTime), typeof(BillPage)); + + public DateTime SelectedDate + { + get => (DateTime)GetValue(SelectedDateProperty); + set => SetValue(SelectedDateProperty, value); + } + + public Command AddBilling { get; } + + public BillPage() + { + AddBilling = new Command(OnAddBilling); + + InitializeComponent(); + + billingDate.SetDateTime(DateTime.Now); + } + + private void OnDateSelected(object sender, DateEventArgs e) + { + SelectedDate = e.Date; + + // TODO: while selecting date + } + + private void OnTitleDateLongPressed(object sender, EventArgs e) + { + billingDate.SetDateTime(DateTime.Now); + } + + private async void OnAddBilling() + { + await Navigation.PushAsync(new AddBillPage()); + } +} diff --git a/Billing.Shared/Views/SettingPage.xaml b/Billing.Shared/Views/SettingPage.xaml new file mode 100644 index 0000000..9387f6e --- /dev/null +++ b/Billing.Shared/Views/SettingPage.xaml @@ -0,0 +1,13 @@ + + + + + \ No newline at end of file diff --git a/Billing.Shared/Views/SettingPage.xaml.cs b/Billing.Shared/Views/SettingPage.xaml.cs new file mode 100644 index 0000000..b1d085a --- /dev/null +++ b/Billing.Shared/Views/SettingPage.xaml.cs @@ -0,0 +1,11 @@ +using Billing.UI; + +namespace Billing.Views; + +public partial class SettingPage : BillingPage +{ + public SettingPage() + { + InitializeComponent(); + } +} diff --git a/Billing.sln b/Billing.sln new file mode 100644 index 0000000..ea1e6c2 --- /dev/null +++ b/Billing.sln @@ -0,0 +1,70 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.2.32210.308 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Billing.Android", "Billing\Billing.Android\Billing.Android.csproj", "{B4CD3B27-C58F-4B6B-B60E-35E515A73E5B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Billing.iOS", "Billing\Billing.iOS\Billing.iOS.csproj", "{5C4F1C35-6F66-4063-9605-A9F37FCABBA8}" +EndProject +Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Billing.Shared", "Billing.Shared\Billing.Shared.shproj", "{6AC75D01-70D6-4A07-8685-BC52AFD97A7A}" +EndProject +Global + GlobalSection(SharedMSBuildProjectFiles) = preSolution + Billing.Shared\Billing.Shared.projitems*{5c4f1c35-6f66-4063-9605-a9f37fcabba8}*SharedItemsImports = 4 + Billing.Shared\Billing.Shared.projitems*{6ac75d01-70d6-4a07-8685-bc52afd97a7a}*SharedItemsImports = 13 + Billing.Shared\Billing.Shared.projitems*{b4cd3b27-c58f-4b6b-b60e-35e515a73e5b}*SharedItemsImports = 4 + EndGlobalSection + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|iPhone = Debug|iPhone + Debug|iPhoneSimulator = Debug|iPhoneSimulator + Release|Any CPU = Release|Any CPU + Release|iPhone = Release|iPhone + Release|iPhoneSimulator = Release|iPhoneSimulator + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {B4CD3B27-C58F-4B6B-B60E-35E515A73E5B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B4CD3B27-C58F-4B6B-B60E-35E515A73E5B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B4CD3B27-C58F-4B6B-B60E-35E515A73E5B}.Debug|Any CPU.Deploy.0 = Debug|Any CPU + {B4CD3B27-C58F-4B6B-B60E-35E515A73E5B}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {B4CD3B27-C58F-4B6B-B60E-35E515A73E5B}.Debug|iPhone.Build.0 = Debug|Any CPU + {B4CD3B27-C58F-4B6B-B60E-35E515A73E5B}.Debug|iPhone.Deploy.0 = Debug|Any CPU + {B4CD3B27-C58F-4B6B-B60E-35E515A73E5B}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {B4CD3B27-C58F-4B6B-B60E-35E515A73E5B}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {B4CD3B27-C58F-4B6B-B60E-35E515A73E5B}.Debug|iPhoneSimulator.Deploy.0 = Debug|Any CPU + {B4CD3B27-C58F-4B6B-B60E-35E515A73E5B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B4CD3B27-C58F-4B6B-B60E-35E515A73E5B}.Release|Any CPU.Build.0 = Release|Any CPU + {B4CD3B27-C58F-4B6B-B60E-35E515A73E5B}.Release|Any CPU.Deploy.0 = Release|Any CPU + {B4CD3B27-C58F-4B6B-B60E-35E515A73E5B}.Release|iPhone.ActiveCfg = Release|Any CPU + {B4CD3B27-C58F-4B6B-B60E-35E515A73E5B}.Release|iPhone.Build.0 = Release|Any CPU + {B4CD3B27-C58F-4B6B-B60E-35E515A73E5B}.Release|iPhone.Deploy.0 = Release|Any CPU + {B4CD3B27-C58F-4B6B-B60E-35E515A73E5B}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {B4CD3B27-C58F-4B6B-B60E-35E515A73E5B}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {B4CD3B27-C58F-4B6B-B60E-35E515A73E5B}.Release|iPhoneSimulator.Deploy.0 = Release|Any CPU + {5C4F1C35-6F66-4063-9605-A9F37FCABBA8}.Debug|Any CPU.ActiveCfg = Debug|iPhone + {5C4F1C35-6F66-4063-9605-A9F37FCABBA8}.Debug|Any CPU.Build.0 = Debug|iPhone + {5C4F1C35-6F66-4063-9605-A9F37FCABBA8}.Debug|Any CPU.Deploy.0 = Debug|iPhone + {5C4F1C35-6F66-4063-9605-A9F37FCABBA8}.Debug|iPhone.ActiveCfg = Debug|iPhone + {5C4F1C35-6F66-4063-9605-A9F37FCABBA8}.Debug|iPhone.Build.0 = Debug|iPhone + {5C4F1C35-6F66-4063-9605-A9F37FCABBA8}.Debug|iPhone.Deploy.0 = Debug|iPhone + {5C4F1C35-6F66-4063-9605-A9F37FCABBA8}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator + {5C4F1C35-6F66-4063-9605-A9F37FCABBA8}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator + {5C4F1C35-6F66-4063-9605-A9F37FCABBA8}.Debug|iPhoneSimulator.Deploy.0 = Debug|iPhoneSimulator + {5C4F1C35-6F66-4063-9605-A9F37FCABBA8}.Release|Any CPU.ActiveCfg = Release|iPhoneSimulator + {5C4F1C35-6F66-4063-9605-A9F37FCABBA8}.Release|Any CPU.Build.0 = Release|iPhoneSimulator + {5C4F1C35-6F66-4063-9605-A9F37FCABBA8}.Release|Any CPU.Deploy.0 = Release|iPhoneSimulator + {5C4F1C35-6F66-4063-9605-A9F37FCABBA8}.Release|iPhone.ActiveCfg = Release|iPhone + {5C4F1C35-6F66-4063-9605-A9F37FCABBA8}.Release|iPhone.Build.0 = Release|iPhone + {5C4F1C35-6F66-4063-9605-A9F37FCABBA8}.Release|iPhone.Deploy.0 = Release|iPhone + {5C4F1C35-6F66-4063-9605-A9F37FCABBA8}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator + {5C4F1C35-6F66-4063-9605-A9F37FCABBA8}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator + {5C4F1C35-6F66-4063-9605-A9F37FCABBA8}.Release|iPhoneSimulator.Deploy.0 = Release|iPhoneSimulator + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {6C946C90-CF6E-4464-920A-CD31FF045FC8} + EndGlobalSection +EndGlobal diff --git a/Billing/Billing.Android/Assets/AboutAssets.txt b/Billing/Billing.Android/Assets/AboutAssets.txt new file mode 100644 index 0000000..072563f --- /dev/null +++ b/Billing/Billing.Android/Assets/AboutAssets.txt @@ -0,0 +1,19 @@ +Any raw assets you want to be deployed with your application can be placed in +this directory (and child directories) and given a Build Action of "AndroidAsset". + +These files will be deployed with your package and will be accessible using Android's +AssetManager, like this: + +public class ReadAsset : Activity +{ + protected override void OnCreate (Bundle bundle) + { + base.OnCreate (bundle); + + InputStream input = Assets.Open ("my_asset.txt"); + } +} + +Additionally, some Android functions will automatically load asset files: + +Typeface tf = Typeface.CreateFromAsset (Context.Assets, "fonts/samplefont.ttf"); diff --git a/Billing/Billing.Android/Assets/CascadiaCode-Bold.ttf b/Billing/Billing.Android/Assets/CascadiaCode-Bold.ttf new file mode 100644 index 0000000..72183ac Binary files /dev/null and b/Billing/Billing.Android/Assets/CascadiaCode-Bold.ttf differ diff --git a/Billing/Billing.Android/Assets/CascadiaCode-Regular.ttf b/Billing/Billing.Android/Assets/CascadiaCode-Regular.ttf new file mode 100644 index 0000000..70a54fa Binary files /dev/null and b/Billing/Billing.Android/Assets/CascadiaCode-Regular.ttf differ diff --git a/Billing/Billing.Android/Assets/RobotoCondensed-Bold.ttf b/Billing/Billing.Android/Assets/RobotoCondensed-Bold.ttf new file mode 100644 index 0000000..c807004 Binary files /dev/null and b/Billing/Billing.Android/Assets/RobotoCondensed-Bold.ttf differ diff --git a/Billing/Billing.Android/Assets/RobotoCondensed-Regular.ttf b/Billing/Billing.Android/Assets/RobotoCondensed-Regular.ttf new file mode 100644 index 0000000..17e8ea5 Binary files /dev/null and b/Billing/Billing.Android/Assets/RobotoCondensed-Regular.ttf differ diff --git a/Billing/Billing.Android/Billing.Android.csproj b/Billing/Billing.Android/Billing.Android.csproj new file mode 100644 index 0000000..b021c21 --- /dev/null +++ b/Billing/Billing.Android/Billing.Android.csproj @@ -0,0 +1,192 @@ + + + + Debug + AnyCPU + {B4CD3B27-C58F-4B6B-B60E-35E515A73E5B} + {EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + {6968b3a4-1835-46a3-ac5c-1ae33b475983} + Library + Billing.Droid + Billing.Android + True + True + Resources\Resource.designer.cs + Resource + Properties\AndroidManifest.xml + Resources + Assets + false + v12.0 + true + true + Xamarin.Android.Net.AndroidClientHandler + + + 10.0 + + + true + portable + false + bin\Debug + DEBUG; + prompt + 4 + None + false + false + false + false + x86_64 + + + true + portable + true + bin\Release + prompt + 4 + true + false + false + false + false + false + arm64-v8a + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Billing/Billing.Android/Definition.cs b/Billing/Billing.Android/Definition.cs new file mode 100644 index 0000000..461ceb6 --- /dev/null +++ b/Billing/Billing.Android/Definition.cs @@ -0,0 +1,9 @@ +namespace Billing.UI; + +public static partial class Definition +{ + public static partial string GetCascadiaRegularFontFamily() => "CascadiaCode-Regular.ttf#CascadiaCode-Regular"; + public static partial string GetCascadiaBoldFontFamily() => "CascadiaCode-Bold.ttf#CascadiaCode-Bold"; + public static partial string GetRobotoCondensedRegularFontFamily() => "RobotoCondensed-Regular.ttf#RobotoCondensed-Regular"; + public static partial string GetRobotoCondensedBoldFontFamily() => "RobotoCondensed-Bold.ttf#RobotoCondensed-Bold"; +} diff --git a/Billing/Billing.Android/MainActivity.cs b/Billing/Billing.Android/MainActivity.cs new file mode 100644 index 0000000..5380d04 --- /dev/null +++ b/Billing/Billing.Android/MainActivity.cs @@ -0,0 +1,31 @@ +using Android.App; +using Android.Content.PM; +using Android.Runtime; +using Android.OS; + +namespace Billing.Droid; + +[Activity( + Label = "Billing", + Icon = "@mipmap/icon", + RoundIcon = "@mipmap/icon_round", + Theme = "@style/MainTheme", + MainLauncher = true, + ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize )] +public class MainActivity : Xamarin.Forms.Platform.Android.FormsAppCompatActivity +{ + protected override void OnCreate(Bundle savedInstanceState) + { + base.OnCreate(savedInstanceState); + + Xamarin.Essentials.Platform.Init(this, savedInstanceState); + Xamarin.Forms.Forms.Init(this, savedInstanceState); + LoadApplication(new App()); + } + public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Android.Content.PM.Permission[] grantResults) + { + Xamarin.Essentials.Platform.OnRequestPermissionsResult(requestCode, permissions, grantResults); + + base.OnRequestPermissionsResult(requestCode, permissions, grantResults); + } +} diff --git a/Billing/Billing.Android/PlatformCulture.cs b/Billing/Billing.Android/PlatformCulture.cs new file mode 100644 index 0000000..f9dffa6 --- /dev/null +++ b/Billing/Billing.Android/PlatformCulture.cs @@ -0,0 +1,66 @@ +using Billing.Droid; +using System.Globalization; +using System.Threading; + +namespace Billing.Languages; + +public partial class PlatformCulture +{ + public partial string GetNamespace() + { + return typeof(MainActivity).Namespace; + } + + public partial void Init() + { + var locale = Java.Util.Locale.Default; + string lang = AndroidToDotnetLanguage($"{locale.Language}-{locale.Country}"); + + CultureInfo ci; + Init(lang); + try + { + ci = new CultureInfo(Language); + } + catch (CultureNotFoundException e) + { + try + { + var fallback = ToDotnetFallbackLanguage(); + Helper.Debug($"{lang} failed, trying {fallback} ({e.Message})"); + ci = new CultureInfo(fallback); + } + catch (CultureNotFoundException e1) + { + Helper.Error("culture.get", $"{lang} couldn't be set, using 'zh-CN' ({e1.Message})"); + ci = new CultureInfo("zh-CN"); + } + } + + Thread.CurrentThread.CurrentCulture = ci; + Thread.CurrentThread.CurrentUICulture = ci; + + Helper.Debug($"CurrentCulture set: {ci.Name}"); + } + + private static string AndroidToDotnetLanguage(string androidLanguage) + { + //certain languages need to be converted to CultureInfo equivalent + string netLanguage = androidLanguage switch + { + // Not supported .NET culture + "ms-BN" or "ms-MY" or "ms-SG" => "ms", // closest supported + // "Indonesian (Indonesia)" has different code in .NET + "in-ID" => "id-ID", // correct code for .NET + // "Schwiizertüütsch (Swiss German)" not supported .NET culture + "gsw-CH" => "de-CH", // closest supported + + // add more application-specific cases here (if required) + // ONLY use cultures that have been tested and known to work + + _ => androidLanguage, + }; + Helper.Debug($"Android Language: {androidLanguage}, .NET Language/Locale: {netLanguage}"); + return netLanguage; + } +} diff --git a/Billing/Billing.Android/Properties/AndroidManifest.xml b/Billing/Billing.Android/Properties/AndroidManifest.xml new file mode 100644 index 0000000..e8284ec --- /dev/null +++ b/Billing/Billing.Android/Properties/AndroidManifest.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Billing/Billing.Android/Properties/AssemblyInfo.cs b/Billing/Billing.Android/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..f42b84f --- /dev/null +++ b/Billing/Billing.Android/Properties/AssemblyInfo.cs @@ -0,0 +1,29 @@ +using System.Reflection; +using System.Runtime.InteropServices; +using Android.App; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Billing.Android")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Billing.Android")] +[assembly: AssemblyCopyright("Copyright © 2014")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: ComVisible(false)] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] + +// Add some common permissions, these can be removed if not needed +[assembly: UsesPermission(Android.Manifest.Permission.Internet)] +[assembly: UsesPermission(Android.Manifest.Permission.WriteExternalStorage)] diff --git a/Billing/Billing.Android/Renderers/LongPressButtonRenderer.cs b/Billing/Billing.Android/Renderers/LongPressButtonRenderer.cs new file mode 100644 index 0000000..c1b76ad --- /dev/null +++ b/Billing/Billing.Android/Renderers/LongPressButtonRenderer.cs @@ -0,0 +1,33 @@ +using Android.Content; +using Billing.Droid.Renderers; +using Billing.UI; +using Xamarin.Forms; +using Xamarin.Forms.Platform.Android; + +[assembly: ExportRenderer(typeof(LongPressButton), typeof(LongPressButtonRenderer))] +namespace Billing.Droid.Renderers; + +public class LongPressButtonRenderer : ButtonRenderer +{ + public LongPressButtonRenderer(Context context) : base(context) + { + } + + protected override void OnElementChanged(ElementChangedEventArgs