Added icons for iOS. Broke out data access into repositories. Added syncing service.
|
@ -15,6 +15,7 @@ using Bit.Android.Services;
|
||||||
using Plugin.Settings;
|
using Plugin.Settings;
|
||||||
using Plugin.Connectivity;
|
using Plugin.Connectivity;
|
||||||
using Acr.UserDialogs;
|
using Acr.UserDialogs;
|
||||||
|
using Bit.App.Repositories;
|
||||||
|
|
||||||
namespace Bit.Android
|
namespace Bit.Android
|
||||||
{
|
{
|
||||||
|
@ -40,15 +41,23 @@ namespace Bit.Android
|
||||||
var container = new UnityContainer();
|
var container = new UnityContainer();
|
||||||
|
|
||||||
container
|
container
|
||||||
.RegisterType<ISqlService, SqlService>(new ContainerControlledLifetimeManager())
|
// Services
|
||||||
.RegisterType<IDatabaseService, DatabaseService>(new ContainerControlledLifetimeManager())
|
.RegisterType<IDatabaseService, DatabaseService>(new ContainerControlledLifetimeManager())
|
||||||
|
.RegisterType<ISqlService, SqlService>(new ContainerControlledLifetimeManager())
|
||||||
.RegisterType<ISecureStorageService, KeyStoreStorageService>(new ContainerControlledLifetimeManager())
|
.RegisterType<ISecureStorageService, KeyStoreStorageService>(new ContainerControlledLifetimeManager())
|
||||||
.RegisterInstance(CrossSettings.Current, new ContainerControlledLifetimeManager())
|
|
||||||
.RegisterType<IApiService, ApiService>(new ContainerControlledLifetimeManager())
|
|
||||||
.RegisterType<ICryptoService, CryptoService>(new ContainerControlledLifetimeManager())
|
.RegisterType<ICryptoService, CryptoService>(new ContainerControlledLifetimeManager())
|
||||||
.RegisterType<IAuthService, AuthService>(new ContainerControlledLifetimeManager())
|
.RegisterType<IAuthService, AuthService>(new ContainerControlledLifetimeManager())
|
||||||
.RegisterType<IFolderService, FolderService>(new ContainerControlledLifetimeManager())
|
.RegisterType<IFolderService, FolderService>(new ContainerControlledLifetimeManager())
|
||||||
.RegisterType<ISiteService, SiteService>(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(CrossConnectivity.Current, new ContainerControlledLifetimeManager())
|
||||||
.RegisterInstance(UserDialogs.Instance, new ContainerControlledLifetimeManager());
|
.RegisterInstance(UserDialogs.Instance, new ContainerControlledLifetimeManager());
|
||||||
|
|
||||||
|
|
19
src/App/Abstractions/Repositories/IApiRepository.cs
Normal file
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
11
src/App/Abstractions/Repositories/IAuthApiRepository.cs
Normal file
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
12
src/App/Abstractions/Repositories/IFolderApiRepository.cs
Normal file
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
12
src/App/Abstractions/Repositories/IFolderRepository.cs
Normal file
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
18
src/App/Abstractions/Repositories/IRepository.cs
Normal file
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
12
src/App/Abstractions/Repositories/ISiteApiRepository.cs
Normal file
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
12
src/App/Abstractions/Repositories/ISiteRepository.cs
Normal file
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -7,6 +7,7 @@ namespace Bit.App.Abstractions
|
||||||
{
|
{
|
||||||
public interface ISiteService
|
public interface ISiteService
|
||||||
{
|
{
|
||||||
|
Task<Site> GetByIdAsync(string id);
|
||||||
Task<IEnumerable<Site>> GetAllAsync();
|
Task<IEnumerable<Site>> GetAllAsync();
|
||||||
Task<ApiResult<SiteResponse>> SaveAsync(Site site);
|
Task<ApiResult<SiteResponse>> SaveAsync(Site site);
|
||||||
Task<ApiResult<object>> DeleteAsync(string id);
|
Task<ApiResult<object>> DeleteAsync(string id);
|
||||||
|
|
9
src/App/Abstractions/Services/ISyncService.cs
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Bit.App.Abstractions
|
||||||
|
{
|
||||||
|
public interface ISyncService
|
||||||
|
{
|
||||||
|
Task<bool> SyncAsync();
|
||||||
|
}
|
||||||
|
}
|
|
@ -48,8 +48,10 @@
|
||||||
<Compile Include="Models\Api\Request\FolderRequest.cs" />
|
<Compile Include="Models\Api\Request\FolderRequest.cs" />
|
||||||
<Compile Include="Models\Api\Request\SiteRequest.cs" />
|
<Compile Include="Models\Api\Request\SiteRequest.cs" />
|
||||||
<Compile Include="Models\Api\Request\TokenRequest.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\ErrorResponse.cs" />
|
||||||
<Compile Include="Models\Api\Response\FolderResponse.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\SiteResponse.cs" />
|
||||||
<Compile Include="Models\Api\Response\TokenResponse.cs" />
|
<Compile Include="Models\Api\Response\TokenResponse.cs" />
|
||||||
<Compile Include="Models\Api\Response\ProfileResponse.cs" />
|
<Compile Include="Models\Api\Response\ProfileResponse.cs" />
|
||||||
|
@ -65,13 +67,28 @@
|
||||||
<Compile Include="Pages\SyncPage.cs" />
|
<Compile Include="Pages\SyncPage.cs" />
|
||||||
<Compile Include="Pages\SettingsPage.cs" />
|
<Compile Include="Pages\SettingsPage.cs" />
|
||||||
<Compile Include="Properties\AssemblyInfo.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\DatabaseService.cs" />
|
||||||
<Compile Include="Services\FolderService.cs" />
|
<Compile Include="Services\FolderService.cs" />
|
||||||
<Compile Include="Services\Repository.cs" />
|
<Compile Include="Repositories\Repository.cs" />
|
||||||
<Compile Include="Abstractions\Services\IApiService.cs" />
|
|
||||||
<Compile Include="Abstractions\Services\IAuthService.cs" />
|
<Compile Include="Abstractions\Services\IAuthService.cs" />
|
||||||
<Compile Include="Abstractions\Services\ICryptoService.cs" />
|
<Compile Include="Abstractions\Services\ICryptoService.cs" />
|
||||||
<Compile Include="Abstractions\Services\IDatabaseService.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\SiteService.cs" />
|
||||||
<Compile Include="Services\AuthService.cs" />
|
<Compile Include="Services\AuthService.cs" />
|
||||||
<Compile Include="Services\CryptoService.cs" />
|
<Compile Include="Services\CryptoService.cs" />
|
||||||
|
@ -83,7 +100,6 @@
|
||||||
<Compile Include="Pages\VaultViewSitePage.cs" />
|
<Compile Include="Pages\VaultViewSitePage.cs" />
|
||||||
<Compile Include="Pages\VaultEditSitePage.cs" />
|
<Compile Include="Pages\VaultEditSitePage.cs" />
|
||||||
<Compile Include="Pages\VaultListPage.cs" />
|
<Compile Include="Pages\VaultListPage.cs" />
|
||||||
<Compile Include="Services\ApiService.cs" />
|
|
||||||
<Compile Include="Utilities\Extentions.cs" />
|
<Compile Include="Utilities\Extentions.cs" />
|
||||||
<Compile Include="Utilities\TokenHttpRequestMessage.cs" />
|
<Compile Include="Utilities\TokenHttpRequestMessage.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
8
src/App/Models/Api/Request/TokenTwoFactorRequest.cs
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
namespace Bit.App.Models.Api
|
||||||
|
{
|
||||||
|
public class TokenTwoFactorRequest
|
||||||
|
{
|
||||||
|
public string Code { get; set; }
|
||||||
|
public string Provider { get; set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,8 +1,11 @@
|
||||||
namespace Bit.App.Models.Api
|
using System;
|
||||||
|
|
||||||
|
namespace Bit.App.Models.Api
|
||||||
{
|
{
|
||||||
public class FolderResponse
|
public class FolderResponse
|
||||||
{
|
{
|
||||||
public string Id { get; set; }
|
public string Id { get; set; }
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
|
public DateTime RevisionDate { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
14
src/App/Models/Api/Response/ListResponse.cs
Normal file
|
@ -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; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,6 @@
|
||||||
namespace Bit.App.Models.Api
|
using System;
|
||||||
|
|
||||||
|
namespace Bit.App.Models.Api
|
||||||
{
|
{
|
||||||
public class SiteResponse
|
public class SiteResponse
|
||||||
{
|
{
|
||||||
|
@ -9,6 +11,7 @@
|
||||||
public string Username { get; set; }
|
public string Username { get; set; }
|
||||||
public string Password { get; set; }
|
public string Password { get; set; }
|
||||||
public string Notes { get; set; }
|
public string Notes { get; set; }
|
||||||
|
public DateTime RevisionDate { get; set; }
|
||||||
|
|
||||||
// Expandables
|
// Expandables
|
||||||
public FolderResponse Folder { get; set; }
|
public FolderResponse Folder { get; set; }
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
using System;
|
using System;
|
||||||
using Bit.App.Abstractions;
|
|
||||||
using Xamarin.Forms;
|
using Xamarin.Forms;
|
||||||
|
|
||||||
namespace Bit.App.Pages
|
namespace Bit.App.Pages
|
||||||
|
@ -8,18 +7,21 @@ namespace Bit.App.Pages
|
||||||
{
|
{
|
||||||
public MainPage()
|
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());
|
var settingsNavigation = new NavigationPage(new SettingsPage());
|
||||||
settingsNavigation.BarBackgroundColor = Color.FromHex("3c8dbc");
|
var vaultNavigation = new NavigationPage(new VaultListPage());
|
||||||
settingsNavigation.BarTextColor = Color.FromHex("ffffff");
|
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.Title = "Settings";
|
||||||
|
settingsNavigation.Icon = "fa-cogs";
|
||||||
|
|
||||||
Children.Add(vaultNavigation);
|
Children.Add(vaultNavigation);
|
||||||
Children.Add(new SyncPage());
|
Children.Add(syncPage);
|
||||||
Children.Add(settingsNavigation);
|
Children.Add(settingsNavigation);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,54 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Acr.UserDialogs;
|
||||||
|
using Bit.App.Abstractions;
|
||||||
using Xamarin.Forms;
|
using Xamarin.Forms;
|
||||||
|
using XLabs.Ioc;
|
||||||
|
|
||||||
namespace Bit.App.Pages
|
namespace Bit.App.Pages
|
||||||
{
|
{
|
||||||
public class SyncPage : ContentPage
|
public class SyncPage : ContentPage
|
||||||
{
|
{
|
||||||
|
private readonly ISyncService _syncService;
|
||||||
|
private readonly IUserDialogs _userDialogs;
|
||||||
|
|
||||||
public SyncPage()
|
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";
|
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.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,21 +31,14 @@ namespace Bit.App.Pages
|
||||||
{
|
{
|
||||||
ToolbarItems.Add(new AddSiteToolBarItem(this));
|
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 };
|
var listView = new ListView { IsGroupingEnabled = true, ItemsSource = Folders };
|
||||||
listView.GroupDisplayBinding = new Binding("Name");
|
listView.GroupDisplayBinding = new Binding("Name");
|
||||||
listView.ItemSelected += SiteSelected;
|
listView.ItemSelected += SiteSelected;
|
||||||
listView.ItemTemplate = new DataTemplate(() => new VaultListViewCell(moreAction, deleteAction));
|
listView.ItemTemplate = new DataTemplate(() => new VaultListViewCell(this));
|
||||||
|
|
||||||
Title = "My Vault";
|
Title = "My Vault";
|
||||||
Content = listView;
|
Content = listView;
|
||||||
|
NavigationPage.SetBackButtonTitle(this, string.Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnAppearing()
|
protected override void OnAppearing()
|
||||||
|
@ -122,7 +115,7 @@ namespace Bit.App.Pages
|
||||||
{
|
{
|
||||||
_page = page;
|
_page = page;
|
||||||
Text = "Add";
|
Text = "Add";
|
||||||
Icon = "";
|
Icon = "fa-plus";
|
||||||
Clicked += ClickedItem;
|
Clicked += ClickedItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -144,12 +137,20 @@ namespace Bit.App.Pages
|
||||||
|
|
||||||
private class VaultListViewCell : TextCell
|
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>(TextProperty, s => s.Name);
|
||||||
this.SetBinding<VaultView.Site>(DetailProperty, s => s.Username);
|
this.SetBinding<VaultView.Site>(DetailProperty, s => s.Username);
|
||||||
ContextActions.Add(moreMenuItem);
|
ContextActions.Add(moreAction);
|
||||||
ContextActions.Add(deleteMenuItem);
|
ContextActions.Add(deleteAction);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
112
src/App/Repositories/ApiRepository.cs
Normal file
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
52
src/App/Repositories/AuthApiRepository.cs
Normal file
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,23 +1,27 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
|
using System.Net.Http.Headers;
|
||||||
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Bit.App.Abstractions;
|
|
||||||
using Bit.App.Models.Api;
|
using Bit.App.Models.Api;
|
||||||
using ModernHttpClient;
|
using ModernHttpClient;
|
||||||
using Newtonsoft.Json;
|
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 = new HttpClient(new NativeMessageHandler());
|
||||||
Client.BaseAddress = new Uri("https://api.bitwarden.com");
|
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)
|
public async Task<ApiResult<T>> HandleErrorAsync<T>(HttpResponseMessage response)
|
||||||
{
|
{
|
34
src/App/Repositories/FolderApiRepository.cs
Normal file
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
22
src/App/Repositories/FolderRepository.cs
Normal file
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,9 +5,9 @@ using System.Threading.Tasks;
|
||||||
using Bit.App.Abstractions;
|
using Bit.App.Abstractions;
|
||||||
using SQLite;
|
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 TId : IEquatable<TId>
|
||||||
where T : class, IDataObject<TId>, new()
|
where T : class, IDataObject<TId>, new()
|
||||||
{
|
{
|
||||||
|
@ -18,34 +18,34 @@ namespace Bit.App.Services
|
||||||
|
|
||||||
protected SQLiteConnection Connection { get; private set; }
|
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));
|
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>());
|
return Task.FromResult(Connection.Table<T>().Cast<T>());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual Task CreateAsync(T obj)
|
public virtual Task InsertAsync(T obj)
|
||||||
{
|
{
|
||||||
Connection.Insert(obj);
|
Connection.Insert(obj);
|
||||||
return Task.FromResult(0);
|
return Task.FromResult(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual Task ReplaceAsync(T obj)
|
public virtual Task UpdateAsync(T obj)
|
||||||
{
|
{
|
||||||
Connection.Update(obj);
|
Connection.Update(obj);
|
||||||
return Task.FromResult(0);
|
return Task.FromResult(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual async Task DeleteAsync(T obj)
|
public virtual async Task DeleteAsync(T obj)
|
||||||
{
|
{
|
||||||
await DeleteAsync(obj.Id);
|
await DeleteAsync(obj.Id);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual Task DeleteAsync(TId id)
|
public virtual Task DeleteAsync(TId id)
|
||||||
{
|
{
|
||||||
Connection.Delete<T>(id);
|
Connection.Delete<T>(id);
|
||||||
return Task.FromResult(0);
|
return Task.FromResult(0);
|
34
src/App/Repositories/SiteApiRepository.cs
Normal file
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
22
src/App/Repositories/SiteRepository.cs
Normal file
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,7 +17,7 @@ namespace Bit.App.Services
|
||||||
private readonly ISecureStorageService _secureStorage;
|
private readonly ISecureStorageService _secureStorage;
|
||||||
private readonly ISettings _settings;
|
private readonly ISettings _settings;
|
||||||
private readonly ICryptoService _cryptoService;
|
private readonly ICryptoService _cryptoService;
|
||||||
private readonly IApiService _apiService;
|
private readonly IAuthApiRepository _authApiRepository;
|
||||||
|
|
||||||
private string _token;
|
private string _token;
|
||||||
private string _userId;
|
private string _userId;
|
||||||
|
@ -26,12 +26,12 @@ namespace Bit.App.Services
|
||||||
ISecureStorageService secureStorage,
|
ISecureStorageService secureStorage,
|
||||||
ISettings settings,
|
ISettings settings,
|
||||||
ICryptoService cryptoService,
|
ICryptoService cryptoService,
|
||||||
IApiService apiService)
|
IAuthApiRepository authApiRepository)
|
||||||
{
|
{
|
||||||
_secureStorage = secureStorage;
|
_secureStorage = secureStorage;
|
||||||
_settings = settings;
|
_settings = settings;
|
||||||
_cryptoService = cryptoService;
|
_cryptoService = cryptoService;
|
||||||
_apiService = apiService;
|
_authApiRepository = authApiRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Token
|
public string Token
|
||||||
|
@ -110,16 +110,8 @@ namespace Bit.App.Services
|
||||||
|
|
||||||
public async Task<ApiResult<TokenResponse>> TokenPostAsync(TokenRequest request)
|
public async Task<ApiResult<TokenResponse>> TokenPostAsync(TokenRequest request)
|
||||||
{
|
{
|
||||||
var requestContent = JsonConvert.SerializeObject(request);
|
// TODO: move more logic in here
|
||||||
var response = await _apiService.Client.PostAsync("/auth/token", new StringContent(requestContent, Encoding.UTF8, "application/json"));
|
return await _authApiRepository.PostTokenAsync(request);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,70 +6,73 @@ using Bit.App.Abstractions;
|
||||||
using Bit.App.Models;
|
using Bit.App.Models;
|
||||||
using Bit.App.Models.Data;
|
using Bit.App.Models.Data;
|
||||||
using Bit.App.Models.Api;
|
using Bit.App.Models.Api;
|
||||||
using Newtonsoft.Json;
|
|
||||||
using System.Net.Http;
|
|
||||||
|
|
||||||
namespace Bit.App.Services
|
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 IAuthService _authService;
|
||||||
private readonly IApiService _apiService;
|
private readonly IFolderApiRepository _folderApiRepository;
|
||||||
|
|
||||||
public FolderService(
|
public FolderService(
|
||||||
ISqlService sqlService,
|
IFolderRepository folderRepository,
|
||||||
IAuthService authService,
|
IAuthService authService,
|
||||||
IApiService apiService)
|
IFolderApiRepository folderApiRepository)
|
||||||
: base(sqlService)
|
|
||||||
{
|
{
|
||||||
|
_folderRepository = folderRepository;
|
||||||
_authService = authService;
|
_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);
|
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));
|
var folders = data.Select(f => new Folder(f));
|
||||||
return Task.FromResult(folders);
|
return folders;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<ApiResult<FolderResponse>> SaveAsync(Folder folder)
|
public async Task<ApiResult<FolderResponse>> SaveAsync(Folder folder)
|
||||||
{
|
{
|
||||||
|
ApiResult<FolderResponse> response = null;
|
||||||
var request = new FolderRequest(folder);
|
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)
|
if(folder.Id == null)
|
||||||
{
|
{
|
||||||
await CreateAsync(data);
|
response = await _folderApiRepository.PostAsync(request);
|
||||||
folder.Id = responseObj.Id;
|
|
||||||
}
|
}
|
||||||
else
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,86 +1,89 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net.Http;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Bit.App.Abstractions;
|
using Bit.App.Abstractions;
|
||||||
using Bit.App.Models;
|
using Bit.App.Models;
|
||||||
using Bit.App.Models.Api;
|
using Bit.App.Models.Api;
|
||||||
using Bit.App.Models.Data;
|
using Bit.App.Models.Data;
|
||||||
using Newtonsoft.Json;
|
|
||||||
|
|
||||||
namespace Bit.App.Services
|
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 IAuthService _authService;
|
||||||
private readonly IApiService _apiService;
|
private readonly ISiteApiRepository _siteApiRepository;
|
||||||
|
|
||||||
public SiteService(
|
public SiteService(
|
||||||
ISqlService sqlService,
|
ISiteRepository siteRepository,
|
||||||
IAuthService authService,
|
IAuthService authService,
|
||||||
IApiService apiService)
|
ISiteApiRepository siteApiRepository)
|
||||||
: base(sqlService)
|
|
||||||
{
|
{
|
||||||
|
_siteRepository = siteRepository;
|
||||||
_authService = authService;
|
_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 data = await _siteRepository.GetByIdAsync(id);
|
||||||
var sites = data.Select(s => new Site(s));
|
if(data == null || data.UserId != _authService.UserId)
|
||||||
return Task.FromResult(sites);
|
{
|
||||||
|
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)
|
public async Task<ApiResult<SiteResponse>> SaveAsync(Site site)
|
||||||
{
|
{
|
||||||
|
ApiResult<SiteResponse> response = null;
|
||||||
var request = new SiteRequest(site);
|
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)
|
if(site.Id == null)
|
||||||
{
|
{
|
||||||
await base.CreateAsync(data);
|
response = await _siteApiRepository.PostAsync(request);
|
||||||
site.Id = responseObj.Id;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
await base.ReplaceAsync(data);
|
response = await _siteApiRepository.PutAsync(site.Id, request);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ApiResult<SiteResponse>.Success(responseObj, response.StatusCode);
|
if(response.Succeeded)
|
||||||
}
|
|
||||||
|
|
||||||
public new async Task<ApiResult<object>> DeleteAsync(string id)
|
|
||||||
{
|
{
|
||||||
var requestMessage = new TokenHttpRequestMessage
|
var data = new SiteData(response.Result, _authService.UserId);
|
||||||
|
if(site.Id == null)
|
||||||
{
|
{
|
||||||
Method = HttpMethod.Delete,
|
await _siteRepository.InsertAsync(data);
|
||||||
RequestUri = new Uri(_apiService.Client.BaseAddress, $"/sites/{id}")
|
site.Id = data.Id;
|
||||||
};
|
}
|
||||||
|
else
|
||||||
var response = await _apiService.Client.SendAsync(requestMessage);
|
|
||||||
if(!response.IsSuccessStatusCode)
|
|
||||||
{
|
{
|
||||||
return await _apiService.HandleErrorAsync<object>(response);
|
await _siteRepository.UpdateAsync(data);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await base.DeleteAsync(id);
|
return response;
|
||||||
return ApiResult<object>.Success(null, response.StatusCode);
|
}
|
||||||
|
|
||||||
|
public async Task<ApiResult<object>> DeleteAsync(string id)
|
||||||
|
{
|
||||||
|
ApiResult<object> response = await _siteApiRepository.DeleteAsync(id);
|
||||||
|
if(response.Succeeded)
|
||||||
|
{
|
||||||
|
await _siteRepository.DeleteAsync(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
108
src/App/Services/SyncService.cs
Normal file
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -13,6 +13,7 @@ using Bit.iOS.Services;
|
||||||
using Plugin.Settings;
|
using Plugin.Settings;
|
||||||
using Plugin.Connectivity;
|
using Plugin.Connectivity;
|
||||||
using Acr.UserDialogs;
|
using Acr.UserDialogs;
|
||||||
|
using Bit.App.Repositories;
|
||||||
|
|
||||||
namespace Bit.iOS
|
namespace Bit.iOS
|
||||||
{
|
{
|
||||||
|
@ -48,15 +49,23 @@ namespace Bit.iOS
|
||||||
var container = new UnityContainer();
|
var container = new UnityContainer();
|
||||||
|
|
||||||
container
|
container
|
||||||
.RegisterType<ISqlService, SqlService>(new ContainerControlledLifetimeManager())
|
// Services
|
||||||
.RegisterType<IDatabaseService, DatabaseService>(new ContainerControlledLifetimeManager())
|
.RegisterType<IDatabaseService, DatabaseService>(new ContainerControlledLifetimeManager())
|
||||||
|
.RegisterType<ISqlService, SqlService>(new ContainerControlledLifetimeManager())
|
||||||
.RegisterType<ISecureStorageService, KeyChainStorageService>(new ContainerControlledLifetimeManager())
|
.RegisterType<ISecureStorageService, KeyChainStorageService>(new ContainerControlledLifetimeManager())
|
||||||
.RegisterInstance(CrossSettings.Current, new ContainerControlledLifetimeManager())
|
|
||||||
.RegisterType<IApiService, ApiService>(new ContainerControlledLifetimeManager())
|
|
||||||
.RegisterType<ICryptoService, CryptoService>(new ContainerControlledLifetimeManager())
|
.RegisterType<ICryptoService, CryptoService>(new ContainerControlledLifetimeManager())
|
||||||
.RegisterType<IAuthService, AuthService>(new ContainerControlledLifetimeManager())
|
.RegisterType<IAuthService, AuthService>(new ContainerControlledLifetimeManager())
|
||||||
.RegisterType<IFolderService, FolderService>(new ContainerControlledLifetimeManager())
|
.RegisterType<IFolderService, FolderService>(new ContainerControlledLifetimeManager())
|
||||||
.RegisterType<ISiteService, SiteService>(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(CrossConnectivity.Current, new ContainerControlledLifetimeManager())
|
||||||
.RegisterInstance(UserDialogs.Instance, new ContainerControlledLifetimeManager());
|
.RegisterInstance(UserDialogs.Instance, new ContainerControlledLifetimeManager());
|
||||||
|
|
||||||
|
|
|
@ -26,7 +26,7 @@
|
||||||
<key>CFBundleDisplayName</key>
|
<key>CFBundleDisplayName</key>
|
||||||
<string>bitwarden</string>
|
<string>bitwarden</string>
|
||||||
<key>CFBundleIdentifier</key>
|
<key>CFBundleIdentifier</key>
|
||||||
<string>com.bitwarden.bitwarden</string>
|
<string>com.bitwarden.vault</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>1.0</string>
|
<string>1.0</string>
|
||||||
<key>CFBundleIconFiles</key>
|
<key>CFBundleIconFiles</key>
|
||||||
|
|
BIN
src/iOS/Resources/fa-cogs.png
Normal file
After Width: | Height: | Size: 705 B |
BIN
src/iOS/Resources/fa-cogs@2x.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
src/iOS/Resources/fa-cogs@3x.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
src/iOS/Resources/fa-lock.png
Normal file
After Width: | Height: | Size: 388 B |
BIN
src/iOS/Resources/fa-lock@2x.png
Normal file
After Width: | Height: | Size: 587 B |
BIN
src/iOS/Resources/fa-lock@3x.png
Normal file
After Width: | Height: | Size: 820 B |
BIN
src/iOS/Resources/fa-plus.png
Normal file
After Width: | Height: | Size: 242 B |
BIN
src/iOS/Resources/fa-plus@2x.png
Normal file
After Width: | Height: | Size: 335 B |
BIN
src/iOS/Resources/fa-plus@3x.png
Normal file
After Width: | Height: | Size: 418 B |
BIN
src/iOS/Resources/fa-refresh.png
Normal file
After Width: | Height: | Size: 595 B |
BIN
src/iOS/Resources/fa-refresh@2x.png
Normal file
After Width: | Height: | Size: 960 B |
BIN
src/iOS/Resources/fa-refresh@3x.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
|
@ -50,6 +50,9 @@
|
||||||
<CodesignKey>iPhone Developer</CodesignKey>
|
<CodesignKey>iPhone Developer</CodesignKey>
|
||||||
<MtouchDebug>true</MtouchDebug>
|
<MtouchDebug>true</MtouchDebug>
|
||||||
<CodesignEntitlements>Entitlements.plist</CodesignEntitlements>
|
<CodesignEntitlements>Entitlements.plist</CodesignEntitlements>
|
||||||
|
<CodesignProvision>2ae5608a-6142-4e1d-9344-326d1982b392</CodesignProvision>
|
||||||
|
<CodesignResourceRules />
|
||||||
|
<CodesignExtraArgs />
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|iPhone' ">
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|iPhone' ">
|
||||||
<DebugType>none</DebugType>
|
<DebugType>none</DebugType>
|
||||||
|
@ -205,6 +208,42 @@
|
||||||
<Name>App</Name>
|
<Name>App</Name>
|
||||||
</ProjectReference>
|
</ProjectReference>
|
||||||
</ItemGroup>
|
</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" />
|
<Import Project="$(MSBuildExtensionsPath)\Xamarin\iOS\Xamarin.iOS.CSharp.targets" />
|
||||||
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
|
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
|