From decd3fc24ec786d5d367adeb98351944f5481f68 Mon Sep 17 00:00:00 2001
From: Kyle Spearrin <kyle.spearrin@gmail.com>
Date: Fri, 6 May 2016 00:17:38 -0400
Subject: [PATCH] Added icons for iOS. Broke out data access into repositories.
 Added syncing service.

---
 src/Android/MainActivity.cs                   |  15 ++-
 .../Repositories/IApiRepository.cs            |  19 +++
 .../Repositories/IAuthApiRepository.cs        |  11 ++
 .../Repositories/IFolderApiRepository.cs      |  12 ++
 .../Repositories/IFolderRepository.cs         |  12 ++
 .../Abstractions/Repositories/IRepository.cs  |  18 +++
 .../Repositories/ISiteApiRepository.cs        |  12 ++
 .../Repositories/ISiteRepository.cs           |  12 ++
 src/App/Abstractions/Services/IApiService.cs  |  13 --
 src/App/Abstractions/Services/ISiteService.cs |   1 +
 src/App/Abstractions/Services/ISyncService.cs |   9 ++
 src/App/App.csproj                            |  22 +++-
 .../Api/Request/TokenTwoFactorRequest.cs      |   8 ++
 src/App/Models/Api/Response/FolderResponse.cs |   5 +-
 src/App/Models/Api/Response/ListResponse.cs   |  14 +++
 src/App/Models/Api/Response/SiteResponse.cs   |   5 +-
 src/App/Pages/MainPage.cs                     |  20 ++--
 src/App/Pages/SyncPage.cs                     |  42 ++++++-
 src/App/Pages/VaultListPage.cs                |  27 +++--
 src/App/Repositories/ApiRepository.cs         | 112 ++++++++++++++++++
 src/App/Repositories/AuthApiRepository.cs     |  52 ++++++++
 .../BaseApiRepository.cs}                     |  14 ++-
 src/App/Repositories/FolderApiRepository.cs   |  34 ++++++
 src/App/Repositories/FolderRepository.cs      |  22 ++++
 .../{Services => Repositories}/Repository.cs  |  16 +--
 src/App/Repositories/SiteApiRepository.cs     |  34 ++++++
 src/App/Repositories/SiteRepository.cs        |  22 ++++
 src/App/Services/AuthService.cs               |  18 +--
 src/App/Services/FolderService.cs             |  69 +++++------
 src/App/Services/SiteService.cs               |  89 +++++++-------
 src/App/Services/SyncService.cs               | 108 +++++++++++++++++
 src/iOS/AppDelegate.cs                        |  15 ++-
 src/iOS/Info.plist                            |   2 +-
 src/iOS/Resources/fa-cogs.png                 | Bin 0 -> 705 bytes
 src/iOS/Resources/fa-cogs@2x.png              | Bin 0 -> 1165 bytes
 src/iOS/Resources/fa-cogs@3x.png              | Bin 0 -> 1660 bytes
 src/iOS/Resources/fa-lock.png                 | Bin 0 -> 388 bytes
 src/iOS/Resources/fa-lock@2x.png              | Bin 0 -> 587 bytes
 src/iOS/Resources/fa-lock@3x.png              | Bin 0 -> 820 bytes
 src/iOS/Resources/fa-plus.png                 | Bin 0 -> 242 bytes
 src/iOS/Resources/fa-plus@2x.png              | Bin 0 -> 335 bytes
 src/iOS/Resources/fa-plus@3x.png              | Bin 0 -> 418 bytes
 src/iOS/Resources/fa-refresh.png              | Bin 0 -> 595 bytes
 src/iOS/Resources/fa-refresh@2x.png           | Bin 0 -> 960 bytes
 src/iOS/Resources/fa-refresh@3x.png           | Bin 0 -> 1269 bytes
 src/iOS/iOS.csproj                            |  39 ++++++
 46 files changed, 773 insertions(+), 150 deletions(-)
 create mode 100644 src/App/Abstractions/Repositories/IApiRepository.cs
 create mode 100644 src/App/Abstractions/Repositories/IAuthApiRepository.cs
 create mode 100644 src/App/Abstractions/Repositories/IFolderApiRepository.cs
 create mode 100644 src/App/Abstractions/Repositories/IFolderRepository.cs
 create mode 100644 src/App/Abstractions/Repositories/IRepository.cs
 create mode 100644 src/App/Abstractions/Repositories/ISiteApiRepository.cs
 create mode 100644 src/App/Abstractions/Repositories/ISiteRepository.cs
 delete mode 100644 src/App/Abstractions/Services/IApiService.cs
 create mode 100644 src/App/Abstractions/Services/ISyncService.cs
 create mode 100644 src/App/Models/Api/Request/TokenTwoFactorRequest.cs
 create mode 100644 src/App/Models/Api/Response/ListResponse.cs
 create mode 100644 src/App/Repositories/ApiRepository.cs
 create mode 100644 src/App/Repositories/AuthApiRepository.cs
 rename src/App/{Services/ApiService.cs => Repositories/BaseApiRepository.cs} (76%)
 create mode 100644 src/App/Repositories/FolderApiRepository.cs
 create mode 100644 src/App/Repositories/FolderRepository.cs
 rename src/App/{Services => Repositories}/Repository.cs (70%)
 create mode 100644 src/App/Repositories/SiteApiRepository.cs
 create mode 100644 src/App/Repositories/SiteRepository.cs
 create mode 100644 src/App/Services/SyncService.cs
 create mode 100644 src/iOS/Resources/fa-cogs.png
 create mode 100644 src/iOS/Resources/fa-cogs@2x.png
 create mode 100644 src/iOS/Resources/fa-cogs@3x.png
 create mode 100644 src/iOS/Resources/fa-lock.png
 create mode 100644 src/iOS/Resources/fa-lock@2x.png
 create mode 100644 src/iOS/Resources/fa-lock@3x.png
 create mode 100644 src/iOS/Resources/fa-plus.png
 create mode 100644 src/iOS/Resources/fa-plus@2x.png
 create mode 100644 src/iOS/Resources/fa-plus@3x.png
 create mode 100644 src/iOS/Resources/fa-refresh.png
 create mode 100644 src/iOS/Resources/fa-refresh@2x.png
 create mode 100644 src/iOS/Resources/fa-refresh@3x.png

diff --git a/src/Android/MainActivity.cs b/src/Android/MainActivity.cs
index 2b3b55c85..696bf2c88 100644
--- a/src/Android/MainActivity.cs
+++ b/src/Android/MainActivity.cs
@@ -15,6 +15,7 @@ using Bit.Android.Services;
 using Plugin.Settings;
 using Plugin.Connectivity;
 using Acr.UserDialogs;
