Compare commits
47 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2405a6f21e | ||
|
|
533dd6135e | ||
|
|
efc25543ca | ||
|
|
82d4745da3 | ||
|
|
ac6e95c442 | ||
|
|
375f23ac9e | ||
|
|
8e5a01d82c | ||
|
|
910658aa93 | ||
|
|
d766ffa040 | ||
|
|
b960640e03 | ||
|
|
c984617b1c | ||
|
|
a12a7127c0 | ||
|
|
98a6a5c93d | ||
|
|
27202fd740 | ||
|
|
c01d02de27 | ||
|
|
1d23bcc979 | ||
|
|
ac8abdaa17 | ||
|
|
613977c6f9 | ||
|
|
54159c9d05 | ||
|
|
8d5d477b4a | ||
|
|
2c73906ad3 | ||
|
|
079fb34120 | ||
|
|
17ed1cdc00 | ||
|
|
d53ea584ba | ||
|
|
b435256911 | ||
|
|
27e996dba0 | ||
|
|
22f3bd1073 | ||
|
|
fb564fa817 | ||
|
|
be9db2930f | ||
|
|
5bce95a686 | ||
|
|
f6ca9b9d0f | ||
|
|
88f186907b | ||
|
|
9faf1d9de5 | ||
|
|
8b1d1d0f6d | ||
|
|
8c19e2c3f2 | ||
|
|
d2d8ee504d | ||
|
|
d96b279beb | ||
|
|
f5e7f9249c | ||
|
|
56c33ee82b | ||
|
|
b05dd4cc2c | ||
|
|
36d4ce8718 | ||
|
|
ddec7ab643 | ||
|
|
75201c9b30 | ||
|
|
99c81e5a5d | ||
|
|
b84ad39133 | ||
|
|
4a19e2b673 | ||
|
|
475c3559f6 |
@@ -3,7 +3,7 @@
|
||||
|
||||
# bitwarden mobile
|
||||
|
||||
<a href="https://play.google.com/store/apps/details?id=com.x8bit.bitwarden" target="_blank"><img alt="Get it on Google Play" src="https://developer.android.com/images/brand/en_generic_rgb_wo_45.png"width="129" height="45"></a> <a href="https://itunes.apple.com/us/app/bitwarden-free-password-manager/id1137397744?mt=8" target="_blank"><img src="https://linkmaker.itunes.apple.com/images/badges/en-us/badge_appstore-lrg.svg" width="165" height="40"></a>
|
||||
<a href="https://play.google.com/store/apps/details?id=com.x8bit.bitwarden" target="_blank"><img alt="Get it on Google Play" src="https://imgur.com/YQzmZi9.png" width="153" height="46"></a> <a href="https://itunes.apple.com/us/app/bitwarden-free-password-manager/id1137397744?mt=8" target="_blank"><img src="https://imgur.com/GdGqPMY.png" width="135" height="40"></a> <a href="https://www.amazon.com/dp/B06XMYGPMV" target="_blank"><img src="https://imgur.com/f75uYeM.png" width="132" height="45"></a>
|
||||
|
||||
The bitwarden mobile application is written in C# with Xamarin Android, Xamarin iOS, and Xamarin Forms.
|
||||
|
||||
@@ -14,7 +14,7 @@ The bitwarden mobile application is written in C# with Xamarin Android, Xamarin
|
||||
- [Visual Studio w/ Xamarin -or- Xamarin Studio](https://store.xamarin.com/)
|
||||
|
||||
By default the app is targeting the production API. If you are running the [Core](https://github.com/bitwarden/core) API locally,
|
||||
you'll need to switch the extension to target your local API. Open `src/App/Utilities/ApiHttpClient.cs` and set `BaseAddress` to your local
|
||||
you'll need to switch the app to target your local API. Open `src/App/Utilities/ApiHttpClient.cs` and set `BaseAddress` to your local
|
||||
API instance (ex. `new Uri("http://localhost:4000")`).
|
||||
|
||||
After restoring the nuget packages, you can now build and run the app.
|
||||
|
||||
14
appveyor.yml
Normal file
@@ -0,0 +1,14 @@
|
||||
skip_tags: true
|
||||
before_build:
|
||||
- nuget restore
|
||||
- IF DEFINED keystore_dec_secret nuget install secure-file -ExcludeVersion
|
||||
after_build:
|
||||
- ps: IF($env:keystore_dec_secret) { .\src\Android\increment-version.ps1 $($env:APPVEYOR_BUILD_FOLDER) $($env:APPVEYOR_BUILD_NUMBER) }
|
||||
- IF DEFINED keystore_dec_secret secure-file\tools\secure-file -decrypt src\Android\8bit.keystore.enc -secret %keystore_dec_secret%
|
||||
- IF DEFINED keystore_password msbuild "/t:SignAndroidPackage" "/p:Configuration=Release" "/p:AndroidKeyStore=true" "/p:AndroidSigningKeyAlias=bitwarden" "/p:AndroidSigningKeyPass=%keystore_password%" "/p:AndroidSigningKeyStore=8bit.keystore" "/p:AndroidSigningStorePass=%keystore_password%" "src\Android\Android.csproj"
|
||||
- ps: IF($env:keystore_dec_secret) { copy-item src\Android\bin\Release\com.x8bit.bitwarden-Signed.apk .\com.x8bit.bitwarden-$($env:APPVEYOR_BUILD_NUMBER).apk }
|
||||
on_success:
|
||||
- IF DEFINED play_dec_secret secure-file\tools\secure-file -decrypt store\google\Publisher\play_creds.json.enc -secret %play_dec_secret%
|
||||
- IF DEFINED play_dec_secret store\google\Publisher\bin\Debug\Publisher.exe %APPVEYOR_BUILD_FOLDER%\store\google\Publisher\play_creds.json %APPVEYOR_BUILD_FOLDER%\src\Android\bin\Release\com.x8bit.bitwarden-Signed.apk alpha
|
||||
artifacts:
|
||||
- path: com.x8bit.bitwarden-%APPVEYOR_BUILD_NUMBER%.apk
|
||||
@@ -91,6 +91,22 @@
|
||||
<HintPath>..\..\packages\BouncyCastle.1.8.1\lib\BouncyCastle.Crypto.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="FFImageLoading, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Xamarin.FFImageLoading.2.2.8\lib\MonoAndroid10\FFImageLoading.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="FFImageLoading.Forms, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Xamarin.FFImageLoading.Forms.2.2.8\lib\MonoAndroid10\FFImageLoading.Forms.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="FFImageLoading.Forms.Droid, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Xamarin.FFImageLoading.Forms.2.2.8\lib\MonoAndroid10\FFImageLoading.Forms.Droid.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="FFImageLoading.Platform, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Xamarin.FFImageLoading.2.2.8\lib\MonoAndroid10\FFImageLoading.Platform.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="FormsViewGroup, Version=2.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Xamarin.Forms.2.3.3.180\lib\MonoAndroid10\FormsViewGroup.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
@@ -327,6 +343,7 @@
|
||||
<Compile Include="MainActivity.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="Services\LogService.cs" />
|
||||
<Compile Include="Services\MemoryService.cs" />
|
||||
<Compile Include="Services\ReflectionService.cs" />
|
||||
<Compile Include="Services\SqlService.cs" />
|
||||
<Compile Include="SplashActivity.cs" />
|
||||
@@ -815,19 +832,19 @@
|
||||
<AndroidResource Include="Resources\values\strings.xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-xxxhdpi\close.png" />
|
||||
<AndroidResource Include="Resources\drawable\search.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-xxhdpi\close.png" />
|
||||
<AndroidResource Include="Resources\drawable-hdpi\search.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-xhdpi\close.png" />
|
||||
<AndroidResource Include="Resources\drawable-xhdpi\search.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-hdpi\close.png" />
|
||||
<AndroidResource Include="Resources\drawable-xxhdpi\search.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable\close.png" />
|
||||
<AndroidResource Include="Resources\drawable-xxxhdpi\search.png" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.CSharp.targets" />
|
||||
<Import Project="..\..\packages\Xamarin.Android.Support.Vector.Drawable.23.3.0\build\Xamarin.Android.Support.Vector.Drawable.targets" Condition="Exists('..\..\packages\Xamarin.Android.Support.Vector.Drawable.23.3.0\build\Xamarin.Android.Support.Vector.Drawable.targets')" />
|
||||
|
||||
@@ -94,8 +94,10 @@ namespace Bit.Android
|
||||
}
|
||||
|
||||
var intent = new Intent(this, typeof(MainActivity));
|
||||
intent.PutExtra("uri", _lastQueriedUri);
|
||||
intent.PutExtra("ts", Java.Lang.JavaSystem.CurrentTimeMillis());
|
||||
if(!callingIntent.Flags.HasFlag(ActivityFlags.LaunchedFromHistory))
|
||||
{
|
||||
intent.PutExtra("uri", _lastQueriedUri);
|
||||
}
|
||||
StartActivityForResult(intent, requestCode);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,91 +19,109 @@ namespace Bit.Android
|
||||
private const string BitwardenPackage = "com.x8bit.bitwarden";
|
||||
private const string BitwardenWebsite = "bitwarden.com";
|
||||
|
||||
public static bool Enabled { get; set; } = false;
|
||||
private static Dictionary<string, string[]> BrowserPackages => new Dictionary<string, string[]>
|
||||
private static Dictionary<string, Browser> SupportedBrowsers => new List<Browser>
|
||||
{
|
||||
{ "com.android.chrome", new string[] { "url_bar" } },
|
||||
{ "com.chrome.beta", new string[] { "url_bar" } },
|
||||
{ "com.android.browser", new string[] { "url" } },
|
||||
{ "com.brave.browser", new string[] { "url_bar" } },
|
||||
{ "com.opera.browser", new string[] { "url_field" } },
|
||||
{ "com.opera.browser.beta", new string[] { "url_field" } },
|
||||
{ "com.opera.mini.native", new string[] { "url_field" } },
|
||||
{ "com.chrome.dev", new string[] { "url_bar" } },
|
||||
{ "com.chrome.canary", new string[] { "url_bar" } },
|
||||
{ "com.google.android.apps.chrome", new string[] { "url_bar" } },
|
||||
{ "com.google.android.apps.chrome_dev", new string[] { "url_bar" } },
|
||||
{ "org.iron.srware", new string[] { "url_bar" } },
|
||||
{ "com.sec.android.app.sbrowser", new string[] { "sbrowser_url_bar" } },
|
||||
{ "com.yandex.browser", new string[] { "bro_common_omnibox_host", "bro_common_omnibox_edit_text" } },
|
||||
{ "org.mozilla.firefox", new string[] { "url_bar_title" } },
|
||||
{ "org.mozilla.firefox_beta", new string[] { "url_bar_title" } },
|
||||
{ "com.ghostery.android.ghostery",new string[] { "search_field" } },
|
||||
{ "org.adblockplus.browser", new string[] { "url_bar_title" } },
|
||||
{ "com.htc.sense.browser", new string[] { "title" } },
|
||||
{ "com.amazon.cloud9", new string[] { "url" } },
|
||||
{ "mobi.mgeek.TunnyBrowser", new string[] { "title" } },
|
||||
{ "com.nubelacorp.javelin", new string[] { "enterUrl" } },
|
||||
{ "com.jerky.browser2", new string[] { "enterUrl" } },
|
||||
{ "com.mx.browser", new string[] { "address_editor_with_progress" } },
|
||||
{ "com.mx.browser.tablet", new string[] { "address_editor_with_progress"} },
|
||||
{ "com.linkbubble.playstore", new string[] { "url_text" }}
|
||||
};
|
||||
new Browser("com.android.chrome", "url_bar"),
|
||||
new Browser("com.chrome.beta", "url_bar"),
|
||||
new Browser("com.android.browser", "url"),
|
||||
new Browser("com.brave.browser", "url_bar"),
|
||||
new Browser("com.opera.browser", "url_field"),
|
||||
new Browser("com.opera.browser.beta", "url_field"),
|
||||
new Browser("com.opera.mini.native", "url_field"),
|
||||
new Browser("com.chrome.dev", "url_bar"),
|
||||
new Browser("com.chrome.canary", "url_bar"),
|
||||
new Browser("com.google.android.apps.chrome", "url_bar"),
|
||||
new Browser("com.google.android.apps.chrome_dev", "url_bar"),
|
||||
new Browser("org.iron.srware", "url_bar"),
|
||||
new Browser("com.sec.android.app.sbrowser", "sbrowser_url_bar"),
|
||||
new Browser("com.sec.android.app.sbrowser.beta", "location_bar_edit_text"),
|
||||
new Browser("com.yandex.browser", "bro_omnibar_address_title_text",
|
||||
(s) => s.Split(' ').FirstOrDefault()),
|
||||
new Browser("org.mozilla.firefox", "url_bar_title"),
|
||||
new Browser("org.mozilla.firefox_beta", "url_bar_title"),
|
||||
new Browser("com.ghostery.android.ghostery", "search_field"),
|
||||
new Browser("org.adblockplus.browser", "url_bar_title"),
|
||||
new Browser("com.htc.sense.browser", "title"),
|
||||
new Browser("com.amazon.cloud9", "url"),
|
||||
new Browser("mobi.mgeek.TunnyBrowser", "title"),
|
||||
new Browser("com.nubelacorp.javelin", "enterUrl"),
|
||||
new Browser("com.jerky.browser2", "enterUrl"),
|
||||
new Browser("com.mx.browser", "address_editor_with_progress"),
|
||||
new Browser("com.mx.browser.tablet", "address_editor_with_progress"),
|
||||
new Browser("com.linkbubble.playstore", "url_text"),
|
||||
new Browser("com.ksmobile.cb", "address_bar_edit_text")
|
||||
}.ToDictionary(n => n.PackageName);
|
||||
|
||||
public override void OnAccessibilityEvent(AccessibilityEvent e)
|
||||
{
|
||||
Enabled = true;
|
||||
var root = RootInActiveWindow;
|
||||
if(string.IsNullOrWhiteSpace(e.PackageName) || e.PackageName == SystemUiPackage ||
|
||||
root?.PackageName != e.PackageName)
|
||||
try
|
||||
{
|
||||
return;
|
||||
}
|
||||
var root = RootInActiveWindow;
|
||||
if(e == null || root == null || string.IsNullOrWhiteSpace(e.PackageName) ||
|
||||
e.PackageName == SystemUiPackage || root.PackageName != e.PackageName)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
switch(e.EventType)
|
||||
{
|
||||
case EventTypes.WindowContentChanged:
|
||||
case EventTypes.WindowStateChanged:
|
||||
var cancelNotification = true;
|
||||
var testNodes = GetWindowNodes(root, e, n => n.ViewIdResourceName != null && n.Text != null, false);
|
||||
var testNodesData = testNodes.Select(n => new { id = n.ViewIdResourceName, text = n.Text });
|
||||
testNodes.Dispose();
|
||||
|
||||
if(e.PackageName == BitwardenPackage)
|
||||
{
|
||||
CancelNotification();
|
||||
break;
|
||||
}
|
||||
var notificationManager = (NotificationManager)GetSystemService(NotificationService);
|
||||
switch(e.EventType)
|
||||
{
|
||||
case EventTypes.WindowContentChanged:
|
||||
case EventTypes.WindowStateChanged:
|
||||
var cancelNotification = true;
|
||||
|
||||
var passwordNodes = GetWindowNodes(root, e, n => n.Password);
|
||||
if(passwordNodes.Any())
|
||||
{
|
||||
var uri = GetUri(root);
|
||||
if(uri.Contains(BitwardenWebsite))
|
||||
if(e.PackageName == BitwardenPackage)
|
||||
{
|
||||
notificationManager?.Cancel(AutoFillNotificationId);
|
||||
break;
|
||||
}
|
||||
|
||||
if(NeedToAutofill(AutofillActivity.LastCredentials, uri))
|
||||
var passwordNodes = GetWindowNodes(root, e, n => n.Password, false);
|
||||
if(passwordNodes.Count > 0)
|
||||
{
|
||||
var allEditTexts = GetWindowNodes(root, e, n => EditText(n));
|
||||
var usernameEditText = allEditTexts.TakeWhile(n => !n.Password).LastOrDefault();
|
||||
FillCredentials(usernameEditText, passwordNodes);
|
||||
}
|
||||
else
|
||||
{
|
||||
NotifyToAutofill(uri);
|
||||
cancelNotification = false;
|
||||
var uri = GetUri(root);
|
||||
if(uri != null && !uri.Contains(BitwardenWebsite))
|
||||
{
|
||||
if(NeedToAutofill(AutofillActivity.LastCredentials, uri))
|
||||
{
|
||||
var allEditTexts = GetWindowNodes(root, e, n => EditText(n), false);
|
||||
var usernameEditText = allEditTexts.TakeWhile(n => !n.Password).LastOrDefault();
|
||||
FillCredentials(usernameEditText, passwordNodes);
|
||||
|
||||
allEditTexts.Dispose();
|
||||
usernameEditText.Dispose();
|
||||
}
|
||||
else
|
||||
{
|
||||
NotifyToAutofill(uri, notificationManager);
|
||||
cancelNotification = false;
|
||||
}
|
||||
}
|
||||
|
||||
AutofillActivity.LastCredentials = null;
|
||||
}
|
||||
|
||||
AutofillActivity.LastCredentials = null;
|
||||
}
|
||||
passwordNodes.Dispose();
|
||||
|
||||
if(cancelNotification)
|
||||
{
|
||||
CancelNotification();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
if(cancelNotification)
|
||||
{
|
||||
notificationManager?.Cancel(AutoFillNotificationId);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
notificationManager?.Dispose();
|
||||
root.Dispose();
|
||||
e.Dispose();
|
||||
}
|
||||
// Some unknown condition is causing NullReferenceException's in production. Suppress it for now.
|
||||
catch(NullReferenceException) { }
|
||||
}
|
||||
|
||||
public override void OnInterrupt()
|
||||
@@ -111,64 +129,44 @@ namespace Bit.Android
|
||||
|
||||
}
|
||||
|
||||
protected override void OnServiceConnected()
|
||||
{
|
||||
base.OnServiceConnected();
|
||||
Enabled = true;
|
||||
}
|
||||
|
||||
public override void OnDestroy()
|
||||
{
|
||||
base.OnDestroy();
|
||||
Enabled = false;
|
||||
}
|
||||
|
||||
private void CancelNotification()
|
||||
{
|
||||
var notificationManager = ((NotificationManager)GetSystemService(NotificationService));
|
||||
notificationManager.Cancel(AutoFillNotificationId);
|
||||
}
|
||||
|
||||
private string GetUri(AccessibilityNodeInfo root)
|
||||
{
|
||||
var uri = string.Concat(App.Constants.AndroidAppProtocol, root.PackageName);
|
||||
if(BrowserPackages.ContainsKey(root.PackageName))
|
||||
if(SupportedBrowsers.ContainsKey(root.PackageName))
|
||||
{
|
||||
foreach(var addressViewId in BrowserPackages[root.PackageName])
|
||||
var addressNode = root.FindAccessibilityNodeInfosByViewId(
|
||||
$"{root.PackageName}:id/{SupportedBrowsers[root.PackageName].UriViewId}").FirstOrDefault();
|
||||
if(addressNode != null)
|
||||
{
|
||||
var addressNode = root.FindAccessibilityNodeInfosByViewId(
|
||||
$"{root.PackageName}:id/{addressViewId}").FirstOrDefault();
|
||||
if(addressNode == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
uri = ExtractUri(uri, addressNode);
|
||||
break;
|
||||
uri = ExtractUri(uri, addressNode, SupportedBrowsers[root.PackageName]);
|
||||
addressNode.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
return uri;
|
||||
}
|
||||
|
||||
private string ExtractUri(string uri, AccessibilityNodeInfo addressNode)
|
||||
private string ExtractUri(string uri, AccessibilityNodeInfo addressNode, Browser browser)
|
||||
{
|
||||
if(addressNode != null)
|
||||
if(addressNode?.Text != null)
|
||||
{
|
||||
uri = addressNode.Text;
|
||||
if(!uri.Contains("://"))
|
||||
uri = browser.GetUriFunction(addressNode.Text).Trim();
|
||||
if(uri != null && uri.Contains("."))
|
||||
{
|
||||
uri = string.Concat("http://", uri);
|
||||
}
|
||||
else if(Build.VERSION.SdkInt <= BuildVersionCodes.KitkatWatch)
|
||||
{
|
||||
var parts = uri.Split(new string[] { ". " }, StringSplitOptions.None);
|
||||
if(parts.Length > 1)
|
||||
if(!uri.Contains("://") && !uri.Contains(" "))
|
||||
{
|
||||
var urlPart = parts.FirstOrDefault(p => p.StartsWith("http"));
|
||||
if(urlPart != null)
|
||||
uri = string.Concat("http://", uri);
|
||||
}
|
||||
else if(Build.VERSION.SdkInt <= BuildVersionCodes.KitkatWatch)
|
||||
{
|
||||
var parts = uri.Split(new string[] { ". " }, StringSplitOptions.None);
|
||||
if(parts.Length > 1)
|
||||
{
|
||||
uri = urlPart.Trim();
|
||||
var urlPart = parts.FirstOrDefault(p => p.StartsWith("http"));
|
||||
if(urlPart != null)
|
||||
{
|
||||
uri = urlPart.Trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -200,11 +198,16 @@ namespace Bit.Android
|
||||
|
||||
private static bool EditText(AccessibilityNodeInfo n)
|
||||
{
|
||||
return n.ClassName != null && n.ClassName.Contains("EditText");
|
||||
return n?.ClassName?.Contains("EditText") ?? false;
|
||||
}
|
||||
|
||||
private void NotifyToAutofill(string uri)
|
||||
private void NotifyToAutofill(string uri, NotificationManager notificationManager)
|
||||
{
|
||||
if(notificationManager == null || string.IsNullOrWhiteSpace(uri))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var intent = new Intent(this, typeof(AutofillActivity));
|
||||
intent.PutExtra("uri", uri);
|
||||
intent.SetFlags(ActivityFlags.NewTask | ActivityFlags.SingleTop | ActivityFlags.ClearTop);
|
||||
@@ -225,16 +228,17 @@ namespace Bit.Android
|
||||
Resource.Color.primary));
|
||||
}
|
||||
|
||||
var notificationManager = (NotificationManager)GetSystemService(NotificationService);
|
||||
notificationManager.Notify(AutoFillNotificationId, builder.Build());
|
||||
|
||||
builder.Dispose();
|
||||
}
|
||||
|
||||
private void FillCredentials(AccessibilityNodeInfo usernameNode, IEnumerable<AccessibilityNodeInfo> passwordNodes)
|
||||
{
|
||||
FillEditText(usernameNode, AutofillActivity.LastCredentials.Username);
|
||||
FillEditText(usernameNode, AutofillActivity.LastCredentials?.Username);
|
||||
foreach(var n in passwordNodes)
|
||||
{
|
||||
FillEditText(n, AutofillActivity.LastCredentials.Password);
|
||||
FillEditText(n, AutofillActivity.LastCredentials?.Password);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -250,22 +254,63 @@ namespace Bit.Android
|
||||
editTextNode.PerformAction(global::Android.Views.Accessibility.Action.SetText, bundle);
|
||||
}
|
||||
|
||||
private IEnumerable<AccessibilityNodeInfo> GetWindowNodes(AccessibilityNodeInfo n,
|
||||
AccessibilityEvent e, Func<AccessibilityNodeInfo, bool> p)
|
||||
private NodeList GetWindowNodes(AccessibilityNodeInfo n, AccessibilityEvent e,
|
||||
Func<AccessibilityNodeInfo, bool> condition, bool disposeIfUnused, NodeList nodes = null)
|
||||
{
|
||||
if(nodes == null)
|
||||
{
|
||||
nodes = new NodeList();
|
||||
}
|
||||
|
||||
if(n != null)
|
||||
{
|
||||
if(n.WindowId == e.WindowId && !(n.ViewIdResourceName?.StartsWith(SystemUiPackage) ?? false) && p(n))
|
||||
var dispose = disposeIfUnused;
|
||||
if(n.WindowId == e.WindowId && !(n.ViewIdResourceName?.StartsWith(SystemUiPackage) ?? false) && condition(n))
|
||||
{
|
||||
yield return n;
|
||||
dispose = false;
|
||||
nodes.Add(n);
|
||||
}
|
||||
|
||||
for(int i = 0; i < n.ChildCount; i++)
|
||||
for(var i = 0; i < n.ChildCount; i++)
|
||||
{
|
||||
foreach(var node in GetWindowNodes(n.GetChild(i), e, p))
|
||||
{
|
||||
yield return node;
|
||||
}
|
||||
GetWindowNodes(n.GetChild(i), e, condition, true, nodes);
|
||||
}
|
||||
|
||||
if(dispose)
|
||||
{
|
||||
n.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
return nodes;
|
||||
}
|
||||
|
||||
public class Browser
|
||||
{
|
||||
public Browser(string packageName, string uriViewId)
|
||||
{
|
||||
PackageName = packageName;
|
||||
UriViewId = uriViewId;
|
||||
}
|
||||
|
||||
public Browser(string packageName, string uriViewId, Func<string, string> getUriFunction)
|
||||
: this(packageName, uriViewId)
|
||||
{
|
||||
GetUriFunction = getUriFunction;
|
||||
}
|
||||
|
||||
public string PackageName { get; set; }
|
||||
public string UriViewId { get; set; }
|
||||
public Func<string, string> GetUriFunction { get; set; } = (s) => s;
|
||||
}
|
||||
|
||||
public class NodeList : List<AccessibilityNodeInfo>, IDisposable
|
||||
{
|
||||
public void Dispose()
|
||||
{
|
||||
foreach(var item in this)
|
||||
{
|
||||
item.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,13 +18,15 @@ namespace Bit.Android.Controls
|
||||
{
|
||||
private bool _isPassword;
|
||||
private bool _toggledPassword;
|
||||
private bool _isDisposed;
|
||||
private ExtendedEntry _view;
|
||||
|
||||
protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
|
||||
{
|
||||
base.OnElementChanged(e);
|
||||
|
||||
var view = (ExtendedEntry)Element;
|
||||
_isPassword = view.IsPassword;
|
||||
_view = (ExtendedEntry)Element;
|
||||
_isPassword = _view.IsPassword;
|
||||
|
||||
if(Control != null)
|
||||
{
|
||||
@@ -36,69 +38,74 @@ namespace Bit.Android.Controls
|
||||
}
|
||||
}
|
||||
|
||||
SetBorder(view);
|
||||
SetMaxLength(view);
|
||||
SetReturnType(view);
|
||||
SetBorder(_view);
|
||||
SetMaxLength(_view);
|
||||
SetReturnType(_view);
|
||||
|
||||
// Editor Action is called when the return button is pressed
|
||||
Control.EditorAction += (object sender, TextView.EditorActionEventArgs args) =>
|
||||
{
|
||||
if(view.ReturnType != ReturnType.Next)
|
||||
{
|
||||
view.Unfocus();
|
||||
}
|
||||
Control.EditorAction += Control_EditorAction;
|
||||
|
||||
// Call all the methods attached to base_entry event handler Completed
|
||||
view.InvokeCompleted();
|
||||
};
|
||||
|
||||
if(view.DisableAutocapitalize)
|
||||
if(_view.DisableAutocapitalize)
|
||||
{
|
||||
Control.SetRawInputType(Control.InputType |= InputTypes.TextVariationEmailAddress);
|
||||
}
|
||||
|
||||
if(view.Autocorrect.HasValue)
|
||||
if(_view.Autocorrect.HasValue)
|
||||
{
|
||||
Control.SetRawInputType(Control.InputType |= InputTypes.TextFlagNoSuggestions);
|
||||
}
|
||||
|
||||
view.ToggleIsPassword += (object sender, EventArgs args) =>
|
||||
{
|
||||
var cursorStart = Control.SelectionStart;
|
||||
var cursorEnd = Control.SelectionEnd;
|
||||
_view.ToggleIsPassword += ToggleIsPassword;
|
||||
|
||||
Control.TransformationMethod = _isPassword ? null : new PasswordTransformationMethod();
|
||||
|
||||
// set focus
|
||||
Control.RequestFocus();
|
||||
|
||||
if(_toggledPassword)
|
||||
{
|
||||
// restore cursor position
|
||||
Control.SetSelection(cursorStart, cursorEnd);
|
||||
}
|
||||
else
|
||||
{
|
||||
// set cursor to end
|
||||
Control.SetSelection(Control.Text.Length);
|
||||
}
|
||||
|
||||
// show keyboard
|
||||
var app = XLabs.Ioc.Resolver.Resolve<global::Android.App.Application>();
|
||||
var inputMethodManager =
|
||||
app.GetSystemService(global::Android.Content.Context.InputMethodService) as InputMethodManager;
|
||||
inputMethodManager.ShowSoftInput(Control, ShowFlags.Forced);
|
||||
inputMethodManager.ToggleSoftInput(ShowFlags.Forced, HideSoftInputFlags.ImplicitOnly);
|
||||
|
||||
_isPassword = view.IsPasswordFromToggled = !_isPassword;
|
||||
_toggledPassword = true;
|
||||
};
|
||||
|
||||
if(view.FontFamily == "monospace")
|
||||
if(_view.FontFamily == "monospace")
|
||||
{
|
||||
Control.Typeface = Typeface.Monospace;
|
||||
}
|
||||
}
|
||||
|
||||
private void ToggleIsPassword(object sender, EventArgs e)
|
||||
{
|
||||
var cursorStart = Control.SelectionStart;
|
||||
var cursorEnd = Control.SelectionEnd;
|
||||
|
||||
Control.TransformationMethod = _isPassword ? null : new PasswordTransformationMethod();
|
||||
|
||||
// set focus
|
||||
Control.RequestFocus();
|
||||
|
||||
if(_toggledPassword)
|
||||
{
|
||||
// restore cursor position
|
||||
Control.SetSelection(cursorStart, cursorEnd);
|
||||
}
|
||||
else
|
||||
{
|
||||
// set cursor to end
|
||||
Control.SetSelection(Control.Text.Length);
|
||||
}
|
||||
|
||||
// show keyboard
|
||||
var app = XLabs.Ioc.Resolver.Resolve<global::Android.App.Application>();
|
||||
var inputMethodManager =
|
||||
app.GetSystemService(global::Android.Content.Context.InputMethodService) as InputMethodManager;
|
||||
inputMethodManager.ShowSoftInput(Control, ShowFlags.Forced);
|
||||
inputMethodManager.ToggleSoftInput(ShowFlags.Forced, HideSoftInputFlags.ImplicitOnly);
|
||||
|
||||
_isPassword = _view.IsPasswordFromToggled = !_isPassword;
|
||||
_toggledPassword = true;
|
||||
}
|
||||
|
||||
private void Control_EditorAction(object sender, TextView.EditorActionEventArgs e)
|
||||
{
|
||||
if(_view.ReturnType != ReturnType.Next)
|
||||
{
|
||||
_view.Unfocus();
|
||||
}
|
||||
|
||||
// Call all the methods attached to base_entry event handler Completed
|
||||
_view.InvokeCompleted();
|
||||
}
|
||||
|
||||
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
var view = (ExtendedEntry)Element;
|
||||
@@ -124,6 +131,23 @@ namespace Bit.Android.Controls
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if(_isDisposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_isDisposed = true;
|
||||
if(disposing && Control != null)
|
||||
{
|
||||
_view.ToggleIsPassword -= ToggleIsPassword;
|
||||
Control.EditorAction -= Control_EditorAction;
|
||||
}
|
||||
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
private void SetReturnType(ExtendedEntry view)
|
||||
{
|
||||
if(view.ReturnType.HasValue)
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using Bit.Android.Controls;
|
||||
using Bit.App.Controls;
|
||||
using Xamarin.Forms;
|
||||
|
||||
@@ -28,22 +28,7 @@ namespace Bit.Android
|
||||
|
||||
protected override void OnCreate(Bundle bundle)
|
||||
{
|
||||
string uri = null;
|
||||
if(!Intent.Flags.HasFlag(ActivityFlags.LaunchedFromHistory) && Intent.HasExtra("uri") && Intent.HasExtra("ts"))
|
||||
{
|
||||
var tsDiff = Java.Lang.JavaSystem.CurrentTimeMillis() - Intent.GetLongExtra("ts", 0);
|
||||
if(tsDiff < 5000)
|
||||
{
|
||||
uri = Intent.GetStringExtra("uri");
|
||||
}
|
||||
|
||||
// Attempt to clear intent for future
|
||||
Intent.ReplaceExtras(new Bundle());
|
||||
Intent.SetAction(string.Empty);
|
||||
Intent.SetData(null);
|
||||
Intent.SetFlags(0);
|
||||
}
|
||||
|
||||
var uri = Intent.GetStringExtra("uri");
|
||||
if(!Resolver.IsSet)
|
||||
{
|
||||
MainApplication.SetIoc(Application);
|
||||
@@ -134,7 +119,7 @@ namespace Bit.Android
|
||||
{
|
||||
Parent.SetResult(Result.Ok, data);
|
||||
}
|
||||
|
||||
|
||||
Finish();
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ using XLabs.Ioc.Unity;
|
||||
using System.Threading.Tasks;
|
||||
using Plugin.Settings.Abstractions;
|
||||
using Xamarin.Android.Net;
|
||||
using FFImageLoading.Forms.Droid;
|
||||
|
||||
namespace Bit.Android
|
||||
{
|
||||
@@ -212,6 +213,7 @@ namespace Bit.Android
|
||||
.RegisterType<IHttpService, HttpService>(new ContainerControlledLifetimeManager())
|
||||
.RegisterType<ITokenService, TokenService>(new ContainerControlledLifetimeManager())
|
||||
.RegisterType<ISettingsService, SettingsService>(new ContainerControlledLifetimeManager())
|
||||
.RegisterType<IMemoryService, MemoryService>(new ContainerControlledLifetimeManager())
|
||||
// Repositories
|
||||
.RegisterType<IFolderRepository, FolderRepository>(new ContainerControlledLifetimeManager())
|
||||
.RegisterType<IFolderApiRepository, FolderApiRepository>(new ContainerControlledLifetimeManager())
|
||||
@@ -231,6 +233,7 @@ namespace Bit.Android
|
||||
|
||||
CrossPushNotification.Initialize(container.Resolve<IPushNotificationListener>(), "962181367620");
|
||||
container.RegisterInstance(CrossPushNotification.Current, new ContainerControlledLifetimeManager());
|
||||
CachedImageRenderer.Init();
|
||||
|
||||
Resolver.SetResolver(new UnityResolver(container));
|
||||
CrossFingerprint.SetCurrentActivityResolver(() => CrossCurrentActivity.Current.Activity);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.x8bit.bitwarden" android:versionName="1.3.0" android:installLocation="auto" android:versionCode="101">
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.x8bit.bitwarden" android:versionName="1.4.4" android:installLocation="auto" android:versionCode="502">
|
||||
<uses-sdk android:minSdkVersion="19" android:targetSdkVersion="23" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
|
||||
|
||||
314
src/Android/Resources/Resource.Designer.cs
generated
@@ -2293,472 +2293,472 @@ namespace Bit.Android
|
||||
public const int accessibility_step2 = 2130837582;
|
||||
|
||||
// aapt resource value: 0x7f02004f
|
||||
public const int close = 2130837583;
|
||||
public const int cloudup = 2130837583;
|
||||
|
||||
// aapt resource value: 0x7f020050
|
||||
public const int cloudup = 2130837584;
|
||||
public const int cogs = 2130837584;
|
||||
|
||||
// aapt resource value: 0x7f020051
|
||||
public const int cogs = 2130837585;
|
||||
public const int cogs_selected = 2130837585;
|
||||
|
||||
// aapt resource value: 0x7f020052
|
||||
public const int cogs_selected = 2130837586;
|
||||
public const int common_full_open_on_phone = 2130837586;
|
||||
|
||||
// aapt resource value: 0x7f020053
|
||||
public const int common_full_open_on_phone = 2130837587;
|
||||
public const int common_google_signin_btn_icon_dark = 2130837587;
|
||||
|
||||
// aapt resource value: 0x7f020054
|
||||
public const int common_google_signin_btn_icon_dark = 2130837588;
|
||||
public const int common_google_signin_btn_icon_dark_disabled = 2130837588;
|
||||
|
||||
// aapt resource value: 0x7f020055
|
||||
public const int common_google_signin_btn_icon_dark_disabled = 2130837589;
|
||||
public const int common_google_signin_btn_icon_dark_focused = 2130837589;
|
||||
|
||||
// aapt resource value: 0x7f020056
|
||||
public const int common_google_signin_btn_icon_dark_focused = 2130837590;
|
||||
public const int common_google_signin_btn_icon_dark_normal = 2130837590;
|
||||
|
||||
// aapt resource value: 0x7f020057
|
||||
public const int common_google_signin_btn_icon_dark_normal = 2130837591;
|
||||
public const int common_google_signin_btn_icon_dark_pressed = 2130837591;
|
||||
|
||||
// aapt resource value: 0x7f020058
|
||||
public const int common_google_signin_btn_icon_dark_pressed = 2130837592;
|
||||
public const int common_google_signin_btn_icon_light = 2130837592;
|
||||
|
||||
// aapt resource value: 0x7f020059
|
||||
public const int common_google_signin_btn_icon_light = 2130837593;
|
||||
public const int common_google_signin_btn_icon_light_disabled = 2130837593;
|
||||
|
||||
// aapt resource value: 0x7f02005a
|
||||
public const int common_google_signin_btn_icon_light_disabled = 2130837594;
|
||||
public const int common_google_signin_btn_icon_light_focused = 2130837594;
|
||||
|
||||
// aapt resource value: 0x7f02005b
|
||||
public const int common_google_signin_btn_icon_light_focused = 2130837595;
|
||||
public const int common_google_signin_btn_icon_light_normal = 2130837595;
|
||||
|
||||
// aapt resource value: 0x7f02005c
|
||||
public const int common_google_signin_btn_icon_light_normal = 2130837596;
|
||||
public const int common_google_signin_btn_icon_light_pressed = 2130837596;
|
||||
|
||||
// aapt resource value: 0x7f02005d
|
||||
public const int common_google_signin_btn_icon_light_pressed = 2130837597;
|
||||
public const int common_google_signin_btn_text_dark = 2130837597;
|
||||
|
||||
// aapt resource value: 0x7f02005e
|
||||
public const int common_google_signin_btn_text_dark = 2130837598;
|
||||
public const int common_google_signin_btn_text_dark_disabled = 2130837598;
|
||||
|
||||
// aapt resource value: 0x7f02005f
|
||||
public const int common_google_signin_btn_text_dark_disabled = 2130837599;
|
||||
public const int common_google_signin_btn_text_dark_focused = 2130837599;
|
||||
|
||||
// aapt resource value: 0x7f020060
|
||||
public const int common_google_signin_btn_text_dark_focused = 2130837600;
|
||||
public const int common_google_signin_btn_text_dark_normal = 2130837600;
|
||||
|
||||
// aapt resource value: 0x7f020061
|
||||
public const int common_google_signin_btn_text_dark_normal = 2130837601;
|
||||
public const int common_google_signin_btn_text_dark_pressed = 2130837601;
|
||||
|
||||
// aapt resource value: 0x7f020062
|
||||
public const int common_google_signin_btn_text_dark_pressed = 2130837602;
|
||||
public const int common_google_signin_btn_text_light = 2130837602;
|
||||
|
||||
// aapt resource value: 0x7f020063
|
||||
public const int common_google_signin_btn_text_light = 2130837603;
|
||||
public const int common_google_signin_btn_text_light_disabled = 2130837603;
|
||||
|
||||
// aapt resource value: 0x7f020064
|
||||
public const int common_google_signin_btn_text_light_disabled = 2130837604;
|
||||
public const int common_google_signin_btn_text_light_focused = 2130837604;
|
||||
|
||||
// aapt resource value: 0x7f020065
|
||||
public const int common_google_signin_btn_text_light_focused = 2130837605;
|
||||
public const int common_google_signin_btn_text_light_normal = 2130837605;
|
||||
|
||||
// aapt resource value: 0x7f020066
|
||||
public const int common_google_signin_btn_text_light_normal = 2130837606;
|
||||
public const int common_google_signin_btn_text_light_pressed = 2130837606;
|
||||
|
||||
// aapt resource value: 0x7f020067
|
||||
public const int common_google_signin_btn_text_light_pressed = 2130837607;
|
||||
public const int common_ic_googleplayservices = 2130837607;
|
||||
|
||||
// aapt resource value: 0x7f020068
|
||||
public const int common_ic_googleplayservices = 2130837608;
|
||||
public const int common_plus_signin_btn_icon_dark = 2130837608;
|
||||
|
||||
// aapt resource value: 0x7f020069
|
||||
public const int common_plus_signin_btn_icon_dark = 2130837609;
|
||||
public const int common_plus_signin_btn_icon_dark_disabled = 2130837609;
|
||||
|
||||
// aapt resource value: 0x7f02006a
|
||||
public const int common_plus_signin_btn_icon_dark_disabled = 2130837610;
|
||||
public const int common_plus_signin_btn_icon_dark_focused = 2130837610;
|
||||
|
||||
// aapt resource value: 0x7f02006b
|
||||
public const int common_plus_signin_btn_icon_dark_focused = 2130837611;
|
||||
public const int common_plus_signin_btn_icon_dark_normal = 2130837611;
|
||||
|
||||
// aapt resource value: 0x7f02006c
|
||||
public const int common_plus_signin_btn_icon_dark_normal = 2130837612;
|
||||
public const int common_plus_signin_btn_icon_dark_pressed = 2130837612;
|
||||
|
||||
// aapt resource value: 0x7f02006d
|
||||
public const int common_plus_signin_btn_icon_dark_pressed = 2130837613;
|
||||
public const int common_plus_signin_btn_icon_light = 2130837613;
|
||||
|
||||
// aapt resource value: 0x7f02006e
|
||||
public const int common_plus_signin_btn_icon_light = 2130837614;
|
||||
public const int common_plus_signin_btn_icon_light_disabled = 2130837614;
|
||||
|
||||
// aapt resource value: 0x7f02006f
|
||||
public const int common_plus_signin_btn_icon_light_disabled = 2130837615;
|
||||
public const int common_plus_signin_btn_icon_light_focused = 2130837615;
|
||||
|
||||
// aapt resource value: 0x7f020070
|
||||
public const int common_plus_signin_btn_icon_light_focused = 2130837616;
|
||||
public const int common_plus_signin_btn_icon_light_normal = 2130837616;
|
||||
|
||||
// aapt resource value: 0x7f020071
|
||||
public const int common_plus_signin_btn_icon_light_normal = 2130837617;
|
||||
public const int common_plus_signin_btn_icon_light_pressed = 2130837617;
|
||||
|
||||
// aapt resource value: 0x7f020072
|
||||
public const int common_plus_signin_btn_icon_light_pressed = 2130837618;
|
||||
public const int common_plus_signin_btn_text_dark = 2130837618;
|
||||
|
||||
// aapt resource value: 0x7f020073
|
||||
public const int common_plus_signin_btn_text_dark = 2130837619;
|
||||
public const int common_plus_signin_btn_text_dark_disabled = 2130837619;
|
||||
|
||||
// aapt resource value: 0x7f020074
|
||||
public const int common_plus_signin_btn_text_dark_disabled = 2130837620;
|
||||
public const int common_plus_signin_btn_text_dark_focused = 2130837620;
|
||||
|
||||
// aapt resource value: 0x7f020075
|
||||
public const int common_plus_signin_btn_text_dark_focused = 2130837621;
|
||||
public const int common_plus_signin_btn_text_dark_normal = 2130837621;
|
||||
|
||||
// aapt resource value: 0x7f020076
|
||||
public const int common_plus_signin_btn_text_dark_normal = 2130837622;
|
||||
public const int common_plus_signin_btn_text_dark_pressed = 2130837622;
|
||||
|
||||
// aapt resource value: 0x7f020077
|
||||
public const int common_plus_signin_btn_text_dark_pressed = 2130837623;
|
||||
public const int common_plus_signin_btn_text_light = 2130837623;
|
||||
|
||||
// aapt resource value: 0x7f020078
|
||||
public const int common_plus_signin_btn_text_light = 2130837624;
|
||||
public const int common_plus_signin_btn_text_light_disabled = 2130837624;
|
||||
|
||||
// aapt resource value: 0x7f020079
|
||||
public const int common_plus_signin_btn_text_light_disabled = 2130837625;
|
||||
public const int common_plus_signin_btn_text_light_focused = 2130837625;
|
||||
|
||||
// aapt resource value: 0x7f02007a
|
||||
public const int common_plus_signin_btn_text_light_focused = 2130837626;
|
||||
public const int common_plus_signin_btn_text_light_normal = 2130837626;
|
||||
|
||||
// aapt resource value: 0x7f02007b
|
||||
public const int common_plus_signin_btn_text_light_normal = 2130837627;
|
||||
public const int common_plus_signin_btn_text_light_pressed = 2130837627;
|
||||
|
||||
// aapt resource value: 0x7f02007c
|
||||
public const int common_plus_signin_btn_text_light_pressed = 2130837628;
|
||||
public const int design_fab_background = 2130837628;
|
||||
|
||||
// aapt resource value: 0x7f02007d
|
||||
public const int design_fab_background = 2130837629;
|
||||
public const int design_snackbar_background = 2130837629;
|
||||
|
||||
// aapt resource value: 0x7f02007e
|
||||
public const int design_snackbar_background = 2130837630;
|
||||
public const int envelope = 2130837630;
|
||||
|
||||
// aapt resource value: 0x7f02007f
|
||||
public const int envelope = 2130837631;
|
||||
public const int eye = 2130837631;
|
||||
|
||||
// aapt resource value: 0x7f020080
|
||||
public const int eye = 2130837632;
|
||||
public const int eye_slash = 2130837632;
|
||||
|
||||
// aapt resource value: 0x7f020081
|
||||
public const int eye_slash = 2130837633;
|
||||
public const int fa_lock = 2130837633;
|
||||
|
||||
// aapt resource value: 0x7f020082
|
||||
public const int fa_lock = 2130837634;
|
||||
public const int fa_lock_selected = 2130837634;
|
||||
|
||||
// aapt resource value: 0x7f020083
|
||||
public const int fa_lock_selected = 2130837635;
|
||||
public const int fingerprint = 2130837635;
|
||||
|
||||
// aapt resource value: 0x7f020084
|
||||
public const int fingerprint = 2130837636;
|
||||
public const int fingerprint_white = 2130837636;
|
||||
|
||||
// aapt resource value: 0x7f020085
|
||||
public const int fingerprint_white = 2130837637;
|
||||
public const int folder = 2130837637;
|
||||
|
||||
// aapt resource value: 0x7f020086
|
||||
public const int folder = 2130837638;
|
||||
public const int globe = 2130837638;
|
||||
|
||||
// aapt resource value: 0x7f020087
|
||||
public const int globe = 2130837639;
|
||||
public const int hockeyapp_btn_background = 2130837639;
|
||||
|
||||
// aapt resource value: 0x7f020088
|
||||
public const int hockeyapp_btn_background = 2130837640;
|
||||
public const int ic_audiotrack = 2130837640;
|
||||
|
||||
// aapt resource value: 0x7f020089
|
||||
public const int ic_audiotrack = 2130837641;
|
||||
public const int ic_audiotrack_light = 2130837641;
|
||||
|
||||
// aapt resource value: 0x7f02008a
|
||||
public const int ic_audiotrack_light = 2130837642;
|
||||
public const int ic_bluetooth_grey = 2130837642;
|
||||
|
||||
// aapt resource value: 0x7f02008b
|
||||
public const int ic_bluetooth_grey = 2130837643;
|
||||
public const int ic_bluetooth_white = 2130837643;
|
||||
|
||||
// aapt resource value: 0x7f02008c
|
||||
public const int ic_bluetooth_white = 2130837644;
|
||||
public const int ic_cast_dark = 2130837644;
|
||||
|
||||
// aapt resource value: 0x7f02008d
|
||||
public const int ic_cast_dark = 2130837645;
|
||||
public const int ic_cast_disabled_light = 2130837645;
|
||||
|
||||
// aapt resource value: 0x7f02008e
|
||||
public const int ic_cast_disabled_light = 2130837646;
|
||||
public const int ic_cast_grey = 2130837646;
|
||||
|
||||
// aapt resource value: 0x7f02008f
|
||||
public const int ic_cast_grey = 2130837647;
|
||||
public const int ic_cast_light = 2130837647;
|
||||
|
||||
// aapt resource value: 0x7f020090
|
||||
public const int ic_cast_light = 2130837648;
|
||||
public const int ic_cast_off_light = 2130837648;
|
||||
|
||||
// aapt resource value: 0x7f020091
|
||||
public const int ic_cast_off_light = 2130837649;
|
||||
public const int ic_cast_on_0_light = 2130837649;
|
||||
|
||||
// aapt resource value: 0x7f020092
|
||||
public const int ic_cast_on_0_light = 2130837650;
|
||||
public const int ic_cast_on_1_light = 2130837650;
|
||||
|
||||
// aapt resource value: 0x7f020093
|
||||
public const int ic_cast_on_1_light = 2130837651;
|
||||
public const int ic_cast_on_2_light = 2130837651;
|
||||
|
||||
// aapt resource value: 0x7f020094
|
||||
public const int ic_cast_on_2_light = 2130837652;
|
||||
public const int ic_cast_on_light = 2130837652;
|
||||
|
||||
// aapt resource value: 0x7f020095
|
||||
public const int ic_cast_on_light = 2130837653;
|
||||
public const int ic_cast_white = 2130837653;
|
||||
|
||||
// aapt resource value: 0x7f020096
|
||||
public const int ic_cast_white = 2130837654;
|
||||
public const int ic_close_dark = 2130837654;
|
||||
|
||||
// aapt resource value: 0x7f020097
|
||||
public const int ic_close_dark = 2130837655;
|
||||
public const int ic_close_light = 2130837655;
|
||||
|
||||
// aapt resource value: 0x7f020098
|
||||
public const int ic_close_light = 2130837656;
|
||||
public const int ic_collapse = 2130837656;
|
||||
|
||||
// aapt resource value: 0x7f020099
|
||||
public const int ic_collapse = 2130837657;
|
||||
public const int ic_collapse_00000 = 2130837657;
|
||||
|
||||
// aapt resource value: 0x7f02009a
|
||||
public const int ic_collapse_00000 = 2130837658;
|
||||
public const int ic_collapse_00001 = 2130837658;
|
||||
|
||||
// aapt resource value: 0x7f02009b
|
||||
public const int ic_collapse_00001 = 2130837659;
|
||||
public const int ic_collapse_00002 = 2130837659;
|
||||
|
||||
// aapt resource value: 0x7f02009c
|
||||
public const int ic_collapse_00002 = 2130837660;
|
||||
public const int ic_collapse_00003 = 2130837660;
|
||||
|
||||
// aapt resource value: 0x7f02009d
|
||||
public const int ic_collapse_00003 = 2130837661;
|
||||
public const int ic_collapse_00004 = 2130837661;
|
||||
|
||||
// aapt resource value: 0x7f02009e
|
||||
public const int ic_collapse_00004 = 2130837662;
|
||||
public const int ic_collapse_00005 = 2130837662;
|
||||
|
||||
// aapt resource value: 0x7f02009f
|
||||
public const int ic_collapse_00005 = 2130837663;
|
||||
public const int ic_collapse_00006 = 2130837663;
|
||||
|
||||
// aapt resource value: 0x7f0200a0
|
||||
public const int ic_collapse_00006 = 2130837664;
|
||||
public const int ic_collapse_00007 = 2130837664;
|
||||
|
||||
// aapt resource value: 0x7f0200a1
|
||||
public const int ic_collapse_00007 = 2130837665;
|
||||
public const int ic_collapse_00008 = 2130837665;
|
||||
|
||||
// aapt resource value: 0x7f0200a2
|
||||
public const int ic_collapse_00008 = 2130837666;
|
||||
public const int ic_collapse_00009 = 2130837666;
|
||||
|
||||
// aapt resource value: 0x7f0200a3
|
||||
public const int ic_collapse_00009 = 2130837667;
|
||||
public const int ic_collapse_00010 = 2130837667;
|
||||
|
||||
// aapt resource value: 0x7f0200a4
|
||||
public const int ic_collapse_00010 = 2130837668;
|
||||
public const int ic_collapse_00011 = 2130837668;
|
||||
|
||||
// aapt resource value: 0x7f0200a5
|
||||
public const int ic_collapse_00011 = 2130837669;
|
||||
public const int ic_collapse_00012 = 2130837669;
|
||||
|
||||
// aapt resource value: 0x7f0200a6
|
||||
public const int ic_collapse_00012 = 2130837670;
|
||||
public const int ic_collapse_00013 = 2130837670;
|
||||
|
||||
// aapt resource value: 0x7f0200a7
|
||||
public const int ic_collapse_00013 = 2130837671;
|
||||
public const int ic_collapse_00014 = 2130837671;
|
||||
|
||||
// aapt resource value: 0x7f0200a8
|
||||
public const int ic_collapse_00014 = 2130837672;
|
||||
public const int ic_collapse_00015 = 2130837672;
|
||||
|
||||
// aapt resource value: 0x7f0200a9
|
||||
public const int ic_collapse_00015 = 2130837673;
|
||||
public const int ic_errorstatus = 2130837673;
|
||||
|
||||
// aapt resource value: 0x7f0200aa
|
||||
public const int ic_errorstatus = 2130837674;
|
||||
public const int ic_expand = 2130837674;
|
||||
|
||||
// aapt resource value: 0x7f0200ab
|
||||
public const int ic_expand = 2130837675;
|
||||
public const int ic_expand_00000 = 2130837675;
|
||||
|
||||
// aapt resource value: 0x7f0200ac
|
||||
public const int ic_expand_00000 = 2130837676;
|
||||
public const int ic_expand_00001 = 2130837676;
|
||||
|
||||
// aapt resource value: 0x7f0200ad
|
||||
public const int ic_expand_00001 = 2130837677;
|
||||
public const int ic_expand_00002 = 2130837677;
|
||||
|
||||
// aapt resource value: 0x7f0200ae
|
||||
public const int ic_expand_00002 = 2130837678;
|
||||
public const int ic_expand_00003 = 2130837678;
|
||||
|
||||
// aapt resource value: 0x7f0200af
|
||||
public const int ic_expand_00003 = 2130837679;
|
||||
public const int ic_expand_00004 = 2130837679;
|
||||
|
||||
// aapt resource value: 0x7f0200b0
|
||||
public const int ic_expand_00004 = 2130837680;
|
||||
public const int ic_expand_00005 = 2130837680;
|
||||
|
||||
// aapt resource value: 0x7f0200b1
|
||||
public const int ic_expand_00005 = 2130837681;
|
||||
public const int ic_expand_00006 = 2130837681;
|
||||
|
||||
// aapt resource value: 0x7f0200b2
|
||||
public const int ic_expand_00006 = 2130837682;
|
||||
public const int ic_expand_00007 = 2130837682;
|
||||
|
||||
// aapt resource value: 0x7f0200b3
|
||||
public const int ic_expand_00007 = 2130837683;
|
||||
public const int ic_expand_00008 = 2130837683;
|
||||
|
||||
// aapt resource value: 0x7f0200b4
|
||||
public const int ic_expand_00008 = 2130837684;
|
||||
public const int ic_expand_00009 = 2130837684;
|
||||
|
||||
// aapt resource value: 0x7f0200b5
|
||||
public const int ic_expand_00009 = 2130837685;
|
||||
public const int ic_expand_00010 = 2130837685;
|
||||
|
||||
// aapt resource value: 0x7f0200b6
|
||||
public const int ic_expand_00010 = 2130837686;
|
||||
public const int ic_expand_00011 = 2130837686;
|
||||
|
||||
// aapt resource value: 0x7f0200b7
|
||||
public const int ic_expand_00011 = 2130837687;
|
||||
public const int ic_expand_00012 = 2130837687;
|
||||
|
||||
// aapt resource value: 0x7f0200b8
|
||||
public const int ic_expand_00012 = 2130837688;
|
||||
public const int ic_expand_00013 = 2130837688;
|
||||
|
||||
// aapt resource value: 0x7f0200b9
|
||||
public const int ic_expand_00013 = 2130837689;
|
||||
public const int ic_expand_00014 = 2130837689;
|
||||
|
||||
// aapt resource value: 0x7f0200ba
|
||||
public const int ic_expand_00014 = 2130837690;
|
||||
public const int ic_expand_00015 = 2130837690;
|
||||
|
||||
// aapt resource value: 0x7f0200bb
|
||||
public const int ic_expand_00015 = 2130837691;
|
||||
public const int ic_media_pause = 2130837691;
|
||||
|
||||
// aapt resource value: 0x7f0200bc
|
||||
public const int ic_media_pause = 2130837692;
|
||||
public const int ic_media_play = 2130837692;
|
||||
|
||||
// aapt resource value: 0x7f0200bd
|
||||
public const int ic_media_play = 2130837693;
|
||||
public const int ic_media_route_disabled_mono_dark = 2130837693;
|
||||
|
||||
// aapt resource value: 0x7f0200be
|
||||
public const int ic_media_route_disabled_mono_dark = 2130837694;
|
||||
public const int ic_media_route_off_mono_dark = 2130837694;
|
||||
|
||||
// aapt resource value: 0x7f0200bf
|
||||
public const int ic_media_route_off_mono_dark = 2130837695;
|
||||
public const int ic_media_route_on_0_mono_dark = 2130837695;
|
||||
|
||||
// aapt resource value: 0x7f0200c0
|
||||
public const int ic_media_route_on_0_mono_dark = 2130837696;
|
||||
public const int ic_media_route_on_1_mono_dark = 2130837696;
|
||||
|
||||
// aapt resource value: 0x7f0200c1
|
||||
public const int ic_media_route_on_1_mono_dark = 2130837697;
|
||||
public const int ic_media_route_on_2_mono_dark = 2130837697;
|
||||
|
||||
// aapt resource value: 0x7f0200c2
|
||||
public const int ic_media_route_on_2_mono_dark = 2130837698;
|
||||
public const int ic_media_route_on_mono_dark = 2130837698;
|
||||
|
||||
// aapt resource value: 0x7f0200c3
|
||||
public const int ic_media_route_on_mono_dark = 2130837699;
|
||||
public const int ic_pause_dark = 2130837699;
|
||||
|
||||
// aapt resource value: 0x7f0200c4
|
||||
public const int ic_pause_dark = 2130837700;
|
||||
public const int ic_pause_light = 2130837700;
|
||||
|
||||
// aapt resource value: 0x7f0200c5
|
||||
public const int ic_pause_light = 2130837701;
|
||||
public const int ic_play_dark = 2130837701;
|
||||
|
||||
// aapt resource value: 0x7f0200c6
|
||||
public const int ic_play_dark = 2130837702;
|
||||
public const int ic_play_light = 2130837702;
|
||||
|
||||
// aapt resource value: 0x7f0200c7
|
||||
public const int ic_play_light = 2130837703;
|
||||
public const int ic_speaker_dark = 2130837703;
|
||||
|
||||
// aapt resource value: 0x7f0200c8
|
||||
public const int ic_speaker_dark = 2130837704;
|
||||
public const int ic_speaker_group_dark = 2130837704;
|
||||
|
||||
// aapt resource value: 0x7f0200c9
|
||||
public const int ic_speaker_group_dark = 2130837705;
|
||||
public const int ic_speaker_group_light = 2130837705;
|
||||
|
||||
// aapt resource value: 0x7f0200ca
|
||||
public const int ic_speaker_group_light = 2130837706;
|
||||
public const int ic_speaker_light = 2130837706;
|
||||
|
||||
// aapt resource value: 0x7f0200cb
|
||||
public const int ic_speaker_light = 2130837707;
|
||||
public const int ic_successstatus = 2130837707;
|
||||
|
||||
// aapt resource value: 0x7f0200cc
|
||||
public const int ic_successstatus = 2130837708;
|
||||
public const int ic_tv_dark = 2130837708;
|
||||
|
||||
// aapt resource value: 0x7f0200cd
|
||||
public const int ic_tv_dark = 2130837709;
|
||||
public const int ic_tv_light = 2130837709;
|
||||
|
||||
// aapt resource value: 0x7f0200ce
|
||||
public const int ic_tv_light = 2130837710;
|
||||
public const int icon = 2130837710;
|
||||
|
||||
// aapt resource value: 0x7f0200cf
|
||||
public const int icon = 2130837711;
|
||||
public const int ion_chevron_right = 2130837711;
|
||||
|
||||
// aapt resource value: 0x7f0200d0
|
||||
public const int ion_chevron_right = 2130837712;
|
||||
public const int lightbulb = 2130837712;
|
||||
|
||||
// aapt resource value: 0x7f0200d1
|
||||
public const int lightbulb = 2130837713;
|
||||
public const int list_selector = 2130837713;
|
||||
|
||||
// aapt resource value: 0x7f0200d2
|
||||
public const int list_selector = 2130837714;
|
||||
public const int @lock = 2130837714;
|
||||
|
||||
// aapt resource value: 0x7f0200d3
|
||||
public const int @lock = 2130837715;
|
||||
public const int logo = 2130837715;
|
||||
|
||||
// aapt resource value: 0x7f0200d4
|
||||
public const int logo = 2130837716;
|
||||
public const int more = 2130837716;
|
||||
|
||||
// aapt resource value: 0x7f0200d5
|
||||
public const int more = 2130837717;
|
||||
public const int mr_dialog_material_background_dark = 2130837717;
|
||||
|
||||
// aapt resource value: 0x7f0200d6
|
||||
public const int mr_dialog_material_background_dark = 2130837718;
|
||||
public const int mr_dialog_material_background_light = 2130837718;
|
||||
|
||||
// aapt resource value: 0x7f0200d7
|
||||
public const int mr_dialog_material_background_light = 2130837719;
|
||||
public const int mr_ic_audiotrack_light = 2130837719;
|
||||
|
||||
// aapt resource value: 0x7f0200d8
|
||||
public const int mr_ic_audiotrack_light = 2130837720;
|
||||
public const int mr_ic_cast_dark = 2130837720;
|
||||
|
||||
// aapt resource value: 0x7f0200d9
|
||||
public const int mr_ic_cast_dark = 2130837721;
|
||||
public const int mr_ic_cast_light = 2130837721;
|
||||
|
||||
// aapt resource value: 0x7f0200da
|
||||
public const int mr_ic_cast_light = 2130837722;
|
||||
public const int mr_ic_close_dark = 2130837722;
|
||||
|
||||
// aapt resource value: 0x7f0200db
|
||||
public const int mr_ic_close_dark = 2130837723;
|
||||
public const int mr_ic_close_light = 2130837723;
|
||||
|
||||
// aapt resource value: 0x7f0200dc
|
||||
public const int mr_ic_close_light = 2130837724;
|
||||
public const int mr_ic_media_route_connecting_mono_dark = 2130837724;
|
||||
|
||||
// aapt resource value: 0x7f0200dd
|
||||
public const int mr_ic_media_route_connecting_mono_dark = 2130837725;
|
||||
public const int mr_ic_media_route_connecting_mono_light = 2130837725;
|
||||
|
||||
// aapt resource value: 0x7f0200de
|
||||
public const int mr_ic_media_route_connecting_mono_light = 2130837726;
|
||||
public const int mr_ic_media_route_mono_dark = 2130837726;
|
||||
|
||||
// aapt resource value: 0x7f0200df
|
||||
public const int mr_ic_media_route_mono_dark = 2130837727;
|
||||
public const int mr_ic_media_route_mono_light = 2130837727;
|
||||
|
||||
// aapt resource value: 0x7f0200e0
|
||||
public const int mr_ic_media_route_mono_light = 2130837728;
|
||||
public const int mr_ic_pause_dark = 2130837728;
|
||||
|
||||
// aapt resource value: 0x7f0200e1
|
||||
public const int mr_ic_pause_dark = 2130837729;
|
||||
public const int mr_ic_pause_light = 2130837729;
|
||||
|
||||
// aapt resource value: 0x7f0200e2
|
||||
public const int mr_ic_pause_light = 2130837730;
|
||||
public const int mr_ic_play_dark = 2130837730;
|
||||
|
||||
// aapt resource value: 0x7f0200e3
|
||||
public const int mr_ic_play_dark = 2130837731;
|
||||
public const int mr_ic_play_light = 2130837731;
|
||||
|
||||
// aapt resource value: 0x7f0200e4
|
||||
public const int mr_ic_play_light = 2130837732;
|
||||
|
||||
// aapt resource value: 0x7f0200e5
|
||||
public const int notification_sm = 2130837733;
|
||||
public const int notification_sm = 2130837732;
|
||||
|
||||
// aapt resource value: 0x7f0200f1
|
||||
public const int notification_template_icon_bg = 2130837745;
|
||||
|
||||
// aapt resource value: 0x7f0200e5
|
||||
public const int plus = 2130837733;
|
||||
|
||||
// aapt resource value: 0x7f0200e6
|
||||
public const int plus = 2130837734;
|
||||
public const int refresh = 2130837734;
|
||||
|
||||
// aapt resource value: 0x7f0200e7
|
||||
public const int refresh = 2130837735;
|
||||
public const int roundedbg = 2130837735;
|
||||
|
||||
// aapt resource value: 0x7f0200e8
|
||||
public const int roundedbg = 2130837736;
|
||||
public const int roundedbgdark = 2130837736;
|
||||
|
||||
// aapt resource value: 0x7f0200e9
|
||||
public const int roundedbgdark = 2130837737;
|
||||
public const int search = 2130837737;
|
||||
|
||||
// aapt resource value: 0x7f0200ea
|
||||
public const int splash_screen = 2130837738;
|
||||
|
||||
|
Before Width: | Height: | Size: 347 B |
BIN
src/Android/Resources/drawable-hdpi/search.png
Normal file
|
After Width: | Height: | Size: 519 B |
|
Before Width: | Height: | Size: 404 B |
BIN
src/Android/Resources/drawable-xhdpi/search.png
Normal file
|
After Width: | Height: | Size: 693 B |
|
Before Width: | Height: | Size: 374 B |
BIN
src/Android/Resources/drawable-xxhdpi/search.png
Normal file
|
After Width: | Height: | Size: 957 B |
|
Before Width: | Height: | Size: 564 B |
BIN
src/Android/Resources/drawable-xxxhdpi/search.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 312 B |
BIN
src/Android/Resources/drawable/search.png
Normal file
|
After Width: | Height: | Size: 449 B |
@@ -3,6 +3,6 @@
|
||||
android:description="@string/AutoFillServiceDescription"
|
||||
android:accessibilityEventTypes="typeWindowStateChanged|typeWindowContentChanged"
|
||||
android:accessibilityFeedbackType="feedbackGeneric"
|
||||
android:accessibilityFlags="flagDefault"
|
||||
android:accessibilityFlags="flagReportViewIds"
|
||||
android:notificationTimeout="100"
|
||||
android:canRetrieveWindowContent="true"/>
|
||||
@@ -1,4 +1,6 @@
|
||||
using Bit.App.Abstractions;
|
||||
using Android.App;
|
||||
using Bit.App.Abstractions;
|
||||
using System.Linq;
|
||||
using AndroidApp = Android.App.Application;
|
||||
|
||||
namespace Bit.Android.Services
|
||||
@@ -11,6 +13,14 @@ namespace Bit.Android.Services
|
||||
public string Build => AndroidApp.Context.ApplicationContext.PackageManager
|
||||
.GetPackageInfo(AndroidApp.Context.PackageName, 0).VersionCode.ToString();
|
||||
|
||||
public bool AutofillServiceEnabled => AutofillService.Enabled;
|
||||
public bool AutofillServiceEnabled => AutofillRunning();
|
||||
|
||||
private bool AutofillRunning()
|
||||
{
|
||||
var manager = ((ActivityManager)Xamarin.Forms.Forms.Context.GetSystemService("activity"));
|
||||
var services = manager.GetRunningServices(int.MaxValue);
|
||||
return services.Any(s => s.Process.ToLowerInvariant().Contains("bitwarden") &&
|
||||
s.Service.ClassName.ToLowerInvariant().Contains("autofill"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
44
src/Android/Services/MemoryService.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
using System;
|
||||
using Android.Content;
|
||||
using Bit.App.Abstractions;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.Android.Services
|
||||
{
|
||||
public class MemoryService : IMemoryService
|
||||
{
|
||||
public MemoryInfo GetInfo()
|
||||
{
|
||||
return MemoryHelper.GetMemoryInfo(Forms.Context);
|
||||
}
|
||||
|
||||
public void Check()
|
||||
{
|
||||
MemoryHelper.MemoryCheck(Forms.Context);
|
||||
}
|
||||
|
||||
public static class MemoryHelper
|
||||
{
|
||||
public static void MemoryCheck(Context context)
|
||||
{
|
||||
Console.WriteLine("MemoryHelper.MemoryCheck.{0} - {1}", "Start", context.ToString());
|
||||
var maxMemory = Java.Lang.Runtime.GetRuntime().MaxMemory();
|
||||
var freeMemory = Java.Lang.Runtime.GetRuntime().FreeMemory();
|
||||
var percentUsed = (maxMemory - freeMemory) / (double)maxMemory;
|
||||
Console.WriteLine("Free memory: {0:N}", freeMemory);
|
||||
Console.WriteLine("Max memory: {0:N}", maxMemory);
|
||||
Console.WriteLine("% used: {0:P}", percentUsed);
|
||||
Console.WriteLine("MemoryHelper.MemoryCheck.{0} {3:P} {1} out of {2}", "End", freeMemory, maxMemory, percentUsed);
|
||||
}
|
||||
|
||||
public static MemoryInfo GetMemoryInfo(Context context)
|
||||
{
|
||||
var retVal = new MemoryInfo();
|
||||
retVal.MaxMemory = Java.Lang.Runtime.GetRuntime().MaxMemory();
|
||||
retVal.FreeMemory = Java.Lang.Runtime.GetRuntime().FreeMemory();
|
||||
retVal.TotalMemory = Java.Lang.Runtime.GetRuntime().TotalMemory();
|
||||
return retVal;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -36,6 +36,8 @@
|
||||
<package id="Xamarin.Android.Support.v7.MediaRouter" version="23.3.0" targetFramework="monoandroid60" />
|
||||
<package id="Xamarin.Android.Support.v7.RecyclerView" version="23.3.0" targetFramework="monoandroid60" />
|
||||
<package id="Xamarin.Android.Support.Vector.Drawable" version="23.3.0" targetFramework="monoandroid60" />
|
||||
<package id="Xamarin.FFImageLoading" version="2.2.8" targetFramework="monoandroid60" />
|
||||
<package id="Xamarin.FFImageLoading.Forms" version="2.2.8" targetFramework="monoandroid60" />
|
||||
<package id="Xamarin.Forms" version="2.3.3.180" targetFramework="monoandroid60" />
|
||||
<package id="Xamarin.GooglePlayServices.Analytics" version="29.0.0.2" targetFramework="monoandroid60" />
|
||||
<package id="Xamarin.GooglePlayServices.Base" version="29.0.0.2" targetFramework="monoandroid60" />
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using System.Threading.Tasks;
|
||||
using Bit.App.Models;
|
||||
using Bit.App.Models.Api;
|
||||
using System;
|
||||
|
||||
namespace Bit.App.Abstractions
|
||||
{
|
||||
@@ -10,7 +11,7 @@ namespace Bit.App.Abstractions
|
||||
Task<Login> GetByIdAsync(string id);
|
||||
Task<IEnumerable<Login>> GetAllAsync();
|
||||
Task<IEnumerable<Login>> GetAllAsync(bool favorites);
|
||||
Task<IEnumerable<Login>> GetAllAsync(string uriString);
|
||||
Task<Tuple<IEnumerable<Login>, IEnumerable<Login>>> GetAllAsync(string uriString);
|
||||
Task<ApiResult<LoginResponse>> SaveAsync(Login login);
|
||||
Task<ApiResult> DeleteAsync(string id);
|
||||
}
|
||||
|
||||
26
src/App/Abstractions/Services/IMemoryService.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
namespace Bit.App.Abstractions
|
||||
{
|
||||
public interface IMemoryService
|
||||
{
|
||||
MemoryInfo GetInfo();
|
||||
void Check();
|
||||
}
|
||||
|
||||
public class MemoryInfo
|
||||
{
|
||||
public long FreeMemory { get; set; }
|
||||
public long MaxMemory { get; set; }
|
||||
public long TotalMemory { get; set; }
|
||||
public long UsedMemory => TotalMemory - FreeMemory;
|
||||
|
||||
public double HeapUsage()
|
||||
{
|
||||
return UsedMemory / (double)TotalMemory;
|
||||
}
|
||||
|
||||
public double Usage()
|
||||
{
|
||||
return UsedMemory / (double)MaxMemory;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,6 @@ using Acr.UserDialogs;
|
||||
using XLabs.Ioc;
|
||||
using System.Reflection;
|
||||
using Bit.App.Resources;
|
||||
using System.Threading;
|
||||
|
||||
namespace Bit.App
|
||||
{
|
||||
@@ -80,7 +79,7 @@ namespace Bit.App
|
||||
|
||||
MessagingCenter.Subscribe<Application, bool>(Current, "Resumed", async (sender, args) =>
|
||||
{
|
||||
await CheckLockAsync(args);
|
||||
Device.BeginInvokeOnMainThread(async () => await CheckLockAsync(args));
|
||||
await Task.Run(() => FullSyncAsync()).ConfigureAwait(false);
|
||||
});
|
||||
|
||||
@@ -91,12 +90,7 @@ namespace Bit.App
|
||||
|
||||
MessagingCenter.Subscribe<Application, string>(Current, "Logout", (sender, args) =>
|
||||
{
|
||||
Device.BeginInvokeOnMainThread(() => Logout(args));
|
||||
});
|
||||
|
||||
MessagingCenter.Subscribe<Application>(Current, "SetMainPage", (sender) =>
|
||||
{
|
||||
SetMainPageFromAutofill();
|
||||
Logout(args);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -126,6 +120,7 @@ namespace Bit.App
|
||||
Debug.WriteLine("OnSleep");
|
||||
|
||||
SetMainPageFromAutofill();
|
||||
|
||||
if(Device.OS == TargetPlatform.Android && !TopPageIsLock())
|
||||
{
|
||||
_settings.AddOrUpdateValue(Constants.LastActivityDate, DateTime.UtcNow);
|
||||
@@ -160,6 +155,21 @@ namespace Bit.App
|
||||
}
|
||||
}
|
||||
|
||||
private void SetMainPageFromAutofill()
|
||||
{
|
||||
if(Device.OS == TargetPlatform.Android && !string.IsNullOrWhiteSpace(_uri))
|
||||
{
|
||||
Task.Run(() =>
|
||||
{
|
||||
Device.BeginInvokeOnMainThread(() =>
|
||||
{
|
||||
Current.MainPage = new MainPage();
|
||||
_uri = null;
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private bool InDebugMode()
|
||||
{
|
||||
#if DEBUG
|
||||
@@ -169,17 +179,6 @@ namespace Bit.App
|
||||
#endif
|
||||
}
|
||||
|
||||
private void SetMainPageFromAutofill()
|
||||
{
|
||||
if(Device.OS != TargetPlatform.Android || string.IsNullOrWhiteSpace(_uri))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
MainPage = new MainPage();
|
||||
_uri = null;
|
||||
}
|
||||
|
||||
private async Task FullSyncAsync()
|
||||
{
|
||||
if(_connectivity.IsConnected)
|
||||
@@ -222,19 +221,18 @@ namespace Bit.App
|
||||
{
|
||||
_authService.LogOut();
|
||||
|
||||
var deviceApiRepository = Resolver.Resolve<IDeviceApiRepository>();
|
||||
var appIdService = Resolver.Resolve<IAppIdService>();
|
||||
await Task.Run(() => deviceApiRepository.PutClearTokenAsync(appIdService.AppId)).ConfigureAwait(false);
|
||||
|
||||
_googleAnalyticsService.TrackAppEvent("LoggedOut");
|
||||
_googleAnalyticsService.RefreshUserId();
|
||||
|
||||
Current.MainPage = new ExtendedNavigationPage(new HomePage());
|
||||
Device.BeginInvokeOnMainThread(() => Current.MainPage = new ExtendedNavigationPage(new HomePage()));
|
||||
if(!string.IsNullOrWhiteSpace(logoutMessage))
|
||||
{
|
||||
_userDialogs.Toast(logoutMessage);
|
||||
}
|
||||
|
||||
var deviceApiRepository = Resolver.Resolve<IDeviceApiRepository>();
|
||||
var appIdService = Resolver.Resolve<IAppIdService>();
|
||||
_settings.Remove(Constants.PushLastRegistrationDate);
|
||||
await Task.Run(() => deviceApiRepository.PutClearTokenAsync(appIdService.AppId)).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async Task CheckLockAsync(bool forceLock)
|
||||
@@ -246,7 +244,12 @@ namespace Bit.App
|
||||
}
|
||||
|
||||
var lockType = _lockService.GetLockType(forceLock);
|
||||
var currentPage = Current.MainPage.Navigation.ModalStack.LastOrDefault() as ExtendedNavigationPage;
|
||||
if(lockType == Enums.LockType.None)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_settings.AddOrUpdateValue(Constants.Locked, true);
|
||||
switch(lockType)
|
||||
{
|
||||
case Enums.LockType.Fingerprint:
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
<Compile Include="Abstractions\Repositories\IAccountsApiRepository.cs" />
|
||||
<Compile Include="Abstractions\Repositories\IDeviceApiRepository.cs" />
|
||||
<Compile Include="Abstractions\Repositories\ISettingsRepository.cs" />
|
||||
<Compile Include="Abstractions\Services\IMemoryService.cs" />
|
||||
<Compile Include="Abstractions\Services\ISettingsService.cs" />
|
||||
<Compile Include="Abstractions\Services\ITokenService.cs" />
|
||||
<Compile Include="Abstractions\Services\IHttpService.cs" />
|
||||
@@ -57,11 +58,13 @@
|
||||
<Compile Include="Abstractions\Services\ISecureStorageService.cs" />
|
||||
<Compile Include="Abstractions\Services\ISqlService.cs" />
|
||||
<Compile Include="Constants.cs" />
|
||||
<Compile Include="Controls\ExtendedToolbarItem.cs" />
|
||||
<Compile Include="Controls\DismissModalToolBarItem.cs" />
|
||||
<Compile Include="Controls\ExtendedEditor.cs" />
|
||||
<Compile Include="Controls\ExtendedButton.cs" />
|
||||
<Compile Include="Controls\ExtendedNavigationPage.cs" />
|
||||
<Compile Include="Controls\ExtendedContentPage.cs" />
|
||||
<Compile Include="Controls\MemoryContentView.cs" />
|
||||
<Compile Include="Controls\StepperCell.cs" />
|
||||
<Compile Include="Controls\ExtendedTableView.cs" />
|
||||
<Compile Include="Controls\ExtendedPicker.cs" />
|
||||
@@ -258,6 +261,18 @@
|
||||
<HintPath>..\..\packages\Acr.UserDialogs.6.3.3\lib\portable-win+net45+wp8+win8+wpa81\Acr.UserDialogs.Interface.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="FFImageLoading, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Xamarin.FFImageLoading.2.2.8\lib\portable-net45+win8+wpa81+wp8\FFImageLoading.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="FFImageLoading.Forms, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Xamarin.FFImageLoading.Forms.2.2.8\lib\portable-net45+win8+wpa81+wp8\FFImageLoading.Forms.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="FFImageLoading.Platform, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Xamarin.FFImageLoading.2.2.8\lib\portable-net45+win8+wpa81+wp8\FFImageLoading.Platform.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="HockeySDK, Version=1.0.6103.22141, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\HockeySDK.Xamarin.4.1.0\lib\portable-net45+wp8+wpa81+win8+MonoAndroid10+MonoTouch10+Xamarin.iOS10\HockeySDK.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
|
||||
@@ -4,23 +4,23 @@ using Xamarin.Forms;
|
||||
|
||||
namespace Bit.App.Controls
|
||||
{
|
||||
public class DismissModalToolBarItem : ToolbarItem
|
||||
public class DismissModalToolBarItem : ExtendedToolbarItem, IDisposable
|
||||
{
|
||||
private readonly ContentPage _page;
|
||||
private readonly Action _cancelClickedAction;
|
||||
|
||||
public DismissModalToolBarItem(ContentPage page, string text = null, Action cancelClickedAction = null)
|
||||
: base(cancelClickedAction)
|
||||
{
|
||||
_cancelClickedAction = cancelClickedAction;
|
||||
_page = page;
|
||||
// TODO: init and dispose events from pages
|
||||
InitEvents();
|
||||
Text = text ?? AppResources.Close;
|
||||
Clicked += ClickedItem;
|
||||
Priority = -1;
|
||||
}
|
||||
|
||||
private async void ClickedItem(object sender, EventArgs e)
|
||||
protected async override void ClickedItem(object sender, EventArgs e)
|
||||
{
|
||||
_cancelClickedAction?.Invoke();
|
||||
base.ClickedItem(sender, e);
|
||||
await _page.Navigation.PopModalAsync();
|
||||
}
|
||||
}
|
||||
|
||||
30
src/App/Controls/ExtendedToolbarItem.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using System;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.App.Controls
|
||||
{
|
||||
public class ExtendedToolbarItem : ToolbarItem, IDisposable
|
||||
{
|
||||
public ExtendedToolbarItem(Action clickAction = null)
|
||||
{
|
||||
ClickAction = clickAction;
|
||||
}
|
||||
|
||||
public Action ClickAction { get; set; }
|
||||
|
||||
protected virtual void ClickedItem(object sender, EventArgs e)
|
||||
{
|
||||
ClickAction?.Invoke();
|
||||
}
|
||||
|
||||
public void InitEvents()
|
||||
{
|
||||
Clicked += ClickedItem;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Clicked -= ClickedItem;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ using Xamarin.Forms;
|
||||
|
||||
namespace Bit.App.Controls
|
||||
{
|
||||
public class FormEditorCell : ExtendedViewCell
|
||||
public class FormEditorCell : ExtendedViewCell, IDisposable
|
||||
{
|
||||
public FormEditorCell(Keyboard entryKeyboard = null, double? height = null)
|
||||
{
|
||||
@@ -26,7 +26,6 @@ namespace Bit.App.Controls
|
||||
|
||||
stackLayout.Children.Add(Editor);
|
||||
|
||||
Tapped += FormEditorCell_Tapped;
|
||||
Editor.AdjustMarginsForDevice();
|
||||
stackLayout.AdjustPaddingForDevice();
|
||||
|
||||
@@ -39,5 +38,15 @@ namespace Bit.App.Controls
|
||||
{
|
||||
Editor.Focus();
|
||||
}
|
||||
|
||||
public void InitEvents()
|
||||
{
|
||||
Tapped += FormEditorCell_Tapped;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Tapped -= FormEditorCell_Tapped;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
using Bit.App.Abstractions;
|
||||
using FFImageLoading.Forms;
|
||||
using System;
|
||||
using Xamarin.Forms;
|
||||
using XLabs.Ioc;
|
||||
|
||||
namespace Bit.App.Controls
|
||||
{
|
||||
public class FormEntryCell : ExtendedViewCell
|
||||
public class FormEntryCell : ExtendedViewCell, IDisposable
|
||||
{
|
||||
private VisualElement _nextElement;
|
||||
private TapGestureRecognizer _tgr;
|
||||
|
||||
public FormEntryCell(
|
||||
string labelText,
|
||||
Keyboard entryKeyboard = null,
|
||||
@@ -17,6 +21,8 @@ namespace Bit.App.Controls
|
||||
Thickness? containerPadding = null,
|
||||
bool useButton = false)
|
||||
{
|
||||
_nextElement = nextElement;
|
||||
|
||||
if(!useLabelAsPlaceholder)
|
||||
{
|
||||
Label = new Label
|
||||
@@ -47,7 +53,6 @@ namespace Bit.App.Controls
|
||||
if(nextElement != null)
|
||||
{
|
||||
Entry.ReturnType = Enums.ReturnType.Next;
|
||||
Entry.Completed += (object sender, EventArgs e) => { nextElement.Focus(); };
|
||||
}
|
||||
|
||||
var imageStackLayout = new StackLayout
|
||||
@@ -61,16 +66,17 @@ namespace Bit.App.Controls
|
||||
|
||||
if(imageSource != null)
|
||||
{
|
||||
var tgr = new TapGestureRecognizer();
|
||||
tgr.Tapped += Tgr_Tapped;
|
||||
_tgr = new TapGestureRecognizer();
|
||||
|
||||
var theImage = new Image
|
||||
var theImage = new CachedImage
|
||||
{
|
||||
Source = imageSource,
|
||||
HorizontalOptions = LayoutOptions.Start,
|
||||
VerticalOptions = LayoutOptions.Center
|
||||
VerticalOptions = LayoutOptions.Center,
|
||||
WidthRequest = 18,
|
||||
HeightRequest = 18
|
||||
};
|
||||
theImage.GestureRecognizers.Add(tgr);
|
||||
theImage.GestureRecognizers.Add(_tgr);
|
||||
|
||||
imageStackLayout.Children.Add(theImage);
|
||||
}
|
||||
@@ -126,8 +132,6 @@ namespace Bit.App.Controls
|
||||
}
|
||||
}
|
||||
|
||||
Tapped += FormEntryCell_Tapped;
|
||||
|
||||
View = imageStackLayout;
|
||||
}
|
||||
|
||||
@@ -135,6 +139,21 @@ namespace Bit.App.Controls
|
||||
public ExtendedEntry Entry { get; private set; }
|
||||
public ExtendedButton Button { get; private set; }
|
||||
|
||||
public void InitEvents()
|
||||
{
|
||||
if(_nextElement != null)
|
||||
{
|
||||
Entry.Completed += Entry_Completed;
|
||||
}
|
||||
|
||||
if(_tgr != null)
|
||||
{
|
||||
_tgr.Tapped += Tgr_Tapped;
|
||||
}
|
||||
|
||||
Tapped += FormEntryCell_Tapped;
|
||||
}
|
||||
|
||||
private void Tgr_Tapped(object sender, EventArgs e)
|
||||
{
|
||||
Entry.Focus();
|
||||
@@ -144,5 +163,21 @@ namespace Bit.App.Controls
|
||||
{
|
||||
Entry.Focus();
|
||||
}
|
||||
|
||||
private void Entry_Completed(object sender, EventArgs e)
|
||||
{
|
||||
_nextElement?.Focus();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if(_tgr != null)
|
||||
{
|
||||
_tgr.Tapped -= Tgr_Tapped;
|
||||
}
|
||||
|
||||
Tapped -= FormEntryCell_Tapped;
|
||||
Entry.Completed -= Entry_Completed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using Bit.App.Resources;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.App.Controls
|
||||
@@ -42,8 +41,6 @@ namespace Bit.App.Controls
|
||||
Picker.AdjustMarginsForDevice();
|
||||
stackLayout.AdjustPaddingForDevice();
|
||||
|
||||
Tapped += FormPickerCell_Tapped;
|
||||
|
||||
View = stackLayout;
|
||||
}
|
||||
|
||||
@@ -54,5 +51,15 @@ namespace Bit.App.Controls
|
||||
{
|
||||
Picker.Focus();
|
||||
}
|
||||
|
||||
public void InitEvents()
|
||||
{
|
||||
Tapped += FormPickerCell_Tapped;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Tapped -= FormPickerCell_Tapped;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
108
src/App/Controls/MemoryContentView.cs
Normal file
@@ -0,0 +1,108 @@
|
||||
using Bit.App.Abstractions;
|
||||
using System;
|
||||
using Xamarin.Forms;
|
||||
using XLabs.Ioc;
|
||||
|
||||
namespace Bit.App.Controls
|
||||
{
|
||||
public class MemoryContentView : ContentView
|
||||
{
|
||||
private readonly IMemoryService _memoryService;
|
||||
|
||||
public MemoryContentView()
|
||||
{
|
||||
_memoryService = Resolver.Resolve<IMemoryService>();
|
||||
|
||||
var grid = new Grid
|
||||
{
|
||||
Padding = 5,
|
||||
BackgroundColor = Color.White,
|
||||
RowDefinitions = new RowDefinitionCollection
|
||||
{
|
||||
new RowDefinition { Height = GridLength.Auto },
|
||||
new RowDefinition { Height = GridLength.Auto },
|
||||
new RowDefinition { Height = GridLength.Auto },
|
||||
new RowDefinition { Height = GridLength.Auto },
|
||||
new RowDefinition { Height = GridLength.Auto },
|
||||
new RowDefinition { Height = GridLength.Auto },
|
||||
new RowDefinition { Height = GridLength.Auto }
|
||||
},
|
||||
ColumnDefinitions = new ColumnDefinitionCollection
|
||||
{
|
||||
new ColumnDefinition { Width = GridLength.Star },
|
||||
new ColumnDefinition { Width = GridLength.Star }
|
||||
}
|
||||
};
|
||||
|
||||
grid.Children.Add(new Label { Text = "Used Memory:" }, 0, 0);
|
||||
grid.Children.Add(new Label { Text = "Free Memory:" }, 0, 1);
|
||||
grid.Children.Add(new Label { Text = "Heap Memory:" }, 0, 2);
|
||||
grid.Children.Add(new Label { Text = "Max Memory:" }, 0, 3);
|
||||
grid.Children.Add(new Label { Text = "% Used Heap:" }, 0, 4);
|
||||
grid.Children.Add(new Label { Text = "% Used Max:" }, 0, 5);
|
||||
|
||||
UsedMemory = new Label { Text = "Used Memory:", HorizontalTextAlignment = TextAlignment.End };
|
||||
FreeMemory = new Label { Text = "Free Memory:", HorizontalTextAlignment = TextAlignment.End };
|
||||
HeapMemory = new Label { Text = "Heap Memory:", HorizontalTextAlignment = TextAlignment.End };
|
||||
MaxMemory = new Label { Text = "Max Memory:", HorizontalTextAlignment = TextAlignment.End };
|
||||
HeapUsage = new Label { Text = "% Used Heap:", HorizontalTextAlignment = TextAlignment.End };
|
||||
TotalUsage = new Label { Text = "% Used Max:", HorizontalTextAlignment = TextAlignment.End };
|
||||
|
||||
grid.Children.Add(UsedMemory, 1, 0);
|
||||
grid.Children.Add(FreeMemory, 1, 1);
|
||||
grid.Children.Add(HeapMemory, 1, 2);
|
||||
grid.Children.Add(MaxMemory, 1, 3);
|
||||
grid.Children.Add(HeapUsage, 1, 4);
|
||||
grid.Children.Add(TotalUsage, 1, 5);
|
||||
|
||||
var button = new ExtendedButton { Text = "Refresh", BackgroundColor = Color.Transparent };
|
||||
button.Clicked += Button_Clicked;
|
||||
grid.Children.Add(button, 0, 6);
|
||||
Grid.SetColumnSpan(button, 2);
|
||||
|
||||
Content = grid;
|
||||
|
||||
RefreshScreen();
|
||||
}
|
||||
|
||||
private void Button_Clicked(object sender, EventArgs e)
|
||||
{
|
||||
RefreshScreen();
|
||||
}
|
||||
|
||||
public Label UsedMemory { get; set; }
|
||||
public Label FreeMemory { get; set; }
|
||||
public Label HeapMemory { get; set; }
|
||||
public Label MaxMemory { get; set; }
|
||||
public Label HeapUsage { get; set; }
|
||||
public Label TotalUsage { get; set; }
|
||||
|
||||
void RefreshScreen()
|
||||
{
|
||||
UsedMemory.Text = FreeMemory.Text = HeapMemory.Text = MaxMemory.Text =
|
||||
HeapUsage.Text = TotalUsage.Text = string.Empty;
|
||||
|
||||
UsedMemory.TextColor = FreeMemory.TextColor = HeapMemory.TextColor =
|
||||
MaxMemory.TextColor = HeapUsage.TextColor = TotalUsage.TextColor = Color.Black;
|
||||
|
||||
if(_memoryService != null)
|
||||
{
|
||||
var info = _memoryService.GetInfo();
|
||||
if(info != null)
|
||||
{
|
||||
UsedMemory.Text = string.Format("{0:N} mb", Math.Round(info.UsedMemory / 1024 / 1024D, 2));
|
||||
FreeMemory.Text = string.Format("{0:N} mb", Math.Round(info.FreeMemory / 1024 / 1024D, 2));
|
||||
HeapMemory.Text = string.Format("{0:N} mb", Math.Round(info.TotalMemory / 1024 / 1024D, 2));
|
||||
MaxMemory.Text = string.Format("{0:N} mb", Math.Round(info.MaxMemory / 1024 / 1024D, 2));
|
||||
HeapUsage.Text = string.Format("{0:P}", info.HeapUsage());
|
||||
TotalUsage.Text = string.Format("{0:P}", info.Usage());
|
||||
|
||||
if(info.Usage() > 0.8)
|
||||
{
|
||||
FreeMemory.TextColor = UsedMemory.TextColor = TotalUsage.TextColor = Color.Red;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,6 @@ namespace Bit.App.Controls
|
||||
MaxLength = 4,
|
||||
Margin = new Thickness(0, int.MaxValue, 0, 0)
|
||||
};
|
||||
Entry.TextChanged += Entry_TextChanged;
|
||||
|
||||
if(Device.OS == TargetPlatform.Android)
|
||||
{
|
||||
@@ -30,6 +29,9 @@ namespace Bit.App.Controls
|
||||
}
|
||||
}
|
||||
|
||||
public Label Label { get; set; }
|
||||
public ExtendedEntry Entry { get; set; }
|
||||
|
||||
private void Entry_TextChanged(object sender, TextChangedEventArgs e)
|
||||
{
|
||||
if(e.NewTextValue.Length >= 4)
|
||||
@@ -38,7 +40,14 @@ namespace Bit.App.Controls
|
||||
}
|
||||
}
|
||||
|
||||
public Label Label { get; set; }
|
||||
public ExtendedEntry Entry { get; set; }
|
||||
public void InitEvents()
|
||||
{
|
||||
Entry.TextChanged += Entry_TextChanged;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Entry.TextChanged -= Entry_TextChanged;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,8 +34,6 @@ namespace Bit.App.Controls
|
||||
Value = value
|
||||
};
|
||||
|
||||
Stepper.ValueChanged += Stepper_ValueChanged;
|
||||
|
||||
var stackLayout = new StackLayout
|
||||
{
|
||||
Orientation = StackOrientation.Horizontal,
|
||||
@@ -56,13 +54,23 @@ namespace Bit.App.Controls
|
||||
View = stackLayout;
|
||||
}
|
||||
|
||||
public Label Label { get; private set; }
|
||||
public Label StepperValueLabel { get; private set; }
|
||||
public Stepper Stepper { get; private set; }
|
||||
|
||||
private void Stepper_ValueChanged(object sender, ValueChangedEventArgs e)
|
||||
{
|
||||
StepperValueLabel.Text = e.NewValue.ToString();
|
||||
}
|
||||
|
||||
public Label Label { get; private set; }
|
||||
public Label StepperValueLabel { get; private set; }
|
||||
public Stepper Stepper { get; private set; }
|
||||
public void InitEvents()
|
||||
{
|
||||
Stepper.ValueChanged += Stepper_ValueChanged;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Stepper.ValueChanged -= Stepper_ValueChanged;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,21 +6,17 @@ namespace Bit.App.Controls
|
||||
{
|
||||
public class VaultListViewCell : LabeledDetailCell
|
||||
{
|
||||
private Action<VaultListPageModel.Login> _moreClickedAction;
|
||||
|
||||
public static readonly BindableProperty LoginParameterProperty = BindableProperty.Create(nameof(LoginParameter),
|
||||
typeof(VaultListPageModel.Login), typeof(VaultListViewCell), null);
|
||||
|
||||
public VaultListViewCell(Action<VaultListPageModel.Login> moreClickedAction)
|
||||
{
|
||||
_moreClickedAction = moreClickedAction;
|
||||
|
||||
SetBinding(LoginParameterProperty, new Binding("."));
|
||||
Label.SetBinding<VaultListPageModel.Login>(Label.TextProperty, s => s.Name);
|
||||
Detail.SetBinding<VaultListPageModel.Login>(Label.TextProperty, s => s.Username);
|
||||
|
||||
Button.Image = "more";
|
||||
Button.Command = new Command(() => ShowMore());
|
||||
Button.Command = new Command(() => moreClickedAction?.Invoke(LoginParameter));
|
||||
Button.BackgroundColor = Color.Transparent;
|
||||
|
||||
BackgroundColor = Color.White;
|
||||
@@ -31,10 +27,5 @@ namespace Bit.App.Controls
|
||||
get { return GetValue(LoginParameterProperty) as VaultListPageModel.Login; }
|
||||
set { SetValue(LoginParameterProperty, value); }
|
||||
}
|
||||
|
||||
private void ShowMore()
|
||||
{
|
||||
_moreClickedAction?.Invoke(LoginParameter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,8 +8,6 @@ namespace Bit.App.Models.Page
|
||||
{
|
||||
public class Login
|
||||
{
|
||||
private string _baseDomain;
|
||||
|
||||
public Login(Models.Login login)
|
||||
{
|
||||
Id = login.Id;
|
||||
@@ -28,6 +26,17 @@ namespace Bit.App.Models.Page
|
||||
public Lazy<string> Uri { get; set; }
|
||||
}
|
||||
|
||||
public class AutofillLogin : Login
|
||||
{
|
||||
public AutofillLogin(Models.Login login, bool fuzzy = false)
|
||||
: base(login)
|
||||
{
|
||||
Fuzzy = fuzzy;
|
||||
}
|
||||
|
||||
public bool Fuzzy { get; set; }
|
||||
}
|
||||
|
||||
public class Folder : List<Login>
|
||||
{
|
||||
public Folder(Models.Folder folder)
|
||||
@@ -44,5 +53,16 @@ namespace Bit.App.Models.Page
|
||||
public string Id { get; set; }
|
||||
public string Name { get; set; } = AppResources.FolderNone;
|
||||
}
|
||||
|
||||
public class AutofillGrouping : List<AutofillLogin>
|
||||
{
|
||||
public AutofillGrouping(List<AutofillLogin> logins, string name)
|
||||
{
|
||||
AddRange(logins);
|
||||
Name = name;
|
||||
}
|
||||
|
||||
public string Name { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,26 +126,19 @@ namespace Bit.App.Models.Page
|
||||
return _uriHost;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var host = new Uri(Uri).Host;
|
||||
|
||||
DomainName domain;
|
||||
if(DomainName.TryParse(host, out domain))
|
||||
{
|
||||
_uriHost = domain.BaseDomain;
|
||||
}
|
||||
else
|
||||
{
|
||||
_uriHost = host;
|
||||
}
|
||||
|
||||
return _uriHost;
|
||||
}
|
||||
catch
|
||||
Uri uri;
|
||||
if(!System.Uri.TryCreate(Uri, UriKind.Absolute, out uri))
|
||||
{
|
||||
return Uri;
|
||||
}
|
||||
|
||||
DomainName domain;
|
||||
if(DomainName.TryParse(uri.Host, out domain))
|
||||
{
|
||||
return domain.BaseDomain;
|
||||
}
|
||||
|
||||
return uri.Host;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ using Xamarin.Forms;
|
||||
using XLabs.Ioc;
|
||||
using Plugin.Settings.Abstractions;
|
||||
using Bit.App.Controls;
|
||||
using FFImageLoading.Forms;
|
||||
|
||||
namespace Bit.App.Pages
|
||||
{
|
||||
@@ -30,11 +31,13 @@ namespace Bit.App.Pages
|
||||
{
|
||||
MessagingCenter.Send(Application.Current, "ShowStatusBar", false);
|
||||
|
||||
var logo = new Image
|
||||
var logo = new CachedImage
|
||||
{
|
||||
Source = "logo",
|
||||
VerticalOptions = LayoutOptions.CenterAndExpand,
|
||||
HorizontalOptions = LayoutOptions.Center
|
||||
HorizontalOptions = LayoutOptions.Center,
|
||||
WidthRequest = 282,
|
||||
HeightRequest = 44
|
||||
};
|
||||
|
||||
var message = new Label
|
||||
|
||||
@@ -38,7 +38,6 @@ namespace Bit.App.Pages
|
||||
useLabelAsPlaceholder: true, imageSource: "lock", containerPadding: padding);
|
||||
|
||||
PasswordCell.Entry.ReturnType = Enums.ReturnType.Go;
|
||||
PasswordCell.Entry.Completed += Entry_Completed;
|
||||
|
||||
var table = new ExtendedTableView
|
||||
{
|
||||
@@ -99,7 +98,16 @@ namespace Bit.App.Pages
|
||||
protected override void OnAppearing()
|
||||
{
|
||||
base.OnAppearing();
|
||||
PasswordCell.InitEvents();
|
||||
PasswordCell.Entry.FocusWithDelay();
|
||||
PasswordCell.Entry.Completed += Entry_Completed;
|
||||
}
|
||||
|
||||
protected override void OnDisappearing()
|
||||
{
|
||||
base.OnDisappearing();
|
||||
PasswordCell.Dispose();
|
||||
PasswordCell.Entry.Completed -= Entry_Completed;
|
||||
}
|
||||
|
||||
protected async Task CheckPasswordAsync()
|
||||
|
||||
@@ -15,6 +15,7 @@ namespace Bit.App.Pages
|
||||
{
|
||||
private readonly IAuthService _authService;
|
||||
private readonly ISettings _settings;
|
||||
private TapGestureRecognizer _tgr;
|
||||
|
||||
public LockPinPage()
|
||||
{
|
||||
@@ -39,7 +40,6 @@ namespace Bit.App.Pages
|
||||
};
|
||||
|
||||
PinControl = new PinControl();
|
||||
PinControl.OnPinEntered += PinEntered;
|
||||
PinControl.Label.SetBinding<PinPageModel>(Label.TextProperty, s => s.LabelText);
|
||||
PinControl.Entry.SetBinding<PinPageModel>(Entry.TextProperty, s => s.PIN);
|
||||
|
||||
@@ -60,14 +60,13 @@ namespace Bit.App.Pages
|
||||
Children = { PinControl.Label, instructionLabel, logoutButton, PinControl.Entry }
|
||||
};
|
||||
|
||||
var tgr = new TapGestureRecognizer();
|
||||
tgr.Tapped += Tgr_Tapped;
|
||||
PinControl.Label.GestureRecognizers.Add(tgr);
|
||||
instructionLabel.GestureRecognizers.Add(tgr);
|
||||
_tgr = new TapGestureRecognizer();
|
||||
PinControl.Label.GestureRecognizers.Add(_tgr);
|
||||
instructionLabel.GestureRecognizers.Add(_tgr);
|
||||
|
||||
Title = AppResources.VerifyPIN;
|
||||
Content = stackLayout;
|
||||
Content.GestureRecognizers.Add(tgr);
|
||||
Content.GestureRecognizers.Add(_tgr);
|
||||
BindingContext = Model;
|
||||
}
|
||||
|
||||
@@ -79,9 +78,20 @@ namespace Bit.App.Pages
|
||||
protected override void OnAppearing()
|
||||
{
|
||||
base.OnAppearing();
|
||||
_tgr.Tapped += Tgr_Tapped;
|
||||
PinControl.OnPinEntered += PinEntered;
|
||||
PinControl.InitEvents();
|
||||
PinControl.Entry.FocusWithDelay();
|
||||
}
|
||||
|
||||
protected override void OnDisappearing()
|
||||
{
|
||||
base.OnDisappearing();
|
||||
_tgr.Tapped -= Tgr_Tapped;
|
||||
PinControl.OnPinEntered -= PinEntered;
|
||||
PinControl.Dispose();
|
||||
}
|
||||
|
||||
protected void PinEntered(object sender, EventArgs args)
|
||||
{
|
||||
if(Model.PIN == _authService.PIN)
|
||||
|
||||
@@ -74,7 +74,6 @@ namespace Bit.App.Pages
|
||||
}
|
||||
|
||||
PasswordCell.Entry.ReturnType = Enums.ReturnType.Go;
|
||||
PasswordCell.Entry.Completed += Entry_Completed;
|
||||
|
||||
var table = new ExtendedTableView
|
||||
{
|
||||
@@ -136,6 +135,10 @@ namespace Bit.App.Pages
|
||||
protected override void OnAppearing()
|
||||
{
|
||||
base.OnAppearing();
|
||||
PasswordCell.InitEvents();
|
||||
EmailCell.InitEvents();
|
||||
|
||||
PasswordCell.Entry.Completed += Entry_Completed;
|
||||
MessagingCenter.Send(Application.Current, "ShowStatusBar", true);
|
||||
|
||||
if(string.IsNullOrWhiteSpace(_email))
|
||||
@@ -151,6 +154,14 @@ namespace Bit.App.Pages
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnDisappearing()
|
||||
{
|
||||
base.OnDisappearing();
|
||||
PasswordCell.Dispose();
|
||||
EmailCell.Dispose();
|
||||
PasswordCell.Entry.Completed -= Entry_Completed;
|
||||
}
|
||||
|
||||
private async void Entry_Completed(object sender, EventArgs e)
|
||||
{
|
||||
await LogIn();
|
||||
|
||||
@@ -64,7 +64,6 @@ namespace Bit.App.Pages
|
||||
|
||||
CodeCell.Entry.Keyboard = Keyboard.Numeric;
|
||||
CodeCell.Entry.ReturnType = Enums.ReturnType.Go;
|
||||
CodeCell.Entry.Completed += Entry_Completed;
|
||||
|
||||
var table = new ExtendedTableView
|
||||
{
|
||||
@@ -129,7 +128,16 @@ namespace Bit.App.Pages
|
||||
protected override void OnAppearing()
|
||||
{
|
||||
base.OnAppearing();
|
||||
CodeCell.InitEvents();
|
||||
CodeCell.Entry.FocusWithDelay();
|
||||
CodeCell.Entry.Completed += Entry_Completed;
|
||||
}
|
||||
|
||||
protected override void OnDisappearing()
|
||||
{
|
||||
base.OnDisappearing();
|
||||
CodeCell.Dispose();
|
||||
CodeCell.Entry.Completed -= Entry_Completed;
|
||||
}
|
||||
|
||||
private void Lost2FAApp()
|
||||
|
||||
@@ -7,13 +7,13 @@ namespace Bit.App.Pages
|
||||
{
|
||||
public class MainPage : ExtendedTabbedPage
|
||||
{
|
||||
public MainPage()
|
||||
public MainPage(string uri = null)
|
||||
{
|
||||
TintColor = Color.FromHex("3c8dbc");
|
||||
|
||||
var settingsNavigation = new ExtendedNavigationPage(new SettingsPage());
|
||||
var favoritesNavigation = new ExtendedNavigationPage(new VaultListLoginsPage(true));
|
||||
var vaultNavigation = new ExtendedNavigationPage(new VaultListLoginsPage(false));
|
||||
var favoritesNavigation = new ExtendedNavigationPage(new VaultListLoginsPage(true, uri));
|
||||
var vaultNavigation = new ExtendedNavigationPage(new VaultListLoginsPage(false, uri));
|
||||
var toolsNavigation = new ExtendedNavigationPage(new ToolsPage());
|
||||
|
||||
favoritesNavigation.Title = AppResources.Favorites;
|
||||
|
||||
@@ -38,7 +38,6 @@ namespace Bit.App.Pages
|
||||
useLabelAsPlaceholder: true, imageSource: "envelope", containerPadding: padding);
|
||||
|
||||
EmailCell.Entry.ReturnType = Enums.ReturnType.Go;
|
||||
EmailCell.Entry.Completed += Entry_Completed;
|
||||
|
||||
var table = new ExtendedTableView
|
||||
{
|
||||
@@ -93,9 +92,18 @@ namespace Bit.App.Pages
|
||||
protected override void OnAppearing()
|
||||
{
|
||||
base.OnAppearing();
|
||||
EmailCell.InitEvents();
|
||||
EmailCell.Entry.Completed += Entry_Completed;
|
||||
EmailCell.Entry.FocusWithDelay();
|
||||
}
|
||||
|
||||
protected override void OnDisappearing()
|
||||
{
|
||||
base.OnDisappearing();
|
||||
EmailCell.Dispose();
|
||||
EmailCell.Entry.Completed -= Entry_Completed;
|
||||
}
|
||||
|
||||
private async void Entry_Completed(object sender, EventArgs e)
|
||||
{
|
||||
await SubmitAsync();
|
||||
|
||||
@@ -35,6 +35,9 @@ namespace Bit.App.Pages
|
||||
public FormEntryCell PasswordCell { get; set; }
|
||||
public FormEntryCell ConfirmPasswordCell { get; set; }
|
||||
public FormEntryCell PasswordHintCell { get; set; }
|
||||
public StackLayout StackLayout { get; set; }
|
||||
public Label PasswordLabel { get; set; }
|
||||
public Label HintLabel { get; set; }
|
||||
|
||||
private void Init()
|
||||
{
|
||||
@@ -58,7 +61,6 @@ namespace Bit.App.Pages
|
||||
containerPadding: padding);
|
||||
|
||||
PasswordHintCell.Entry.ReturnType = Enums.ReturnType.Done;
|
||||
PasswordHintCell.Entry.Completed += Entry_Completed;
|
||||
|
||||
var table = new FormTableView
|
||||
{
|
||||
@@ -72,7 +74,7 @@ namespace Bit.App.Pages
|
||||
}
|
||||
};
|
||||
|
||||
var passwordLabel = new Label
|
||||
PasswordLabel = new Label
|
||||
{
|
||||
Text = AppResources.MasterPasswordDescription,
|
||||
LineBreakMode = LineBreakMode.WordWrap,
|
||||
@@ -94,7 +96,7 @@ namespace Bit.App.Pages
|
||||
}
|
||||
};
|
||||
|
||||
var hintLabel = new Label
|
||||
HintLabel = new Label
|
||||
{
|
||||
Text = AppResources.MasterPasswordHintDescription,
|
||||
LineBreakMode = LineBreakMode.WordWrap,
|
||||
@@ -103,21 +105,15 @@ namespace Bit.App.Pages
|
||||
Margin = new Thickness(15, (this.IsLandscape() ? 5 : 0), 15, 25)
|
||||
};
|
||||
|
||||
var layout = new StackLayout
|
||||
StackLayout = new StackLayout
|
||||
{
|
||||
Children = { table, passwordLabel, table2, hintLabel },
|
||||
Children = { table, PasswordLabel, table2, HintLabel },
|
||||
Spacing = 0
|
||||
};
|
||||
|
||||
layout.LayoutChanged += (sender, args) =>
|
||||
{
|
||||
passwordLabel.WidthRequest = layout.Bounds.Width - passwordLabel.Bounds.Left * 2;
|
||||
hintLabel.WidthRequest = layout.Bounds.Width - hintLabel.Bounds.Left * 2;
|
||||
};
|
||||
|
||||
var scrollView = new ScrollView
|
||||
{
|
||||
Content = layout
|
||||
Content = StackLayout
|
||||
};
|
||||
|
||||
var loginToolbarItem = new ToolbarItem(AppResources.Submit, null, async () =>
|
||||
@@ -144,8 +140,30 @@ namespace Bit.App.Pages
|
||||
{
|
||||
base.OnAppearing();
|
||||
MessagingCenter.Send(Application.Current, "ShowStatusBar", true);
|
||||
EmailCell.InitEvents();
|
||||
PasswordCell.InitEvents();
|
||||
PasswordHintCell.InitEvents();
|
||||
ConfirmPasswordCell.InitEvents();
|
||||
PasswordHintCell.Entry.Completed += Entry_Completed;
|
||||
StackLayout.LayoutChanged += Layout_LayoutChanged;
|
||||
EmailCell.Entry.FocusWithDelay();
|
||||
}
|
||||
protected override void OnDisappearing()
|
||||
{
|
||||
base.OnDisappearing();
|
||||
EmailCell.Dispose();
|
||||
PasswordCell.Dispose();
|
||||
PasswordHintCell.Dispose();
|
||||
ConfirmPasswordCell.Dispose();
|
||||
PasswordHintCell.Entry.Completed -= Entry_Completed;
|
||||
StackLayout.LayoutChanged -= Layout_LayoutChanged;
|
||||
}
|
||||
|
||||
private void Layout_LayoutChanged(object sender, EventArgs e)
|
||||
{
|
||||
PasswordLabel.WidthRequest = StackLayout.Bounds.Width - PasswordLabel.Bounds.Left * 2;
|
||||
HintLabel.WidthRequest = StackLayout.Bounds.Width - HintLabel.Bounds.Left * 2;
|
||||
}
|
||||
|
||||
private async void Entry_Completed(object sender, EventArgs e)
|
||||
{
|
||||
|
||||
@@ -4,6 +4,7 @@ using Xamarin.Forms;
|
||||
using Bit.App.Abstractions;
|
||||
using XLabs.Ioc;
|
||||
using Bit.App.Resources;
|
||||
using FFImageLoading.Forms;
|
||||
|
||||
namespace Bit.App.Pages
|
||||
{
|
||||
@@ -17,12 +18,16 @@ namespace Bit.App.Pages
|
||||
Init();
|
||||
}
|
||||
|
||||
public ExtendedTextCell CreditsCell { get; set; }
|
||||
|
||||
public void Init()
|
||||
{
|
||||
var logo = new Image
|
||||
var logo = new CachedImage
|
||||
{
|
||||
Source = "logo",
|
||||
HorizontalOptions = LayoutOptions.Center
|
||||
HorizontalOptions = LayoutOptions.Center,
|
||||
WidthRequest = 282,
|
||||
HeightRequest = 44
|
||||
};
|
||||
|
||||
var versionLabel = new Label
|
||||
@@ -40,12 +45,11 @@ namespace Bit.App.Pages
|
||||
Padding = new Thickness(0, 40, 0, 0)
|
||||
};
|
||||
|
||||
var creditsCell = new ExtendedTextCell
|
||||
CreditsCell = new ExtendedTextCell
|
||||
{
|
||||
Text = AppResources.Credits,
|
||||
ShowDisclousure = true
|
||||
};
|
||||
creditsCell.Tapped += RateCell_Tapped;
|
||||
|
||||
var table = new ExtendedTableView
|
||||
{
|
||||
@@ -57,7 +61,7 @@ namespace Bit.App.Pages
|
||||
{
|
||||
new TableSection
|
||||
{
|
||||
creditsCell
|
||||
CreditsCell
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -87,5 +91,17 @@ namespace Bit.App.Pages
|
||||
{
|
||||
Navigation.PushAsync(new SettingsCreditsPage());
|
||||
}
|
||||
|
||||
protected override void OnAppearing()
|
||||
{
|
||||
base.OnAppearing();
|
||||
CreditsCell.Tapped += RateCell_Tapped;
|
||||
}
|
||||
|
||||
protected override void OnDisappearing()
|
||||
{
|
||||
base.OnDisappearing();
|
||||
CreditsCell.Tapped -= RateCell_Tapped;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,9 +28,11 @@ namespace Bit.App.Pages
|
||||
Init();
|
||||
}
|
||||
|
||||
public FormEntryCell NameCell { get; set; }
|
||||
|
||||
private void Init()
|
||||
{
|
||||
var nameCell = new FormEntryCell(AppResources.Name);
|
||||
NameCell = new FormEntryCell(AppResources.Name);
|
||||
|
||||
var table = new ExtendedTableView
|
||||
{
|
||||
@@ -41,7 +43,7 @@ namespace Bit.App.Pages
|
||||
{
|
||||
new TableSection()
|
||||
{
|
||||
nameCell
|
||||
NameCell
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -60,7 +62,7 @@ namespace Bit.App.Pages
|
||||
return;
|
||||
}
|
||||
|
||||
if(string.IsNullOrWhiteSpace(nameCell.Entry.Text))
|
||||
if(string.IsNullOrWhiteSpace(NameCell.Entry.Text))
|
||||
{
|
||||
await DisplayAlert(AppResources.AnErrorHasOccurred, string.Format(AppResources.ValidationFieldRequired,
|
||||
AppResources.Name), AppResources.Ok);
|
||||
@@ -69,7 +71,7 @@ namespace Bit.App.Pages
|
||||
|
||||
var folder = new Folder
|
||||
{
|
||||
Name = nameCell.Entry.Text.Encrypt()
|
||||
Name = NameCell.Entry.Text.Encrypt()
|
||||
};
|
||||
|
||||
_userDialogs.ShowLoading(AppResources.Saving, MaskType.Black);
|
||||
@@ -105,12 +107,19 @@ namespace Bit.App.Pages
|
||||
protected override void OnAppearing()
|
||||
{
|
||||
base.OnAppearing();
|
||||
NameCell.InitEvents();
|
||||
if(!_connectivity.IsConnected)
|
||||
{
|
||||
AlertNoConnection();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnDisappearing()
|
||||
{
|
||||
base.OnDisappearing();
|
||||
NameCell.Dispose();
|
||||
}
|
||||
|
||||
private void AlertNoConnection()
|
||||
{
|
||||
DisplayAlert(AppResources.InternetConnectionRequiredTitle, AppResources.InternetConnectionRequiredMessage, AppResources.Ok);
|
||||
|
||||
@@ -25,8 +25,9 @@ namespace Bit.App.Pages
|
||||
new TableSection(AppResources.Translations)
|
||||
{
|
||||
new CustomViewCell(@"@felixqu - Chinese
|
||||
@Primokorn - French
|
||||
@King-Tut-Tut - Swedish")
|
||||
@Primokorn, @maxlandry - French
|
||||
@King-Tut-Tut - Swedish
|
||||
@Igetin - Finnish")
|
||||
},
|
||||
new TableSection(AppResources.Icons)
|
||||
{
|
||||
|
||||
@@ -29,6 +29,9 @@ namespace Bit.App.Pages
|
||||
Init();
|
||||
}
|
||||
|
||||
public FormEntryCell NameCell { get; set; }
|
||||
public ExtendedTextCell DeleteCell { get; set; }
|
||||
|
||||
private void Init()
|
||||
{
|
||||
var folder = _folderService.GetByIdAsync(_folderId).GetAwaiter().GetResult();
|
||||
@@ -38,11 +41,10 @@ namespace Bit.App.Pages
|
||||
return;
|
||||
}
|
||||
|
||||
var nameCell = new FormEntryCell(AppResources.Name);
|
||||
nameCell.Entry.Text = folder.Name.Decrypt();
|
||||
NameCell = new FormEntryCell(AppResources.Name);
|
||||
NameCell.Entry.Text = folder.Name.Decrypt();
|
||||
|
||||
var deleteCell = new ExtendedTextCell { Text = AppResources.Delete, TextColor = Color.Red };
|
||||
deleteCell.Tapped += DeleteCell_Tapped;
|
||||
DeleteCell = new ExtendedTextCell { Text = AppResources.Delete, TextColor = Color.Red };
|
||||
|
||||
var mainTable = new ExtendedTableView
|
||||
{
|
||||
@@ -54,11 +56,11 @@ namespace Bit.App.Pages
|
||||
{
|
||||
new TableSection
|
||||
{
|
||||
nameCell
|
||||
NameCell
|
||||
},
|
||||
new TableSection
|
||||
{
|
||||
deleteCell
|
||||
DeleteCell
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -77,14 +79,14 @@ namespace Bit.App.Pages
|
||||
return;
|
||||
}
|
||||
|
||||
if(string.IsNullOrWhiteSpace(nameCell.Entry.Text))
|
||||
if(string.IsNullOrWhiteSpace(NameCell.Entry.Text))
|
||||
{
|
||||
await DisplayAlert(AppResources.AnErrorHasOccurred, string.Format(AppResources.ValidationFieldRequired,
|
||||
AppResources.Name), AppResources.Ok);
|
||||
return;
|
||||
}
|
||||
|
||||
folder.Name = nameCell.Entry.Text.Encrypt();
|
||||
folder.Name = NameCell.Entry.Text.Encrypt();
|
||||
|
||||
_userDialogs.ShowLoading(AppResources.Saving, MaskType.Black);
|
||||
var saveResult = await _folderService.SaveAsync(folder);
|
||||
@@ -119,12 +121,22 @@ namespace Bit.App.Pages
|
||||
protected override void OnAppearing()
|
||||
{
|
||||
base.OnAppearing();
|
||||
NameCell.InitEvents();
|
||||
DeleteCell.Tapped += DeleteCell_Tapped;
|
||||
|
||||
if(!_connectivity.IsConnected)
|
||||
{
|
||||
AlertNoConnection();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnDisappearing()
|
||||
{
|
||||
base.OnDisappearing();
|
||||
NameCell.Dispose();
|
||||
DeleteCell.Tapped -= DeleteCell_Tapped;
|
||||
}
|
||||
|
||||
private async void DeleteCell_Tapped(object sender, EventArgs e)
|
||||
{
|
||||
if(!_connectivity.IsConnected)
|
||||
|
||||
@@ -18,14 +18,21 @@ namespace Bit.App.Pages
|
||||
Init();
|
||||
}
|
||||
|
||||
public ExtendedTextCell EmailCell { get; set; }
|
||||
public ExtendedTextCell WebsiteCell { get; set; }
|
||||
public ExtendedTextCell BugCell { get; set; }
|
||||
public StackLayout StackLayout { get; set; }
|
||||
private CustomLabel EmailLabel { get; set; }
|
||||
private CustomLabel WebsiteLabel { get; set; }
|
||||
private CustomLabel BugLabel { get; set; }
|
||||
|
||||
public void Init()
|
||||
{
|
||||
var emailCell = new ExtendedTextCell
|
||||
EmailCell = new ExtendedTextCell
|
||||
{
|
||||
Text = AppResources.EmailUs,
|
||||
ShowDisclousure = true
|
||||
};
|
||||
emailCell.Tapped += EmailCell_Tapped;
|
||||
|
||||
var emailTable = new CustomTableView
|
||||
{
|
||||
@@ -33,22 +40,21 @@ namespace Bit.App.Pages
|
||||
{
|
||||
new TableSection
|
||||
{
|
||||
emailCell
|
||||
EmailCell
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var emailLabel = new CustomLabel(this)
|
||||
EmailLabel = new CustomLabel(this)
|
||||
{
|
||||
Text = AppResources.EmailUsDescription
|
||||
};
|
||||
|
||||
var websiteCell = new ExtendedTextCell
|
||||
WebsiteCell = new ExtendedTextCell
|
||||
{
|
||||
Text = AppResources.VisitOurWebsite,
|
||||
ShowDisclousure = true
|
||||
};
|
||||
websiteCell.Tapped += WebsiteCell_Tapped;
|
||||
|
||||
var websiteTable = new CustomTableView
|
||||
{
|
||||
@@ -57,22 +63,21 @@ namespace Bit.App.Pages
|
||||
{
|
||||
new TableSection
|
||||
{
|
||||
websiteCell
|
||||
WebsiteCell
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var websiteLabel = new CustomLabel(this)
|
||||
WebsiteLabel = new CustomLabel(this)
|
||||
{
|
||||
Text = AppResources.VisitOurWebsiteDescription
|
||||
};
|
||||
|
||||
var bugCell = new ExtendedTextCell
|
||||
BugCell = new ExtendedTextCell
|
||||
{
|
||||
Text = AppResources.FileBugReport,
|
||||
ShowDisclousure = true
|
||||
};
|
||||
bugCell.Tapped += BugCell_Tapped;
|
||||
|
||||
var bugTable = new CustomTableView
|
||||
{
|
||||
@@ -81,36 +86,54 @@ namespace Bit.App.Pages
|
||||
{
|
||||
new TableSection
|
||||
{
|
||||
bugCell
|
||||
BugCell
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var bugLabel = new CustomLabel(this)
|
||||
BugLabel = new CustomLabel(this)
|
||||
{
|
||||
Text = AppResources.FileBugReportDescription
|
||||
};
|
||||
|
||||
var stackLayout = new StackLayout
|
||||
StackLayout = new StackLayout
|
||||
{
|
||||
Children = { emailTable, emailLabel, websiteTable, websiteLabel, bugTable, bugLabel },
|
||||
Children = { emailTable, EmailLabel, websiteTable, WebsiteLabel, bugTable, BugLabel },
|
||||
Spacing = 0
|
||||
};
|
||||
|
||||
stackLayout.LayoutChanged += (sender, args) =>
|
||||
{
|
||||
websiteLabel.WidthRequest = stackLayout.Bounds.Width - websiteLabel.Bounds.Left * 2;
|
||||
emailLabel.WidthRequest = stackLayout.Bounds.Width - emailLabel.Bounds.Left * 2;
|
||||
bugLabel.WidthRequest = stackLayout.Bounds.Width - bugLabel.Bounds.Left * 2;
|
||||
};
|
||||
|
||||
if(Device.OS == TargetPlatform.iOS)
|
||||
{
|
||||
ToolbarItems.Add(new DismissModalToolBarItem(this, AppResources.Cancel));
|
||||
}
|
||||
|
||||
Title = AppResources.HelpAndFeedback;
|
||||
Content = new ScrollView { Content = stackLayout };
|
||||
Content = new ScrollView { Content = StackLayout };
|
||||
}
|
||||
|
||||
protected override void OnAppearing()
|
||||
{
|
||||
base.OnAppearing();
|
||||
EmailCell.Tapped += EmailCell_Tapped;
|
||||
WebsiteCell.Tapped += WebsiteCell_Tapped;
|
||||
BugCell.Tapped += BugCell_Tapped;
|
||||
StackLayout.LayoutChanged += StackLayout_LayoutChanged;
|
||||
}
|
||||
|
||||
protected override void OnDisappearing()
|
||||
{
|
||||
base.OnDisappearing();
|
||||
EmailCell.Tapped -= EmailCell_Tapped;
|
||||
WebsiteCell.Tapped -= WebsiteCell_Tapped;
|
||||
BugCell.Tapped -= BugCell_Tapped;
|
||||
StackLayout.LayoutChanged -= StackLayout_LayoutChanged;
|
||||
}
|
||||
|
||||
private void StackLayout_LayoutChanged(object sender, EventArgs e)
|
||||
{
|
||||
WebsiteLabel.WidthRequest = StackLayout.Bounds.Width - WebsiteLabel.Bounds.Left * 2;
|
||||
EmailLabel.WidthRequest = StackLayout.Bounds.Width - EmailLabel.Bounds.Left * 2;
|
||||
BugLabel.WidthRequest = StackLayout.Bounds.Width - BugLabel.Bounds.Left * 2;
|
||||
}
|
||||
|
||||
private void EmailCell_Tapped(object sender, EventArgs e)
|
||||
|
||||
@@ -25,18 +25,21 @@ namespace Bit.App.Pages
|
||||
Init();
|
||||
}
|
||||
|
||||
public ExtendedObservableCollection<SettingsFolderPageModel> Folders { get; private set; } = new ExtendedObservableCollection<SettingsFolderPageModel>();
|
||||
public ExtendedObservableCollection<SettingsFolderPageModel> Folders { get; private set; }
|
||||
= new ExtendedObservableCollection<SettingsFolderPageModel>();
|
||||
public ListView ListView { get; set; }
|
||||
private AddFolderToolBarItem AddItem { get; set; }
|
||||
|
||||
private void Init()
|
||||
{
|
||||
ToolbarItems.Add(new AddFolderToolBarItem(this));
|
||||
AddItem = new AddFolderToolBarItem(this);
|
||||
ToolbarItems.Add(AddItem);
|
||||
|
||||
var listView = new ListView
|
||||
ListView = new ListView
|
||||
{
|
||||
ItemsSource = Folders
|
||||
ItemsSource = Folders,
|
||||
ItemTemplate = new DataTemplate(() => new SettingsFolderListViewCell(this))
|
||||
};
|
||||
listView.ItemSelected += FolderSelected;
|
||||
listView.ItemTemplate = new DataTemplate(() => new SettingsFolderListViewCell(this));
|
||||
|
||||
if(Device.OS == TargetPlatform.iOS)
|
||||
{
|
||||
@@ -44,15 +47,24 @@ namespace Bit.App.Pages
|
||||
}
|
||||
|
||||
Title = AppResources.Folders;
|
||||
Content = listView;
|
||||
Content = ListView;
|
||||
}
|
||||
|
||||
protected override void OnAppearing()
|
||||
{
|
||||
base.OnAppearing();
|
||||
ListView.ItemSelected += FolderSelected;
|
||||
AddItem.InitEvents();
|
||||
LoadFoldersAsync().Wait();
|
||||
}
|
||||
|
||||
protected override void OnDisappearing()
|
||||
{
|
||||
base.OnDisappearing();
|
||||
ListView.ItemSelected -= FolderSelected;
|
||||
AddItem.Dispose();
|
||||
}
|
||||
|
||||
private async Task LoadFoldersAsync()
|
||||
{
|
||||
var folders = await _folderService.GetAllAsync();
|
||||
@@ -67,7 +79,7 @@ namespace Bit.App.Pages
|
||||
await Navigation.PushForDeviceAsync(page);
|
||||
}
|
||||
|
||||
private class AddFolderToolBarItem : ToolbarItem
|
||||
private class AddFolderToolBarItem : ExtendedToolbarItem
|
||||
{
|
||||
private readonly SettingsListFoldersPage _page;
|
||||
|
||||
@@ -76,10 +88,10 @@ namespace Bit.App.Pages
|
||||
_page = page;
|
||||
Text = AppResources.Add;
|
||||
Icon = "plus";
|
||||
Clicked += ClickedItem;
|
||||
ClickAction = () => ClickedItem();
|
||||
}
|
||||
|
||||
private async void ClickedItem(object sender, EventArgs e)
|
||||
private async void ClickedItem()
|
||||
{
|
||||
var page = new SettingsAddFolderPage();
|
||||
await _page.Navigation.PushForDeviceAsync(page);
|
||||
|
||||
@@ -37,6 +37,17 @@ namespace Bit.App.Pages
|
||||
private ExtendedSwitchCell PinCell { get; set; }
|
||||
private ExtendedSwitchCell FingerprintCell { get; set; }
|
||||
private ExtendedTextCell LockOptionsCell { get; set; }
|
||||
private ExtendedTextCell TwoStepCell { get; set; }
|
||||
private ExtendedTextCell ChangeMasterPasswordCell { get; set; }
|
||||
private ExtendedTextCell ChangeEmailCell { get; set; }
|
||||
private ExtendedTextCell FoldersCell { get; set; }
|
||||
private ExtendedTextCell SyncCell { get; set; }
|
||||
private ExtendedTextCell LockCell { get; set; }
|
||||
private ExtendedTextCell LogOutCell { get; set; }
|
||||
private ExtendedTextCell AboutCell { get; set; }
|
||||
private ExtendedTextCell HelpCell { get; set; }
|
||||
private ExtendedTextCell RateCell { get; set; }
|
||||
private LongDetailViewCell RateCellLong { get; set; }
|
||||
private ExtendedTableView Table { get; set; }
|
||||
|
||||
private void Init()
|
||||
@@ -46,7 +57,6 @@ namespace Bit.App.Pages
|
||||
Text = AppResources.UnlockWithPIN,
|
||||
On = _settings.GetValueOrDefault(Constants.SettingPinUnlockOn, false)
|
||||
};
|
||||
PinCell.OnChanged += PinCell_Changed;
|
||||
|
||||
LockOptionsCell = new ExtendedTextCell
|
||||
{
|
||||
@@ -54,20 +64,18 @@ namespace Bit.App.Pages
|
||||
Detail = GetLockOptionsDetailsText(),
|
||||
ShowDisclousure = true
|
||||
};
|
||||
LockOptionsCell.Tapped += LockOptionsCell_Tapped;
|
||||
|
||||
var twoStepCell = new ExtendedTextCell
|
||||
TwoStepCell = new ExtendedTextCell
|
||||
{
|
||||
Text = AppResources.TwoStepLogin,
|
||||
ShowDisclousure = true
|
||||
};
|
||||
twoStepCell.Tapped += TwoStepCell_Tapped; ;
|
||||
|
||||
var securitySecion = new TableSection(AppResources.Security)
|
||||
{
|
||||
LockOptionsCell,
|
||||
PinCell,
|
||||
twoStepCell
|
||||
TwoStepCell
|
||||
};
|
||||
|
||||
if(_fingerprint.IsAvailable)
|
||||
@@ -80,87 +88,76 @@ namespace Bit.App.Pages
|
||||
On = _settings.GetValueOrDefault(Constants.SettingFingerprintUnlockOn, false),
|
||||
IsEnabled = _fingerprint.IsAvailable
|
||||
};
|
||||
FingerprintCell.OnChanged += FingerprintCell_Changed;
|
||||
securitySecion.Insert(1, FingerprintCell);
|
||||
}
|
||||
|
||||
var changeMasterPasswordCell = new ExtendedTextCell
|
||||
ChangeMasterPasswordCell = new ExtendedTextCell
|
||||
{
|
||||
Text = AppResources.ChangeMasterPassword,
|
||||
ShowDisclousure = true
|
||||
};
|
||||
changeMasterPasswordCell.Tapped += ChangeMasterPasswordCell_Tapped;
|
||||
|
||||
var changeEmailCell = new ExtendedTextCell
|
||||
ChangeEmailCell = new ExtendedTextCell
|
||||
{
|
||||
Text = AppResources.ChangeEmail,
|
||||
ShowDisclousure = true
|
||||
};
|
||||
changeEmailCell.Tapped += ChangeEmailCell_Tapped;
|
||||
|
||||
var foldersCell = new ExtendedTextCell
|
||||
FoldersCell = new ExtendedTextCell
|
||||
{
|
||||
Text = AppResources.Folders,
|
||||
ShowDisclousure = true
|
||||
};
|
||||
foldersCell.Tapped += FoldersCell_Tapped;
|
||||
|
||||
var syncCell = new ExtendedTextCell
|
||||
SyncCell = new ExtendedTextCell
|
||||
{
|
||||
Text = AppResources.Sync,
|
||||
ShowDisclousure = true
|
||||
};
|
||||
syncCell.Tapped += SyncCell_Tapped;
|
||||
|
||||
var lockCell = new ExtendedTextCell
|
||||
LockCell = new ExtendedTextCell
|
||||
{
|
||||
Text = AppResources.Lock
|
||||
};
|
||||
lockCell.Tapped += LockCell_Tapped;
|
||||
|
||||
var logOutCell = new ExtendedTextCell
|
||||
LogOutCell = new ExtendedTextCell
|
||||
{
|
||||
Text = AppResources.LogOut
|
||||
};
|
||||
logOutCell.Tapped += LogOutCell_Tapped;
|
||||
|
||||
var aboutCell = new ExtendedTextCell
|
||||
AboutCell = new ExtendedTextCell
|
||||
{
|
||||
Text = AppResources.About,
|
||||
ShowDisclousure = true
|
||||
};
|
||||
aboutCell.Tapped += AboutCell_Tapped;
|
||||
|
||||
var helpCell = new ExtendedTextCell
|
||||
HelpCell = new ExtendedTextCell
|
||||
{
|
||||
Text = AppResources.HelpAndFeedback,
|
||||
ShowDisclousure = true
|
||||
};
|
||||
helpCell.Tapped += HelpCell_Tapped;
|
||||
|
||||
var otherSection = new TableSection(AppResources.Other)
|
||||
{
|
||||
aboutCell,
|
||||
helpCell
|
||||
AboutCell,
|
||||
HelpCell
|
||||
};
|
||||
|
||||
if(Device.OS == TargetPlatform.iOS)
|
||||
{
|
||||
var rateCell = new LongDetailViewCell(AppResources.RateTheApp, AppResources.RateTheAppDescriptionAppStore);
|
||||
rateCell.Tapped += RateCell_Tapped;
|
||||
otherSection.Add(rateCell);
|
||||
RateCellLong = new LongDetailViewCell(AppResources.RateTheApp, AppResources.RateTheAppDescriptionAppStore);
|
||||
otherSection.Add(RateCellLong);
|
||||
}
|
||||
else
|
||||
{
|
||||
var rateCell = new ExtendedTextCell
|
||||
RateCell = new ExtendedTextCell
|
||||
{
|
||||
Text = AppResources.RateTheApp,
|
||||
Detail = AppResources.RateTheAppDescription,
|
||||
ShowDisclousure = true,
|
||||
DetailLineBreakMode = LineBreakMode.WordWrap
|
||||
};
|
||||
rateCell.Tapped += RateCell_Tapped;
|
||||
otherSection.Add(rateCell);
|
||||
otherSection.Add(RateCell);
|
||||
}
|
||||
|
||||
Table = new CustomTable
|
||||
@@ -170,18 +167,18 @@ namespace Bit.App.Pages
|
||||
securitySecion,
|
||||
new TableSection(AppResources.Account)
|
||||
{
|
||||
changeMasterPasswordCell,
|
||||
changeEmailCell
|
||||
ChangeMasterPasswordCell,
|
||||
ChangeEmailCell
|
||||
},
|
||||
new TableSection(AppResources.Manage)
|
||||
{
|
||||
foldersCell,
|
||||
syncCell
|
||||
FoldersCell,
|
||||
SyncCell
|
||||
},
|
||||
new TableSection(AppResources.CurrentSession)
|
||||
{
|
||||
lockCell,
|
||||
logOutCell
|
||||
LockCell,
|
||||
LogOutCell
|
||||
},
|
||||
otherSection
|
||||
}
|
||||
@@ -191,6 +188,72 @@ namespace Bit.App.Pages
|
||||
Content = Table;
|
||||
}
|
||||
|
||||
protected override void OnAppearing()
|
||||
{
|
||||
base.OnAppearing();
|
||||
|
||||
PinCell.OnChanged += PinCell_Changed;
|
||||
LockOptionsCell.Tapped += LockOptionsCell_Tapped;
|
||||
TwoStepCell.Tapped += TwoStepCell_Tapped;
|
||||
ChangeMasterPasswordCell.Tapped += ChangeMasterPasswordCell_Tapped;
|
||||
|
||||
if(FingerprintCell != null)
|
||||
{
|
||||
FingerprintCell.OnChanged += FingerprintCell_Changed;
|
||||
}
|
||||
|
||||
ChangeEmailCell.Tapped += ChangeEmailCell_Tapped;
|
||||
FoldersCell.Tapped += FoldersCell_Tapped;
|
||||
SyncCell.Tapped += SyncCell_Tapped;
|
||||
LockCell.Tapped += LockCell_Tapped;
|
||||
LogOutCell.Tapped += LogOutCell_Tapped;
|
||||
AboutCell.Tapped += AboutCell_Tapped;
|
||||
HelpCell.Tapped += HelpCell_Tapped;
|
||||
|
||||
if(RateCellLong != null)
|
||||
{
|
||||
RateCellLong.Tapped += RateCell_Tapped;
|
||||
}
|
||||
|
||||
if(RateCell != null)
|
||||
{
|
||||
RateCell.Tapped += RateCell_Tapped;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnDisappearing()
|
||||
{
|
||||
base.OnDisappearing();
|
||||
|
||||
PinCell.OnChanged -= PinCell_Changed;
|
||||
LockOptionsCell.Tapped -= LockOptionsCell_Tapped;
|
||||
TwoStepCell.Tapped -= TwoStepCell_Tapped;
|
||||
ChangeMasterPasswordCell.Tapped -= ChangeMasterPasswordCell_Tapped;
|
||||
|
||||
if(FingerprintCell != null)
|
||||
{
|
||||
FingerprintCell.OnChanged -= FingerprintCell_Changed;
|
||||
}
|
||||
|
||||
ChangeEmailCell.Tapped -= ChangeEmailCell_Tapped;
|
||||
FoldersCell.Tapped -= FoldersCell_Tapped;
|
||||
SyncCell.Tapped -= SyncCell_Tapped;
|
||||
LockCell.Tapped -= LockCell_Tapped;
|
||||
LogOutCell.Tapped -= LogOutCell_Tapped;
|
||||
AboutCell.Tapped -= AboutCell_Tapped;
|
||||
HelpCell.Tapped -= HelpCell_Tapped;
|
||||
|
||||
if(RateCellLong != null)
|
||||
{
|
||||
RateCellLong.Tapped -= RateCell_Tapped;
|
||||
}
|
||||
|
||||
if(RateCell != null)
|
||||
{
|
||||
RateCell.Tapped -= RateCell_Tapped;
|
||||
}
|
||||
}
|
||||
|
||||
private async void TwoStepCell_Tapped(object sender, EventArgs e)
|
||||
{
|
||||
if(!await _userDialogs.ConfirmAsync(AppResources.TwoStepLoginConfirmation, null, AppResources.Yes,
|
||||
@@ -274,7 +337,6 @@ namespace Bit.App.Pages
|
||||
private void LockCell_Tapped(object sender, EventArgs e)
|
||||
{
|
||||
_googleAnalyticsService.TrackAppEvent("Locked");
|
||||
_settings.AddOrUpdateValue(Constants.Locked, true);
|
||||
MessagingCenter.Send(Application.Current, "Lock", true);
|
||||
}
|
||||
|
||||
@@ -340,8 +402,7 @@ namespace Bit.App.Pages
|
||||
if(cell.On && !_settings.GetValueOrDefault(Constants.SettingPinUnlockOn, false))
|
||||
{
|
||||
cell.On = false;
|
||||
var pinPage = new SettingsPinPage();
|
||||
pinPage.OnPinEntered += PinEntered;
|
||||
var pinPage = new SettingsPinPage((page) => PinEntered(page));
|
||||
Navigation.PushModalAsync(new ExtendedNavigationPage(pinPage));
|
||||
}
|
||||
else if(!cell.On)
|
||||
@@ -350,9 +411,8 @@ namespace Bit.App.Pages
|
||||
}
|
||||
}
|
||||
|
||||
private void PinEntered(object sender, EventArgs args)
|
||||
private void PinEntered(SettingsPinPage page)
|
||||
{
|
||||
var page = sender as SettingsPinPage;
|
||||
page.PinControl.Entry.Unfocus();
|
||||
page.Navigation.PopModalAsync();
|
||||
|
||||
|
||||
@@ -15,9 +15,11 @@ namespace Bit.App.Pages
|
||||
{
|
||||
private readonly IUserDialogs _userDialogs;
|
||||
private readonly ISettings _settings;
|
||||
private Action<SettingsPinPage> _pinEnteredAction;
|
||||
|
||||
public SettingsPinPage()
|
||||
public SettingsPinPage(Action<SettingsPinPage> pinEnteredAction)
|
||||
{
|
||||
_pinEnteredAction = pinEnteredAction;
|
||||
_userDialogs = Resolver.Resolve<IUserDialogs>();
|
||||
_settings = Resolver.Resolve<ISettings>();
|
||||
|
||||
@@ -26,7 +28,7 @@ namespace Bit.App.Pages
|
||||
|
||||
public PinPageModel Model { get; set; } = new PinPageModel();
|
||||
public PinControl PinControl { get; set; }
|
||||
public EventHandler OnPinEntered;
|
||||
public TapGestureRecognizer Tgr { get; set; }
|
||||
|
||||
public void Init()
|
||||
{
|
||||
@@ -40,7 +42,6 @@ namespace Bit.App.Pages
|
||||
};
|
||||
|
||||
PinControl = new PinControl();
|
||||
PinControl.OnPinEntered += PinEntered;
|
||||
PinControl.Label.SetBinding<PinPageModel>(Label.TextProperty, s => s.LabelText);
|
||||
PinControl.Entry.SetBinding<PinPageModel>(Entry.TextProperty, s => s.PIN);
|
||||
|
||||
@@ -51,10 +52,9 @@ namespace Bit.App.Pages
|
||||
Children = { PinControl.Label, instructionLabel, PinControl.Entry }
|
||||
};
|
||||
|
||||
var tgr = new TapGestureRecognizer();
|
||||
tgr.Tapped += Tgr_Tapped;
|
||||
PinControl.Label.GestureRecognizers.Add(tgr);
|
||||
instructionLabel.GestureRecognizers.Add(tgr);
|
||||
Tgr = new TapGestureRecognizer();
|
||||
PinControl.Label.GestureRecognizers.Add(Tgr);
|
||||
instructionLabel.GestureRecognizers.Add(Tgr);
|
||||
|
||||
if(Device.OS == TargetPlatform.iOS)
|
||||
{
|
||||
@@ -63,24 +63,35 @@ namespace Bit.App.Pages
|
||||
|
||||
Title = AppResources.SetPIN;
|
||||
Content = stackLayout;
|
||||
Content.GestureRecognizers.Add(tgr);
|
||||
Content.GestureRecognizers.Add(Tgr);
|
||||
BindingContext = Model;
|
||||
}
|
||||
|
||||
protected override void OnAppearing()
|
||||
{
|
||||
base.OnAppearing();
|
||||
Tgr.Tapped += Tgr_Tapped;
|
||||
PinControl.OnPinEntered += PinEntered;
|
||||
PinControl.InitEvents();
|
||||
PinControl.Entry.FocusWithDelay();
|
||||
}
|
||||
|
||||
protected override void OnDisappearing()
|
||||
{
|
||||
base.OnDisappearing();
|
||||
PinControl.Dispose();
|
||||
Tgr.Tapped -= Tgr_Tapped;
|
||||
PinControl.OnPinEntered -= PinEntered;
|
||||
}
|
||||
|
||||
protected void PinEntered(object sender, EventArgs args)
|
||||
{
|
||||
_pinEnteredAction?.Invoke(this);
|
||||
}
|
||||
|
||||
private void Tgr_Tapped(object sender, EventArgs e)
|
||||
{
|
||||
PinControl.Entry.Focus();
|
||||
}
|
||||
|
||||
protected override void OnAppearing()
|
||||
{
|
||||
base.OnAppearing();
|
||||
PinControl.Entry.FocusWithDelay();
|
||||
}
|
||||
|
||||
protected void PinEntered(object sender, EventArgs args)
|
||||
{
|
||||
OnPinEntered.Invoke(this, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ using Xamarin.Forms;
|
||||
using XLabs.Ioc;
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.App.Resources;
|
||||
using FFImageLoading.Forms;
|
||||
|
||||
namespace Bit.App.Pages
|
||||
{
|
||||
@@ -74,7 +75,7 @@ namespace Bit.App.Pages
|
||||
TextColor = Color.Black
|
||||
};
|
||||
|
||||
var step1Image = new Image
|
||||
var step1Image = new CachedImage
|
||||
{
|
||||
Source = "accessibility_step1",
|
||||
HorizontalOptions = LayoutOptions.Center,
|
||||
@@ -92,7 +93,7 @@ namespace Bit.App.Pages
|
||||
TextColor = Color.Black
|
||||
};
|
||||
|
||||
var step2Image = new Image
|
||||
var step2Image = new CachedImage
|
||||
{
|
||||
Source = "accessibility_step2",
|
||||
HorizontalOptions = LayoutOptions.Center,
|
||||
@@ -119,7 +120,7 @@ namespace Bit.App.Pages
|
||||
TextColor = Color.Black
|
||||
};
|
||||
|
||||
var tapNotificationImage = new Image
|
||||
var tapNotificationImage = new CachedImage
|
||||
{
|
||||
Source = "accessibility_notification",
|
||||
HorizontalOptions = LayoutOptions.Center,
|
||||
@@ -128,7 +129,7 @@ namespace Bit.App.Pages
|
||||
HeightRequest = 74
|
||||
};
|
||||
|
||||
var tapNotificationIcon = new Image
|
||||
var tapNotificationIcon = new CachedImage
|
||||
{
|
||||
Source = "accessibility_notification_icon",
|
||||
HorizontalOptions = LayoutOptions.Center,
|
||||
|
||||
@@ -8,6 +8,7 @@ using Xamarin.Forms;
|
||||
using XLabs.Ioc;
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.App.Resources;
|
||||
using FFImageLoading.Forms;
|
||||
|
||||
namespace Bit.App.Pages
|
||||
{
|
||||
@@ -52,12 +53,14 @@ namespace Bit.App.Pages
|
||||
LineBreakMode = LineBreakMode.WordWrap
|
||||
};
|
||||
|
||||
var notStartedImage = new Image
|
||||
var notStartedImage = new CachedImage
|
||||
{
|
||||
Source = "ext-more",
|
||||
VerticalOptions = LayoutOptions.CenterAndExpand,
|
||||
HorizontalOptions = LayoutOptions.Center,
|
||||
Margin = new Thickness(0, -10, 0, 0)
|
||||
Margin = new Thickness(0, -10, 0, 0),
|
||||
WidthRequest = 290,
|
||||
HeightRequest = 252
|
||||
};
|
||||
|
||||
var notStartedButton = new ExtendedButton
|
||||
@@ -101,12 +104,14 @@ namespace Bit.App.Pages
|
||||
LineBreakMode = LineBreakMode.WordWrap
|
||||
};
|
||||
|
||||
var notActivatedImage = new Image
|
||||
var notActivatedImage = new CachedImage
|
||||
{
|
||||
Source = "ext-act",
|
||||
VerticalOptions = LayoutOptions.CenterAndExpand,
|
||||
HorizontalOptions = LayoutOptions.Center,
|
||||
Margin = new Thickness(0, -10, 0, 0)
|
||||
Margin = new Thickness(0, -10, 0, 0),
|
||||
WidthRequest = 290,
|
||||
HeightRequest = 252
|
||||
};
|
||||
|
||||
var notActivatedButton = new ExtendedButton
|
||||
@@ -151,12 +156,14 @@ namespace Bit.App.Pages
|
||||
Margin = new Thickness(0, 10, 0, 0)
|
||||
};
|
||||
|
||||
var activatedImage = new Image
|
||||
var activatedImage = new CachedImage
|
||||
{
|
||||
Source = "ext-use",
|
||||
VerticalOptions = LayoutOptions.CenterAndExpand,
|
||||
HorizontalOptions = LayoutOptions.Center,
|
||||
Margin = new Thickness(0, -10, 0, 0)
|
||||
Margin = new Thickness(0, -10, 0, 0),
|
||||
WidthRequest = 290,
|
||||
HeightRequest = 252
|
||||
};
|
||||
|
||||
var activatedButton = new ExtendedButton
|
||||
|
||||
@@ -6,6 +6,7 @@ using Bit.App.Controls;
|
||||
using Bit.App.Resources;
|
||||
using Xamarin.Forms;
|
||||
using XLabs.Ioc;
|
||||
using FFImageLoading.Forms;
|
||||
|
||||
namespace Bit.App.Pages
|
||||
{
|
||||
@@ -22,42 +23,37 @@ namespace Bit.App.Pages
|
||||
Init();
|
||||
}
|
||||
|
||||
public ToolsViewCell GeneratorCell { get; set; }
|
||||
public ToolsViewCell WebCell { get; set; }
|
||||
public ToolsViewCell ImportCell { get; set; }
|
||||
public ToolsViewCell ExtensionCell { get; set; }
|
||||
public ToolsViewCell AutofillCell { get; set; }
|
||||
|
||||
public void Init()
|
||||
{
|
||||
var generatorCell = new ToolsViewCell(AppResources.PasswordGenerator, AppResources.PasswordGeneratorDescription,
|
||||
GeneratorCell = new ToolsViewCell(AppResources.PasswordGenerator, AppResources.PasswordGeneratorDescription,
|
||||
"refresh");
|
||||
generatorCell.Tapped += GeneratorCell_Tapped;
|
||||
var webCell = new ToolsViewCell(AppResources.WebVault, AppResources.WebVaultDescription, "globe");
|
||||
webCell.Tapped += WebCell_Tapped;
|
||||
var importCell = new ToolsViewCell(AppResources.ImportLogins, AppResources.ImportLoginsDescription, "cloudup");
|
||||
importCell.Tapped += ImportCell_Tapped;
|
||||
WebCell = new ToolsViewCell(AppResources.WebVault, AppResources.WebVaultDescription, "globe");
|
||||
ImportCell = new ToolsViewCell(AppResources.ImportLogins, AppResources.ImportLoginsDescription, "cloudup");
|
||||
|
||||
var section = new TableSection { generatorCell };
|
||||
var section = new TableSection { GeneratorCell };
|
||||
|
||||
if(Device.OS == TargetPlatform.iOS)
|
||||
{
|
||||
var extensionCell = new ToolsViewCell(AppResources.BitwardenAppExtension,
|
||||
ExtensionCell = new ToolsViewCell(AppResources.BitwardenAppExtension,
|
||||
AppResources.BitwardenAppExtensionDescription, "upload");
|
||||
extensionCell.Tapped += (object sender, EventArgs e) =>
|
||||
{
|
||||
Navigation.PushModalAsync(new ExtendedNavigationPage(new ToolsExtensionPage()));
|
||||
};
|
||||
section.Add(extensionCell);
|
||||
section.Add(ExtensionCell);
|
||||
}
|
||||
else
|
||||
{
|
||||
var autofillServiceCell = new ToolsViewCell(
|
||||
AutofillCell = new ToolsViewCell(
|
||||
string.Format("{0} ({1})", AppResources.BitwardenAutofillService, AppResources.Beta),
|
||||
AppResources.BitwardenAutofillServiceDescription, "upload");
|
||||
autofillServiceCell.Tapped += (object sender, EventArgs e) =>
|
||||
{
|
||||
Navigation.PushModalAsync(new ExtendedNavigationPage(new ToolsAutofillServicePage()));
|
||||
};
|
||||
section.Add(autofillServiceCell);
|
||||
section.Add(AutofillCell);
|
||||
}
|
||||
|
||||
section.Add(webCell);
|
||||
section.Add(importCell);
|
||||
section.Add(WebCell);
|
||||
section.Add(ImportCell);
|
||||
|
||||
var table = new ExtendedTableView
|
||||
{
|
||||
@@ -80,6 +76,49 @@ namespace Bit.App.Pages
|
||||
Content = table;
|
||||
}
|
||||
|
||||
protected override void OnAppearing()
|
||||
{
|
||||
base.OnAppearing();
|
||||
GeneratorCell.Tapped += GeneratorCell_Tapped;
|
||||
WebCell.Tapped += WebCell_Tapped;
|
||||
ImportCell.Tapped += ImportCell_Tapped;
|
||||
if(ExtensionCell != null)
|
||||
{
|
||||
ExtensionCell.Tapped += ExtensionCell_Tapped;
|
||||
}
|
||||
if(AutofillCell != null)
|
||||
{
|
||||
AutofillCell.Tapped += AutofillCell_Tapped;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnDisappearing()
|
||||
{
|
||||
base.OnDisappearing();
|
||||
GeneratorCell.Tapped -= GeneratorCell_Tapped;
|
||||
WebCell.Tapped -= WebCell_Tapped;
|
||||
ImportCell.Tapped -= ImportCell_Tapped;
|
||||
if(ExtensionCell != null)
|
||||
{
|
||||
ExtensionCell.Tapped -= ExtensionCell_Tapped;
|
||||
}
|
||||
if(AutofillCell != null)
|
||||
{
|
||||
AutofillCell.Tapped -= AutofillCell_Tapped;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void AutofillCell_Tapped(object sender, EventArgs e)
|
||||
{
|
||||
Navigation.PushModalAsync(new ExtendedNavigationPage(new ToolsAutofillServicePage()));
|
||||
}
|
||||
|
||||
private void ExtensionCell_Tapped(object sender, EventArgs e)
|
||||
{
|
||||
Navigation.PushModalAsync(new ExtendedNavigationPage(new ToolsExtensionPage()));
|
||||
}
|
||||
|
||||
private async void GeneratorCell_Tapped(object sender, EventArgs e)
|
||||
{
|
||||
await Navigation.PushForDeviceAsync(new ToolsPasswordGeneratorPage());
|
||||
@@ -127,9 +166,11 @@ namespace Bit.App.Pages
|
||||
Text = detailText
|
||||
};
|
||||
|
||||
var image = new Image
|
||||
var image = new CachedImage
|
||||
{
|
||||
Source = imageSource
|
||||
Source = imageSource,
|
||||
WidthRequest = 44,
|
||||
HeightRequest = 44
|
||||
};
|
||||
|
||||
var grid = new Grid
|
||||
|
||||
@@ -36,6 +36,10 @@ namespace Bit.App.Pages
|
||||
public PasswordGeneratorPageModel Model { get; private set; } = new PasswordGeneratorPageModel();
|
||||
public Label Password { get; private set; }
|
||||
public SliderViewCell SliderCell { get; private set; }
|
||||
public TapGestureRecognizer Tgr { get; set; }
|
||||
public ExtendedTextCell SettingsCell { get; set; }
|
||||
public ExtendedTextCell RegenerateCell { get; set; }
|
||||
public ExtendedTextCell CopyCell { get; set; }
|
||||
|
||||
public void Init()
|
||||
{
|
||||
@@ -49,20 +53,16 @@ namespace Bit.App.Pages
|
||||
VerticalOptions = LayoutOptions.Start
|
||||
};
|
||||
|
||||
var tgr = new TapGestureRecognizer();
|
||||
tgr.Tapped += Tgr_Tapped;
|
||||
Password.GestureRecognizers.Add(tgr);
|
||||
Tgr = new TapGestureRecognizer();
|
||||
Password.GestureRecognizers.Add(Tgr);
|
||||
Password.SetBinding<PasswordGeneratorPageModel>(Label.TextProperty, m => m.Password);
|
||||
|
||||
SliderCell = new SliderViewCell(this, _passwordGenerationService, _settings);
|
||||
var settingsCell = new ExtendedTextCell { Text = AppResources.MoreSettings, ShowDisclousure = true };
|
||||
settingsCell.Tapped += SettingsCell_Tapped;
|
||||
SettingsCell = new ExtendedTextCell { Text = AppResources.MoreSettings, ShowDisclousure = true };
|
||||
|
||||
var buttonColor = Color.FromHex("3c8dbc");
|
||||
var regenerateCell = new ExtendedTextCell { Text = AppResources.RegeneratePassword, TextColor = buttonColor };
|
||||
regenerateCell.Tapped += RegenerateCell_Tapped; ;
|
||||
var copyCell = new ExtendedTextCell { Text = AppResources.CopyPassword, TextColor = buttonColor };
|
||||
copyCell.Tapped += CopyCell_Tapped;
|
||||
RegenerateCell = new ExtendedTextCell { Text = AppResources.RegeneratePassword, TextColor = buttonColor };
|
||||
CopyCell = new ExtendedTextCell { Text = AppResources.CopyPassword, TextColor = buttonColor };
|
||||
|
||||
var table = new ExtendedTableView
|
||||
{
|
||||
@@ -75,13 +75,13 @@ namespace Bit.App.Pages
|
||||
{
|
||||
new TableSection
|
||||
{
|
||||
regenerateCell,
|
||||
copyCell
|
||||
RegenerateCell,
|
||||
CopyCell
|
||||
},
|
||||
new TableSection(AppResources.Options)
|
||||
{
|
||||
SliderCell,
|
||||
settingsCell
|
||||
SettingsCell
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -142,6 +142,12 @@ namespace Bit.App.Pages
|
||||
protected override void OnAppearing()
|
||||
{
|
||||
base.OnAppearing();
|
||||
Tgr.Tapped += Tgr_Tapped;
|
||||
RegenerateCell.Tapped += RegenerateCell_Tapped;
|
||||
SettingsCell.Tapped += SettingsCell_Tapped;
|
||||
CopyCell.Tapped += CopyCell_Tapped;
|
||||
SliderCell.InitEvents();
|
||||
|
||||
if(_fromAutofill)
|
||||
{
|
||||
_googleAnalyticsService.TrackExtensionEvent("GeneratedPassword");
|
||||
@@ -154,6 +160,16 @@ namespace Bit.App.Pages
|
||||
Model.Length = _settings.GetValueOrDefault(Constants.PasswordGeneratorLength, 10).ToString();
|
||||
}
|
||||
|
||||
protected override void OnDisappearing()
|
||||
{
|
||||
base.OnDisappearing();
|
||||
Tgr.Tapped -= Tgr_Tapped;
|
||||
RegenerateCell.Tapped -= RegenerateCell_Tapped;
|
||||
SettingsCell.Tapped -= SettingsCell_Tapped;
|
||||
CopyCell.Tapped -= CopyCell_Tapped;
|
||||
SliderCell.Dispose();
|
||||
}
|
||||
|
||||
private void RegenerateCell_Tapped(object sender, EventArgs e)
|
||||
{
|
||||
Model.Password = _passwordGenerationService.GeneratePassword();
|
||||
@@ -192,7 +208,7 @@ namespace Bit.App.Pages
|
||||
}
|
||||
|
||||
// TODO: move to standalone reusable control
|
||||
public class SliderViewCell : ExtendedViewCell
|
||||
public class SliderViewCell : ExtendedViewCell, IDisposable
|
||||
{
|
||||
private readonly ToolsPasswordGeneratorPage _page;
|
||||
private readonly IPasswordGenerationService _passwordGenerationService;
|
||||
@@ -234,8 +250,6 @@ namespace Bit.App.Pages
|
||||
|
||||
Value.SetBinding<PasswordGeneratorPageModel>(Label.TextProperty, m => m.Length);
|
||||
|
||||
LengthSlider.ValueChanged += Slider_ValueChanged;
|
||||
|
||||
var stackLayout = new StackLayout
|
||||
{
|
||||
Orientation = StackOrientation.Horizontal,
|
||||
@@ -263,6 +277,16 @@ namespace Bit.App.Pages
|
||||
_page.Model.Length = length.ToString();
|
||||
_page.Model.Password = _passwordGenerationService.GeneratePassword();
|
||||
}
|
||||
|
||||
public void InitEvents()
|
||||
{
|
||||
LengthSlider.ValueChanged += Slider_ValueChanged;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
LengthSlider.ValueChanged -= Slider_ValueChanged;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,35 +39,30 @@ namespace Bit.App.Pages
|
||||
Text = "A-Z",
|
||||
On = _settings.GetValueOrDefault(Constants.PasswordGeneratorUppercase, true)
|
||||
};
|
||||
UppercaseCell.OnChanged += UppercaseCell_OnChanged;
|
||||
|
||||
LowercaseCell = new ExtendedSwitchCell
|
||||
{
|
||||
Text = "a-z",
|
||||
On = _settings.GetValueOrDefault(Constants.PasswordGeneratorLowercase, true)
|
||||
};
|
||||
LowercaseCell.OnChanged += LowercaseCell_OnChanged;
|
||||
|
||||
SpecialCell = new ExtendedSwitchCell
|
||||
{
|
||||
Text = "!@#$%^&*",
|
||||
On = _settings.GetValueOrDefault(Constants.PasswordGeneratorSpecial, true)
|
||||
};
|
||||
SpecialCell.OnChanged += SpecialCell_OnChanged;
|
||||
|
||||
NumbersCell = new ExtendedSwitchCell
|
||||
{
|
||||
Text = "0-9",
|
||||
On = _settings.GetValueOrDefault(Constants.PasswordGeneratorNumbers, true)
|
||||
};
|
||||
NumbersCell.OnChanged += NumbersCell_OnChanged;
|
||||
|
||||
AvoidAmbiguousCell = new ExtendedSwitchCell
|
||||
{
|
||||
Text = AppResources.AvoidAmbiguousCharacters,
|
||||
On = !_settings.GetValueOrDefault(Constants.PasswordGeneratorAmbiguous, false)
|
||||
};
|
||||
AvoidAmbiguousCell.OnChanged += AvoidAmbiguousCell_OnChanged; ;
|
||||
|
||||
NumbersMinCell = new StepperCell(AppResources.MinNumbers,
|
||||
_settings.GetValueOrDefault(Constants.PasswordGeneratorMinNumbers, 1), 0, 5, 1);
|
||||
@@ -111,8 +106,29 @@ namespace Bit.App.Pages
|
||||
Content = table;
|
||||
}
|
||||
|
||||
protected override void OnAppearing()
|
||||
{
|
||||
base.OnAppearing();
|
||||
SpecialCell.OnChanged += SpecialCell_OnChanged;
|
||||
AvoidAmbiguousCell.OnChanged += AvoidAmbiguousCell_OnChanged;
|
||||
UppercaseCell.OnChanged += UppercaseCell_OnChanged;
|
||||
LowercaseCell.OnChanged += LowercaseCell_OnChanged;
|
||||
NumbersCell.OnChanged += NumbersCell_OnChanged;
|
||||
NumbersMinCell.InitEvents();
|
||||
SpecialMinCell.InitEvents();
|
||||
}
|
||||
|
||||
protected override void OnDisappearing()
|
||||
{
|
||||
base.OnDisappearing();
|
||||
SpecialCell.OnChanged -= SpecialCell_OnChanged;
|
||||
AvoidAmbiguousCell.OnChanged -= AvoidAmbiguousCell_OnChanged;
|
||||
UppercaseCell.OnChanged -= UppercaseCell_OnChanged;
|
||||
LowercaseCell.OnChanged -= LowercaseCell_OnChanged;
|
||||
NumbersCell.OnChanged -= NumbersCell_OnChanged;
|
||||
NumbersMinCell.Dispose();
|
||||
SpecialMinCell.Dispose();
|
||||
|
||||
_settings.AddOrUpdateValue(Constants.PasswordGeneratorMinNumbers,
|
||||
Convert.ToInt32(NumbersMinCell.Stepper.Value));
|
||||
|
||||
|
||||
@@ -46,32 +46,37 @@ namespace Bit.App.Pages
|
||||
}
|
||||
|
||||
public FormEntryCell PasswordCell { get; private set; }
|
||||
public FormEntryCell UsernameCell { get; private set; }
|
||||
public FormEntryCell UriCell { get; private set; }
|
||||
public FormEntryCell NameCell { get; private set; }
|
||||
public FormEditorCell NotesCell { get; private set; }
|
||||
public FormPickerCell FolderCell { get; private set; }
|
||||
public ExtendedTextCell GenerateCell { get; private set; }
|
||||
|
||||
private void Init()
|
||||
{
|
||||
var notesCell = new FormEditorCell(height: 90);
|
||||
PasswordCell = new FormEntryCell(AppResources.Password, isPassword: true, nextElement: notesCell.Editor,
|
||||
NotesCell = new FormEditorCell(height: 90);
|
||||
PasswordCell = new FormEntryCell(AppResources.Password, isPassword: true, nextElement: NotesCell.Editor,
|
||||
useButton: true);
|
||||
PasswordCell.Button.Image = "eye";
|
||||
PasswordCell.Button.Clicked += PasswordButton_Clicked;
|
||||
PasswordCell.Entry.DisableAutocapitalize = true;
|
||||
PasswordCell.Entry.Autocorrect = false;
|
||||
PasswordCell.Entry.FontFamily = Device.OnPlatform(iOS: "Courier", Android: "monospace", WinPhone: "Courier");
|
||||
|
||||
var usernameCell = new FormEntryCell(AppResources.Username, nextElement: PasswordCell.Entry);
|
||||
usernameCell.Entry.DisableAutocapitalize = true;
|
||||
usernameCell.Entry.Autocorrect = false;
|
||||
UsernameCell = new FormEntryCell(AppResources.Username, nextElement: PasswordCell.Entry);
|
||||
UsernameCell.Entry.DisableAutocapitalize = true;
|
||||
UsernameCell.Entry.Autocorrect = false;
|
||||
|
||||
var uriCell = new FormEntryCell(AppResources.URI, Keyboard.Url, nextElement: usernameCell.Entry);
|
||||
UriCell = new FormEntryCell(AppResources.URI, Keyboard.Url, nextElement: UsernameCell.Entry);
|
||||
if(!string.IsNullOrWhiteSpace(_defaultUri))
|
||||
{
|
||||
uriCell.Entry.Text = _defaultUri;
|
||||
UriCell.Entry.Text = _defaultUri;
|
||||
}
|
||||
|
||||
var nameCell = new FormEntryCell(AppResources.Name, nextElement: uriCell.Entry);
|
||||
NameCell = new FormEntryCell(AppResources.Name, nextElement: UriCell.Entry);
|
||||
if(!string.IsNullOrWhiteSpace(_defaultName))
|
||||
{
|
||||
nameCell.Entry.Text = _defaultName;
|
||||
NameCell.Entry.Text = _defaultName;
|
||||
}
|
||||
|
||||
var folderOptions = new List<string> { AppResources.FolderNone };
|
||||
@@ -81,14 +86,13 @@ namespace Bit.App.Pages
|
||||
{
|
||||
folderOptions.Add(folder.Name.Decrypt());
|
||||
}
|
||||
var folderCell = new FormPickerCell(AppResources.Folder, folderOptions.ToArray());
|
||||
FolderCell = new FormPickerCell(AppResources.Folder, folderOptions.ToArray());
|
||||
|
||||
var generateCell = new ExtendedTextCell
|
||||
GenerateCell = new ExtendedTextCell
|
||||
{
|
||||
Text = AppResources.GeneratePassword,
|
||||
ShowDisclousure = true
|
||||
};
|
||||
generateCell.Tapped += GenerateCell_Tapped; ;
|
||||
|
||||
var favoriteCell = new ExtendedSwitchCell { Text = AppResources.Favorite };
|
||||
|
||||
@@ -101,20 +105,20 @@ namespace Bit.App.Pages
|
||||
{
|
||||
new TableSection(AppResources.LoginInformation)
|
||||
{
|
||||
nameCell,
|
||||
uriCell,
|
||||
usernameCell,
|
||||
NameCell,
|
||||
UriCell,
|
||||
UsernameCell,
|
||||
PasswordCell,
|
||||
generateCell
|
||||
GenerateCell
|
||||
},
|
||||
new TableSection
|
||||
{
|
||||
folderCell,
|
||||
FolderCell,
|
||||
favoriteCell
|
||||
},
|
||||
new TableSection(AppResources.Notes)
|
||||
{
|
||||
notesCell
|
||||
NotesCell
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -137,7 +141,7 @@ namespace Bit.App.Pages
|
||||
return;
|
||||
}
|
||||
|
||||
if(string.IsNullOrWhiteSpace(nameCell.Entry.Text))
|
||||
if(string.IsNullOrWhiteSpace(NameCell.Entry.Text))
|
||||
{
|
||||
await DisplayAlert(AppResources.AnErrorHasOccurred, string.Format(AppResources.ValidationFieldRequired,
|
||||
AppResources.Name), AppResources.Ok);
|
||||
@@ -146,17 +150,17 @@ namespace Bit.App.Pages
|
||||
|
||||
var login = new Login
|
||||
{
|
||||
Uri = uriCell.Entry.Text?.Encrypt(),
|
||||
Name = nameCell.Entry.Text?.Encrypt(),
|
||||
Username = usernameCell.Entry.Text?.Encrypt(),
|
||||
Uri = UriCell.Entry.Text?.Encrypt(),
|
||||
Name = NameCell.Entry.Text?.Encrypt(),
|
||||
Username = UsernameCell.Entry.Text?.Encrypt(),
|
||||
Password = PasswordCell.Entry.Text?.Encrypt(),
|
||||
Notes = notesCell.Editor.Text?.Encrypt(),
|
||||
Notes = NotesCell.Editor.Text?.Encrypt(),
|
||||
Favorite = favoriteCell.On
|
||||
};
|
||||
|
||||
if(folderCell.Picker.SelectedIndex > 0)
|
||||
if(FolderCell.Picker.SelectedIndex > 0)
|
||||
{
|
||||
login.FolderId = folders.ElementAt(folderCell.Picker.SelectedIndex - 1).Id;
|
||||
login.FolderId = folders.ElementAt(FolderCell.Picker.SelectedIndex - 1).Id;
|
||||
}
|
||||
|
||||
_userDialogs.ShowLoading(AppResources.Saving, MaskType.Black);
|
||||
@@ -203,6 +207,15 @@ namespace Bit.App.Pages
|
||||
AlertNoConnection();
|
||||
}
|
||||
|
||||
PasswordCell.InitEvents();
|
||||
UsernameCell.InitEvents();
|
||||
UriCell.InitEvents();
|
||||
NameCell.InitEvents();
|
||||
NotesCell.InitEvents();
|
||||
FolderCell.InitEvents();
|
||||
PasswordCell.Button.Clicked += PasswordButton_Clicked;
|
||||
GenerateCell.Tapped += GenerateCell_Tapped;
|
||||
|
||||
if(!_fromAutofill && !_settings.GetValueOrDefault(AddedLoginAlertKey, false))
|
||||
{
|
||||
_settings.AddOrUpdateValue(AddedLoginAlertKey, true);
|
||||
@@ -219,6 +232,19 @@ namespace Bit.App.Pages
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnDisappearing()
|
||||
{
|
||||
base.OnDisappearing();
|
||||
PasswordCell.Dispose();
|
||||
UsernameCell.Dispose();
|
||||
UriCell.Dispose();
|
||||
NameCell.Dispose();
|
||||
NotesCell.Dispose();
|
||||
FolderCell.Dispose();
|
||||
PasswordCell.Button.Clicked -= PasswordButton_Clicked;
|
||||
GenerateCell.Tapped -= GenerateCell_Tapped;
|
||||
}
|
||||
|
||||
private void PasswordButton_Clicked(object sender, EventArgs e)
|
||||
{
|
||||
PasswordCell.Entry.InvokeToggleIsPassword();
|
||||
|
||||
@@ -19,7 +19,6 @@ namespace Bit.App.Pages
|
||||
{
|
||||
private readonly ILoginService _loginService;
|
||||
private readonly IDeviceInfoService _deviceInfoService;
|
||||
private readonly IUserDialogs _userDialogs;
|
||||
private readonly IClipboardService _clipboardService;
|
||||
private readonly ISettingsService _settingsService;
|
||||
private CancellationTokenSource _filterResultsCancellationTokenSource;
|
||||
@@ -47,20 +46,23 @@ namespace Bit.App.Pages
|
||||
|
||||
_loginService = Resolver.Resolve<ILoginService>();
|
||||
_deviceInfoService = Resolver.Resolve<IDeviceInfoService>();
|
||||
_userDialogs = Resolver.Resolve<IUserDialogs>();
|
||||
_clipboardService = Resolver.Resolve<IClipboardService>();
|
||||
_settingsService = Resolver.Resolve<ISettingsService>();
|
||||
UserDialogs = Resolver.Resolve<IUserDialogs>();
|
||||
GoogleAnalyticsService = Resolver.Resolve<IGoogleAnalyticsService>();
|
||||
|
||||
Init();
|
||||
}
|
||||
|
||||
public ExtendedObservableCollection<VaultListPageModel.Login> PresentationLogins { get; private set; }
|
||||
= new ExtendedObservableCollection<VaultListPageModel.Login>();
|
||||
public ExtendedObservableCollection<VaultListPageModel.AutofillGrouping> PresentationLoginsGroup { get; private set; }
|
||||
= new ExtendedObservableCollection<VaultListPageModel.AutofillGrouping>();
|
||||
public StackLayout NoDataStackLayout { get; set; }
|
||||
public ListView ListView { get; set; }
|
||||
public ActivityIndicator LoadingIndicator { get; set; }
|
||||
private SearchToolBarItem SearchItem { get; set; }
|
||||
private AddLoginToolBarItem AddLoginItem { get; set; }
|
||||
private IGoogleAnalyticsService GoogleAnalyticsService { get; set; }
|
||||
private IUserDialogs UserDialogs { get; set; }
|
||||
private string Uri { get; set; }
|
||||
|
||||
private void Init()
|
||||
@@ -88,13 +90,17 @@ namespace Bit.App.Pages
|
||||
Spacing = 20
|
||||
};
|
||||
|
||||
ToolbarItems.Add(new AddLoginToolBarItem(this));
|
||||
ToolbarItems.Add(new CloseToolBarItem(this));
|
||||
AddLoginItem = new AddLoginToolBarItem(this);
|
||||
ToolbarItems.Add(AddLoginItem);
|
||||
SearchItem = new SearchToolBarItem(this);
|
||||
ToolbarItems.Add(SearchItem);
|
||||
|
||||
ListView = new ListView(ListViewCachingStrategy.RecycleElement)
|
||||
{
|
||||
ItemsSource = PresentationLogins,
|
||||
IsGroupingEnabled = true,
|
||||
ItemsSource = PresentationLoginsGroup,
|
||||
HasUnevenRows = true,
|
||||
GroupHeaderTemplate = new DataTemplate(() => new HeaderViewCell()),
|
||||
ItemTemplate = new DataTemplate(() => new VaultListViewCell(
|
||||
(VaultListPageModel.Login l) => MoreClickedAsync(l)))
|
||||
};
|
||||
@@ -104,8 +110,6 @@ namespace Bit.App.Pages
|
||||
ListView.RowHeight = -1;
|
||||
}
|
||||
|
||||
ListView.ItemSelected += LoginSelected;
|
||||
|
||||
Title = string.Format(AppResources.LoginsForUri, _name ?? "--");
|
||||
|
||||
LoadingIndicator = new ActivityIndicator
|
||||
@@ -121,9 +125,20 @@ namespace Bit.App.Pages
|
||||
protected override void OnAppearing()
|
||||
{
|
||||
base.OnAppearing();
|
||||
ListView.ItemSelected += LoginSelected;
|
||||
AddLoginItem.InitEvents();
|
||||
SearchItem.InitEvents();
|
||||
_filterResultsCancellationTokenSource = FetchAndLoadVault();
|
||||
}
|
||||
|
||||
protected override void OnDisappearing()
|
||||
{
|
||||
base.OnDisappearing();
|
||||
ListView.ItemSelected -= LoginSelected;
|
||||
AddLoginItem.Dispose();
|
||||
SearchItem.Dispose();
|
||||
}
|
||||
|
||||
protected override bool OnBackButtonPressed()
|
||||
{
|
||||
GoogleAnalyticsService.TrackExtensionEvent("BackClosed", Uri.StartsWith("http") ? "Website" : "App");
|
||||
@@ -133,7 +148,7 @@ namespace Bit.App.Pages
|
||||
|
||||
private void AdjustContent()
|
||||
{
|
||||
if(PresentationLogins.Count > 0)
|
||||
if(PresentationLoginsGroup.Count > 0)
|
||||
{
|
||||
Content = ListView;
|
||||
}
|
||||
@@ -150,14 +165,35 @@ namespace Bit.App.Pages
|
||||
|
||||
Task.Run(async () =>
|
||||
{
|
||||
var autofillGroupings = new List<VaultListPageModel.AutofillGrouping>();
|
||||
var logins = await _loginService.GetAllAsync(Uri);
|
||||
var sortedLogins = logins.Select(l => new VaultListPageModel.Login(l))
|
||||
|
||||
var normalLogins = logins?.Item1.Select(l => new VaultListPageModel.AutofillLogin(l, false))
|
||||
.OrderBy(s => s.Name)
|
||||
.ThenBy(s => s.Username);
|
||||
.ThenBy(s => s.Username)
|
||||
.ToList();
|
||||
if(normalLogins?.Any() ?? false)
|
||||
{
|
||||
autofillGroupings.Add(new VaultListPageModel.AutofillGrouping(normalLogins, AppResources.MatchingLogins));
|
||||
}
|
||||
|
||||
var fuzzyLogins = logins?.Item2.Select(l => new VaultListPageModel.AutofillLogin(l, true))
|
||||
.OrderBy(s => s.Name)
|
||||
.ThenBy(s => s.Username)
|
||||
.ToList();
|
||||
if(fuzzyLogins?.Any() ?? false)
|
||||
{
|
||||
autofillGroupings.Add(new VaultListPageModel.AutofillGrouping(fuzzyLogins,
|
||||
AppResources.PossibleMatchingLogins));
|
||||
}
|
||||
|
||||
Device.BeginInvokeOnMainThread(() =>
|
||||
{
|
||||
PresentationLogins.ResetWithRange(sortedLogins);
|
||||
if(autofillGroupings.Any())
|
||||
{
|
||||
PresentationLoginsGroup.ResetWithRange(autofillGroupings);
|
||||
}
|
||||
|
||||
AdjustContent();
|
||||
});
|
||||
}, cts.Token);
|
||||
@@ -165,18 +201,36 @@ namespace Bit.App.Pages
|
||||
return cts;
|
||||
}
|
||||
|
||||
private void LoginSelected(object sender, SelectedItemChangedEventArgs e)
|
||||
private async void LoginSelected(object sender, SelectedItemChangedEventArgs e)
|
||||
{
|
||||
var login = e.SelectedItem as VaultListPageModel.Login;
|
||||
var login = e.SelectedItem as VaultListPageModel.AutofillLogin;
|
||||
if(login == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if(Uri.StartsWith("http") && _deviceInfoService.Version < 21)
|
||||
{
|
||||
MoreClickedAsync(login);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
bool doAutofill = true;
|
||||
if(login.Fuzzy)
|
||||
{
|
||||
doAutofill = await UserDialogs.ConfirmAsync(
|
||||
string.Format(AppResources.BitwardenAutofillServiceMatchConfirm, _name),
|
||||
okText: AppResources.Yes, cancelText: AppResources.No);
|
||||
}
|
||||
|
||||
if(doAutofill)
|
||||
{
|
||||
GoogleAnalyticsService.TrackExtensionEvent("AutoFilled", Uri.StartsWith("http") ? "Website" : "App");
|
||||
MessagingCenter.Send(Application.Current, "Autofill", login as VaultListPageModel.Login);
|
||||
}
|
||||
}
|
||||
|
||||
GoogleAnalyticsService.TrackExtensionEvent("AutoFilled", Uri.StartsWith("http") ? "Website" : "App");
|
||||
MessagingCenter.Send(Application.Current, "Autofill", login);
|
||||
((ListView)sender).SelectedItem = null;
|
||||
}
|
||||
|
||||
private async void AddLoginAsync()
|
||||
@@ -222,45 +276,64 @@ namespace Bit.App.Pages
|
||||
private void Copy(string copyText, string alertLabel)
|
||||
{
|
||||
_clipboardService.CopyToClipboard(copyText);
|
||||
_userDialogs.Toast(string.Format(AppResources.ValueHasBeenCopied, alertLabel));
|
||||
UserDialogs.Toast(string.Format(AppResources.ValueHasBeenCopied, alertLabel));
|
||||
}
|
||||
|
||||
private class AddLoginToolBarItem : ToolbarItem
|
||||
private class AddLoginToolBarItem : ExtendedToolbarItem
|
||||
{
|
||||
private readonly VaultAutofillListLoginsPage _page;
|
||||
|
||||
public AddLoginToolBarItem(VaultAutofillListLoginsPage page)
|
||||
: base(() => page.AddLoginAsync())
|
||||
{
|
||||
_page = page;
|
||||
Text = AppResources.Add;
|
||||
Icon = "plus";
|
||||
Clicked += ClickedItem;
|
||||
Priority = 1;
|
||||
}
|
||||
|
||||
private void ClickedItem(object sender, EventArgs e)
|
||||
{
|
||||
_page.AddLoginAsync();
|
||||
Priority = 2;
|
||||
}
|
||||
}
|
||||
|
||||
private class CloseToolBarItem : ToolbarItem
|
||||
private class SearchToolBarItem : ExtendedToolbarItem
|
||||
{
|
||||
private readonly VaultAutofillListLoginsPage _page;
|
||||
|
||||
public CloseToolBarItem(VaultAutofillListLoginsPage page)
|
||||
public SearchToolBarItem(VaultAutofillListLoginsPage page)
|
||||
{
|
||||
_page = page;
|
||||
Text = AppResources.Close;
|
||||
Icon = "close";
|
||||
Clicked += ClickedItem;
|
||||
Priority = 2;
|
||||
Text = AppResources.Search;
|
||||
Icon = "search";
|
||||
Priority = 1;
|
||||
ClickAction = () => DoClick();
|
||||
}
|
||||
|
||||
private void ClickedItem(object sender, EventArgs e)
|
||||
private void DoClick()
|
||||
{
|
||||
_page.GoogleAnalyticsService.TrackExtensionEvent("Closed", _page.Uri.StartsWith("http") ? "Website" : "App");
|
||||
MessagingCenter.Send(Application.Current, "SetMainPage");
|
||||
_page.GoogleAnalyticsService.TrackExtensionEvent("CloseToSearch",
|
||||
_page.Uri.StartsWith("http") ? "Website" : "App");
|
||||
Application.Current.MainPage = new MainPage(_page.Uri);
|
||||
_page.UserDialogs.Toast(string.Format(AppResources.BitwardenAutofillServiceSearch, _page._name),
|
||||
TimeSpan.FromSeconds(10));
|
||||
}
|
||||
}
|
||||
|
||||
private class HeaderViewCell : ExtendedViewCell
|
||||
{
|
||||
public HeaderViewCell()
|
||||
{
|
||||
var label = new Label
|
||||
{
|
||||
FontSize = Device.GetNamedSize(NamedSize.Medium, typeof(Label)),
|
||||
Style = (Style)Application.Current.Resources["text-muted"],
|
||||
VerticalTextAlignment = TextAlignment.Center
|
||||
};
|
||||
|
||||
label.SetBinding<VaultListPageModel.AutofillGrouping>(Label.TextProperty, s => s.Name);
|
||||
|
||||
var grid = new ContentView
|
||||
{
|
||||
Padding = new Thickness(16, 8, 0, 8),
|
||||
Content = label
|
||||
};
|
||||
|
||||
View = grid;
|
||||
BackgroundColor = Color.FromHex("efeff4");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,13 @@ namespace Bit.App.Pages
|
||||
}
|
||||
|
||||
public FormEntryCell PasswordCell { get; private set; }
|
||||
public FormEntryCell UsernameCell { get; private set; }
|
||||
public FormEntryCell UriCell { get; private set; }
|
||||
public FormEntryCell NameCell { get; private set; }
|
||||
public FormEditorCell NotesCell { get; private set; }
|
||||
public FormPickerCell FolderCell { get; private set; }
|
||||
public ExtendedTextCell GenerateCell { get; private set; }
|
||||
public ExtendedTextCell DeleteCell { get; private set; }
|
||||
|
||||
private void Init()
|
||||
{
|
||||
@@ -43,34 +50,32 @@ namespace Bit.App.Pages
|
||||
return;
|
||||
}
|
||||
|
||||
var notesCell = new FormEditorCell(height: 90);
|
||||
notesCell.Editor.Text = login.Notes?.Decrypt();
|
||||
NotesCell = new FormEditorCell(height: 90);
|
||||
NotesCell.Editor.Text = login.Notes?.Decrypt();
|
||||
|
||||
PasswordCell = new FormEntryCell(AppResources.Password, isPassword: true, nextElement: notesCell.Editor,
|
||||
PasswordCell = new FormEntryCell(AppResources.Password, isPassword: true, nextElement: NotesCell.Editor,
|
||||
useButton: true);
|
||||
PasswordCell.Entry.Text = login.Password?.Decrypt();
|
||||
PasswordCell.Button.Image = "eye";
|
||||
PasswordCell.Button.Clicked += PasswordButton_Clicked;
|
||||
PasswordCell.Entry.DisableAutocapitalize = true;
|
||||
PasswordCell.Entry.Autocorrect = false;
|
||||
PasswordCell.Entry.FontFamily = Device.OnPlatform(iOS: "Courier", Android: "monospace", WinPhone: "Courier");
|
||||
|
||||
var usernameCell = new FormEntryCell(AppResources.Username, nextElement: PasswordCell.Entry);
|
||||
usernameCell.Entry.Text = login.Username?.Decrypt();
|
||||
usernameCell.Entry.DisableAutocapitalize = true;
|
||||
usernameCell.Entry.Autocorrect = false;
|
||||
UsernameCell = new FormEntryCell(AppResources.Username, nextElement: PasswordCell.Entry);
|
||||
UsernameCell.Entry.Text = login.Username?.Decrypt();
|
||||
UsernameCell.Entry.DisableAutocapitalize = true;
|
||||
UsernameCell.Entry.Autocorrect = false;
|
||||
|
||||
var uriCell = new FormEntryCell(AppResources.URI, Keyboard.Url, nextElement: usernameCell.Entry);
|
||||
uriCell.Entry.Text = login.Uri?.Decrypt();
|
||||
var nameCell = new FormEntryCell(AppResources.Name, nextElement: uriCell.Entry);
|
||||
nameCell.Entry.Text = login.Name?.Decrypt();
|
||||
UriCell = new FormEntryCell(AppResources.URI, Keyboard.Url, nextElement: UsernameCell.Entry);
|
||||
UriCell.Entry.Text = login.Uri?.Decrypt();
|
||||
NameCell = new FormEntryCell(AppResources.Name, nextElement: UriCell.Entry);
|
||||
NameCell.Entry.Text = login.Name?.Decrypt();
|
||||
|
||||
var generateCell = new ExtendedTextCell
|
||||
GenerateCell = new ExtendedTextCell
|
||||
{
|
||||
Text = AppResources.GeneratePassword,
|
||||
ShowDisclousure = true
|
||||
};
|
||||
generateCell.Tapped += GenerateCell_Tapped; ;
|
||||
|
||||
var folderOptions = new List<string> { AppResources.FolderNone };
|
||||
var folders = _folderService.GetAllAsync().GetAwaiter().GetResult()
|
||||
@@ -87,8 +92,8 @@ namespace Bit.App.Pages
|
||||
|
||||
folderOptions.Add(folder.Name.Decrypt());
|
||||
}
|
||||
var folderCell = new FormPickerCell(AppResources.Folder, folderOptions.ToArray());
|
||||
folderCell.Picker.SelectedIndex = selectedIndex;
|
||||
FolderCell = new FormPickerCell(AppResources.Folder, folderOptions.ToArray());
|
||||
FolderCell.Picker.SelectedIndex = selectedIndex;
|
||||
|
||||
var favoriteCell = new ExtendedSwitchCell
|
||||
{
|
||||
@@ -96,8 +101,7 @@ namespace Bit.App.Pages
|
||||
On = login.Favorite
|
||||
};
|
||||
|
||||
var deleteCell = new ExtendedTextCell { Text = AppResources.Delete, TextColor = Color.Red };
|
||||
deleteCell.Tapped += DeleteCell_Tapped;
|
||||
DeleteCell = new ExtendedTextCell { Text = AppResources.Delete, TextColor = Color.Red };
|
||||
|
||||
var table = new ExtendedTableView
|
||||
{
|
||||
@@ -108,24 +112,24 @@ namespace Bit.App.Pages
|
||||
{
|
||||
new TableSection(AppResources.LoginInformation)
|
||||
{
|
||||
nameCell,
|
||||
uriCell,
|
||||
usernameCell,
|
||||
NameCell,
|
||||
UriCell,
|
||||
UsernameCell,
|
||||
PasswordCell,
|
||||
generateCell
|
||||
GenerateCell
|
||||
},
|
||||
new TableSection
|
||||
{
|
||||
folderCell,
|
||||
FolderCell,
|
||||
favoriteCell
|
||||
},
|
||||
new TableSection(AppResources.Notes)
|
||||
{
|
||||
notesCell
|
||||
NotesCell
|
||||
},
|
||||
new TableSection
|
||||
{
|
||||
deleteCell
|
||||
DeleteCell
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -148,23 +152,23 @@ namespace Bit.App.Pages
|
||||
return;
|
||||
}
|
||||
|
||||
if(string.IsNullOrWhiteSpace(nameCell.Entry.Text))
|
||||
if(string.IsNullOrWhiteSpace(NameCell.Entry.Text))
|
||||
{
|
||||
await DisplayAlert(AppResources.AnErrorHasOccurred, string.Format(AppResources.ValidationFieldRequired,
|
||||
AppResources.Name), AppResources.Ok);
|
||||
return;
|
||||
}
|
||||
|
||||
login.Uri = uriCell.Entry.Text?.Encrypt();
|
||||
login.Name = nameCell.Entry.Text?.Encrypt();
|
||||
login.Username = usernameCell.Entry.Text?.Encrypt();
|
||||
login.Uri = UriCell.Entry.Text?.Encrypt();
|
||||
login.Name = NameCell.Entry.Text?.Encrypt();
|
||||
login.Username = UsernameCell.Entry.Text?.Encrypt();
|
||||
login.Password = PasswordCell.Entry.Text?.Encrypt();
|
||||
login.Notes = notesCell.Editor.Text?.Encrypt();
|
||||
login.Notes = NotesCell.Editor.Text?.Encrypt();
|
||||
login.Favorite = favoriteCell.On;
|
||||
|
||||
if(folderCell.Picker.SelectedIndex > 0)
|
||||
if(FolderCell.Picker.SelectedIndex > 0)
|
||||
{
|
||||
login.FolderId = folders.ElementAt(folderCell.Picker.SelectedIndex - 1).Id;
|
||||
login.FolderId = folders.ElementAt(FolderCell.Picker.SelectedIndex - 1).Id;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -208,6 +212,50 @@ namespace Bit.App.Pages
|
||||
{
|
||||
AlertNoConnection();
|
||||
}
|
||||
|
||||
PasswordCell?.InitEvents();
|
||||
UsernameCell?.InitEvents();
|
||||
UriCell?.InitEvents();
|
||||
NameCell?.InitEvents();
|
||||
NotesCell?.InitEvents();
|
||||
FolderCell?.InitEvents();
|
||||
|
||||
if(PasswordCell?.Button != null)
|
||||
{
|
||||
PasswordCell.Button.Clicked += PasswordButton_Clicked;
|
||||
}
|
||||
if(GenerateCell != null)
|
||||
{
|
||||
GenerateCell.Tapped += GenerateCell_Tapped;
|
||||
}
|
||||
if(DeleteCell != null)
|
||||
{
|
||||
DeleteCell.Tapped += DeleteCell_Tapped;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnDisappearing()
|
||||
{
|
||||
base.OnDisappearing();
|
||||
PasswordCell?.Dispose();
|
||||
UsernameCell?.Dispose();
|
||||
UriCell?.Dispose();
|
||||
NameCell?.Dispose();
|
||||
NotesCell?.Dispose();
|
||||
FolderCell?.Dispose();
|
||||
|
||||
if(PasswordCell?.Button != null)
|
||||
{
|
||||
PasswordCell.Button.Clicked -= PasswordButton_Clicked;
|
||||
}
|
||||
if(GenerateCell != null)
|
||||
{
|
||||
GenerateCell.Tapped -= GenerateCell_Tapped;
|
||||
}
|
||||
if(DeleteCell != null)
|
||||
{
|
||||
DeleteCell.Tapped -= DeleteCell_Tapped;
|
||||
}
|
||||
}
|
||||
|
||||
private void PasswordButton_Clicked(object sender, EventArgs e)
|
||||
|
||||
@@ -14,6 +14,7 @@ using Plugin.Settings.Abstractions;
|
||||
using Plugin.Connectivity.Abstractions;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using FFImageLoading.Forms;
|
||||
|
||||
namespace Bit.App.Pages
|
||||
{
|
||||
@@ -28,11 +29,12 @@ namespace Bit.App.Pages
|
||||
private readonly IPushNotification _pushNotification;
|
||||
private readonly IDeviceInfoService _deviceInfoService;
|
||||
private readonly ISettings _settings;
|
||||
private readonly IGoogleAnalyticsService _googleAnalyticsService;
|
||||
private readonly bool _favorites;
|
||||
private bool _loadExistingData;
|
||||
private CancellationTokenSource _filterResultsCancellationTokenSource;
|
||||
|
||||
public VaultListLoginsPage(bool favorites)
|
||||
public VaultListLoginsPage(bool favorites, string uri = null)
|
||||
: base(true)
|
||||
{
|
||||
_favorites = favorites;
|
||||
@@ -45,10 +47,13 @@ namespace Bit.App.Pages
|
||||
_pushNotification = Resolver.Resolve<IPushNotification>();
|
||||
_deviceInfoService = Resolver.Resolve<IDeviceInfoService>();
|
||||
_settings = Resolver.Resolve<ISettings>();
|
||||
_googleAnalyticsService = Resolver.Resolve<IGoogleAnalyticsService>();
|
||||
|
||||
var cryptoService = Resolver.Resolve<ICryptoService>();
|
||||
_loadExistingData = !_settings.GetValueOrDefault(Constants.FirstVaultLoad, true) || !cryptoService.KeyChanged;
|
||||
|
||||
Uri = uri;
|
||||
|
||||
Init();
|
||||
}
|
||||
|
||||
@@ -61,6 +66,8 @@ namespace Bit.App.Pages
|
||||
public StackLayout NoDataStackLayout { get; set; }
|
||||
public StackLayout ResultsStackLayout { get; set; }
|
||||
public ActivityIndicator LoadingIndicator { get; set; }
|
||||
private AddLoginToolBarItem AddLoginItem { get; set; }
|
||||
public string Uri { get; set; }
|
||||
|
||||
private void Init()
|
||||
{
|
||||
@@ -74,7 +81,8 @@ namespace Bit.App.Pages
|
||||
|
||||
if(!_favorites)
|
||||
{
|
||||
ToolbarItems.Add(new AddLoginToolBarItem(this));
|
||||
AddLoginItem = new AddLoginToolBarItem(this);
|
||||
ToolbarItems.Add(AddLoginItem);
|
||||
}
|
||||
|
||||
ListView = new ListView(ListViewCachingStrategy.RecycleElement)
|
||||
@@ -92,16 +100,12 @@ namespace Bit.App.Pages
|
||||
ListView.RowHeight = -1;
|
||||
}
|
||||
|
||||
ListView.ItemSelected += LoginSelected;
|
||||
|
||||
Search = new SearchBar
|
||||
{
|
||||
Placeholder = AppResources.SearchVault,
|
||||
FontSize = Device.GetNamedSize(NamedSize.Small, typeof(Button)),
|
||||
CancelButtonColor = Color.FromHex("3c8dbc")
|
||||
};
|
||||
Search.TextChanged += SearchBar_TextChanged;
|
||||
Search.SearchButtonPressed += SearchBar_SearchButtonPressed;
|
||||
// Bug with searchbar on android 7, ref https://bugzilla.xamarin.com/show_bug.cgi?id=43975
|
||||
if(Device.OS == TargetPlatform.Android && _deviceInfoService.Version >= 24)
|
||||
{
|
||||
@@ -225,6 +229,11 @@ namespace Bit.App.Pages
|
||||
protected override void OnAppearing()
|
||||
{
|
||||
base.OnAppearing();
|
||||
ListView.ItemSelected += LoginSelected;
|
||||
Search.TextChanged += SearchBar_TextChanged;
|
||||
Search.SearchButtonPressed += SearchBar_SearchButtonPressed;
|
||||
AddLoginItem?.InitEvents();
|
||||
|
||||
if(_loadExistingData)
|
||||
{
|
||||
_filterResultsCancellationTokenSource = FetchAndLoadVault();
|
||||
@@ -262,6 +271,27 @@ namespace Bit.App.Pages
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnDisappearing()
|
||||
{
|
||||
base.OnDisappearing();
|
||||
ListView.ItemSelected -= LoginSelected;
|
||||
Search.TextChanged -= SearchBar_TextChanged;
|
||||
Search.SearchButtonPressed -= SearchBar_SearchButtonPressed;
|
||||
AddLoginItem?.Dispose();
|
||||
}
|
||||
|
||||
protected override bool OnBackButtonPressed()
|
||||
{
|
||||
if(string.IsNullOrWhiteSpace(Uri))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_googleAnalyticsService.TrackExtensionEvent("BackClosed", Uri.StartsWith("http") ? "Website" : "App");
|
||||
MessagingCenter.Send(Application.Current, "Autofill", (VaultListPageModel.Login)null);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void AdjustContent()
|
||||
{
|
||||
if(PresentationFolders.Count > 0 || !string.IsNullOrWhiteSpace(Search.Text))
|
||||
@@ -363,8 +393,37 @@ namespace Bit.App.Pages
|
||||
private async void LoginSelected(object sender, SelectedItemChangedEventArgs e)
|
||||
{
|
||||
var login = e.SelectedItem as VaultListPageModel.Login;
|
||||
var page = new VaultViewLoginPage(login.Id);
|
||||
await Navigation.PushForDeviceAsync(page);
|
||||
if(login == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
string selection = null;
|
||||
if(!string.IsNullOrWhiteSpace(Uri))
|
||||
{
|
||||
selection = await DisplayActionSheet(AppResources.AutofillOrView, AppResources.Cancel, null,
|
||||
AppResources.Autofill, AppResources.View);
|
||||
}
|
||||
|
||||
if(selection == AppResources.View || string.IsNullOrWhiteSpace(Uri))
|
||||
{
|
||||
var page = new VaultViewLoginPage(login.Id);
|
||||
await Navigation.PushForDeviceAsync(page);
|
||||
}
|
||||
else if(selection == AppResources.Autofill)
|
||||
{
|
||||
if(Uri.StartsWith("http") && _deviceInfoService.Version < 21)
|
||||
{
|
||||
MoreClickedAsync(login);
|
||||
}
|
||||
else
|
||||
{
|
||||
_googleAnalyticsService.TrackExtensionEvent("AutoFilled", Uri.StartsWith("http") ? "Website" : "App");
|
||||
MessagingCenter.Send(Application.Current, "Autofill", login);
|
||||
}
|
||||
}
|
||||
|
||||
((ListView)sender).SelectedItem = null;
|
||||
}
|
||||
|
||||
private async void MoreClickedAsync(VaultListPageModel.Login login)
|
||||
@@ -418,25 +477,20 @@ namespace Bit.App.Pages
|
||||
|
||||
private async void AddLogin()
|
||||
{
|
||||
var page = new VaultAddLoginPage();
|
||||
var page = new VaultAddLoginPage(Uri);
|
||||
await Navigation.PushForDeviceAsync(page);
|
||||
}
|
||||
|
||||
private class AddLoginToolBarItem : ToolbarItem
|
||||
private class AddLoginToolBarItem : ExtendedToolbarItem
|
||||
{
|
||||
private readonly VaultListLoginsPage _page;
|
||||
|
||||
public AddLoginToolBarItem(VaultListLoginsPage page)
|
||||
: base(() => page.AddLogin())
|
||||
{
|
||||
_page = page;
|
||||
Text = AppResources.Add;
|
||||
Icon = "plus";
|
||||
Clicked += ClickedItem;
|
||||
}
|
||||
|
||||
private void ClickedItem(object sender, EventArgs e)
|
||||
{
|
||||
_page.AddLogin();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -444,7 +498,7 @@ namespace Bit.App.Pages
|
||||
{
|
||||
public VaultListHeaderViewCell(VaultListLoginsPage page)
|
||||
{
|
||||
var image = new Image
|
||||
var image = new CachedImage
|
||||
{
|
||||
Source = "folder",
|
||||
WidthRequest = 18,
|
||||
|
||||
@@ -6,6 +6,7 @@ using Bit.App.Models.Page;
|
||||
using Bit.App.Resources;
|
||||
using Xamarin.Forms;
|
||||
using XLabs.Ioc;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Bit.App.Pages
|
||||
{
|
||||
@@ -33,10 +34,12 @@ namespace Bit.App.Pages
|
||||
public LabeledValueCell UsernameCell { get; set; }
|
||||
public LabeledValueCell PasswordCell { get; set; }
|
||||
public LabeledValueCell UriCell { get; set; }
|
||||
private EditLoginToolBarItem EditItem { get; set; }
|
||||
|
||||
private void Init()
|
||||
{
|
||||
ToolbarItems.Add(new EditLoginToolBarItem(this, _loginId));
|
||||
EditItem = new EditLoginToolBarItem(this, _loginId);
|
||||
ToolbarItems.Add(EditItem);
|
||||
if(Device.OS == TargetPlatform.iOS)
|
||||
{
|
||||
ToolbarItems.Add(new DismissModalToolBarItem(this));
|
||||
@@ -121,6 +124,8 @@ namespace Bit.App.Pages
|
||||
|
||||
protected async override void OnAppearing()
|
||||
{
|
||||
EditItem.InitEvents();
|
||||
|
||||
var login = await _loginService.GetByIdAsync(_loginId);
|
||||
if(login == null)
|
||||
{
|
||||
@@ -169,13 +174,18 @@ namespace Bit.App.Pages
|
||||
base.OnAppearing();
|
||||
}
|
||||
|
||||
protected override void OnDisappearing()
|
||||
{
|
||||
EditItem.Dispose();
|
||||
}
|
||||
|
||||
private void Copy(string copyText, string alertLabel)
|
||||
{
|
||||
_clipboardService.CopyToClipboard(copyText);
|
||||
_userDialogs.Toast(string.Format(AppResources.ValueHasBeenCopied, alertLabel));
|
||||
}
|
||||
|
||||
private class EditLoginToolBarItem : ToolbarItem
|
||||
private class EditLoginToolBarItem : ExtendedToolbarItem
|
||||
{
|
||||
private readonly VaultViewLoginPage _page;
|
||||
private readonly string _loginId;
|
||||
@@ -185,10 +195,10 @@ namespace Bit.App.Pages
|
||||
_page = page;
|
||||
_loginId = loginId;
|
||||
Text = AppResources.Edit;
|
||||
Clicked += ClickedItem;
|
||||
ClickAction = async () => await ClickedItem();
|
||||
}
|
||||
|
||||
private async void ClickedItem(object sender, EventArgs e)
|
||||
private async Task ClickedItem()
|
||||
{
|
||||
var page = new VaultEditLoginPage(_loginId);
|
||||
await _page.Navigation.PushForDeviceAsync(page);
|
||||
|
||||
63
src/App/Resources/AppResources.Designer.cs
generated
@@ -142,6 +142,15 @@ namespace Bit.App.Resources {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Auto-fill.
|
||||
/// </summary>
|
||||
public static string Autofill {
|
||||
get {
|
||||
return ResourceManager.GetString("Autofill", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Use the bitwarden accessibility service to auto-fill your logins across apps and the web..
|
||||
/// </summary>
|
||||
@@ -151,6 +160,15 @@ namespace Bit.App.Resources {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Do you want to auto-fill or view this login?.
|
||||
/// </summary>
|
||||
public static string AutofillOrView {
|
||||
get {
|
||||
return ResourceManager.GetString("AutofillOrView", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Auto-fill Service.
|
||||
/// </summary>
|
||||
@@ -250,6 +268,15 @@ namespace Bit.App.Resources {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Are you sure you want to auto-fill this login? It is not a complete match for "{0}"..
|
||||
/// </summary>
|
||||
public static string BitwardenAutofillServiceMatchConfirm {
|
||||
get {
|
||||
return ResourceManager.GetString("BitwardenAutofillServiceMatchConfirm", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to When you see a bitwarden auto-fill notification, you can tap it to launch the auto-fill service..
|
||||
/// </summary>
|
||||
@@ -277,6 +304,15 @@ namespace Bit.App.Resources {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to You are searching for an auto-fill login for "{0}"..
|
||||
/// </summary>
|
||||
public static string BitwardenAutofillServiceSearch {
|
||||
get {
|
||||
return ResourceManager.GetString("BitwardenAutofillServiceSearch", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to 1. On the Android Accessibility Settings screen, touch "bitwarden" under the Services heading..
|
||||
/// </summary>
|
||||
@@ -1204,6 +1240,15 @@ namespace Bit.App.Resources {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Matching Logins.
|
||||
/// </summary>
|
||||
public static string MatchingLogins {
|
||||
get {
|
||||
return ResourceManager.GetString("MatchingLogins", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Minimum Numbers.
|
||||
/// </summary>
|
||||
@@ -1456,6 +1501,15 @@ namespace Bit.App.Resources {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Possible Matching Logins.
|
||||
/// </summary>
|
||||
public static string PossibleMatchingLogins {
|
||||
get {
|
||||
return ResourceManager.GetString("PossibleMatchingLogins", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to bitwarden keeps your vault automatically synced by using push notifications. For the best possible experience, please select "Ok" on the following prompt when asked to enable push notifications..
|
||||
/// </summary>
|
||||
@@ -1528,6 +1582,15 @@ namespace Bit.App.Resources {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Search.
|
||||
/// </summary>
|
||||
public static string Search {
|
||||
get {
|
||||
return ResourceManager.GetString("Search", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Search vault.
|
||||
/// </summary>
|
||||
|
||||
@@ -118,7 +118,7 @@
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="About" xml:space="preserve">
|
||||
<value>A propos</value>
|
||||
<value>À propos</value>
|
||||
</data>
|
||||
<data name="Add" xml:space="preserve">
|
||||
<value>Ajouter</value>
|
||||
@@ -128,7 +128,7 @@
|
||||
<value>Ajouter dossier</value>
|
||||
</data>
|
||||
<data name="AddLogin" xml:space="preserve">
|
||||
<value>Ajouter site</value>
|
||||
<value>Ajouter identifiant</value>
|
||||
<comment>The title for the add login page.</comment>
|
||||
</data>
|
||||
<data name="AnErrorHasOccurred" xml:space="preserve">
|
||||
@@ -209,7 +209,7 @@
|
||||
<value>Ouvrir un ticket dans notre dépôt Github.</value>
|
||||
</data>
|
||||
<data name="FingerprintDirection" xml:space="preserve">
|
||||
<value>Utiliser votre empreinte pour contrôle.</value>
|
||||
<value>Utiliser votre empreinte pour vous identifier.</value>
|
||||
</data>
|
||||
<data name="Folder" xml:space="preserve">
|
||||
<value>Dossier</value>
|
||||
@@ -323,7 +323,7 @@
|
||||
<comment>Reveal a hidden value (password).</comment>
|
||||
</data>
|
||||
<data name="LoginDeleted" xml:space="preserve">
|
||||
<value>Le site a été supprimé.</value>
|
||||
<value>L'identifiant a été supprimé.</value>
|
||||
<comment>Confirmation message after successfully deleting a login.</comment>
|
||||
</data>
|
||||
<data name="LoginNoName" xml:space="preserve">
|
||||
@@ -468,7 +468,7 @@
|
||||
<value>Session actuelle</value>
|
||||
</data>
|
||||
<data name="EditLogin" xml:space="preserve">
|
||||
<value>Modifier Site</value>
|
||||
<value>Modifier Identifiant</value>
|
||||
</data>
|
||||
<data name="EnableAutomaticSyncing" xml:space="preserve">
|
||||
<value>Activer la synchronisation automatique</value>
|
||||
@@ -517,7 +517,7 @@
|
||||
<value>Favori</value>
|
||||
</data>
|
||||
<data name="Fingerprint" xml:space="preserve">
|
||||
<value>Empreintre</value>
|
||||
<value>Empreinte</value>
|
||||
</data>
|
||||
<data name="GeneratePassword" xml:space="preserve">
|
||||
<value>Générer un mot de passe</value>
|
||||
@@ -541,7 +541,7 @@
|
||||
<value>Longueur</value>
|
||||
</data>
|
||||
<data name="Lock" xml:space="preserve">
|
||||
<value>Verrouiler</value>
|
||||
<value>Verrouiller</value>
|
||||
</data>
|
||||
<data name="LockOption15Minutes" xml:space="preserve">
|
||||
<value>15 minutes</value>
|
||||
@@ -566,7 +566,7 @@
|
||||
<comment>Message shown when interacting with the server</comment>
|
||||
</data>
|
||||
<data name="LoginOrCreateNewAccount" xml:space="preserve">
|
||||
<value>Identifiez-vous pour créez un nouveau compte pour accéder à votre coffre sécurisé.</value>
|
||||
<value>Identifiez-vous ou créez un nouveau compte pour accéder à votre coffre sécurisé.</value>
|
||||
</data>
|
||||
<data name="Manage" xml:space="preserve">
|
||||
<value>Gérer</value>
|
||||
@@ -604,19 +604,19 @@
|
||||
<value>Jamais</value>
|
||||
</data>
|
||||
<data name="NewLoginCreated" xml:space="preserve">
|
||||
<value>Nouveau site créé.</value>
|
||||
<value>Nouvel identifiant créé.</value>
|
||||
</data>
|
||||
<data name="NoFavorites" xml:space="preserve">
|
||||
<value>Aucun favori dans votre coffre.</value>
|
||||
</data>
|
||||
<data name="NoLogins" xml:space="preserve">
|
||||
<value>Aucun site dans votre coffre.</value>
|
||||
<value>Aucun identifiant dans votre coffre.</value>
|
||||
</data>
|
||||
<data name="NoLoginsTap" xml:space="preserve">
|
||||
<value>Aucun site dans votre coffre pour ce site web. Appuyez pour en ajouter un.</value>
|
||||
<value>Aucun identifiant dans votre coffre pour ce site web. Appuyez pour en ajouter un.</value>
|
||||
</data>
|
||||
<data name="NoUsernamePasswordConfigured" xml:space="preserve">
|
||||
<value>Ce site n'a ni nom d'utilisateur ni mot de passe de configuré.</value>
|
||||
<value>Cet identifiant n'a ni nom d'utilisateur ni mot de passe de configuré.</value>
|
||||
</data>
|
||||
<data name="OkGotIt" xml:space="preserve">
|
||||
<value>Ok, compris !</value>
|
||||
@@ -647,20 +647,20 @@
|
||||
<value>Nous nous avons envoyé un e-mail avec l'indice de votre mot de passe.</value>
|
||||
</data>
|
||||
<data name="PasswordOverrideAlert" xml:space="preserve">
|
||||
<value>Êtes-vous sûr(e) d'écraser le mot de passe existant ?</value>
|
||||
<value>Êtes-vous sûr(e) de vouloir écraser le mot de passe existant ?</value>
|
||||
</data>
|
||||
<data name="PushNotificationAlert" xml:space="preserve">
|
||||
<value>bitwarden conserve automatiquement votre coffre synchronisé en utilisant des notifications push. Pour la meilleure expérience possible, veuillez choisir "Ok" sur la boîte de dialoque suivante (activation des notifications push).</value>
|
||||
<value>bitwarden conserve automatiquement votre coffre synchronisé en utilisant des notifications push. Pour la meilleure expérience possible, veuillez choisir "Ok" sur la boîte de dialogue suivante (activation des notifications push).</value>
|
||||
<comment>Push notifications for apple products</comment>
|
||||
</data>
|
||||
<data name="RateTheApp" xml:space="preserve">
|
||||
<value>Noter l'application</value>
|
||||
</data>
|
||||
<data name="RateTheAppDescription" xml:space="preserve">
|
||||
<value>Merci de nous aider en postant un commentaire positif !</value>
|
||||
<value>Merci de nous aider en rédigeant un commentaire positif !</value>
|
||||
</data>
|
||||
<data name="RateTheAppDescriptionAppStore" xml:space="preserve">
|
||||
<value>Les notes des magasins d'applications sont réinitialisées à chaque nouvelle version de bitwarden. Merci de nous aider en postant un commentaire positif !</value>
|
||||
<value>Les notes des magasins d'applications sont réinitialisées à chaque nouvelle version de bitwarden. Merci de nous aider en rédigeant un commentaire positif !</value>
|
||||
</data>
|
||||
<data name="RegeneratePassword" xml:space="preserve">
|
||||
<value>Regénérer un mot de passe</value>
|
||||
@@ -690,7 +690,7 @@
|
||||
<value>Informations sur le site</value>
|
||||
</data>
|
||||
<data name="LoginUpdated" xml:space="preserve">
|
||||
<value>Site mis à jour.</value>
|
||||
<value>Identifiant mis à jour.</value>
|
||||
</data>
|
||||
<data name="Submitting" xml:space="preserve">
|
||||
<value>Soumission...</value>
|
||||
@@ -720,7 +720,7 @@
|
||||
<value>L'authentification à double facteurs rend votre compte plus sécurisé en vous demandant la saisie d'un code de sécurité à chaque identification depuis l'application d'authentification. L'identification à double facteurs peut être activée dans le coffre web sur bitwarden.com. Voulez-vous visiter le site web maintenant ?</value>
|
||||
</data>
|
||||
<data name="UnlockWith" xml:space="preserve">
|
||||
<value>Déverrouiler avec {0}</value>
|
||||
<value>Déverrouiller avec {0}</value>
|
||||
</data>
|
||||
<data name="UnlockWithPIN" xml:space="preserve">
|
||||
<value>Déverrouiller avec un code PIN</value>
|
||||
@@ -745,7 +745,7 @@
|
||||
<value>Application d'authentification perdue ?</value>
|
||||
</data>
|
||||
<data name="Logins" xml:space="preserve">
|
||||
<value>Sites</value>
|
||||
<value>Identifiants</value>
|
||||
<comment>Screen title</comment>
|
||||
</data>
|
||||
<data name="ExtensionActivated" xml:space="preserve">
|
||||
@@ -757,4 +757,63 @@
|
||||
<data name="Translations" xml:space="preserve">
|
||||
<value>Traductions</value>
|
||||
</data>
|
||||
<data name="LoginsForUri" xml:space="preserve">
|
||||
<value>Identifiants pour {0}</value>
|
||||
<comment>This is used for the autofill service. ex. "Logins for twitter.com"</comment>
|
||||
</data>
|
||||
<data name="NoLoginsForUri" xml:space="preserve">
|
||||
<value>Il n'y a aucun identifiant dans votre coffre pour {0}.</value>
|
||||
<comment>This is used for the autofill service. ex. "There are no logins in your vault for twitter.com".</comment>
|
||||
</data>
|
||||
<data name="BitwardenAutofillServiceNotification" xml:space="preserve">
|
||||
<value>Lorsque vous voyez une notification de remplissage automatique de la part de bitwarden, vous pouvez la toucher pour démarrer le service de remplissage automatique.</value>
|
||||
</data>
|
||||
<data name="BitwardenAutofillServiceNotificationContent" xml:space="preserve">
|
||||
<value>Touchez cette notification pour remplir automatiquement vos informations de connexion à partir de votre coffre.</value>
|
||||
</data>
|
||||
<data name="BitwardenAutofillServiceOpenSettings" xml:space="preserve">
|
||||
<value>Dans vos paramètres Android, allez dans «Accessibilité»</value>
|
||||
</data>
|
||||
<data name="BitwardenAutofillServiceStep1" xml:space="preserve">
|
||||
<value>1. Dans les paramètres d'accessibilité d'Android, sélectionnez «bitwarden» dans la section «Services».</value>
|
||||
</data>
|
||||
<data name="BitwardenAutofillServiceStep2" xml:space="preserve">
|
||||
<value>2. Activez l'interrupteur et appuyez sur OK pour accepter.</value>
|
||||
</data>
|
||||
<data name="Disabled" xml:space="preserve">
|
||||
<value>Désactivé</value>
|
||||
</data>
|
||||
<data name="Enabled" xml:space="preserve">
|
||||
<value>Activé</value>
|
||||
</data>
|
||||
<data name="Status" xml:space="preserve">
|
||||
<value>État</value>
|
||||
</data>
|
||||
<data name="Beta" xml:space="preserve">
|
||||
<value>Bêta</value>
|
||||
</data>
|
||||
<data name="BitwardenAutofillServiceAlert" xml:space="preserve">
|
||||
<value>L'option de remplissage automatique est la façon la plus simple d'ajouter de nouveaux identifiants à votre coffre. Apprenez-en plus sur l'option de remplissage automatique de bitwarden en vous rendant dans le menu «Outils».</value>
|
||||
</data>
|
||||
<data name="Autofill" xml:space="preserve">
|
||||
<value>Remplissage automatique</value>
|
||||
</data>
|
||||
<data name="AutofillOrView" xml:space="preserve">
|
||||
<value>Voulez vous remplir automatiquement ou voir cet identifiant?</value>
|
||||
</data>
|
||||
<data name="BitwardenAutofillServiceMatchConfirm" xml:space="preserve">
|
||||
<value>Êtes-vous sûr de vouloir remplir automatiquement cet identifiant? Il ne correspond pas complètement à «{0}».</value>
|
||||
</data>
|
||||
<data name="MatchingLogins" xml:space="preserve">
|
||||
<value>Identifiants correspondants</value>
|
||||
</data>
|
||||
<data name="PossibleMatchingLogins" xml:space="preserve">
|
||||
<value>Identifiants pouvant correspondre</value>
|
||||
</data>
|
||||
<data name="Search" xml:space="preserve">
|
||||
<value>Rechercher</value>
|
||||
</data>
|
||||
<data name="BitwardenAutofillServiceSearch" xml:space="preserve">
|
||||
<value>Vous recherchez un identifiant pour remplir automatiquement "{0}".</value>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
@@ -795,4 +795,25 @@
|
||||
<data name="BitwardenAutofillServiceAlert" xml:space="preserve">
|
||||
<value>The easiest way to add new logins to your vault is from the bitwarden Auto-fill Service. Learn more about using the bitwarden Auto-fill Service by navigating to the "Tools" screen.</value>
|
||||
</data>
|
||||
<data name="Autofill" xml:space="preserve">
|
||||
<value>Auto-fill</value>
|
||||
</data>
|
||||
<data name="AutofillOrView" xml:space="preserve">
|
||||
<value>Do you want to auto-fill or view this login?</value>
|
||||
</data>
|
||||
<data name="BitwardenAutofillServiceMatchConfirm" xml:space="preserve">
|
||||
<value>Are you sure you want to auto-fill this login? It is not a complete match for "{0}".</value>
|
||||
</data>
|
||||
<data name="MatchingLogins" xml:space="preserve">
|
||||
<value>Matching Logins</value>
|
||||
</data>
|
||||
<data name="PossibleMatchingLogins" xml:space="preserve">
|
||||
<value>Possible Matching Logins</value>
|
||||
</data>
|
||||
<data name="Search" xml:space="preserve">
|
||||
<value>Search</value>
|
||||
</data>
|
||||
<data name="BitwardenAutofillServiceSearch" xml:space="preserve">
|
||||
<value>You are searching for an auto-fill login for "{0}".</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -830,4 +830,42 @@
|
||||
<data name="Translations" xml:space="preserve">
|
||||
<value>Översättningar</value>
|
||||
</data>
|
||||
<data name="LoginsForUri" xml:space="preserve">
|
||||
<value>Inloggningar för {0}</value>
|
||||
<comment>This is used for the autofill service. ex. "Logins for twitter.com"</comment>
|
||||
</data>
|
||||
<data name="NoLoginsForUri" xml:space="preserve">
|
||||
<value>Det finns inga inloggningar i ditt valv för {0}.</value>
|
||||
<comment>This is used for the autofill service. ex. "There are no logins in your vault for twitter.com".</comment>
|
||||
</data>
|
||||
<data name="BitwardenAutofillServiceNotification" xml:space="preserve">
|
||||
<value>När du ser en bitwarden-notifikation för automatisk ifyllnad så kan du trycka på den för att öppna tjänsten.</value>
|
||||
</data>
|
||||
<data name="BitwardenAutofillServiceNotificationContent" xml:space="preserve">
|
||||
<value>Tryck här för att automatiskt fylla i en inloggning från ditt valv.</value>
|
||||
</data>
|
||||
<data name="BitwardenAutofillServiceOpenSettings" xml:space="preserve">
|
||||
<value>Öppna tillgänglighetsinställningar</value>
|
||||
</data>
|
||||
<data name="BitwardenAutofillServiceStep1" xml:space="preserve">
|
||||
<value>1. I Androids tillgänglighetsinställningar trycker du på "bitwarden" under tjänster.</value>
|
||||
</data>
|
||||
<data name="BitwardenAutofillServiceStep2" xml:space="preserve">
|
||||
<value>2. Växla till på-läget och tryck på OK för att acceptera.</value>
|
||||
</data>
|
||||
<data name="Disabled" xml:space="preserve">
|
||||
<value>Avstängd</value>
|
||||
</data>
|
||||
<data name="Enabled" xml:space="preserve">
|
||||
<value>Aktiverad</value>
|
||||
</data>
|
||||
<data name="Status" xml:space="preserve">
|
||||
<value>Status</value>
|
||||
</data>
|
||||
<data name="Beta" xml:space="preserve">
|
||||
<value>Beta</value>
|
||||
</data>
|
||||
<data name="BitwardenAutofillServiceAlert" xml:space="preserve">
|
||||
<value>Det lättaste sättet att lägga till nya inloggningar till ditt valv är från bitwardens hjälpmedelsservice för automatisk ifyllnad. Lär dig mer om hur man använder bitwardens hjälpmedelsservice för automatisk ifyllnad genom att navigera till "Verktyg"-skärmen.</value>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
@@ -187,6 +187,8 @@ namespace Bit.App.Services
|
||||
Email = null;
|
||||
_cryptoService.Key = null;
|
||||
_settings.Remove(Constants.FirstVaultLoad);
|
||||
_settings.Remove(Constants.PushLastRegistrationDate);
|
||||
_settings.Remove(Constants.Locked);
|
||||
}
|
||||
|
||||
public async Task<ApiResult<TokenResponse>> TokenPostAsync(TokenRequest request)
|
||||
|
||||
@@ -55,11 +55,11 @@ namespace Bit.App.Services
|
||||
return logins;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<Login>> GetAllAsync(string uriString)
|
||||
public async Task<Tuple<IEnumerable<Login>, IEnumerable<Login>>> GetAllAsync(string uriString)
|
||||
{
|
||||
if(string.IsNullOrWhiteSpace(uriString))
|
||||
{
|
||||
return new List<Login>();
|
||||
return null;
|
||||
}
|
||||
|
||||
Uri uri = null;
|
||||
@@ -70,28 +70,53 @@ namespace Bit.App.Services
|
||||
{
|
||||
if(domainName == null)
|
||||
{
|
||||
androidApp = uriString.StartsWith(Constants.AndroidAppProtocol);
|
||||
androidApp = UriIsAndroidApp(uriString);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if(!androidApp && domainName == null)
|
||||
{
|
||||
return new List<Login>();
|
||||
return null;
|
||||
}
|
||||
|
||||
var androidAppWebUriString = WebUriFromAndroidAppUri(uriString);
|
||||
var eqDomains = (await _settingsService.GetEquivalentDomainsAsync()).Select(d => d.ToArray());
|
||||
var matchingDomains = eqDomains
|
||||
.Where(d => (androidApp && Array.IndexOf(d, uriString) >= 0) ||
|
||||
(!androidApp && Array.IndexOf(d, domainName.BaseDomain) >= 0))
|
||||
.SelectMany(d => d).ToList();
|
||||
var matchingDomains = new List<string>();
|
||||
var matchingFuzzyDomains = new List<string>();
|
||||
foreach(var eqDomain in eqDomains)
|
||||
{
|
||||
if(androidApp)
|
||||
{
|
||||
if(Array.IndexOf(eqDomain, uriString) >= 0)
|
||||
{
|
||||
matchingDomains.AddRange(eqDomain.Select(d => d).ToList());
|
||||
}
|
||||
|
||||
if(androidAppWebUriString != null && Array.IndexOf(eqDomain, androidAppWebUriString) >= 0)
|
||||
{
|
||||
matchingFuzzyDomains.AddRange(eqDomain.Select(d => d).ToList());
|
||||
}
|
||||
}
|
||||
else if(Array.IndexOf(eqDomain, domainName.BaseDomain) >= 0)
|
||||
{
|
||||
matchingDomains.AddRange(eqDomain.Select(d => d).ToList());
|
||||
}
|
||||
}
|
||||
|
||||
if(!matchingDomains.Any())
|
||||
{
|
||||
matchingDomains.Add(androidApp ? uriString : domainName.BaseDomain);
|
||||
}
|
||||
|
||||
if(androidApp && androidAppWebUriString != null && !matchingFuzzyDomains.Any())
|
||||
{
|
||||
matchingFuzzyDomains.Add(androidAppWebUriString);
|
||||
}
|
||||
|
||||
var matchingDomainsArray = matchingDomains.ToArray();
|
||||
var matchingFuzzyDomainsArray = matchingFuzzyDomains.ToArray();
|
||||
var matchingLogins = new List<Login>();
|
||||
var matchingFuzzyLogins = new List<Login>();
|
||||
var logins = await _loginRepository.GetAllByUserIdAsync(_authService.UserId);
|
||||
foreach(var login in logins)
|
||||
{
|
||||
@@ -106,11 +131,21 @@ namespace Bit.App.Services
|
||||
continue;
|
||||
}
|
||||
|
||||
if(androidApp && Array.IndexOf(matchingDomainsArray, loginUriString) >= 0)
|
||||
if(Array.IndexOf(matchingDomainsArray, loginUriString) >= 0)
|
||||
{
|
||||
matchingLogins.Add(new Login(login));
|
||||
continue;
|
||||
}
|
||||
else if(androidApp && Array.IndexOf(matchingFuzzyDomainsArray, loginUriString) >= 0)
|
||||
{
|
||||
matchingFuzzyLogins.Add(new Login(login));
|
||||
continue;
|
||||
}
|
||||
else if(!androidApp && Array.IndexOf(matchingDomainsArray, WebUriFromAndroidAppUri(loginUriString)) >= 0)
|
||||
{
|
||||
matchingFuzzyLogins.Add(new Login(login));
|
||||
continue;
|
||||
}
|
||||
|
||||
Uri loginUri;
|
||||
DomainName loginDomainName;
|
||||
@@ -124,9 +159,13 @@ namespace Bit.App.Services
|
||||
{
|
||||
matchingLogins.Add(new Login(login));
|
||||
}
|
||||
else if(androidApp && Array.IndexOf(matchingFuzzyDomainsArray, loginDomainName.BaseDomain) >= 0)
|
||||
{
|
||||
matchingFuzzyLogins.Add(new Login(login));
|
||||
}
|
||||
}
|
||||
|
||||
return matchingLogins;
|
||||
return new Tuple<IEnumerable<Login>, IEnumerable<Login>>(matchingLogins, matchingFuzzyLogins);
|
||||
}
|
||||
|
||||
public async Task<ApiResult<LoginResponse>> SaveAsync(Login login)
|
||||
@@ -180,5 +219,26 @@ namespace Bit.App.Services
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
private string WebUriFromAndroidAppUri(string androidAppUriString)
|
||||
{
|
||||
if(!UriIsAndroidApp(androidAppUriString))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var androidUriParts = androidAppUriString.Replace(Constants.AndroidAppProtocol, string.Empty).Split('.');
|
||||
if(androidUriParts.Length >= 2)
|
||||
{
|
||||
return string.Join(".", androidUriParts[1], androidUriParts[0]);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private bool UriIsAndroidApp(string uriString)
|
||||
{
|
||||
return uriString.StartsWith(Constants.AndroidAppProtocol);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,35 +73,27 @@ namespace Bit.App.Services
|
||||
return false;
|
||||
}
|
||||
|
||||
switch(cipher.Result.Type)
|
||||
try
|
||||
{
|
||||
case Enums.CipherType.Folder:
|
||||
var folderData = new FolderData(cipher.Result, _authService.UserId);
|
||||
var existingLocalFolder = _folderRepository.GetByIdAsync(id);
|
||||
if(existingLocalFolder == null)
|
||||
{
|
||||
await _folderRepository.InsertAsync(folderData).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
await _folderRepository.UpdateAsync(folderData).ConfigureAwait(false);
|
||||
}
|
||||
break;
|
||||
case Enums.CipherType.Login:
|
||||
var loginData = new LoginData(cipher.Result, _authService.UserId);
|
||||
var existingLocalLogin = _loginRepository.GetByIdAsync(id);
|
||||
if(existingLocalLogin == null)
|
||||
{
|
||||
await _loginRepository.InsertAsync(loginData).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
await _loginRepository.UpdateAsync(loginData).ConfigureAwait(false);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
SyncCompleted(false);
|
||||
return false;
|
||||
switch(cipher.Result.Type)
|
||||
{
|
||||
case Enums.CipherType.Folder:
|
||||
var folderData = new FolderData(cipher.Result, _authService.UserId);
|
||||
await _folderRepository.UpsertAsync(folderData).ConfigureAwait(false);
|
||||
break;
|
||||
case Enums.CipherType.Login:
|
||||
var loginData = new LoginData(cipher.Result, _authService.UserId);
|
||||
await _loginRepository.UpsertAsync(loginData).ConfigureAwait(false);
|
||||
break;
|
||||
default:
|
||||
SyncCompleted(false);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch(SQLite.SQLiteException)
|
||||
{
|
||||
SyncCompleted(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
SyncCompleted(true);
|
||||
@@ -117,9 +109,17 @@ namespace Bit.App.Services
|
||||
|
||||
SyncStarted();
|
||||
|
||||
await _folderRepository.DeleteWithLoginUpdateAsync(id, revisionDate).ConfigureAwait(false);
|
||||
SyncCompleted(true);
|
||||
return true;
|
||||
try
|
||||
{
|
||||
await _folderRepository.DeleteWithLoginUpdateAsync(id, revisionDate).ConfigureAwait(false);
|
||||
SyncCompleted(true);
|
||||
return true;
|
||||
}
|
||||
catch(SQLite.SQLiteException)
|
||||
{
|
||||
SyncCompleted(false);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> SyncDeleteLoginAsync(string id)
|
||||
@@ -131,9 +131,17 @@ namespace Bit.App.Services
|
||||
|
||||
SyncStarted();
|
||||
|
||||
await _loginRepository.DeleteAsync(id).ConfigureAwait(false);
|
||||
SyncCompleted(true);
|
||||
return true;
|
||||
try
|
||||
{
|
||||
await _loginRepository.DeleteAsync(id).ConfigureAwait(false);
|
||||
SyncCompleted(true);
|
||||
return true;
|
||||
}
|
||||
catch(SQLite.SQLiteException)
|
||||
{
|
||||
SyncCompleted(false);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> FullSyncAsync(TimeSpan syncThreshold, bool forceSync = false)
|
||||
@@ -188,8 +196,8 @@ namespace Bit.App.Services
|
||||
var logins = ciphers.Result.Data.Where(c => c.Type == Enums.CipherType.Login).ToDictionary(s => s.Id);
|
||||
var folders = ciphers.Result.Data.Where(c => c.Type == Enums.CipherType.Folder).ToDictionary(f => f.Id);
|
||||
|
||||
var loginTask = SyncLoginsAsync(logins, true);
|
||||
var folderTask = SyncFoldersAsync(folders, true);
|
||||
var loginTask = SyncLoginsAsync(logins);
|
||||
var folderTask = SyncFoldersAsync(folders);
|
||||
var domainsTask = SyncDomainsAsync(domains.Result);
|
||||
await Task.WhenAll(loginTask, folderTask, domainsTask).ConfigureAwait(false);
|
||||
|
||||
@@ -228,7 +236,7 @@ namespace Bit.App.Services
|
||||
return false;
|
||||
}
|
||||
|
||||
private async Task SyncFoldersAsync(IDictionary<string, CipherResponse> serverFolders, bool deleteMissing)
|
||||
private async Task SyncFoldersAsync(IDictionary<string, CipherResponse> serverFolders)
|
||||
{
|
||||
if(!_authService.IsAuthenticated)
|
||||
{
|
||||
@@ -245,31 +253,25 @@ namespace Bit.App.Services
|
||||
return;
|
||||
}
|
||||
|
||||
var existingLocalFolder = localFolders.ContainsKey(serverFolder.Key) ? localFolders[serverFolder.Key] : null;
|
||||
if(existingLocalFolder == null)
|
||||
try
|
||||
{
|
||||
var data = new FolderData(serverFolder.Value, _authService.UserId);
|
||||
await _folderRepository.InsertAsync(data).ConfigureAwait(false);
|
||||
await _folderRepository.UpsertAsync(data).ConfigureAwait(false);
|
||||
}
|
||||
else if(existingLocalFolder.RevisionDateTime != serverFolder.Value.RevisionDate)
|
||||
{
|
||||
var data = new FolderData(serverFolder.Value, _authService.UserId);
|
||||
await _folderRepository.UpdateAsync(data).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
if(!deleteMissing)
|
||||
{
|
||||
return;
|
||||
catch(SQLite.SQLiteException) { }
|
||||
}
|
||||
|
||||
foreach(var folder in localFolders.Where(localFolder => !serverFolders.ContainsKey(localFolder.Key)))
|
||||
{
|
||||
await _folderRepository.DeleteAsync(folder.Value.Id).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
await _folderRepository.DeleteAsync(folder.Value.Id).ConfigureAwait(false);
|
||||
}
|
||||
catch(SQLite.SQLiteException) { }
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SyncLoginsAsync(IDictionary<string, CipherResponse> serverLogins, bool deleteMissing)
|
||||
private async Task SyncLoginsAsync(IDictionary<string, CipherResponse> serverLogins)
|
||||
{
|
||||
if(!_authService.IsAuthenticated)
|
||||
{
|
||||
@@ -286,27 +288,21 @@ namespace Bit.App.Services
|
||||
return;
|
||||
}
|
||||
|
||||
var existingLocalLogin = localLogins.ContainsKey(serverLogin.Key) ? localLogins[serverLogin.Key] : null;
|
||||
if(existingLocalLogin == null)
|
||||
try
|
||||
{
|
||||
var data = new LoginData(serverLogin.Value, _authService.UserId);
|
||||
await _loginRepository.InsertAsync(data).ConfigureAwait(false);
|
||||
await _loginRepository.UpsertAsync(data).ConfigureAwait(false);
|
||||
}
|
||||
else if(existingLocalLogin.RevisionDateTime != serverLogin.Value.RevisionDate)
|
||||
{
|
||||
var data = new LoginData(serverLogin.Value, _authService.UserId);
|
||||
await _loginRepository.UpdateAsync(data).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
if(!deleteMissing)
|
||||
{
|
||||
return;
|
||||
catch(SQLite.SQLiteException) { }
|
||||
}
|
||||
|
||||
foreach(var login in localLogins.Where(localLogin => !serverLogins.ContainsKey(localLogin.Key)))
|
||||
{
|
||||
await _loginRepository.DeleteAsync(login.Value.Id).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
await _loginRepository.DeleteAsync(login.Value.Id).ConfigureAwait(false);
|
||||
}
|
||||
catch(SQLite.SQLiteException) { }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -323,11 +319,15 @@ namespace Bit.App.Services
|
||||
eqDomains.AddRange(serverDomains.GlobalEquivalentDomains.Select(d => d.Domains));
|
||||
}
|
||||
|
||||
await _settingsRepository.UpsertAsync(new SettingsData
|
||||
try
|
||||
{
|
||||
Id = _authService.UserId,
|
||||
EquivalentDomains = JsonConvert.SerializeObject(eqDomains)
|
||||
});
|
||||
await _settingsRepository.UpsertAsync(new SettingsData
|
||||
{
|
||||
Id = _authService.UserId,
|
||||
EquivalentDomains = JsonConvert.SerializeObject(eqDomains)
|
||||
});
|
||||
}
|
||||
catch(SQLite.SQLiteException) { }
|
||||
}
|
||||
|
||||
private void SyncStarted()
|
||||
|
||||
@@ -21,6 +21,8 @@
|
||||
<package id="Xam.Plugin.Connectivity" version="2.2.12" targetFramework="portable45-net45+win8+wpa81" />
|
||||
<package id="Xam.Plugin.PushNotification" version="1.2.4" targetFramework="portable45-net45+win8+wpa81" developmentDependency="true" />
|
||||
<package id="Xam.Plugins.Settings" version="2.5.1.0" targetFramework="portable45-net45+win8+wpa81" />
|
||||
<package id="Xamarin.FFImageLoading" version="2.2.8" targetFramework="portable45-net45+win8+wpa81" />
|
||||
<package id="Xamarin.FFImageLoading.Forms" version="2.2.8" targetFramework="portable45-net45+win8+wpa81" />
|
||||
<package id="Xamarin.Forms" version="2.3.3.180" targetFramework="portable45-net45+win8+wpa81" />
|
||||
<package id="XLabs.IoC" version="2.0.5782" targetFramework="portable45-net45+win8+wpa81" />
|
||||
</packages>
|
||||
@@ -93,6 +93,7 @@
|
||||
<HintPath>..\..\packages\XLabs.IoC.2.0.5782\lib\portable-net45+netcore45+wp8+MonoAndroid1+MonoTouch1\XLabs.Ioc.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System.Net.Http" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Folder Include="Resources\" />
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>XPC!</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.3.0</string>
|
||||
<string>1.4.1</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
|
||||
@@ -115,11 +115,10 @@ namespace Bit.iOS.Extension
|
||||
{
|
||||
var loginService = Resolver.Resolve<ILoginService>();
|
||||
var logins = await loginService.GetAllAsync(_context.UrlString);
|
||||
var loginModels = logins;
|
||||
_tableItems = logins.Select(s => new LoginViewModel(s))
|
||||
_tableItems = logins?.Item1.Select(s => new LoginViewModel(s))
|
||||
.OrderBy(s => s.Name)
|
||||
.ThenBy(s => s.Username)
|
||||
.ToList();
|
||||
.ToList() ?? new List<LoginViewModel>();
|
||||
}
|
||||
|
||||
public IEnumerable<LoginViewModel> TableItems { get; set; }
|
||||
|
||||
@@ -25,6 +25,7 @@ using Bit.App.Pages;
|
||||
using HockeyApp.iOS;
|
||||
using Bit.iOS.Core;
|
||||
using Google.Analytics;
|
||||
using FFImageLoading.Forms.Touch;
|
||||
|
||||
namespace Bit.iOS
|
||||
{
|
||||
@@ -288,6 +289,7 @@ namespace Bit.iOS
|
||||
|
||||
CrossPushNotification.Initialize(container.Resolve<IPushNotificationListener>());
|
||||
container.RegisterInstance(CrossPushNotification.Current, new ContainerControlledLifetimeManager());
|
||||
CachedImageRenderer.Init();
|
||||
|
||||
Resolver.SetResolver(new UnityResolver(container));
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.3.0</string>
|
||||
<string>1.4.1</string>
|
||||
<key>UIMainStoryboardFile~ipad</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||
|
||||
@@ -272,6 +272,22 @@
|
||||
<HintPath>..\..\packages\BTProgressHUD.1.2.0.3\lib\Xamarin.iOS10\BTProgressHUD.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="FFImageLoading, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Xamarin.FFImageLoading.2.2.8\lib\Xamarin.iOS10\FFImageLoading.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="FFImageLoading.Forms, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Xamarin.FFImageLoading.Forms.2.2.8\lib\Xamarin.iOS10\FFImageLoading.Forms.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="FFImageLoading.Forms.Touch, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Xamarin.FFImageLoading.Forms.2.2.8\lib\Xamarin.iOS10\FFImageLoading.Forms.Touch.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="FFImageLoading.Platform, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Xamarin.FFImageLoading.2.2.8\lib\Xamarin.iOS10\FFImageLoading.Platform.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Google.Analytics, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Xamarin.Google.iOS.Analytics.3.17.0\lib\Xamarin.iOS10\Google.Analytics.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
@@ -374,6 +390,10 @@
|
||||
<HintPath>..\..\packages\Validation.2.3.7\lib\dotnet\Validation.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="WebP.Touch, Version=1.0.5923.35383, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\WebP.Touch.1.0.2\lib\Xamarin.iOS10\WebP.Touch.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Xamarin.Forms.Core, Version=2.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Xamarin.Forms.2.3.3.180\lib\Xamarin.iOS10\Xamarin.Forms.Core.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
|
||||
@@ -21,10 +21,13 @@
|
||||
<package id="SQLitePCLRaw.provider.sqlite3.ios_unified" version="1.1.2" targetFramework="xamarinios10" />
|
||||
<package id="Unity" version="3.5.1405-prerelease" targetFramework="xamarinios10" />
|
||||
<package id="Validation" version="2.3.7" targetFramework="xamarinios10" />
|
||||
<package id="WebP.Touch" version="1.0.2" targetFramework="xamarinios10" />
|
||||
<package id="Xam.Plugin.Connectivity" version="2.2.12" targetFramework="xamarinios10" />
|
||||
<package id="Xam.Plugin.PushNotification" version="1.2.4" targetFramework="xamarinios10" developmentDependency="true" />
|
||||
<package id="Xam.Plugins.Settings" version="2.5.1.0" targetFramework="xamarinios10" />
|
||||
<package id="Xamarin.Build.Download" version="0.3.0" targetFramework="xamarinios10" />
|
||||
<package id="Xamarin.FFImageLoading" version="2.2.8" targetFramework="xamarinios10" />
|
||||
<package id="Xamarin.FFImageLoading.Forms" version="2.2.8" targetFramework="xamarinios10" />
|
||||
<package id="Xamarin.Forms" version="2.3.3.180" targetFramework="xamarinios10" />
|
||||
<package id="Xamarin.Google.iOS.Analytics" version="3.17.0" targetFramework="xamarinios10" />
|
||||
<package id="XLabs.IoC" version="2.0.5782" targetFramework="xamarinios10" />
|
||||
|
||||
28
store/amazon/en/CAPTIONS.md
Normal file
@@ -0,0 +1,28 @@
|
||||
# Feature Graphic
|
||||
|
||||
A secure and free password manager for all of your devices
|
||||
|
||||
# Screenshot 1
|
||||
|
||||
Manage all your logins and passwords from a secure vault
|
||||
|
||||
# Screenshot 2
|
||||
|
||||
Automatically generate strong, random, and secure passwords
|
||||
|
||||
# Screenshot 3
|
||||
|
||||
Protect your vault with fingerprint, PIN code, or master password
|
||||
|
||||
# Screenshot 4
|
||||
|
||||
Quickly auto-fill logins from within your web browser and other apps
|
||||
|
||||
# Screenshot 5
|
||||
|
||||
Sync and access your vault from multiple devices
|
||||
|
||||
- Phone
|
||||
- Tablet
|
||||
- Desktop
|
||||
- Web
|
||||
19
store/amazon/en/COPY.md
Normal file
@@ -0,0 +1,19 @@
|
||||
# Title
|
||||
|
||||
bitwarden Free Password Manager
|
||||
|
||||
# Description
|
||||
|
||||
bitwarden is the easiest and safest way to store all of your logins and passwords while conveniently keeping them synced between all of your devices.
|
||||
|
||||
Password theft is a serious problem. The websites and apps that you use are under attack every day. Security breaches occur and your passwords are stolen. When you reuse the same passwords across apps and websites hackers can easily access your email, bank, and other important accounts.
|
||||
|
||||
Security experts recommend that you use a different, randomly generated password for every account that you create. But how do you manage all those passwords? bitwarden makes it easy for you to create, store, and access your passwords.
|
||||
|
||||
bitwarden stores all of your logins in an encrypted vault that syncs across all of your devices. Since it's fully encrypted before it ever leaves your device, only you have access to your data. Not even the team at bitwarden can read your data, even if we wanted to. Your data is sealed with AES-256 bit encryption, salted hashing, and PBKDF2 SHA-256.
|
||||
|
||||
# Product Feature Bullets
|
||||
|
||||
- Sync your logins and passwords across all your browsers and devices
|
||||
- Fill in logins on the web and in other apps automatically
|
||||
- Automatically generate strong, secure passwords for your logins
|
||||
BIN
store/amazon/en/assets/feature-graphic.png
Normal file
|
After Width: | Height: | Size: 83 KiB |
BIN
store/amazon/en/assets/large-icon.png
Normal file
|
After Width: | Height: | Size: 9.3 KiB |
BIN
store/amazon/en/assets/small-icon.png
Normal file
|
After Width: | Height: | Size: 2.9 KiB |
BIN
store/amazon/en/screenshots/Nexus 5x - Screenshot 1.png
Normal file
|
After Width: | Height: | Size: 441 KiB |
BIN
store/amazon/en/screenshots/Nexus 5x - Screenshot 2.png
Normal file
|
After Width: | Height: | Size: 374 KiB |
BIN
store/amazon/en/screenshots/Nexus 5x - Screenshot 3.png
Normal file
|
After Width: | Height: | Size: 325 KiB |
BIN
store/amazon/en/screenshots/Nexus 5x - Screenshot 4.png
Normal file
|
After Width: | Height: | Size: 443 KiB |
BIN
store/amazon/en/screenshots/Nexus 5x - Screenshot 5.png
Normal file
|
After Width: | Height: | Size: 402 KiB |
@@ -16,7 +16,7 @@ Protect your vault with fingerprint, PIN code, or master password
|
||||
|
||||
# Screenshot 4
|
||||
|
||||
Quick and easy access whenever you need your logins and passwords
|
||||
Quickly auto-fill logins from within your web browser and other apps
|
||||
|
||||
# Screenshot 5
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
# Title
|
||||
# Title (max 30 characters)
|
||||
|
||||
bitwarden Password Manager
|
||||
|
||||
# Short Description
|
||||
# Short Description (max 80 characters)
|
||||
|
||||
bitwarden is a login and password manager that helps keep you safe while online.
|
||||
|
||||
# Full Description
|
||||
# Full Description (max 4000 characters)
|
||||
|
||||
bitwarden is the easiest and safest way to store all of your logins and passwords while conveniently keeping them synced between all of your devices.
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 578 KiB After Width: | Height: | Size: 571 KiB |
|
Before Width: | Height: | Size: 559 KiB After Width: | Height: | Size: 499 KiB |
|
Before Width: | Height: | Size: 413 KiB After Width: | Height: | Size: 406 KiB |
|
Before Width: | Height: | Size: 560 KiB After Width: | Height: | Size: 587 KiB |
|
Before Width: | Height: | Size: 397 KiB After Width: | Height: | Size: 397 KiB |
|
Before Width: | Height: | Size: 345 KiB After Width: | Height: | Size: 315 KiB |
|
Before Width: | Height: | Size: 283 KiB After Width: | Height: | Size: 283 KiB |