1
0
mirror of https://github.com/bitwarden/mobile synced 2025-12-05 23:53:33 +00:00

Compare commits

..

45 Commits

Author SHA1 Message Date
Joseph Flinn
2cdf7595d3 Version bump 2.14.2 (#1622)
(cherry picked from commit bf1aa7c4eb)
2021-10-29 12:50:46 -07:00
Jake Fink
f8e7a6263f fix for bug stopping vault timeout to never (#1618)
- use nullable int on settings page and in vault service
2021-10-29 11:29:44 -04:00
Matt Portune
19658da65a remove numeric restriction on cc number field (#1617) 2021-10-29 11:29:25 -04:00
Joseph Flinn
a2bb490fc4 Version bump 2.14.1 (#1614)
(cherry picked from commit 5e4365084b)
2021-10-28 06:51:02 -07:00
Joseph Flinn
97c21cd666 adding the missing flag for sed in the fdroid build (#1613)
(cherry picked from commit ea5e4aafa3)
2021-10-28 06:49:58 -07:00
Joseph Flinn
2318f3b357 Version Bump 2.14.0 (#1610)
(cherry picked from commit 0d3f819e93)
2021-10-27 08:40:51 -07:00
github-actions[bot]
5a8918cf0d Autosync the updated translations (#1609)
Co-authored-by: github-actions <>
(cherry picked from commit 3760e0f9f4)
2021-10-27 07:53:44 -07:00
Joseph Flinn
38c5f19da1 resolving a weird conflict on rc 2021-10-22 13:26:12 -07:00
Joseph Flinn
b9c7a63d55 Change release branch contraint (#1598)
* removing the master branch release ci code execution

* updating some verbiage

(cherry picked from commit bfb050a6f9)
2021-10-22 13:18:15 -07:00
Matt Portune
56664ae981 utilize iOS safe area in UI (#1597) 2021-10-22 10:06:50 -04:00
Matt Portune
9a83877cbb Autofill support for upcoming changes to google search (#1596) 2021-10-21 10:59:33 -04:00
Kyle Spearrin
35e801aa3e New Crowdin updates (#1583)
* New translations AppResources.resx (Romanian)

* New translations AppResources.resx (Norwegian Nynorsk)

* New translations AppResources.resx (Chinese Traditional)

* New translations AppResources.resx (Vietnamese)

* New translations AppResources.resx (Portuguese, Brazilian)

* New translations AppResources.resx (Indonesian)

* New translations AppResources.resx (Persian)

* New translations AppResources.resx (Tamil)

* New translations AppResources.resx (Bengali)

* New translations AppResources.resx (Thai)

* New translations AppResources.resx (Croatian)

* New translations AppResources.resx (Estonian)

* New translations AppResources.resx (Ukrainian)

* New translations AppResources.resx (Latvian)

* New translations AppResources.resx (Azerbaijani)

* New translations AppResources.resx (Hindi)

* New translations AppResources.resx (English, United Kingdom)

* New translations AppResources.resx (Filipino)

* New translations AppResources.resx (Malayalam)

* New translations AppResources.resx (Bosnian)

* New translations AppResources.resx (Sinhala)

* New translations AppResources.resx (Kannada)

* New translations AppResources.resx (Norwegian Bokmal)

* New translations AppResources.resx (Chinese Simplified)

* New translations AppResources.resx (Turkish)

* New translations AppResources.resx (French)

* New translations AppResources.resx (Hebrew)

* New translations AppResources.resx (Spanish)

* New translations AppResources.resx (Afrikaans)

* New translations AppResources.resx (Belarusian)

* New translations AppResources.resx (Bulgarian)

* New translations AppResources.resx (Catalan)

* New translations AppResources.resx (Czech)

* New translations AppResources.resx (Danish)

* New translations AppResources.resx (German)

* New translations AppResources.resx (Greek)

* New translations AppResources.resx (Finnish)

* New translations AppResources.resx (Hungarian)

* New translations AppResources.resx (Swedish)

* New translations AppResources.resx (Italian)

* New translations AppResources.resx (Japanese)

* New translations AppResources.resx (Korean)

* New translations AppResources.resx (Dutch)

* New translations AppResources.resx (Polish)

* New translations AppResources.resx (Portuguese)

* New translations AppResources.resx (Russian)

* New translations AppResources.resx (Slovak)

* New translations AppResources.resx (Slovenian)

* New translations AppResources.resx (Serbian (Cyrillic))

* New translations AppResources.resx (English, India)

* New translations copy.resx (English, United Kingdom)

* New translations copy.resx (Chinese Simplified)

* New translations copy.resx (English, India)

* New translations copy.resx (English, United Kingdom)

* New translations copy.resx (Chinese Simplified)

* New translations copy.resx (English, India)
2021-10-19 12:30:14 -04:00
github-actions[bot]
dcc840b598 Autosync the updated translations (#1582)
Co-authored-by: github-actions <>
2021-10-19 12:30:02 -04:00
Chad Scharf
d6cd83349a Revert "New Crowdin updates (#1583)"
This reverts commit 166c5eb779.

Revert crowdin merge (wrong order)
2021-10-19 12:29:05 -04:00
Chad Scharf
82098ab6a1 Revert "Autosync the updated translations (#1582)"
This reverts commit ac75b14ec4.

Revert resx merge from crowdin
2021-10-19 12:28:24 -04:00
github-actions[bot]
ac75b14ec4 Autosync the updated translations (#1582)
Co-authored-by: github-actions <>
2021-10-19 12:21:39 -04:00
Kyle Spearrin
166c5eb779 New Crowdin updates (#1583)
* New translations AppResources.resx (Romanian)

* New translations AppResources.resx (Norwegian Nynorsk)

* New translations AppResources.resx (Chinese Traditional)

* New translations AppResources.resx (Vietnamese)

* New translations AppResources.resx (Portuguese, Brazilian)

* New translations AppResources.resx (Indonesian)

* New translations AppResources.resx (Persian)

* New translations AppResources.resx (Tamil)

* New translations AppResources.resx (Bengali)

* New translations AppResources.resx (Thai)

* New translations AppResources.resx (Croatian)

* New translations AppResources.resx (Estonian)

* New translations AppResources.resx (Ukrainian)

* New translations AppResources.resx (Latvian)

* New translations AppResources.resx (Azerbaijani)

* New translations AppResources.resx (Hindi)

* New translations AppResources.resx (English, United Kingdom)

* New translations AppResources.resx (Filipino)

* New translations AppResources.resx (Malayalam)

* New translations AppResources.resx (Bosnian)

* New translations AppResources.resx (Sinhala)

* New translations AppResources.resx (Kannada)

* New translations AppResources.resx (Norwegian Bokmal)

* New translations AppResources.resx (Chinese Simplified)

* New translations AppResources.resx (Turkish)

* New translations AppResources.resx (French)

* New translations AppResources.resx (Hebrew)

* New translations AppResources.resx (Spanish)

* New translations AppResources.resx (Afrikaans)

* New translations AppResources.resx (Belarusian)

* New translations AppResources.resx (Bulgarian)

* New translations AppResources.resx (Catalan)

* New translations AppResources.resx (Czech)

* New translations AppResources.resx (Danish)

* New translations AppResources.resx (German)

* New translations AppResources.resx (Greek)

* New translations AppResources.resx (Finnish)

* New translations AppResources.resx (Hungarian)

* New translations AppResources.resx (Swedish)

* New translations AppResources.resx (Italian)

* New translations AppResources.resx (Japanese)

* New translations AppResources.resx (Korean)

* New translations AppResources.resx (Dutch)

* New translations AppResources.resx (Polish)

* New translations AppResources.resx (Portuguese)

* New translations AppResources.resx (Russian)

* New translations AppResources.resx (Slovak)

* New translations AppResources.resx (Slovenian)

* New translations AppResources.resx (Serbian (Cyrillic))

* New translations AppResources.resx (English, India)

* New translations copy.resx (English, United Kingdom)

* New translations copy.resx (Chinese Simplified)

* New translations copy.resx (English, India)

* New translations copy.resx (English, United Kingdom)

* New translations copy.resx (Chinese Simplified)

* New translations copy.resx (English, India)
2021-10-19 12:17:38 -04:00
Matt Portune
6f3c5be598 fix gap in background color application resulting in flashing during transitions, part 2 (#1595) 2021-10-18 09:58:27 -04:00
Matt Portune
8c4f5c3378 fix for crash when checking for running accessibility service without activity (#1591) 2021-10-18 09:58:02 -04:00
Matt Portune
c2657cf93e fix for crash when terminating app (#1589) 2021-10-16 07:57:17 -04:00
Matt Portune
1b93131a1f fix gap in theme application resulting in flashing during transitions (#1588) 2021-10-15 15:47:14 -04:00
Jake Fink
b9fc72d436 check password for null before setting cursor position (#1586) 2021-10-15 11:03:04 -04:00
Matt Portune
5d02ed78f0 support for new btnReturnText in mobile webauthn connector (#1576)
* support for new btnReturnText in mobile webauthn connector

* added header text for connector localization
2021-10-15 10:32:54 -04:00
Matt Portune
ab80c3d4c7 fix for crash on startup when policies are non-existent (#1579) 2021-10-14 13:55:09 -04:00
Jake Fink
6a6764394d fix for Captcha crashing on account creation (#1580) 2021-10-14 13:44:26 -04:00
Oscar Hinton
4509feb16a MaximumVaultTimeout policy fixes (#1573)
(cherry picked from commit c9ce7256e5)
2021-10-14 19:41:31 +02:00
Matt Portune
8bc7ed916c Fix for Android 5.x crash caused by new switch styling (#1575) 2021-10-13 12:31:05 -04:00
Vincent Salucci
34aba0e168 [Reset Password] BUG - Update local policies for enforcement (#1565)
* [Reset Password] BUG - Update local policies for enforcement

* Updated with blocking sync

* add the stuff I forgot to tell vsalucci about

* removed the lies I fed vsalucci

* remove unnecessary import

Co-authored-by: Matt Portune <mportune@bitwarden.com>
2021-10-08 16:51:16 -05:00
Matt Portune
9e9e2e12d8 fix padding between sync button and last sync timestamp (#1571) 2021-10-08 15:56:36 -04:00
Jake Fink
3eec349038 Fix crash from SelectableLabelRenderer (#1570) 2021-10-08 13:14:23 -04:00
Jake Fink
69650a1ab5 Hide label if no subtitle for Cipher (#1569) 2021-10-08 12:09:26 -04:00
Jake Fink
faac7ebe5e Set cursor position after toggle password visibility (#1568)
- Lock page
- Login page
- Register page
2021-10-08 12:09:08 -04:00
Jake Fink
d3734c63fc Add SelectableLabel Custom Renderer to allow copy of note text (#1564)
* Add SelectableLabel Custom Renderer to allow copy of note text
- Remove SelectableLabelEffect

* Remove editor changes from text custom field
2021-10-08 08:49:15 -04:00
Matt Portune
4aad34cd75 Dynamic theme switching and visual tweaks (#1556)
* Dynamic theme switching and visual tweaks

* update action runner to use macos-11 for iOS 15 support

* additional tweaks

* refinements

* refinements

* formatting and tweaks
2021-10-08 08:47:40 -04:00
Matt Gibson
73eb3c2c1e Split collections permissions (#1558)
* Split collections permissions

* Remove old permissions

There is no backwards compatibility issue since these permissions are unused.

* Remove unused imports
2021-10-05 11:12:55 -05:00
Jake Fink
6109091ec0 filter Ciphers to exclude deleted items (#1559) 2021-10-04 13:46:45 -04:00
Jake Fink
c0783cd162 Exclude deleted items from folder counts (#1555) 2021-10-04 08:33:23 -04:00
Vince Grassia
a9a4fa56c1 Pipeline fixes (#1549)
* Update path to fix 'Deploy to App Store' step in Build workflow
* Change `sed` to save changes to file by using `-i` flag
2021-09-24 17:02:34 -04:00
Vincent Salucci
271e6b3d92 [Reset Password v1] Update Temp Password (#1492)
* [Reset Password v1] Update Temp Password

* fixed order of operations for reset temp password flow

* Refactored bool with auth result

* Finished removal of temp password flow from set password

* iOS extension support plus extension bugfixes

Co-authored-by: addison <addisonbeck1@gmail.com>
Co-authored-by: Matt Portune <mportune@bitwarden.com>
2021-09-24 13:14:26 -05:00
Vince Grassia
750faf8a83 Update pipeline to new model (#1472)
* Update pipelines to new model
2021-09-24 13:50:54 -04:00
Thomas Rittson
716e52f6ff Move policy checks inside PolicyService (#1533)
* Move policy checks inside PolicyService

* Remove leftover code

* Remove duplicate code

* Reorder code for consistency
2021-09-24 09:51:02 +10:00
Matt Portune
010a4210f4 bump version for testflight (#1547) 2021-09-23 14:24:52 -04:00
Matt Portune
8d23bc89e8 Update XF to remove AndroidX dependency hack (#1544)
* update forms and other libs

* change async pattern
2021-09-23 13:11:51 -04:00
Oscar Hinton
f2857397f0 Disable Private Vault Export Policy (#1546) 2021-09-23 18:30:20 +02:00
Oscar Hinton
6023374fbe Vault Timeout Policy (#1530) 2021-09-23 15:42:38 +02:00
217 changed files with 11434 additions and 2172 deletions

View File

@@ -1,13 +0,0 @@
param (
[Parameter(Mandatory=$true)]
[string] $configuration
)
$rootPath = $env:GITHUB_WORKSPACE;
$androidPath = $($rootPath + "/src/Android/Android.csproj");
Write-Output "########################################"
Write-Output "##### Build $configuration Configuration"
Write-Output "########################################"
msbuild "$($androidPath)" "/p:Configuration=$configuration"

View File

@@ -1,69 +0,0 @@
$rootPath = $env:GITHUB_WORKSPACE;
$androidPath = $($rootPath + "/src/Android/Android.csproj");
$appPath = $($rootPath + "/src/App/App.csproj");
$androidManifest = $($rootPath + "/src/Android/Properties/AndroidManifest.xml");
Write-Output "########################################"
Write-Output "##### Clean Android and App"
Write-Output "########################################"
msbuild "$($androidPath)" "/t:Clean" "/p:Configuration=FDroid"
msbuild "$($appPath)" "/t:Clean" "/p:Configuration=FDroid"
Write-Output "########################################"
Write-Output "##### Backup project files"
Write-Output "########################################"
Copy-Item $androidManifest $($androidManifest + ".original");
Copy-Item $androidPath $($androidPath + ".original");
Copy-Item $appPath $($appPath + ".original");
Write-Output "########################################"
Write-Output "##### Cleanup Android Manifest"
Write-Output "########################################"
$xml=New-Object XML;
$xml.Load($androidManifest);
$nsAndroid=New-Object System.Xml.XmlNamespaceManager($xml.NameTable);
$nsAndroid.AddNamespace("android", "http://schemas.android.com/apk/res/android");
$xml.Save($androidManifest);
Write-Output "########################################"
Write-Output "##### Uninstall from Android.csproj"
Write-Output "########################################"
$xml=New-Object XML;
$xml.Load($androidPath);
$ns=New-Object System.Xml.XmlNamespaceManager($xml.NameTable);
$ns.AddNamespace("ns", $xml.DocumentElement.NamespaceURI);
$firebaseNode=$xml.SelectSingleNode(`
"/ns:Project/ns:ItemGroup/ns:PackageReference[@Include='Xamarin.Firebase.Messaging']", $ns);
$firebaseNode.ParentNode.RemoveChild($firebaseNode);
$daggerNode=$xml.SelectSingleNode(`
"/ns:Project/ns:ItemGroup/ns:PackageReference[@Include='Xamarin.Google.Dagger']", $ns);
$daggerNode.ParentNode.RemoveChild($daggerNode);
$safetyNetNode=$xml.SelectSingleNode(`
"/ns:Project/ns:ItemGroup/ns:PackageReference[@Include='Xamarin.GooglePlayServices.SafetyNet']", $ns);
$safetyNetNode.ParentNode.RemoveChild($safetyNetNode);
$xml.Save($androidPath);
Write-Output "########################################"
Write-Output "##### Uninstall from App.csproj"
Write-Output "########################################"
$xml=New-Object XML;
$xml.Load($appPath);
$appCenterNode=$xml.SelectSingleNode("/Project/ItemGroup/PackageReference[@Include='Microsoft.AppCenter.Crashes']");
$appCenterNode.ParentNode.RemoveChild($appCenterNode);
$xml.Save($appPath);

View File

@@ -1,24 +0,0 @@
#!/usr/bin/env bash
cd $GITHUB_WORKSPACE
mkdir dist
cp CNAME ./dist
cd store
chmod 600 fdroid/config.py fdroid/keystore.jks
mkdir -p temp/fdroid
TEMP_DIR="$GITHUB_WORKSPACE/store/temp/fdroid"
cd fdroid
echo "keypass=\"$FDROID_STORE_KEYSTORE_PASSWORD\"" >>config.py
echo "keystorepass=\"$FDROID_STORE_KEYSTORE_PASSWORD\"" >>config.py
echo "local_copy_dir=\"$TEMP_DIR\"" >>config.py
mkdir -p repo
curl -Lo repo/com.x8bit.bitwarden-fdroid.apk \
https://github.com/bitwarden/mobile/releases/download/$RELEASE_TAG_NAME/com.x8bit.bitwarden-fdroid.apk
fdroid update
fdroid server update
cd ..
rm -rf temp/fdroid/archive
mv -v temp/fdroid ../dist
cd fdroid
cp index.html btn.png qr.png ../../dist/fdroid
cd $GITHUB_WORKSPACE

View File

@@ -1,22 +0,0 @@
$rootPath = $env:GITHUB_WORKSPACE;
$decryptSecretPath = $($rootPath + "/.github/scripts/decrypt-secret.ps1");
$appKeystorePlayFilename = "app_play-keystore.jks";
$appKeystorePlayPath = $($rootPath + "/src/Android/$appKeystorePlayFilename");
$appKeystoreUploadFilename = "app_upload-keystore.jks";
$appKeystoreUploadPath = $($rootPath + "/src/Android/$appKeystoreUploadFilename");
$appKeystoreFdroidFilename = "app_fdroid-keystore.jks";
$appKeystoreFdroidPath = $($rootPath + "/src/Android/$appKeystoreFdroidFilename");
$googleServicesFilename = "google-services.json";
$googleServicesPath = $($rootPath + "/src/Android/$googleServicesFilename");
Invoke-Expression `
"& `"$decryptSecretPath`" -filename $($appKeystorePlayFilename + ".gpg") -output $($appKeystorePlayPath)"
Invoke-Expression `
"& `"$decryptSecretPath`" -filename $($appKeystoreUploadFilename + ".gpg") -output $($appKeystoreUploadPath)"
Invoke-Expression `
"& `"$decryptSecretPath`" -filename $($appKeystoreFdroidFilename + ".gpg") -output $($appKeystoreFdroidPath)"
Invoke-Expression `
"& `"$decryptSecretPath`" -filename $($googleServicesFilename + ".gpg") -output $($googleServicesPath)"
Invoke-Expression "& `"$decryptSecretPath`" -filename play_creds.json.gpg"

View File

@@ -1,9 +0,0 @@
$rootPath = $env:GITHUB_WORKSPACE;
$homePath = Resolve-Path "~" | Select-Object -ExpandProperty Path;
$publisherPath = $($rootPath + "/store/google/Publisher/bin/Release/netcoreapp2.0/Publisher.dll");
$credsPath = $($homePath + "/secrets/play_creds.json");
$aabPath = $($rootPath + "/com.x8bit.bitwarden.aab");
$track = "internal";
dotnet $publisherPath $credsPath $aabPath $track

View File

@@ -1,16 +0,0 @@
$rootPath = $env:GITHUB_WORKSPACE;
$buildNumber = 3000 + [int]$env:GITHUB_RUN_NUMBER;
Write-Output "########################################"
Write-Output "##### Setting Version Code $buildNumber"
Write-Output "########################################"
$androidManifest = $($rootPath + "/src/Android/Properties/AndroidManifest.xml");
$xml=New-Object XML;
$xml.Load($androidManifest);
$node=$xml.SelectNodes("/manifest");
$node.SetAttribute("android:versionCode", [string]$buildNumber);
$xml.Save($androidManifest);

View File

@@ -1,23 +0,0 @@
$rootPath = $env:GITHUB_WORKSPACE;
$androidPath = $($rootPath + "/src/Android/Android.csproj");
$appKeystoreFdroidFilename = "app_fdroid-keystore.jks";
Write-Output "########################################"
Write-Output "##### Sign FDroid Configuration"
Write-Output "########################################"
msbuild "$($androidPath)" "/t:SignAndroidPackage" "/p:Configuration=FDroid" "/p:AndroidKeyStore=true" `
"/p:AndroidSigningKeyAlias=bitwarden" "/p:AndroidSigningKeyPass=$($env:FDROID_KEYSTORE_PASSWORD)" `
"/p:AndroidSigningKeyStore=$($appKeystoreFdroidFilename)" `
"/p:AndroidSigningStorePass=$($env:FDROID_KEYSTORE_PASSWORD)" "/v:quiet"
Write-Output "########################################"
Write-Output "##### Copy FDroid apk to project root"
Write-Output "########################################"
$signedApkPath = $($rootPath + "/src/Android/bin/FDroid/com.x8bit.bitwarden-Signed.apk");
$signedApkDestPath = $($rootPath + "/com.x8bit.bitwarden-fdroid.apk");
Copy-Item $signedApkPath $signedApkDestPath

View File

@@ -1,42 +0,0 @@
$rootPath = $env:GITHUB_WORKSPACE;
$androidPath = $($rootPath + "/src/Android/Android.csproj");
$appKeystorePlayFilename = "app_play-keystore.jks";
$appKeystoreUploadFilename = "app_upload-keystore.jks";
Write-Output "########################################"
Write-Output "##### Sign Google Play Bundle Release Configuration"
Write-Output "########################################"
msbuild "$($androidPath)" "/t:SignAndroidPackage" "/p:Configuration=Release" "/p:AndroidKeyStore=true" `
"/p:AndroidSigningKeyAlias=upload" "/p:AndroidSigningKeyPass=$($env:UPLOAD_KEYSTORE_PASSWORD)" `
"/p:AndroidSigningKeyStore=$($appKeystoreUploadFilename)" `
"/p:AndroidSigningStorePass=$($env:UPLOAD_KEYSTORE_PASSWORD)" "/p:AndroidPackageFormat=aab" "/v:quiet"
Write-Output "########################################"
Write-Output "##### Copy Google Play Bundle to project root"
Write-Output "########################################"
$signedAabPath = $($rootPath + "/src/Android/bin/Release/com.x8bit.bitwarden-Signed.aab");
$signedAabDestPath = $($rootPath + "/com.x8bit.bitwarden.aab");
Copy-Item $signedAabPath $signedAabDestPath
Write-Output "########################################"
Write-Output "##### Sign APK Release Configuration"
Write-Output "########################################"
msbuild "$($androidPath)" "/t:SignAndroidPackage" "/p:Configuration=Release" "/p:AndroidKeyStore=true" `
"/p:AndroidSigningKeyAlias=bitwarden" "/p:AndroidSigningKeyPass=$($env:PLAY_KEYSTORE_PASSWORD)" `
"/p:AndroidSigningKeyStore=$($appKeystorePlayFilename)" `
"/p:AndroidSigningStorePass=$($env:PLAY_KEYSTORE_PASSWORD)" "/v:quiet"
Write-Output "########################################"
Write-Output "##### Copy Release APK to project root"
Write-Output "########################################"
$signedApkPath = $($rootPath + "/src/Android/bin/Release/com.x8bit.bitwarden-Signed.apk");
$signedApkDestPath = $($rootPath + "/com.x8bit.bitwarden.apk");
Copy-Item $signedApkPath $signedApkDestPath

View File

@@ -1,29 +0,0 @@
param (
[Parameter(Mandatory=$true)]
[string] $filename,
[string] $output
)
$homePath = Resolve-Path "~" | Select-Object -ExpandProperty Path
$rootPath = $env:GITHUB_WORKSPACE
$secretInputPath = $rootPath + "/.github/secrets"
$input = $secretInputPath + "/" + $filename
$passphrase = $env:DECRYPT_FILE_PASSWORD
$secretOutputPath = $homePath + "/secrets"
if ([string]::IsNullOrEmpty($output)) {
if ($filename.EndsWith(".gpg")) {
$output = $secretOutputPath + "/" + $filename.TrimEnd(".gpg")
} else {
$output = $secretOutputPath + "/" + $filename + ".plaintext"
}
}
if (!(Test-Path -Path $secretOutputPath))
{
New-Item -ItemType Directory -Path $secretOutputPath
}
gpg --quiet --batch --yes --decrypt --passphrase="$passphrase" --output $output $input

View File

@@ -1,29 +0,0 @@
param (
[Parameter(Mandatory=$true)]
[string] $configuration,
[string] $platform = "iPhone",
[switch] $archive
)
$rootPath = $env:GITHUB_WORKSPACE;
$iosPath = $($rootPath + "/src/iOS/iOS.csproj");
if ($archive)
{
Write-Output "########################################"
Write-Output "##### Archive $configuration Configuration for $platform Platform"
Write-Output "########################################"
msbuild "$($iosPath)" "/p:Platform=$platform" "/p:Configuration=$configuration" `
"/p:ArchiveOnBuild=true" "/t:`"Build`""
Write-Output "########################################"
Write-Output "##### Done"
Write-Output "########################################"
ls ~/Library/Developer/Xcode/Archives
} else
{
Write-Output "########################################"
Write-Output "##### Build $configuration Configuration for $platform Platform"
Write-Output "########################################"
msbuild "$($iosPath)" "/p:Platform=$platform" "/p:Configuration=$configuration" "/t:`"Build`""
}

View File

@@ -1,9 +0,0 @@
$rootPath = $env:GITHUB_WORKSPACE;
$decryptSecretPath = $($rootPath + "/.github/scripts/decrypt-secret.ps1");
Invoke-Expression "& `"$decryptSecretPath`" -filename bitwarden-mobile-key.p12.gpg"
Invoke-Expression "& `"$decryptSecretPath`" -filename iphone-distribution-cert.p12.gpg"
Invoke-Expression "& `"$decryptSecretPath`" -filename dist_autofill.mobileprovision.gpg"
Invoke-Expression "& `"$decryptSecretPath`" -filename dist_bitwarden.mobileprovision.gpg"
Invoke-Expression "& `"$decryptSecretPath`" -filename dist_extension.mobileprovision.gpg"

View File

@@ -1,5 +0,0 @@
$rootPath = $env:GITHUB_WORKSPACE;
$ipaPath = "$rootPath/bitwarden-export/Bitwarden.ipa"
xcrun altool --upload-app --type ios --file "$ipaPath" `
--username "$env:APPLE_ID_USERNAME" --password "$env:APPLE_ID_PASSWORD"

View File

@@ -1,13 +0,0 @@
param (
[Parameter(Mandatory=$true)]
[string] $method
)
$rootPath = $env:GITHUB_WORKSPACE;
$homePath = Resolve-Path "~" | Select-Object -ExpandProperty Path
$exportOptionsPath = "$rootPath/.github/resources/export-options-$method.plist";
$archivePath = "$homePath/Library/Developer/Xcode/Archives/*/*.xcarchive";
$exportPath = "$rootPath/bitwarden-export";
xcodebuild -exportArchive -archivePath $archivePath -exportPath $exportPath -exportOptionsPlist $exportOptionsPath

View File

@@ -1,26 +0,0 @@
$rootPath = $env:GITHUB_WORKSPACE;
$buildNumber = 100 + [int]$env:GITHUB_RUN_NUMBER;
$bitwardenInfo = $($rootPath + "/src/iOS/Info.plist");
$extensionInfo = $($rootPath + "/src/iOS.Extension/Info.plist");
$autofillInfo = $($rootPath + "/src/iOS.Autofill/Info.plist");
Write-Output "########################################"
Write-Output "##### Setting CFBundleVersion $buildNumber"
Write-Output "########################################"
function Update-Version($file) {
$xml=New-Object XML;
$xml.Load($file);
Select-Xml -xml $xml -XPath "//dict/key[. = 'CFBundleVersion']/following-sibling::string[1]" |
%{
$_.Node.InnerXml = $buildNumber
}
$xml.Save($file);
}
Update-Version $bitwardenInfo
Update-Version $extensionInfo
Update-Version $autofillInfo

View File

@@ -1,13 +0,0 @@
$homePath = Resolve-Path "~" | Select-Object -ExpandProperty Path;
$secretsPath = $homePath + "/secrets"
$mobileKeyPath = $($secretsPath + "/bitwarden-mobile-key.p12");
$distCertPath = $($secretsPath + "/iphone-distribution-cert.p12");
security create-keychain -p $env:KEYCHAIN_PASSWORD build.keychain
security default-keychain -s build.keychain
security unlock-keychain -p $env:KEYCHAIN_PASSWORD build.keychain
security set-keychain-settings -lut 1200 build.keychain
security import $mobileKeyPath -k build.keychain -P $env:MOBILE_KEY_PASSWORD -T /usr/bin/codesign -T /usr/bin/security
security import $distCertPath -k build.keychain -P $env:DIST_CERT_PASSWORD -T /usr/bin/codesign -T /usr/bin/security
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k $env:KEYCHAIN_PASSWORD build.keychain

View File

@@ -1,21 +0,0 @@
$homePath = Resolve-Path "~" | Select-Object -ExpandProperty Path;
$secretsPath = $homePath + "/secrets"
$autofillProfilePath = $($secretsPath + "/dist_autofill.mobileprovision");
$bitwardenProfilePath = $($secretsPath + "/dist_bitwarden.mobileprovision");
$extensionProfilePath = $($secretsPath + "/dist_extension.mobileprovision");
$profilesDirPath = "~/Library/MobileDevice/Provisioning Profiles"
if (!(Test-Path -Path $profilesDirPath))
{
New-Item -ItemType Directory -Path $profilesDirPath
}
$autofill_uuid = grep UUID -A1 -a $autofillProfilePath | grep -io "[-A-F0-9]\{36\}"
Copy-Item $autofillProfilePath -destination "$profilesDirPath/$autofill_uuid.mobileprovision"
$bitwarden_uuid = grep UUID -A1 -a $bitwardenProfilePath | grep -io "[-A-F0-9]\{36\}"
Copy-Item $bitwardenProfilePath -destination "$profilesDirPath/$bitwarden_uuid.mobileprovision"
$extension_uuid = grep UUID -A1 -a $extensionProfilePath | grep -io "[-A-F0-9]\{36\}"
Copy-Item $extensionProfilePath -destination "$profilesDirPath/$extension_uuid.mobileprovision"

View File

@@ -6,21 +6,16 @@ on:
branches-ignore:
- 'l10n_master'
- 'gh-pages'
release:
types:
- published
jobs:
cloc:
name: CLOC
runs-on: ubuntu-latest
runs-on: ubuntu-20.04
steps:
- name: Checkout repo
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
- name: Set up cloc
- name: Set up CLOC
run: |
sudo apt-get update
sudo apt-get -y install cloc
@@ -28,13 +23,41 @@ jobs:
- name: Print lines of code
run: cloc --vcs git --exclude-dir Resources,store,test,Properties --include-lang C#,XAML
setup:
name: Setup
runs-on: ubuntu-20.04
outputs:
rc_branch_exists: ${{ steps.branch-check.outputs.rc_branch_exists }}
release_branch_exists: ${{ steps.branch-check.outputs.release_branch_exists }}
steps:
- name: Checkout repo
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
- name: Check if special branches exist
id: branch-check
run: |
if [[ $(git ls-remote --heads origin rc) ]]; then
echo "::set-output name=rc_branch_exists::1"
else
echo "::set-output name=rc_branch_exists::0"
fi
if [[ $(git ls-remote --heads origin release) ]]; then
echo "::set-output name=release_branch_exists::1"
else
echo "::set-output name=release_branch_exists::0"
fi
shell: bash
android:
name: Android
runs-on: windows-latest
runs-on: windows-2019
needs: setup
steps:
- name: Set up MSBuild
uses: microsoft/setup-msbuild@c26a08ba26249b81327e26f6ef381897b6a8754d
uses: microsoft/setup-msbuild@c26a08ba26249b81327e26f6ef381897b6a8754d # v1
- name: Print environment
run: |
@@ -43,179 +66,294 @@ jobs:
dotnet --info
echo "GitHub ref: $GITHUB_REF"
echo "GitHub event: $GITHUB_EVENT"
env:
GITHUB_REF: ${{ github.ref }}
GITHUB_EVENT: ${{ github.event_name }}
- name: Checkout repo
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
- name: Decrypt secrets
if: github.ref == 'refs/heads/master' || github.event_name == 'release'
run: ./.github/scripts/android/decrypt-secrets.ps1
shell: pwsh
env:
DECRYPT_FILE_PASSWORD: ${{ secrets.DECRYPT_FILE_PASSWORD }}
run: |
mkdir -p ~/secrets
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
--output ./src/Android/app_play-keystore.jks ./.github/secrets/app_play-keystore.jks.gpg
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
--output ./src/Android/app_upload-keystore.jks ./.github/secrets/app_upload-keystore.jks.gpg
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
--output ./src/Android/google-services.json ./.github/secrets/google-services.json.gpg
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
--output $HOME/secrets/play_creds.json ./.github/secrets/play_creds.json.gpg
shell: bash
- name: Increment version
if: github.ref == 'refs/heads/master' || github.event_name == 'release'
run: ./.github/scripts/android/increment-version.ps1
shell: pwsh
run: |
BUILD_NUMBER=$((3000 + $GITHUB_RUN_NUMBER))
echo "########################################"
echo "##### Setting Version Code $BUILD_NUMBER"
echo "########################################"
sed -i "s/android:versionCode=\"1\"/android:versionCode=\"$BUILD_NUMBER\"/" \
./src/Android/Properties/AndroidManifest.xml
shell: bash
- name: Restore packages
run: nuget restore
- name: Run Core Tests
- name: Run Core tests
run: dotnet test test/Core.Test/Core.Test.csproj
- name: Build Play Store publisher
run: dotnet build ./store/google/Publisher/Publisher.csproj -p:Configuration=Release
- name: Build for Play Store
run: ./.github/scripts/android/build.ps1 -configuration Release
run: |
$configuration = "Release";
Write-Output "########################################"
Write-Output "##### Build $configuration Configuration"
Write-Output "########################################"
msbuild "$($env:GITHUB_WORKSPACE + "/src/Android/Android.csproj")" "/p:Configuration=$configuration"
shell: pwsh
- name: Sign for Play Store
if: github.ref == 'refs/heads/master' || github.event_name == 'release'
run: ./.github/scripts/android/sign-play.ps1
shell: pwsh
env:
PLAY_KEYSTORE_PASSWORD: ${{ secrets.PLAY_KEYSTORE_PASSWORD }}
UPLOAD_KEYSTORE_PASSWORD: ${{ secrets.UPLOAD_KEYSTORE_PASSWORD }}
run: |
$androidPath = $($env:GITHUB_WORKSPACE + "/src/Android/Android.csproj");
Write-Output "########################################"
Write-Output "##### Sign Google Play Bundle Release Configuration"
Write-Output "########################################"
msbuild "$($androidPath)" "/t:SignAndroidPackage" "/p:Configuration=Release" "/p:AndroidKeyStore=true" `
"/p:AndroidSigningKeyAlias=upload" "/p:AndroidSigningKeyPass=$($env:UPLOAD_KEYSTORE_PASSWORD)" `
"/p:AndroidSigningKeyStore=$("app_upload-keystore.jks")" `
"/p:AndroidSigningStorePass=$($env:UPLOAD_KEYSTORE_PASSWORD)" "/p:AndroidPackageFormat=aab" "/v:quiet"
Write-Output "########################################"
Write-Output "##### Copy Google Play Bundle to project root"
Write-Output "########################################"
$signedAabPath = $($env:GITHUB_WORKSPACE + "/src/Android/bin/Release/com.x8bit.bitwarden-Signed.aab");
$signedAabDestPath = $($env:GITHUB_WORKSPACE + "/com.x8bit.bitwarden.aab");
Copy-Item $signedAabPath $signedAabDestPath
Write-Output "########################################"
Write-Output "##### Sign APK Release Configuration"
Write-Output "########################################"
msbuild "$($androidPath)" "/t:SignAndroidPackage" "/p:Configuration=Release" "/p:AndroidKeyStore=true" `
"/p:AndroidSigningKeyAlias=bitwarden" "/p:AndroidSigningKeyPass=$($env:PLAY_KEYSTORE_PASSWORD)" `
"/p:AndroidSigningKeyStore=$("app_play-keystore.jks")" `
"/p:AndroidSigningStorePass=$($env:PLAY_KEYSTORE_PASSWORD)" "/v:quiet"
Write-Output "########################################"
Write-Output "##### Copy Release APK to project root"
Write-Output "########################################"
$signedApkPath = $($env:GITHUB_WORKSPACE + "/src/Android/bin/Release/com.x8bit.bitwarden-Signed.apk");
$signedApkDestPath = $($env:GITHUB_WORKSPACE + "/com.x8bit.bitwarden.apk");
Copy-Item $signedApkPath $signedApkDestPath
shell: pwsh
- name: Upload Play Store .aab artifact
if: github.ref == 'refs/heads/master' || github.event_name == 'release'
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700 # v2.2.4
with:
name: com.x8bit.bitwarden.aab
path: ./com.x8bit.bitwarden.aab
if-no-files-found: error
- name: Upload Play Store .apk artifact
if: github.ref == 'refs/heads/master' || github.event_name == 'release'
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700 # v2.2.4
with:
name: com.x8bit.bitwarden.apk
path: ./com.x8bit.bitwarden.apk
if-no-files-found: error
- name: Deploy to Play Store
if: |
(github.ref == 'refs/heads/master'
&& needs.setup.outputs.rc_branch_exists == 0
&& needs.setup.outputs.release_branch_exists == 0)
|| (github.ref == 'refs/heads/rc' && needs.setup.outputs.release_branch_exists == 0)
|| github.ref == 'refs/heads/release'
run: |
PUBLISHER_PATH="$GITHUB_WORKSPACE/store/google/Publisher/bin/Release/netcoreapp2.0/Publisher.dll"
CREDS_PATH="$HOME/secrets/play_creds.json"
AAB_PATH="$GITHUB_WORKSPACE/com.x8bit.bitwarden.aab"
TRACK="internal"
dotnet $PUBLISHER_PATH $CREDS_PATH $AAB_PATH $TRACK
shell: bash
f-droid:
name: F-Droid Build
runs-on: windows-2019
steps:
- name: Set up MSBuild
uses: microsoft/setup-msbuild@c26a08ba26249b81327e26f6ef381897b6a8754d # v1
- name: Print environment
run: |
nuget help | grep Version
msbuild -version
dotnet --info
echo "GitHub ref: $GITHUB_REF"
echo "GitHub event: $GITHUB_EVENT"
- name: Checkout repo
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
- name: Decrypt secrets
env:
DECRYPT_FILE_PASSWORD: ${{ secrets.DECRYPT_FILE_PASSWORD }}
run: |
mkdir -p ~/secrets
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
--output ./src/Android/app_fdroid-keystore.jks ./.github/secrets/app_fdroid-keystore.jks.gpg
shell: bash
- name: Increment version
run: |
BUILD_NUMBER=$((3000 + $GITHUB_RUN_NUMBER))
echo "########################################"
echo "##### Setting Version Code $BUILD_NUMBER"
echo "########################################"
sed -i "s/android:versionCode=\"1\"/android:versionCode=\"$BUILD_NUMBER\"/" \
./src/Android/Properties/AndroidManifest.xml
shell: bash
- name: Clean for F-Droid
run: ./.github/scripts/android/clean-fdroid.ps1
run: |
$androidPath = $($env:GITHUB_WORKSPACE + "/src/Android/Android.csproj");
$appPath = $($env:GITHUB_WORKSPACE + "/src/App/App.csproj");
$androidManifest = $($env:GITHUB_WORKSPACE + "/src/Android/Properties/AndroidManifest.xml");
Write-Output "########################################"
Write-Output "##### Clean Android and App"
Write-Output "########################################"
msbuild "$($androidPath)" "/t:Clean" "/p:Configuration=FDroid"
msbuild "$($appPath)" "/t:Clean" "/p:Configuration=FDroid"
Write-Output "########################################"
Write-Output "##### Backup project files"
Write-Output "########################################"
Copy-Item $androidManifest $($androidManifest + ".original");
Copy-Item $androidPath $($androidPath + ".original");
Copy-Item $appPath $($appPath + ".original");
Write-Output "########################################"
Write-Output "##### Cleanup Android Manifest"
Write-Output "########################################"
$xml=New-Object XML;
$xml.Load($androidManifest);
$nsAndroid=New-Object System.Xml.XmlNamespaceManager($xml.NameTable);
$nsAndroid.AddNamespace("android", "http://schemas.android.com/apk/res/android");
$xml.Save($androidManifest);
Write-Output "########################################"
Write-Output "##### Uninstall from Android.csproj"
Write-Output "########################################"
$xml=New-Object XML;
$xml.Load($androidPath);
$ns=New-Object System.Xml.XmlNamespaceManager($xml.NameTable);
$ns.AddNamespace("ns", $xml.DocumentElement.NamespaceURI);
$firebaseNode=$xml.SelectSingleNode(`
"/ns:Project/ns:ItemGroup/ns:PackageReference[@Include='Xamarin.Firebase.Messaging']", $ns);
$firebaseNode.ParentNode.RemoveChild($firebaseNode);
$daggerNode=$xml.SelectSingleNode(`
"/ns:Project/ns:ItemGroup/ns:PackageReference[@Include='Xamarin.Google.Dagger']", $ns);
$daggerNode.ParentNode.RemoveChild($daggerNode);
$safetyNetNode=$xml.SelectSingleNode(`
"/ns:Project/ns:ItemGroup/ns:PackageReference[@Include='Xamarin.GooglePlayServices.SafetyNet']", $ns);
$safetyNetNode.ParentNode.RemoveChild($safetyNetNode);
$xml.Save($androidPath);
Write-Output "########################################"
Write-Output "##### Uninstall from App.csproj"
Write-Output "########################################"
$xml=New-Object XML;
$xml.Load($appPath);
$appCenterNode=$xml.SelectSingleNode("/Project/ItemGroup/PackageReference[@Include='Microsoft.AppCenter.Crashes']");
$appCenterNode.ParentNode.RemoveChild($appCenterNode);
$xml.Save($appPath);
shell: pwsh
- name: Restore packages
run: nuget restore
- name: Build for F-Droid
run: ./.github/scripts/android/build.ps1 -configuration FDroid
run: |
$configuration = "FDroid";
Write-Output "########################################"
Write-Output "##### Build $configuration Configuration"
Write-Output "########################################"
msbuild "$($env:GITHUB_WORKSPACE + "/src/Android/Android.csproj")" "/p:Configuration=$configuration"
shell: pwsh
- name: Sign for F-Droid
if: github.ref == 'refs/heads/master' || github.event_name == 'release'
run: ./.github/scripts/android/sign-fdroid.ps1
shell: pwsh
env:
FDROID_KEYSTORE_PASSWORD: ${{ secrets.FDROID_KEYSTORE_PASSWORD }}
run: |
Write-Output "########################################"
Write-Output "##### Sign FDroid Configuration"
Write-Output "########################################"
msbuild "$($env:GITHUB_WORKSPACE + "/src/Android/Android.csproj")" `
"/t:SignAndroidPackage" "/p:Configuration=FDroid" "/p:AndroidKeyStore=true" `
"/p:AndroidSigningKeyAlias=bitwarden" "/p:AndroidSigningKeyPass=$($env:FDROID_KEYSTORE_PASSWORD)" `
"/p:AndroidSigningKeyStore=$("app_fdroid-keystore.jks")" `
"/p:AndroidSigningStorePass=$($env:FDROID_KEYSTORE_PASSWORD)" "/v:quiet"
Write-Output "########################################"
Write-Output "##### Copy FDroid apk to project root"
Write-Output "########################################"
$signedApkPath = $($env:GITHUB_WORKSPACE + "/src/Android/bin/FDroid/com.x8bit.bitwarden-Signed.apk");
$signedApkDestPath = $($env:GITHUB_WORKSPACE + "/com.x8bit.bitwarden-fdroid.apk");
Copy-Item $signedApkPath $signedApkDestPath
shell: pwsh
- name: Upload F-Droid .apk artifact
if: github.ref == 'refs/heads/master' || github.event_name == 'release'
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700 # v2.2.4
with:
name: com.x8bit.bitwarden-fdroid.apk
path: ./com.x8bit.bitwarden-fdroid.apk
if-no-files-found: error
- name: Deploy to Play Store
if: github.ref == 'refs/heads/master' || github.event_name == 'release'
run: ./.github/scripts/android/deploy-play.ps1
shell: pwsh
- name: Upload release assets
if: github.event_name == 'release'
run: |
hub release edit `
-a ./com.x8bit.bitwarden.aab `
-a ./com.x8bit.bitwarden.apk `
-a ./com.x8bit.bitwarden-fdroid.apk `
-m "Version $($env:RELEASE_TAG_NAME.TrimStart('v'))" `
$env:RELEASE_TAG_NAME
shell: pwsh
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
RELEASE_TAG_NAME: ${{ github.event.release.tag_name }}
android-ubuntu:
name: Android Ubuntu
runs-on: ubuntu-latest
needs: android
steps:
- name: Set up Node
if: github.event_name == 'release'
uses: actions/setup-node@46071b5c7a2e0c34e49c3cb8a0e792e86e18d5ea
with:
node-version: '10.x'
- name: Set up F-Droid server
if: github.event_name == 'release'
run: |
sudo apt-get -qq update
sudo apt-get -qqy install --no-install-recommends fdroidserver wget
- name: Set up git credentials
if: github.event_name == 'release'
env:
ACCESS_TOKEN: ${{ secrets.ACCESS_TOKEN }}
run: |
git config --global credential.helper store
echo "https://${ACCESS_TOKEN}:x-oauth-basic@github.com" >> ~/.git-credentials
git config --global user.email "ci@bitwarden.com"
git config --global user.name "Bitwarden CI"
- name: Print environment
if: github.event_name == 'release'
run: |
node --version
npm --version
git --version
Write-Output "GitHub ref: $env:GITHUB_REF"
Write-Output "GitHub event: $env:GITHUB_EVENT"
shell: pwsh
env:
GITHUB_REF: ${{ github.ref }}
GITHUB_EVENT: ${{ github.event_name }}
- name: Checkout repo
if: github.event_name == 'release'
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
- name: Install Node dependencies
if: github.event_name == 'release'
run: npm install
- name: Decrypt secrets
if: github.event_name == 'release'
run: |
./.github/scripts/decrypt-secret.ps1 -filename store_fdroid-keystore.jks.gpg `
-output ./store/fdroid/keystore.jks
shell: pwsh
env:
DECRYPT_FILE_PASSWORD: ${{ secrets.DECRYPT_FILE_PASSWORD }}
- name: Compile for F-Droid Store
if: github.event_name == 'release'
run: |
sudo chmod +x ./.github/scripts/android/compile-fdroid.sh
./.github/scripts/android/compile-fdroid.sh
env:
FDROID_STORE_KEYSTORE_PASSWORD: ${{ secrets.FDROID_STORE_KEYSTORE_PASSWORD }}
RELEASE_TAG_NAME: ${{ github.event.release.tag_name }}
- name: Deploy to gh-pages
if: github.event_name == 'release'
run: npm run deploy
ios:
name: Apple iOS
runs-on: macos-latest
runs-on: macos-11
needs: setup
steps:
- name: Print environment
run: |
@@ -224,77 +362,125 @@ jobs:
dotnet --info
echo "GitHub ref: $GITHUB_REF"
echo "GitHub event: $GITHUB_EVENT"
env:
GITHUB_REF: ${{ github.ref }}
GITHUB_EVENT: ${{ github.event_name }}
- name: Checkout repo
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
- name: Decrypt secrets
run: ./.github/scripts/ios/decrypt-secrets.ps1
shell: pwsh
env:
DECRYPT_FILE_PASSWORD: ${{ secrets.DECRYPT_FILE_PASSWORD }}
run: |
mkdir -p ~/secrets
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
--output $HOME/secrets/bitwarden-mobile-key.p12 ./.github/secrets/bitwarden-mobile-key.p12.gpg
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
--output $HOME/secrets/iphone-distribution-cert.p12 ./.github/secrets/iphone-distribution-cert.p12.gpg
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
--output $HOME/secrets/dist_autofill.mobileprovision ./.github/secrets/dist_autofill.mobileprovision.gpg
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
--output $HOME/secrets/dist_bitwarden.mobileprovision ./.github/secrets/dist_bitwarden.mobileprovision.gpg
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
--output $HOME/secrets/dist_extension.mobileprovision ./.github/secrets/dist_extension.mobileprovision.gpg
shell: bash
- name: Increment version
if: github.ref == 'refs/heads/master' || github.event_name == 'release'
run: ./.github/scripts/ios/increment-version.ps1
shell: pwsh
run: |
BUILD_NUMBER=$((100 + $GITHUB_RUN_NUMBER))
- name: Set up keychain
run: ./.github/scripts/ios/setup-keychain.ps1
shell: pwsh
echo "########################################"
echo "##### Setting CFBundleVersion $BUILD_NUMBER"
echo "########################################"
perl -0777 -pi.bak -e 's/<key>CFBundleVersion<\/key>\s*<string>1<\/string>/<key>CFBundleVersion<\/key>\n\t<string>'"$BUILD_NUMBER"'<\/string>/' ./src/iOS/Info.plist
perl -0777 -pi.bak -e 's/<key>CFBundleVersion<\/key>\s*<string>1<\/string>/<key>CFBundleVersion<\/key>\n\t<string>'"$BUILD_NUMBER"'<\/string>/' ./src/iOS.Extension/Info.plist
perl -0777 -pi.bak -e 's/<key>CFBundleVersion<\/key>\s*<string>1<\/string>/<key>CFBundleVersion<\/key>\n\t<string>'"$BUILD_NUMBER"'<\/string>/' ./src/iOS.Autofill/Info.plist
shell: bash
- name: Set up Keychain
env:
KEYCHAIN_PASSWORD: ${{ secrets.IOS_KEYCHAIN_PASSWORD }}
MOBILE_KEY_PASSWORD: ${{ secrets.IOS_KEY_PASSWORD }}
DIST_CERT_PASSWORD: ${{ secrets.IOS_DIST_CERT_PASSWORD }}
run: |
security create-keychain -p $KEYCHAIN_PASSWORD build.keychain
security default-keychain -s build.keychain
security unlock-keychain -p $KEYCHAIN_PASSWORD build.keychain
security set-keychain-settings -lut 1200 build.keychain
security import ~/secrets/bitwarden-mobile-key.p12 -k build.keychain -P $MOBILE_KEY_PASSWORD \
-T /usr/bin/codesign -T /usr/bin/security
security import ~/secrets/iphone-distribution-cert.p12 -k build.keychain -P $DIST_CERT_PASSWORD \
-T /usr/bin/codesign -T /usr/bin/security
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k $KEYCHAIN_PASSWORD build.keychain
shell: bash
- name: Set up provisioning profiles
run: ./.github/scripts/ios/setup-profiles.ps1
shell: pwsh
run: |
AUTOFILL_PROFILE_PATH=$HOME/secrets/dist_autofill.mobileprovision
BITWARDEN_PROFILE_PATH=$HOME/secrets/dist_bitwarden.mobileprovision
EXTENSION_PROFILE_PATH=$HOME/secrets/dist_extension.mobileprovision
PROFILES_DIR_PATH=$HOME/Library/MobileDevice/Provisioning\ Profiles
mkdir -p "$PROFILES_DIR_PATH"
AUTOFILL_UUID=$(grep UUID -A1 -a $AUTOFILL_PROFILE_PATH | grep -io "[-A-F0-9]\{36\}")
cp $AUTOFILL_PROFILE_PATH "$PROFILES_DIR_PATH/$AUTOFILL_UUID.mobileprovision"
BITWARDEN_UUID=$(grep UUID -A1 -a $BITWARDEN_PROFILE_PATH | grep -io "[-A-F0-9]\{36\}")
cp $BITWARDEN_PROFILE_PATH "$PROFILES_DIR_PATH/$BITWARDEN_UUID.mobileprovision"
EXTENSION_UUID=$(grep UUID -A1 -a $EXTENSION_PROFILE_PATH | grep -io "[-A-F0-9]\{36\}")
cp $EXTENSION_PROFILE_PATH "$PROFILES_DIR_PATH/$EXTENSION_UUID.mobileprovision"
shell: bash
- name: Restore packages
run: nuget restore
- name: Archive Build for App Store
if: github.ref == 'refs/heads/master' || github.event_name == 'release'
run: ./.github/scripts/ios/build.ps1 -configuration AppStore -platform iPhone -archive
shell: pwsh
run: |
$configuration = "AppStore";
$platform = "iPhone";
- name: Build for App Store
if: github.ref != 'refs/heads/master'
run: ./.github/scripts/ios/build.ps1 -configuration AppStore -platform iPhone
Write-Output "########################################"
Write-Output "##### Archive $configuration Configuration for $platform Platform"
Write-Output "########################################"
msbuild "$($env:GITHUB_WORKSPACE + "/src/iOS/iOS.csproj")" "/p:Platform=$platform" `
"/p:Configuration=$configuration" "/p:ArchiveOnBuild=true" "/t:`"Build`""
Write-Output "########################################"
Write-Output "##### Done"
Write-Output "########################################"
ls ~/Library/Developer/Xcode/Archives
shell: pwsh
- name: Export .ipa for App Store
if: github.ref == 'refs/heads/master' || github.event_name == 'release'
run: ./.github/scripts/ios/export-ipa.ps1 -method app-store
shell: pwsh
run: |
EXPORT_OPTIONS_PATH="./.github/resources/export-options-app-store.plist"
ARCHIVE_PATH="$HOME/Library/Developer/Xcode/Archives/*/*.xcarchive"
EXPORT_PATH="./bitwarden-export"
xcodebuild -exportArchive -archivePath $ARCHIVE_PATH -exportPath $EXPORT_PATH \
-exportOptionsPlist $EXPORT_OPTIONS_PATH
shell: bash
- name: Upload App Store .ipa artifact
if: github.ref == 'refs/heads/master' || github.event_name == 'release'
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700 # v2.2.4
with:
name: Bitwarden.ipa
path: ./bitwarden-export/Bitwarden.ipa
if-no-files-found: error
- name: Deploy to App Store
if: github.ref == 'refs/heads/master' || github.event_name == 'release'
run: ./.github/scripts/ios/deploy-app-store.ps1
shell: pwsh
if: |
(github.ref == 'refs/heads/master'
&& needs.setup.outputs.rc_branch_exists == 0
&& needs.setup.outputs.release_branch_exists == 0)
|| (github.ref == 'refs/heads/rc' && needs.setup.outputs.release_branch_exists == 0)
|| github.ref == 'refs/heads/release'
env:
APPLE_ID_USERNAME: ${{ secrets.APPLE_ID_USERNAME }}
APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
- name: Upload release assets
if: github.event_name == 'release'
run: |
hub release edit `
-a ./bitwarden-export/Bitwarden.ipa `
-m "Version $($env:RELEASE_TAG_NAME.TrimStart('v'))" `
$env:RELEASE_TAG_NAME
shell: pwsh
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
RELEASE_TAG_NAME: ${{ github.event.release.tag_name }}
xcrun altool --upload-app --type ios --file "./bitwarden-export/Bitwarden.ipa" \
--username "$APPLE_ID_USERNAME" --password "$APPLE_ID_PASSWORD"
shell: bash

View File

@@ -5,19 +5,149 @@ on:
workflow_dispatch:
jobs:
release:
name: Create Release
runs-on: ubuntu-20.04
steps:
- name: Branch check
run: |
if [[ "$GITHUB_REF" != "refs/heads/release" ]]; then
echo "==================================="
echo "[!] Can only release from the 'release' branch"
echo "==================================="
exit 1
fi
cloc:
name: CLOC
runs-on: ubuntu-latest
- name: Checkout repo
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
with:
ref: release
- name: Retrieve Mobile release version
id: retrieve-mobile-version
run: |
ver=$(sed -n -e '/android:versionName/ s/.*\= *//p' ./src/Android/Properties/AndroidManifest.xml | tr -d '"')
echo "::set-output name=mobile_version::${ver}"
shell: bash
- name: Check to make sure Mobile release version has been bumped
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
latest_ver=$(hub release -L 1 -f '%T')
latest_ver=${latest_ver:1}
echo "Latest version: $latest_ver"
ver=${{ steps.retrieve-mobile-version.outputs.mobile_version }}
echo "Version: $ver"
if [ "$latest_ver" = "$ver" ]; then
echo "Version has not been bumped!"
exit 1
fi
shell: bash
- name: Download all artifacts
uses: dawidd6/action-download-artifact@b9571484721e8187f1fd08147b497129f8972c74 # v2.14.0
with:
workflow: build.yml
workflow_conclusion: success
branch: release
- name: Create release
uses: ncipollo/release-action@95215a3cb6e6a1908b3c44e00b4fdb15548b1e09 # v2.8.5
with:
artifacts: "./com.x8bit.bitwarden.aab/com.x8bit.bitwarden.aab,
./com.x8bit.bitwarden.apk/com.x8bit.bitwarden.apk,
./com.x8bit.bitwarden-fdroid.apk/com.x8bit.bitwarden-fdroid.apk,
./Bitwarden.ipa/Bitwarden.ipa"
commit: ${{ github.sha }}
tag: v${{ steps.retrieve-mobile-version.outputs.mobile_version }}
name: Test Version ${{ steps.retrieve-mobile-version.outputs.mobile_version }}
body: "<insert release notes here>"
token: ${{ secrets.GITHUB_TOKEN }}
draft: true
f-droid:
name: F-Droid Release
runs-on: ubuntu-20.04
needs: release
steps:
- name: Checkout repo
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
with:
ref: release
- name: Set up cloc
- name: Download F-Droid .apk artifact
uses: dawidd6/action-download-artifact@b9571484721e8187f1fd08147b497129f8972c74 # v2.14.0
with:
workflow: build.yml
workflow_conclusion: success
branch: release
name: com.x8bit.bitwarden-fdroid.apk
- name: Set up Node
uses: actions/setup-node@46071b5c7a2e0c34e49c3cb8a0e792e86e18d5ea # v2.3.0
with:
node-version: '10.x'
- name: Set up F-Droid server
run: |
sudo apt-get update
sudo apt-get -y install cloc
sudo apt-get -qq update
sudo apt-get -qqy install --no-install-recommends fdroidserver wget
- name: Print lines of code
run: cloc --vcs git --exclude-dir Resources,store,test,Properties --include-lang C#,XAML
- name: Set up Git credentials
env:
ACCESS_TOKEN: ${{ secrets.ACCESS_TOKEN }}
run: |
git config --global credential.helper store
echo "https://${ACCESS_TOKEN}:x-oauth-basic@github.com" >> ~/.git-credentials
git config --global user.email "ci@bitwarden.com"
git config --global user.name "Bitwarden CI"
- name: Print environment
run: |
node --version
npm --version
git --version
echo "GitHub ref: $GITHUB_REF"
echo "GitHub event: $GITHUB_EVENT"
- name: Install Node dependencies
run: npm install
- name: Decrypt secrets
env:
DECRYPT_FILE_PASSWORD: ${{ secrets.DECRYPT_FILE_PASSWORD }}
run: |
mkdir -p ~/secrets
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
--output ./store/fdroid/keystore.jks ./.github/secrets/store_fdroid-keystore.jks.gpg
- name: Compile for F-Droid Store
env:
FDROID_STORE_KEYSTORE_PASSWORD: ${{ secrets.FDROID_STORE_KEYSTORE_PASSWORD }}
run: |
cd $GITHUB_WORKSPACE
mkdir dist
cp CNAME ./dist
cd store
chmod 600 fdroid/config.py fdroid/keystore.jks
mkdir -p temp/fdroid
TEMP_DIR="$GITHUB_WORKSPACE/store/temp/fdroid"
cd fdroid
echo "keypass=\"$FDROID_STORE_KEYSTORE_PASSWORD\"" >>config.py
echo "keystorepass=\"$FDROID_STORE_KEYSTORE_PASSWORD\"" >>config.py
echo "local_copy_dir=\"$TEMP_DIR\"" >>config.py
mkdir -p repo
mv $GITHUB_WORKSPACE/com.x8bit.bitwarden-fdroid.apk ./repo/
fdroid update
fdroid server update
cd ..
rm -rf temp/fdroid/archive
mv -v temp/fdroid ../dist
cd fdroid
cp index.html btn.png qr.png ../../dist/fdroid
cd $GITHUB_WORKSPACE
- name: Deploy to gh-pages
run: npm run deploy

View File

@@ -9,7 +9,7 @@ using Bit.Core.Utilities;
namespace Bit.Droid.Accessibility
{
[Activity(Theme = "@style/LightTheme.Splash", WindowSoftInputMode = SoftInput.StateHidden)]
[Activity(Theme = "@style/BaseTheme", WindowSoftInputMode = SoftInput.StateHidden)]
public class AccessibilityActivity : Activity
{
private DateTime? _lastLaunch = null;

View File

@@ -76,13 +76,11 @@
<PackageReference Include="Portable.BouncyCastle">
<Version>1.8.10</Version>
</PackageReference>
<PackageReference Include="Xamarin.AndroidX.Core" Version="1.5.0" />
<PackageReference Include="Xamarin.AndroidX.AppCompat.AppCompatResources" Version="1.3.0" />
<PackageReference Include="Xamarin.AndroidX.AppCompat" Version="1.3.0" />
<PackageReference Include="Xamarin.AndroidX.AutoFill" Version="1.1.0.6" />
<PackageReference Include="Xamarin.AndroidX.CardView" Version="1.0.0.8" />
<PackageReference Include="Xamarin.AndroidX.Legacy.Support.V4" Version="1.0.0.7" />
<PackageReference Include="Xamarin.AndroidX.MediaRouter" Version="1.2.4" />
<PackageReference Include="Xamarin.AndroidX.AppCompat" Version="1.3.1.3" />
<PackageReference Include="Xamarin.AndroidX.AutoFill" Version="1.1.0.9" />
<PackageReference Include="Xamarin.AndroidX.CardView" Version="1.0.0.11" />
<PackageReference Include="Xamarin.AndroidX.Legacy.Support.V4" Version="1.0.0.10" />
<PackageReference Include="Xamarin.AndroidX.MediaRouter" Version="1.2.5.2" />
<PackageReference Include="Xamarin.AndroidX.Migration" Version="1.0.8" />
<PackageReference Include="Xamarin.Essentials">
<Version>1.7.0</Version>
@@ -90,10 +88,10 @@
<PackageReference Include="Xamarin.Firebase.Messaging">
<Version>122.0.0</Version>
</PackageReference>
<PackageReference Include="Xamarin.Google.Android.Material" Version="1.3.0.1" />
<PackageReference Include="Xamarin.Google.Android.Material" Version="1.4.0.4" />
<PackageReference Include="Xamarin.Google.Dagger" Version="2.37.0" />
<PackageReference Include="Xamarin.GooglePlayServices.SafetyNet">
<Version>117.0.0</Version>
<Version>117.0.1</Version>
</PackageReference>
</ItemGroup>
<ItemGroup>
@@ -113,7 +111,6 @@
<Compile Include="Autofill\SavedItem.cs" />
<Compile Include="Effects\FabShadowEffect.cs" />
<Compile Include="Effects\FixedSizeEffect.cs" />
<Compile Include="Effects\SelectableLabelEffect.cs" />
<Compile Include="Effects\TabBarEffect.cs" />
<Compile Include="Push\FirebaseMessagingService.cs" />
<Compile Include="Receivers\ClearClipboardAlarmReceiver.cs" />
@@ -124,6 +121,8 @@
<Compile Include="Renderers\ExtendedDatePickerRenderer.cs" />
<Compile Include="Renderers\CustomTabbedRenderer.cs" />
<Compile Include="Renderers\ExtendedStackLayoutRenderer.cs" />
<Compile Include="Renderers\ExtendedStepperRenderer.cs" />
<Compile Include="Renderers\CustomSwitchRenderer.cs" />
<Compile Include="Renderers\ExtendedTimePickerRenderer.cs" />
<Compile Include="Renderers\ExtendedSliderRenderer.cs" />
<Compile Include="Renderers\CustomEditorRenderer.cs" />
@@ -146,7 +145,9 @@
<Compile Include="Tiles\MyVaultTileService.cs" />
<Compile Include="Utilities\AndroidHelpers.cs" />
<Compile Include="Utilities\AppCenterHelper.cs" />
<Compile Include="Utilities\ThemeHelpers.cs" />
<Compile Include="WebAuthCallbackActivity.cs" />
<Compile Include="Renderers\SelectableLabelRenderer.cs" />
</ItemGroup>
<ItemGroup>
<AndroidAsset Include="Assets\FontAwesome.ttf" />
@@ -173,8 +174,6 @@
<AndroidResource Include="Resources\drawable\id.xml" />
<AndroidResource Include="Resources\drawable\info.xml" />
<AndroidResource Include="Resources\drawable\list_item_bg.xml" />
<AndroidResource Include="Resources\drawable\list_item_bg_dark.xml" />
<AndroidResource Include="Resources\drawable\list_item_bg_nord.xml" />
<AndroidResource Include="Resources\drawable\lock.xml" />
<AndroidResource Include="Resources\drawable\login.xml" />
<AndroidResource Include="Resources\drawable\logo.xml" />
@@ -187,6 +186,7 @@
<AndroidResource Include="Resources\drawable\shield.xml" />
<AndroidResource Include="Resources\drawable-v23\splash_screen.xml" />
<AndroidResource Include="Resources\drawable-v23\splash_screen_dark.xml" />
<AndroidResource Include="Resources\drawable\switch_thumb.xml" />
<AndroidResource Include="Resources\layout\Tabbar.axml" />
<AndroidResource Include="Resources\layout\Toolbar.axml" />
<AndroidResource Include="Resources\mipmap-anydpi-v26\ic_launcher.xml" />

View File

@@ -35,6 +35,7 @@ namespace Bit.Droid.Autofill
public static HashSet<string> TrustedBrowsers = new HashSet<string>
{
"com.duckduckgo.mobile.android",
"com.google.android.googlequicksearchbox",
"org.mozilla.focus",
"org.mozilla.klar",
};

View File

@@ -94,24 +94,12 @@ namespace Bit.Droid.Autofill
_policyService ??= ServiceContainer.Resolve<IPolicyService>("policyService");
var personalOwnershipPolicies = await _policyService.GetAll(PolicyType.PersonalOwnership);
if (personalOwnershipPolicies != null)
var personalOwnershipPolicyApplies = await _policyService.PolicyAppliesToUser(PolicyType.PersonalOwnership);
if (personalOwnershipPolicyApplies)
{
_userService ??= ServiceContainer.Resolve<IUserService>("userService");
foreach (var policy in personalOwnershipPolicies)
{
if (policy.Enabled)
{
var org = await _userService.GetOrganizationAsync(policy.OrganizationId);
if (org != null && org.Enabled && org.UsePolicies && !org.canManagePolicies
&& org.Status == OrganizationUserStatusType.Confirmed)
{
return;
}
}
}
return;
}
var parser = new Parser(structure, ApplicationContext);
parser.Parse();

View File

@@ -1,6 +1,6 @@
using Android.Graphics.Drawables;
using Bit.App.Utilities;
using Bit.Droid.Effects;
using Bit.Droid.Utilities;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
@@ -14,7 +14,7 @@ namespace Bit.Droid.Effects
if (Control is Android.Widget.Button button)
{
var gd = new GradientDrawable();
gd.SetColor(ThemeManager.GetResourceColor("FabColor").ToAndroid());
gd.SetColor(ThemeHelpers.FabColor);
gd.SetCornerRadius(100);
button.SetBackground(gd);

View File

@@ -1,23 +0,0 @@
using Android.Widget;
using Bit.Droid.Effects;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
[assembly: ExportEffect(typeof(SelectableLabelEffect), "SelectableLabelEffect")]
namespace Bit.Droid.Effects
{
public class SelectableLabelEffect : PlatformEffect
{
protected override void OnAttached()
{
if (Control is TextView textView)
{
textView.SetTextIsSelectable(true);
}
}
protected override void OnDetached()
{
}
}
}

View File

@@ -15,9 +15,9 @@ using Bit.Droid.Receivers;
using Bit.App.Models;
using Bit.Core.Enums;
using Android.Nfc;
using Bit.App.Utilities;
using System.Threading.Tasks;
using AndroidX.Core.Content;
using Bit.App.Utilities;
using ZXing.Net.Mobile.Android;
namespace Bit.Droid
@@ -30,7 +30,7 @@ namespace Bit.Droid
LaunchMode = LaunchMode.SingleTask,
ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation |
ConfigChanges.Keyboard | ConfigChanges.KeyboardHidden |
ConfigChanges.Navigation)]
ConfigChanges.Navigation | ConfigChanges.UiMode)]
[IntentFilter(
new[] { Intent.ActionSend },
Categories = new[] { Intent.CategoryDefault },
@@ -81,7 +81,6 @@ namespace Bit.Droid
TabLayoutResource = Resource.Layout.Tabbar;
ToolbarResource = Resource.Layout.Toolbar;
UpdateTheme(ThemeManager.GetTheme(true));
base.OnCreate(savedInstanceState);
if (!CoreHelpers.InDebugMode())
{
@@ -118,7 +117,7 @@ namespace Bit.Droid
}
else if (message.Command == "updatedTheme")
{
RestartApp();
Xamarin.Forms.Device.BeginInvokeOnMainThread(() => AppearanceAdjustments());
}
else if (message.Command == "exit")
{
@@ -141,6 +140,7 @@ namespace Bit.Droid
{
base.OnResume();
Xamarin.Essentials.Platform.OnResume();
AppearanceAdjustments();
if (_deviceActionService.SupportsNfc())
{
try
@@ -382,45 +382,11 @@ namespace Bit.Droid
}
}
private void UpdateTheme(string theme)
private void AppearanceAdjustments()
{
if (theme == "light")
{
SetTheme(Resource.Style.LightTheme);
}
else if (theme == "dark")
{
SetTheme(Resource.Style.DarkTheme);
}
else if (theme == "black")
{
SetTheme(Resource.Style.BlackTheme);
}
else if (theme == "nord")
{
SetTheme(Resource.Style.NordTheme);
}
else
{
if (_deviceActionService.UsingDarkTheme())
{
SetTheme(Resource.Style.DarkTheme);
}
else
{
SetTheme(Resource.Style.LightTheme);
}
}
}
private void RestartApp()
{
var intent = new Intent(this, typeof(MainActivity));
var pendingIntent = PendingIntent.GetActivity(this, 5923650, intent, PendingIntentFlags.CancelCurrent);
var alarmManager = GetSystemService(AlarmService) as AlarmManager;
var triggerMs = Java.Lang.JavaSystem.CurrentTimeMillis() + 500;
alarmManager.Set(AlarmType.Rtc, triggerMs, pendingIntent);
Java.Lang.JavaSystem.Exit(0);
Window?.SetStatusBarColor(ThemeHelpers.NavBarBackgroundColor);
Window?.DecorView.SetBackgroundColor(ThemeHelpers.BackgroundColor);
ThemeHelpers.SetAppearance(ThemeManager.GetTheme(true), ThemeManager.OsDarkModeEnabled());
}
private void ExitApp()

View File

@@ -3,7 +3,7 @@
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:versionCode="1"
android:versionName="2.13.0"
android:versionName="2.14.2"
android:installLocation="internalOnly"
package="com.x8bit.bitwarden">

View File

@@ -1,6 +1,9 @@
using Android.Content;
using System.ComponentModel;
using Android.Content;
using Android.Content.Res;
using Android.Views.InputMethods;
using Bit.Droid.Renderers;
using Bit.Droid.Utilities;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
@@ -25,6 +28,7 @@ namespace Bit.Droid.Renderers
protected override void OnElementChanged(ElementChangedEventArgs<Editor> e)
{
base.OnElementChanged(e);
UpdateBorderColor();
if (Control != null && e.NewElement != null)
{
Control.SetPadding(Control.PaddingLeft, Control.PaddingTop - 10, Control.PaddingRight,
@@ -33,5 +37,33 @@ namespace Bit.Droid.Renderers
(ImeAction)ImeFlags.NoExtractUi;
}
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName == Entry.TextColorProperty.PropertyName)
{
UpdateBorderColor();
}
}
private void UpdateBorderColor()
{
if (Control != null)
{
var states = new[]
{
new[] { Android.Resource.Attribute.StateFocused }, // focused
new[] { -Android.Resource.Attribute.StateFocused }, // unfocused
};
var colors = new int[]
{
ThemeHelpers.PrimaryColor,
ThemeHelpers.MutedColor
};
Control.BackgroundTintList = new ColorStateList(states, colors);
}
}
}
}

View File

@@ -1,10 +1,12 @@
using System.ComponentModel;
using Android.Content;
using Android.Content.Res;
using Android.Graphics;
using Android.Text;
using Android.Views.InputMethods;
using Android.Widget;
using Bit.Droid.Renderers;
using Bit.Droid.Utilities;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
@@ -20,6 +22,7 @@ namespace Bit.Droid.Renderers
protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
{
base.OnElementChanged(e);
UpdateBorderColor();
if (Control != null && e.NewElement != null)
{
Control.SetPadding(Control.PaddingLeft, Control.PaddingTop - 10, Control.PaddingRight,
@@ -68,6 +71,28 @@ namespace Bit.Droid.Renderers
}
}
}
else if (e.PropertyName == Entry.TextColorProperty.PropertyName)
{
UpdateBorderColor();
}
}
private void UpdateBorderColor()
{
if (Control != null)
{
var states = new[]
{
new[] { Android.Resource.Attribute.StateFocused }, // focused
new[] { -Android.Resource.Attribute.StateFocused }, // unfocused
};
var colors = new int[]
{
ThemeHelpers.PrimaryColor,
ThemeHelpers.MutedColor
};
Control.BackgroundTintList = new ColorStateList(states, colors);
}
}
}
}

View File

@@ -1,5 +1,8 @@
using Android.Content;
using System.ComponentModel;
using Android.Content;
using Android.Content.Res;
using Bit.Droid.Renderers;
using Bit.Droid.Utilities;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
@@ -15,11 +18,40 @@ namespace Bit.Droid.Renderers
protected override void OnElementChanged(ElementChangedEventArgs<Picker> e)
{
base.OnElementChanged(e);
UpdateBorderColor();
if (Control != null && e.NewElement != null)
{
Control.SetPadding(Control.PaddingLeft, Control.PaddingTop - 10, Control.PaddingRight,
Control.PaddingBottom + 20);
}
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName == Picker.TextColorProperty.PropertyName)
{
UpdateBorderColor();
}
}
private void UpdateBorderColor()
{
if (Control != null)
{
var states = new[]
{
new[] { Android.Resource.Attribute.StateFocused }, // focused
new[] { -Android.Resource.Attribute.StateFocused }, // unfocused
};
var colors = new int[]
{
ThemeHelpers.PrimaryColor,
ThemeHelpers.MutedColor
};
Control.BackgroundTintList = new ColorStateList(states, colors);
}
}
}
}

View File

@@ -0,0 +1,66 @@
using System.ComponentModel;
using Android.Content;
using Android.Content.Res;
using Android.Graphics.Drawables;
using Android.OS;
using AndroidX.Core.Content.Resources;
using Bit.Droid.Renderers;
using Bit.Droid.Utilities;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
[assembly: ExportRenderer(typeof(Switch), typeof(CustomSwitchRenderer))]
namespace Bit.Droid.Renderers
{
public class CustomSwitchRenderer : SwitchRenderer
{
public CustomSwitchRenderer(Context context)
: base(context)
{}
protected override void OnElementChanged(ElementChangedEventArgs<Switch> e)
{
base.OnElementChanged(e);
UpdateColors();
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName == Switch.OnColorProperty.PropertyName)
{
UpdateColors();
}
}
private void UpdateColors()
{
if (Build.VERSION.SdkInt <= BuildVersionCodes.LollipopMr1)
{
// Android 5.x doesn't support ThumbTintList, and using SwitchCompat on every version after 5.x
// doesn't apply tinting the way we want. Let 5.x to do its own thing here.
return;
}
if (Control != null)
{
var t = ResourcesCompat.GetDrawable(Resources, Resource.Drawable.switch_thumb, null);
if (t is GradientDrawable thumb)
{
Control.ThumbDrawable = thumb;
}
var thumbStates = new[]
{
new[] { Android.Resource.Attribute.StateChecked }, // checked
new[] { -Android.Resource.Attribute.StateChecked }, // unchecked
};
var thumbColors = new int[]
{
ThemeHelpers.SwitchOnColor,
ThemeHelpers.SwitchThumbColor
};
Control.ThumbTintList = new ColorStateList(thumbStates, thumbColors);
}
}
}
}

View File

@@ -49,11 +49,11 @@ namespace Bit.Droid.Renderers
return null;
}
async void BottomNavigationView.IOnNavigationItemReselectedListener.OnNavigationItemReselected(IMenuItem item)
public void OnNavigationItemReselected(IMenuItem item)
{
if (_page?.CurrentPage?.Navigation != null && _page.CurrentPage.Navigation.NavigationStack.Count > 0)
{
await _page.CurrentPage.Navigation.PopToRootAsync();
Device.BeginInvokeOnMainThread(async () => await _page.CurrentPage.Navigation.PopToRootAsync());
}
}
}

View File

@@ -1,6 +1,5 @@
using Android.Content;
using Bit.App.Controls;
using Bit.App.Utilities;
using Bit.Droid.Renderers;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
@@ -10,8 +9,6 @@ namespace Bit.Droid.Renderers
{
public class ExtendedGridRenderer : ViewRenderer
{
private static int? _bgResId;
public ExtendedGridRenderer(Context context) : base(context) { }
protected override void OnElementChanged(ElementChangedEventArgs<View> elementChangedEvent)
@@ -19,25 +16,8 @@ namespace Bit.Droid.Renderers
base.OnElementChanged(elementChangedEvent);
if (elementChangedEvent.NewElement != null)
{
SetBackgroundResource(GetBgResId());
SetBackgroundResource(Resource.Drawable.list_item_bg);
}
}
private int GetBgResId()
{
if (_bgResId == null)
{
if (ThemeManager.GetTheme(true) == "nord")
{
_bgResId = Resource.Drawable.list_item_bg_nord;
}
else
{
_bgResId ??= ThemeManager.UsingLightTheme ? Resource.Drawable.list_item_bg :
Resource.Drawable.list_item_bg_dark;
}
}
return _bgResId.Value;
}
}
}

View File

@@ -1,4 +1,5 @@
using Android.Content;
using System.ComponentModel;
using Android.Content;
using Android.Graphics.Drawables;
using AndroidX.Core.Content.Resources;
using Bit.App.Controls;
@@ -18,6 +19,21 @@ namespace Bit.Droid.Renderers
protected override void OnElementChanged(ElementChangedEventArgs<Slider> e)
{
base.OnElementChanged(e);
UpdateColor();
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName == ExtendedSlider.ThumbBorderColorProperty.PropertyName)
{
UpdateColor();
}
}
private void UpdateColor()
{
if (Control != null && Element is ExtendedSlider view)
{
var t = ResourcesCompat.GetDrawable(Resources, Resource.Drawable.slider_thumb, null);

View File

@@ -1,6 +1,5 @@
using Android.Content;
using Bit.App.Controls;
using Bit.App.Utilities;
using Bit.Droid.Renderers;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
@@ -10,8 +9,6 @@ namespace Bit.Droid.Renderers
{
public class ExtendedStackLayoutRenderer : ViewRenderer
{
private static int? _bgResId;
public ExtendedStackLayoutRenderer(Context context) : base(context) { }
protected override void OnElementChanged(ElementChangedEventArgs<View> elementChangedEvent)
@@ -19,25 +16,8 @@ namespace Bit.Droid.Renderers
base.OnElementChanged(elementChangedEvent);
if (elementChangedEvent.NewElement != null)
{
SetBackgroundResource(GetBgResId());
SetBackgroundResource(Resource.Drawable.list_item_bg);
}
}
private int GetBgResId()
{
if (_bgResId == null)
{
if (ThemeManager.GetTheme(true) == "nord")
{
_bgResId = Resource.Drawable.list_item_bg_nord;
}
else
{
_bgResId ??= ThemeManager.UsingLightTheme ? Resource.Drawable.list_item_bg :
Resource.Drawable.list_item_bg_dark;
}
}
return _bgResId.Value;
}
}
}

View File

@@ -0,0 +1,72 @@
using System.ComponentModel;
using Android.Content;
using Android.Graphics;
using Android.OS;
using Bit.App.Controls;
using Bit.Droid.Renderers;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
[assembly: ExportRenderer(typeof(ExtendedStepper), typeof(ExtendedStepperRenderer))]
namespace Bit.Droid.Renderers
{
public class ExtendedStepperRenderer : StepperRenderer
{
public ExtendedStepperRenderer(Context context)
: base(context)
{}
protected override void OnElementChanged(ElementChangedEventArgs<Stepper> e)
{
base.OnElementChanged(e);
UpdateBgColor();
UpdateFgColor();
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName == ExtendedStepper.StepperBackgroundColorProperty.PropertyName)
{
UpdateBgColor();
}
else if (e.PropertyName == ExtendedStepper.StepperForegroundColorProperty.PropertyName)
{
UpdateFgColor();
}
}
private void UpdateBgColor()
{
if (Control != null && Element is ExtendedStepper view)
{
if (Build.VERSION.SdkInt >= BuildVersionCodes.Q)
{
Control.GetChildAt(0)?.Background?.SetColorFilter(
new BlendModeColorFilter(view.StepperBackgroundColor.ToAndroid(), BlendMode.Multiply));
Control.GetChildAt(1)?.Background?.SetColorFilter(
new BlendModeColorFilter(view.StepperBackgroundColor.ToAndroid(), BlendMode.Multiply));
}
else
{
Control.GetChildAt(0)?.Background?.SetColorFilter(
view.StepperBackgroundColor.ToAndroid(), PorterDuff.Mode.Multiply);
Control.GetChildAt(1)?.Background?.SetColorFilter(
view.StepperBackgroundColor.ToAndroid(), PorterDuff.Mode.Multiply);
}
}
}
private void UpdateFgColor()
{
if (Control != null && Element is ExtendedStepper view)
{
var btn0 = Control.GetChildAt(0) as Android.Widget.Button;
btn0?.SetTextColor(view.StepperForegroundColor.ToAndroid());
var btn1 = Control.GetChildAt(1) as Android.Widget.Button;
btn1?.SetTextColor(view.StepperForegroundColor.ToAndroid());
}
}
}
}

View File

@@ -0,0 +1,25 @@
using System;
using Android.Content;
using Bit.App.Controls;
using Bit.Droid.Renderers;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
[assembly: ExportRenderer(typeof(SelectableLabel), typeof(SelectableLabelRenderer))]
namespace Bit.Droid.Renderers
{
public class SelectableLabelRenderer : LabelRenderer
{
public SelectableLabelRenderer(Context context) : base(context) { }
protected override void OnElementChanged(ElementChangedEventArgs<Label> e)
{
base.OnElementChanged(e);
if (Control != null)
{
Control.SetTextIsSelectable(true);
}
}
}
}

View File

@@ -6,7 +6,7 @@ TODO: When API 23 becomes our new minimum, replace 'splash_screen.xml' in 'drawa
-->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<color android:color="@color/lightgray"/>
<color android:color="@color/white"/>
</item>
<item
android:drawable="@drawable/logo"

View File

@@ -1,5 +1,5 @@
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="@color/itemPressed">
android:color="#8E8E93">
<item
android:id="@android:id/mask"

View File

@@ -1,7 +0,0 @@
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="@color/dark_primary">
<item
android:id="@android:id/mask"
android:drawable="@android:color/white" />
</ripple>

View File

@@ -1,7 +0,0 @@
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="@color/nord_primary">
<item
android:id="@android:id/mask"
android:drawable="@android:color/white" />
</ripple>

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<color android:color="@color/lightgray"/>
<color android:color="@color/white"/>
</item>
<item>
<bitmap android:src="@drawable/logo_legacy" android:tileMode="disabled" android:gravity="center"/>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval">
<solid android:color="@color/white"/>
<size android:width="20dp" android:height="20dp"/>
</shape>

View File

@@ -1,8 +1,26 @@
<?xml version="1.0" encoding="utf-8" ?>
<resources>
<!-- Launch theme (for auto dark/light based on system) -->
<style name="LaunchTheme" parent="DarkTheme.Base">
<style name="LaunchTheme" parent="BaseTheme">
<item name="android:windowBackground">@drawable/splash_screen_dark</item>
<item name="android:windowNoTitle">true</item>
</style>
<style name="BaseTheme" parent="Theme.AppCompat">
<item name="windowNoTitle">true</item>
<item name="windowActionBar">false</item>
<item name="colorPrimaryDark">@color/dark_notificationBar</item>
<item name="colorAccent">@color/dark_primary</item>
<item name="colorControlNormal">@color/dark_border</item>
<item name="android:navigationBarColor">@color/dark_navigationBarBackground</item>
<item name="windowActionModeOverlay">true</item>
<item name="android:colorActivatedHighlight">@android:color/transparent</item>
<item name="android:textCursorDrawable">@null</item>
<item name="popupTheme">@style/ThemeOverlay.AppCompat</item>
<item name="buttonStyle">@style/ButtonStyle</item>
</style>
<style name="ButtonStyle" parent="Widget.AppCompat.Button">
<item name="android:textAllCaps">false</item>
</style>
</resources>

View File

@@ -1,38 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Light theme -->
<!-- Light System -->
<color name="colorPrimary">#175DDC</color>
<color name="colorPrimaryDark">#1A3B66</color>
<color name="primary">#175DDC</color>
<color name="notificationBar">#1452BC</color>
<color name="border">#dddddd</color>
<color name="itemPressed">#bbbbbb</color>
<!-- Dark theme -->
<!-- Dark System -->
<color name="dark_primary">#52bdfb</color>
<color name="dark_notificationBar">#191919</color>
<color name="dark_border">#191919</color>
<color name="dark_border">#666666</color>
<color name="dark_navigationBarBackground">#191919</color>
<!-- Black theme -->
<color name="black_border">#282828</color>
<!-- Nord theme -->
<color name="nord_background">#3b4252</color>
<color name="nord_text">#e5e9f0</color>
<color name="nord_primary">#81a1c1</color>
<color name="nord_actionBar">#2e3440</color>
<color name="nord_actionBarText">#e5e9f0</color>
<color name="nord_notificationBar">#20242D</color>
<color name="nord_dialogBackground">#3b4252</color>
<color name="nord_border">#2e3440</color>
<color name="nord_popupBackground">#4c566a</color>
<color name="nord_popupText">#e5e9f0</color>
<color name="nord_buttonBackground">#4c566a</color>
<color name="nord_navigationBarBackground">#20242D</color>
<!-- Other -->
<color name="launcher_background">#FFFFFF</color>
<color name="white">#FFFFFF</color>
<color name="black">#000000</color>
<color name="darkgray">#333333</color>

View File

@@ -1,21 +1,12 @@
<?xml version="1.0" encoding="utf-8" ?>
<resources xmlns:tools="http://schemas.android.com/tools">
<resources>
<!-- Launch theme (for auto dark/light based on system) -->
<style name="LaunchTheme" parent="LightTheme.Base">
<style name="LaunchTheme" parent="BaseTheme">
<item name="android:windowBackground">@drawable/splash_screen</item>
<item name="android:windowNoTitle">true</item>
</style>
<!-- Light theme -->
<style name="LightTheme" parent="LightTheme.Base">
</style>
<style name="LightTheme.Splash" parent="LightTheme.Base">
<item name="android:windowBackground">@drawable/splash_screen</item>
<item name="android:windowNoTitle">true</item>
</style>
<style name="LightTheme.Base" parent="Theme.AppCompat.Light.DarkActionBar">
<style name="BaseTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<item name="windowNoTitle">true</item>
<item name="windowActionBar">false</item>
<item name="colorPrimary">@color/primary</item>
@@ -27,70 +18,10 @@
<item name="android:colorActivatedHighlight">@android:color/transparent</item>
<item name="android:textCursorDrawable">@null</item>
<item name="popupTheme">@style/ThemeOverlay.AppCompat.Light</item>
<item name="buttonStyle">@style/ButtonStyle</item>
</style>
<!-- Dark theme -->
<style name="DarkTheme" parent="DarkTheme.Base">
</style>
<style name="DarkTheme.Splash" parent="DarkTheme.Base">
<item name="android:windowBackground">@drawable/splash_screen_dark</item>
<item name="android:windowNoTitle">true</item>
</style>
<style name="DarkTheme.Base" parent="Theme.AppCompat">
<item name="windowNoTitle">true</item>
<item name="windowActionBar">false</item>
<item name="colorPrimaryDark">@color/dark_notificationBar</item>
<item name="colorAccent">@color/dark_primary</item>
<item name="colorControlNormal">@color/dark_border</item>
<item name="android:navigationBarColor">@color/dark_navigationBarBackground</item>
<item name="windowActionModeOverlay">true</item>
<item name="android:colorActivatedHighlight">@android:color/transparent</item>
<item name="android:textCursorDrawable">@null</item>
<item name="popupTheme">@style/ThemeOverlay.AppCompat</item>
</style>
<!-- Black theme -->
<style name="BlackTheme" parent="BlackTheme.Base">
</style>
<style name="BlackTheme.Splash" parent="DarkTheme.Splash">
</style>
<style name="BlackTheme.Base" parent="DarkTheme.Base">
<item name="android:windowBackground">@android:color/black</item>
<item name="colorPrimary">@android:color/black</item>
<item name="colorPrimaryDark">@android:color/black</item>
<item name="colorControlNormal">@color/black_border</item>
<item name="android:navigationBarColor">@android:color/transparent</item>
</style>
<!-- Nord theme -->
<style name="NordTheme" parent="NordTheme.Base">
</style>
<style name="NordTheme.Splash" parent="DarkTheme.Splash">
</style>
<style name="NordTheme.Base" parent="DarkTheme.Base">
<item name="android:windowBackground">@color/nord_background</item>
<item name="android:actionMenuTextColor">@color/nord_actionBarText</item>
<item name="android:textColor">@color/nord_text</item>
<item name="colorAccent">@color/nord_primary</item>
<item name="colorPrimary">@color/nord_actionBar</item>
<item name="colorPrimaryDark">@color/nord_notificationBar</item>
<item name="colorControlNormal">@color/nord_border</item>
<item name="android:navigationBarColor">@color/nord_navigationBarBackground</item>
<item name="colorBackgroundFloating">@color/nord_dialogBackground</item>
<item name="android:colorBackgroundFloating" tools:targetApi="23">@color/nord_dialogBackground</item>
<item name="popupTheme">@style/NordTheme.Popup</item>
<item name="colorButtonNormal">@color/nord_buttonBackground</item>
<item name="android:colorButtonNormal">@color/nord_buttonBackground</item>
</style>
<style name="NordTheme.Popup" parent="ThemeOverlay.AppCompat">
<item name="android:colorBackground">@color/nord_popupBackground</item>
<item name="android:textColor">@color/nord_popupText</item>
</style>
<style name="ButtonStyle" parent="Widget.AppCompat.Button">
<item name="android:textAllCaps">false</item>
</style>
</resources>

View File

@@ -8,7 +8,6 @@ using Android.App;
using Android.App.Assist;
using Android.Content;
using Android.Content.PM;
using Android.Content.Res;
using Android.Nfc;
using Android.OS;
using Android.Provider;
@@ -641,18 +640,10 @@ namespace Bit.Droid.Services
public bool AutofillAccessibilityServiceRunning()
{
try
{
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
var manager = activity.GetSystemService(Context.ActivityService) as ActivityManager;
var services = manager.GetRunningServices(int.MaxValue);
return services.Any(s => s.Process.ToLowerInvariant().Contains("bitwarden") &&
s.Service.ClassName.ToLowerInvariant().Contains("accessibilityservice"));
}
catch
{
return false;
}
var enabledServices = Settings.Secure.GetString(Application.Context.ContentResolver,
Settings.Secure.EnabledAccessibilityServices);
return Application.Context.PackageName != null &&
(enabledServices?.Contains(Application.Context.PackageName) ?? false);
}
public bool AutofillAccessibilityOverlayPermitted()
@@ -741,21 +732,6 @@ namespace Bit.Droid.Services
}
}
public bool UsingDarkTheme()
{
try
{
if (Build.VERSION.SdkInt >= BuildVersionCodes.Q)
{
var app = CrossCurrentActivity.Current.AppContext;
var uiModeFlags = app.Resources.Configuration.UiMode & UiMode.NightMask;
return uiModeFlags == UiMode.NightYes;
}
}
catch { }
return false;
}
public long GetActiveTime()
{
// Returns milliseconds since the system was booted, and includes deep sleep. This clock is guaranteed to

View File

@@ -0,0 +1,71 @@
using Android.Graphics;
using Bit.App.Utilities;
using Xamarin.Forms.Platform.Android;
namespace Bit.Droid.Utilities
{
public class ThemeHelpers
{
public static bool LightTheme = true;
public static Color PrimaryColor
{
get => ThemeManager.GetResourceColor("PrimaryColor").ToAndroid();
}
public static Color MutedColor
{
get => ThemeManager.GetResourceColor("MutedColor").ToAndroid();
}
public static Color BackgroundColor
{
get => ThemeManager.GetResourceColor("BackgroundColor").ToAndroid();
}
public static Color NavBarBackgroundColor
{
get => ThemeManager.GetResourceColor("NavigationBarBackgroundColor").ToAndroid();
}
public static Color FabColor
{
get => ThemeManager.GetResourceColor("FabColor").ToAndroid();
}
public static Color SwitchOnColor
{
get => ThemeManager.GetResourceColor("SwitchOnColor").ToAndroid();
}
public static Color SwitchThumbColor
{
get => ThemeManager.GetResourceColor("SwitchThumbColor").ToAndroid();
}
public static void SetAppearance(string theme, bool osDarkModeEnabled)
{
SetThemeVariables(theme, osDarkModeEnabled);
}
public static int GetDialogTheme()
{
if (LightTheme)
{
return Android.Resource.Style.ThemeMaterialLightDialog;
}
return Android.Resource.Style.ThemeMaterialDialog;
}
private static void SetThemeVariables(string theme, bool osDarkModeEnabled)
{
if (string.IsNullOrWhiteSpace(theme) && osDarkModeEnabled)
{
theme = "dark";
}
if (theme == "dark" || theme == "black" || theme == "nord")
{
LightTheme = false;
}
else
{
LightTheme = true;
}
}
}
}

View File

@@ -42,7 +42,6 @@ namespace Bit.App.Abstractions
void OpenAccessibilitySettings();
void OpenAccessibilityOverlayPermissionSettings();
void OpenAutofillSettings();
bool UsingDarkTheme();
long GetActiveTime();
void CloseMainApp();
bool SupportsFido2();

View File

@@ -13,11 +13,11 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AppCenter.Crashes" Version="4.3.0" />
<PackageReference Include="Microsoft.AppCenter.Crashes" Version="4.4.0" />
<PackageReference Include="Plugin.Fingerprint" Version="2.1.4" />
<PackageReference Include="Xamarin.Essentials" Version="1.7.0" />
<PackageReference Include="Xamarin.FFImageLoading.Forms" Version="2.4.11.982" />
<PackageReference Include="Xamarin.Forms" Version="5.0.0.2083" />
<PackageReference Include="Xamarin.Forms" Version="5.0.0.2125" />
<PackageReference Include="ZXing.Net.Mobile" Version="2.4.1" />
<PackageReference Include="ZXing.Net.Mobile.Forms" Version="2.4.1" />
</ItemGroup>

View File

@@ -17,21 +17,12 @@ namespace Bit.App
{
public partial class App : Application
{
private readonly MobileI18nService _i18nService;
private readonly IUserService _userService;
private readonly IBroadcasterService _broadcasterService;
private readonly IMessagingService _messagingService;
private readonly IStateService _stateService;
private readonly IVaultTimeoutService _vaultTimeoutService;
private readonly ISyncService _syncService;
private readonly ITokenService _tokenService;
private readonly ICryptoService _cryptoService;
private readonly ICipherService _cipherService;
private readonly IFolderService _folderService;
private readonly ICollectionService _collectionService;
private readonly ISettingsService _settingsService;
private readonly IPasswordGenerationService _passwordGenerationService;
private readonly ISearchService _searchService;
private readonly IPlatformUtilsService _platformUtilsService;
private readonly IAuthService _authService;
private readonly IStorageService _storageService;
@@ -54,20 +45,10 @@ namespace Bit.App
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
_vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
_syncService = ServiceContainer.Resolve<ISyncService>("syncService");
_tokenService = ServiceContainer.Resolve<ITokenService>("tokenService");
_cryptoService = ServiceContainer.Resolve<ICryptoService>("cryptoService");
_cipherService = ServiceContainer.Resolve<ICipherService>("cipherService");
_folderService = ServiceContainer.Resolve<IFolderService>("folderService");
_settingsService = ServiceContainer.Resolve<ISettingsService>("settingsService");
_collectionService = ServiceContainer.Resolve<ICollectionService>("collectionService");
_searchService = ServiceContainer.Resolve<ISearchService>("searchService");
_authService = ServiceContainer.Resolve<IAuthService>("authService");
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
_secureStorageService = ServiceContainer.Resolve<IStorageService>("secureStorageService");
_passwordGenerationService = ServiceContainer.Resolve<IPasswordGenerationService>(
"passwordGenerationService");
_i18nService = ServiceContainer.Resolve<II18nService>("i18nService") as MobileI18nService;
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
Bootstrap();
@@ -219,6 +200,7 @@ namespace Bit.App
private async void ResumedAsync()
{
UpdateTheme();
await _vaultTimeoutService.CheckVaultTimeoutAsync();
_messagingService.Send("startEventTimer");
await ClearCacheIfNeededAsync();
@@ -240,22 +222,7 @@ namespace Bit.App
private async Task LogOutAsync(bool expired)
{
var userId = await _userService.GetUserIdAsync();
await Task.WhenAll(
_syncService.SetLastSyncAsync(DateTime.MinValue),
_tokenService.ClearTokenAsync(),
_cryptoService.ClearKeysAsync(),
_userService.ClearAsync(),
_settingsService.ClearAsync(userId),
_cipherService.ClearAsync(userId),
_folderService.ClearAsync(userId),
_collectionService.ClearAsync(userId),
_passwordGenerationService.ClearAsync(),
_vaultTimeoutService.ClearAsync(),
_stateService.PurgeAsync(),
_deviceActionService.ClearCacheAsync());
_vaultTimeoutService.BiometricLocked = true;
_searchService.ClearIndex();
await AppHelpers.LogOutAsync();
_authService.LogOut(() =>
{
Current.MainPage = new HomePage();
@@ -309,12 +276,7 @@ namespace Bit.App
{
return;
}
// Will only ever be null - look to remove this in the future
var vaultTimeout = _platformUtilsService.LockTimeout();
if (vaultTimeout == null)
{
vaultTimeout = await _storageService.GetAsync<int?>(Constants.VaultTimeoutKey);
}
var vaultTimeout = await _storageService.GetAsync<int?>(Constants.VaultTimeoutKey);
vaultTimeout = vaultTimeout.GetValueOrDefault(-1);
if (vaultTimeout == 0)
{
@@ -376,6 +338,10 @@ namespace Bit.App
InitializeComponent();
SetCulture();
ThemeManager.SetTheme(Device.RuntimePlatform == Device.Android, Current.Resources);
Current.RequestedThemeChanged += (s, a) =>
{
UpdateTheme();
};
Current.MainPage = new HomePage();
var mainPageTask = SetMainPageAsync();
ServiceContainer.Resolve<MobilePlatformUtilsService>("platformUtilsService").Init();
@@ -398,6 +364,15 @@ namespace Bit.App
});
}
private void UpdateTheme()
{
Device.BeginInvokeOnMainThread(() =>
{
ThemeManager.SetTheme(Device.RuntimePlatform == Device.Android, Current.Resources);
_messagingService.Send("updatedTheme");
});
}
private async Task LockedAsync(bool autoPromptBiometric)
{
await _stateService.PurgeAsync();

View File

@@ -14,6 +14,7 @@
<u:IconGlyphConverter x:Key="iconGlyphConverter"/>
<u:IconImageConverter x:Key="iconImageConverter"/>
<u:InverseBoolConverter x:Key="inverseBool" />
<u:StringHasValueConverter x:Key="stringHasValueConverter" />
</Grid.Resources>
<Grid.RowDefinitions>
@@ -72,7 +73,9 @@
Grid.Row="1"
Grid.ColumnSpan="3"
StyleClass="list-subtitle, list-subtitle-platform"
Text="{Binding Cipher.SubTitle}" />
Text="{Binding Cipher.SubTitle}"
IsVisible="{Binding Source={RelativeSource Self}, Path=Text,
Converter={StaticResource stringHasValueConverter}}"/>
<controls:FaLabel
Grid.Column="1"
Grid.Row="0"

View File

@@ -5,7 +5,7 @@ namespace Bit.App.Controls
public class ExtendedSlider : Slider
{
public static readonly BindableProperty ThumbBorderColorProperty = BindableProperty.Create(
nameof(ThumbBorderColor), typeof(Color), typeof(ExtendedSlider), Color.Gray);
nameof(ThumbBorderColor), typeof(Color), typeof(ExtendedSlider), Color.Default);
public Color ThumbBorderColor
{

View File

@@ -0,0 +1,25 @@
using Xamarin.Forms;
namespace Bit.App.Controls
{
public class ExtendedStepper : Stepper
{
public static readonly BindableProperty StepperBackgroundColorProperty = BindableProperty.Create(
nameof(StepperBackgroundColor), typeof(Color), typeof(ExtendedStepper), Color.Default);
public static readonly BindableProperty StepperForegroundColorProperty = BindableProperty.Create(
nameof(StepperForegroundColor), typeof(Color), typeof(ExtendedStepper), Color.Default);
public Color StepperBackgroundColor
{
get => (Color)GetValue(StepperBackgroundColorProperty);
set => SetValue(StepperBackgroundColorProperty, value);
}
public Color StepperForegroundColor
{
get => (Color)GetValue(StepperForegroundColorProperty);
set => SetValue(StepperForegroundColorProperty, value);
}
}
}

View File

@@ -0,0 +1,10 @@
using System;
using Xamarin.Forms;
namespace Bit.App.Controls
{
public class SelectableLabel : Label
{
}
}

View File

@@ -1,11 +0,0 @@
using Xamarin.Forms;
namespace Bit.App.Effects
{
public class SelectableLabelEffect : RoutingEffect
{
public SelectableLabelEffect()
: base("Bitwarden.SelectableLabelEffect")
{ }
}
}

View File

@@ -0,0 +1,187 @@
using Bit.App.Resources;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Bit.App.Abstractions;
using Bit.Core.Models.Domain;
using Xamarin.Essentials;
namespace Bit.App.Pages
{
public class BaseChangePasswordViewModel : BaseViewModel
{
protected readonly IPlatformUtilsService _platformUtilsService;
protected readonly IUserService _userService;
protected readonly IPolicyService _policyService;
protected readonly IPasswordGenerationService _passwordGenerationService;
protected readonly II18nService _i18nService;
protected readonly ICryptoService _cryptoService;
protected readonly IDeviceActionService _deviceActionService;
protected readonly IApiService _apiService;
protected readonly ISyncService _syncService;
private bool _showPassword;
private bool _isPolicyInEffect;
private string _policySummary;
private MasterPasswordPolicyOptions _policy;
protected BaseChangePasswordViewModel()
{
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_userService = ServiceContainer.Resolve<IUserService>("userService");
_policyService = ServiceContainer.Resolve<IPolicyService>("policyService");
_passwordGenerationService =
ServiceContainer.Resolve<IPasswordGenerationService>("passwordGenerationService");
_i18nService = ServiceContainer.Resolve<II18nService>("i18nService");
_cryptoService = ServiceContainer.Resolve<ICryptoService>("cryptoService");
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
_apiService = ServiceContainer.Resolve<IApiService>("apiService");
_syncService = ServiceContainer.Resolve<ISyncService>("syncService");
}
public bool ShowPassword
{
get => _showPassword;
set => SetProperty(ref _showPassword, value,
additionalPropertyNames: new[] { nameof(ShowPasswordIcon) });
}
public bool IsPolicyInEffect
{
get => _isPolicyInEffect;
set => SetProperty(ref _isPolicyInEffect, value);
}
public string PolicySummary
{
get => _policySummary;
set => SetProperty(ref _policySummary, value);
}
public MasterPasswordPolicyOptions Policy
{
get => _policy;
set => SetProperty(ref _policy, value);
}
public string ShowPasswordIcon => ShowPassword ? "" : "";
public string MasterPassword { get; set; }
public string ConfirmMasterPassword { get; set; }
public string Hint { get; set; }
public async Task InitAsync(bool forceSync = false)
{
if (forceSync)
{
var task = Task.Run(async () => await _syncService.FullSyncAsync(true));
await task.ContinueWith(async (t) => await CheckPasswordPolicy());
}
else
{
await CheckPasswordPolicy();
}
}
private async Task CheckPasswordPolicy()
{
Policy = await _policyService.GetMasterPasswordPolicyOptions();
IsPolicyInEffect = Policy?.InEffect() ?? false;
if (!IsPolicyInEffect)
{
return;
}
var bullet = "\n" + "".PadLeft(4) + "\u2022 ";
var sb = new StringBuilder();
sb.Append(_i18nService.T("MasterPasswordPolicyInEffect"));
if (Policy.MinComplexity > 0)
{
sb.Append(bullet)
.Append(string.Format(_i18nService.T("PolicyInEffectMinComplexity"), Policy.MinComplexity));
}
if (Policy.MinLength > 0)
{
sb.Append(bullet).Append(string.Format(_i18nService.T("PolicyInEffectMinLength"), Policy.MinLength));
}
if (Policy.RequireUpper)
{
sb.Append(bullet).Append(_i18nService.T("PolicyInEffectUppercase"));
}
if (Policy.RequireLower)
{
sb.Append(bullet).Append(_i18nService.T("PolicyInEffectLowercase"));
}
if (Policy.RequireNumbers)
{
sb.Append(bullet).Append(_i18nService.T("PolicyInEffectNumbers"));
}
if (Policy.RequireSpecial)
{
sb.Append(bullet).Append(string.Format(_i18nService.T("PolicyInEffectSpecial"), "!@#$%^&*"));
}
PolicySummary = sb.ToString();
}
protected async Task<bool> ValidateMasterPasswordAsync()
{
if (Connectivity.NetworkAccess == NetworkAccess.None)
{
await _platformUtilsService.ShowDialogAsync(AppResources.InternetConnectionRequiredMessage,
AppResources.InternetConnectionRequiredTitle, AppResources.Ok);
return false;
}
if (string.IsNullOrWhiteSpace(MasterPassword))
{
await _platformUtilsService.ShowDialogAsync(
string.Format(AppResources.ValidationFieldRequired, AppResources.MasterPassword),
AppResources.AnErrorHasOccurred, AppResources.Ok);
return false;
}
if (IsPolicyInEffect)
{
var userInput = await GetPasswordStrengthUserInput();
var passwordStrength = _passwordGenerationService.PasswordStrength(MasterPassword, userInput);
if (!await _policyService.EvaluateMasterPassword(passwordStrength.Score, MasterPassword, Policy))
{
await _platformUtilsService.ShowDialogAsync(AppResources.MasterPasswordPolicyValidationMessage,
AppResources.MasterPasswordPolicyValidationTitle, AppResources.Ok);
return false;
}
}
else
{
if (MasterPassword.Length < 8)
{
await _platformUtilsService.ShowDialogAsync(AppResources.MasterPasswordLengthValMessage,
AppResources.MasterPasswordPolicyValidationTitle, AppResources.Ok);
return false;
}
}
if (MasterPassword != ConfirmMasterPassword)
{
await _platformUtilsService.ShowDialogAsync(AppResources.MasterPasswordConfirmationValMessage,
AppResources.AnErrorHasOccurred, AppResources.Ok);
return false;
}
return true;
}
private async Task<List<string>> GetPasswordStrengthUserInput()
{
var email = await _userService.GetEmailAsync();
List<string> userInput = null;
var atPosition = email.IndexOf('@');
if (atPosition > -1)
{
var rx = new Regex("/[^A-Za-z0-9]/", RegexOptions.Compiled);
var data = rx.Split(email.Substring(0, atPosition).Trim().ToLower());
userInput = new List<string>(data);
}
return userInput;
}
}
}

View File

@@ -40,11 +40,12 @@
HorizontalTextAlignment="Center"></Label>
<StackLayout Spacing="5">
<Button Text="{u:I18n LogIn}"
Clicked="LogIn_Clicked"></Button>
StyleClass="btn-primary"
Clicked="LogIn_Clicked" />
<Button Text="{u:I18n CreateAccount}"
Clicked="Register_Clicked"></Button>
Clicked="Register_Clicked" />
<Button Text="{u:I18n LogInSso}"
Clicked="LogInSso_Clicked"></Button>
Clicked="LogInSso_Clicked" />
</StackLayout>
</StackLayout>
</StackLayout>

View File

@@ -13,11 +13,13 @@ namespace Bit.App.Pages
private readonly HomeViewModel _vm;
private readonly AppOptions _appOptions;
private IMessagingService _messagingService;
private IBroadcasterService _broadcasterService;
public HomePage(AppOptions appOptions = null)
{
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
_messagingService.Send("showStatusBar", false);
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
_appOptions = appOptions;
InitializeComponent();
_vm = BindingContext as HomeViewModel;
@@ -26,7 +28,7 @@ namespace Bit.App.Pages
_vm.StartRegisterAction = () => Device.BeginInvokeOnMainThread(async () => await StartRegisterAsync());
_vm.StartSsoLoginAction = () => Device.BeginInvokeOnMainThread(async () => await StartSsoLoginAsync());
_vm.StartEnvironmentAction = () => Device.BeginInvokeOnMainThread(async () => await StartEnvironmentAsync());
_logo.Source = !ThemeManager.UsingLightTheme ? "logo_white.png" : "logo.png";
UpdateLogo();
}
public async Task DismissRegisterPageAndLogInAsync(string email)
@@ -39,6 +41,27 @@ namespace Bit.App.Pages
{
base.OnAppearing();
_messagingService.Send("showStatusBar", false);
_broadcasterService.Subscribe(nameof(HomePage), async (message) =>
{
if (message.Command == "updatedTheme")
{
Device.BeginInvokeOnMainThread(() =>
{
UpdateLogo();
});
}
});
}
protected override void OnDisappearing()
{
base.OnDisappearing();
_broadcasterService.Unsubscribe(nameof(HomePage));
}
private void UpdateLogo()
{
_logo.Source = !ThemeManager.UsingLightTheme ? "logo_white.png" : "logo.png";
}
private void Close_Clicked(object sender, EventArgs e)

View File

@@ -120,7 +120,9 @@
IsVisible="{Binding BiometricIntegrityValid, Converter={StaticResource inverseBool}}" />
<Button Text="{Binding BiometricButtonText}" Clicked="Biometric_Clicked"
IsVisible="{Binding BiometricButtonVisible}"></Button>
<Button Text="{u:I18n Unlock}" Clicked="Unlock_Clicked"></Button>
<Button Text="{u:I18n Unlock}"
StyleClass="btn-primary"
Clicked="Unlock_Clicked" />
</StackLayout>
</StackLayout>
</ScrollView>

View File

@@ -317,7 +317,9 @@ namespace Bit.App.Pages
ShowPassword = !ShowPassword;
var page = (Page as LockPage);
var entry = PinLock ? page.PinEntry : page.MasterPasswordEntry;
var str = PinLock ? Pin : MasterPassword;
entry.Focus();
entry.CursorPosition = String.IsNullOrEmpty(str) ? 0 : str.Length;
}
public async Task PromptBiometricAsync()

View File

@@ -82,7 +82,9 @@
</Grid>
</StackLayout>
<StackLayout Padding="10, 0">
<Button Text="{u:I18n LogIn}" Clicked="LogIn_Clicked" />
<Button Text="{u:I18n LogIn}"
StyleClass="btn-primary"
Clicked="LogIn_Clicked" />
</StackLayout>
</StackLayout>
</ScrollView>

View File

@@ -29,6 +29,8 @@ namespace Bit.App.Pages
_vm.Page = this;
_vm.StartTwoFactorAction = () => Device.BeginInvokeOnMainThread(async () => await StartTwoFactorAsync());
_vm.LogInSuccessAction = () => Device.BeginInvokeOnMainThread(async () => await LogInSuccessAsync());
_vm.UpdateTempPasswordAction =
() => Device.BeginInvokeOnMainThread(async () => await UpdateTempPasswordAsync());
_vm.CloseAction = async () =>
{
_messagingService.Send("showStatusBar", false);
@@ -122,5 +124,11 @@ namespace Bit.App.Pages
var previousPage = await AppHelpers.ClearPreviousPage();
Application.Current.MainPage = new TabsPage(_appOptions, previousPage);
}
private async Task UpdateTempPasswordAsync()
{
var page = new UpdateTempPasswordPage();
await Navigation.PushModalAsync(new NavigationPage(page));
}
}
}

View File

@@ -8,11 +8,6 @@ using System;
using System.Threading.Tasks;
using Bit.App.Utilities;
using Xamarin.Forms;
using Newtonsoft.Json;
using System.Text;
using Xamarin.Essentials;
using System.Text.RegularExpressions;
using Bit.Core.Services;
namespace Bit.App.Pages
{
@@ -78,6 +73,7 @@ namespace Bit.App.Pages
public bool RememberEmail { get; set; }
public Action StartTwoFactorAction { get; set; }
public Action LogInSuccessAction { get; set; }
public Action UpdateTempPasswordAction { get; set; }
public Action CloseAction { get; set; }
protected override II18nService i18nService => _i18nService;
@@ -100,15 +96,14 @@ namespace Bit.App.Pages
if (Xamarin.Essentials.Connectivity.NetworkAccess == Xamarin.Essentials.NetworkAccess.None)
{
await _platformUtilsService.ShowDialogAsync(AppResources.InternetConnectionRequiredMessage,
AppResources.InternetConnectionRequiredTitle);
AppResources.InternetConnectionRequiredTitle, AppResources.Ok);
return;
}
if (string.IsNullOrWhiteSpace(Email))
{
await _platformUtilsService.ShowDialogAsync(
string.Format(AppResources.ValidationFieldRequired, AppResources.EmailAddress),
AppResources.AnErrorHasOccurred,
AppResources.Ok);
AppResources.AnErrorHasOccurred, AppResources.Ok);
return;
}
if (!Email.Contains("@"))
@@ -121,8 +116,7 @@ namespace Bit.App.Pages
{
await _platformUtilsService.ShowDialogAsync(
string.Format(AppResources.ValidationFieldRequired, AppResources.MasterPassword),
AppResources.AnErrorHasOccurred,
AppResources.Ok);
AppResources.AnErrorHasOccurred, AppResources.Ok);
return;
}
@@ -163,6 +157,10 @@ namespace Bit.App.Pages
{
StartTwoFactorAction?.Invoke();
}
else if (response.ForcePasswordReset)
{
UpdateTempPasswordAction?.Invoke();
}
else
{
var disableFavicon = await _storageService.GetAsync<bool?>(Constants.DisableFaviconKey);
@@ -179,7 +177,7 @@ namespace Bit.App.Pages
if (e?.Error != null)
{
await _platformUtilsService.ShowDialogAsync(e.Error.GetSingleMessage(),
AppResources.AnErrorHasOccurred);
AppResources.AnErrorHasOccurred, AppResources.Ok);
}
}
}
@@ -187,7 +185,9 @@ namespace Bit.App.Pages
public void TogglePassword()
{
ShowPassword = !ShowPassword;
(Page as LoginPage).MasterPasswordEntry.Focus();
var entry = (Page as LoginPage).MasterPasswordEntry;
entry.Focus();
entry.CursorPosition = String.IsNullOrEmpty(MasterPassword) ? 0 : MasterPassword.Length;
}
}
}

View File

@@ -37,7 +37,8 @@
</StackLayout>
<StackLayout Padding="10, 0">
<Button Text="{u:I18n LogIn}"
Clicked="LogIn_Clicked"></Button>
StyleClass="btn-primary"
Clicked="LogIn_Clicked"></Button>
</StackLayout>
</StackLayout>
</ScrollView>

View File

@@ -32,6 +32,8 @@ namespace Bit.App.Pages
_vm.StartSetPasswordAction = () =>
Device.BeginInvokeOnMainThread(async () => await StartSetPasswordAsync());
_vm.SsoAuthSuccessAction = () => Device.BeginInvokeOnMainThread(async () => await SsoAuthSuccessAsync());
_vm.UpdateTempPasswordAction =
() => Device.BeginInvokeOnMainThread(async () => await UpdateTempPasswordAsync());
_vm.CloseAction = async () =>
{
_messagingService.Send("showStatusBar", false);
@@ -103,6 +105,12 @@ namespace Bit.App.Pages
var page = new SetPasswordPage(_appOptions, _vm.OrgIdentifier);
await Navigation.PushModalAsync(new NavigationPage(page));
}
private async Task UpdateTempPasswordAsync()
{
var page = new UpdateTempPasswordPage();
await Navigation.PushModalAsync(new NavigationPage(page));
}
private async Task SsoAuthSuccessAsync()
{

View File

@@ -60,6 +60,7 @@ namespace Bit.App.Pages
public Action StartSetPasswordAction { get; set; }
public Action SsoAuthSuccessAction { get; set; }
public Action CloseAction { get; set; }
public Action UpdateTempPasswordAction { get; set; }
public async Task InitAsync()
{
@@ -185,6 +186,10 @@ namespace Bit.App.Pages
else if (response.ResetMasterPassword)
{
StartSetPasswordAction?.Invoke();
}
else if (response.ForcePasswordReset)
{
UpdateTempPasswordAction?.Invoke();
}
else
{

View File

@@ -137,7 +137,7 @@
<FormattedString>
<Span Text="{u:I18n AcceptPolicies}" />
<Span Text="{u:I18n TermsOfService}"
TextColor="{StaticResource HyperlinkColor}">
TextColor="{DynamicResource HyperlinkColor}">
<Span.GestureRecognizers>
<TapGestureRecognizer Command="{Binding PoliciesClickCommand}"
CommandParameter="https://bitwarden.com/terms/" />
@@ -145,7 +145,7 @@
</Span>
<Span Text=", " />
<Span Text="{u:I18n PrivacyPolicy}"
TextColor="{StaticResource HyperlinkColor}">
TextColor="{DynamicResource HyperlinkColor}">
<Span.GestureRecognizers>
<TapGestureRecognizer Command="{Binding PoliciesClickCommand}"
CommandParameter="https://bitwarden.com/privacy/" />

View File

@@ -90,44 +90,47 @@ namespace Bit.App.Pages
if (Xamarin.Essentials.Connectivity.NetworkAccess == Xamarin.Essentials.NetworkAccess.None)
{
await _platformUtilsService.ShowDialogAsync(AppResources.InternetConnectionRequiredMessage,
AppResources.InternetConnectionRequiredTitle);
AppResources.InternetConnectionRequiredTitle, AppResources.Ok);
return;
}
if (string.IsNullOrWhiteSpace(Email))
{
await Page.DisplayAlert(AppResources.AnErrorHasOccurred,
await _platformUtilsService.ShowDialogAsync(
string.Format(AppResources.ValidationFieldRequired, AppResources.EmailAddress),
AppResources.AnErrorHasOccurred,
AppResources.Ok);
return;
}
if (!Email.Contains("@"))
{
await Page.DisplayAlert(AppResources.AnErrorHasOccurred, AppResources.InvalidEmail, AppResources.Ok);
await _platformUtilsService.ShowDialogAsync(AppResources.InvalidEmail, AppResources.AnErrorHasOccurred,
AppResources.Ok);
return;
}
if (string.IsNullOrWhiteSpace(MasterPassword))
{
await Page.DisplayAlert(AppResources.AnErrorHasOccurred,
await _platformUtilsService.ShowDialogAsync(
string.Format(AppResources.ValidationFieldRequired, AppResources.MasterPassword),
AppResources.AnErrorHasOccurred,
AppResources.Ok);
return;
}
if (MasterPassword.Length < 8)
{
await Page.DisplayAlert(AppResources.AnErrorHasOccurred,
AppResources.MasterPasswordLengthValMessage, AppResources.Ok);
await _platformUtilsService.ShowDialogAsync(AppResources.MasterPasswordLengthValMessage,
AppResources.AnErrorHasOccurred, AppResources.Ok);
return;
}
if (MasterPassword != ConfirmMasterPassword)
{
await Page.DisplayAlert(AppResources.AnErrorHasOccurred,
AppResources.MasterPasswordConfirmationValMessage, AppResources.Ok);
await _platformUtilsService.ShowDialogAsync(AppResources.MasterPasswordConfirmationValMessage,
AppResources.AnErrorHasOccurred, AppResources.Ok);
return;
}
if (ShowTerms && !AcceptPolicies)
{
await Page.DisplayAlert(AppResources.AnErrorHasOccurred,
AppResources.AcceptPoliciesError, AppResources.Ok);
await _platformUtilsService.ShowDialogAsync(AppResources.AcceptPoliciesError,
AppResources.AnErrorHasOccurred, AppResources.Ok);
return;
}
@@ -190,7 +193,7 @@ namespace Bit.App.Pages
if (e?.Error != null)
{
await _platformUtilsService.ShowDialogAsync(e.Error.GetSingleMessage(),
AppResources.AnErrorHasOccurred);
AppResources.AnErrorHasOccurred, AppResources.Ok);
}
}
}
@@ -198,13 +201,17 @@ namespace Bit.App.Pages
public void TogglePassword()
{
ShowPassword = !ShowPassword;
(Page as RegisterPage).MasterPasswordEntry.Focus();
var entry = (Page as RegisterPage).MasterPasswordEntry;
entry.Focus();
entry.CursorPosition = String.IsNullOrEmpty(MasterPassword) ? 0 : MasterPassword.Length;
}
public void ToggleConfirmPassword()
{
ShowPassword = !ShowPassword;
(Page as RegisterPage).ConfirmMasterPasswordEntry.Focus();
var entry = (Page as RegisterPage).ConfirmMasterPasswordEntry;
entry.Focus();
entry.CursorPosition = String.IsNullOrEmpty(ConfirmMasterPassword) ? 0 : ConfirmMasterPassword.Length;
}
}
}

View File

@@ -90,6 +90,7 @@ namespace Bit.App.Pages
public string ConfirmMasterPassword { get; set; }
public string Hint { get; set; }
public Action SetPasswordSuccessAction { get; set; }
public Action UpdateTempPasswordAction { get; set; }
public Action CloseAction { get; set; }
public string OrgIdentifier { get; set; }
public string OrgId { get; set; }
@@ -221,7 +222,6 @@ namespace Bit.App.Pages
}
await _deviceActionService.HideLoadingAsync();
SetPasswordSuccessAction?.Invoke();
}
catch (ApiException e)

View File

@@ -40,6 +40,8 @@ namespace Bit.App.Pages
Device.BeginInvokeOnMainThread(async () => await StartSetPasswordAsync());
_vm.TwoFactorAuthSuccessAction = () =>
Device.BeginInvokeOnMainThread(async () => await TwoFactorAuthSuccessAsync());
_vm.UpdateTempPasswordAction =
() => Device.BeginInvokeOnMainThread(async () => await UpdateTempPasswordAsync());
_vm.CloseAction = async () => await Navigation.PopModalAsync();
DuoWebView = _duoWebView;
if (Device.RuntimePlatform == Device.Android)
@@ -189,6 +191,12 @@ namespace Bit.App.Pages
var page = new SetPasswordPage(_appOptions, _orgIdentifier);
await Navigation.PushModalAsync(new NavigationPage(page));
}
private async Task UpdateTempPasswordAsync()
{
var page = new UpdateTempPasswordPage();
await Navigation.PushModalAsync(new NavigationPage(page));
}
private async Task TwoFactorAuthSuccessAsync()
{

View File

@@ -49,7 +49,7 @@ namespace Bit.App.Pages
_environmentService = ServiceContainer.Resolve<IEnvironmentService>("environmentService");
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
PageTitle = AppResources.TwoStepLogin;
SubmitCommand = new Command(async () => await SubmitAsync());
@@ -113,6 +113,7 @@ namespace Bit.App.Pages
public Action TwoFactorAuthSuccessAction { get; set; }
public Action StartSetPasswordAction { get; set; }
public Action CloseAction { get; set; }
public Action UpdateTempPasswordAction { get; set; }
public void Init()
{
@@ -203,7 +204,9 @@ namespace Bit.App.Pages
{
callbackUri = callbackUri,
data = JsonConvert.SerializeObject(providerData),
headerText = AppResources.Fido2Title,
btnText = AppResources.Fido2AuthenticateWebAuthn,
btnReturnText = AppResources.Fido2ReturnToApp,
});
var url = _webVaultUrl + "/webauthn-mobile-connector.html?" + "data=" + data +
@@ -243,12 +246,13 @@ namespace Bit.App.Pages
if (authResult != null && authResult.Properties.TryGetValue("error", out var resultError))
{
var message = AppResources.Fido2CheckBrowser + "\n\n" + resultError;
await _platformUtilsService.ShowDialogAsync(message, AppResources.AnErrorHasOccurred);
await _platformUtilsService.ShowDialogAsync(message, AppResources.AnErrorHasOccurred,
AppResources.Ok);
}
else
{
await _platformUtilsService.ShowDialogAsync(AppResources.Fido2CheckBrowser,
AppResources.AnErrorHasOccurred);
AppResources.AnErrorHasOccurred, AppResources.Ok);
}
}
}
@@ -262,14 +266,14 @@ namespace Bit.App.Pages
if (Xamarin.Essentials.Connectivity.NetworkAccess == Xamarin.Essentials.NetworkAccess.None)
{
await _platformUtilsService.ShowDialogAsync(AppResources.InternetConnectionRequiredMessage,
AppResources.InternetConnectionRequiredTitle);
AppResources.InternetConnectionRequiredTitle, AppResources.Ok);
return;
}
if (string.IsNullOrWhiteSpace(Token))
{
await _platformUtilsService.ShowDialogAsync(
string.Format(AppResources.ValidationFieldRequired, AppResources.VerificationCode),
AppResources.AnErrorHasOccurred);
AppResources.AnErrorHasOccurred, AppResources.Ok);
return;
}
if (SelectedProviderType == TwoFactorProviderType.Email ||
@@ -293,6 +297,10 @@ namespace Bit.App.Pages
{
StartSetPasswordAction?.Invoke();
}
else if (result.ForcePasswordReset)
{
UpdateTempPasswordAction?.Invoke();
}
else
{
var disableFavicon = await _storageService.GetAsync<bool?>(Constants.DisableFaviconKey);
@@ -306,7 +314,7 @@ namespace Bit.App.Pages
if (e?.Error != null)
{
await _platformUtilsService.ShowDialogAsync(e.Error.GetSingleMessage(),
AppResources.AnErrorHasOccurred);
AppResources.AnErrorHasOccurred, AppResources.Ok);
}
}
}
@@ -344,7 +352,7 @@ namespace Bit.App.Pages
if (Xamarin.Essentials.Connectivity.NetworkAccess == Xamarin.Essentials.NetworkAccess.None)
{
await _platformUtilsService.ShowDialogAsync(AppResources.InternetConnectionRequiredMessage,
AppResources.InternetConnectionRequiredTitle);
AppResources.InternetConnectionRequiredTitle, AppResources.Ok);
return false;
}
try
@@ -375,7 +383,8 @@ namespace Bit.App.Pages
{
await _deviceActionService.HideLoadingAsync();
}
await _platformUtilsService.ShowDialogAsync(AppResources.VerificationEmailNotSent);
await _platformUtilsService.ShowDialogAsync(AppResources.VerificationEmailNotSent,
AppResources.AnErrorHasOccurred, AppResources.Ok);
return false;
}
}

View File

@@ -0,0 +1,163 @@
<?xml version="1.0" encoding="utf-8" ?>
<pages:BaseContentPage
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Bit.App.Pages.UpdateTempPasswordPage"
xmlns:pages="clr-namespace:Bit.App.Pages"
xmlns:controls="clr-namespace:Bit.App.Controls"
xmlns:u="clr-namespace:Bit.App.Utilities"
x:DataType="pages:UpdateTempPasswordPageViewModel"
Title="{Binding PageTitle}">
<ContentPage.BindingContext>
<pages:UpdateTempPasswordPageViewModel />
</ContentPage.BindingContext>
<ContentPage.Resources>
<ResourceDictionary>
<u:InverseBoolConverter x:Key="inverseBool" />
</ResourceDictionary>
</ContentPage.Resources>
<ContentPage.ToolbarItems>
<ToolbarItem Text="{u:I18n LogOut}" Clicked="LogOut_Clicked" Order="Primary" Priority="-1" />
<ToolbarItem Text="{u:I18n Submit}" Clicked="Submit_Clicked" />
</ContentPage.ToolbarItems>
<ScrollView>
<StackLayout
x:Name="_mainLayout"
Spacing="20">
<StackLayout StyleClass="box">
<Grid Margin="0, 12, 0, 0"
RowSpacing="0"
ColumnSpacing="0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Frame Padding="10"
Margin="0"
HasShadow="False"
BackgroundColor="Transparent"
BorderColor="Accent">
<Label
Text="{u:I18n UpdateMasterPasswordWarning}"
StyleClass="text-muted, text-sm, text-bold"
HorizontalTextAlignment="Center" />
</Frame>
</Grid>
<Grid IsVisible="{Binding IsPolicyInEffect}"
Margin="0, 12, 0, 0"
RowSpacing="0"
ColumnSpacing="0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Frame Padding="10"
Margin="0"
HasShadow="False"
BackgroundColor="Transparent"
BorderColor="Accent">
<Label
Text="{Binding PolicySummary}"
StyleClass="text-muted, text-sm, text-bold"
HorizontalTextAlignment="Start" />
</Frame>
</Grid>
<Grid StyleClass="box-row">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Label
Text="{u:I18n MasterPassword}"
StyleClass="box-label"
Grid.Row="0"
Grid.Column="0" />
<controls:MonoEntry
x:Name="_masterPassword"
Text="{Binding MasterPassword}"
StyleClass="box-value"
IsSpellCheckEnabled="False"
IsTextPredictionEnabled="False"
IsPassword="{Binding ShowPassword, Converter={StaticResource inverseBool}}"
Grid.Row="1"
Grid.Column="0" />
<controls:FaButton
StyleClass="box-row-button, box-row-button-platform"
Text="{Binding ShowPasswordIcon}"
Command="{Binding TogglePasswordCommand}"
Grid.Row="0"
Grid.Column="1"
Grid.RowSpan="2"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n ToggleVisibility}" />
</Grid>
</StackLayout>
<StackLayout StyleClass="box">
<Grid StyleClass="box-row">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Label
Text="{u:I18n RetypeMasterPassword}"
StyleClass="box-label"
Grid.Row="0"
Grid.Column="0" />
<controls:MonoEntry
x:Name="_confirmMasterPassword"
Text="{Binding ConfirmMasterPassword}"
StyleClass="box-value"
IsSpellCheckEnabled="False"
IsTextPredictionEnabled="False"
IsPassword="{Binding ShowPassword, Converter={StaticResource inverseBool}}"
Grid.Row="1"
Grid.Column="0" />
<controls:FaButton
StyleClass="box-row-button, box-row-button-platform"
Text="{Binding ShowPasswordIcon}"
Command="{Binding ToggleConfirmPasswordCommand}"
Grid.Row="0"
Grid.Column="1"
Grid.RowSpan="2"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n ToggleVisibility}" />
</Grid>
<StackLayout StyleClass="box-row">
<Label
Text="{u:I18n MasterPasswordHint}"
StyleClass="box-label" />
<Entry
x:Name="_hint"
Text="{Binding Hint}"
StyleClass="box-value"
ReturnType="Go"
ReturnCommand="{Binding SubmitCommand}" />
</StackLayout>
<Label
Text="{u:I18n MasterPasswordHintDescription}"
StyleClass="box-footer-label" />
</StackLayout>
</StackLayout>
</ScrollView>
</pages:BaseContentPage>

View File

@@ -0,0 +1,89 @@
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using System;
using Bit.App.Resources;
using Xamarin.Forms;
namespace Bit.App.Pages
{
public partial class UpdateTempPasswordPage : BaseContentPage
{
private readonly IMessagingService _messagingService;
private readonly IPlatformUtilsService _platformUtilsService;
private readonly UpdateTempPasswordPageViewModel _vm;
private readonly string _pageName;
public UpdateTempPasswordPage()
{
// Service Init
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
// Service Use
_messagingService.Send("showStatusBar", true);
// Binding
InitializeComponent();
_pageName = string.Concat(nameof(UpdateTempPasswordPage), "_", DateTime.UtcNow.Ticks);
_vm = BindingContext as UpdateTempPasswordPageViewModel;
_vm.Page = this;
SetActivityIndicator();
// Actions Declaration
_vm.LogOutAction = () =>
{
_messagingService.Send("logout");
};
_vm.UpdateTempPasswordSuccessAction = () => Device.BeginInvokeOnMainThread(UpdateTempPasswordSuccess);
// Link fields that will be referenced in codebehind
MasterPasswordEntry = _masterPassword;
ConfirmMasterPasswordEntry = _confirmMasterPassword;
// Return Types and Commands
_masterPassword.ReturnType = ReturnType.Next;
_masterPassword.ReturnCommand = new Command(() => _confirmMasterPassword.Focus());
_confirmMasterPassword.ReturnType = ReturnType.Next;
_confirmMasterPassword.ReturnCommand = new Command(() => _hint.Focus());
}
public Entry MasterPasswordEntry { get; set; }
public Entry ConfirmMasterPasswordEntry { get; set; }
protected override async void OnAppearing()
{
base.OnAppearing();
await LoadOnAppearedAsync(_mainLayout, true, async () =>
{
await _vm.InitAsync(true);
});
RequestFocus(_masterPassword);
}
private async void Submit_Clicked(object sender, EventArgs e)
{
if (DoOnce())
{
await _vm.SubmitAsync();
}
}
private async void LogOut_Clicked(object sender, EventArgs e)
{
if (DoOnce())
{
var confirmed = await _platformUtilsService.ShowDialogAsync(AppResources.LogoutConfirmation,
AppResources.LogOut, AppResources.Yes, AppResources.Cancel);
if (confirmed)
{
_vm.LogOutAction();
}
}
}
private void UpdateTempPasswordSuccess()
{
_messagingService.Send("logout");
}
}
}

View File

@@ -0,0 +1,90 @@
using Bit.App.Resources;
using System;
using System.Threading.Tasks;
using Bit.Core.Exceptions;
using Bit.Core.Models.Request;
using Xamarin.Forms;
namespace Bit.App.Pages
{
public class UpdateTempPasswordPageViewModel : BaseChangePasswordViewModel
{
public UpdateTempPasswordPageViewModel()
{
PageTitle = AppResources.UpdateMasterPassword;
TogglePasswordCommand = new Command(TogglePassword);
ToggleConfirmPasswordCommand = new Command(ToggleConfirmPassword);
SubmitCommand = new Command(async () => await SubmitAsync());
}
public Command SubmitCommand { get; }
public Command TogglePasswordCommand { get; }
public Command ToggleConfirmPasswordCommand { get; }
public Action UpdateTempPasswordSuccessAction { get; set; }
public Action LogOutAction { get; set; }
public void TogglePassword()
{
ShowPassword = !ShowPassword;
(Page as UpdateTempPasswordPage).MasterPasswordEntry.Focus();
}
public void ToggleConfirmPassword()
{
ShowPassword = !ShowPassword;
(Page as UpdateTempPasswordPage).ConfirmMasterPasswordEntry.Focus();
}
public async Task SubmitAsync()
{
if (!await ValidateMasterPasswordAsync())
{
return;
}
// Retrieve details for key generation
var kdf = await _userService.GetKdfAsync();
var kdfIterations = await _userService.GetKdfIterationsAsync();
var email = await _userService.GetEmailAsync();
// Create new key and hash new password
var key = await _cryptoService.MakeKeyAsync(MasterPassword, email, kdf, kdfIterations);
var masterPasswordHash = await _cryptoService.HashPasswordAsync(MasterPassword, key);
// Create new encKey for the User
var newEncKey = await _cryptoService.RemakeEncKeyAsync(key);
// Create request
var request = new UpdateTempPasswordRequest
{
Key = newEncKey.Item2.EncryptedString,
NewMasterPasswordHash = masterPasswordHash,
MasterPasswordHint = Hint
};
// Initiate API action
try
{
await _deviceActionService.ShowLoadingAsync(AppResources.UpdatingPassword);
await _apiService.PutUpdateTempPasswordAsync(request);
await _deviceActionService.HideLoadingAsync();
UpdateTempPasswordSuccessAction?.Invoke();
}
catch (ApiException e)
{
await _deviceActionService.HideLoadingAsync();
if (e?.Error != null)
{
await _platformUtilsService.ShowDialogAsync(e.Error.GetSingleMessage(),
AppResources.AnErrorHasOccurred, AppResources.Ok);
}
else
{
await _platformUtilsService.ShowDialogAsync(AppResources.UpdatePasswordError,
AppResources.AnErrorHasOccurred, AppResources.Ok);
}
}
}
}
}

View File

@@ -4,6 +4,7 @@ using Bit.Core.Utilities;
using System;
using System.Threading.Tasks;
using Bit.App.Abstractions;
using Bit.App.Utilities;
using Xamarin.Forms;
using Xamarin.Forms.PlatformConfiguration;
using Xamarin.Forms.PlatformConfiguration.iOSSpecific;
@@ -22,6 +23,7 @@ namespace Bit.App.Pages
{
if (Device.RuntimePlatform == Device.iOS)
{
On<iOS>().SetUseSafeArea(true);
On<iOS>().SetModalPresentationStyle(UIModalPresentationStyle.FullScreen);
}
}
@@ -52,7 +54,8 @@ namespace Bit.App.Pages
{
IsRunning = true,
VerticalOptions = LayoutOptions.CenterAndExpand,
HorizontalOptions = LayoutOptions.Center
HorizontalOptions = LayoutOptions.Center,
Color = ThemeManager.GetResourceColor("PrimaryColor"),
};
if (targetView != null)
{

View File

@@ -27,7 +27,12 @@ namespace Bit.App.Pages
captchaRequiredText = AppResources.CaptchaRequired,
});
var url = environmentService.GetWebVaultUrl() + "/captcha-mobile-connector.html?" + "data=" + data +
var url = environmentService.GetWebVaultUrl();
if (url == null)
{
url = "https://vault.bitwarden.com";
}
url += "/captcha-mobile-connector.html?" + "data=" + data +
"&parent=" + Uri.EscapeDataString(callbackUri) + "&v=1";
WebAuthenticatorResult authResult = null;

View File

@@ -71,6 +71,7 @@
HorizontalOptions="CenterAndExpand"
LineBreakMode="CharacterWrap" />
<Button Text="{u:I18n RegeneratePassword}"
StyleClass="btn-primary"
HorizontalOptions="FillAndExpand"
Clicked="Regenerate_Clicked"></Button>
<Button Text="{u:I18n CopyPassword}"
@@ -108,7 +109,7 @@
HorizontalTextAlignment="End"
VerticalOptions="FillAndExpand"
VerticalTextAlignment="Center" />
<Stepper
<controls:ExtendedStepper
Value="{Binding NumWords}"
Maximum="20"
Minimum="3"
@@ -239,7 +240,7 @@
HorizontalTextAlignment="End"
VerticalOptions="FillAndExpand"
VerticalTextAlignment="Center" />
<Stepper
<controls:ExtendedStepper
Value="{Binding MinNumber}"
Maximum="5"
Minimum="0"
@@ -259,7 +260,7 @@
HorizontalTextAlignment="End"
VerticalOptions="FillAndExpand"
VerticalTextAlignment="Center" />
<Stepper
<controls:ExtendedStepper
Value="{Binding MinSpecial}"
Maximum="5"
Minimum="0"

View File

@@ -1,6 +1,8 @@
using Bit.App.Resources;
using System;
using System.Threading.Tasks;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using Xamarin.Forms;
using Xamarin.Forms.PlatformConfiguration;
using Xamarin.Forms.PlatformConfiguration.iOSSpecific;
@@ -9,6 +11,8 @@ namespace Bit.App.Pages
{
public partial class GeneratorPage : BaseContentPage
{
private readonly IBroadcasterService _broadcasterService;
private GeneratorPageViewModel _vm;
private readonly bool _fromTabPage;
private readonly Action<string> _selectAction;
@@ -18,6 +22,7 @@ namespace Bit.App.Pages
{
_tabsPage = tabsPage;
InitializeComponent();
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
_vm = BindingContext as GeneratorPageViewModel;
_vm.Page = this;
_fromTabPage = fromTabPage;
@@ -60,6 +65,22 @@ namespace Bit.App.Pages
{
await InitAsync();
}
_broadcasterService.Subscribe(nameof(GeneratorPage), async (message) =>
{
if (message.Command == "updatedTheme")
{
Device.BeginInvokeOnMainThread(() =>
{
_vm.RedrawPassword();
});
}
});
}
protected override void OnDisappearing()
{
base.OnDisappearing();
_broadcasterService.Unsubscribe(nameof(GeneratorPage));
}
protected override bool OnBackButtonPressed()

View File

@@ -262,6 +262,14 @@ namespace Bit.App.Pages
await _passwordGenerationService.AddHistoryAsync(Password);
}
public void RedrawPassword()
{
if (!string.IsNullOrEmpty(_password))
{
TriggerPropertyChanged(nameof(ColoredPassword));
}
}
public async Task SaveOptionsAsync(bool regenerate = true)
{
if (!_doneIniting)

View File

@@ -54,26 +54,6 @@
x:Name="_deleteItem"
x:Key="deleteItem" />
<Style x:Key="segmentedButtonBase" TargetType="Button">
<Setter Property="HeightRequest" Value="{Binding SegmentedButtonHeight}" />
<Setter Property="FontSize" Value="{Binding SegmentedButtonFontSize}" />
<Setter Property="CornerRadius" Value="0" />
</Style>
<Style x:Key="segmentedButtonNormal" BaseResourceKey="segmentedButtonBase" TargetType="Button">
<Setter Property="TextColor" Value="{StaticResource PrimaryColor}" />
<Setter Property="FontAttributes" Value="None" />
<Setter Property="BackgroundColor" Value="Transparent" />
<Setter Property="BorderColor" Value="{StaticResource PrimaryColor}" />
<Setter Property="BorderWidth" Value="1" />
</Style>
<Style x:Key="segmentedButtonDisabled" BaseResourceKey="segmentedButtonBase" TargetType="Button">
<Setter Property="TextColor" Value="{StaticResource TitleTextColor}" />
<Setter Property="FontAttributes" Value="Bold" />
<Setter Property="BackgroundColor" Value="{StaticResource PrimaryColor}" />
<Setter Property="BorderWidth" Value="0" />
</Style>
<ScrollView x:Key="scrollView" x:Name="_scrollView">
<StackLayout Spacing="20">
<StackLayout StyleClass="box">
@@ -131,51 +111,25 @@
</Grid.ColumnDefinitions>
<Button
Text="{u:I18n TypeFile}"
StyleClass="segmented-button"
IsEnabled="{Binding IsText}"
HeightRequest="{Binding SegmentedButtonHeight}"
FontSize="{Binding SegmentedButtonFontSize}"
Clicked="FileType_Clicked"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n File}"
Grid.Column="0">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal">
<VisualState.Setters>
<Setter Property="Style"
Value="{StaticResource segmentedButtonNormal}" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Disabled">
<VisualState.Setters>
<Setter Property="Style"
Value="{StaticResource segmentedButtonDisabled}" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Button>
<Button
Text="{u:I18n TypeText}"
StyleClass="segmented-button"
IsEnabled="{Binding IsFile}"
HeightRequest="{Binding SegmentedButtonHeight}"
FontSize="{Binding SegmentedButtonFontSize}"
Clicked="TextType_Clicked"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n Text}"
Grid.Column="1">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal">
<VisualState.Setters>
<Setter Property="Style"
Value="{StaticResource segmentedButtonNormal}" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Disabled">
<VisualState.Setters>
<Setter Property="Style"
Value="{StaticResource segmentedButtonDisabled}" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Button>
</Grid>
</StackLayout>
@@ -289,20 +243,20 @@
Text="{u:I18n Options}"
x:Name="_btnOptions"
StyleClass="box-row-button"
TextColor="{StaticResource PrimaryColor}"
TextColor="{DynamicResource PrimaryColor}"
Margin="0" />
<controls:FaButton
x:Name="_btnOptionsUp"
Text="&#xf077;"
StyleClass="box-row-button"
TextColor="{StaticResource PrimaryColor}"
TextColor="{DynamicResource PrimaryColor}"
Clicked="ToggleOptions_Clicked"
IsVisible="False" />
<controls:FaButton
x:Name="_btnOptionsDown"
Text="&#xf078;"
StyleClass="box-row-button"
TextColor="{StaticResource PrimaryColor}"
TextColor="{DynamicResource PrimaryColor}"
Clicked="ToggleOptions_Clicked"
IsVisible="False" />
</StackLayout>
@@ -421,7 +375,7 @@
MaxLength="9"
TextChanged="OnMaxAccessCountTextChanged"
HorizontalOptions="FillAndExpand" />
<Stepper
<controls:ExtendedStepper
x:Name="_maxAccessCountStepper"
Value="{Binding MaxAccessCount}"
Maximum="999999999"

View File

@@ -61,7 +61,6 @@ namespace Bit.App.Pages
protected override async void OnAppearing()
{
base.OnAppearing();
await _vm.InitAsync();
if (_syncService.SyncInProgress)
{
IsBusy = true;
@@ -162,7 +161,7 @@ namespace Bit.App.Pages
if (DoOnce())
{
var page = new SendsPage(_vm.Filter, _vm.Type != null);
await Navigation.PushModalAsync(new NavigationPage(page), false);
await Navigation.PushModalAsync(new NavigationPage(page));
}
}

View File

@@ -110,11 +110,6 @@ namespace Bit.App.Pages
public Command<SendView> SendOptionsCommand { get; set; }
public bool LoadedOnce { get; set; }
public async Task InitAsync()
{
SendEnabled = ! await AppHelpers.IsSendDisabledByPolicyAsync();
}
public async Task LoadAsync()
{
if (_doingLoad)
@@ -142,6 +137,7 @@ namespace Bit.App.Pages
ShowNoData = false;
Loading = true;
ShowList = false;
SendEnabled = ! await AppHelpers.IsSendDisabledByPolicyAsync();
var groupedSends = new List<SendGroupingsPageListGroup>();
var page = Page as SendGroupingsPage;

View File

@@ -29,7 +29,7 @@
HorizontalOptions="End" />
<Button
Clicked="ToggleAutofillService"
BackgroundColor="Transparent"
StyleClass="box-overlay"
RelativeLayout.XConstraint="0"
RelativeLayout.YConstraint="0"
RelativeLayout.WidthConstraint="{ConstraintExpression Type=RelativeToView, ElementName=AutofillServiceSwitch, Property=Width}"
@@ -58,8 +58,7 @@
HorizontalOptions="End" />
<Button
Clicked="ToggleInlineAutofill"
IsEnabled="{Binding InlineAutofillEnabled}"
BackgroundColor="Transparent"
StyleClass="box-overlay"
RelativeLayout.XConstraint="0"
RelativeLayout.YConstraint="0"
RelativeLayout.WidthConstraint="{ConstraintExpression Type=RelativeToView, ElementName=InlineAutofillSwitch, Property=Width}"
@@ -85,7 +84,7 @@
HorizontalOptions="End" />
<Button
Clicked="ToggleAccessibility"
BackgroundColor="Transparent"
StyleClass="box-overlay"
RelativeLayout.XConstraint="0"
RelativeLayout.YConstraint="0"
RelativeLayout.WidthConstraint="{ConstraintExpression Type=RelativeToView, ElementName=AccessibilitySwitch, Property=Width}"
@@ -114,8 +113,7 @@
HorizontalOptions="End" />
<Button
Clicked="ToggleDrawOver"
IsEnabled="{Binding DrawOverEnabled}"
BackgroundColor="Transparent"
StyleClass="box-overlay"
RelativeLayout.XConstraint="0"
RelativeLayout.YConstraint="0"
RelativeLayout.WidthConstraint="{ConstraintExpression Type=RelativeToView, ElementName=DrawOverSwitch, Property=Width}"

View File

@@ -170,6 +170,10 @@ namespace Bit.App.Pages
public void ToggleInlineAutofill()
{
if (!InlineAutofillEnabled)
{
return;
}
InlineAutofillToggled = !InlineAutofillToggled;
}
@@ -180,6 +184,10 @@ namespace Bit.App.Pages
public void ToggleDrawOver()
{
if (!DrawOverEnabled)
{
return;
}
_deviceActionService.OpenAccessibilityOverlayPermissionSettings();
}

View File

@@ -27,6 +27,18 @@
<ScrollView>
<StackLayout Spacing="20">
<StackLayout StyleClass="box">
<Frame
IsVisible="{Binding DisablePrivateVaultPolicyEnabled}"
Padding="10"
Margin="0, 12, 0, 0"
HasShadow="False"
BackgroundColor="Transparent"
BorderColor="Accent">
<Label
Text="{u:I18n DisablePersonalVaultExportPolicyInEffect}"
StyleClass="text-muted, text-sm, text-bold"
HorizontalTextAlignment="Center" />
</Frame>
<StackLayout StyleClass="box-row, box-row-input, box-row-input-options-platform">
<Label
Text="{u:I18n FileFormat}"
@@ -36,7 +48,8 @@
ItemsSource="{Binding FileFormatOptions, Mode=OneTime}"
SelectedIndex="{Binding FileFormatSelectedIndex}"
SelectedIndexChanged="FileFormat_Changed"
StyleClass="box-value" />
StyleClass="box-value"
IsEnabled="{Binding DisablePrivateVaultPolicyEnabled, Converter={StaticResource inverseBool}}" />
</StackLayout>
<Grid StyleClass="box-row">
<Grid.RowDefinitions>
@@ -59,6 +72,7 @@
IsSpellCheckEnabled="False"
IsTextPredictionEnabled="False"
IsPassword="{Binding ShowPassword, Converter={StaticResource inverseBool}}"
IsEnabled="{Binding DisablePrivateVaultPolicyEnabled, Converter={StaticResource inverseBool}}"
Grid.Row="1"
Grid.Column="0"
ReturnType="Go"
@@ -80,7 +94,8 @@
<Button Text="{u:I18n ExportVault}"
Clicked="ExportVault_Clicked"
HorizontalOptions="Fill"
VerticalOptions="End" />
VerticalOptions="End"
IsEnabled="{Binding DisablePrivateVaultPolicyEnabled, Converter={StaticResource inverseBool}}"/>
</StackLayout>
</StackLayout>
</StackLayout>

View File

@@ -4,8 +4,11 @@ using Bit.App.Resources;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;
using System.Threading.Tasks;
using Bit.Core;
using Bit.Core.Enums;
#if !FDROID
using Microsoft.AppCenter.Crashes;
#endif
@@ -20,6 +23,7 @@ namespace Bit.App.Pages
private readonly II18nService _i18nService;
private readonly ICryptoService _cryptoService;
private readonly IExportService _exportService;
private readonly IPolicyService _policyService;
private int _fileFormatSelectedIndex;
private string _exportWarningMessage;
@@ -36,6 +40,7 @@ namespace Bit.App.Pages
_i18nService = ServiceContainer.Resolve<II18nService>("i18nService");
_cryptoService = ServiceContainer.Resolve<ICryptoService>("cryptoService");
_exportService = ServiceContainer.Resolve<IExportService>("exportService");
_policyService = ServiceContainer.Resolve<IPolicyService>("policyService");
PageTitle = AppResources.ExportVault;
TogglePasswordCommand = new Command(TogglePassword);
@@ -53,10 +58,22 @@ namespace Bit.App.Pages
{
_initialized = true;
FileFormatSelectedIndex = FileFormatOptions.FindIndex(k => k.Key == "json");
DisablePrivateVaultPolicyEnabled = await _policyService.PolicyAppliesToUser(PolicyType.DisablePersonalVaultExport);
UpdateWarning();
}
public List<KeyValuePair<string, string>> FileFormatOptions { get; set; }
private bool _disabledPrivateVaultPolicyEnabled = false;
public bool DisablePrivateVaultPolicyEnabled
{
get => _disabledPrivateVaultPolicyEnabled;
set
{
SetProperty(ref _disabledPrivateVaultPolicyEnabled, value);
}
}
public int FileFormatSelectedIndex
{

View File

@@ -31,8 +31,7 @@
</StackLayout>
<Label
StyleClass="box-footer-label"
Text="{u:I18n ThemeDescription}"
x:Name="_themeDescriptionLabel" />
Text="{u:I18n ThemeDescription}" />
</StackLayout>
<StackLayout StyleClass="box">
<StackLayout StyleClass="box-row, box-row-input, box-row-input-options-platform">

View File

@@ -25,8 +25,6 @@ namespace Bit.App.Pages
{
ToolbarItems.RemoveAt(0);
_vm.ShowAndroidAutofillSettings = _deviceActionService.SupportsAutofillService();
_themeDescriptionLabel.Text = string.Concat(_themeDescriptionLabel.Text, " ",
AppResources.RestartIsRequired);
}
else
{

View File

@@ -59,7 +59,7 @@ namespace Bit.App.Pages
}
ThemeOptions = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>(null, AppResources.Default),
new KeyValuePair<string, string>(null, AppResources.ThemeDefault),
new KeyValuePair<string, string>("light", AppResources.Light),
new KeyValuePair<string, string>("dark", AppResources.Dark),
new KeyValuePair<string, string>("black", AppResources.Black),
@@ -215,17 +215,8 @@ namespace Bit.App.Pages
{
var theme = ThemeOptions[ThemeSelectedIndex].Key;
await _storageService.SaveAsync(Constants.ThemeKey, theme);
if (Device.RuntimePlatform == Device.Android)
{
await _deviceActionService.ShowLoadingAsync(AppResources.Restarting);
await Task.Delay(1000);
}
_messagingService.Send("updatedTheme", theme);
if (Device.RuntimePlatform == Device.iOS)
{
await Task.Delay(500);
await _platformUtilsService.ShowDialogAsync(AppResources.ThemeAppliedOnRestart);
}
ThemeManager.SetTheme(Device.RuntimePlatform == Device.Android, Application.Current.Resources);
_messagingService.Send("updatedTheme");
}
}

View File

@@ -21,18 +21,38 @@
x:DataType="pages:SettingsPageListItem">
<controls:ExtendedStackLayout Orientation="Horizontal"
StyleClass="list-row, list-row-platform">
<Label Text="{Binding Name, Mode=OneWay}"
LineBreakMode="TailTruncation"
<Frame
IsVisible="{Binding UseFrame}"
Padding="10"
HasShadow="False"
BackgroundColor="Transparent"
BorderColor="Accent">
<Label
Text="{Binding Name, Mode=OneWay}"
StyleClass="text-muted, text-sm, text-bold"
HorizontalTextAlignment="Center" />
</Frame>
<Label IsVisible="{Binding UseFrame, Converter={StaticResource inverseBool}}"
Text="{Binding Name, Mode=OneWay}"
LineBreakMode="{Binding LineBreakMode}"
HorizontalOptions="StartAndExpand"
VerticalOptions="CenterAndExpand"
StyleClass="list-title"/>
<Label Text="{Binding SubLabel, Mode=OneWay}"
IsVisible="{Binding SubLabel, Converter={StaticResource stringHasValue}}"
IsVisible="{Binding ShowSubLabel}"
HorizontalOptions="End"
HorizontalTextAlignment="End"
VerticalOptions="CenterAndExpand"
TextColor="{Binding SubLabelColor}"
StyleClass="list-sub" />
<TimePicker IsVisible="{Binding ShowTimeInput}"
Time="{Binding Time}" Format="HH:mm"
PropertyChanged="OnTimePickerPropertyChanged"
HorizontalOptions="End"
VerticalOptions="Center"
FontSize="Small"
TextColor="{Binding SubLabelColor}"
StyleClass="list-sub" Margin="-5" />
</controls:ExtendedStackLayout>
</DataTemplate>

View File

@@ -1,4 +1,5 @@
using Bit.App.Abstractions;
using System.ComponentModel;
using Bit.App.Abstractions;
using Bit.App.Resources;
using Bit.Core.Utilities;
using System.Linq;
@@ -43,6 +44,16 @@ namespace Bit.App.Pages
return base.OnBackButtonPressed();
}
async void OnTimePickerPropertyChanged(object sender, PropertyChangedEventArgs args)
{
var s = (TimePicker) sender;
var time = s.Time.TotalMinutes;
if (s.IsFocused && args.PropertyName == "Time")
{
await _vm.VaultTimeoutAsync(false, (int)time);
}
}
private async void RowSelected(object sender, SelectionChangedEventArgs e)
{
((ExtendedCollectionView)sender).SelectedItem = null;

View File

@@ -1,4 +1,5 @@
using Bit.App.Resources;
using System;
using Bit.App.Resources;
using Bit.App.Utilities;
using System.Collections.Generic;
using Xamarin.Forms;
@@ -10,7 +11,12 @@ namespace Bit.App.Pages
public string Icon { get; set; }
public string Name { get; set; }
public string SubLabel { get; set; }
public TimeSpan? Time { get; set; }
public bool UseFrame { get; set; }
public bool SubLabelTextEnabled => SubLabel == AppResources.Enabled;
public string LineBreakMode => SubLabel == null ? "TailTruncation" : "";
public bool ShowSubLabel => SubLabel.Length != 0;
public bool ShowTimeInput => Time != null;
public Color SubLabelColor => SubLabelTextEnabled ?
ThemeManager.GetResourceColor("SuccessColor") :
ThemeManager.GetResourceColor("MutedColor");

View File

@@ -7,6 +7,8 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Bit.Core.Enums;
using Bit.Core.Models.Domain;
using Xamarin.Forms;
namespace Bit.App.Pages
@@ -23,6 +25,9 @@ namespace Bit.App.Pages
private readonly IStorageService _storageService;
private readonly ISyncService _syncService;
private readonly IBiometricService _biometricService;
private readonly IPolicyService _policyService;
private const int CustomVaultTimeoutValue = -100;
private bool _supportsBiometric;
private bool _pin;
@@ -42,6 +47,7 @@ namespace Bit.App.Pages
new KeyValuePair<string, int?>(AppResources.FourHours, 240),
new KeyValuePair<string, int?>(AppResources.OnRestart, -1),
new KeyValuePair<string, int?>(AppResources.Never, null),
new KeyValuePair<string, int?>(AppResources.Custom, CustomVaultTimeoutValue),
};
private List<KeyValuePair<string, string>> _vaultTimeoutActions =
new List<KeyValuePair<string, string>>
@@ -50,6 +56,9 @@ namespace Bit.App.Pages
new KeyValuePair<string, string>(AppResources.LogOut, "logOut"),
};
private Policy _vaultTimeoutPolicy;
private int? _vaultTimeout;
public SettingsPageViewModel()
{
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
@@ -62,6 +71,7 @@ namespace Bit.App.Pages
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
_syncService = ServiceContainer.Resolve<ISyncService>("syncService");
_biometricService = ServiceContainer.Resolve<IBiometricService>("biometricService");
_policyService = ServiceContainer.Resolve<IPolicyService>("policyService");
GroupedItems = new ExtendedObservableCollection<SettingsPageListGroup>();
PageTitle = AppResources.Settings;
@@ -79,13 +89,30 @@ namespace Bit.App.Pages
_lastSyncDate = string.Format("{0} {1}", lastSync.Value.ToShortDateString(),
lastSync.Value.ToShortTimeString());
}
var timeout = await _storageService.GetAsync<int?>(Constants.VaultTimeoutKey);
_vaultTimeoutDisplayValue = _vaultTimeouts.FirstOrDefault(o => o.Value == timeout).Key;
if (await _policyService.PolicyAppliesToUser(PolicyType.MaximumVaultTimeout))
{
_vaultTimeoutPolicy = (await _policyService.GetAll(PolicyType.MaximumVaultTimeout)).First();
var minutes = _policyService.GetPolicyInt(_vaultTimeoutPolicy, "minutes").GetValueOrDefault();
_vaultTimeouts = _vaultTimeouts.Where(t =>
t.Value <= minutes &&
(t.Value > 0 || t.Value == CustomVaultTimeoutValue) &&
t.Value != null).ToList();
}
_vaultTimeout = await _vaultTimeoutService.GetVaultTimeout();
_vaultTimeoutDisplayValue = _vaultTimeouts.FirstOrDefault(o => o.Value == _vaultTimeout).Key;
var action = await _storageService.GetAsync<string>(Constants.VaultTimeoutActionKey) ?? "lock";
_vaultTimeoutActionDisplayValue = _vaultTimeoutActions.FirstOrDefault(o => o.Value == action).Key;
var pinSet = await _vaultTimeoutService.IsPinLockSetAsync();
_pin = pinSet.Item1 || pinSet.Item2;
_biometric = await _vaultTimeoutService.IsBiometricLockSetAsync();
if (_vaultTimeoutDisplayValue == null)
{
_vaultTimeoutDisplayValue = AppResources.Custom;
}
BuildList();
}
@@ -193,22 +220,51 @@ namespace Bit.App.Pages
await _vaultTimeoutService.LockAsync(true, true);
}
public async Task VaultTimeoutAsync()
public async Task VaultTimeoutAsync(bool promptOptions = true, int? newTimeout = 0)
{
var oldTimeout = _vaultTimeout;
var options = _vaultTimeouts.Select(
o => o.Key == _vaultTimeoutDisplayValue ? $"✓ {o.Key}" : o.Key).ToArray();
var selection = await Page.DisplayActionSheet(AppResources.VaultTimeout,
AppResources.Cancel, null, options);
if (selection == null || selection == AppResources.Cancel)
if (promptOptions)
{
return;
var selection = await Page.DisplayActionSheet(AppResources.VaultTimeout,
AppResources.Cancel, null, options);
if (selection == null || selection == AppResources.Cancel)
{
return;
}
var cleanSelection = selection.Replace("✓ ", string.Empty);
var selectionOption = _vaultTimeouts.FirstOrDefault(o => o.Key == cleanSelection);
_vaultTimeoutDisplayValue = selectionOption.Key;
newTimeout = selectionOption.Value;
}
var cleanSelection = selection.Replace("✓ ", string.Empty);
var selectionOption = _vaultTimeouts.FirstOrDefault(o => o.Key == cleanSelection);
_vaultTimeoutDisplayValue = selectionOption.Key;
await _vaultTimeoutService.SetVaultTimeoutOptionsAsync(selectionOption.Value,
if (_vaultTimeoutPolicy != null)
{
var maximumTimeout = _policyService.GetPolicyInt(_vaultTimeoutPolicy, "minutes");
if (newTimeout > maximumTimeout)
{
await _platformUtilsService.ShowDialogAsync(AppResources.VaultTimeoutToLarge, AppResources.Warning);
var timeout = await _vaultTimeoutService.GetVaultTimeout();
_vaultTimeoutDisplayValue = _vaultTimeouts.FirstOrDefault(o => o.Value == timeout).Key ??
AppResources.Custom;
return;
}
}
await _vaultTimeoutService.SetVaultTimeoutOptionsAsync(newTimeout,
GetVaultTimeoutActionFromKey(_vaultTimeoutActionDisplayValue));
BuildList();
if (newTimeout != CustomVaultTimeoutValue)
{
_vaultTimeout = newTimeout;
}
if (oldTimeout != newTimeout)
{
await Device.InvokeOnMainThreadAsync(BuildList);
}
}
public async Task VaultTimeoutActionAsync()
@@ -235,7 +291,7 @@ namespace Bit.App.Pages
var selectionOption = _vaultTimeoutActions.FirstOrDefault(o => o.Key == cleanSelection);
var changed = _vaultTimeoutActionDisplayValue != selectionOption.Key;
_vaultTimeoutActionDisplayValue = selectionOption.Key;
await _vaultTimeoutService.SetVaultTimeoutOptionsAsync(GetVaultTimeoutFromKey(_vaultTimeoutDisplayValue),
await _vaultTimeoutService.SetVaultTimeoutOptionsAsync(_vaultTimeout,
selectionOption.Value);
if (changed)
{
@@ -377,6 +433,25 @@ namespace Bit.App.Pages
};
securityItems.Insert(2, item);
}
if (_vaultTimeoutDisplayValue == AppResources.Custom)
{
securityItems.Insert(1, new SettingsPageListItem
{
Name = AppResources.Custom,
Time = TimeSpan.FromMinutes(Math.Abs((double)_vaultTimeout.GetValueOrDefault())),
});
}
if (_vaultTimeoutPolicy != null)
{
var maximumTimeout = _policyService.GetPolicyInt(_vaultTimeoutPolicy, "minutes").GetValueOrDefault();
securityItems.Insert(0, new SettingsPageListItem
{
Name = string.Format(AppResources.VaultTimeoutPolicyInEffect,
Math.Floor((float)maximumTimeout / 60),
maximumTimeout % 60),
UseFrame = true,
});
}
var accountItems = new List<SettingsPageListItem>
{
new SettingsPageListItem { Name = AppResources.FingerprintPhrase },

View File

@@ -35,7 +35,7 @@
</StackLayout>
<StackLayout StyleClass="box">
<Button Text="{u:I18n SyncVaultNow}" Clicked="Sync_Clicked"></Button>
<Label StyleClass="text-muted, text-sm" HorizontalTextAlignment="Center">
<Label StyleClass="text-muted, text-sm" HorizontalTextAlignment="Center" Margin="0,10">
<Label.FormattedText>
<FormattedString>
<Span Text="{u:I18n LastSync}" />

View File

@@ -1,18 +1,24 @@
using Bit.App.Effects;
using Bit.App.Models;
using Bit.App.Resources;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using Xamarin.Forms;
namespace Bit.App.Pages
{
public class TabsPage : TabbedPage
{
private readonly IMessagingService _messagingService;
private NavigationPage _groupingsPage;
private NavigationPage _sendGroupingsPage;
private NavigationPage _generatorPage;
public TabsPage(AppOptions appOptions = null, PreviousPageInfo previousPage = null)
{
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
_groupingsPage = new NavigationPage(new GroupingsPage(true, previousPage: previousPage))
{
Title = AppResources.MyVault,
@@ -85,6 +91,7 @@ namespace Bit.App.Pages
{
if (CurrentPage is NavigationPage navPage)
{
_messagingService.Send("updatedTheme");
if (navPage.RootPage is GroupingsPage groupingsPage)
{
// Load something?

View File

@@ -239,7 +239,6 @@
StyleClass="box-value"
Grid.Row="1"
Grid.Column="0"
Keyboard="Numeric"
IsPassword="{Binding ShowCardNumber, Converter={StaticResource inverseBool}}"
IsSpellCheckEnabled="False"
IsTextPredictionEnabled="False" />
@@ -563,7 +562,7 @@
StyleClass="box-row-button, box-row-button-platform"
Text="&#xf29c;"
Command="{Binding PasswordPromptHelpCommand}"
TextColor="{StaticResource MutedColor}"
TextColor="{DynamicResource MutedColor}"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n ToggleVisibility}"
HorizontalOptions="StartAndExpand" />

View File

@@ -309,7 +309,6 @@ namespace Bit.App.Pages
public async Task<bool> LoadAsync(AppOptions appOptions = null)
{
var policies = (await _policyService.GetAll(PolicyType.PersonalOwnership))?.ToList();
var myEmail = await _userService.GetEmailAsync();
OwnershipOptions.Add(new KeyValuePair<string, string>(myEmail, null));
var orgs = await _userService.GetAllOrganizationAsync();
@@ -318,28 +317,17 @@ namespace Bit.App.Pages
if (org.Enabled && org.Status == OrganizationUserStatusType.Confirmed)
{
OwnershipOptions.Add(new KeyValuePair<string, string>(org.Name, org.Id));
if ((!EditMode || CloneMode) && policies != null && org.UsePolicies && !org.canManagePolicies &&
AllowPersonal)
{
foreach (var policy in policies)
{
if (policy.OrganizationId == org.Id && policy.Enabled)
{
AllowPersonal = false;
// Remove personal ownership
OwnershipOptions.RemoveAt(0);
// Default to the organization who owns this policy for now (if necessary)
if (string.IsNullOrWhiteSpace(OrganizationId))
{
OrganizationId = org.Id;
}
break;
}
}
}
}
}
var personalOwnershipPolicyApplies = await _policyService.PolicyAppliesToUser(PolicyType.PersonalOwnership);
if (personalOwnershipPolicyApplies && (!EditMode || CloneMode))
{
AllowPersonal = false;
// Remove personal ownership
OwnershipOptions.RemoveAt(0);
}
var allCollections = await _collectionService.GetAllDecryptedAsync();
_writeableCollections = allCollections.Where(c => !c.ReadOnly).ToList();
if (CollectionIds?.Any() ?? false)

View File

@@ -234,7 +234,7 @@ namespace Bit.App.Pages
{
var page = new CiphersPage(_vm.Filter, _vm.FolderId != null, _vm.CollectionId != null,
_vm.Type != null, deleted: _vm.Deleted);
await Navigation.PushModalAsync(new NavigationPage(page), false);
await Navigation.PushModalAsync(new NavigationPage(page));
}
}

View File

@@ -485,6 +485,11 @@ namespace Bit.App.Pages
}
}
if (c.IsDeleted)
{
continue;
}
var fId = c.FolderId ?? "none";
if (_folderCounts.ContainsKey(fId))
{

View File

@@ -526,14 +526,9 @@
StyleClass="box-header, box-header-platform" />
</StackLayout>
<StackLayout StyleClass="box-row">
<Label
<controls:SelectableLabel
Text="{Binding Cipher.Notes, Mode=OneWay}"
StyleClass="box-value"
LineBreakMode="WordWrap">
<Label.Effects>
<effects:SelectableLabelEffect />
</Label.Effects>
</Label>
StyleClass="box-value"/>
</StackLayout>
<BoxView StyleClass="box-row-separator" />
</StackLayout>

Some files were not shown because too many files have changed in this diff Show More