From 60f7824cb5655ca6ee3193e9d78ba82192bb2ba2 Mon Sep 17 00:00:00 2001
From: Tsanie Lily <tsorgy@gmail.com>
Date: Thu, 17 Mar 2022 16:19:18 +0800
Subject: [PATCH] feature: save location

---
 Billing.Shared/App.cs                         | 38 ++++----
 Billing.Shared/Helper.cs                      | 11 +++
 Billing.Shared/Languages/en.xml               |  1 +
 Billing.Shared/Languages/zh-CN.xml            |  1 +
 Billing.Shared/Models/Bill.cs                 |  5 +
 Billing.Shared/Views/AddBillPage.xaml.cs      | 93 ++++++++++++++-----
 Billing.Shared/Views/RankPage.xaml.cs         | 10 +-
 Billing.Shared/Views/SettingPage.xaml         |  2 +
 Billing.Shared/Views/SettingPage.xaml.cs      | 14 ++-
 Billing/Billing.Android/MainActivity.cs       | 78 +++++++++++++++-
 .../Properties/AndroidManifest.xml            |  2 +-
 .../Properties/AssemblyInfo.cs                |  5 +
 Billing/Billing.Android/SplashActivity.cs     |  4 +-
 .../Billing.iOS/Base.lproj/InfoPlist.strings  |  3 +-
 Billing/Billing.iOS/Info.plist                |  6 +-
 .../zh-Hans.lproj/InfoPlist.strings           |  3 +-
 16 files changed, 216 insertions(+), 60 deletions(-)

diff --git a/Billing.Shared/App.cs b/Billing.Shared/App.cs
index d5ff537..dc9cd94 100644
--- a/Billing.Shared/App.cs
+++ b/Billing.Shared/App.cs
@@ -1,5 +1,4 @@
-using System;
-using System.Collections.Generic;
+using System.Collections.Generic;
 using System.IO;
 using System.Threading.Tasks;
 using Billing.Languages;
@@ -14,15 +13,19 @@ namespace Billing
 {
     public class App : Application
     {
+        private const string SaveLocationKey = nameof(SaveLocationKey);
+
         public static AppTheme CurrentTheme { get; private set; }
         public static PlatformCulture CurrentCulture { get; private set; }
         public static List<Bill> Bills => bills ??= new List<Bill>();
         public static List<Account> Accounts => accounts ??= new List<Account>();
         public static List<Category> Categories => categories ??= new List<Category>();
+        public static bool SaveLocation => saveLocation;
 
         private static List<Bill> bills;
         private static List<Account> accounts;
         private static List<Category> categories;
+        private static bool saveLocation;
 
         private string initialUrl;
 
@@ -48,15 +51,6 @@ namespace Billing
             }
         }
 
-        public static async Task InitializeData()
-        {
-            var instance = await StoreHelper.Instance;
-            await Task.WhenAll(
-                Task.Run(async () => accounts = await instance.GetListAsync<Account>()),
-                Task.Run(async () => categories = await instance.GetListAsync<Category>()),
-                Task.Run(async () => bills = await instance.GetListAsync<Bill>()));
-        }
-
         protected override void OnResume()
         {
             SetTheme(AppInfo.RequestedTheme);
@@ -93,6 +87,21 @@ namespace Billing
             Resources = instance;
         }
 
+        public static void SetSaveLocation(bool flag)
+        {
+            saveLocation = flag;
+            Preferences.Set(SaveLocationKey, flag);
+        }
+
+        public static async Task InitializeData()
+        {
+            var instance = await StoreHelper.Instance;
+            await Task.WhenAll(
+                Task.Run(async () => accounts = await instance.GetListAsync<Account>()),
+                Task.Run(async () => categories = await instance.GetListAsync<Category>()),
+                Task.Run(async () => bills = await instance.GetListAsync<Bill>()));
+        }
+
         public static async Task<bool> OpenUrl(string url)
         {
             if (string.IsNullOrEmpty(url))
@@ -101,12 +110,7 @@ namespace Billing
             }
             if (File.Exists(url))
             {
-                var status = await Permissions.CheckStatusAsync<Permissions.StorageRead>();
-                if (status != PermissionStatus.Disabled &&
-                    status != PermissionStatus.Granted)
-                {
-                    status = await Permissions.RequestAsync<Permissions.StorageRead>();
-                }
+                var status = await Helper.CheckAndRequestPermissionAsync<Permissions.StorageRead>();
                 if (status != PermissionStatus.Granted)
                 {
                     return false;
diff --git a/Billing.Shared/Helper.cs b/Billing.Shared/Helper.cs
index c698ef5..69223a3 100644
--- a/Billing.Shared/Helper.cs
+++ b/Billing.Shared/Helper.cs
@@ -136,6 +136,17 @@ namespace Billing
         }
 
         public delegate void PropertyValueChanged<TResult, TOwner>(TOwner obj, TResult old, TResult @new);
+
+        public static async Task<PermissionStatus> CheckAndRequestPermissionAsync<T>() where T : Permissions.BasePermission, new()
+        {
+            var status = await Permissions.CheckStatusAsync<T>();
+            if (status != PermissionStatus.Disabled &&
+                status != PermissionStatus.Granted)
+            {
+                status = await Permissions.RequestAsync<T>();
+            }
+            return status;
+        }
     }
 
     public class AsyncLazy<T>