+using Bit.App.Repositories;
 
 namespace Bit.Android
 {
@@ -40,15 +41,23 @@ namespace Bit.Android
             var container = new UnityContainer();
 
             container
-                .RegisterType<ISqlService, SqlService>(new ContainerControlledLifetimeManager())
+                // Services
                 .RegisterType<IDatabaseService, DatabaseService>(new ContainerControlledLifetimeManager())
+                .RegisterType<ISqlService, SqlService>(new ContainerControlledLifetimeManager())
                 .RegisterType<ISecureStorageService, KeyStoreStorageService>(new ContainerControlledLifetimeManager())
-                .RegisterInstance(CrossSettings.Current, new ContainerControlledLifetimeManager())
-                .RegisterType<IApiService, ApiService>(new ContainerControlledLifetimeManager())
                 .RegisterType<ICryptoService, CryptoService>(new ContainerControlledLifetimeManager())
                 .RegisterType<IAuthService, AuthService>(new ContainerControlledLifetimeManager())
                 .RegisterType<IFolderService, FolderService>(new ContainerControlledLifetimeManager())
                 .RegisterType<ISiteService, SiteService>(new ContainerControlledLifetimeManager())
+                .RegisterType<ISyncService, SyncService>(new ContainerControlledLifetimeManager())
+                // Repositories
+                .RegisterType<IFolderRepository, FolderRepository>(new ContainerControlledLifetimeManager())
+                .RegisterType<IFolderApiRepository, FolderApiRepository>(new ContainerControlledLifetimeManager())
+                .RegisterType<ISiteRepository, SiteRepository>(new ContainerControlledLifetimeManager())
+                .RegisterType<ISiteApiRepository, SiteApiRepository>(new ContainerControlledLifetimeManager())
+                .RegisterType<IAuthApiRepository, AuthApiRepository>(new ContainerControlledLifetimeManager())
+                // Other
+                .RegisterInstance(CrossSettings.Current, new ContainerControlledLifetimeManager())
                 .RegisterInstance(CrossConnectivity.Current, new ContainerControlledLifetimeManager())
                 .RegisterInstance(UserDialogs.Instance, new ContainerControlledLifetimeManager());
 
diff --git a/src/App/Abstractions/Repositories/IApiRepository.cs b/src/App/Abstractions/Repositories/IApiRepository.cs
new file mode 100644
index 000000000..651e69701
--- /dev/null
+++ b/src/App/Abstractions/Repositories/IApiRepository.cs
@@ -0,0 +1,19 @@
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Bit.App.Models.Api;
+
+namespace Bit.App.Abstractions
+{
+    public interface IApiRepository<TRequest, TResponse, TId>
+        where TRequest : class
+        where TResponse : class
+        where TId : IEquatable<TId>
+    {
+        Task<ApiResult<TResponse>> GetByIdAsync(TId id);
+        Task<ApiResult<ListResponse<TResponse>>> GetAsync();
+        Task<ApiResult<TResponse>> PostAsync(TRequest requestObj);
+        Task<ApiResult<TResponse>> PutAsync(TId id, TRequest requestObj);
+        Task<ApiResult<object>> DeleteAsync(TId id);
+    }
+}
\ No newline at end of file
diff --git a/src/App/Abstractions/Repositories/IAuthApiRepository.cs b/src/App/Abstractions/Repositories/IAuthApiRepository.cs
new file mode 100644
index 000000000..208c515a2
--- /dev/null
+++ b/src/App/Abstractions/Repositories/IAuthApiRepository.cs
@@ -0,0 +1,11 @@
+using System.Threading.Tasks;
+using Bit.App.Models.Api;
+
+namespace Bit.App.Abstractions
+{
+    public interface IAuthApiRepository
+    {
+        Task<ApiResult<TokenResponse>> PostTokenAsync(TokenRequest requestObj);
+        Task<ApiResult<TokenResponse>> PostTokenTwoFactorAsync(TokenTwoFactorRequest requestObj);
+    }
+}
\ No newline at end of file
diff --git a/src/App/Abstractions/Repositories/IFolderApiRepository.cs b/src/App/Abstractions/Repositories/IFolderApiRepository.cs
new file mode 100644
index 000000000..c46e84aa4
--- /dev/null
+++ b/src/App/Abstractions/Repositories/IFolderApiRepository.cs
@@ -0,0 +1,12 @@
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Bit.App.Models.Api;
+
+namespace Bit.App.Abstractions
+{
+    public interface IFolderApiRepository : IApiRepository<FolderRequest, FolderResponse, string>
+    {
+        Task<ApiResult<ListResponse<FolderResponse>>> GetByRevisionDateAsync(DateTime since);
+    }
+}
\ No newline at end of file
diff --git a/src/App/Abstractions/Repositories/IFolderRepository.cs b/src/App/Abstractions/Repositories/IFolderRepository.cs
new file mode 100644
index 000000000..57045d646
--- /dev/null
+++ b/src/App/Abstractions/Repositories/IFolderRepository.cs
@@ -0,0 +1,12 @@
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Bit.App.Models.Data;
+
+namespace Bit.App.Abstractions
+{
+    public interface IFolderRepository : IRepository<FolderData, string>
+    {
+        Task<IEnumerable<FolderData>> GetAllByUserIdAsync(string userId);
+    }
+}
diff --git a/src/App/Abstractions/Repositories/IRepository.cs b/src/App/Abstractions/Repositories/IRepository.cs
new file mode 100644
index 000000000..4bf4e762d
--- /dev/null
+++ b/src/App/Abstractions/Repositories/IRepository.cs
@@ -0,0 +1,18 @@
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+
+namespace Bit.App.Abstractions
+{
+    public interface IRepository<T, TId>
+        where T : class, IDataObject<TId>, new()
+        where TId : IEquatable<TId>
+    {
+        Task<T> GetByIdAsync(TId id);
+        Task<IEnumerable<T>> GetAllAsync();
+        Task UpdateAsync(T obj);
+        Task InsertAsync(T obj);
+        Task DeleteAsync(TId id);
+        Task DeleteAsync(T obj);
+    }
+}
diff --git a/src/App/Abstractions/Repositories/ISiteApiRepository.cs b/src/App/Abstractions/Repositories/ISiteApiRepository.cs
new file mode 100644
index 000000000..6cd45b70d
--- /dev/null
+++ b/src/App/Abstractions/Repositories/ISiteApiRepository.cs
@@ -0,0 +1,12 @@
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Bit.App.Models.Api;
+
+namespace Bit.App.Abstractions
+{
+    public interface ISiteApiRepository : IApiRepository<SiteRequest, SiteResponse, string>
+    {
+        Task<ApiResult<ListResponse<SiteResponse>>> GetByRevisionDateAsync(DateTime since);
+    }
+}
\ No newline at end of file
diff --git a/src/App/Abstractions/Repositories/ISiteRepository.cs b/src/App/Abstractions/Repositories/ISiteRepository.cs
new file mode 100644
index 000000000..c16edea2b
--- /dev/null
+++ b/src/App/Abstractions/Repositories/ISiteRepository.cs
@@ -0,0 +1,12 @@
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Bit.App.Models.Data;
+
+namespace Bit.App.Abstractions
+{
+    public interface ISiteRepository : IRepository<SiteData, string>
+    {
+        Task<IEnumerable<SiteData>> GetAllByUserIdAsync(string userId);
+    }
+}
diff --git a/src/App/Abstractions/Services/IApiService.cs b/src/App/Abstractions/Services/IApiService.cs
deleted file mode 100644
index 0373a2d27..000000000
--- a/src/App/Abstractions/Services/IApiService.cs
+++ /dev/null
@@ -1,13 +0,0 @@
-using System.Net.Http;
-using System.Threading.Tasks;
-using Bit.App.Models.Api;
-
-namespace Bit.App.Abstractions
-{
-    public interface IApiService
-    {
-        HttpClient Client { get; set; }
-
-        Task<ApiResult<T>> HandleErrorAsync<T>(HttpResponseMessage response);
-    }
-}
diff --git a/src/App/Abstractions/Services/ISiteService.cs b/src/App/Abstractions/Services/ISiteService.cs
index 96b11482e..0f3bde43a 100644
--- a/src/App/Abstractions/Services/ISiteService.cs
+++ b/src/App/Abstractions/Services/ISiteService.cs
@@ -7,6 +7,7 @@ namespace Bit.App.Abstractions
 {
     public interface ISiteService
     {
+        Task<Site> GetByIdAsync(string id);
         Task<IEnumerable<Site>> GetAllAsync();
         Task<ApiResult<SiteResponse>> SaveAsync(Site site);
         Task<ApiResult<object>> DeleteAsync(string id);
diff --git a/src/App/Abstractions/Services/ISyncService.cs b/src/App/Abstractions/Services/ISyncService.cs
new file mode 100644
index 000000000..4317e67da
--- /dev/null
+++ b/src/App/Abstractions/Services/ISyncService.cs
@@ -0,0 +1,9 @@
+using System.Threading.Tasks;
+
+namespace Bit.App.Abstractions
+{
+    public interface ISyncService
+    {
+        Task<bool> SyncAsync();
+    }
+}
\ No newline at end of file
diff --git a/src/App/App.csproj b/src/App/App.csproj
index 050fcad7d..bb21c8877 100644
--- a/src/App/App.csproj
+++ b/src/App/App.csproj
@@ -48,8 +48,10 @@
     <Compile Include="Models\Api\Request\FolderRequest.cs" />
     <Compile Include="Models\Api\Request\SiteRequest.cs" />
     <Compile Include="Models\Api\Request\TokenRequest.cs" />
+    <Compile Include="Models\Api\Request\TokenTwoFactorRequest.cs" />
     <Compile Include="Models\Api\Response\ErrorResponse.cs" />
     <Compile Include="Models\Api\Response\FolderResponse.cs" />
+    <Compile Include="Models\Api\Response\ListResponse.cs" />
     <Compile Include="Models\Api\Response\SiteResponse.cs" />
     <Compile Include="Models\Api\Response\TokenResponse.cs" />
     <Compile Include="Models\Api\Response\ProfileResponse.cs" />
@@ -65,13 +67,28 @@
     <Compile Include="Pages\SyncPage.cs" />
     <Compile Include="Pages\SettingsPage.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
+    <Compile Include="Abstractions\Repositories\ISiteRepository.cs" />
+    <Compile Include="Repositories\ApiRepository.cs" />
+    <Compile Include="Repositories\BaseApiRepository.cs" />
+    <Compile Include="Abstractions\Repositories\IApiRepository.cs" />
+    <Compile Include="Abstractions\Repositories\IFolderApiRepository.cs" />
+    <Compile Include="Abstractions\Repositories\ISiteApiRepository.cs" />
+    <Compile Include="Repositories\AuthApiRepository.cs" />
+    <Compile Include="Abstractions\Repositories\IAuthApiRepository.cs" />
+    <Compile Include="Repositories\SiteApiRepository.cs" />
+    <Compile Include="Repositories\FolderApiRepository.cs" />
+    <Compile Include="Repositories\SiteRepository.cs" />
+    <Compile Include="Repositories\FolderRepository.cs" />
+    <Compile Include="Abstractions\Repositories\IFolderRepository.cs" />
+    <Compile Include="Abstractions\Repositories\IRepository.cs" />
     <Compile Include="Services\DatabaseService.cs" />
     <Compile Include="Services\FolderService.cs" />
-    <Compile Include="Services\Repository.cs" />
-    <Compile Include="Abstractions\Services\IApiService.cs" />
+    <Compile Include="Repositories\Repository.cs" />
     <Compile Include="Abstractions\Services\IAuthService.cs" />
     <Compile Include="Abstractions\Services\ICryptoService.cs" />
     <Compile Include="Abstractions\Services\IDatabaseService.cs" />
+    <Compile Include="Abstractions\Services\ISyncService.cs" />
+    <Compile Include="Services\SyncService.cs" />
     <Compile Include="Services\SiteService.cs" />
     <Compile Include="Services\AuthService.cs" />
     <Compile Include="Services\CryptoService.cs" />
@@ -83,7 +100,6 @@
     <Compile Include="Pages\VaultViewSitePage.cs" />
     <Compile Include="Pages\VaultEditSitePage.cs" />
     <Compile Include="Pages\VaultListPage.cs" />
-    <Compile Include="Services\ApiService.cs" />
     <Compile Include="Utilities\Extentions.cs" />
     <Compile Include="Utilities\TokenHttpRequestMessage.cs" />
   </ItemGroup>
diff --git a/src/App/Models/Api/Request/TokenTwoFactorRequest.cs b/src/App/Models/Api/Request/TokenTwoFactorRequest.cs
new file mode 100644
index 000000000..edf1258e8
--- /dev/null
+++ b/src/App/Models/Api/Request/TokenTwoFactorRequest.cs
@@ -0,0 +1,8 @@
+namespace Bit.App.Models.Api
+{
+    public class TokenTwoFactorRequest
+    {
+        public string Code { get; set; }
+        public string Provider { get; set; }
+    }
+}
diff --git a/src/App/Models/Api/Response/FolderResponse.cs b/src/App/Models/Api/Response/FolderResponse.cs
index 6ad1f0a69..869a476f5 100644
--- a/src/App/Models/Api/Response/FolderResponse.cs
+++ b/src/App/Models/Api/Response/FolderResponse.cs
@@ -1,8 +1,11 @@
-namespace Bit.App.Models.Api
+using System;
+
+namespace Bit.App.Models.Api
 {
     public class FolderResponse
     {
         public string Id { get; set; }
         public string Name { get; set; }
+        public DateTime RevisionDate { get; set; }
     }
 }
diff --git a/src/App/Models/Api/Response/ListResponse.cs b/src/App/Models/Api/Response/ListResponse.cs
new file mode 100644
index 000000000..148170a40
--- /dev/null
+++ b/src/App/Models/Api/Response/ListResponse.cs
@@ -0,0 +1,14 @@
+using System.Collections.Generic;
+
+namespace Bit.App.Models.Api
+{
+    public class ListResponse<T>
+    {
+        public ListResponse(IEnumerable<T> data)
+        {
+            Data = data;
+        }
+
+        public IEnumerable<T> Data { get; set; }
+    }
+}
diff --git a/src/App/Models/Api/Response/SiteResponse.cs b/src/App/Models/Api/Response/SiteResponse.cs
index cf2dd2d69..1db44bda5 100644
--- a/src/App/Models/Api/Response/SiteResponse.cs
+++ b/src/App/Models/Api/Response/SiteResponse.cs
@@ -1,4 +1,6 @@
-namespace Bit.App.Models.Api
+using System;
+
+namespace Bit.App.Models.Api
 {
     public class SiteResponse
     {
@@ -9,6 +11,7 @@
         public string Username { get; set; }
         public string Password { get; set; }
         public string Notes { get; set; }
+        public DateTime RevisionDate { get; set; }
 
         // Expandables
         public FolderResponse Folder { get; set; }
diff --git a/src/App/Pages/MainPage.cs b/src/App/Pages/MainPage.cs
index 0f0cdb74e..f903edd61 100644
--- a/src/App/Pages/MainPage.cs
+++ b/src/App/Pages/MainPage.cs
@@ -1,5 +1,4 @@
 using System;
-using Bit.App.Abstractions;
 using Xamarin.Forms;
 
 namespace Bit.App.Pages
@@ -8,18 +7,21 @@ namespace Bit.App.Pages
     {
         public MainPage()
         {
-            var vaultNavigation = new NavigationPage(new VaultListPage());
-            vaultNavigation.BarBackgroundColor = Color.FromHex("3c8dbc");
-            vaultNavigation.BarTextColor = Color.FromHex("ffffff");
-            vaultNavigation.Title = "My Vault";
-
             var settingsNavigation = new NavigationPage(new SettingsPage());
-            settingsNavigation.BarBackgroundColor = Color.FromHex("3c8dbc");
-            settingsNavigation.BarTextColor = Color.FromHex("ffffff");
+            var vaultNavigation = new NavigationPage(new VaultListPage());
+            var syncPage = new SyncPage();
+
+            vaultNavigation.BarBackgroundColor = settingsNavigation.BarBackgroundColor = Color.FromHex("3c8dbc");
+            vaultNavigation.BarTextColor = settingsNavigation.BarTextColor = Color.FromHex("ffffff");
+
+            vaultNavigation.Title = "My Vault";
+            vaultNavigation.Icon = "fa-lock";
+
             settingsNavigation.Title = "Settings";
+            settingsNavigation.Icon = "fa-cogs";
 
             Children.Add(vaultNavigation);
-            Children.Add(new SyncPage());
+            Children.Add(syncPage);
             Children.Add(settingsNavigation);
         }
     }
diff --git a/src/App/Pages/SyncPage.cs b/src/App/Pages/SyncPage.cs
index 28a531030..d129293e9 100644
--- a/src/App/Pages/SyncPage.cs
+++ b/src/App/Pages/SyncPage.cs
@@ -1,14 +1,54 @@
 using System;
+using System.Threading.Tasks;
+using Acr.UserDialogs;
+using Bit.App.Abstractions;
 using Xamarin.Forms;
+using XLabs.Ioc;
 
 namespace Bit.App.Pages
 {
     public class SyncPage : ContentPage
     {
+        private readonly ISyncService _syncService;
+        private readonly IUserDialogs _userDialogs;
+
         public SyncPage()
         {
+            _syncService = Resolver.Resolve<ISyncService>();
+            _userDialogs = Resolver.Resolve<IUserDialogs>();
+
+            Init();
+        }
+
+        public void Init()
+        {
+            var syncButton = new Button
+            {
+                Text = "Sync Vault",
+                Command = new Command(async () => await SyncAsync())
+            };
+
+            var stackLayout = new StackLayout { };
+            stackLayout.Children.Add(syncButton);
+
             Title = "Sync";
-            Content = null;
+            Content = stackLayout;
+            Icon = "fa-refresh";
+        }
+
+        public async Task SyncAsync()
+        {
+            _userDialogs.ShowLoading("Syncing...", MaskType.Black);
+            var succeeded = await _syncService.SyncAsync();
+            _userDialogs.HideLoading();
+            if(succeeded)
+            {
+                _userDialogs.SuccessToast("Syncing complete.");
+            }
+            else
+            {
+                _userDialogs.ErrorToast("Syncing failed.");
+            }
         }
     }
 }
diff --git a/src/App/Pages/VaultListPage.cs b/src/App/Pages/VaultListPage.cs
index 129750f9c..8a20fb68c 100644
--- a/src/App/Pages/VaultListPage.cs
+++ b/src/App/Pages/VaultListPage.cs
@@ -31,21 +31,14 @@ namespace Bit.App.Pages
         {
             ToolbarItems.Add(new AddSiteToolBarItem(this));
 
-            var moreAction = new MenuItem { Text = "More" };
-            moreAction.SetBinding(MenuItem.CommandParameterProperty, new Binding("."));
-            moreAction.Clicked += MoreClickedAsync;
-
-            var deleteAction = new MenuItem { Text = "Delete", IsDestructive = true };
-            deleteAction.SetBinding(MenuItem.CommandParameterProperty, new Binding("."));
-            deleteAction.Clicked += DeleteClickedAsync;
-
             var listView = new ListView { IsGroupingEnabled = true, ItemsSource = Folders };
             listView.GroupDisplayBinding = new Binding("Name");
             listView.ItemSelected += SiteSelected;
-            listView.ItemTemplate = new DataTemplate(() => new VaultListViewCell(moreAction, deleteAction));
+            listView.ItemTemplate = new DataTemplate(() => new VaultListViewCell(this));
 
             Title = "My Vault";
             Content = listView;
+            NavigationPage.SetBackButtonTitle(this, string.Empty);
         }
 
         protected override void OnAppearing()
@@ -122,7 +115,7 @@ namespace Bit.App.Pages
             {
                 _page = page;
                 Text = "Add";
-                Icon = "";
+                Icon = "fa-plus";
                 Clicked += ClickedItem;
             }
 
@@ -144,12 +137,20 @@ namespace Bit.App.Pages
 
         private class VaultListViewCell : TextCell
         {
-            public VaultListViewCell(MenuItem moreMenuItem, MenuItem deleteMenuItem)
+            public VaultListViewCell(VaultListPage page)
             {
+                var moreAction = new MenuItem { Text = "More" };
+                moreAction.SetBinding(MenuItem.CommandParameterProperty, new Binding("."));
+                moreAction.Clicked += page.MoreClickedAsync;
+
+                var deleteAction = new MenuItem { Text = "Delete", IsDestructive = true };
+                deleteAction.SetBinding(MenuItem.CommandParameterProperty, new Binding("."));
+                deleteAction.Clicked += page.DeleteClickedAsync;
+
                 this.SetBinding<VaultView.Site>(TextProperty, s => s.Name);
                 this.SetBinding<VaultView.Site>(DetailProperty, s => s.Username);
-                ContextActions.Add(moreMenuItem);
-                ContextActions.Add(deleteMenuItem);
+                ContextActions.Add(moreAction);
+                ContextActions.Add(deleteAction);
             }
         }
     }
diff --git a/src/App/Repositories/ApiRepository.cs b/src/App/Repositories/ApiRepository.cs
new file mode 100644
index 000000000..080055d33
--- /dev/null
+++ b/src/App/Repositories/ApiRepository.cs
@@ -0,0 +1,112 @@
+using System;
+using System.Collections.Generic;
+using System.Net.Http;
+using System.Threading.Tasks;
+using Bit.App.Abstractions;
+using Bit.App.Models.Api;
+using Newtonsoft.Json;
+
+namespace Bit.App.Repositories
+{
+    public abstract class ApiRepository<TRequest, TResponse, TId> : BaseApiRepository, IApiRepository<TRequest, TResponse, TId>
+        where TId : IEquatable<TId>
+        where TRequest : class
+        where TResponse : class
+    {
+        public ApiRepository()
+        { }
+
+        public virtual async Task<ApiResult<TResponse>> GetByIdAsync(TId id)
+        {
+            var requestMessage = new TokenHttpRequestMessage()
+            {
+                Method = HttpMethod.Get,
+                RequestUri = new Uri(Client.BaseAddress, string.Concat(ApiRoute, "/", id)),
+            };
+
+            var response = await Client.SendAsync(requestMessage);
+            if(!response.IsSuccessStatusCode)
+            {
+                return await HandleErrorAsync<TResponse>(response);
+            }
+
+            var responseContent = await response.Content.ReadAsStringAsync();
+            var responseObj = JsonConvert.DeserializeObject<TResponse>(responseContent);
+            return ApiResult<TResponse>.Success(responseObj, response.StatusCode);
+        }
+
+        public virtual async Task<ApiResult<ListResponse<TResponse>>> GetAsync()
+        {
+            var requestMessage = new TokenHttpRequestMessage()
+            {
+                Method = HttpMethod.Get,
+                RequestUri = new Uri(Client.BaseAddress, ApiRoute),
+            };
+
+            var response = await Client.SendAsync(requestMessage);
+            if(!response.IsSuccessStatusCode)
+            {
+                return await HandleErrorAsync<ListResponse<TResponse>>(response);
+            }
+
+            var responseContent = await response.Content.ReadAsStringAsync();
+            var responseObj = JsonConvert.DeserializeObject<ListResponse<TResponse>>(responseContent);
+            return ApiResult<ListResponse<TResponse>>.Success(responseObj, response.StatusCode);
+        }
+
+        public virtual async Task<ApiResult<TResponse>> PostAsync(TRequest requestObj)
+        {
+            var requestMessage = new TokenHttpRequestMessage(requestObj)
+            {
+                Method = HttpMethod.Post,
+                RequestUri = new Uri(Client.BaseAddress, ApiRoute),
+            };
+
+            var response = await Client.SendAsync(requestMessage);
+            if(!response.IsSuccessStatusCode)
+            {
+                return await HandleErrorAsync<TResponse>(response);
+            }
+
+            var responseContent = await response.Content.ReadAsStringAsync();
+            var responseObj = JsonConvert.DeserializeObject<TResponse>(responseContent);
+            return ApiResult<TResponse>.Success(responseObj, response.StatusCode);
+        }
+
+        public virtual async Task<ApiResult<TResponse>> PutAsync(TId id, TRequest requestObj)
+        {
+            var requestMessage = new TokenHttpRequestMessage(requestObj)
+            {
+                Method = HttpMethod.Put,
+                RequestUri = new Uri(Client.BaseAddress, string.Concat(ApiRoute, "/", id)),
+            };
+
+            var response = await Client.SendAsync(requestMessage);
+            if(!response.IsSuccessStatusCode)
+            {
+                return await HandleErrorAsync<TResponse>(response);
+            }
+
+            var responseContent = await response.Content.ReadAsStringAsync();
+            var responseObj = JsonConvert.DeserializeObject<TResponse>(responseContent);
+            return ApiResult<TResponse>.Success(responseObj, response.StatusCode);
+        }
+
+        public virtual async Task<ApiResult<object>> DeleteAsync(TId id)
+        {
+            var requestMessage = new TokenHttpRequestMessage()
+            {
+                Method = HttpMethod.Delete,
+                RequestUri = new Uri(Client.BaseAddress, string.Concat(ApiRoute, "/", id)),
+            };
+
+            var response = await Client.SendAsync(requestMessage);
+            if(!response.IsSuccessStatusCode)
+            {
+                return await HandleErrorAsync<object>(response);
+            }
+
+            return ApiResult<object>.Success(null, response.StatusCode);
+        }
+    }
+}
diff --git a/src/App/Repositories/AuthApiRepository.cs b/src/App/Repositories/AuthApiRepository.cs
new file mode 100644
index 000000000..a149d10b9
--- /dev/null
+++ b/src/App/Repositories/AuthApiRepository.cs
@@ -0,0 +1,52 @@
+using System;
+using System.Net.Http;
+using System.Threading.Tasks;
+using Bit.App.Abstractions;
+using Bit.App.Models.Api;
+using Newtonsoft.Json;
+
+namespace Bit.App.Repositories
+{
+    public class AuthApiRepository : BaseApiRepository, IAuthApiRepository
+    {
+        protected override string ApiRoute => "auth";
+
+        public virtual async Task<ApiResult<TokenResponse>> PostTokenAsync(TokenRequest requestObj)
+        {
+            var requestMessage = new TokenHttpRequestMessage(requestObj)
+            {
+                Method = HttpMethod.Post,
+                RequestUri = new Uri(Client.BaseAddress, string.Concat(ApiRoute, "/token")),
+            };
+
+            var response = await Client.SendAsync(requestMessage);
+            if(!response.IsSuccessStatusCode)
+            {
+                return await HandleErrorAsync<TokenResponse>(response);
+            }
+
+            var responseContent = await response.Content.ReadAsStringAsync();
+            var responseObj = JsonConvert.DeserializeObject<TokenResponse>(responseContent);
+            return ApiResult<TokenResponse>.Success(responseObj, response.StatusCode);
+        }
+
+        public virtual async Task<ApiResult<TokenResponse>> PostTokenTwoFactorAsync(TokenTwoFactorRequest requestObj)
+        {
+            var requestMessage = new TokenHttpRequestMessage(requestObj)
+            {
+                Method = HttpMethod.Post,
+                RequestUri = new Uri(Client.BaseAddress, string.Concat(ApiRoute, "/token/two-factor")),
+            };
+
+            var response = await Client.SendAsync(requestMessage);
+            if(!response.IsSuccessStatusCode)
+            {
+                return await HandleErrorAsync<TokenResponse>(response);
+            }
+
+            var responseContent = await response.Content.ReadAsStringAsync();
+            var responseObj = JsonConvert.DeserializeObject<TokenResponse>(responseContent);
+            return ApiResult<TokenResponse>.Success(responseObj, response.StatusCode);
+        }
+    }
+}
diff --git a/src/App/Services/ApiService.cs b/src/App/Repositories/BaseApiRepository.cs
similarity index 76%
rename from src/App/Services/ApiService.cs
rename to src/App/Repositories/BaseApiRepository.cs
index 306d809dc..e8fe811d4 100644
--- a/src/App/Services/ApiService.cs
+++ b/src/App/Repositories/BaseApiRepository.cs
@@ -1,23 +1,27 @@
 using System;
 using System.Collections.Generic;
+using System.Linq;
 using System.Net.Http;
+using System.Net.Http.Headers;
+using System.Text;
 using System.Threading.Tasks;
-using Bit.App.Abstractions;
 using Bit.App.Models.Api;
 using ModernHttpClient;
 using Newtonsoft.Json;
 
-namespace Bit.App.Services
+namespace Bit.App.Repositories
 {
-    public class ApiService : IApiService
+    public abstract class BaseApiRepository
     {
-        public ApiService()
+        public BaseApiRepository()
         {
             Client = new HttpClient(new NativeMessageHandler());
             Client.BaseAddress = new Uri("https://api.bitwarden.com");
+            Client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
         }
 
-        public HttpClient Client { get; set; }
+        protected virtual HttpClient Client { get; private set; }
+        protected abstract string ApiRoute { get; }
 
         public async Task<ApiResult<T>> HandleErrorAsync<T>(HttpResponseMessage response)
         {
diff --git a/src/App/Repositories/FolderApiRepository.cs b/src/App/Repositories/FolderApiRepository.cs
new file mode 100644
index 000000000..7e116833f
--- /dev/null
+++ b/src/App/Repositories/FolderApiRepository.cs
@@ -0,0 +1,34 @@
+using System;
+using System.Collections.Generic;
+using System.Net.Http;
+using System.Threading.Tasks;
+using Bit.App.Abstractions;
+using Bit.App.Models.Api;
+using Newtonsoft.Json;
+
+namespace Bit.App.Repositories
+{
+    public class FolderApiRepository : ApiRepository<FolderRequest, FolderResponse, string>, IFolderApiRepository
+    {
+        protected override string ApiRoute => "folders";
+
+        public virtual async Task<ApiResult<ListResponse<FolderResponse>>> GetByRevisionDateAsync(DateTime since)
+        {
+            var requestMessage = new TokenHttpRequestMessage()
+            {
+                Method = HttpMethod.Get,
+                RequestUri = new Uri(Client.BaseAddress, string.Concat(ApiRoute, "?since=", since)),
+            };
+
+            var response = await Client.SendAsync(requestMessage);
+            if(!response.IsSuccessStatusCode)
+            {
+                return await HandleErrorAsync<ListResponse<FolderResponse>>(response);
+            }
+
+            var responseContent = await response.Content.ReadAsStringAsync();
+            var responseObj = JsonConvert.DeserializeObject<ListResponse<FolderResponse>>(responseContent);
+            return ApiResult<ListResponse<FolderResponse>>.Success(responseObj, response.StatusCode);
+        }
+    }
+}
diff --git a/src/App/Repositories/FolderRepository.cs b/src/App/Repositories/FolderRepository.cs
new file mode 100644
index 000000000..0b636caf7
--- /dev/null
+++ b/src/App/Repositories/FolderRepository.cs
@@ -0,0 +1,22 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Bit.App.Abstractions;
+using Bit.App.Models.Data;
+
+namespace Bit.App.Repositories
+{
+    public class FolderRepository : Repository<FolderData, string>, IFolderRepository
+    {
+        public FolderRepository(ISqlService sqlService)
+            : base(sqlService)
+        { }
+
+        public Task<IEnumerable<FolderData>> GetAllByUserIdAsync(string userId)
+        {
+            var folders = Connection.Table<FolderData>().Where(f => f.UserId == userId).Cast<FolderData>();
+            return Task.FromResult(folders);
+        }
+    }
+}
diff --git a/src/App/Services/Repository.cs b/src/App/Repositories/Repository.cs
similarity index 70%
rename from src/App/Services/Repository.cs
rename to src/App/Repositories/Repository.cs
index c549a8ed4..b6a7146b6 100644
--- a/src/App/Services/Repository.cs
+++ b/src/App/Repositories/Repository.cs
@@ -5,9 +5,9 @@ using System.Threading.Tasks;
 using Bit.App.Abstractions;
 using SQLite;
 
-namespace Bit.App.Services
+namespace Bit.App.Repositories
 {
-    public abstract class Repository<T, TId>
+    public abstract class Repository<T, TId> : IRepository<T, TId>
         where TId : IEquatable<TId> 
         where T : class, IDataObject<TId>, new()
     {
@@ -18,34 +18,34 @@ namespace Bit.App.Services
 
         protected SQLiteConnection Connection { get; private set; }
 
-        protected virtual Task<T> GetByIdAsync(TId id)
+        public virtual Task<T> GetByIdAsync(TId id)
         {
             return Task.FromResult(Connection.Get<T>(id));
         }
 
-        protected virtual Task<IEnumerable<T>> GetAllAsync()
+        public virtual Task<IEnumerable<T>> GetAllAsync()
         {
             return Task.FromResult(Connection.Table<T>().Cast<T>());
         }
 
-        protected virtual Task CreateAsync(T obj)
+        public virtual Task InsertAsync(T obj)
         {
             Connection.Insert(obj);
             return Task.FromResult(0);
         }
 
-        protected virtual Task ReplaceAsync(T obj)
+        public virtual Task UpdateAsync(T obj)
         {
             Connection.Update(obj);
             return Task.FromResult(0);
         }
 
-        protected virtual async Task DeleteAsync(T obj)
+        public virtual async Task DeleteAsync(T obj)
         {
             await DeleteAsync(obj.Id);
         }
 
-        protected virtual Task DeleteAsync(TId id)
+        public virtual Task DeleteAsync(TId id)
         {
             Connection.Delete<T>(id);
             return Task.FromResult(0);
diff --git a/src/App/Repositories/SiteApiRepository.cs b/src/App/Repositories/SiteApiRepository.cs
new file mode 100644
index 000000000..67f746b65
--- /dev/null
+++ b/src/App/Repositories/SiteApiRepository.cs
@@ -0,0 +1,34 @@
+using System;
+using System.Collections.Generic;
+using System.Net.Http;
+using System.Threading.Tasks;
+using Bit.App.Abstractions;
+using Bit.App.Models.Api;
+using Newtonsoft.Json;
+
+namespace Bit.App.Repositories
+{
+    public class SiteApiRepository : ApiRepository<SiteRequest, SiteResponse, string>, ISiteApiRepository
+    {
+        protected override string ApiRoute => "sites";
+
+        public virtual async Task<ApiResult<ListResponse<SiteResponse>>> GetByRevisionDateAsync(DateTime since)
+        {
+            var requestMessage = new TokenHttpRequestMessage()
+            {
+                Method = HttpMethod.Get,
+                RequestUri = new Uri(Client.BaseAddress, string.Concat(ApiRoute, "?since=", since)),
+            };
+
+            var response = await Client.SendAsync(requestMessage);
+            if(!response.IsSuccessStatusCode)
+            {
+                return await HandleErrorAsync<ListResponse<SiteResponse>>(response);
+            }
+
+            var responseContent = await response.Content.ReadAsStringAsync();
+            var responseObj = JsonConvert.DeserializeObject<ListResponse<SiteResponse>>(responseContent);
+            return ApiResult<ListResponse<SiteResponse>>.Success(responseObj, response.StatusCode);
+        }
+    }
+}
diff --git a/src/App/Repositories/SiteRepository.cs b/src/App/Repositories/SiteRepository.cs
new file mode 100644
index 000000000..1c76cd0b3
--- /dev/null
+++ b/src/App/Repositories/SiteRepository.cs
@@ -0,0 +1,22 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Bit.App.Abstractions;
+using Bit.App.Models.Data;
+
+namespace Bit.App.Repositories
+{
+    public class SiteRepository : Repository<SiteData, string>, ISiteRepository
+    {
+        public SiteRepository(ISqlService sqlService)
+            : base(sqlService)
+        { }
+
+        public Task<IEnumerable<SiteData>> GetAllByUserIdAsync(string userId)
+        {
+            var sites = Connection.Table<SiteData>().Where(f => f.UserId == userId).Cast<SiteData>();
+            return Task.FromResult(sites);
+        }
+    }
+}
diff --git a/src/App/Services/AuthService.cs b/src/App/Services/AuthService.cs
index 53b6f49d5..988633db2 100644
--- a/src/App/Services/AuthService.cs
+++ b/src/App/Services/AuthService.cs
@@ -17,7 +17,7 @@ namespace Bit.App.Services
         private readonly ISecureStorageService _secureStorage;
         private readonly ISettings _settings;
         private readonly ICryptoService _cryptoService;
-        private readonly IApiService _apiService;
+        private readonly IAuthApiRepository _authApiRepository;
 
         private string _token;
         private string _userId;
@@ -26,12 +26,12 @@ namespace Bit.App.Services
             ISecureStorageService secureStorage,
             ISettings settings,
             ICryptoService cryptoService,
-            IApiService apiService)
+            IAuthApiRepository authApiRepository)
         {
             _secureStorage = secureStorage;
             _settings = settings;
             _cryptoService = cryptoService;
-            _apiService = apiService;
+            _authApiRepository = authApiRepository;
         }
 
         public string Token
@@ -110,16 +110,8 @@ namespace Bit.App.Services
 
         public async Task<ApiResult<TokenResponse>> TokenPostAsync(TokenRequest request)
         {
-            var requestContent = JsonConvert.SerializeObject(request);
-            var response = await _apiService.Client.PostAsync("/auth/token", new StringContent(requestContent, Encoding.UTF8, "application/json"));
-            if(!response.IsSuccessStatusCode)
-            {
-                return await _apiService.HandleErrorAsync<TokenResponse>(response);
-            }
-
-            var responseContent = await response.Content.ReadAsStringAsync();
-            var responseObj = JsonConvert.DeserializeObject<TokenResponse>(responseContent);
-            return ApiResult<TokenResponse>.Success(responseObj, response.StatusCode);
+            // TODO: move more logic in here
+            return await _authApiRepository.PostTokenAsync(request);
         }
     }
 }
diff --git a/src/App/Services/FolderService.cs b/src/App/Services/FolderService.cs
index 9cfc77ead..f1e7c17fa 100644
--- a/src/App/Services/FolderService.cs
+++ b/src/App/Services/FolderService.cs
@@ -6,70 +6,73 @@ using Bit.App.Abstractions;
 using Bit.App.Models;
 using Bit.App.Models.Data;
 using Bit.App.Models.Api;
-using Newtonsoft.Json;
-using System.Net.Http;
 
 namespace Bit.App.Services
 {
-    public class FolderService : Repository<FolderData, string>, IFolderService
+    public class FolderService : IFolderService
     {
+        private readonly IFolderRepository _folderRepository;
         private readonly IAuthService _authService;
-        private readonly IApiService _apiService;
+        private readonly IFolderApiRepository _folderApiRepository;
 
         public FolderService(
-            ISqlService sqlService,
+            IFolderRepository folderRepository,
             IAuthService authService,
-            IApiService apiService)
-            : base(sqlService)
+            IFolderApiRepository folderApiRepository)
         {
+            _folderRepository = folderRepository;
             _authService = authService;
-            _apiService = apiService;
+            _folderApiRepository = folderApiRepository;
         }
 
-        public new Task<Folder> GetByIdAsync(string id)
+        public async Task<Folder> GetByIdAsync(string id)
         {
-            var data = Connection.Table<FolderData>().Where(f => f.UserId == _authService.UserId && f.Id == id).FirstOrDefault();
+            var data = await _folderRepository.GetByIdAsync(id);
+            if(data == null || data.UserId != _authService.UserId)
+            {
+                return null;
+            }
+
             var folder = new Folder(data);
-            return Task.FromResult(folder);
+            return folder;
         }
 
-        public new Task<IEnumerable<Folder>> GetAllAsync()
+        public async Task<IEnumerable<Folder>> GetAllAsync()
         {
-            var data = Connection.Table<FolderData>().Where(f => f.UserId == _authService.UserId).Cast<FolderData>();
+            var data = await _folderRepository.GetAllByUserIdAsync(_authService.UserId);
             var folders = data.Select(f => new Folder(f));
-            return Task.FromResult(folders);
+            return folders;
         }
 
         public async Task<ApiResult<FolderResponse>> SaveAsync(Folder folder)
         {
+            ApiResult<FolderResponse> response = null;
             var request = new FolderRequest(folder);
-            var requestMessage = new TokenHttpRequestMessage(request)
-            {
-                Method = folder.Id == null ? HttpMethod.Post : HttpMethod.Put,
-                RequestUri = new Uri(_apiService.Client.BaseAddress, folder.Id == null ? "/folders" : $"/folders/{folder.Id}"),
-            };
-
-            var response = await _apiService.Client.SendAsync(requestMessage);
-            if(!response.IsSuccessStatusCode)
-            {
-                return await _apiService.HandleErrorAsync<FolderResponse>(response);
-            }
-
-            var responseContent = await response.Content.ReadAsStringAsync();
-            var responseObj = JsonConvert.DeserializeObject<FolderResponse>(responseContent);
-            var data = new FolderData(responseObj, _authService.UserId);
 
             if(folder.Id == null)
             {
-                await CreateAsync(data);
-                folder.Id = responseObj.Id;
+                response = await _folderApiRepository.PostAsync(request);
             }
             else
             {
-                await ReplaceAsync(data);
+                response = await _folderApiRepository.PutAsync(folder.Id, request);
             }
 
-            return ApiResult<FolderResponse>.Success(responseObj, response.StatusCode);
+            if(response.Succeeded)
+            {
+                var data = new FolderData(response.Result, _authService.UserId);
+                if(folder.Id == null)
+                {
+                    await _folderRepository.InsertAsync(data);
+                    folder.Id = data.Id;
+                }
+                else
+                {
+                    await _folderRepository.UpdateAsync(data);
+                }
+            }
+
+            return response;
         }
     }
 }
diff --git a/src/App/Services/SiteService.cs b/src/App/Services/SiteService.cs
index 83aa7fc71..f993d2e6f 100644
--- a/src/App/Services/SiteService.cs
+++ b/src/App/Services/SiteService.cs
@@ -1,86 +1,89 @@
 using System;
 using System.Collections.Generic;
 using System.Linq;
-using System.Net.Http;
 using System.Threading.Tasks;
 using Bit.App.Abstractions;
 using Bit.App.Models;
 using Bit.App.Models.Api;
 using Bit.App.Models.Data;
-using Newtonsoft.Json;
 
 namespace Bit.App.Services
 {
-    public class SiteService : Repository<SiteData, string>, ISiteService
+    public class SiteService : ISiteService
     {
+        private readonly ISiteRepository _siteRepository;
         private readonly IAuthService _authService;
-        private readonly IApiService _apiService;
+        private readonly ISiteApiRepository _siteApiRepository;
 
         public SiteService(
-            ISqlService sqlService,
+            ISiteRepository siteRepository,
             IAuthService authService,
-            IApiService apiService)
-            : base(sqlService)
+            ISiteApiRepository siteApiRepository)
         {
+            _siteRepository = siteRepository;
             _authService = authService;
-            _apiService = apiService;
+            _siteApiRepository = siteApiRepository;
         }
 
-        public new Task<IEnumerable<Site>> GetAllAsync()
+        public async Task<Site> GetByIdAsync(string id)
         {
-            var data = Connection.Table<SiteData>().Where(f => f.UserId == _authService.UserId).Cast<SiteData>();
-            var sites = data.Select(s => new Site(s));
-            return Task.FromResult(sites);
+            var data = await _siteRepository.GetByIdAsync(id);
+            if(data == null || data.UserId != _authService.UserId)
+            {
+                return null;
+            }
+
+            var site = new Site(data);
+            return site;
+        }
+
+        public async Task<IEnumerable<Site>> GetAllAsync()
+        {
+            var data = await _siteRepository.GetAllByUserIdAsync(_authService.UserId);
+            var sites = data.Select(f => new Site(f));
+            return sites;
         }
 
         public async Task<ApiResult<SiteResponse>> SaveAsync(Site site)
         {
+            ApiResult<SiteResponse> response = null;
             var request = new SiteRequest(site);
-            var requestMessage = new TokenHttpRequestMessage(request)
-            {
-                Method = site.Id == null ? HttpMethod.Post : HttpMethod.Put,
-                RequestUri = new Uri(_apiService.Client.BaseAddress, site.Id == null ? "/sites" : $"/folders/{site.Id}")
-            };
-
-            var response = await _apiService.Client.SendAsync(requestMessage);
-            if(!response.IsSuccessStatusCode)
-            {
-                return await _apiService.HandleErrorAsync<SiteResponse>(response);
-            }
-
-            var responseContent = await response.Content.ReadAsStringAsync();
-            var responseObj = JsonConvert.DeserializeObject<SiteResponse>(responseContent);
-            var data = new SiteData(responseObj, _authService.UserId);
 
             if(site.Id == null)
             {
-                await base.CreateAsync(data);
-                site.Id = responseObj.Id;
+                response = await _siteApiRepository.PostAsync(request);
             }
             else
             {
-                await base.ReplaceAsync(data);
+                response = await _siteApiRepository.PutAsync(site.Id, request);
             }
 
-            return ApiResult<SiteResponse>.Success(responseObj, response.StatusCode);
+            if(response.Succeeded)
+            {
+                var data = new SiteData(response.Result, _authService.UserId);
+                if(site.Id == null)
+                {
+                    await _siteRepository.InsertAsync(data);
+                    site.Id = data.Id;
+                }
+                else
+                {
+                    await _siteRepository.UpdateAsync(data);
+                }
+            }
+
+            return response;
         }
 
-        public new async Task<ApiResult<object>> DeleteAsync(string id)
+        public async Task<ApiResult<object>> DeleteAsync(string id)
         {
-            var requestMessage = new TokenHttpRequestMessage
+            ApiResult<object> response = await _siteApiRepository.DeleteAsync(id);
+            if(response.Succeeded)
             {
-                Method = HttpMethod.Delete,
-                RequestUri = new Uri(_apiService.Client.BaseAddress, $"/sites/{id}")
-            };
-
-            var response = await _apiService.Client.SendAsync(requestMessage);
-            if(!response.IsSuccessStatusCode)
-            {
-                return await _apiService.HandleErrorAsync<object>(response);
+                await _siteRepository.DeleteAsync(id);
             }
 
-            await base.DeleteAsync(id);
-            return ApiResult<object>.Success(null, response.StatusCode);
+            return response;
         }
     }
 }
diff --git a/src/App/Services/SyncService.cs b/src/App/Services/SyncService.cs
new file mode 100644
index 000000000..46e05629d
--- /dev/null
+++ b/src/App/Services/SyncService.cs
@@ -0,0 +1,108 @@
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+using Bit.App.Abstractions;
+using Bit.App.Models.Data;
+
+namespace Bit.App.Services
+{
+    public class SyncService : ISyncService
+    {
+        private readonly IFolderApiRepository _folderApiRepository;
+        private readonly ISiteApiRepository _siteApiRepository;
+        private readonly IFolderRepository _folderRepository;
+        private readonly ISiteRepository _siteRepository;
+        private readonly IAuthService _authService;
+
+        public SyncService(
+            IFolderApiRepository folderApiRepository,
+            ISiteApiRepository siteApiRepository,
+            IFolderRepository folderRepository,
+            ISiteRepository siteRepository,
+            IAuthService authService)
+        {
+            _folderApiRepository = folderApiRepository;
+            _siteApiRepository = siteApiRepository;
+            _folderRepository = folderRepository;
+            _siteRepository = siteRepository;
+            _authService = authService;
+        }
+
+        public async Task<bool> SyncAsync()
+        {
+            // TODO: store now in settings and only fetch from last time stored
+            var now = DateTime.UtcNow.AddYears(-100);
+
+            var siteTask = await SyncSitesAsync(now);
+            var folderTask = await SyncFoldersAsync(now);
+
+            return siteTask && folderTask;
+        }
+
+        private async Task<bool> SyncFoldersAsync(DateTime now)
+        {
+            var folderResponse = await _folderApiRepository.GetAsync();
+            if(!folderResponse.Succeeded)
+            {
+                return false;
+            }
+
+            var serverFolders = folderResponse.Result.Data;
+            var folders = await _folderRepository.GetAllByUserIdAsync(_authService.UserId);
+
+            foreach(var serverFolder in serverFolders.Where(f => f.RevisionDate >= now))
+            {
+                var data = new FolderData(serverFolder, _authService.UserId);
+                var existingLocalFolder = folders.SingleOrDefault(f => f.Id == serverFolder.Id);
+                if(existingLocalFolder == null)
+                {
+                    await _folderRepository.InsertAsync(data);
+                }
+                else
+                {
+                    await _folderRepository.UpdateAsync(data);
+                }
+            }
+
+            foreach(var folder in folders.Where(localFolder => !serverFolders.Any(serverFolder => serverFolder.Id == localFolder.Id)))
+            {
+                await _folderRepository.DeleteAsync(folder.Id);
+            }
+
+            return true;
+        }
+
+        private async Task<bool> SyncSitesAsync(DateTime now)
+        {
+            var siteResponse = await _siteApiRepository.GetAsync();
+            if(!siteResponse.Succeeded)
+            {
+                return false;
+            }
+
+            var serverSites = siteResponse.Result.Data;
+            var sites = await _siteRepository.GetAllByUserIdAsync(_authService.UserId);
+
+            foreach(var serverSite in serverSites.Where(s => s.RevisionDate >= now))
+            {
+                var data = new SiteData(serverSite, _authService.UserId);
+                var existingLocalSite = sites.SingleOrDefault(s => s.Id == serverSite.Id);
+                if(existingLocalSite == null)
+                {
+                    await _siteRepository.InsertAsync(data);
+                }
+                else
+                {
+                    await _siteRepository.UpdateAsync(data);
+                }
+            }
+
+            foreach(var site in sites.Where(localSite => !serverSites.Any(serverSite => serverSite.Id == localSite.Id)))
+            {
+                await _siteRepository.DeleteAsync(site.Id);
+            }
+
+            return true;
+        }
+    }
+}
diff --git a/src/iOS/AppDelegate.cs b/src/iOS/AppDelegate.cs
index c09c7e4ba..56bd49a93 100644
--- a/src/iOS/AppDelegate.cs
+++ b/src/iOS/AppDelegate.cs
@@ -13,6 +13,7 @@ using Bit.iOS.Services;
 using Plugin.Settings;
 using Plugin.Connectivity;
 using Acr.UserDialogs;
+using Bit.App.Repositories;
 
 namespace Bit.iOS
 {
@@ -48,15 +49,23 @@ namespace Bit.iOS
             var container = new UnityContainer();
 
             container
-                .RegisterType<ISqlService, SqlService>(new ContainerControlledLifetimeManager())
+                // Services
                 .RegisterType<IDatabaseService, DatabaseService>(new ContainerControlledLifetimeManager())
+                .RegisterType<ISqlService, SqlService>(new ContainerControlledLifetimeManager())
                 .RegisterType<ISecureStorageService, KeyChainStorageService>(new ContainerControlledLifetimeManager())
-                .RegisterInstance(CrossSettings.Current, new ContainerControlledLifetimeManager())
-                .RegisterType<IApiService, ApiService>(new ContainerControlledLifetimeManager())
                 .RegisterType<ICryptoService, CryptoService>(new ContainerControlledLifetimeManager())
                 .RegisterType<IAuthService, AuthService>(new ContainerControlledLifetimeManager())
                 .RegisterType<IFolderService, FolderService>(new ContainerControlledLifetimeManager())
                 .RegisterType<ISiteService, SiteService>(new ContainerControlledLifetimeManager())
+                .RegisterType<ISyncService, SyncService>(new ContainerControlledLifetimeManager())
+                // Repositories
+                .RegisterType<IFolderRepository, FolderRepository>(new ContainerControlledLifetimeManager())
+                .RegisterType<IFolderApiRepository, FolderApiRepository>(new ContainerControlledLifetimeManager())
+                .RegisterType<ISiteRepository, SiteRepository>(new ContainerControlledLifetimeManager())
+                .RegisterType<ISiteApiRepository, SiteApiRepository>(new ContainerControlledLifetimeManager())
+                .RegisterType<IAuthApiRepository, AuthApiRepository>(new ContainerControlledLifetimeManager())
+                // Other
+                .RegisterInstance(CrossSettings.Current, new ContainerControlledLifetimeManager())
                 .RegisterInstance(CrossConnectivity.Current, new ContainerControlledLifetimeManager())
                 .RegisterInstance(UserDialogs.Instance, new ContainerControlledLifetimeManager());
 
diff --git a/src/iOS/Info.plist b/src/iOS/Info.plist
index ffbb5beaa..a27212cb0 100644
--- a/src/iOS/Info.plist
+++ b/src/iOS/Info.plist
@@ -26,7 +26,7 @@
 	<key>CFBundleDisplayName</key>
 	<string>bitwarden</string>
 	<key>CFBundleIdentifier</key>
-	<string>com.bitwarden.bitwarden</string>
+	<string>com.bitwarden.vault</string>
 	<key>CFBundleVersion</key>
 	<string>1.0</string>
 	<key>CFBundleIconFiles</key>
diff --git a/src/iOS/Resources/fa-cogs.png b/src/iOS/Resources/fa-cogs.png
new file mode 100644
index 0000000000000000000000000000000000000000..436f9683f8e80ea9d4dcd283fdc99e6567037cbb
GIT binary patch
literal 705
zcmeAS@N?(olHy`uVBq!ia0vp^Vj#@H3?x5i&EaHVU{nk632_B-M*+bQV7vC^BT!Ld
zNswPK10xeN3o9Et2PY3NzmSNin7D+bjJ&d%x|X(%o|(CgqmzrPyQi18e_&WdY+QVH
zZhm2Lc}+u8Yg^yMDYND-Sg~sL`VCvQ?%H?Y;L+nJPMte{`RdJE4<0>v@$&u0&tHH3
z{##_Y{u$7+m!2+;Arg{H4^|p86*3%u_<sFV6_xNlnVrS+EuB&(u24~te*66Ie$L&-
zDQfkS=NJFqq1)EK%5keTtK8K|=hl00oejD4P&M67>3+$6sTgfJe}@pZpierFrZp(}
z^xm1nn|sx*`IF9v+z2y0Q|0-U$NOev%&gX&Y<zqAG@IY59A2-Q?`(={=(1ruv062H
zx#R?q#$WU5B-(qHUJzByeC%-Ya6?UU!*7-yhIc<6m_0FHROUeA&Uv2?J@MRn{ls>=
zJSh>A***svc$R#5`Y72w==-zRvu0+zJ9g!ngvRUX&!;=yei>qYS#`B@{a4Oa>x_e+
Smb9mVqQTSE&t;ucLK6UPw6g60

literal 0
HcmV?d00001

diff --git a/src/iOS/Resources/fa-cogs@2x.png b/src/iOS/Resources/fa-cogs@2x.png
new file mode 100644
index 0000000000000000000000000000000000000000..4b5c927a1d90aa13c4ae30db01f057731342aac7
GIT binary patch
literal 1165
zcmeAS@N?(olHy`uVBq!ia0vp^Iv~u!3?wz9Rv9xeFg6GHgt!8^qhMe|fbmWI8K4Uq
zN`m}?85o(ESy<WFIXJnvc?AT8ghj+9q-5k26qVJ~HMO*LboKNN49zVpt*mWq?HwGQ
zUEJKgef$CfgF-^X!XqN1W8x~RYwGG7np)dBI=g%N`X^4AI(^2hx$_n+Ub<@ahD}>`
z?m2Mq@X_O^&YU}c@$%K{H*ej!_u$F17cXCb{`&pr@4roQhdwYcFj;!KIEF|_UOo6b
z-n&rd_{Z;-o44wGk52K>DHrmc=%Tl?<xqs4;`J30if`Y1UH*H{=Jzw(Z$2)p`|_dY
zRs74Et!|fIuADRJZd2=&ZH{kSgr+vUJh=Wqrs^%%HNKsD8g^~^cj@;Hkt-9dc#5sB
zePItVugEg$o;lI@4|7D`<PwI@n)=7jpS)sl+Gurf5T~)`V_~gTdfOwc)lxf_ELA?(
zzQ$`twUOPzMM^u@AGrE1-DTdpy^K2B=KkVXv&Gwr>q=Z+xW?a^V)uT3s(NsFpYLJQ
zy0Q~DoxSHy5)Uw6+r}9sF@4MQh}}v5td*{woH+5M)<pApON>4|NMW^D+-BUOcFE`^
zr;~Wfx<hl;_nNM17mfZZS?qaE;@NKX&6UfXFCDxcd6jp{e!*20MSHl^X2{yFNV7Pc
zWzoMl&7#uuN#PY9_4REweX}0U2|vuyH%s)`y!8oM^JLz0FFd-^ZtH_kgXvFW4u;gs
zJZmgF=~rIb*V_yWttDrkiFHa(U3Xy4dffoqB{ROy@+hb;`BLYQVyF62FyPgHCK1uy
ze%$;jrNXCA-E{M|1^Z;i|2DB_;V1PE9D2vwY#Mg0vdBojA$#{cfyyn9l>##5qnPT_
zRAmp=ya|eZ_SS3t6tzjy_*g!#4n5>^@<&To(VsPEjMOG%eqSn-7IaKK<cfTnf&P;@
zzxi~wduk<!mA&F`-E?z3+kWw9yRLl;U%Br4lq1|Lw6$;MB}J-Marp4-GBSIaFm`L^
nw%#gKeO<FREGS-c`Twi>PtLgaypP)y1<F#Mu6{1-oD!M<bv$F^

literal 0
HcmV?d00001

diff --git a/src/iOS/Resources/fa-cogs@3x.png b/src/iOS/Resources/fa-cogs@3x.png
new file mode 100644
index 0000000000000000000000000000000000000000..da8fe3925fb2b79161eab6063bbe2a68fc6cd1b6
GIT binary patch
literal 1660
zcmeAS@N?(olHy`uVBq!ia0vp^P9V&|3?#2~eYnNI!1yx2C&U%V9R(vH1TwOwE(H2>
zQAv<rFaskKGYcylI|nBhHxDl#zo3w?h^UyjgtUyDyn>RlimIBrrk1v@p1y&hk%_69
zg_X69t)0E2le3GPyQi18kFS3~P)KN4M08ASTw+pkN?LkGc1~`7L1A%8c|~<ieM3`o
zYg<QWPyfV8Q>M<EJ$K%MMN3z$Ub}w7rY&2y@7%R#-+_aNj~+j9=G^&<m#<#GdF#%-
z2aldSd-3x1+xH(ofBpXR_uu=+XFOnFU|!(q;us<!`S#$;_>fQrwg=zu%w%-eGf?4k
zT#`CXYQof=M;3*71bR+;BE%IqMYY>$DF<7dg23z(T8D++$KSGjK5u6AyPm^;+RoeY
z$K6_b;3>P9%EjN#9J>o&SyqZ)(ob0vrjXsQQOo>5W~pGxTeGQ?*M|oEl#Q&OT-QEf
zZ@P(Yg7~WptM)HWT@GDUUK`c3r$|k>Cam>e$<tt$XVFhx26l0eJXhw5jW$`9A0our
z%{6jG9B%D)%S`@|&ApN@;5L81?^?li{wr>NVDVlsXW=)!DU$>kX5U&}@OE*Ua^im>
zYZjUNLBD1AUZ<aX%4oJ#Xou33oFh~7C&`><c9Gv|&7?6$VP^=(>?qwB`Jl}Ur{)HB
zh1Tvo)$+V|`8JKUA}qU-Ld2XWFl`RGwu`AiG{4BbySgDDkX3eZE5n;aE3Gc4j?H2-
z586-Ix7?)Uwc^s%1})oW99-4#xOSG`fsms-aqT94msM)?irvK(re0bvV#oZ&$85g+
zeH%}I^C@Op)BS2(m-5;P9=m&+ed!Oo$2l6tQ|eeRcx8&&rLT;2I#hmT)k2o_lVk)L
zjtHE5?fB!&#j8ArOLr|>Ao|7jZ0i)`&+dvsE>Auzf4jKEw(Yz`?A_j}7iB!eV#Ih3
zo5(brmo_O;mX%8SApF`yK~}12!V^ATkM=e(m7R{vmli%`n{;Oh<I4*dx>agH(q=3*
zN7Q&dme<anclbl)?6%(?`T0MO+g~)*OAwe`^7m%VCLJD4hHwVWWiPW@7tUR0m~pYa
zJJDm|%85ZLmcDCQ3l~kj#UAkOj*?{}qd>n$S=+<O=XXpj(s<f0Dc<;LN|1Sjnb+qJ
zFPEq9T)c8+^2~XOj4NADojt!UFthO`SNRntO|ShLJjZ7o%=Az;={=-yZRWu;KABJx
z+4hz5C4D$7ZoRm%uX^ood#kz@O_Rg2x^0SYHc45v9bzc@{c?roW>%p?pQl{w3p;O^
zxa`*hp`}+3Jy3}K%;<4VB$nCnNNc+1KUua5o@==s&M};v%8`?m{=mxbteew2i`lF)
zyjS!NDXuFx#g>y52udCmYnU|LSJtvDbzs=Aa_XT_pR;bOlCqwDxN&9HgIx|lj=)v}
zn?>u2XI%;tT=J*X>+<&3EO7}9r<heXC#zh)CoFJ0%=6f%iPHimtZF-@HFfii)~Z=j
z6Kps7T<&tXc5mT*!IZV_rzVE;<p#fUy!LNV2>*g<muyqkKa%_@y@ao5q0sI`(=S{e
gi=K0H>o5Hw-`aTL=X1rUCqN~rr>mdKI;Vst0FMU;{Qv*}

literal 0
HcmV?d00001

diff --git a/src/iOS/Resources/fa-lock.png b/src/iOS/Resources/fa-lock.png
new file mode 100644
index 0000000000000000000000000000000000000000..00856ab8546ab6ebef82343d175db37819c2de05
GIT binary patch
literal 388
zcmeAS@N?(olHy`uVBq!ia0vp^Vj#@H3?x5i&EW)6T>(BJu0VQ_0AVH$eV|ixOM?7@
z85o&ZSUI_Qghj+9WEGUvwG1q*9Yf<%a!SfuJEzZEuxRmyorllgx%cAr*WZ7a7$x@t
zHP(2#IEF}spS^fn=%9g!+d~#d&8}&SuKc{R?t8uM;*%khW)@}NeN(^hw(gPZzfRv(
zl>N19k=EqCv>8Rw%L`<^CF2yf{Wuf6H~;y$w~KQ<zIr)az0<lgPLRd)d&08oENu1_
zTUswKd&Tkl#0Tc(mki?8);~U!{JE?3@zW@i?@aoc>Lx*5{tck8^mO%eS?83{1OS^O
BWqSYs

literal 0
HcmV?d00001

diff --git a/src/iOS/Resources/fa-lock@2x.png b/src/iOS/Resources/fa-lock@2x.png
new file mode 100644
index 0000000000000000000000000000000000000000..071a2073295a21f705f5b9fc3ee38d470d3ee6eb
GIT binary patch
literal 587
zcmeAS@N?(olHy`uVBq!ia0vp^Iv~u!3?wz9Rv81SO94J1u0VR2fh+5xZUMdIS`y?J
z%)rRR!pg?M$;HhhEFvx?qoAy=simWDVqs}*XYb_Z>E$039v7dKl9O9lT2a~1+CO{g
zhD|&79X@*e)WrwSUcCSK{pat$hYLfqfd)SIba4!^IQ@3=b-pG85eLTa_bdvu-|hYG
z@caM%lsy?m+O1hNmkRDxz4M&BZKXm@t9EFP$>~kMdqd_;W<RZ?&a1<bB)siTn6gOt
z(LK5X%%+>oyd+MjZ7;ZVeD#)Sk(l$%S$1i6t}dVY+^W9yYsb~9uVQz@oy-~CVwybp
zwM9eb3Z8%0+%2sAvZ6fNjc0dHra6PAWEA@kEnU$Ga+5zl&6vp|X>z3QgJje($F_e8
zeOrz_c<$V#dBRh@EqZ2w#)68o-TdqQb31={>fGEY``>5J<E`mmf<xl&xNlq$^=@ze
c;`py-=E5KNB|dsR@&P3YPgg&ebxsLQ0CJz37XSbN

literal 0
HcmV?d00001

diff --git a/src/iOS/Resources/fa-lock@3x.png b/src/iOS/Resources/fa-lock@3x.png
new file mode 100644
index 0000000000000000000000000000000000000000..eed9ef096a46b99f2eb2506e25a90fb079b712e3
GIT binary patch
literal 820
zcmeAS@N?(olHy`uVBq!ia0vp^P9V&|3?#2~eYnNIz$hEw6XFWwjsjdEaP(eNB2X@_
zB*-tAfsu)sg_Vt+gOi(wPe4dmL{v;%LP|zXQAJf<OIydl$i%|h*3Qw%*~K?7C@efO
zIwm$TIW0XqC%3Soy1uEowR6hsxl32CUB6-L_MHcho;q{x;^nK??>&0|@%zu;fA7yX
z-U*D73{Mxwkch)?XM{%|G7xFA);Se$r1#Oi#VZuLgulB4vb1D<{$Jk~uD({$GFM>z
znev(6ALnwaRtEY0zWl{+%^Nw9;@2-T(&zg>dDXIP<NPUNJaNKm<;g)zpJx;lCAOOL
zT;VX6U!2JM(qE~=TKt@W^8}q%!=BwUno^p0FREoJ_$dVhe!F^qlcx9eHBX*(=YI(0
zH(2viZh2*X#jm^4+Y6g7ev(@{PyRty(|V3oHx?ZA{#NI&dCOUpcZIFA&ZYN_TQ}U4
zSQy^5{cs0!fly$|!jdD4Z##5%JQDLVl-t@6CcyefF4V4$rM<$)=Sy|QExv+}TG!a8
z>e|J#-O~>_vS8DR=aLUJ7b@{hWwo2nWVc4-znkF{`^DP9Kj+M5e_+0pDZN@p;N+YQ
zZIMcILofcfsLAR%G%<X`YBzSaB#qV}2rBV^_^-}<^(pCDXXa&{*5<p(e0uVlg-P3F
ltKO_SQ*-6Bn|XY34fB;1$-efp&hG`qyr-+5%Q~loCIH<Q<LCeY

literal 0
HcmV?d00001

diff --git a/src/iOS/Resources/fa-plus.png b/src/iOS/Resources/fa-plus.png
new file mode 100644
index 0000000000000000000000000000000000000000..770cf597b88595d77f6f15f3287848a6f22d3855
GIT binary patch
literal 242
zcmeAS@N?(olHy`uVBq!ia0vp^Vj#@H3?x5i&EW)6mH|E?u0WbRu%|4*7N}FGB*-tA
zfsvEj$ja8iHz{-CvYq=b-}`z!CdM2n8|LZa7$OnA_nal)0R<kHz(ak9#qR&#u<_+0
zLCv32zuxIF{J+y_ivELBli1&#ddYKp#+H>e>o31PcQ8QAE%nyt=9m`e*SCJ1D-GK&
kU(qnTh=p%cLBxZ6{hjqZ{Q4|U13)hKboFyt=akR{05jx9(*OVf

literal 0
HcmV?d00001

diff --git a/src/iOS/Resources/fa-plus@2x.png b/src/iOS/Resources/fa-plus@2x.png
new file mode 100644
index 0000000000000000000000000000000000000000..87f33d4d205628f8245cccf579c5d4c4c076b470
GIT binary patch
literal 335
zcmeAS@N?(olHy`uVBq!ia0vp^Iv~u!3?wz9Rv81S@Bp6>S0GIbz&CwzF3?<=k|4ie
z21Zs+0TCsA3p)pQ-?YrOiHp{6*tBQg<$LeH{%(%APykfd?djqeV$u8dvL|1I0*^!B
zoWdiO2k!l!<76^zYVISq-ulRU>s&jcSzS}Lm%85$I2`Do%$jc4$$mt$)BVWJe*2EU
zD#fz|ub;EHyIw1*AvFE{?x&FqnIBFaU3xHv`>OZHBS%{gT3&v2U|oRO;>pLJ-<0In
xs=3tp@5jd-j&mkN%q#oosq^Wt`I=mp+pEpq9}m5u)xrr56;D?`mvv4FO#p;OW@-Qc

literal 0
HcmV?d00001

diff --git a/src/iOS/Resources/fa-plus@3x.png b/src/iOS/Resources/fa-plus@3x.png
new file mode 100644
index 0000000000000000000000000000000000000000..0240071db1151b4fd0cc1d626d980da2f3e1adf6
GIT binary patch
literal 418
zcmeAS@N?(olHy`uVBq!ia0vp^P9V&|3?#2~eYgdr@&kNAT!Az_LGG))Zb17~N`m}?
z85o(_1;nK^bo33a>>Qk&<I*zACr+KcW!Ij4hfmyl|MBb3-+yO*NzVtW-{<M#7!q;#
z?NxWaW(NV6i-(xip3mu>`Fs9FZuXmJvm_KFSNz}gb=uVf6FW7|#;NvgbT1J9c~#*G
zYw=Y3t*>14Ji}8LEo)itQ!<5PW<nFE+P{_^Hyn3lE^*no_v8KQS7(aK*D^48zcP_v
z(9(6XVo<YHVqBnpqNlT1sN7bG^}=gG!G;L$2o?qrsY9>q*bi&Z&b!<j*MEDqm3ies
zpn;vA1$b4NQWq_0S?g2bGDH6q%U@o;GLi4|<1ItQ`ra@;?O{&&A0;6T3TaPQKbLh*
G2~7Y6!+MJV

literal 0
HcmV?d00001

diff --git a/src/iOS/Resources/fa-refresh.png b/src/iOS/Resources/fa-refresh.png
new file mode 100644
index 0000000000000000000000000000000000000000..14053d2b5e49d62556e18f812e4b0a4e532b63c3
GIT binary patch
literal 595
zcmeAS@N?(olHy`uVBq!ia0vp^Vj#@H3?x5i&EW)6Zv%WnT!Hk+0eNQEZUcSmR}$nG
z%)rRR%)-jS&BMzlC?qT*DlQ=<uc)k|rmm@_t)r)JXl8C<Yv-1dTUcCLUQyH9(?4a#
zthw_RtX;oh)7AqAkDfYn?()^^Hy^xs`TFhqpTD=as4N7UeB9H;F+@Ug>cQ|JCPxO>
zi?^AT)dY^}Bs(2!uuKqhW#P&He|n>EXieZ#%bwJ2=c_M0&lElVgmc@kF1u@<@2V<%
zdoA`%FX&5&%{zB?jm-a3hgW%3%|1H2VgmDwbyrRbtLR!DShYl`G4mwztV_CqfpP2x
znOPF2mi9f|-!*gdu?r>Bd>P|EJ?o5HTI+C+^J3c)CWYrcD?aGPCY*dZ?W5T5OXpRl
zHO;=;dg|WdHL@}<cdZjN-Sb`ha8dL{?@on-kyaH_JoBVQkNskPVr=GqL*?oOP}1;p
L^>bP0l+XkK-IJBw

literal 0
HcmV?d00001

diff --git a/src/iOS/Resources/fa-refresh@2x.png b/src/iOS/Resources/fa-refresh@2x.png
new file mode 100644
index 0000000000000000000000000000000000000000..138dd599c6f9c8f6d7090894ae7ceb042679bb86
GIT binary patch
literal 960
zcmeAS@N?(olHy`uVBq!ia0vp^Iv~u!3?wz9Rv9xeFa`(sgt!8^qky_0u$Qwb8)#xd
zNswPK10xd)D;qlpCpRx2zmSNSgrt;=oV<dPvYM8*j;@}*p{bdLrInq7le3GfyO(cZ
zP)KBSOnhQWdVWD+aY<=iLu*HOU;otUGiJ?Ruz2Z;wd*%*+PZVkfuqMyoH=*#`ki|Z
z9zA*X;^phN?>~P2`u*qczrSOqumYoYji-xah=k<ZgOTBv9T?aytUoDosmUvPs;jGD
zph)<JchjO)?taj9B8*#P?X)Mq|NEz@c^c2$^5cP}U8VlNuGn1_%Y$lT=4nP)%-Qj)
zGxpwXP3~IWFdMb4r_GqYmp@;y`(EO${?ilYP1<r{YHVWu>BxjmyT*>qN9&}Hzna40
zw3F?>hl0Yao{IH)y-(OawZ8nNug1x8&)`e(JvSBO?e23MikcUSN4{92Wy*AE!RZ4`
zKH(C=6SR2lIC=FNGR}V-xKe4UTW>@Dyi?m)gL|zQ@2gFfoH5V9kwIe7rumJE=T)Q+
zuxfD|L|R{2dnkSGtS`PP6O`1{k{^rxGoE6%?)bHLibsF$dH1`BWgnZc-+?b3UW|5X
z6_yfjn7wp8uIzklFm<I(i_6)A8b0o}TCN^FrG1B|++DPZsdZzvLF#^^fR;luth<+7
z;$zkqT#&c3VaDbY7tEy@)%MOm>nvpvn|__c$tQbV-j<a!-CBZPFFRegXY-3i>rHze
z=~ZX<D_&Fl-p1JR{LRy5=CjOZ?F)L=UffbE=UK@;adPSOS>?}-EP70<e&0N{?|1Fz
euXl}^{-4QT>g2Dg5oTurN`;=TelF{r5}E+BHxNYt

literal 0
HcmV?d00001

diff --git a/src/iOS/Resources/fa-refresh@3x.png b/src/iOS/Resources/fa-refresh@3x.png
new file mode 100644
index 0000000000000000000000000000000000000000..4558159448ecdf5b646cbb5f7fb15324f0f6eced
GIT binary patch
literal 1269
zcmeAS@N?(olHy`uVBq!ia0vp^P9V&|3?#2~eYnNIz}O$)6XFWwj)K7x0{>?;F95ox
zr6kBNn1PXrnT3^&or9B$n}?T=Ur<O`L{v;%LP}ajR!&|)Nm)fzO<hY{M^{hZ(Ad<>
z!q(2g(aGJ@%iGt_KO{6FIyOErDJ>%>H@~2;w7jCauD+qExwW&qr*HbKxeFF8U9ook
zhD}?x?%cEQz`?^ukDoYm{^Hf^H*ei}@a)Cwx9>lG{`&pr@4r&Dmvk5ym?nC<IEF|_
zzCD;8EF8$ddSSP>fyRn#m!>9Jmks3t;Rl;oO~L}^FIsf0i%ZOji>0W3$Lvji_o^Bk
z?vb})f335v^!dGa>&|WKmAd9|-~7uekK;bCB8<bFUkj?X)$HHFS=tqQGVo!g^`2R7
z@5&W?cJDdC@lr*`ZCVO@)3?8mxQ>}G$cpZp#{S{5+s;if{HKI0r*Zzs`EzL35)*}q
z>8%adzwx~OqPETUycT2sIi=TT(Fwf`tM{JRDo~IUn-K8p*hSgsM<1Wb)*gPpQYoSP
z?~?o1Cl^*6@vB)jrQxk=?WNy>mwrwZVlqvCv9&-dXn7Zp;EF7*OOKs}MP@CRR0%M3
z>|ZW5<;!&*ffXK?9&;*5M6z*&>{;@<mCaQ7v_hj%^Sb-I0mhqn1T~}=ua?bno0iBV
zmfP5O8z?qKV8fRSfnClkRlOw{PbXUi1<YPDr9sudopEcWmoG!c!elPNV;ZG04Q3~2
z?F@6d;J%sXr*X&sf4{dM<@?h1=9+-(`&VnNRvf%EZTS%&WhI-%Z>@APy<*#MiCMKJ
zY6iX%ypnf$Be&Qo*@mE;&TXo^VqP-?cDo!;o)UPh<yIS$Y;}Mer?8;f%NfqBhkaZm
zmb{kHl1NfIB)E&qN=w3|i6d5Z7hAy51wYL+<}N?3&l}1U`K~?Yuyd8H3$N-uHHC|_
z7lw10dHHTpdho%dw(iA-z{Fkm8+`AV7SF3|6|-2e?T20IS(#nNc84cilH0LE?e2#K
zxi45>Z>`~oTVvR`dyT&yr+nDV4Rhj>WbJaA;yY8-w9M)sb-9_a_*_gnRAzSEV}7(?
zQcA?DcI~p<Mlbt*M>qdt4=)$wO$*BZ6|n!v)%QFV<qKI?U)fRr;NF$~GeL>(ZhwwW
dpKNvQV!YJNW3@b-DSJU#*VEO{Wt~$(696vseC_}M

literal 0
HcmV?d00001

diff --git a/src/iOS/iOS.csproj b/src/iOS/iOS.csproj
index ff83fd540..6489af708 100644
--- a/src/iOS/iOS.csproj
+++ b/src/iOS/iOS.csproj
@@ -50,6 +50,9 @@
     <CodesignKey>iPhone Developer</CodesignKey>
     <MtouchDebug>true</MtouchDebug>
     <CodesignEntitlements>Entitlements.plist</CodesignEntitlements>
+    <CodesignProvision>2ae5608a-6142-4e1d-9344-326d1982b392</CodesignProvision>
+    <CodesignResourceRules />
+    <CodesignExtraArgs />
   </PropertyGroup>
   <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|iPhone' ">
     <DebugType>none</DebugType>
@@ -205,6 +208,42 @@
       <Name>App</Name>
     </ProjectReference>
   </ItemGroup>
+  <ItemGroup>
+    <BundleResource Include="Resources\fa-refresh.png" />
+  </ItemGroup>
+  <ItemGroup>
+    <BundleResource Include="Resources\fa-refresh%403x.png" />
+  </ItemGroup>
+  <ItemGroup>
+    <BundleResource Include="Resources\fa-refresh%402x.png" />
+  </ItemGroup>
+  <ItemGroup>
+    <BundleResource Include="Resources\fa-cogs.png" />
+  </ItemGroup>
+  <ItemGroup>
+    <BundleResource Include="Resources\fa-cogs%402x.png" />
+  </ItemGroup>
+  <ItemGroup>
+    <BundleResource Include="Resources\fa-cogs%403x.png" />
+  </ItemGroup>
+  <ItemGroup>
+    <BundleResource Include="Resources\fa-lock.png" />
+  </ItemGroup>
+  <ItemGroup>
+    <BundleResource Include="Resources\fa-lock%402x.png" />
+  </ItemGroup>
+  <ItemGroup>
+    <BundleResource Include="Resources\fa-lock%403x.png" />
+  </ItemGroup>
+  <ItemGroup>
+    <BundleResource Include="Resources\fa-plus.png" />
+  </ItemGroup>
+  <ItemGroup>
+    <BundleResource Include="Resources\fa-plus%402x.png" />
+  </ItemGroup>
+  <ItemGroup>
+    <BundleResource Include="Resources\fa-plus%403x.png" />
+  </ItemGroup>
   <Import Project="$(MSBuildExtensionsPath)\Xamarin\iOS\Xamarin.iOS.CSharp.targets" />
   <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
     <PropertyGroup>