diff --git a/Billing.Shared/Languages/en.xml b/Billing.Shared/Languages/en.xml
index a604265..dd0f4e9 100644
--- a/Billing.Shared/Languages/en.xml
+++ b/Billing.Shared/Languages/en.xml
@@ -101,6 +101,7 @@
 	<Feature>Feature</Feature>
 	<CategoryManage>Category Management</CategoryManage>
 	<Detail>Detail</Detail>
+	<SaveLocation>Save Location</SaveLocation>
 	<AddCategory>Add Category</AddCategory>
 	<ConfirmDeleteCategory>Are you sure you want to delete the category: {0}?</ConfirmDeleteCategory>
 	<SelectCategory>Select Category</SelectCategory>
diff --git a/Billing.Shared/Languages/zh-CN.xml b/Billing.Shared/Languages/zh-CN.xml
index 79f9426..e25f4b4 100644
--- a/Billing.Shared/Languages/zh-CN.xml
+++ b/Billing.Shared/Languages/zh-CN.xml
@@ -101,6 +101,7 @@
 	<Feature>功能</Feature>
 	<CategoryManage>分类管理</CategoryManage>
 	<Detail>详细</Detail>
+	<SaveLocation>保存位置</SaveLocation>
 	<AddCategory>新建分类</AddCategory>
 	<ConfirmDeleteCategory>是否确认删除该分类:{0}?</ConfirmDeleteCategory>
 	<SelectCategory>选择类别</SelectCategory>
diff --git a/Billing.Shared/Models/Bill.cs b/Billing.Shared/Models/Bill.cs
index bd11615..e04b279 100644
--- a/Billing.Shared/Models/Bill.cs
+++ b/Billing.Shared/Models/Bill.cs
@@ -14,5 +14,10 @@ namespace Billing.Models
         public string Store { get; set; }
         public DateTime CreateTime { get; set; }
         public string Note { get; set; }
+        public double? Latitude { get; set; }
+        public double? Longitude { get; set; }
+        public double? Altitude { get; set; }
+        public double? Accuracy { get; set; }
+        public bool? IsGps { get; set; }
     }
 }
\ No newline at end of file
diff --git a/Billing.Shared/Views/AddBillPage.xaml.cs b/Billing.Shared/Views/AddBillPage.xaml.cs
index d93a088..480ace3 100644
--- a/Billing.Shared/Views/AddBillPage.xaml.cs
+++ b/Billing.Shared/Views/AddBillPage.xaml.cs
@@ -1,7 +1,8 @@
 using System;
 using System.Globalization;
 using System.Linq;
-using Billing.Languages;
+using System.Threading;
+using System.Threading.Tasks;
 using Billing.Models;
 using Billing.Store;
 using Billing.UI;
@@ -13,6 +14,7 @@ namespace Billing.Views
 {
     public partial class AddBillPage : BillingPage
     {
+        private static readonly BindableProperty CheckBillProperty = Helper.Create<Command, AddBillPage>(nameof(CheckBill), defaultValue: new Command(() => { }, () => false));
         private static readonly BindableProperty AmountProperty = Helper.Create<string, AddBillPage>(nameof(Amount));
         private static readonly BindableProperty NameProperty = Helper.Create<string, AddBillPage>(nameof(Name));
         private static readonly BindableProperty CategoryProperty = Helper.Create<Category, AddBillPage>(nameof(Category));
@@ -22,6 +24,7 @@ namespace Billing.Views
         private static readonly BindableProperty CreatedTimeProperty = Helper.Create<TimeSpan, AddBillPage>(nameof(CreatedTime));
         private static readonly BindableProperty NoteProperty = Helper.Create<string, AddBillPage>(nameof(Note));
 
+        public Command CheckBill => (Command)GetValue(CheckBillProperty);
         public string Amount
         {
             get => (string)GetValue(AmountProperty);
@@ -55,7 +58,6 @@ namespace Billing.Views
             set => SetValue(NoteProperty, value);
         }
 
-        public Command CheckBill { get; }
         public Command SelectCategory { get; }
         public Command SelectWallet { get; }
 
@@ -65,11 +67,12 @@ namespace Billing.Views
         private readonly DateTime createDate;
 
         private bool categoryChanged;
+        private CancellationTokenSource tokenSource;
+        private Location location;
 
         public AddBillPage(DateTime date)
         {
             createDate = date;
-            CheckBill = new Command(OnCheckBill);
             SelectCategory = new Command(OnSelectCategory);
             SelectWallet = new Command(OnSelectWallet);
             InitializeComponent();
@@ -81,7 +84,6 @@ namespace Billing.Views
         public AddBillPage(Bill bill)
         {
             this.bill = bill;
-            CheckBill = new Command(OnCheckBill);
             SelectCategory = new Command(OnSelectCategory);
             SelectWallet = new Command(OnSelectWallet);
             InitializeComponent();
@@ -90,6 +92,15 @@ namespace Billing.Views
             Initial();
         }
 
+        protected override void OnDisappearing()
+        {
+            if (tokenSource != null && !tokenSource.IsCancellationRequested)
+            {
+                tokenSource.Cancel();
+            }
+            base.OnDisappearing();
+        }
+
         private void Initial()
         {
             if (bill != null)
@@ -116,6 +127,41 @@ namespace Billing.Views
         protected override void OnLoaded()
         {
             editorAmount.SetFocus();
+
+            if (App.SaveLocation)
+            {
+                _ = GetCurrentLocation();
+            }
+            else
+            {
+                SetValue(CheckBillProperty, new Command(OnCheckBill));
+            }
+        }
+
+        private async Task GetCurrentLocation()
+        {
+            try
+            {
+                var request = new GeolocationRequest(GeolocationAccuracy.Best, TimeSpan.FromSeconds(10));
+                tokenSource = new CancellationTokenSource();
+                var status = await Helper.CheckAndRequestPermissionAsync<Permissions.LocationWhenInUse>();
+                if (status != PermissionStatus.Granted)
+                {
+                    return;
+                }
+                location = await Geolocation.GetLocationAsync(request, tokenSource.Token);
+            }
+            catch (FeatureNotSupportedException) { }
+            catch (FeatureNotEnabledException) { }
+            catch (PermissionException) { }
+            catch (Exception ex)
+            {
+                Helper.Error("location.get", ex);
+            }
+            finally
+            {
+                SetValue(CheckBillProperty, new Command(OnCheckBill));
+            }
         }
 
         private async void OnCheckBill()
@@ -148,26 +194,27 @@ namespace Billing.Views
                 {
                     name = category.Name;
                 }
-                if (bill != null)
-                {
-                    bill.Amount = amount;
-                    bill.Name = name;
-                    bill.CategoryId = category.Id;
-                    bill.WalletId = wallet.Id;
-                    bill.CreateTime = CreatedDate.Date.Add(CreatedTime);
-                    bill.Store = Store;
-                    bill.Note = Note;
+                Bill b = bill;
+                if (b == null)
+                {
+                    b = new Bill();
+                }
+                b.Amount = amount;
+                b.Name = name;
+                b.CategoryId = category.Id;
+                b.WalletId = wallet.Id;
+                b.CreateTime = CreatedDate.Date.Add(CreatedTime);
+                b.Store = Store;
+                b.Note = Note;
+                if (location != null)
+                {
+                    b.Latitude = location.Latitude;
+                    b.Longitude = location.Longitude;
+                    b.Altitude = location.Altitude;
+                    b.Accuracy = location.Accuracy;
+                    b.IsGps = location.IsFromMockProvider;
                 }
-                BillChecked?.Invoke(this, bill ?? new Bill
-                {
-                    Amount = amount,
-                    Name = name,
-                    CategoryId = category.Id,
-                    WalletId = wallet.Id,
-                    CreateTime = CreatedDate.Date.Add(CreatedTime),
-                    Store = Store,
-                    Note = Note
-                });
+                BillChecked?.Invoke(this, b);
 
                 category.LastAccountId = wallet.Id;
                 category.LastUsed = DateTime.Now;
diff --git a/Billing.Shared/Views/RankPage.xaml.cs b/Billing.Shared/Views/RankPage.xaml.cs
index 552f6c2..b7957d6 100644
--- a/Billing.Shared/Views/RankPage.xaml.cs
+++ b/Billing.Shared/Views/RankPage.xaml.cs
@@ -189,13 +189,17 @@ namespace Billing.Views
             LeftCommand = new Command(OnLeftCommand);
             RightCommand = new Command(OnRightCommand);
             FilterCommand = new Command(OnFilterCommand);
-            EditBilling = new Command(OnEditBilling);
-
-            var style = SKFontManager.Default.GetFontStyles("PingFang SC");
+            EditBilling = new Command(OnEditBilling);
+
+#if __IOS__
+            var style = SKFontManager.Default.GetFontStyles("PingFang SC");
             if (style != null)
             {
                 font = style.CreateTypeface(SKFontStyle.Normal);
             }
+            else
+#endif
+                font = SKFontManager.Default.MatchCharacter(0x4e00);
 
             DateTypes = new List<string>
             {
diff --git a/Billing.Shared/Views/SettingPage.xaml b/Billing.Shared/Views/SettingPage.xaml
index da7753e..4aa525e 100644
--- a/Billing.Shared/Views/SettingPage.xaml
+++ b/Billing.Shared/Views/SettingPage.xaml
@@ -24,6 +24,8 @@
             <ui:OptionSelectCell Height="36" Title="{r:Text CategoryManage}"
                                  Detail="{r:Text Detail}"
                                  Command="{Binding CategoryCommand}"/>
+            <ui:OptionSwitchCell Height="36" Title="{r:Text SaveLocation}"
+                                 IsToggled="{Binding SaveLocation, Mode=TwoWay}"/>
         </TableSection>
         <TableSection Title="{r:Text Preference}">
             <ui:OptionEntryCell Height="36" Title="{r:Text PrimaryColor}"
diff --git a/Billing.Shared/Views/SettingPage.xaml.cs b/Billing.Shared/Views/SettingPage.xaml.cs
index e68bf2a..24e63de 100644
--- a/Billing.Shared/Views/SettingPage.xaml.cs
+++ b/Billing.Shared/Views/SettingPage.xaml.cs
@@ -11,10 +11,16 @@ namespace Billing.Views
     public partial class SettingPage : BillingPage
     {
         private static readonly BindableProperty VersionProperty = Helper.Create<string, SettingPage>(nameof(Version));
+        private static readonly BindableProperty SaveLocationProperty = Helper.Create<bool, SettingPage>(nameof(SaveLocation));
         private static readonly BindableProperty PrimaryColorProperty = Helper.Create<string, SettingPage>(nameof(PrimaryColor));
         private static readonly BindableProperty ManyRecordsProperty = Helper.Create<string, SettingPage>(nameof(ManyRecords));
 
         public string Version => (string)GetValue(VersionProperty);
+        public bool SaveLocation
+        {
+            get => (bool)GetValue(SaveLocationProperty);
+            set => SetValue(SaveLocationProperty, value);
+        }
         public string PrimaryColor
         {
             get => (string)GetValue(PrimaryColorProperty);
@@ -35,16 +41,14 @@ namespace Billing.Views
             ShareLogsCommand = new Command(OnShareLogsCommand);
             InitializeComponent();
 
-            var main = AppInfo.VersionString;
-            var build = AppInfo.BuildString;
-            SetValue(VersionProperty, $"{main} ({build})");
+            SetValue(VersionProperty, $"{AppInfo.VersionString} ({AppInfo.BuildString})");
         }
 
         protected override async void OnAppearing()
         {
             base.OnAppearing();
 
-            //SetValue(VersionProperty, $"{AppInfo.VersionString} ({AppInfo.BuildString})");
+            SaveLocation = App.SaveLocation;
             var colorString = Preferences.Get(Definition.PrimaryColorKey, Helper.DEFAULT_COLOR);
             PrimaryColor = Helper.WrapColorString(colorString);
 
@@ -56,8 +60,8 @@ namespace Billing.Views
         {
             base.OnDisappearing();
 
+            App.SetSaveLocation(SaveLocation);
             Preferences.Set(Definition.PrimaryColorKey, PrimaryColor);
-            //Light.Instance.RefreshColor(Color.FromHex(color));
         }
 
         protected override async void OnRefresh()
diff --git a/Billing/Billing.Android/MainActivity.cs b/Billing/Billing.Android/MainActivity.cs
index 51d68ff..edb5074 100644
--- a/Billing/Billing.Android/MainActivity.cs
+++ b/Billing/Billing.Android/MainActivity.cs
@@ -4,6 +4,9 @@ using Android.Content.PM;
 using Android.Runtime;
 using Android.OS;
 using Android.Net;
+using System.Collections.Generic;
+using Android.Provider;
+using Android.Database;
 
 namespace Billing.Droid
 {
@@ -23,11 +26,7 @@ namespace Billing.Droid
             string url;
             if (Intent.ActionView.Equals(Intent.Action) && Intent.Data is Uri uri)
             {
-                url = uri.Path;
-                if (url != null && url.StartsWith("/root"))
-                {
-                    url = url[5..];
-                }
+                url = GetFilePath(BaseContext, uri);
             }
             else
             {
@@ -44,5 +43,74 @@ namespace Billing.Droid
 
             base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
         }
+
+        private string GetFilePath(Context context, Uri uri)
+        {
+            if (DocumentsContract.IsDocumentUri(context, uri))
+            {
+                Uri contentUri;
+                string[] split;
+                switch (uri.Authority)
+                {
+                    case "com.android.externalstorage.documents":
+                        split = DocumentsContract.GetDocumentId(uri).Split(':');
+                        if (split[0] == "primary")
+                        {
+                            var external = ExternalCacheDir.Path;
+                            external = external[..external.IndexOf("/Android/")];
+                            return external + "/" + split[1];
+                        }
+                        break;
+
+                    case "com.android.providers.downloads.documents":
+                        contentUri = ContentUris.WithAppendedId(
+                            Uri.Parse("content://downloads/public_downloads"),
+                            long.Parse(DocumentsContract.GetDocumentId(uri)));
+                        return GetDataColumn(context, contentUri, null, null);
+
+                    case "com.android.providers.media.documents":
+                        split = DocumentsContract.GetDocumentId(uri).Split(':');
+                        contentUri = split[0] switch
+                        {
+                            "image" => MediaStore.Images.Media.ExternalContentUri,
+                            "video" => MediaStore.Video.Media.ExternalContentUri,
+                            "audio" => MediaStore.Audio.Media.ExternalContentUri,
+                            _ => null
+                        };
+                        return GetDataColumn(context, contentUri, "_id=?", new[] { split[1] });
+                }
+            }
+            else if (uri.Scheme == "content")
+            {
+                return GetDataColumn(context, uri, null, null);
+            }
+            else if (uri.Scheme == "file")
+            {
+                return uri.Path;
+            }
+            return null;
+        }
+
+        private string GetDataColumn(Context context, Uri uri, string selection, string[] selectionArgs)
+        {
+            ICursor cursor = null;
+            try
+            {
+                cursor = context.ContentResolver.Query(uri, new[] { "_data" }, selection, selectionArgs, null);
+                if (cursor != null && cursor.MoveToFirst())
+                {
+                    var index = cursor.GetColumnIndexOrThrow("_data");
+                    return cursor.GetString(index);
+                }
+            }
+            finally
+            {
+                if (cursor != null)
+                {
+                    cursor.Close();
+                }
+            }
+            return null;
+        }
     }
 }
\ No newline at end of file
diff --git a/Billing/Billing.Android/Properties/AndroidManifest.xml b/Billing/Billing.Android/Properties/AndroidManifest.xml
index 555f42d..a984595 100644
--- a/Billing/Billing.Android/Properties/AndroidManifest.xml
+++ b/Billing/Billing.Android/Properties/AndroidManifest.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionName="1.1.316" package="org.tsanie.billing" android:installLocation="auto" android:versionCode="15">
+<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionName="1.2.317" package="org.tsanie.billing" android:installLocation="auto" android:versionCode="16">
 	<uses-sdk android:minSdkVersion="24" android:targetSdkVersion="30" />
 	<application android:label="@string/applabel" android:theme="@style/MainTheme"></application>
 	<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
diff --git a/Billing/Billing.Android/Properties/AssemblyInfo.cs b/Billing/Billing.Android/Properties/AssemblyInfo.cs
index 8c2ea37..e756626 100644
--- a/Billing/Billing.Android/Properties/AssemblyInfo.cs
+++ b/Billing/Billing.Android/Properties/AssemblyInfo.cs
@@ -30,3 +30,8 @@ using Android.App;
 [assembly: UsesPermission(Android.Manifest.Permission.ReadExternalStorage)]
 [assembly: UsesPermission(Android.Manifest.Permission.ManageExternalStorage)]
 [assembly: UsesPermission(Android.Manifest.Permission.Vibrate)]
+[assembly: UsesPermission(Android.Manifest.Permission.AccessCoarseLocation)]
+[assembly: UsesPermission(Android.Manifest.Permission.AccessFineLocation)]
+[assembly: UsesFeature("android.hardware.location", Required = false)]
+[assembly: UsesFeature("android.hardware.location.gps", Required = false)]
+[assembly: UsesFeature("android.hardware.location.network", Required = false)]
diff --git a/Billing/Billing.Android/SplashActivity.cs b/Billing/Billing.Android/SplashActivity.cs
index 286ffe2..354a998 100644
--- a/Billing/Billing.Android/SplashActivity.cs
+++ b/Billing/Billing.Android/SplashActivity.cs
@@ -15,9 +15,9 @@ namespace Billing.Droid
     [IntentFilter(
         new[] { Intent.ActionView },
         Categories = new[] { Intent.CategoryDefault, Intent.CategoryBrowsable },
-        DataScheme = "file",
+        //DataScheme = "file",
         DataMimeType = "*/*",
-        DataPathPattern = ".*\\\\.db3")]
+        DataPathPattern = ".*\\.db3")]
     public class SplashActivity : AppCompatActivity
     {
         public override void OnCreate(Bundle savedInstanceState, PersistableBundle persistentState)
diff --git a/Billing/Billing.iOS/Base.lproj/InfoPlist.strings b/Billing/Billing.iOS/Base.lproj/InfoPlist.strings
index 18ab93e..19f66b9 100644
--- a/Billing/Billing.iOS/Base.lproj/InfoPlist.strings
+++ b/Billing/Billing.iOS/Base.lproj/InfoPlist.strings
@@ -1 +1,2 @@
-"CFBundleDisplayName" = "Billing";
\ No newline at end of file
+"CFBundleDisplayName" = "Billing";
+"NSLocationWhenInUseUsageDescription" = "When &quot;Save Location&quot; is checked, the Billing app needs to access the location.";
\ No newline at end of file
diff --git a/Billing/Billing.iOS/Info.plist b/Billing/Billing.iOS/Info.plist
index 5b8fa19..dce218b 100644
--- a/Billing/Billing.iOS/Info.plist
+++ b/Billing/Billing.iOS/Info.plist
@@ -80,9 +80,9 @@
 		</dict>
 	</array>
 	<key>CFBundleVersion</key>
-	<string>15</string>
+	<string>16</string>
 	<key>CFBundleShortVersionString</key>
-	<string>1.1.316</string>
+	<string>1.2.317</string>
 	<key>LSApplicationQueriesSchemes</key>
 	<array>
 		<string>mailto</string>
@@ -91,5 +91,7 @@
 	<true/>
 	<key>LSSupportsOpeningDocumentsInPlace</key>
 	<true/>
+	<key>NSLocationWhenInUseUsageDescription</key>
+	<string>When &quot;Save Location&quot; is checked, the Billing app needs to access the location.</string>
 </dict>
 </plist>
diff --git a/Billing/Billing.iOS/zh-Hans.lproj/InfoPlist.strings b/Billing/Billing.iOS/zh-Hans.lproj/InfoPlist.strings
index 4149251..e009fa3 100644
--- a/Billing/Billing.iOS/zh-Hans.lproj/InfoPlist.strings
+++ b/Billing/Billing.iOS/zh-Hans.lproj/InfoPlist.strings
@@ -1 +1,2 @@
-"CFBundleDisplayName" = "记账本";
\ No newline at end of file
+"CFBundleDisplayName" = "记账本";
+"NSLocationWhenInUseUsageDescription" = "当选中“保存位置”时,记账本需要访问位置信息。";
\ No newline at end of file