1
0
mirror of https://github.com/bitwarden/mobile synced 2025-12-16 08:13:20 +00:00

Compare commits

..

16 Commits

Author SHA1 Message Date
github-actions[bot]
ececcb7bd7 Bump version to 2.17.0 (#1858)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
(cherry picked from commit a3a508eb83)
2022-03-22 10:50:41 -06:00
Matt Portune
81cccf22db Remove verbose state & value storage debug logging (#1857) 2022-03-21 16:46:18 -04:00
Federico Maccaroni
4e36bc83ae Added null checks for iOS crash OnActivated on KeyWindow 2022-03-21 12:36:21 -03:00
github-actions[bot]
9db70119b9 Bumped version to 2.16.5 (#1854)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
(cherry picked from commit fdcb2d76c9)
2022-03-18 13:22:15 -07:00
Federico Maccaroni
3d485f6fb5 Fixed flickering on iOS while loading collections for the collection crash hotfix 2022-03-18 15:49:48 -03:00
Matt Portune
372c875cb2 Misc fixes for account switching (#1849)
* Misc fixes for account switching

* use unique bio integrity key in ShareExtension
2022-03-17 17:57:31 -04:00
Federico Maccaroni
2e60787128 Fix iOS 15.4 crash from empty list to adding an item by awaiting after every header add; also added that on Settings just in case there is another crash scenario 2022-03-17 17:35:42 -03:00
github-actions[bot]
d283586d96 Bumped version to 2.16.4 (#1846)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
(cherry picked from commit c47aad0412)
2022-03-15 09:29:41 -07:00
Federico Maccaroni
3a2a1b527f Removed grouping from Settings to fix a crash on iOS 15.4 2022-03-15 11:31:33 -03:00
Federico Maccaroni
d908d5e64d Fix #1745 crash on scroll of grouped collection view on iOS 15.4 beta 2022-03-14 13:54:37 -03:00
Matt Portune
8c59552d41 fixes for font cutoff on samsung devices (#1838) 2022-03-10 13:56:33 -05:00
Matt Portune
77c4c423e0 fixed issues with logging out inactive accounts (#1836) 2022-03-10 09:03:06 -05:00
Joseph Flinn
47241fd5a9 Update hotfix release branch name to hotfix-rc (#1834)
(cherry picked from commit bdd0ea007b)
2022-03-09 12:54:20 -08:00
Matt Portune
51cfd70398 Fix for short profile Name value crashing app (#1833) 2022-03-09 09:01:16 -05:00
Matt Portune
de566be994 fix for lock & logout message parsing issue (#1832) 2022-03-08 14:32:13 -05:00
Federico Maccaroni
819d1b616a Remove Microsoft.AppCenter.Crashes from Core.csproj on FDroid on the build.yml (#1831) 2022-03-08 14:31:57 -05:00
374 changed files with 5546 additions and 9994 deletions

View File

@@ -1,12 +0,0 @@
{
"version": 1,
"isRoot": true,
"tools": {
"dotnet-format": {
"version": "5.1.250801",
"commands": [
"dotnet-format"
]
}
}
}

View File

@@ -6,7 +6,6 @@ root = true
# Don't use tabs for indentation. # Don't use tabs for indentation.
[*] [*]
indent_style = space indent_style = space
end_of_line = lf
# (Please don't specify an indent_size here; that has too many unintended consequences.) # (Please don't specify an indent_size here; that has too many unintended consequences.)
# Code files # Code files

View File

@@ -1,2 +0,0 @@
# .NET format https://github.com/bitwarden/mobile/pull/1738
04539af2a66668b6e85476d5cf318c9150ec4357

2
.gitattributes vendored
View File

@@ -1,7 +1,7 @@
############################################################################### ###############################################################################
# Set default behavior to automatically normalize line endings. # Set default behavior to automatically normalize line endings.
############################################################################### ###############################################################################
* text=auto eol=lf * text=auto
############################################################################### ###############################################################################
# Set default behavior for command prompt diff. # Set default behavior for command prompt diff.

View File

@@ -21,8 +21,12 @@
## Testing requirements
<!--What functionality requires testing by QA? This includes testing new behavior and regression testing-->
## Before you submit ## Before you submit
- [ ] I have checked for formatting errors (`dotnet tool run dotnet-format --check`) (required)
- [ ] I have added **unit tests** where it makes sense to do so (encouraged but not required) - [ ] I have added **unit tests** where it makes sense to do so (encouraged but not required)
- [ ] This change requires a **documentation update** (notify the documentation team) - [ ] This change requires a **documentation update** (notify the documentation team)
- [ ] This change has particular **deployment requirements** (notify the DevOps team) - [ ] This change has particular **deployment requirements** (notify the DevOps team)

View File

@@ -17,7 +17,7 @@ jobs:
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
steps: steps:
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846 uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
- name: Set up CLOC - name: Set up CLOC
run: | run: |
@@ -36,7 +36,7 @@ jobs:
hotfix_branch_exists: ${{ steps.branch-check.outputs.hotfix_branch_exists }} hotfix_branch_exists: ${{ steps.branch-check.outputs.hotfix_branch_exists }}
steps: steps:
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846 uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
- name: Check if special branches exist - name: Check if special branches exist
id: branch-check id: branch-check
@@ -60,13 +60,8 @@ jobs:
runs-on: windows-2019 runs-on: windows-2019
needs: setup needs: setup
steps: steps:
- name: Setup NuGet
uses: nuget/setup-nuget@b2bc17b761a1d88cab755a776c7922eb26eefbfa # v1.0.6
with:
nuget-version: 5.9.0
- name: Set up MSBuild - name: Set up MSBuild
uses: microsoft/setup-msbuild@ab534842b4bdf384b8aaf93765dc6f721d9f5fab uses: microsoft/setup-msbuild@c26a08ba26249b81327e26f6ef381897b6a8754d # v1
- name: Print environment - name: Print environment
run: | run: |
@@ -77,7 +72,7 @@ jobs:
echo "GitHub event: $GITHUB_EVENT" echo "GitHub event: $GITHUB_EVENT"
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846 uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
- name: Decrypt secrets - name: Decrypt secrets
env: env:
@@ -110,14 +105,6 @@ jobs:
- name: Restore packages - name: Restore packages
run: nuget restore run: nuget restore
- name: Restore tools
run: dotnet tool restore
shell: pwsh
- name: Verify Format
run: dotnet tool run dotnet-format --check
shell: pwsh
- name: Run Core tests - name: Run Core tests
run: dotnet test test/Core.Test/Core.Test.csproj run: dotnet test test/Core.Test/Core.Test.csproj
@@ -180,14 +167,14 @@ jobs:
shell: pwsh shell: pwsh
- name: Upload Play Store .aab artifact - name: Upload Play Store .aab artifact
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535 uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700 # v2.2.4
with: with:
name: com.x8bit.bitwarden.aab name: com.x8bit.bitwarden.aab
path: ./com.x8bit.bitwarden.aab path: ./com.x8bit.bitwarden.aab
if-no-files-found: error if-no-files-found: error
- name: Upload Play Store .apk artifact - name: Upload Play Store .apk artifact
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535 uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700 # v2.2.4
with: with:
name: com.x8bit.bitwarden.apk name: com.x8bit.bitwarden.apk
path: ./com.x8bit.bitwarden.apk path: ./com.x8bit.bitwarden.apk
@@ -214,13 +201,8 @@ jobs:
name: F-Droid Build name: F-Droid Build
runs-on: windows-2019 runs-on: windows-2019
steps: steps:
- name: Setup NuGet
uses: nuget/setup-nuget@b2bc17b761a1d88cab755a776c7922eb26eefbfa # v1.0.6
with:
nuget-version: 5.9.0
- name: Set up MSBuild - name: Set up MSBuild
uses: microsoft/setup-msbuild@ab534842b4bdf384b8aaf93765dc6f721d9f5fab uses: microsoft/setup-msbuild@c26a08ba26249b81327e26f6ef381897b6a8754d # v1
- name: Print environment - name: Print environment
run: | run: |
@@ -231,7 +213,7 @@ jobs:
echo "GitHub event: $GITHUB_EVENT" echo "GitHub event: $GITHUB_EVENT"
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846 uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
- name: Decrypt secrets - name: Decrypt secrets
env: env:
@@ -314,6 +296,18 @@ jobs:
$xml.Save($androidPath); $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);
Write-Output "########################################" Write-Output "########################################"
Write-Output "##### Uninstall from Core.csproj" Write-Output "##### Uninstall from Core.csproj"
Write-Output "########################################" Write-Output "########################################"
@@ -366,7 +360,7 @@ jobs:
shell: pwsh shell: pwsh
- name: Upload F-Droid .apk artifact - name: Upload F-Droid .apk artifact
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535 uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700 # v2.2.4
with: with:
name: com.x8bit.bitwarden-fdroid.apk name: com.x8bit.bitwarden-fdroid.apk
path: ./com.x8bit.bitwarden-fdroid.apk path: ./com.x8bit.bitwarden-fdroid.apk
@@ -378,11 +372,6 @@ jobs:
runs-on: macos-11 runs-on: macos-11
needs: setup needs: setup
steps: steps:
- name: Setup NuGet
uses: nuget/setup-nuget@b2bc17b761a1d88cab755a776c7922eb26eefbfa # v1.0.6
with:
nuget-version: 5.9.0
- name: Print environment - name: Print environment
run: | run: |
nuget help | grep Version nuget help | grep Version
@@ -392,16 +381,16 @@ jobs:
echo "GitHub event: $GITHUB_EVENT" echo "GitHub event: $GITHUB_EVENT"
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846 uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
- name: Login to Azure - Prod Subscription - name: Login to Azure - Prod Subscription
uses: Azure/login@1f63701bf3e6892515f1b7ce2d2bf1708b46beaf uses: Azure/login@77f1b2e3fb80c0e8645114159d17008b8a2e475a
with: with:
creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }} creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }}
- name: Retrieve secrets - name: Retrieve secrets
id: retrieve-secrets id: retrieve-secrets
uses: Azure/get-keyvault-secrets@b5c723b9ac7870c022b8c35befe620b7009b336f uses: Azure/get-keyvault-secrets@80ccd3fafe5662407cc2e55f202ee34bfff8c403
with: with:
keyvault: "bitwarden-prod-kv" keyvault: "bitwarden-prod-kv"
secrets: "appcenter-ios-token" secrets: "appcenter-ios-token"
@@ -423,8 +412,7 @@ jobs:
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \ gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
--output $HOME/secrets/dist_extension.mobileprovision ./.github/secrets/dist_extension.mobileprovision.gpg --output $HOME/secrets/dist_extension.mobileprovision ./.github/secrets/dist_extension.mobileprovision.gpg
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \ gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
--output $HOME/secrets/dist_share_extension.mobileprovision \ --output $HOME/secrets/dist_share_extension.mobileprovision ./.github/secrets/dist_share_extension.mobileprovision.gpg
./.github/secrets/dist_share_extension.mobileprovision.gpg
shell: bash shell: bash
- name: Increment version - name: Increment version
@@ -529,7 +517,7 @@ jobs:
shell: bash shell: bash
- name: Upload App Store .ipa & dSYMs artifacts - name: Upload App Store .ipa & dSYMs artifacts
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535 uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700 # v2.2.4
with: with:
name: Bitwarden iOS name: Bitwarden iOS
path: | path: |
@@ -539,12 +527,15 @@ jobs:
- name: Install AppCenter CLI - name: Install AppCenter CLI
if: | if: |
(github.ref == 'refs/heads/master' (github.ref == 'refs/heads/master'
&& needs.setup.outputs.rc_branch_exists == 0 && needs.setup.outputs.rc_branch_exists == 0
&& needs.setup.outputs.hotfix_branch_exists == 0) && needs.setup.outputs.hotfix_branch_exists == 0)
|| (github.ref == 'refs/heads/rc' && needs.setup.outputs.hotfix_branch_exists == 0) || (github.ref == 'refs/heads/rc' && needs.setup.outputs.hotfix_branch_exists == 0)
|| github.ref == 'refs/heads/hotfix-rc' || github.ref == 'refs/heads/hotfix-rc'
run: npm install -g appcenter-cli uses: actions/setup-node@v2
with:
node-version: '14'
- run: npm install -g appcenter-cli
- name: Upload dSYMs to App Center - name: Upload dSYMs to App Center
if: | if: |
@@ -555,7 +546,7 @@ jobs:
|| github.ref == 'refs/heads/hotfix-rc' || github.ref == 'refs/heads/hotfix-rc'
env: env:
APPCENTER_IOS_TOKEN: ${{ steps.retrieve-secrets.outputs.appcenter-ios-token }} APPCENTER_IOS_TOKEN: ${{ steps.retrieve-secrets.outputs.appcenter-ios-token }}
run: appcenter crashes upload-symbols -a bitwarden/bitwarden -s "./bitwarden-export/dSYMs" --token $APPCENTER_IOS_TOKEN run: appcenter crashes upload-symbols -a kspearrin/bitwarden -s "./bitwarden-export/dSYMs" --token $APPCENTER_IOS_TOKEN
shell: bash shell: bash
- name: Deploy to App Store - name: Deploy to App Store
@@ -586,22 +577,22 @@ jobs:
_CROWDIN_PROJECT_ID: "269690" _CROWDIN_PROJECT_ID: "269690"
steps: steps:
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846 uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
- name: Login to Azure - name: Login to Azure
uses: Azure/login@1f63701bf3e6892515f1b7ce2d2bf1708b46beaf uses: Azure/login@77f1b2e3fb80c0e8645114159d17008b8a2e475a
with: with:
creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }} creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }}
- name: Retrieve secrets - name: Retrieve secrets
id: retrieve-secrets id: retrieve-secrets
uses: Azure/get-keyvault-secrets@b5c723b9ac7870c022b8c35befe620b7009b336f uses: Azure/get-keyvault-secrets@80ccd3fafe5662407cc2e55f202ee34bfff8c403
with: with:
keyvault: "bitwarden-prod-kv" keyvault: "bitwarden-prod-kv"
secrets: "crowdin-api-token" secrets: "crowdin-api-token"
- name: Upload Sources - name: Upload Sources
uses: crowdin/github-action@9237b4cb361788dfce63feb2e2f15c09e2fe7415 uses: crowdin/github-action@e39093fd75daae7859c68eded4b43d42ec78d8ea # v1.3.2
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CROWDIN_API_TOKEN: ${{ steps.retrieve-secrets.outputs.crowdin-api-token }} CROWDIN_API_TOKEN: ${{ steps.retrieve-secrets.outputs.crowdin-api-token }}
@@ -648,21 +639,21 @@ jobs:
fi fi
- name: Login to Azure - Prod Subscription - name: Login to Azure - Prod Subscription
uses: Azure/login@1f63701bf3e6892515f1b7ce2d2bf1708b46beaf uses: Azure/login@77f1b2e3fb80c0e8645114159d17008b8a2e475a
if: failure() if: failure()
with: with:
creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }} creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }}
- name: Retrieve secrets - name: Retrieve secrets
id: retrieve-secrets id: retrieve-secrets
uses: Azure/get-keyvault-secrets@b5c723b9ac7870c022b8c35befe620b7009b336f uses: Azure/get-keyvault-secrets@80ccd3fafe5662407cc2e55f202ee34bfff8c403
if: failure() if: failure()
with: with:
keyvault: "bitwarden-prod-kv" keyvault: "bitwarden-prod-kv"
secrets: "devops-alerts-slack-webhook-url" secrets: "devops-alerts-slack-webhook-url"
- name: Notify Slack on failure - name: Notify Slack on failure
uses: act10ns/slack@da3191ebe2e67f49b46880b4633f5591a96d1d33 uses: act10ns/slack@e4e71685b9b239384b0f676a63c32367f59c2522 # v1.2.2
if: failure() if: failure()
env: env:
SLACK_WEBHOOK_URL: ${{ steps.retrieve-secrets.outputs.devops-alerts-slack-webhook-url }} SLACK_WEBHOOK_URL: ${{ steps.retrieve-secrets.outputs.devops-alerts-slack-webhook-url }}

View File

@@ -12,7 +12,7 @@ on:
options: options:
- Initial Release - Initial Release
- Redeploy - Redeploy
- Dry Run - dry-run
jobs: jobs:
release: release:
@@ -22,7 +22,7 @@ jobs:
branch-name: ${{ steps.branch.outputs.branch-name }} branch-name: ${{ steps.branch.outputs.branch-name }}
steps: steps:
- name: Branch check - name: Branch check
if: github.event.inputs.release_type != 'Dry Run' if: github.event.inputs.release_type != 'dry-run'
run: | run: |
if [[ "$GITHUB_REF" != "refs/heads/rc" ]] && [[ "$GITHUB_REF" != "refs/heads/hotfix-rc" ]]; then if [[ "$GITHUB_REF" != "refs/heads/rc" ]] && [[ "$GITHUB_REF" != "refs/heads/hotfix-rc" ]]; then
echo "===================================" echo "==================================="
@@ -34,13 +34,29 @@ jobs:
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 # v2.4.0 uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 # v2.4.0
- name: Check Release Version - name: Retrieve Mobile release version
id: version id: retrieve-mobile-version
uses: bitwarden/gh-actions/release-version-check@8f055ef543c7433c967a1b9b04a0f230923233bb run: |
with: ver=$(sed -E -n '/^<manifest/s/^.*[ ]android:versionName="([^"]+)".*$/\1/p' \
release-type: ${{ github.event.inputs.release_type }} ./src/Android/Properties/AndroidManifest.xml | tr -d '"')
project-type: xamarin echo "::set-output name=mobile_version::${ver}"
file: src/Android/Properties/AndroidManifest.xml shell: bash
- name: Check to make sure Mobile release version has been bumped
if: ${{ github.event.inputs.release_type == 'Initial Release' }}
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: Get branch name - name: Get branch name
id: branch id: branch
@@ -59,7 +75,7 @@ jobs:
run: zip -r Bitwarden\ iOS.zip Bitwarden\ iOS run: zip -r Bitwarden\ iOS.zip Bitwarden\ iOS
- name: Create release - name: Create release
if: github.event.inputs.release_type != 'Dry Run' if: github.event.inputs.release_type != 'dry-run'
uses: ncipollo/release-action@40bb172bd05f266cf9ba4ff965cb61e9ee5f6d01 # v1.9.0 uses: ncipollo/release-action@40bb172bd05f266cf9ba4ff965cb61e9ee5f6d01 # v1.9.0
with: with:
artifacts: "./com.x8bit.bitwarden.aab/com.x8bit.bitwarden.aab, artifacts: "./com.x8bit.bitwarden.aab/com.x8bit.bitwarden.aab,
@@ -67,8 +83,8 @@ jobs:
./com.x8bit.bitwarden-fdroid.apk/com.x8bit.bitwarden-fdroid.apk, ./com.x8bit.bitwarden-fdroid.apk/com.x8bit.bitwarden-fdroid.apk,
./Bitwarden iOS.zip" ./Bitwarden iOS.zip"
commit: ${{ github.sha }} commit: ${{ github.sha }}
tag: v${{ steps.version.outputs.version }} tag: v${{ steps.retrieve-mobile-version.outputs.mobile_version }}
name: Version ${{ steps.version.outputs.version }} name: Version ${{ steps.retrieve-mobile-version.outputs.mobile_version }}
body: "<insert release notes here>" body: "<insert release notes here>"
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
draft: true draft: true
@@ -155,5 +171,5 @@ jobs:
cd $GITHUB_WORKSPACE cd $GITHUB_WORKSPACE
- name: Deploy to gh-pages - name: Deploy to gh-pages
if: github.event.inputs.release_type != 'Dry Run' if: github.event.inputs.release_type != 'dry-run'
run: npm run deploy run: npm run deploy

View File

@@ -14,7 +14,7 @@ Here is how you can get involved:
* **Write documentation:** Submit a pull request to the [Bitwarden help repository](https://github.com/bitwarden/help) * **Write documentation:** Submit a pull request to the [Bitwarden help repository](https://github.com/bitwarden/help)
* **Help other users:** Go to the [Ask the Bitwarden Community category](https://community.bitwarden.com/c/support/) on the Community Forums * **Help other users:** Go to the [User-to-User Support category](https://community.bitwarden.com/c/support/) on the Community Forums
* **Translate:** See the localization (i10n) section below * **Translate:** See the localization (i10n) section below
@@ -35,6 +35,6 @@ We use a translation tool called [Crowdin](https://crowdin.com) to help manage o
If you are interested in helping translate the Bitwarden mobile app into another language (or make a translation correction), please register an account at Crowdin and join our project here: https://crowdin.com/project/bitwarden-mobile If you are interested in helping translate the Bitwarden mobile app into another language (or make a translation correction), please register an account at Crowdin and join our project here: https://crowdin.com/project/bitwarden-mobile
If the language that you are interested in translating is not already listed, create a new account on Crowdin, join the project, and contact the project owner (https://crowdin.com/profile/dwbit). If the language that you are interested in translating is not already listed, create a new account on Crowdin, join the project, and contact the project owner (https://crowdin.com/profile/kspearrin).
You can read Crowdin's getting started guide for translators here: https://support.crowdin.com/crowdin-intro/ You can read Crowdin's getting started guide for translators here: https://support.crowdin.com/crowdin-intro/

View File

@@ -33,23 +33,3 @@ Code contributions are welcome! Visual Studio with Xamarin is required to work o
Learn more about how to contribute by reading the [`CONTRIBUTING.md`](CONTRIBUTING.md) file. Learn more about how to contribute by reading the [`CONTRIBUTING.md`](CONTRIBUTING.md) file.
Security audits and feedback are welcome. Please open an issue or email us privately if the report is sensitive in nature. You can read our security policy in the [`SECURITY.md`](SECURITY.md) file. Security audits and feedback are welcome. Please open an issue or email us privately if the report is sensitive in nature. You can read our security policy in the [`SECURITY.md`](SECURITY.md) file.
### Dotnet-format
We recently migrated to using dotnet-format as code formatter. All previous branches will need to updated to avoid large merge conflicts using the following steps:
1. Check out your local Branch
2. Run `git merge e0efcfbe45b2a27c73e9593bfd7a71fad2aa7a35`
3. Resolve any merge conflicts, commit.
4. Run `dotnet tool run dotnet-format`
5. Commit
6. Run `git merge -Xours 04539af2a66668b6e85476d5cf318c9150ec4357`
7. Push
#### Git blame
We also recommend that you configure git to ignore the prettier revision using:
```bash
git config blame.ignoreRevsFile .git-blame-ignore-revs
```

View File

@@ -1,11 +1,39 @@
Bitwarden believes that working with security researchers across the globe is crucial to keeping our users safe. If you believe you've found a security issue in our product or service, we encourage you to please submit a report through our [HackerOne Program](https://hackerone.com/bitwarden/). We welcome working with you to resolve the issue promptly. Thanks in advance! Bitwarden believes that working with security researchers across the globe is crucial to keeping our
users safe. If you believe you've found a security issue in our product or service, we encourage you to
notify us. We welcome working with you to resolve the issue promptly. Thanks in advance!
# Disclosure Policy # Disclosure Policy
- Let us know as soon as possible upon discovery of a potential security issue, and we'll make every effort to quickly resolve the issue. - Let us know as soon as possible upon discovery of a potential security issue, and we'll make every
- Provide us a reasonable amount of time to resolve the issue before any disclosure to the public or a third-party. We may publicly disclose the issue before resolving it, if appropriate. effort to quickly resolve the issue.
- Make a good faith effort to avoid privacy violations, destruction of data, and interruption or degradation of our service. Only interact with accounts you own or with explicit permission of the account holder. - Provide us a reasonable amount of time to resolve the issue before any disclosure to the public or a
- If you would like to encrypt your report, please use the PGP key with long ID `0xDE6887086F892325FEC04CC0D847525B6931381F` (available in the public keyserver pool). third-party. We may publicly disclose the issue before resolving it, if appropriate.
- Make a good faith effort to avoid privacy violations, destruction of data, and interruption or
degradation of our service. Only interact with accounts you own or with explicit permission of the
account holder.
- If you would like to encrypt your report, please use the PGP key with long ID
`0xDE6887086F892325FEC04CC0D847525B6931381F` (available in the public keyserver pool).
# In-scope
- Security issues in any current release of Bitwarden. This includes the web vault, browser extension,
and mobile apps (iOS and Android). Product downloads are available at https://bitwarden.com. Source
code is available at https://github.com/bitwarden.
# Exclusions
The following bug classes are out-of scope:
- Bugs that are already reported on any of Bitwarden's issue trackers (https://github.com/bitwarden),
or that we already know of. Note that some of our issue tracking is private.
- Issues in an upstream software dependency (ex: Xamarin, ASP.NET) which are already reported to the
upstream maintainer.
- Attacks requiring physical access to a user's device.
- Self-XSS
- Issues related to software or protocols not under Bitwarden's control
- Vulnerabilities in outdated versions of Bitwarden
- Missing security best practices that do not directly lead to a vulnerability
- Issues that do not have any impact on the general public
While researching, we'd like to ask you to refrain from: While researching, we'd like to ask you to refrain from:
@@ -14,8 +42,4 @@ While researching, we'd like to ask you to refrain from:
- Social engineering (including phishing) of Bitwarden staff or contractors - Social engineering (including phishing) of Bitwarden staff or contractors
- Any physical attempts against Bitwarden property or data centers - Any physical attempts against Bitwarden property or data centers
# We want to help you!
If you have something that you feel is close to exploitation, or if you'd like some information regarding the internal API, or generally have any questions regarding the app that would help in your efforts, please email us at https://bitwarden.com/contact and ask for that information. As stated above, Bitwarden wants to help you find issues, and is more than willing to help.
Thank you for helping keep Bitwarden and our users safe! Thank you for helping keep Bitwarden and our users safe!

View File

@@ -84,7 +84,7 @@
<PackageReference Include="Xamarin.AndroidX.MediaRouter" Version="1.2.5.2" /> <PackageReference Include="Xamarin.AndroidX.MediaRouter" Version="1.2.5.2" />
<PackageReference Include="Xamarin.AndroidX.Migration" Version="1.0.8" /> <PackageReference Include="Xamarin.AndroidX.Migration" Version="1.0.8" />
<PackageReference Include="Xamarin.Essentials"> <PackageReference Include="Xamarin.Essentials">
<Version>1.7.3</Version> <Version>1.7.1</Version>
</PackageReference> </PackageReference>
<PackageReference Include="Xamarin.Firebase.Messaging"> <PackageReference Include="Xamarin.Firebase.Messaging">
<Version>122.0.0</Version> <Version>122.0.0</Version>
@@ -145,12 +145,12 @@
<Compile Include="Tiles\GeneratorTileService.cs" /> <Compile Include="Tiles\GeneratorTileService.cs" />
<Compile Include="Tiles\MyVaultTileService.cs" /> <Compile Include="Tiles\MyVaultTileService.cs" />
<Compile Include="Utilities\AndroidHelpers.cs" /> <Compile Include="Utilities\AndroidHelpers.cs" />
<Compile Include="Utilities\AppCenterHelper.cs" />
<Compile Include="Utilities\ThemeHelpers.cs" /> <Compile Include="Utilities\ThemeHelpers.cs" />
<Compile Include="WebAuthCallbackActivity.cs" /> <Compile Include="WebAuthCallbackActivity.cs" />
<Compile Include="Renderers\SelectableLabelRenderer.cs" /> <Compile Include="Renderers\SelectableLabelRenderer.cs" />
<Compile Include="Services\ClipboardService.cs" /> <Compile Include="Services\ClipboardService.cs" />
<Compile Include="Utilities\IntentExtensions.cs" /> <Compile Include="Utilities\IntentExtensions.cs" />
<Compile Include="Renderers\CustomPageRenderer.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<AndroidAsset Include="Assets\bwi-font.ttf" /> <AndroidAsset Include="Assets\bwi-font.ttf" />

View File

@@ -69,20 +69,16 @@ namespace Bit.Droid
Window.AddFlags(Android.Views.WindowManagerFlags.Secure); Window.AddFlags(Android.Views.WindowManagerFlags.Secure);
} }
ServiceContainer.Resolve<ILogger>("logger").InitAsync(); #if !DEBUG && !FDROID
var appCenterHelper = new AppCenterHelper(_appIdService, _stateService);
var toplayout = Window?.DecorView?.RootView; var appCenterTask = appCenterHelper.InitAsync();
if (toplayout != null) #endif
{
toplayout.FilterTouchesWhenObscured = true;
}
Xamarin.Essentials.Platform.Init(this, savedInstanceState); Xamarin.Essentials.Platform.Init(this, savedInstanceState);
Xamarin.Forms.Forms.Init(this, savedInstanceState); Xamarin.Forms.Forms.Init(this, savedInstanceState);
_appOptions = GetOptions(); _appOptions = GetOptions();
LoadApplication(new App.App(_appOptions)); LoadApplication(new App.App(_appOptions));
_broadcasterService.Subscribe(_activityKey, (message) => _broadcasterService.Subscribe(_activityKey, (message) =>
{ {
if (message.Command == "startEventTimer") if (message.Command == "startEventTimer")

View File

@@ -12,6 +12,7 @@ using Bit.Core.Abstractions;
using Bit.Core.Services; using Bit.Core.Services;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using Bit.Droid.Services; using Bit.Droid.Services;
using Bit.Droid.Utilities;
using Plugin.CurrentActivity; using Plugin.CurrentActivity;
using Plugin.Fingerprint; using Plugin.Fingerprint;
using Xamarin.Android.Net; using Xamarin.Android.Net;
@@ -19,7 +20,6 @@ using System.Net.Http;
using System.Net; using System.Net;
using Bit.App.Utilities; using Bit.App.Utilities;
using Bit.App.Pages; using Bit.App.Pages;
using Bit.App.Utilities.AccountManagement;
#if !FDROID #if !FDROID
using Android.Gms.Security; using Android.Gms.Security;
#endif #endif
@@ -62,15 +62,6 @@ namespace Bit.Droid
ServiceContainer.Resolve<IPasswordRepromptService>("passwordRepromptService"), ServiceContainer.Resolve<IPasswordRepromptService>("passwordRepromptService"),
ServiceContainer.Resolve<ICryptoService>("cryptoService")); ServiceContainer.Resolve<ICryptoService>("cryptoService"));
ServiceContainer.Register<IVerificationActionsFlowHelper>("verificationActionsFlowHelper", verificationActionsFlowHelper); ServiceContainer.Register<IVerificationActionsFlowHelper>("verificationActionsFlowHelper", verificationActionsFlowHelper);
var accountsManager = new AccountsManager(
ServiceContainer.Resolve<IBroadcasterService>("broadcasterService"),
ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService"),
ServiceContainer.Resolve<IStorageService>("secureStorageService"),
ServiceContainer.Resolve<IStateService>("stateService"),
ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService"),
ServiceContainer.Resolve<IAuthService>("authService"));
ServiceContainer.Register<IAccountsManager>("accountsManager", accountsManager);
} }
#if !FDROID #if !FDROID
if (Build.VERSION.SdkInt <= BuildVersionCodes.Kitkat) if (Build.VERSION.SdkInt <= BuildVersionCodes.Kitkat)
@@ -130,14 +121,13 @@ namespace Bit.Droid
var secureStorageService = new SecureStorageService(); var secureStorageService = new SecureStorageService();
var cryptoPrimitiveService = new CryptoPrimitiveService(); var cryptoPrimitiveService = new CryptoPrimitiveService();
var mobileStorageService = new MobileStorageService(preferencesStorage, liteDbStorage); var mobileStorageService = new MobileStorageService(preferencesStorage, liteDbStorage);
var stateService = new StateService(mobileStorageService, secureStorageService, messagingService); var stateService = new StateService(mobileStorageService, secureStorageService);
var stateMigrationService = var stateMigrationService =
new StateMigrationService(liteDbStorage, preferencesStorage, secureStorageService); new StateMigrationService(liteDbStorage, preferencesStorage, secureStorageService);
var clipboardService = new ClipboardService(stateService); var deviceActionService = new DeviceActionService(stateService, messagingService,
var deviceActionService = new DeviceActionService(clipboardService, stateService, messagingService,
broadcasterService, () => ServiceContainer.Resolve<IEventService>("eventService")); broadcasterService, () => ServiceContainer.Resolve<IEventService>("eventService"));
var platformUtilsService = new MobilePlatformUtilsService(deviceActionService, clipboardService, var platformUtilsService = new MobilePlatformUtilsService(deviceActionService, messagingService,
messagingService, broadcasterService); broadcasterService);
var biometricService = new BiometricService(); var biometricService = new BiometricService();
var cryptoFunctionService = new PclCryptoFunctionService(cryptoPrimitiveService); var cryptoFunctionService = new PclCryptoFunctionService(cryptoPrimitiveService);
var cryptoService = new CryptoService(stateService, cryptoFunctionService); var cryptoService = new CryptoService(stateService, cryptoFunctionService);
@@ -152,7 +142,7 @@ namespace Bit.Droid
ServiceContainer.Register<IStorageService>("secureStorageService", secureStorageService); ServiceContainer.Register<IStorageService>("secureStorageService", secureStorageService);
ServiceContainer.Register<IStateService>("stateService", stateService); ServiceContainer.Register<IStateService>("stateService", stateService);
ServiceContainer.Register<IStateMigrationService>("stateMigrationService", stateMigrationService); ServiceContainer.Register<IStateMigrationService>("stateMigrationService", stateMigrationService);
ServiceContainer.Register<IClipboardService>("clipboardService", clipboardService); ServiceContainer.Register<IClipboardService>("clipboardService", new ClipboardService(stateService));
ServiceContainer.Register<IDeviceActionService>("deviceActionService", deviceActionService); ServiceContainer.Register<IDeviceActionService>("deviceActionService", deviceActionService);
ServiceContainer.Register<IPlatformUtilsService>("platformUtilsService", platformUtilsService); ServiceContainer.Register<IPlatformUtilsService>("platformUtilsService", platformUtilsService);
ServiceContainer.Register<IBiometricService>("biometricService", biometricService); ServiceContainer.Register<IBiometricService>("biometricService", biometricService);

View File

@@ -1,5 +1,5 @@
<?xml version='1.0' encoding='UTF-8'?> <?xml version='1.0' encoding='UTF-8'?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:versionCode="1" android:versionName="2022.6.1" android:installLocation="internalOnly" package="com.x8bit.bitwarden"> <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:versionCode="1" android:versionName="2.17.0" android:installLocation="internalOnly" package="com.x8bit.bitwarden">
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="30"/> <uses-sdk android:minSdkVersion="21" android:targetSdkVersion="30"/>

View File

@@ -1,31 +0,0 @@
using System;
using Android.App;
using Android.Content;
using AndroidX.AppCompat.Widget;
using Bit.App.Resources;
using Bit.Droid.Renderers;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
[assembly: ExportRenderer(typeof(ContentPage), typeof(CustomPageRenderer))]
namespace Bit.Droid.Renderers
{
public class CustomPageRenderer : PageRenderer
{
public CustomPageRenderer(Context context) : base(context)
{
}
protected override void OnElementChanged(ElementChangedEventArgs<Page> e)
{
base.OnElementChanged(e);
Activity context = (Activity)this.Context;
var toolbar = context.FindViewById<Toolbar>(Resource.Id.toolbar);
if(toolbar != null)
{
toolbar.NavigationContentDescription = AppResources.TapToGoBack;
}
}
}
}

View File

@@ -2,5 +2,4 @@
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/> <background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/> <foreground android:drawable="@drawable/ic_launcher_foreground"/>
<monochrome android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon> </adaptive-icon>

View File

@@ -2,5 +2,4 @@
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/> <background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/> <foreground android:drawable="@drawable/ic_launcher_foreground"/>
<monochrome android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon> </adaptive-icon>

View File

@@ -1,10 +1,11 @@
using System; using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Android.OS; using Android.OS;
using Android.Security.Keystore; using Android.Security.Keystore;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Services;
using Bit.Core.Utilities;
using Java.Security; using Java.Security;
using Javax.Crypto; using Javax.Crypto;
@@ -42,22 +43,23 @@ namespace Bit.Droid.Services
public Task<bool> ValidateIntegrityAsync(string bioIntegrityKey = null) public Task<bool> ValidateIntegrityAsync(string bioIntegrityKey = null)
{ {
// bioIntegrityKey used in iOS only
if (Build.VERSION.SdkInt < BuildVersionCodes.M) if (Build.VERSION.SdkInt < BuildVersionCodes.M)
{ {
return Task.FromResult(true); return Task.FromResult(true);
} }
_keystore.Load(null);
IKey key = _keystore.GetKey(KeyName, null);
Cipher cipher = Cipher.GetInstance(Transformation);
if (key == null || cipher == null)
{
return Task.FromResult(true);
}
try try
{ {
_keystore.Load(null);
var key = _keystore.GetKey(KeyName, null);
var cipher = Cipher.GetInstance(Transformation);
if (key == null || cipher == null)
{
return Task.FromResult(true);
}
cipher.Init(CipherMode.EncryptMode, key); cipher.Init(CipherMode.EncryptMode, key);
} }
catch (KeyPermanentlyInvalidatedException e) catch (KeyPermanentlyInvalidatedException e)
@@ -73,7 +75,6 @@ namespace Bit.Droid.Services
catch (InvalidKeyException e) catch (InvalidKeyException e)
{ {
// Fallback for old bitwarden users without a key // Fallback for old bitwarden users without a key
LoggerHelper.LogEvenIfCantBeResolved(e);
CreateKey(); CreateKey();
} }
@@ -94,11 +95,10 @@ namespace Bit.Droid.Services
keyGen.Init(keyGenSpec); keyGen.Init(keyGenSpec);
keyGen.GenerateKey(); keyGen.GenerateKey();
} }
catch (Exception e) catch
{ {
// Catch silently to allow biometrics to function on devices that are in a state where key generation // Catch silently to allow biometrics to function on devices that are in a state where key generation
// is not functioning // is not functioning
LoggerHelper.LogEvenIfCantBeResolved(e);
} }
} }
} }

View File

@@ -2,7 +2,7 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using Android.App; using Android.App;
using Android.Content; using Android.Content;
using Android.OS; using Bit.Core;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Droid.Receivers; using Bit.Droid.Receivers;
using Plugin.CurrentActivity; using Plugin.CurrentActivity;
@@ -26,41 +26,13 @@ namespace Bit.Droid.Services
PendingIntentFlags.UpdateCurrent)); PendingIntentFlags.UpdateCurrent));
} }
public async Task CopyTextAsync(string text, int expiresInMs = -1, bool isSensitive = true) public async Task CopyTextAsync(string text, int expiresInMs = -1)
{ {
// Xamarin.Essentials.Clipboard currently doesn't support the IS_SENSITIVE flag for API 33+ await Clipboard.SetTextAsync(text);
if ((int)Build.VERSION.SdkInt < 33)
{
await Clipboard.SetTextAsync(text);
}
else
{
CopyToClipboard(text, isSensitive);
}
await ClearClipboardAlarmAsync(expiresInMs); await ClearClipboardAlarmAsync(expiresInMs);
} }
public bool IsCopyNotificationHandledByPlatform()
{
// Android 13+ provides built-in notification when text is copied to the clipboard
return (int)Build.VERSION.SdkInt >= 33;
}
private void CopyToClipboard(string text, bool isSensitive = true)
{
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
var clipboardManager = activity.GetSystemService(
Context.ClipboardService) as Android.Content.ClipboardManager;
var clipData = ClipData.NewPlainText("bitwarden", text);
if (isSensitive)
{
clipData.Description.Extras ??= new PersistableBundle();
clipData.Description.Extras.PutBoolean("android.content.extra.IS_SENSITIVE", true);
}
clipboardManager.PrimaryClip = clipData;
}
private async Task ClearClipboardAlarmAsync(int expiresInMs = -1) private async Task ClearClipboardAlarmAsync(int expiresInMs = -1)
{ {
var clearMs = expiresInMs; var clearMs = expiresInMs;

View File

@@ -35,7 +35,6 @@ namespace Bit.Droid.Services
{ {
public class DeviceActionService : IDeviceActionService public class DeviceActionService : IDeviceActionService
{ {
private readonly IClipboardService _clipboardService;
private readonly IStateService _stateService; private readonly IStateService _stateService;
private readonly IMessagingService _messagingService; private readonly IMessagingService _messagingService;
private readonly IBroadcasterService _broadcasterService; private readonly IBroadcasterService _broadcasterService;
@@ -48,13 +47,11 @@ namespace Bit.Droid.Services
private string _userAgent; private string _userAgent;
public DeviceActionService( public DeviceActionService(
IClipboardService clipboardService,
IStateService stateService, IStateService stateService,
IMessagingService messagingService, IMessagingService messagingService,
IBroadcasterService broadcasterService, IBroadcasterService broadcasterService,
Func<IEventService> eventServiceFunc) Func<IEventService> eventServiceFunc)
{ {
_clipboardService = clipboardService;
_stateService = stateService; _stateService = stateService;
_messagingService = messagingService; _messagingService = messagingService;
_broadcasterService = broadcasterService; _broadcasterService = broadcasterService;
@@ -677,7 +674,7 @@ namespace Bit.Droid.Services
else else
{ {
var data = new Intent(); var data = new Intent();
if (cipher?.Login == null) if (cipher == null)
{ {
data.PutExtra("canceled", "true"); data.PutExtra("canceled", "true");
} }
@@ -737,11 +734,6 @@ namespace Bit.Droid.Services
return Accessibility.AccessibilityHelpers.OverlayPermitted(); return Accessibility.AccessibilityHelpers.OverlayPermitted();
} }
public bool HasAutofillService()
{
return true;
}
public void OpenAccessibilityOverlayPermissionSettings() public void OpenAccessibilityOverlayPermissionSettings()
{ {
var activity = (MainActivity)CrossCurrentActivity.Current.Activity; var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
@@ -932,12 +924,20 @@ namespace Bit.Droid.Services
var totp = await totpService.GetCodeAsync(cipher.Login.Totp); var totp = await totpService.GetCodeAsync(cipher.Login.Totp);
if (totp != null) if (totp != null)
{ {
await _clipboardService.CopyTextAsync(totp); CopyToClipboard(totp);
} }
} }
} }
} }
private void CopyToClipboard(string text)
{
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
var clipboardManager = activity.GetSystemService(
Context.ClipboardService) as Android.Content.ClipboardManager;
clipboardManager.PrimaryClip = ClipData.NewPlainText("bitwarden", text);
}
public float GetSystemFontSizeScale() public float GetSystemFontSizeScale()
{ {
var activity = CrossCurrentActivity.Current?.Activity as MainActivity; var activity = CrossCurrentActivity.Current?.Activity as MainActivity;

View File

@@ -0,0 +1,58 @@
#if !FDROID
using Bit.Core.Abstractions;
using System.Threading.Tasks;
using Microsoft.AppCenter;
using Microsoft.AppCenter.Crashes;
using Newtonsoft.Json;
namespace Bit.Droid.Utilities
{
public class AppCenterHelper
{
private const string AppSecret = "d3834185-b4a6-4347-9047-b86c65293d42";
private readonly IAppIdService _appIdService;
private readonly IStateService _stateService;
private string _userId;
private string _appId;
public AppCenterHelper(
IAppIdService appIdService,
IStateService stateService)
{
_appIdService = appIdService;
_stateService = stateService;
}
public async Task InitAsync()
{
_userId = await _stateService.GetActiveUserIdAsync();
_appId = await _appIdService.GetAppIdAsync();
AppCenter.Start(AppSecret, typeof(Crashes));
AppCenter.SetUserId(_userId);
Crashes.GetErrorAttachments = (ErrorReport report) =>
{
return new ErrorAttachmentLog[]
{
ErrorAttachmentLog.AttachmentWithText(Description, "crshdesc.txt"),
};
};
}
public string Description
{
get
{
return JsonConvert.SerializeObject(new
{
AppId = _appId,
UserId = _userId
}, Formatting.Indented);
}
}
}
}
#endif

View File

@@ -1,12 +0,0 @@
using System;
using System.Threading.Tasks;
using Bit.App.Models;
namespace Bit.App.Abstractions
{
public interface IAccountsManager
{
void Init(Func<AppOptions> getOptionsFunc, IAccountsManagerHost accountsManagerHost);
Task NavigateOnAccountChangeAsync(bool? isAuthed = null);
}
}

View File

@@ -1,14 +0,0 @@
using System.Threading.Tasks;
using Bit.Core.Enums;
namespace Bit.App.Abstractions
{
public interface INavigationParams { }
public interface IAccountsManagerHost
{
Task SetPreviousPageInfoAsync();
void Navigate(NavigationTarget navTarget, INavigationParams navParams = null);
Task UpdateThemeAsync();
}
}

View File

@@ -1,6 +1,6 @@
using System.Threading.Tasks; using Bit.Core.Enums;
using Bit.Core.Enums;
using Bit.Core.Models.View; using Bit.Core.Models.View;
using System.Threading.Tasks;
namespace Bit.App.Abstractions namespace Bit.App.Abstractions
{ {
@@ -35,7 +35,6 @@ namespace Bit.App.Abstractions
void Background(); void Background();
bool AutofillAccessibilityServiceRunning(); bool AutofillAccessibilityServiceRunning();
bool AutofillAccessibilityOverlayPermitted(); bool AutofillAccessibilityOverlayPermitted();
bool HasAutofillService();
bool AutofillServiceEnabled(); bool AutofillServiceEnabled();
void DisableAutofillService(); void DisableAutofillService();
bool AutofillServicesEnabled(); bool AutofillServicesEnabled();

View File

@@ -1,5 +1,5 @@
using System.Threading.Tasks; using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Linq; using System.Threading.Tasks;
namespace Bit.App.Abstractions namespace Bit.App.Abstractions
{ {

View File

@@ -13,12 +13,13 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Plugin.Fingerprint" Version="2.1.5" /> <PackageReference Include="Microsoft.AppCenter.Crashes" Version="4.4.0" />
<PackageReference Include="Plugin.Fingerprint" Version="2.1.4" />
<PackageReference Include="SkiaSharp.Views.Forms" Version="2.80.3" /> <PackageReference Include="SkiaSharp.Views.Forms" Version="2.80.3" />
<PackageReference Include="Xamarin.CommunityToolkit" Version="2.0.2" /> <PackageReference Include="Xamarin.CommunityToolkit" Version="2.0.0" />
<PackageReference Include="Xamarin.Essentials" Version="1.7.3" /> <PackageReference Include="Xamarin.Essentials" Version="1.7.1" />
<PackageReference Include="Xamarin.FFImageLoading.Forms" Version="2.4.11.982" /> <PackageReference Include="Xamarin.FFImageLoading.Forms" Version="2.4.11.982" />
<PackageReference Include="Xamarin.Forms" Version="5.0.0.2478" /> <PackageReference Include="Xamarin.Forms" Version="5.0.0.2337" />
<PackageReference Include="ZXing.Net.Mobile" Version="2.4.1" /> <PackageReference Include="ZXing.Net.Mobile" Version="2.4.1" />
<PackageReference Include="ZXing.Net.Mobile.Forms" Version="2.4.1" /> <PackageReference Include="ZXing.Net.Mobile.Forms" Version="2.4.1" />
</ItemGroup> </ItemGroup>
@@ -128,7 +129,6 @@
<Folder Include="Resources\" /> <Folder Include="Resources\" />
<Folder Include="Behaviors\" /> <Folder Include="Behaviors\" />
<Folder Include="Controls\AccountSwitchingOverlay\" /> <Folder Include="Controls\AccountSwitchingOverlay\" />
<Folder Include="Utilities\AccountManagement\" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@@ -421,6 +421,5 @@
<None Remove="Behaviors\" /> <None Remove="Behaviors\" />
<None Remove="Xamarin.CommunityToolkit" /> <None Remove="Xamarin.CommunityToolkit" />
<None Remove="Controls\AccountSwitchingOverlay\" /> <None Remove="Controls\AccountSwitchingOverlay\" />
<None Remove="Utilities\AccountManagement\" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@@ -1,23 +1,22 @@
using System; using Bit.App.Abstractions;
using System.Threading.Tasks;
using Bit.App.Abstractions;
using Bit.App.Models; using Bit.App.Models;
using Bit.App.Pages; using Bit.App.Pages;
using Bit.App.Resources; using Bit.App.Resources;
using Bit.App.Services; using Bit.App.Services;
using Bit.App.Utilities; using Bit.App.Utilities;
using Bit.App.Utilities.AccountManagement;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Models.Data; using Bit.Core.Models.Data;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using System;
using System.Threading.Tasks;
using Bit.Core.Enums;
using Xamarin.Forms; using Xamarin.Forms;
using Xamarin.Forms.Xaml; using Xamarin.Forms.Xaml;
[assembly: XamlCompilation(XamlCompilationOptions.Compile)] [assembly: XamlCompilation(XamlCompilationOptions.Compile)]
namespace Bit.App namespace Bit.App
{ {
public partial class App : Application, IAccountsManagerHost public partial class App : Application
{ {
private readonly IBroadcasterService _broadcasterService; private readonly IBroadcasterService _broadcasterService;
private readonly IMessagingService _messagingService; private readonly IMessagingService _messagingService;
@@ -28,7 +27,6 @@ namespace Bit.App
private readonly IAuthService _authService; private readonly IAuthService _authService;
private readonly IStorageService _secureStorageService; private readonly IStorageService _secureStorageService;
private readonly IDeviceActionService _deviceActionService; private readonly IDeviceActionService _deviceActionService;
private readonly IAccountsManager _accountsManager;
private static bool _isResumed; private static bool _isResumed;
@@ -49,9 +47,6 @@ namespace Bit.App
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService"); _platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_secureStorageService = ServiceContainer.Resolve<IStorageService>("secureStorageService"); _secureStorageService = ServiceContainer.Resolve<IStorageService>("secureStorageService");
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService"); _deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
_accountsManager = ServiceContainer.Resolve<IAccountsManager>("accountsManager");
_accountsManager.Init(() => Options, this);
Bootstrap(); Bootstrap();
_broadcasterService.Subscribe(nameof(App), async (message) => _broadcasterService.Subscribe(nameof(App), async (message) =>
@@ -76,6 +71,30 @@ namespace Bit.App
_messagingService.Send("showDialogResolve", new Tuple<int, bool>(details.DialogId, confirmed)); _messagingService.Send("showDialogResolve", new Tuple<int, bool>(details.DialogId, confirmed));
}); });
} }
else if (message.Command == "locked")
{
var extras = message.Data as Tuple<string, bool>;
var userId = extras?.Item1;
var userInitiated = extras?.Item2 ?? false;
Device.BeginInvokeOnMainThread(async () => await LockedAsync(userId, userInitiated));
}
else if (message.Command == "lockVault")
{
await _vaultTimeoutService.LockAsync(true);
}
else if (message.Command == "logout")
{
var extras = message.Data as Tuple<string, bool, bool>;
var userId = extras?.Item1;
var userInitiated = extras?.Item2 ?? true;
var expired = extras?.Item3 ?? false;
Device.BeginInvokeOnMainThread(async () => await LogOutAsync(userId, userInitiated, expired));
}
else if (message.Command == "loggedOut")
{
// Clean up old migrated key if they ever log out.
await _secureStorageService.RemoveAsync("oldKey");
}
else if (message.Command == "resumed") else if (message.Command == "resumed")
{ {
if (Device.RuntimePlatform == Device.iOS) if (Device.RuntimePlatform == Device.iOS)
@@ -90,10 +109,22 @@ namespace Bit.App
await SleptAsync(); await SleptAsync();
} }
} }
else if (message.Command == "addAccount")
{
await AddAccount();
}
else if (message.Command == "accountAdded")
{
await UpdateThemeAsync();
}
else if (message.Command == "switchedAccount")
{
await SwitchedAccountAsync();
}
else if (message.Command == "migrated") else if (message.Command == "migrated")
{ {
await Task.Delay(1000); await Task.Delay(1000);
await _accountsManager.NavigateOnAccountChangeAsync(); await SetMainPageAsync();
} }
else if (message.Command == "popAllAndGoToTabGenerator" || else if (message.Command == "popAllAndGoToTabGenerator" ||
message.Command == "popAllAndGoToTabMyVault" || message.Command == "popAllAndGoToTabMyVault" ||
@@ -137,6 +168,7 @@ namespace Bit.App
new NavigationPage(new RemoveMasterPasswordPage())); new NavigationPage(new RemoveMasterPasswordPage()));
}); });
} }
}); });
} }
@@ -176,10 +208,7 @@ namespace Bit.App
{ {
await _stateService.SetLastActiveTimeAsync(_deviceActionService.GetActiveTime()); await _stateService.SetLastActiveTimeAsync(_deviceActionService.GetActiveTime());
} }
if (!SetTabsPageFromAutofill(isLocked)) SetTabsPageFromAutofill(isLocked);
{
ClearAutofillUri();
}
await SleptAsync(); await SleptAsync();
} }
} }
@@ -202,7 +231,6 @@ namespace Bit.App
private async Task ResumedAsync() private async Task ResumedAsync()
{ {
await _stateService.CheckExtensionActiveUserAndSwitchIfNeededAsync();
await _vaultTimeoutService.CheckVaultTimeoutAsync(); await _vaultTimeoutService.CheckVaultTimeoutAsync();
_messagingService.Send("startEventTimer"); _messagingService.Send("startEventTimer");
await UpdateThemeAsync(); await UpdateThemeAsync();
@@ -232,6 +260,102 @@ namespace Bit.App
new System.Globalization.UmAlQuraCalendar(); new System.Globalization.UmAlQuraCalendar();
} }
private async Task LogOutAsync(string userId, bool userInitiated, bool expired)
{
await AppHelpers.LogOutAsync(userId, userInitiated);
await SetMainPageAsync();
_authService.LogOut(() =>
{
if (expired)
{
_platformUtilsService.ShowToast("warning", null, AppResources.LoginExpired);
}
});
}
private async Task AddAccount()
{
Device.BeginInvokeOnMainThread(async () =>
{
Options.HideAccountSwitcher = false;
Current.MainPage = new NavigationPage(new HomePage(Options));
});
}
private async Task SwitchedAccountAsync()
{
await AppHelpers.OnAccountSwitchAsync();
Device.BeginInvokeOnMainThread(async () =>
{
if (await _vaultTimeoutService.ShouldTimeoutAsync())
{
await _vaultTimeoutService.ExecuteTimeoutActionAsync();
}
else
{
await SetMainPageAsync();
}
await Task.Delay(50);
await UpdateThemeAsync();
});
}
private async Task SetMainPageAsync()
{
var authed = await _stateService.IsAuthenticatedAsync();
if (authed)
{
if (await _vaultTimeoutService.IsLoggedOutByTimeoutAsync() ||
await _vaultTimeoutService.ShouldLogOutByTimeoutAsync())
{
// TODO implement orgIdentifier flow to SSO Login page, same as email flow below
// var orgIdentifier = await _stateService.GetOrgIdentifierAsync();
var email = await _stateService.GetEmailAsync();
Options.HideAccountSwitcher = await _stateService.GetActiveUserIdAsync() == null;
Current.MainPage = new NavigationPage(new LoginPage(email, Options));
}
else if (await _vaultTimeoutService.IsLockedAsync() ||
await _vaultTimeoutService.ShouldLockAsync())
{
Current.MainPage = new NavigationPage(new LockPage(Options));
}
else if (Options.FromAutofillFramework && Options.SaveType.HasValue)
{
Current.MainPage = new NavigationPage(new AddEditPage(appOptions: Options));
}
else if (Options.Uri != null)
{
Current.MainPage = new NavigationPage(new AutofillCiphersPage(Options));
}
else if (Options.CreateSend != null)
{
Current.MainPage = new NavigationPage(new SendAddEditPage(Options));
}
else
{
Current.MainPage = new TabsPage(Options);
}
}
else
{
Options.HideAccountSwitcher = await _stateService.GetActiveUserIdAsync() == null;
if (await _vaultTimeoutService.IsLoggedOutByTimeoutAsync() ||
await _vaultTimeoutService.ShouldLogOutByTimeoutAsync())
{
// TODO implement orgIdentifier flow to SSO Login page, same as email flow below
// var orgIdentifier = await _stateService.GetOrgIdentifierAsync();
var email = await _stateService.GetEmailAsync();
Current.MainPage = new NavigationPage(new LoginPage(email, Options));
}
else
{
Current.MainPage = new NavigationPage(new HomePage(Options));
}
}
}
private async Task ClearCacheIfNeededAsync() private async Task ClearCacheIfNeededAsync()
{ {
var lastClear = await _stateService.GetLastFileCacheClearAsync(); var lastClear = await _stateService.GetLastFileCacheClearAsync();
@@ -241,15 +365,7 @@ namespace Bit.App
} }
} }
private void ClearAutofillUri() private void SetTabsPageFromAutofill(bool isLocked)
{
if (Device.RuntimePlatform == Device.Android && !string.IsNullOrWhiteSpace(Options.Uri))
{
Options.Uri = null;
}
}
private bool SetTabsPageFromAutofill(bool isLocked)
{ {
if (Device.RuntimePlatform == Device.Android && !string.IsNullOrWhiteSpace(Options.Uri) && if (Device.RuntimePlatform == Device.Android && !string.IsNullOrWhiteSpace(Options.Uri) &&
!Options.FromAutofillFramework) !Options.FromAutofillFramework)
@@ -269,9 +385,7 @@ namespace Bit.App
} }
}); });
}); });
return true;
} }
return false;
} }
private void Prime() private void Prime()
@@ -293,7 +407,7 @@ namespace Bit.App
UpdateThemeAsync(); UpdateThemeAsync();
}; };
Current.MainPage = new NavigationPage(new HomePage(Options)); Current.MainPage = new NavigationPage(new HomePage(Options));
var mainPageTask = _accountsManager.NavigateOnAccountChangeAsync(); var mainPageTask = SetMainPageAsync();
ServiceContainer.Resolve<MobilePlatformUtilsService>("platformUtilsService").Init(); ServiceContainer.Resolve<MobilePlatformUtilsService>("platformUtilsService").Init();
} }
@@ -314,8 +428,23 @@ namespace Bit.App
}); });
} }
public async Task SetPreviousPageInfoAsync() private async Task LockedAsync(string userId, bool userInitiated)
{ {
if (!await _stateService.IsActiveAccountAsync(userId))
{
_platformUtilsService.ShowToast("info", null, AppResources.AccountLockedSuccessfully);
return;
}
var autoPromptBiometric = !userInitiated;
if (autoPromptBiometric && Device.RuntimePlatform == Device.iOS)
{
var vaultTimeout = await _stateService.GetVaultTimeoutAsync();
if (vaultTimeout == 0)
{
autoPromptBiometric = false;
}
}
PreviousPageInfo lastPageBeforeLock = null; PreviousPageInfo lastPageBeforeLock = null;
if (Current.MainPage is TabbedPage tabbedPage && tabbedPage.Navigation.ModalStack.Count > 0) if (Current.MainPage is TabbedPage tabbedPage && tabbedPage.Navigation.ModalStack.Count > 0)
{ {
@@ -341,44 +470,8 @@ namespace Bit.App
} }
} }
await _stateService.SetPreviousPageInfoAsync(lastPageBeforeLock); await _stateService.SetPreviousPageInfoAsync(lastPageBeforeLock);
} var lockPage = new LockPage(Options, autoPromptBiometric);
Device.BeginInvokeOnMainThread(() => Current.MainPage = new NavigationPage(lockPage));
public void Navigate(NavigationTarget navTarget, INavigationParams navParams)
{
switch (navTarget)
{
case NavigationTarget.HomeLogin:
Current.MainPage = new NavigationPage(new HomePage(Options));
break;
case NavigationTarget.Login:
if (navParams is LoginNavigationParams loginParams)
{
Current.MainPage = new NavigationPage(new LoginPage(loginParams.Email, Options));
}
break;
case NavigationTarget.Lock:
if (navParams is LockNavigationParams lockParams)
{
Current.MainPage = new NavigationPage(new LockPage(Options, lockParams.AutoPromptBiometric));
}
else
{
Current.MainPage = new NavigationPage(new LockPage(Options));
}
break;
case NavigationTarget.Home:
Current.MainPage = new TabsPage(Options);
break;
case NavigationTarget.AddEditCipher:
Current.MainPage = new NavigationPage(new AddEditPage(appOptions: Options));
break;
case NavigationTarget.AutofillCiphers:
Current.MainPage = new NavigationPage(new AutofillCiphersPage(Options));
break;
case NavigationTarget.SendAddEdit:
Current.MainPage = new NavigationPage(new SendAddEditPage(Options));
break;
}
} }
} }
} }

View File

@@ -63,10 +63,6 @@ namespace Bit.App.Controls
public int AccountListRowHeight => Device.RuntimePlatform == Device.Android ? 74 : 70; public int AccountListRowHeight => Device.RuntimePlatform == Device.Android ? 74 : 70;
public bool LongPressAccountEnabled { get; set; } = true;
public Action AfterHide { get; set; }
public async Task ToggleVisibilityAsync() public async Task ToggleVisibilityAsync()
{ {
if (IsVisible) if (IsVisible)
@@ -139,8 +135,6 @@ namespace Bit.App.Controls
// remove overlay // remove overlay
IsVisible = false; IsVisible = false;
AfterHide?.Invoke();
}); });
} }
@@ -173,7 +167,7 @@ namespace Bit.App.Controls
private async Task LongPressAccountAsync(AccountViewCellViewModel item) private async Task LongPressAccountAsync(AccountViewCellViewModel item)
{ {
if (!LongPressAccountEnabled || !item.IsAccount) if (!item.IsAccount)
{ {
return; return;
} }

View File

@@ -45,28 +45,23 @@ namespace Bit.App.Controls
public ICommand LongPressAccountCommand { get; } public ICommand LongPressAccountCommand { get; }
public bool FromIOSExtension { get; set; }
private async Task SelectAccountAsync(AccountViewCellViewModel item) private async Task SelectAccountAsync(AccountViewCellViewModel item)
{ {
if (!item.AccountView.IsAccount) if (item.AccountView.IsAccount)
{ {
_messagingService.Send(AccountsManagerMessageCommands.ADD_ACCOUNT); if (!item.AccountView.IsActive)
return;
}
if (!item.AccountView.IsActive)
{
await _stateService.SetActiveUserAsync(item.AccountView.UserId);
_messagingService.Send(AccountsManagerMessageCommands.SWITCHED_ACCOUNT);
if (FromIOSExtension)
{ {
await _stateService.SaveExtensionActiveUserIdToStorageAsync(item.AccountView.UserId); await _stateService.SetActiveUserAsync(item.AccountView.UserId);
_messagingService.Send("switchedAccount");
}
else if (AllowActiveAccountSelection)
{
_messagingService.Send("switchedAccount");
} }
} }
else if (AllowActiveAccountSelection) else
{ {
_messagingService.Send(AccountsManagerMessageCommands.SWITCHED_ACCOUNT); _messagingService.Send("addAccount");
} }
} }

View File

@@ -1,5 +1,5 @@
using System.Windows.Input;
using Bit.Core.Models.View; using Bit.Core.Models.View;
using System.Windows.Input;
using Xamarin.Forms; using Xamarin.Forms;
namespace Bit.App.Controls namespace Bit.App.Controls

View File

@@ -112,13 +112,13 @@ namespace Bit.App.Controls
private string GetFirstLetters(string data, int charCount) private string GetFirstLetters(string data, int charCount)
{ {
var parts = data.Split(); var parts = data.Split();
if (parts.Length > 1 && charCount <= 2) if (parts.Length > 1 && charCount <= 2)
{ {
var text = ""; var text = "";
for (int i = 0; i < charCount; i++) for (int i = 0; i < charCount; i++)
{ {
text += parts[i].Substring(0, 1); text += parts[i].Substring(0,1);
} }
return text; return text;
} }

View File

@@ -108,7 +108,7 @@
<controls:MiButton <controls:MiButton
Grid.Row="0" Grid.Row="0"
Grid.Column="2" Grid.Column="2"
Text="{Binding Source={x:Static core:BitwardenIcons.ViewCellMenu}}" Text="&#xe5d3;"
StyleClass="list-row-button, list-row-button-platform, btn-disabled" StyleClass="list-row-button, list-row-button-platform, btn-disabled"
Clicked="MoreButton_Clicked" Clicked="MoreButton_Clicked"
VerticalOptions="CenterAndExpand" VerticalOptions="CenterAndExpand"

View File

@@ -4,6 +4,5 @@ namespace Bit.App.Controls
{ {
public class ExtendedCollectionView : CollectionView public class ExtendedCollectionView : CollectionView
{ {
public string ExtraDataForLogging { get; set; }
} }
} }

View File

@@ -122,7 +122,7 @@
<controls:MiButton <controls:MiButton
Grid.Row="0" Grid.Row="0"
Grid.Column="2" Grid.Column="2"
Text="{Binding Source={x:Static core:BitwardenIcons.ViewCellMenu}}" Text="&#xe5d3;"
IsVisible="{Binding ShowOptions, Mode=OneWay}" IsVisible="{Binding ShowOptions, Mode=OneWay}"
StyleClass="list-row-button, list-row-button-platform, btn-disabled" StyleClass="list-row-button, list-row-button-platform, btn-disabled"
Clicked="MoreButton_Clicked" Clicked="MoreButton_Clicked"

View File

@@ -1,4 +1,4 @@
using System; using System;
using Bit.App.Abstractions; using Bit.App.Abstractions;
using Bit.Core.Models.View; using Bit.Core.Models.View;
using Bit.Core.Utilities; using Bit.Core.Utilities;

View File

@@ -1,12 +1,12 @@
using System.Collections.Generic; using Bit.App.Resources;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using System.Collections.Generic;
using System.Text; using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading.Tasks; using System.Threading.Tasks;
using Bit.App.Abstractions; using Bit.App.Abstractions;
using Bit.App.Resources;
using Bit.Core.Abstractions;
using Bit.Core.Models.Domain; using Bit.Core.Models.Domain;
using Bit.Core.Utilities;
using Xamarin.Essentials; using Xamarin.Essentials;
namespace Bit.App.Pages namespace Bit.App.Pages
@@ -46,11 +46,7 @@ namespace Bit.App.Pages
{ {
get => _showPassword; get => _showPassword;
set => SetProperty(ref _showPassword, value, set => SetProperty(ref _showPassword, value,
additionalPropertyNames: new[] additionalPropertyNames: new[] { nameof(ShowPasswordIcon) });
{
nameof(ShowPasswordIcon),
nameof(PasswordVisibilityAccessibilityText)
});
} }
public bool IsPolicyInEffect public bool IsPolicyInEffect
@@ -72,7 +68,6 @@ namespace Bit.App.Pages
} }
public string ShowPasswordIcon => ShowPassword ? "" : ""; public string ShowPasswordIcon => ShowPassword ? "" : "";
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.PasswordIsVisibleTapToHide : AppResources.PasswordIsNotVisibleTapToShow;
public string MasterPassword { get; set; } public string MasterPassword { get; set; }
public string ConfirmMasterPassword { get; set; } public string ConfirmMasterPassword { get; set; }
public string Hint { get; set; } public string Hint { get; set; }

View File

@@ -1,8 +1,8 @@
using System; using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Bit.App.Resources; using Bit.App.Resources;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using Xamarin.Forms; using Xamarin.Forms;
namespace Bit.App.Pages namespace Bit.App.Pages

View File

@@ -1,8 +1,8 @@
using System; using Bit.App.Resources;
using System.Threading.Tasks;
using Bit.App.Resources;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using System;
using System.Threading.Tasks;
using Xamarin.Forms; using Xamarin.Forms;
namespace Bit.App.Pages namespace Bit.App.Pages

View File

@@ -1,9 +1,9 @@
using System.Threading.Tasks; using Bit.App.Abstractions;
using Bit.App.Abstractions;
using Bit.App.Resources; using Bit.App.Resources;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Exceptions; using Bit.Core.Exceptions;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using System.Threading.Tasks;
using Xamarin.Forms; using Xamarin.Forms;
namespace Bit.App.Pages namespace Bit.App.Pages

View File

@@ -80,8 +80,7 @@
Grid.Column="1" Grid.Column="1"
Grid.RowSpan="2" Grid.RowSpan="2"
AutomationProperties.IsInAccessibleTree="True" AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n ToggleVisibility}" AutomationProperties.Name="{u:I18n ToggleVisibility}" />
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}"/>
</Grid> </Grid>
<Grid <Grid
x:Name="_passwordGrid" x:Name="_passwordGrid"
@@ -120,8 +119,7 @@
Grid.Column="1" Grid.Column="1"
Grid.RowSpan="2" Grid.RowSpan="2"
AutomationProperties.IsInAccessibleTree="True" AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n ToggleVisibility}" AutomationProperties.Name="{u:I18n ToggleVisibility}" />
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}" />
</Grid> </Grid>
<StackLayout <StackLayout
StyleClass="box-row" StyleClass="box-row"

View File

@@ -72,8 +72,7 @@ namespace Bit.App.Pages
set => SetProperty(ref _showPassword, value, set => SetProperty(ref _showPassword, value,
additionalPropertyNames: new string[] additionalPropertyNames: new string[]
{ {
nameof(ShowPasswordIcon), nameof(ShowPasswordIcon)
nameof(PasswordVisibilityAccessibilityText),
}); });
} }
@@ -129,7 +128,6 @@ namespace Bit.App.Pages
public Command SubmitCommand { get; } public Command SubmitCommand { get; }
public Command TogglePasswordCommand { get; } public Command TogglePasswordCommand { get; }
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye; public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.PasswordIsVisibleTapToHide : AppResources.PasswordIsNotVisibleTapToShow;
public string MasterPassword { get; set; } public string MasterPassword { get; set; }
public string Pin { get; set; } public string Pin { get; set; }
public Action UnlockedAction { get; set; } public Action UnlockedAction { get; set; }

View File

@@ -101,8 +101,7 @@
Grid.Column="1" Grid.Column="1"
Grid.RowSpan="2" Grid.RowSpan="2"
AutomationProperties.IsInAccessibleTree="True" AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n ToggleVisibility}" AutomationProperties.Name="{u:I18n ToggleVisibility}" />
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}"/>
</Grid> </Grid>
</StackLayout> </StackLayout>
<StackLayout Padding="10, 0"> <StackLayout Padding="10, 0">

View File

@@ -58,8 +58,7 @@ namespace Bit.App.Pages
set => SetProperty(ref _showPassword, value, set => SetProperty(ref _showPassword, value,
additionalPropertyNames: new string[] additionalPropertyNames: new string[]
{ {
nameof(ShowPasswordIcon), nameof(ShowPasswordIcon)
nameof(PasswordVisibilityAccessibilityText)
}); });
} }
@@ -86,7 +85,6 @@ namespace Bit.App.Pages
public Command LogInCommand { get; } public Command LogInCommand { get; }
public Command TogglePasswordCommand { get; } public Command TogglePasswordCommand { get; }
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye; public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.PasswordIsVisibleTapToHide : AppResources.PasswordIsNotVisibleTapToShow;
public Action StartTwoFactorAction { get; set; } public Action StartTwoFactorAction { get; set; }
public Action LogInSuccessAction { get; set; } public Action LogInSuccessAction { get; set; }
public Action UpdateTempPasswordAction { get; set; } public Action UpdateTempPasswordAction { get; set; }

View File

@@ -1,9 +1,9 @@
using System; using Bit.App.Models;
using System.Threading.Tasks;
using Bit.App.Models;
using Bit.App.Utilities;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using System;
using System.Threading.Tasks;
using Bit.App.Utilities;
using Xamarin.Forms; using Xamarin.Forms;
namespace Bit.App.Pages namespace Bit.App.Pages

View File

@@ -1,13 +1,13 @@
using System; using Bit.App.Abstractions;
using System.Threading.Tasks;
using Bit.App.Abstractions;
using Bit.App.Resources; using Bit.App.Resources;
using Bit.App.Utilities;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using System;
using System.Threading.Tasks;
using Bit.App.Utilities;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Exceptions; using Bit.Core.Exceptions;
using Bit.Core.Models.Domain; using Bit.Core.Models.Domain;
using Bit.Core.Utilities;
using Xamarin.Essentials; using Xamarin.Essentials;
using Xamarin.Forms; using Xamarin.Forms;
@@ -81,12 +81,10 @@ namespace Bit.App.Pages
} }
await _deviceActionService.ShowLoadingAsync(AppResources.LoggingIn); await _deviceActionService.ShowLoadingAsync(AppResources.LoggingIn);
string ssoToken;
try try
{ {
var response = await _apiService.PreValidateSso(OrgIdentifier); await _apiService.PreValidateSso(OrgIdentifier);
ssoToken = response.Token;
} }
catch (ApiException e) catch (ApiException e)
{ {
@@ -114,8 +112,7 @@ namespace Bit.App.Pages
"response_type=code&scope=api%20offline_access&" + "response_type=code&scope=api%20offline_access&" +
"state=" + state + "&code_challenge=" + codeChallenge + "&" + "state=" + state + "&code_challenge=" + codeChallenge + "&" +
"code_challenge_method=S256&response_mode=query&" + "code_challenge_method=S256&response_mode=query&" +
"domain_hint=" + Uri.EscapeDataString(OrgIdentifier) + "&" + "domain_hint=" + Uri.EscapeDataString(OrgIdentifier);
"ssoToken=" + Uri.EscapeDataString(ssoToken);
WebAuthenticatorResult authResult = null; WebAuthenticatorResult authResult = null;
try try

View File

@@ -68,8 +68,7 @@
Grid.Column="1" Grid.Column="1"
Grid.RowSpan="2" Grid.RowSpan="2"
AutomationProperties.IsInAccessibleTree="True" AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n ToggleVisibility}" AutomationProperties.Name="{u:I18n ToggleVisibility}" />
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}"/>
</Grid> </Grid>
<Label <Label
Text="{u:I18n MasterPasswordDescription}" Text="{u:I18n MasterPasswordDescription}"
@@ -107,8 +106,7 @@
Grid.Column="1" Grid.Column="1"
Grid.RowSpan="2" Grid.RowSpan="2"
AutomationProperties.IsInAccessibleTree="True" AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n ToggleVisibility}" AutomationProperties.Name="{u:I18n ToggleVisibility}" />
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}" />
</Grid> </Grid>
<StackLayout StyleClass="box-row"> <StackLayout StyleClass="box-row">
<Label <Label

View File

@@ -1,14 +1,14 @@
using System; using Bit.App.Abstractions;
using System.Threading.Tasks;
using System.Windows.Input;
using Bit.App.Abstractions;
using Bit.App.Resources; using Bit.App.Resources;
using Bit.Core;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Exceptions; using Bit.Core.Exceptions;
using Bit.Core.Models.Request; using Bit.Core.Models.Request;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using System;
using System.Threading.Tasks;
using System.Windows.Input;
using Bit.Core;
using Xamarin.Forms; using Xamarin.Forms;
namespace Bit.App.Pages namespace Bit.App.Pages
@@ -51,8 +51,7 @@ namespace Bit.App.Pages
set => SetProperty(ref _showPassword, value, set => SetProperty(ref _showPassword, value,
additionalPropertyNames: new string[] additionalPropertyNames: new string[]
{ {
nameof(ShowPasswordIcon), nameof(ShowPasswordIcon)
nameof(PasswordVisibilityAccessibilityText)
}); });
} }
@@ -74,7 +73,6 @@ namespace Bit.App.Pages
public Command TogglePasswordCommand { get; } public Command TogglePasswordCommand { get; }
public Command ToggleConfirmPasswordCommand { get; } public Command ToggleConfirmPasswordCommand { get; }
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye; public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.PasswordIsVisibleTapToHide : AppResources.PasswordIsNotVisibleTapToShow;
public string Name { get; set; } public string Name { get; set; }
public string Email { get; set; } public string Email { get; set; }
public string MasterPassword { get; set; } public string MasterPassword { get; set; }

View File

@@ -107,8 +107,7 @@
Grid.Column="1" Grid.Column="1"
Grid.RowSpan="2" Grid.RowSpan="2"
AutomationProperties.IsInAccessibleTree="True" AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n ToggleVisibility}" AutomationProperties.Name="{u:I18n ToggleVisibility}" />
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}" />
</Grid> </Grid>
<Label <Label
Text="{u:I18n MasterPasswordDescription}" Text="{u:I18n MasterPasswordDescription}"
@@ -146,8 +145,7 @@
Grid.Column="1" Grid.Column="1"
Grid.RowSpan="2" Grid.RowSpan="2"
AutomationProperties.IsInAccessibleTree="True" AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n ToggleVisibility}" AutomationProperties.Name="{u:I18n ToggleVisibility}" />
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}" />
</Grid> </Grid>
<StackLayout StyleClass="box-row"> <StackLayout StyleClass="box-row">
<Label <Label

View File

@@ -1,17 +1,17 @@
using System; using Bit.App.Abstractions;
using Bit.App.Resources;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.Models.Request;
using Bit.Core.Utilities;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text; using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading.Tasks; using System.Threading.Tasks;
using Bit.App.Abstractions;
using Bit.App.Resources;
using Bit.Core; using Bit.Core;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.Models.Domain; using Bit.Core.Models.Domain;
using Bit.Core.Models.Request;
using Bit.Core.Utilities;
using Xamarin.Essentials; using Xamarin.Essentials;
using Xamarin.Forms; using Xamarin.Forms;
@@ -55,11 +55,7 @@ namespace Bit.App.Pages
{ {
get => _showPassword; get => _showPassword;
set => SetProperty(ref _showPassword, value, set => SetProperty(ref _showPassword, value,
additionalPropertyNames: new[] additionalPropertyNames: new[] { nameof(ShowPasswordIcon) });
{
nameof(ShowPasswordIcon),
nameof(PasswordVisibilityAccessibilityText)
});
} }
public bool IsPolicyInEffect public bool IsPolicyInEffect
@@ -90,7 +86,6 @@ namespace Bit.App.Pages
public Command TogglePasswordCommand { get; } public Command TogglePasswordCommand { get; }
public Command ToggleConfirmPasswordCommand { get; } public Command ToggleConfirmPasswordCommand { get; }
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye; public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.PasswordIsVisibleTapToHide : AppResources.PasswordIsNotVisibleTapToShow;
public string MasterPassword { get; set; } public string MasterPassword { get; set; }
public string ConfirmMasterPassword { get; set; } public string ConfirmMasterPassword { get; set; }
public string Hint { get; set; } public string Hint { get; set; }
@@ -219,8 +214,7 @@ namespace Bit.App.Pages
// Request // Request
var resetRequest = new OrganizationUserResetPasswordEnrollmentRequest var resetRequest = new OrganizationUserResetPasswordEnrollmentRequest
{ {
ResetPasswordKey = encryptedKey.EncryptedString, ResetPasswordKey = encryptedKey.EncryptedString
MasterPasswordHash = masterPasswordHash,
}; };
var userId = await _stateService.GetActiveUserIdAsync(); var userId = await _stateService.GetActiveUserIdAsync();
// Enroll user // Enroll user

View File

@@ -1,11 +1,11 @@
using System; using Bit.App.Controls;
using System.Threading.Tasks;
using Bit.App.Controls;
using Bit.App.Models; using Bit.App.Models;
using Bit.App.Resources; using Bit.App.Resources;
using Bit.App.Utilities;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using System;
using System.Threading.Tasks;
using Bit.App.Utilities;
using Xamarin.Forms; using Xamarin.Forms;
namespace Bit.App.Pages namespace Bit.App.Pages
@@ -47,8 +47,7 @@ namespace Bit.App.Pages
if (Device.RuntimePlatform == Device.iOS) if (Device.RuntimePlatform == Device.iOS)
{ {
ToolbarItems.Add(_moreItem); ToolbarItems.Add(_moreItem);
} } else
else
{ {
ToolbarItems.Add(_useAnotherTwoStepMethod); ToolbarItems.Add(_useAnotherTwoStepMethod);
} }
@@ -93,8 +92,7 @@ namespace Bit.App.Pages
if (_vm.TotpMethod) if (_vm.TotpMethod)
{ {
RequestFocus(_totpEntry); RequestFocus(_totpEntry);
} } else if (_vm.YubikeyMethod)
else if (_vm.YubikeyMethod)
{ {
RequestFocus(_yubikeyTokenEntry); RequestFocus(_yubikeyTokenEntry);
} }

View File

@@ -1,16 +1,16 @@
using System; using Bit.App.Abstractions;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using Bit.App.Abstractions;
using Bit.App.Resources; using Bit.App.Resources;
using Bit.App.Utilities;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Exceptions; using Bit.Core.Exceptions;
using Bit.Core.Models.Request; using Bit.Core.Models.Request;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using Bit.App.Utilities;
using Newtonsoft.Json; using Newtonsoft.Json;
using Xamarin.Essentials; using Xamarin.Essentials;
using Xamarin.Forms; using Xamarin.Forms;
@@ -29,7 +29,6 @@ namespace Bit.App.Pages
private readonly IBroadcasterService _broadcasterService; private readonly IBroadcasterService _broadcasterService;
private readonly IStateService _stateService; private readonly IStateService _stateService;
private readonly II18nService _i18nService; private readonly II18nService _i18nService;
private readonly IAppIdService _appIdService;
private TwoFactorProviderType? _selectedProviderType; private TwoFactorProviderType? _selectedProviderType;
private string _totpInstruction; private string _totpInstruction;
@@ -50,7 +49,6 @@ namespace Bit.App.Pages
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService"); _broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
_stateService = ServiceContainer.Resolve<IStateService>("stateService"); _stateService = ServiceContainer.Resolve<IStateService>("stateService");
_i18nService = ServiceContainer.Resolve<II18nService>("i18nService"); _i18nService = ServiceContainer.Resolve<II18nService>("i18nService");
_appIdService = ServiceContainer.Resolve<IAppIdService>("appIdService");
PageTitle = AppResources.TwoStepLogin; PageTitle = AppResources.TwoStepLogin;
SubmitCommand = new Command(async () => await SubmitAsync()); SubmitCommand = new Command(async () => await SubmitAsync());
@@ -382,8 +380,7 @@ namespace Bit.App.Pages
var request = new TwoFactorEmailRequest var request = new TwoFactorEmailRequest
{ {
Email = _authService.Email, Email = _authService.Email,
MasterPasswordHash = _authService.MasterPasswordHash, MasterPasswordHash = _authService.MasterPasswordHash
DeviceIdentifier = await _appIdService.GetAppIdAsync()
}; };
await _apiService.PostTwoFactorEmailAsync(request); await _apiService.PostTwoFactorEmailAsync(request);
if (showLoading) if (showLoading)

View File

@@ -105,8 +105,7 @@
Grid.Column="1" Grid.Column="1"
Grid.RowSpan="2" Grid.RowSpan="2"
AutomationProperties.IsInAccessibleTree="True" AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n ToggleVisibility}" AutomationProperties.Name="{u:I18n ToggleVisibility}" />
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}" />
</Grid> </Grid>
</StackLayout> </StackLayout>
<StackLayout StyleClass="box"> <StackLayout StyleClass="box">
@@ -141,8 +140,7 @@
Grid.Column="1" Grid.Column="1"
Grid.RowSpan="2" Grid.RowSpan="2"
AutomationProperties.IsInAccessibleTree="True" AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n ToggleVisibility}" AutomationProperties.Name="{u:I18n ToggleVisibility}" />
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}" />
</Grid> </Grid>
<StackLayout StyleClass="box-row"> <StackLayout StyleClass="box-row">
<Label <Label

View File

@@ -1,7 +1,7 @@
using System;
using Bit.App.Resources;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using System;
using Bit.App.Resources;
using Xamarin.Forms; using Xamarin.Forms;
namespace Bit.App.Pages namespace Bit.App.Pages

View File

@@ -1,6 +1,6 @@
using System;
using System.Threading.Tasks;
using Bit.App.Resources; using Bit.App.Resources;
using System;
using System.Threading.Tasks;
using Bit.Core.Exceptions; using Bit.Core.Exceptions;
using Bit.Core.Models.Request; using Bit.Core.Models.Request;
using Xamarin.Forms; using Xamarin.Forms;

View File

@@ -1,4 +1,4 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Bit.App.Abstractions; using Bit.App.Abstractions;
using Bit.App.Resources; using Bit.App.Resources;
@@ -37,14 +37,11 @@ namespace Bit.App.Pages
bool cancelled = false; bool cancelled = false;
try try
{ {
// PrefersEphemeralWebBrowserSession should be false to allow access to the hCaptcha accessibility
// cookie set in the default browser
// https://www.hcaptcha.com/accessibility
var options = new WebAuthenticatorOptions var options = new WebAuthenticatorOptions
{ {
Url = new Uri(url), Url = new Uri(url),
CallbackUrl = new Uri(callbackUri), CallbackUrl = new Uri(callbackUri),
PrefersEphemeralWebBrowserSession = false, PrefersEphemeralWebBrowserSession = true,
}; };
authResult = await WebAuthenticator.AuthenticateAsync(options); authResult = await WebAuthenticator.AuthenticateAsync(options);
} }

View File

@@ -48,8 +48,7 @@
IsVisible="{Binding ShowNoData, Converter={StaticResource inverseBool}}" IsVisible="{Binding ShowNoData, Converter={StaticResource inverseBool}}"
ItemsSource="{Binding History}" ItemsSource="{Binding History}"
VerticalOptions="FillAndExpand" VerticalOptions="FillAndExpand"
StyleClass="list, list-platform" StyleClass="list, list-platform">
ExtraDataForLogging="Generator History Page">
<CollectionView.ItemTemplate> <CollectionView.ItemTemplate>
<DataTemplate x:DataType="domain:GeneratedPasswordHistory"> <DataTemplate x:DataType="domain:GeneratedPasswordHistory">
<Grid <Grid

View File

@@ -31,8 +31,7 @@ namespace Bit.App.Pages
{ {
base.OnAppearing(); base.OnAppearing();
await LoadOnAppearedAsync(_mainLayout, true, async () => await LoadOnAppearedAsync(_mainLayout, true, async () => {
{
await _vm.InitAsync(); await _vm.InitAsync();
}); });
} }

View File

@@ -41,11 +41,8 @@ namespace Bit.App.Pages
public async Task InitAsync() public async Task InitAsync()
{ {
var history = await _passwordGenerationService.GetHistoryAsync(); var history = await _passwordGenerationService.GetHistoryAsync();
Device.BeginInvokeOnMainThread(() => History.ResetWithRange(history ?? new List<GeneratedPasswordHistory>());
{ ShowNoData = History.Count == 0;
History.ResetWithRange(history ?? new List<GeneratedPasswordHistory>());
ShowNoData = History.Count == 0;
});
} }
public async Task ClearAsync() public async Task ClearAsync()
@@ -58,7 +55,8 @@ namespace Bit.App.Pages
private async void CopyAsync(GeneratedPasswordHistory ph) private async void CopyAsync(GeneratedPasswordHistory ph)
{ {
await _clipboardService.CopyTextAsync(ph.Password); await _clipboardService.CopyTextAsync(ph.Password);
_platformUtilsService.ShowToastForCopiedValue(AppResources.Password); _platformUtilsService.ShowToast("info", null,
string.Format(AppResources.ValueHasBeenCopied, AppResources.Password));
} }
public async Task UpdateOnThemeChanged() public async Task UpdateOnThemeChanged()

View File

@@ -183,68 +183,52 @@
<Label <Label
Text="A-Z" Text="A-Z"
StyleClass="box-label-regular" StyleClass="box-label-regular"
HorizontalOptions="StartAndExpand" HorizontalOptions="StartAndExpand" />
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n UppercaseAtoZ}"/>
<Switch <Switch
IsToggled="{Binding Uppercase}" IsToggled="{Binding Uppercase}"
IsEnabled="{Binding EnforcedPolicyOptions.UseUppercase, IsEnabled="{Binding EnforcedPolicyOptions.UseUppercase,
Converter={StaticResource inverseBool}}" Converter={StaticResource inverseBool}}"
StyleClass="box-value" StyleClass="box-value"
HorizontalOptions="End" HorizontalOptions="End" />
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n UppercaseAtoZ}"/>
</StackLayout> </StackLayout>
<BoxView StyleClass="box-row-separator" /> <BoxView StyleClass="box-row-separator" />
<StackLayout StyleClass="box-row, box-row-switch"> <StackLayout StyleClass="box-row, box-row-switch">
<Label <Label
Text="a-z" Text="a-z"
StyleClass="box-label-regular" StyleClass="box-label-regular"
HorizontalOptions="StartAndExpand" HorizontalOptions="StartAndExpand" />
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n LowercaseAtoZ}"/>
<Switch <Switch
IsToggled="{Binding Lowercase}" IsToggled="{Binding Lowercase}"
IsEnabled="{Binding EnforcedPolicyOptions.UseLowercase, IsEnabled="{Binding EnforcedPolicyOptions.UseLowercase,
Converter={StaticResource inverseBool}}" Converter={StaticResource inverseBool}}"
StyleClass="box-value" StyleClass="box-value"
HorizontalOptions="End" HorizontalOptions="End" />
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n LowercaseAtoZ}" />
</StackLayout> </StackLayout>
<BoxView StyleClass="box-row-separator" /> <BoxView StyleClass="box-row-separator" />
<StackLayout StyleClass="box-row, box-row-switch"> <StackLayout StyleClass="box-row, box-row-switch">
<Label <Label
Text="0-9" Text="0-9"
StyleClass="box-label-regular" StyleClass="box-label-regular"
HorizontalOptions="StartAndExpand" HorizontalOptions="StartAndExpand" />
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n NumbersZeroToNine}"/>
<Switch <Switch
IsToggled="{Binding Number}" IsToggled="{Binding Number}"
IsEnabled="{Binding EnforcedPolicyOptions.UseNumbers, IsEnabled="{Binding EnforcedPolicyOptions.UseNumbers,
Converter={StaticResource inverseBool}}" Converter={StaticResource inverseBool}}"
StyleClass="box-value" StyleClass="box-value"
HorizontalOptions="End" HorizontalOptions="End" />
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n NumbersZeroToNine}"/>
</StackLayout> </StackLayout>
<BoxView StyleClass="box-row-separator" /> <BoxView StyleClass="box-row-separator" />
<StackLayout StyleClass="box-row, box-row-switch"> <StackLayout StyleClass="box-row, box-row-switch">
<Label <Label
Text="!@#$%^&amp;*" Text="!@#$%^&amp;*"
StyleClass="box-label-regular" StyleClass="box-label-regular"
HorizontalOptions="StartAndExpand" HorizontalOptions="StartAndExpand" />
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n SpecialCharacters}"/>
<Switch <Switch
IsToggled="{Binding Special}" IsToggled="{Binding Special}"
IsEnabled="{Binding EnforcedPolicyOptions.UseSpecial, IsEnabled="{Binding EnforcedPolicyOptions.UseSpecial,
Converter={StaticResource inverseBool}}" Converter={StaticResource inverseBool}}"
StyleClass="box-value" StyleClass="box-value"
HorizontalOptions="End" HorizontalOptions="End" />
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n SpecialCharacters}"/>
</StackLayout> </StackLayout>
<BoxView StyleClass="box-row-separator" /> <BoxView StyleClass="box-row-separator" />
<StackLayout StyleClass="box-row, box-row-stepper"> <StackLayout StyleClass="box-row, box-row-stepper">
@@ -293,7 +277,7 @@
StyleClass="box-label-regular" StyleClass="box-label-regular"
HorizontalOptions="StartAndExpand" /> HorizontalOptions="StartAndExpand" />
<Switch <Switch
IsToggled="{Binding AvoidAmbiguousChars}" IsToggled="{Binding AvoidAmbiguous}"
StyleClass="box-value" StyleClass="box-value"
HorizontalOptions="End" /> HorizontalOptions="End" />
</StackLayout> </StackLayout>

View File

@@ -1,10 +1,11 @@
using System.Collections.Generic; using Bit.App.Resources;
using System.Threading.Tasks;
using Bit.App.Resources;
using Bit.App.Utilities; using Bit.App.Utilities;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Models.Domain; using Bit.Core.Models.Domain;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using System.Collections.Generic;
using System.Threading.Tasks;
using Xamarin.Forms;
namespace Bit.App.Pages namespace Bit.App.Pages
{ {
@@ -22,7 +23,7 @@ namespace Bit.App.Pages
private bool _lowercase; private bool _lowercase;
private bool _number; private bool _number;
private bool _special; private bool _special;
private bool _allowAmbiguousChars; private bool _avoidAmbiguous;
private int _minNumber; private int _minNumber;
private int _minSpecial; private int _minSpecial;
private int _length = 5; private int _length = 5;
@@ -129,29 +130,19 @@ namespace Bit.App.Pages
} }
} }
public bool AllowAmbiguousChars public bool AvoidAmbiguous
{ {
get => _allowAmbiguousChars; get => _avoidAmbiguous;
set set
{ {
if (SetProperty(ref _allowAmbiguousChars, value, if (SetProperty(ref _avoidAmbiguous, value))
additionalPropertyNames: new string[]
{
nameof(AvoidAmbiguousChars)
}))
{ {
_options.AllowAmbiguousChar = value; _options.Ambiguous = !value;
var task = SaveOptionsAsync(); var task = SaveOptionsAsync();
} }
} }
} }
public bool AvoidAmbiguousChars
{
get => !AllowAmbiguousChars;
set => AllowAmbiguousChars = !value;
}
public int MinNumber public int MinNumber
{ {
get => _minNumber; get => _minNumber;
@@ -318,12 +309,13 @@ namespace Bit.App.Pages
public async Task CopyAsync() public async Task CopyAsync()
{ {
await _clipboardService.CopyTextAsync(Password); await _clipboardService.CopyTextAsync(Password);
_platformUtilsService.ShowToastForCopiedValue(AppResources.Password); _platformUtilsService.ShowToast("success", null,
string.Format(AppResources.ValueHasBeenCopied, AppResources.Password));
} }
private void LoadFromOptions() private void LoadFromOptions()
{ {
AllowAmbiguousChars = _options.AllowAmbiguousChar.GetValueOrDefault(); AvoidAmbiguous = !_options.Ambiguous.GetValueOrDefault();
TypeSelectedIndex = _options.Type == "passphrase" ? 1 : 0; TypeSelectedIndex = _options.Type == "passphrase" ? 1 : 0;
IsPassword = TypeSelectedIndex == 0; IsPassword = TypeSelectedIndex == 0;
MinNumber = _options.MinNumber.GetValueOrDefault(); MinNumber = _options.MinNumber.GetValueOrDefault();
@@ -341,7 +333,7 @@ namespace Bit.App.Pages
private void SetOptions() private void SetOptions()
{ {
_options.AllowAmbiguousChar = AllowAmbiguousChars; _options.Ambiguous = !AvoidAmbiguous;
_options.Type = TypeSelectedIndex == 1 ? "passphrase" : "password"; _options.Type = TypeSelectedIndex == 1 ? "passphrase" : "password";
_options.MinNumber = MinNumber; _options.MinNumber = MinNumber;
_options.MinSpecial = MinSpecial; _options.MinSpecial = MinSpecial;

View File

@@ -2,7 +2,6 @@
<pages:BaseContentPage <pages:BaseContentPage
xmlns="http://xamarin.com/schemas/2014/forms" xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:xct="http://xamarin.com/schemas/2020/toolkit"
x:Class="Bit.App.Pages.SendAddEditPage" x:Class="Bit.App.Pages.SendAddEditPage"
xmlns:pages="clr-namespace:Bit.App.Pages" xmlns:pages="clr-namespace:Bit.App.Pages"
xmlns:u="clr-namespace:Bit.App.Utilities" xmlns:u="clr-namespace:Bit.App.Utilities"
@@ -122,7 +121,6 @@
Clicked="FileType_Clicked" Clicked="FileType_Clicked"
AutomationProperties.IsInAccessibleTree="True" AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n File}" AutomationProperties.Name="{u:I18n File}"
AutomationProperties.HelpText="{Binding FileTypeAccessibilityLabel}"
Grid.Column="0"> Grid.Column="0">
</Button> </Button>
<Button <Button
@@ -134,7 +132,6 @@
Clicked="TextType_Clicked" Clicked="TextType_Clicked"
AutomationProperties.IsInAccessibleTree="True" AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n Text}" AutomationProperties.Name="{u:I18n Text}"
AutomationProperties.HelpText="{Binding TextTypeAccessibilityLabel}"
Grid.Column="1"> Grid.Column="1">
</Button> </Button>
</Grid> </Grid>
@@ -253,33 +250,29 @@
</StackLayout> </StackLayout>
<StackLayout <StackLayout
Orientation="Horizontal" Orientation="Horizontal"
Spacing="0" Spacing="0">
xct:TouchEffect.Command="{Binding ToggleOptionsCommand}"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{Binding OptionsAccessilibityText}">
<Button <Button
Text="{u:I18n Options}" Text="{u:I18n Options}"
x:Name="_btnOptions" x:Name="_btnOptions"
StyleClass="box-row-button" StyleClass="box-row-button"
TextColor="{DynamicResource PrimaryColor}" TextColor="{DynamicResource PrimaryColor}"
Margin="0" Margin="0" />
AutomationProperties.IsInAccessibleTree="False"/>
<controls:IconButton <controls:IconButton
x:Name="_btnOptionsUp" x:Name="_btnOptionsUp"
Text="{Binding Source={x:Static core:BitwardenIcons.ChevronUp}}" Text="{Binding Source={x:Static core:BitwardenIcons.ChevronUp}}"
StyleClass="box-row-button" StyleClass="box-row-button"
TextColor="{DynamicResource PrimaryColor}" TextColor="{DynamicResource PrimaryColor}"
IsVisible="{Binding ShowOptions}" Clicked="ToggleOptions_Clicked"
AutomationProperties.IsInAccessibleTree="False"/> IsVisible="False" />
<controls:IconButton <controls:IconButton
x:Name="_btnOptionsDown" x:Name="_btnOptionsDown"
Text="{Binding Source={x:Static core:BitwardenIcons.AngleDown}}" Text="{Binding Source={x:Static core:BitwardenIcons.AngleDown}}"
StyleClass="box-row-button" StyleClass="box-row-button"
TextColor="{DynamicResource PrimaryColor}" TextColor="{DynamicResource PrimaryColor}"
IsVisible="{Binding ShowOptions, Converter={StaticResource inverseBool}}" Clicked="ToggleOptions_Clicked"
AutomationProperties.IsInAccessibleTree="False"/> IsVisible="False" />
</StackLayout> </StackLayout>
<StackLayout IsVisible="{Binding ShowOptions}"> <StackLayout IsVisible="True">
<StackLayout <StackLayout
StyleClass="box-row" StyleClass="box-row"
Margin="0,10,0,0"> Margin="0,10,0,0">
@@ -444,8 +437,7 @@
Command="{Binding TogglePasswordCommand}" Command="{Binding TogglePasswordCommand}"
Margin="10,0,0,0" Margin="10,0,0,0"
AutomationProperties.IsInAccessibleTree="True" AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n ToggleVisibility}" AutomationProperties.Name="{u:I18n ToggleVisibility}" />
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}" />
</StackLayout> </StackLayout>
<Label <Label
Text="{u:I18n PasswordInfo}" Text="{u:I18n PasswordInfo}"

View File

@@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using Bit.App.Models; using Bit.App.Models;
@@ -53,9 +53,10 @@ namespace Bit.App.Pages
_vm.SegmentedButtonFontSize = 13; _vm.SegmentedButtonFontSize = 13;
_vm.SegmentedButtonMargins = new Thickness(0, 10, 0, 0); _vm.SegmentedButtonMargins = new Thickness(0, 10, 0, 0);
_vm.EditorMargins = new Thickness(0, 5, 0, 0); _vm.EditorMargins = new Thickness(0, 5, 0, 0);
_btnOptions.WidthRequest = 70; // Review this when https://github.com/bitwarden/mobile/pull/1454 workaround can be reverted
_btnOptionsDown.WidthRequest = 30; //_btnOptions.WidthRequest = 70;
_btnOptionsUp.WidthRequest = 30; //_btnOptionsDown.WidthRequest = 30;
//_btnOptionsUp.WidthRequest = 30;
} }
else if (Device.RuntimePlatform == Device.iOS) else if (Device.RuntimePlatform == Device.iOS)
{ {
@@ -209,6 +210,11 @@ namespace Bit.App.Pages
} }
} }
private void ToggleOptions_Clicked(object sender, EventArgs e)
{
_vm.ToggleOptions();
}
private void ClearExpirationDate_Clicked(object sender, EventArgs e) private void ClearExpirationDate_Clicked(object sender, EventArgs e)
{ {
if (DoOnce()) if (DoOnce())

View File

@@ -40,12 +40,10 @@ namespace Bit.App.Pages
private TimeSpan? _expirationTime; private TimeSpan? _expirationTime;
private bool _isOverridingPickers; private bool _isOverridingPickers;
private int? _maxAccessCount; private int? _maxAccessCount;
private string[] _additionalSendProperties = new[] private string[] _additionalSendProperties = new []
{ {
nameof(IsText), nameof(IsText),
nameof(IsFile), nameof(IsFile),
nameof(FileTypeAccessibilityLabel),
nameof(TextTypeAccessibilityLabel)
}; };
private bool _disableHideEmail; private bool _disableHideEmail;
private bool _sendOptionsPolicyInEffect; private bool _sendOptionsPolicyInEffect;
@@ -61,7 +59,6 @@ namespace Bit.App.Pages
_logger = ServiceContainer.Resolve<ILogger>("logger"); _logger = ServiceContainer.Resolve<ILogger>("logger");
TogglePasswordCommand = new Command(TogglePassword); TogglePasswordCommand = new Command(TogglePassword);
ToggleOptionsCommand = new Command(ToggleOptions);
TypeOptions = new List<KeyValuePair<string, SendType>> TypeOptions = new List<KeyValuePair<string, SendType>>
{ {
@@ -92,7 +89,6 @@ namespace Bit.App.Pages
} }
public Command TogglePasswordCommand { get; set; } public Command TogglePasswordCommand { get; set; }
public Command ToggleOptionsCommand { get; set; }
public string SendId { get; set; } public string SendId { get; set; }
public int SegmentedButtonHeight { get; set; } public int SegmentedButtonHeight { get; set; }
public int SegmentedButtonFontSize { get; set; } public int SegmentedButtonFontSize { get; set; }
@@ -106,7 +102,6 @@ namespace Bit.App.Pages
public bool DisableHideEmailControl { get; set; } public bool DisableHideEmailControl { get; set; }
public bool IsAddFromShare { get; set; } public bool IsAddFromShare { get; set; }
public string ShareOnSaveText => CopyInsteadOfShareAfterSaving ? AppResources.CopySendLinkOnSave : AppResources.ShareOnSave; public string ShareOnSaveText => CopyInsteadOfShareAfterSaving ? AppResources.CopySendLinkOnSave : AppResources.ShareOnSave;
public string OptionsAccessilibityText => ShowOptions ? AppResources.OptionsExpanded : AppResources.OptionsCollapsed;
public List<KeyValuePair<string, SendType>> TypeOptions { get; } public List<KeyValuePair<string, SendType>> TypeOptions { get; }
public List<KeyValuePair<string, string>> DeletionTypeOptions { get; } public List<KeyValuePair<string, string>> DeletionTypeOptions { get; }
public List<KeyValuePair<string, string>> ExpirationTypeOptions { get; } public List<KeyValuePair<string, string>> ExpirationTypeOptions { get; }
@@ -139,11 +134,7 @@ namespace Bit.App.Pages
public bool ShowOptions public bool ShowOptions
{ {
get => _showOptions; get => _showOptions;
set => SetProperty(ref _showOptions, value, set => SetProperty(ref _showOptions, value);
additionalPropertyNames: new[]
{
nameof(OptionsAccessilibityText)
});
} }
public int ExpirationDateTypeSelectedIndex public int ExpirationDateTypeSelectedIndex
{ {
@@ -218,10 +209,9 @@ namespace Bit.App.Pages
{ {
get => _showPassword; get => _showPassword;
set => SetProperty(ref _showPassword, value, set => SetProperty(ref _showPassword, value,
additionalPropertyNames: new[] additionalPropertyNames: new []
{ {
nameof(ShowPasswordIcon), nameof(ShowPasswordIcon)
nameof(PasswordVisibilityAccessibilityText)
}); });
} }
public bool DisableHideEmail public bool DisableHideEmail
@@ -241,16 +231,13 @@ namespace Bit.App.Pages
public bool ShowDeletionCustomPickers => EditMode || DeletionDateTypeSelectedIndex == 6; public bool ShowDeletionCustomPickers => EditMode || DeletionDateTypeSelectedIndex == 6;
public bool ShowExpirationCustomPickers => EditMode || ExpirationDateTypeSelectedIndex == 7; public bool ShowExpirationCustomPickers => EditMode || ExpirationDateTypeSelectedIndex == 7;
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye; public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.PasswordIsVisibleTapToHide : AppResources.PasswordIsNotVisibleTapToShow;
public string FileTypeAccessibilityLabel => IsFile ? AppResources.FileTypeIsSelected : AppResources.FileTypeIsNotSelected;
public string TextTypeAccessibilityLabel => IsText ? AppResources.TextTypeIsSelected : AppResources.TextTypeIsNotSelected;
public async Task InitAsync() public async Task InitAsync()
{ {
PageTitle = EditMode ? AppResources.EditSend : AppResources.AddSend; PageTitle = EditMode ? AppResources.EditSend : AppResources.AddSend;
_canAccessPremium = await _stateService.CanAccessPremiumAsync(); _canAccessPremium = await _stateService.CanAccessPremiumAsync();
_emailVerified = await _stateService.GetEmailVerifiedAsync(); _emailVerified = await _stateService.GetEmailVerifiedAsync();
SendEnabled = !await AppHelpers.IsSendDisabledByPolicyAsync(); SendEnabled = ! await AppHelpers.IsSendDisabledByPolicyAsync();
DisableHideEmail = await AppHelpers.IsHideEmailDisabledByPolicyAsync(); DisableHideEmail = await AppHelpers.IsHideEmailDisabledByPolicyAsync();
SendOptionsPolicyInEffect = SendEnabled && DisableHideEmail; SendOptionsPolicyInEffect = SendEnabled && DisableHideEmail;
} }

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<pages:BaseContentPage xmlns="http://xamarin.com/schemas/2014/forms" <pages:BaseContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Bit.App.Pages.SendGroupingsPage" x:Class="Bit.App.Pages.SendGroupingsPage"
@@ -138,8 +138,7 @@
ItemTemplate="{StaticResource sendListItemDataTemplateSelector}" ItemTemplate="{StaticResource sendListItemDataTemplateSelector}"
SelectionMode="Single" SelectionMode="Single"
SelectionChanged="RowSelected" SelectionChanged="RowSelected"
StyleClass="list, list-platform" StyleClass="list, list-platform" />
ExtraDataForLogging="Send Groupings Page" />
</RefreshView> </RefreshView>
</StackLayout> </StackLayout>
</ResourceDictionary> </ResourceDictionary>

View File

@@ -1,4 +1,4 @@
using System; using System;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Bit.App.Controls; using Bit.App.Controls;
@@ -160,7 +160,7 @@ namespace Bit.App.Pages
{ {
if (DoOnce()) if (DoOnce())
{ {
var page = new SendsPage(_vm.Filter, _vm.Type); var page = new SendsPage(_vm.Filter, _vm.Type != null);
await Navigation.PushModalAsync(new NavigationPage(page)); await Navigation.PushModalAsync(new NavigationPage(page));
} }
} }

View File

@@ -9,6 +9,6 @@
} }
public string Title { get; } public string Title { get; }
public string ItemCount { get; } public string ItemCount { get; }
} }
} }

View File

@@ -136,7 +136,7 @@ namespace Bit.App.Pages
ShowNoData = false; ShowNoData = false;
Loading = true; Loading = true;
ShowList = false; ShowList = false;
SendEnabled = !await AppHelpers.IsSendDisabledByPolicyAsync(); SendEnabled = ! await AppHelpers.IsSendDisabledByPolicyAsync();
var groupedSends = new List<SendGroupingsPageListGroup>(); var groupedSends = new List<SendGroupingsPageListGroup>();
var page = Page as SendGroupingsPage; var page = Page as SendGroupingsPage;

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<pages:BaseContentPage <pages:BaseContentPage
xmlns="http://xamarin.com/schemas/2014/forms" xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
@@ -66,8 +66,7 @@
VerticalOptions="FillAndExpand" VerticalOptions="FillAndExpand"
SelectionMode="Single" SelectionMode="Single"
SelectionChanged="RowSelected" SelectionChanged="RowSelected"
StyleClass="list, list-platform" StyleClass="list, list-platform">
ExtraDataForLogging="Sends Page">
<CollectionView.ItemTemplate> <CollectionView.ItemTemplate>
<DataTemplate x:DataType="views:SendView"> <DataTemplate x:DataType="views:SendView">
<controls:SendViewCell <controls:SendViewCell

View File

@@ -2,7 +2,6 @@
using System.Linq; using System.Linq;
using Bit.App.Controls; using Bit.App.Controls;
using Bit.App.Resources; using Bit.App.Resources;
using Bit.Core.Enums;
using Bit.Core.Models.View; using Bit.Core.Models.View;
using Xamarin.Forms; using Xamarin.Forms;
@@ -13,22 +12,15 @@ namespace Bit.App.Pages
private SendsPageViewModel _vm; private SendsPageViewModel _vm;
private bool _hasFocused; private bool _hasFocused;
public SendsPage(Func<SendView, bool> filter, SendType? type = null) public SendsPage(Func<SendView, bool> filter, bool type = false)
{ {
InitializeComponent(); InitializeComponent();
_vm = BindingContext as SendsPageViewModel; _vm = BindingContext as SendsPageViewModel;
_vm.Page = this; _vm.Page = this;
_vm.Filter = filter; _vm.Filter = filter;
if (type != null) if (type)
{ {
if (type == SendType.File) _vm.PageTitle = AppResources.SearchType;
{
_vm.PageTitle = AppResources.SearchFileSends;
}
else if (type == SendType.Text)
{
_vm.PageTitle = AppResources.SearchTextSends;
}
} }
else else
{ {
@@ -41,7 +33,6 @@ namespace Bit.App.Pages
_searchBar.Placeholder = AppResources.Search; _searchBar.Placeholder = AppResources.Search;
_mainLayout.Children.Insert(0, _searchBar); _mainLayout.Children.Insert(0, _searchBar);
_mainLayout.Children.Insert(1, _separator); _mainLayout.Children.Insert(1, _separator);
ShowModalAnimationDelay = 0;
} }
else else
{ {

View File

@@ -39,7 +39,7 @@ namespace Bit.App.Pages
public bool ShowNoData public bool ShowNoData
{ {
get => _showNoData; get => _showNoData;
set => SetProperty(ref _showNoData, value, additionalPropertyNames: new[] set => SetProperty(ref _showNoData, value, additionalPropertyNames: new []
{ {
nameof(ShowSearchDirection) nameof(ShowSearchDirection)
}); });
@@ -48,7 +48,7 @@ namespace Bit.App.Pages
public bool ShowList public bool ShowList
{ {
get => _showList; get => _showList;
set => SetProperty(ref _showList, value, additionalPropertyNames: new[] set => SetProperty(ref _showList, value, additionalPropertyNames: new []
{ {
nameof(ShowSearchDirection) nameof(ShowSearchDirection)
}); });
@@ -58,7 +58,7 @@ namespace Bit.App.Pages
public async Task InitAsync() public async Task InitAsync()
{ {
SendEnabled = !await AppHelpers.IsSendDisabledByPolicyAsync(); SendEnabled = ! await AppHelpers.IsSendDisabledByPolicyAsync();
if (!string.IsNullOrWhiteSpace((Page as SendsPage).SearchBar.Text)) if (!string.IsNullOrWhiteSpace((Page as SendsPage).SearchBar.Text))
{ {
Search((Page as SendsPage).SearchBar.Text, 200); Search((Page as SendsPage).SearchBar.Text, 200);

View File

@@ -192,8 +192,7 @@ namespace Bit.App.Pages
public void UpdateEnabled() public void UpdateEnabled()
{ {
AutofillServiceToggled = AutofillServiceToggled = _deviceActionService.AutofillServiceEnabled();
_deviceActionService.HasAutofillService() && _deviceActionService.AutofillServiceEnabled();
AccessibilityToggled = _deviceActionService.AutofillAccessibilityServiceRunning(); AccessibilityToggled = _deviceActionService.AutofillAccessibilityServiceRunning();
DrawOverToggled = _deviceActionService.AutofillAccessibilityOverlayPermitted(); DrawOverToggled = _deviceActionService.AutofillAccessibilityOverlayPermitted();
} }

View File

@@ -105,7 +105,6 @@
Grid.Column="1" Grid.Column="1"
AutomationProperties.IsInAccessibleTree="True" AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n ToggleVisibility}" AutomationProperties.Name="{u:I18n ToggleVisibility}"
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}"
IsVisible="{Binding UseOTPVerification, Converter={StaticResource inverseBool}}"/> IsVisible="{Binding UseOTPVerification, Converter={StaticResource inverseBool}}"/>
<Label <Label
Text="{u:I18n ConfirmYourIdentity}" Text="{u:I18n ConfirmYourIdentity}"

View File

@@ -4,11 +4,11 @@ using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Bit.App.Abstractions; using Bit.App.Abstractions;
using Bit.App.Resources; using Bit.App.Resources;
using Bit.Core;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Exceptions; using Bit.Core.Exceptions;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using Bit.Core;
using Xamarin.Forms; using Xamarin.Forms;
namespace Bit.App.Pages namespace Bit.App.Pages
@@ -109,11 +109,7 @@ namespace Bit.App.Pages
{ {
get => _showPassword; get => _showPassword;
set => SetProperty(ref _showPassword, value, set => SetProperty(ref _showPassword, value,
additionalPropertyNames: new string[] additionalPropertyNames: new string[] {nameof(ShowPasswordIcon)});
{
nameof(ShowPasswordIcon),
nameof(PasswordVisibilityAccessibilityText),
});
} }
public bool UseOTPVerification public bool UseOTPVerification
@@ -143,7 +139,6 @@ namespace Bit.App.Pages
public Command TogglePasswordCommand { get; } public Command TogglePasswordCommand { get; }
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye; public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.PasswordIsVisibleTapToHide : AppResources.PasswordIsNotVisibleTapToShow;
public void TogglePassword() public void TogglePassword()
{ {

View File

@@ -1,7 +1,7 @@
using System.Threading.Tasks; using Bit.App.Resources;
using Bit.App.Resources;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using System.Threading.Tasks;
namespace Bit.App.Pages namespace Bit.App.Pages
{ {

View File

@@ -1,5 +1,5 @@
using System.Collections.Generic; using Bit.App.Resources;
using Bit.App.Resources; using System.Collections.Generic;
using Xamarin.Forms; using Xamarin.Forms;
namespace Bit.App.Pages namespace Bit.App.Pages

View File

@@ -1,10 +1,10 @@
using System.Threading.Tasks; using Bit.App.Abstractions;
using Bit.App.Abstractions;
using Bit.App.Resources; using Bit.App.Resources;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Exceptions; using Bit.Core.Exceptions;
using Bit.Core.Models.View; using Bit.Core.Models.View;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using System.Threading.Tasks;
using Xamarin.Forms; using Xamarin.Forms;
namespace Bit.App.Pages namespace Bit.App.Pages

View File

@@ -38,8 +38,7 @@
VerticalOptions="FillAndExpand" VerticalOptions="FillAndExpand"
SelectionMode="Single" SelectionMode="Single"
SelectionChanged="RowSelected" SelectionChanged="RowSelected"
StyleClass="list, list-platform" StyleClass="list, list-platform">
ExtraDataForLogging="Folders Page">
<CollectionView.ItemTemplate> <CollectionView.ItemTemplate>
<DataTemplate x:DataType="views:FolderView"> <DataTemplate x:DataType="views:FolderView">
<controls:ExtendedStackLayout <controls:ExtendedStackLayout

View File

@@ -1,7 +1,7 @@
using System; using Bit.Core.Models.View;
using System;
using System.Linq; using System.Linq;
using Bit.App.Controls; using Bit.App.Controls;
using Bit.Core.Models.View;
using Xamarin.Forms; using Xamarin.Forms;
namespace Bit.App.Pages namespace Bit.App.Pages

View File

@@ -1,10 +1,10 @@
using System.Collections.Generic; using Bit.App.Resources;
using System.Linq;
using System.Threading.Tasks;
using Bit.App.Resources;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Models.View; using Bit.Core.Models.View;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Bit.App.Pages namespace Bit.App.Pages
{ {

View File

@@ -1,11 +1,11 @@
using System.Collections.Generic; using Bit.App.Resources;
using System.Threading.Tasks;
using Bit.App.Resources;
using Bit.App.Utilities; using Bit.App.Utilities;
using Bit.Core; using Bit.Core;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using System.Collections.Generic;
using System.Threading.Tasks;
using Xamarin.Forms; using Xamarin.Forms;
namespace Bit.App.Pages namespace Bit.App.Pages

View File

@@ -113,7 +113,6 @@
ItemTemplate="{StaticResource listItemDataTemplateSelector}" ItemTemplate="{StaticResource listItemDataTemplateSelector}"
SelectionMode="Single" SelectionMode="Single"
SelectionChanged="RowSelected" SelectionChanged="RowSelected"
StyleClass="list, list-platform" StyleClass="list, list-platform" />
ExtraDataForLogging="Settings Page" />
</pages:BaseContentPage> </pages:BaseContentPage>

View File

@@ -59,7 +59,7 @@ namespace Bit.App.Pages
async void OnTimePickerPropertyChanged(object sender, PropertyChangedEventArgs args) async void OnTimePickerPropertyChanged(object sender, PropertyChangedEventArgs args)
{ {
var s = (TimePicker)sender; var s = (TimePicker) sender;
var time = s.Time.TotalMinutes; var time = s.Time.TotalMinutes;
if (s.IsFocused && args.PropertyName == "Time") if (s.IsFocused && args.PropertyName == "Time")
{ {
@@ -167,10 +167,6 @@ namespace Bit.App.Pages
{ {
await _vm.UpdatePinAsync(); await _vm.UpdatePinAsync();
} }
else if (item.Name == AppResources.SubmitCrashLogs)
{
await _vm.LoggerReportingAsync();
}
else else
{ {
var biometricName = AppResources.Biometrics; var biometricName = AppResources.Biometrics;

View File

@@ -1,16 +1,16 @@
using System; using Bit.App.Abstractions;
using Bit.App.Resources;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Bit.App.Abstractions;
using Bit.App.Resources;
using Bit.Core.Abstractions;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Models.Domain; using Bit.Core.Models.Domain;
using Bit.Core.Services;
using Bit.Core.Utilities;
using Xamarin.CommunityToolkit.ObjectModel;
using Xamarin.Forms; using Xamarin.Forms;
using ZXing.Client.Result;
using Xamarin.CommunityToolkit.ObjectModel;
namespace Bit.App.Pages namespace Bit.App.Pages
{ {
@@ -29,7 +29,7 @@ namespace Bit.App.Pages
private readonly ILocalizeService _localizeService; private readonly ILocalizeService _localizeService;
private readonly IKeyConnectorService _keyConnectorService; private readonly IKeyConnectorService _keyConnectorService;
private readonly IClipboardService _clipboardService; private readonly IClipboardService _clipboardService;
private readonly ILogger _loggerService;
private const int CustomVaultTimeoutValue = -100; private const int CustomVaultTimeoutValue = -100;
private bool _supportsBiometric; private bool _supportsBiometric;
@@ -39,7 +39,6 @@ namespace Bit.App.Pages
private string _vaultTimeoutDisplayValue; private string _vaultTimeoutDisplayValue;
private string _vaultTimeoutActionDisplayValue; private string _vaultTimeoutActionDisplayValue;
private bool _showChangeMasterPassword; private bool _showChangeMasterPassword;
private bool _reportLoggingEnabled;
private List<KeyValuePair<string, int?>> _vaultTimeouts = private List<KeyValuePair<string, int?>> _vaultTimeouts =
new List<KeyValuePair<string, int?>> new List<KeyValuePair<string, int?>>
@@ -80,7 +79,6 @@ namespace Bit.App.Pages
_localizeService = ServiceContainer.Resolve<ILocalizeService>("localizeService"); _localizeService = ServiceContainer.Resolve<ILocalizeService>("localizeService");
_keyConnectorService = ServiceContainer.Resolve<IKeyConnectorService>("keyConnectorService"); _keyConnectorService = ServiceContainer.Resolve<IKeyConnectorService>("keyConnectorService");
_clipboardService = ServiceContainer.Resolve<IClipboardService>("clipboardService"); _clipboardService = ServiceContainer.Resolve<IClipboardService>("clipboardService");
_loggerService = ServiceContainer.Resolve<ILogger>("logger");
GroupedItems = new ObservableRangeCollection<ISettingsPageListItem>(); GroupedItems = new ObservableRangeCollection<ISettingsPageListItem>();
PageTitle = AppResources.Settings; PageTitle = AppResources.Settings;
@@ -125,7 +123,7 @@ namespace Bit.App.Pages
_showChangeMasterPassword = IncludeLinksWithSubscriptionInfo() && _showChangeMasterPassword = IncludeLinksWithSubscriptionInfo() &&
!await _keyConnectorService.GetUsesKeyConnector(); !await _keyConnectorService.GetUsesKeyConnector();
_reportLoggingEnabled = await _loggerService.IsEnabled();
BuildList(); BuildList();
} }
@@ -288,26 +286,6 @@ namespace Bit.App.Pages
} }
} }
public async Task LoggerReportingAsync()
{
var options = new[]
{
CreateSelectableOption(AppResources.Yes, _reportLoggingEnabled),
CreateSelectableOption(AppResources.No, !_reportLoggingEnabled),
};
var selection = await Page.DisplayActionSheet(AppResources.SubmitCrashLogsDescription, AppResources.Cancel, null, options);
if (selection == null || selection == AppResources.Cancel)
{
return;
}
await _loggerService.SetEnabled(CompareSelection(selection, AppResources.Yes));
_reportLoggingEnabled = await _loggerService.IsEnabled();
BuildList();
}
public async Task VaultTimeoutActionAsync() public async Task VaultTimeoutActionAsync()
{ {
var options = _vaultTimeoutActions.Select(o => var options = _vaultTimeoutActions.Select(o =>
@@ -516,19 +494,11 @@ namespace Bit.App.Pages
toolsItems.Add(new SettingsPageListItem { Name = AppResources.LearnOrg }); toolsItems.Add(new SettingsPageListItem { Name = AppResources.LearnOrg });
toolsItems.Add(new SettingsPageListItem { Name = AppResources.WebVault }); toolsItems.Add(new SettingsPageListItem { Name = AppResources.WebVault });
} }
var otherItems = new List<SettingsPageListItem> var otherItems = new List<SettingsPageListItem>
{ {
new SettingsPageListItem { Name = AppResources.Options }, new SettingsPageListItem { Name = AppResources.Options },
new SettingsPageListItem { Name = AppResources.About }, new SettingsPageListItem { Name = AppResources.About },
new SettingsPageListItem { Name = AppResources.HelpAndFeedback }, new SettingsPageListItem { Name = AppResources.HelpAndFeedback },
#if !FDROID
new SettingsPageListItem
{
Name = AppResources.SubmitCrashLogs,
SubLabel = _reportLoggingEnabled ? AppResources.Enabled : AppResources.Disabled,
},
#endif
new SettingsPageListItem { Name = AppResources.RateTheApp }, new SettingsPageListItem { Name = AppResources.RateTheApp },
new SettingsPageListItem { Name = AppResources.DeleteAccount } new SettingsPageListItem { Name = AppResources.DeleteAccount }
}; };
@@ -606,9 +576,5 @@ namespace Bit.App.Pages
{ {
return _vaultTimeouts.FirstOrDefault(o => o.Key == key).Value; return _vaultTimeouts.FirstOrDefault(o => o.Key == key).Value;
} }
private string CreateSelectableOption(string option, bool selected) => selected ? $"✓ {option}" : option;
private bool CompareSelection(string selection, string compareTo) => selection == compareTo || selection == $"✓ {compareTo}";
} }
} }

View File

@@ -1,9 +1,9 @@
using System.Threading.Tasks; using Bit.App.Abstractions;
using Bit.App.Abstractions;
using Bit.App.Resources; using Bit.App.Resources;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Exceptions; using Bit.Core.Exceptions;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using System.Threading.Tasks;
namespace Bit.App.Pages namespace Bit.App.Pages
{ {

View File

@@ -1,6 +1,4 @@
using System; using Bit.App.Effects;
using System.Threading.Tasks;
using Bit.App.Effects;
using Bit.App.Models; using Bit.App.Models;
using Bit.App.Resources; using Bit.App.Resources;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
@@ -12,10 +10,8 @@ namespace Bit.App.Pages
{ {
public class TabsPage : TabbedPage public class TabsPage : TabbedPage
{ {
private readonly IBroadcasterService _broadcasterService;
private readonly IMessagingService _messagingService; private readonly IMessagingService _messagingService;
private readonly IKeyConnectorService _keyConnectorService; private readonly IKeyConnectorService _keyConnectorService;
private readonly LazyResolve<ILogger> _logger = new LazyResolve<ILogger>("logger");
private NavigationPage _groupingsPage; private NavigationPage _groupingsPage;
private NavigationPage _sendGroupingsPage; private NavigationPage _sendGroupingsPage;
@@ -23,7 +19,6 @@ namespace Bit.App.Pages
public TabsPage(AppOptions appOptions = null, PreviousPageInfo previousPage = null) public TabsPage(AppOptions appOptions = null, PreviousPageInfo previousPage = null)
{ {
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService"); _messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
_keyConnectorService = ServiceContainer.Resolve<IKeyConnectorService>("keyConnectorService"); _keyConnectorService = ServiceContainer.Resolve<IKeyConnectorService>("keyConnectorService");
@@ -83,26 +78,12 @@ namespace Bit.App.Pages
protected override async void OnAppearing() protected override async void OnAppearing()
{ {
base.OnAppearing(); base.OnAppearing();
_broadcasterService.Subscribe(nameof(TabsPage), async (message) =>
{
if (message.Command == "syncCompleted")
{
Device.BeginInvokeOnMainThread(async () => await UpdateVaultButtonTitleAsync());
}
});
await UpdateVaultButtonTitleAsync();
if (await _keyConnectorService.UserNeedsMigration()) if (await _keyConnectorService.UserNeedsMigration())
{ {
_messagingService.Send("convertAccountToKeyConnector"); _messagingService.Send("convertAccountToKeyConnector");
} }
} }
protected override void OnDisappearing()
{
base.OnDisappearing();
_broadcasterService.Unsubscribe(nameof(TabsPage));
}
public void ResetToVaultPage() public void ResetToVaultPage()
{ {
CurrentPage = _groupingsPage; CurrentPage = _groupingsPage;
@@ -150,19 +131,5 @@ namespace Bit.App.Pages
groupingsPage.HideAccountSwitchingOverlayAsync().FireAndForget(); groupingsPage.HideAccountSwitchingOverlayAsync().FireAndForget();
} }
} }
private async Task UpdateVaultButtonTitleAsync()
{
try
{
var policyService = ServiceContainer.Resolve<IPolicyService>("policyService");
var isShowingVaultFilter = await policyService.ShouldShowVaultFilterAsync();
_groupingsPage.Title = isShowingVaultFilter ? AppResources.Vaults : AppResources.MyVault;
}
catch (Exception ex)
{
_logger.Value.Exception(ex);
}
}
} }
} }

View File

@@ -162,7 +162,6 @@
Grid.RowSpan="2" Grid.RowSpan="2"
AutomationProperties.IsInAccessibleTree="True" AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n ToggleVisibility}" AutomationProperties.Name="{u:I18n ToggleVisibility}"
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}"
IsVisible="{Binding Cipher.ViewPassword}" /> IsVisible="{Binding Cipher.ViewPassword}" />
<controls:IconButton <controls:IconButton
StyleClass="box-row-button, box-row-button-platform" StyleClass="box-row-button, box-row-button-platform"
@@ -733,6 +732,7 @@
<BoxView StyleClass="box-row-separator" /> <BoxView StyleClass="box-row-separator" />
</StackLayout> </StackLayout>
<controls:RepeaterView <controls:RepeaterView
x:Name="_collectionsRepeaterView"
ItemsSource="{Binding Collections}" ItemsSource="{Binding Collections}"
IsVisible="{Binding HasCollections}"> IsVisible="{Binding HasCollections}">
<controls:RepeaterView.ItemTemplate> <controls:RepeaterView.ItemTemplate>

View File

@@ -1,12 +1,12 @@
using System.Collections.Generic; using Bit.App.Abstractions;
using System.Threading.Tasks;
using Bit.App.Abstractions;
using Bit.App.Models; using Bit.App.Models;
using Bit.App.Resources; using Bit.App.Resources;
using Bit.App.Utilities; using Bit.App.Utilities;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using System.Collections.Generic;
using System.Threading.Tasks;
using Xamarin.Essentials; using Xamarin.Essentials;
using Xamarin.Forms; using Xamarin.Forms;
using Xamarin.Forms.PlatformConfiguration; using Xamarin.Forms.PlatformConfiguration;
@@ -30,7 +30,6 @@ namespace Bit.App.Pages
CipherType? type = null, CipherType? type = null,
string folderId = null, string folderId = null,
string collectionId = null, string collectionId = null,
string organizationId = null,
string name = null, string name = null,
string uri = null, string uri = null,
bool fromAutofill = false, bool fromAutofill = false,
@@ -52,7 +51,7 @@ namespace Bit.App.Pages
_vm.CipherId = cipherId; _vm.CipherId = cipherId;
_vm.FolderId = folderId == "none" ? null : folderId; _vm.FolderId = folderId == "none" ? null : folderId;
_vm.CollectionIds = collectionId != null ? new HashSet<string>(new List<string> { collectionId }) : null; _vm.CollectionIds = collectionId != null ? new HashSet<string>(new List<string> { collectionId }) : null;
_vm.OrganizationId = organizationId; _vm.CollectionsRepeaterView = _collectionsRepeaterView;
_vm.Type = type; _vm.Type = type;
_vm.DefaultName = name ?? appOptions?.SaveName; _vm.DefaultName = name ?? appOptions?.SaveName;
_vm.DefaultUri = uri ?? appOptions?.Uri; _vm.DefaultUri = uri ?? appOptions?.Uri;
@@ -172,6 +171,7 @@ namespace Bit.App.Pages
{ {
RequestFocus(_nameEntry); RequestFocus(_nameEntry);
} }
_scrollView.Scrolled += (sender, args) => _vm.HandleScroll();
}); });
// Hide password reprompt option if using key connector // Hide password reprompt option if using key connector
_passwordPrompt.IsVisible = !await _keyConnectorService.GetUsesKeyConnector(); _passwordPrompt.IsVisible = !await _keyConnectorService.GetUsesKeyConnector();

View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Bit.App.Abstractions; using Bit.App.Abstractions;
using Bit.App.Controls;
using Bit.App.Models; using Bit.App.Models;
using Bit.App.Resources; using Bit.App.Resources;
using Bit.Core; using Bit.Core;
@@ -43,6 +44,7 @@ namespace Bit.App.Pages
private int _ownershipSelectedIndex; private int _ownershipSelectedIndex;
private bool _hasCollections; private bool _hasCollections;
private string _previousCipherId; private string _previousCipherId;
private DateTime _lastHandledScrollTime;
private List<Core.Models.View.CollectionView> _writeableCollections; private List<Core.Models.View.CollectionView> _writeableCollections;
private string[] _additionalCipherProperties = new string[] private string[] _additionalCipherProperties = new string[]
{ {
@@ -166,7 +168,7 @@ namespace Bit.App.Pages
public ExtendedObservableCollection<LoginUriView> Uris { get; set; } public ExtendedObservableCollection<LoginUriView> Uris { get; set; }
public ExtendedObservableCollection<AddEditPageFieldViewModel> Fields { get; set; } public ExtendedObservableCollection<AddEditPageFieldViewModel> Fields { get; set; }
public ExtendedObservableCollection<CollectionViewModel> Collections { get; set; } public ExtendedObservableCollection<CollectionViewModel> Collections { get; set; }
public RepeaterView CollectionsRepeaterView { get; set; }
public int TypeSelectedIndex public int TypeSelectedIndex
{ {
get => _typeSelectedIndex; get => _typeSelectedIndex;
@@ -249,8 +251,7 @@ namespace Bit.App.Pages
set => SetProperty(ref _showPassword, value, set => SetProperty(ref _showPassword, value,
additionalPropertyNames: new string[] additionalPropertyNames: new string[]
{ {
nameof(ShowPasswordIcon), nameof(ShowPasswordIcon)
nameof(PasswordVisibilityAccessibilityText)
}); });
} }
public bool ShowCardNumber public bool ShowCardNumber
@@ -299,7 +300,6 @@ namespace Bit.App.Pages
public int TotpColumnSpan => Cipher.ViewPassword ? 1 : 2; public int TotpColumnSpan => Cipher.ViewPassword ? 1 : 2;
public bool AllowPersonal { get; set; } public bool AllowPersonal { get; set; }
public bool PasswordPrompt => Cipher.Reprompt != CipherRepromptType.None; public bool PasswordPrompt => Cipher.Reprompt != CipherRepromptType.None;
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.PasswordIsVisibleTapToHide : AppResources.PasswordIsNotVisibleTapToShow;
public void Init() public void Init()
{ {
@@ -457,7 +457,7 @@ namespace Bit.App.Pages
if ((!EditMode || CloneMode) && !AllowPersonal && string.IsNullOrWhiteSpace(Cipher.OrganizationId)) if ((!EditMode || CloneMode) && !AllowPersonal && string.IsNullOrWhiteSpace(Cipher.OrganizationId))
{ {
await Page.DisplayAlert(AppResources.AnErrorHasOccurred, await Page.DisplayAlert(AppResources.AnErrorHasOccurred,
AppResources.PersonalOwnershipSubmitError, AppResources.Ok); AppResources.PersonalOwnershipSubmitError,AppResources.Ok);
return false; return false;
} }
@@ -537,7 +537,7 @@ namespace Bit.App.Pages
AppResources.AnErrorHasOccurred); AppResources.AnErrorHasOccurred);
} }
} }
catch (Exception genex) catch(Exception genex)
{ {
_logger.Exception(genex); _logger.Exception(genex);
await _deviceActionService.HideLoadingAsync(); await _deviceActionService.HideLoadingAsync();
@@ -821,13 +821,30 @@ namespace Bit.App.Pages
{ {
var cols = _writeableCollections.Where(c => c.OrganizationId == Cipher.OrganizationId) var cols = _writeableCollections.Where(c => c.OrganizationId == Cipher.OrganizationId)
.Select(c => new CollectionViewModel { Collection = c }).ToList(); .Select(c => new CollectionViewModel { Collection = c }).ToList();
HasCollections = cols.Any();
Collections.ResetWithRange(cols); Collections.ResetWithRange(cols);
Collections = new ExtendedObservableCollection<CollectionViewModel>(cols);
} }
else else
{ {
HasCollections = false;
Collections.ResetWithRange(new List<CollectionViewModel>()); Collections.ResetWithRange(new List<CollectionViewModel>());
Collections = new ExtendedObservableCollection<CollectionViewModel>(new List<CollectionViewModel>());
} }
HasCollections = Collections.Any(); }
public void HandleScroll()
{
// workaround for https://github.com/xamarin/Xamarin.Forms/issues/13607
// required for org ownership/collections to render properly in XF4.5+
if (!HasCollections ||
EditMode ||
(DateTime.Now - _lastHandledScrollTime < TimeSpan.FromMilliseconds(200)))
{
return;
}
CollectionsRepeaterView.ItemsSource = Collections;
_lastHandledScrollTime = DateTime.Now;
} }
private void TriggerCipherChanged() private void TriggerCipherChanged()

View File

@@ -1,6 +1,6 @@
using System; using Bit.Core.Abstractions;
using Bit.Core.Abstractions;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using System;
using Xamarin.Forms; using Xamarin.Forms;
namespace Bit.App.Pages namespace Bit.App.Pages

View File

@@ -1,13 +1,13 @@
using System.Collections.Generic; using Bit.App.Abstractions;
using System.Linq;
using System.Threading.Tasks;
using Bit.App.Abstractions;
using Bit.App.Resources; using Bit.App.Resources;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Exceptions; using Bit.Core.Exceptions;
using Bit.Core.Models.Domain; using Bit.Core.Models.Domain;
using Bit.Core.Models.View; using Bit.Core.Models.View;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Xamarin.Forms; using Xamarin.Forms;
namespace Bit.App.Pages namespace Bit.App.Pages

View File

@@ -15,18 +15,7 @@
</ContentPage.BindingContext> </ContentPage.BindingContext>
<ContentPage.ToolbarItems> <ContentPage.ToolbarItems>
<controls:ExtendedToolbarItem <ToolbarItem Icon="search.png" Clicked="Search_Clicked" />
x:Name="_accountAvatar"
IconImageSource="{Binding AvatarImageSource}"
Command="{Binding Source={x:Reference _accountListOverlay}, Path=ToggleVisibililtyCommand}"
Order="Primary"
Priority="-1"
UseOriginalImage="True"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n Account}" />
<ToolbarItem Icon="search.png" Clicked="Search_Clicked"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n Search}" />
</ContentPage.ToolbarItems> </ContentPage.ToolbarItems>
<ContentPage.Resources> <ContentPage.Resources>
@@ -69,7 +58,7 @@
VerticalOptions="CenterAndExpand" VerticalOptions="CenterAndExpand"
Padding="20, 0" Padding="20, 0"
Spacing="20" Spacing="20"
IsVisible="{Binding ShowNoData}"> IsVisible="{Binding ShowList, Converter={StaticResource inverseBool}}">
<Label <Label
Text="{Binding NoDataText}" Text="{Binding NoDataText}"
HorizontalTextAlignment="Center"></Label> HorizontalTextAlignment="Center"></Label>
@@ -85,8 +74,7 @@
ItemTemplate="{StaticResource listItemDataTemplateSelector}" ItemTemplate="{StaticResource listItemDataTemplateSelector}"
SelectionMode="Single" SelectionMode="Single"
SelectionChanged="RowSelected" SelectionChanged="RowSelected"
StyleClass="list, list-platform" StyleClass="list, list-platform" />
ExtraDataForLogging="Autofill Ciphers Page" />
</StackLayout> </StackLayout>
</ResourceDictionary> </ResourceDictionary>
</ContentPage.Resources> </ContentPage.Resources>
@@ -100,8 +88,6 @@
AbsoluteLayout.LayoutFlags="All" AbsoluteLayout.LayoutFlags="All"
AbsoluteLayout.LayoutBounds="0, 0, 1, 1"> AbsoluteLayout.LayoutBounds="0, 0, 1, 1">
</ContentView> </ContentView>
<!-- Android FAB -->
<Button <Button
x:Name="_fab" x:Name="_fab"
Image="plus.png" Image="plus.png"
@@ -113,14 +99,6 @@
<effects:FabShadowEffect /> <effects:FabShadowEffect />
</Button.Effects> </Button.Effects>
</Button> </Button>
<controls:AccountSwitchingOverlayView
x:Name="_accountListOverlay"
AbsoluteLayout.LayoutBounds="0, 0, 1, 1"
AbsoluteLayout.LayoutFlags="All"
MainPage="{Binding Source={x:Reference _page}}"
MainFab="{Binding Source={x:Reference _fab}, Path=.}"
BindingContext="{Binding AccountSwitchingOverlayViewModel}"/>
</AbsoluteLayout> </AbsoluteLayout>
</pages:BaseContentPage> </pages:BaseContentPage>

View File

@@ -1,12 +1,13 @@
using System; using Bit.App.Models;
using System.Linq; using Bit.App.Resources;
using System.Threading.Tasks;
using Bit.App.Controls;
using Bit.App.Models;
using Bit.App.Utilities;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using System;
using System.Linq;
using System.Threading.Tasks;
using Bit.App.Controls;
using Bit.App.Utilities;
using Xamarin.Forms; using Xamarin.Forms;
namespace Bit.App.Pages namespace Bit.App.Pages
@@ -14,8 +15,7 @@ namespace Bit.App.Pages
public partial class AutofillCiphersPage : BaseContentPage public partial class AutofillCiphersPage : BaseContentPage
{ {
private readonly AppOptions _appOptions; private readonly AppOptions _appOptions;
private readonly IBroadcasterService _broadcasterService; private readonly IPlatformUtilsService _platformUtilsService;
private readonly ISyncService _syncService;
private readonly IVaultTimeoutService _vaultTimeoutService; private readonly IVaultTimeoutService _vaultTimeoutService;
private AutofillCiphersPageViewModel _vm; private AutofillCiphersPageViewModel _vm;
@@ -24,23 +24,17 @@ namespace Bit.App.Pages
{ {
_appOptions = appOptions; _appOptions = appOptions;
InitializeComponent(); InitializeComponent();
SetActivityIndicator(_mainContent);
_vm = BindingContext as AutofillCiphersPageViewModel; _vm = BindingContext as AutofillCiphersPageViewModel;
_vm.Page = this; _vm.Page = this;
_vm.Init(appOptions); _vm.Init(appOptions);
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService"); _platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_syncService = ServiceContainer.Resolve<ISyncService>("syncService");
_vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService"); _vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
} }
protected async override void OnAppearing() protected async override void OnAppearing()
{ {
base.OnAppearing(); base.OnAppearing();
if (_syncService.SyncInProgress)
{
IsBusy = true;
}
if (!await AppHelpers.IsVaultTimeoutImmediateAsync()) if (!await AppHelpers.IsVaultTimeoutImmediateAsync())
{ {
await _vaultTimeoutService.CheckVaultTimeoutAsync(); await _vaultTimeoutService.CheckVaultTimeoutAsync();
@@ -49,37 +43,13 @@ namespace Bit.App.Pages
{ {
return; return;
} }
_accountAvatar?.OnAppearing();
_vm.AvatarImageSource = await GetAvatarImageSourceAsync();
_broadcasterService.Subscribe(nameof(AutofillCiphersPage), async (message) =>
{
if (message.Command == "syncStarted")
{
Device.BeginInvokeOnMainThread(() => IsBusy = true);
}
else if (message.Command == "syncCompleted")
{
await Task.Delay(500);
Device.BeginInvokeOnMainThread(() =>
{
IsBusy = false;
if (_vm.LoadedOnce)
{
var task = _vm.LoadAsync();
}
});
}
});
await LoadOnAppearedAsync(_mainLayout, false, async () => await LoadOnAppearedAsync(_mainLayout, false, async () =>
{ {
try try
{ {
await _vm.LoadAsync(); await _vm.LoadAsync();
} }
catch (Exception e) when (e.Message.Contains("No key.")) catch (Exception e) when(e.Message.Contains("No key."))
{ {
await Task.Delay(1000); await Task.Delay(1000);
await _vm.LoadAsync(); await _vm.LoadAsync();
@@ -89,11 +59,6 @@ namespace Bit.App.Pages
protected override bool OnBackButtonPressed() protected override bool OnBackButtonPressed()
{ {
if (_accountListOverlay.IsVisible)
{
_accountListOverlay.HideAsync().FireAndForget();
return true;
}
if (Device.RuntimePlatform == Device.Android) if (Device.RuntimePlatform == Device.Android)
{ {
_appOptions.Uri = null; _appOptions.Uri = null;
@@ -101,13 +66,6 @@ namespace Bit.App.Pages
return base.OnBackButtonPressed(); return base.OnBackButtonPressed();
} }
protected override void OnDisappearing()
{
base.OnDisappearing();
IsBusy = false;
_accountAvatar?.OnDisappearing();
}
private async void RowSelected(object sender, SelectionChangedEventArgs e) private async void RowSelected(object sender, SelectionChangedEventArgs e)
{ {
((ExtendedCollectionView)sender).SelectedItem = null; ((ExtendedCollectionView)sender).SelectedItem = null;

View File

@@ -1,8 +1,4 @@
using System.Collections.Generic; using Bit.App.Abstractions;
using System.Linq;
using System.Threading.Tasks;
using Bit.App.Abstractions;
using Bit.App.Controls;
using Bit.App.Models; using Bit.App.Models;
using Bit.App.Resources; using Bit.App.Resources;
using Bit.App.Utilities; using Bit.App.Utilities;
@@ -12,6 +8,9 @@ using Bit.Core.Enums;
using Bit.Core.Exceptions; using Bit.Core.Exceptions;
using Bit.Core.Models.View; using Bit.Core.Models.View;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Xamarin.CommunityToolkit.ObjectModel; using Xamarin.CommunityToolkit.ObjectModel;
using Xamarin.Forms; using Xamarin.Forms;
@@ -24,10 +23,8 @@ namespace Bit.App.Pages
private readonly ICipherService _cipherService; private readonly ICipherService _cipherService;
private readonly IStateService _stateService; private readonly IStateService _stateService;
private readonly IPasswordRepromptService _passwordRepromptService; private readonly IPasswordRepromptService _passwordRepromptService;
private readonly IMessagingService _messagingService;
private readonly ILogger _logger;
private bool _showNoData; private AppOptions _appOptions;
private bool _showList; private bool _showList;
private string _noDataText; private string _noDataText;
private bool _websiteIconsEnabled; private bool _websiteIconsEnabled;
@@ -39,30 +36,15 @@ namespace Bit.App.Pages
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService"); _deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
_stateService = ServiceContainer.Resolve<IStateService>("stateService"); _stateService = ServiceContainer.Resolve<IStateService>("stateService");
_passwordRepromptService = ServiceContainer.Resolve<IPasswordRepromptService>("passwordRepromptService"); _passwordRepromptService = ServiceContainer.Resolve<IPasswordRepromptService>("passwordRepromptService");
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
_logger = ServiceContainer.Resolve<ILogger>("logger");
GroupedItems = new ObservableRangeCollection<IGroupingsPageListItem>(); GroupedItems = new ObservableRangeCollection<IGroupingsPageListItem>();
CipherOptionsCommand = new Command<CipherView>(CipherOptionsAsync); CipherOptionsCommand = new Command<CipherView>(CipherOptionsAsync);
AccountSwitchingOverlayViewModel = new AccountSwitchingOverlayViewModel(_stateService, _messagingService, _logger)
{
AllowAddAccountRow = false
};
} }
public string Name { get; set; } public string Name { get; set; }
public string Uri { get; set; } public string Uri { get; set; }
public Command CipherOptionsCommand { get; set; } public Command CipherOptionsCommand { get; set; }
public bool LoadedOnce { get; set; }
public ObservableRangeCollection<IGroupingsPageListItem> GroupedItems { get; set; } public ObservableRangeCollection<IGroupingsPageListItem> GroupedItems { get; set; }
public AccountSwitchingOverlayViewModel AccountSwitchingOverlayViewModel { get; }
public bool ShowNoData
{
get => _showNoData;
set => SetProperty(ref _showNoData, value);
}
public bool ShowList public bool ShowList
{ {
@@ -83,9 +65,10 @@ namespace Bit.App.Pages
public void Init(AppOptions appOptions) public void Init(AppOptions appOptions)
{ {
Uri = appOptions?.Uri; _appOptions = appOptions;
Uri = appOptions.Uri;
string name = null; string name = null;
if (Uri?.StartsWith(Constants.AndroidAppProtocol) ?? false) if (Uri.StartsWith(Constants.AndroidAppProtocol))
{ {
name = Uri.Substring(Constants.AndroidAppProtocol.Length); name = Uri.Substring(Constants.AndroidAppProtocol.Length);
} }
@@ -104,10 +87,8 @@ namespace Bit.App.Pages
public async Task LoadAsync() public async Task LoadAsync()
{ {
LoadedOnce = true;
ShowList = false;
ShowNoData = false;
WebsiteIconsEnabled = !(await _stateService.GetDisableFaviconAsync()).GetValueOrDefault(); WebsiteIconsEnabled = !(await _stateService.GetDisableFaviconAsync()).GetValueOrDefault();
ShowList = false;
var groupedItems = new List<GroupingsPageListGroup>(); var groupedItems = new List<GroupingsPageListGroup>();
var ciphers = await _cipherService.GetAllDecryptedByUrlAsync(Uri, null); var ciphers = await _cipherService.GetAllDecryptedByUrlAsync(Uri, null);
var matching = ciphers.Item1?.Select(c => new GroupingsPageListItem { Cipher = c }).ToList(); var matching = ciphers.Item1?.Select(c => new GroupingsPageListItem { Cipher = c }).ToList();
@@ -169,7 +150,6 @@ namespace Bit.App.Pages
} }
} }
ShowList = groupedItems.Any(); ShowList = groupedItems.Any();
ShowNoData = !ShowList;
} }
public async Task SelectCipherAsync(CipherView cipher, bool fuzzy) public async Task SelectCipherAsync(CipherView cipher, bool fuzzy)

View File

@@ -33,9 +33,7 @@
Text="&#xe5c4;" Text="&#xe5c4;"
VerticalOptions="CenterAndExpand" VerticalOptions="CenterAndExpand"
Clicked="BackButton_Clicked" Clicked="BackButton_Clicked"
x:Name="_backButton" x:Name="_backButton" />
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n TapToGoBack}"/>
<controls:ExtendedSearchBar <controls:ExtendedSearchBar
x:Name="_searchBar" x:Name="_searchBar"
HorizontalOptions="FillAndExpand" HorizontalOptions="FillAndExpand"
@@ -49,31 +47,6 @@
</ContentPage.Resources> </ContentPage.Resources>
<StackLayout x:Name="_mainLayout" Spacing="0" Padding="0"> <StackLayout x:Name="_mainLayout" Spacing="0" Padding="0">
<StackLayout
IsVisible="{Binding ShowVaultFilter}"
Orientation="Horizontal"
HorizontalOptions="FillAndExpand"
Margin="0,5,0,0">
<Label
Text="{Binding VaultFilterDescription}"
LineBreakMode="TailTruncation"
Margin="10,0"
StyleClass="text-md, text-muted"
VerticalOptions="Center"
HorizontalOptions="StartAndExpand"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n Filter}" />
<controls:MiButton
Text="{Binding Source={x:Static core:BitwardenIcons.ViewCellMenu}}"
StyleClass="list-row-button-text, list-row-button-platform"
Command="{Binding VaultFilterCommand}"
VerticalOptions="Center"
HorizontalOptions="End"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n Filter}" />
</StackLayout>
<BoxView StyleClass="box-row-separator" />
<controls:IconLabel IsVisible="{Binding ShowSearchDirection}" <controls:IconLabel IsVisible="{Binding ShowSearchDirection}"
Text="{Binding Source={x:Static core:BitwardenIcons.Search}}" Text="{Binding Source={x:Static core:BitwardenIcons.Search}}"
StyleClass="text-muted" StyleClass="text-muted"
@@ -93,8 +66,7 @@
VerticalOptions="FillAndExpand" VerticalOptions="FillAndExpand"
SelectionMode="Single" SelectionMode="Single"
SelectionChanged="RowSelected" SelectionChanged="RowSelected"
StyleClass="list, list-platform" StyleClass="list, list-platform">
ExtraDataForLogging="Ciphers Page">
<CollectionView.ItemTemplate> <CollectionView.ItemTemplate>
<DataTemplate x:DataType="views:CipherView"> <DataTemplate x:DataType="views:CipherView">
<controls:CipherViewCell <controls:CipherViewCell

View File

@@ -1,10 +1,10 @@
using System; using Bit.App.Abstractions;
using System.Linq;
using Bit.App.Abstractions;
using Bit.App.Controls;
using Bit.App.Resources; using Bit.App.Resources;
using Bit.Core.Models.View; using Bit.Core.Models.View;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using System;
using System.Linq;
using Bit.App.Controls;
using Xamarin.Forms; using Xamarin.Forms;
namespace Bit.App.Pages namespace Bit.App.Pages
@@ -17,8 +17,8 @@ namespace Bit.App.Pages
private CiphersPageViewModel _vm; private CiphersPageViewModel _vm;
private bool _hasFocused; private bool _hasFocused;
public CiphersPage(Func<CipherView, bool> filter, string pageTitle = null, string vaultFilterSelection = null, public CiphersPage(Func<CipherView, bool> filter, bool folder = false, bool collection = false,
string autofillUrl = null, bool deleted = false) bool type = false, string autofillUrl = null, bool deleted = false)
{ {
InitializeComponent(); InitializeComponent();
_vm = BindingContext as CiphersPageViewModel; _vm = BindingContext as CiphersPageViewModel;
@@ -26,15 +26,26 @@ namespace Bit.App.Pages
_vm.Filter = filter; _vm.Filter = filter;
_vm.AutofillUrl = _autofillUrl = autofillUrl; _vm.AutofillUrl = _autofillUrl = autofillUrl;
_vm.Deleted = deleted; _vm.Deleted = deleted;
if (pageTitle != null) if (deleted)
{ {
_vm.PageTitle = string.Format(AppResources.SearchGroup, pageTitle); _vm.PageTitle = AppResources.SearchTrash;
}
else if (folder)
{
_vm.PageTitle = AppResources.SearchFolder;
}
else if (collection)
{
_vm.PageTitle = AppResources.SearchCollection;
}
else if (type)
{
_vm.PageTitle = AppResources.SearchType;
} }
else else
{ {
_vm.PageTitle = AppResources.SearchVault; _vm.PageTitle = AppResources.SearchVault;
} }
_vm.VaultFilterDescription = vaultFilterSelection;
if (Device.RuntimePlatform == Device.iOS) if (Device.RuntimePlatform == Device.iOS)
{ {
@@ -42,7 +53,6 @@ namespace Bit.App.Pages
_searchBar.Placeholder = AppResources.Search; _searchBar.Placeholder = AppResources.Search;
_mainLayout.Children.Insert(0, _searchBar); _mainLayout.Children.Insert(0, _searchBar);
_mainLayout.Children.Insert(1, _separator); _mainLayout.Children.Insert(1, _separator);
ShowModalAnimationDelay = 0;
} }
else else
{ {

View File

@@ -1,21 +1,21 @@
using System; using Bit.App.Abstractions;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Bit.App.Abstractions;
using Bit.App.Resources; using Bit.App.Resources;
using Bit.Core;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Exceptions; using Bit.Core.Exceptions;
using Bit.Core.Models.View; using Bit.Core.Models.View;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using Xamarin.CommunityToolkit.ObjectModel; using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Xamarin.Forms; using Xamarin.Forms;
namespace Bit.App.Pages namespace Bit.App.Pages
{ {
public class CiphersPageViewModel : VaultFilterViewModel public class CiphersPageViewModel : BaseViewModel
{ {
private readonly IPlatformUtilsService _platformUtilsService; private readonly IPlatformUtilsService _platformUtilsService;
private readonly ICipherService _cipherService; private readonly ICipherService _cipherService;
@@ -23,10 +23,7 @@ namespace Bit.App.Pages
private readonly IDeviceActionService _deviceActionService; private readonly IDeviceActionService _deviceActionService;
private readonly IStateService _stateService; private readonly IStateService _stateService;
private readonly IPasswordRepromptService _passwordRepromptService; private readonly IPasswordRepromptService _passwordRepromptService;
private readonly IOrganizationService _organizationService;
private readonly IPolicyService _policyService;
private CancellationTokenSource _searchCancellationTokenSource; private CancellationTokenSource _searchCancellationTokenSource;
private readonly ILogger _logger;
private bool _showNoData; private bool _showNoData;
private bool _showList; private bool _showList;
@@ -40,9 +37,6 @@ namespace Bit.App.Pages
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService"); _deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
_stateService = ServiceContainer.Resolve<IStateService>("stateService"); _stateService = ServiceContainer.Resolve<IStateService>("stateService");
_passwordRepromptService = ServiceContainer.Resolve<IPasswordRepromptService>("passwordRepromptService"); _passwordRepromptService = ServiceContainer.Resolve<IPasswordRepromptService>("passwordRepromptService");
_organizationService = ServiceContainer.Resolve<IOrganizationService>("organizationService");
_policyService = ServiceContainer.Resolve<IPolicyService>("policyService");
_logger = ServiceContainer.Resolve<ILogger>("logger");
Ciphers = new ExtendedObservableCollection<CipherView>(); Ciphers = new ExtendedObservableCollection<CipherView>();
CipherOptionsCommand = new Command<CipherView>(CipherOptionsAsync); CipherOptionsCommand = new Command<CipherView>(CipherOptionsAsync);
@@ -54,11 +48,6 @@ namespace Bit.App.Pages
public string AutofillUrl { get; set; } public string AutofillUrl { get; set; }
public bool Deleted { get; set; } public bool Deleted { get; set; }
protected override ICipherService cipherService => _cipherService;
protected override IPolicyService policyService => _policyService;
protected override IOrganizationService organizationService => _organizationService;
protected override ILogger logger => _logger;
public bool ShowNoData public bool ShowNoData
{ {
get => _showNoData; get => _showNoData;
@@ -87,9 +76,11 @@ namespace Bit.App.Pages
public async Task InitAsync() public async Task InitAsync()
{ {
await InitVaultFilterAsync(true);
WebsiteIconsEnabled = !(await _stateService.GetDisableFaviconAsync()).GetValueOrDefault(); WebsiteIconsEnabled = !(await _stateService.GetDisableFaviconAsync()).GetValueOrDefault();
PerformSearchIfPopulated(); if (!string.IsNullOrWhiteSpace((Page as CiphersPage).SearchBar.Text))
{
Search((Page as CiphersPage).SearchBar.Text, 200);
}
} }
public void Search(string searchText, int? timeout = null) public void Search(string searchText, int? timeout = null)
@@ -116,9 +107,8 @@ namespace Bit.App.Pages
} }
try try
{ {
var vaultFilteredCiphers = await GetAllCiphersAsync();
ciphers = await _searchService.SearchCiphersAsync(searchText, ciphers = await _searchService.SearchCiphersAsync(searchText,
Filter ?? (c => c.IsDeleted == Deleted), vaultFilteredCiphers, cts.Token); Filter ?? (c => c.IsDeleted == Deleted), null, cts.Token);
cts.Token.ThrowIfCancellationRequested(); cts.Token.ThrowIfCancellationRequested();
} }
catch (OperationCanceledException) catch (OperationCanceledException)
@@ -202,19 +192,6 @@ namespace Bit.App.Pages
} }
} }
private void PerformSearchIfPopulated()
{
if (!string.IsNullOrWhiteSpace((Page as CiphersPage).SearchBar.Text))
{
Search((Page as CiphersPage).SearchBar.Text, 200);
}
}
protected override async Task OnVaultFilterSelectedAsync()
{
PerformSearchIfPopulated();
}
private async void CipherOptionsAsync(CipherView cipher) private async void CipherOptionsAsync(CipherView cipher)
{ {
if ((Page as BaseContentPage).DoOnce()) if ((Page as BaseContentPage).DoOnce())

View File

@@ -1,13 +1,13 @@
using System.Collections.Generic; using Bit.App.Abstractions;
using System.Linq;
using System.Threading.Tasks;
using Bit.App.Abstractions;
using Bit.App.Resources; using Bit.App.Resources;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Exceptions; using Bit.Core.Exceptions;
using Bit.Core.Models.Domain; using Bit.Core.Models.Domain;
using Bit.Core.Models.View; using Bit.Core.Models.View;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Bit.App.Pages namespace Bit.App.Pages
{ {

View File

@@ -6,7 +6,6 @@
xmlns:u="clr-namespace:Bit.App.Utilities" xmlns:u="clr-namespace:Bit.App.Utilities"
xmlns:effects="clr-namespace:Bit.App.Effects" xmlns:effects="clr-namespace:Bit.App.Effects"
xmlns:controls="clr-namespace:Bit.App.Controls" xmlns:controls="clr-namespace:Bit.App.Controls"
xmlns:core="clr-namespace:Bit.Core;assembly=BitwardenCore"
x:DataType="pages:GroupingsPageViewModel" x:DataType="pages:GroupingsPageViewModel"
Title="{Binding PageTitle}" Title="{Binding PageTitle}"
x:Name="_page"> x:Name="_page">
@@ -107,30 +106,6 @@
GroupTemplate="{StaticResource groupTemplate}" /> GroupTemplate="{StaticResource groupTemplate}" />
<StackLayout x:Key="mainLayout" x:Name="_mainLayout"> <StackLayout x:Key="mainLayout" x:Name="_mainLayout">
<StackLayout
IsVisible="{Binding ShowVaultFilter}"
Orientation="Horizontal"
HorizontalOptions="FillAndExpand"
Margin="0,5,0,0">
<Label
Text="{Binding VaultFilterDescription}"
LineBreakMode="TailTruncation"
Margin="10,0"
StyleClass="text-md, text-muted"
VerticalOptions="Center"
HorizontalOptions="StartAndExpand"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n Filter}" />
<controls:MiButton
Text="{Binding Source={x:Static core:BitwardenIcons.ViewCellMenu}}"
StyleClass="list-row-button-text, list-row-button-platform"
Command="{Binding VaultFilterCommand}"
VerticalOptions="Center"
HorizontalOptions="End"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n Filter}" />
</StackLayout>
<StackLayout <StackLayout
VerticalOptions="CenterAndExpand" VerticalOptions="CenterAndExpand"
Padding="20, 0" Padding="20, 0"
@@ -155,8 +130,7 @@
ItemTemplate="{StaticResource listItemDataTemplateSelector}" ItemTemplate="{StaticResource listItemDataTemplateSelector}"
SelectionMode="Single" SelectionMode="Single"
SelectionChanged="RowSelected" SelectionChanged="RowSelected"
StyleClass="list, list-platform" StyleClass="list, list-platform" />
ExtraDataForLogging="Groupings Page" />
</RefreshView> </RefreshView>
</StackLayout> </StackLayout>
</ResourceDictionary> </ResourceDictionary>

View File

@@ -27,8 +27,8 @@ namespace Bit.App.Pages
private PreviousPageInfo _previousPage; private PreviousPageInfo _previousPage;
public GroupingsPage(bool mainPage, CipherType? type = null, string folderId = null, public GroupingsPage(bool mainPage, CipherType? type = null, string folderId = null,
string collectionId = null, string pageTitle = null, string vaultFilterSelection = null, string collectionId = null, string pageTitle = null, PreviousPageInfo previousPage = null,
PreviousPageInfo previousPage = null, bool deleted = false) bool deleted = false)
{ {
_pageName = string.Concat(nameof(GroupingsPage), "_", DateTime.UtcNow.Ticks); _pageName = string.Concat(nameof(GroupingsPage), "_", DateTime.UtcNow.Ticks);
InitializeComponent(); InitializeComponent();
@@ -52,10 +52,6 @@ namespace Bit.App.Pages
{ {
_vm.PageTitle = pageTitle; _vm.PageTitle = pageTitle;
} }
if (vaultFilterSelection != null)
{
_vm.VaultFilterDescription = vaultFilterSelection;
}
if (Device.RuntimePlatform == Device.iOS) if (Device.RuntimePlatform == Device.iOS)
{ {
@@ -121,7 +117,7 @@ namespace Bit.App.Pages
{ {
await _vm.LoadAsync(); await _vm.LoadAsync();
} }
catch (Exception e) when (e.Message.Contains("No key.")) catch (Exception e) when(e.Message.Contains("No key."))
{ {
await Task.Delay(1000); await Task.Delay(1000);
await _vm.LoadAsync(); await _vm.LoadAsync();
@@ -229,7 +225,8 @@ namespace Bit.App.Pages
await _accountListOverlay.HideAsync(); await _accountListOverlay.HideAsync();
if (DoOnce()) if (DoOnce())
{ {
var page = new CiphersPage(_vm.Filter, _vm.MainPage ? null : _vm.PageTitle, deleted: _vm.Deleted); var page = new CiphersPage(_vm.Filter, _vm.FolderId != null, _vm.CollectionId != null,
_vm.Type != null, deleted: _vm.Deleted);
await Navigation.PushModalAsync(new NavigationPage(page)); await Navigation.PushModalAsync(new NavigationPage(page));
} }
} }
@@ -263,7 +260,7 @@ namespace Bit.App.Pages
} }
if (!_vm.Deleted && DoOnce()) if (!_vm.Deleted && DoOnce())
{ {
var page = new AddEditPage(null, _vm.Type, _vm.FolderId, _vm.CollectionId, _vm.GetVaultFilterOrgId()); var page = new AddEditPage(null, _vm.Type, _vm.FolderId, _vm.CollectionId);
await Navigation.PushModalAsync(new NavigationPage(page)); await Navigation.PushModalAsync(new NavigationPage(page));
} }
} }

View File

@@ -16,7 +16,7 @@ using Xamarin.Forms;
namespace Bit.App.Pages namespace Bit.App.Pages
{ {
public class GroupingsPageViewModel : VaultFilterViewModel public class GroupingsPageViewModel : BaseViewModel
{ {
private const int NoFolderListSize = 100; private const int NoFolderListSize = 100;
@@ -46,8 +46,6 @@ namespace Bit.App.Pages
private readonly IMessagingService _messagingService; private readonly IMessagingService _messagingService;
private readonly IStateService _stateService; private readonly IStateService _stateService;
private readonly IPasswordRepromptService _passwordRepromptService; private readonly IPasswordRepromptService _passwordRepromptService;
private readonly IOrganizationService _organizationService;
private readonly IPolicyService _policyService;
private readonly ILogger _logger; private readonly ILogger _logger;
public GroupingsPageViewModel() public GroupingsPageViewModel()
@@ -62,11 +60,10 @@ namespace Bit.App.Pages
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService"); _messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
_stateService = ServiceContainer.Resolve<IStateService>("stateService"); _stateService = ServiceContainer.Resolve<IStateService>("stateService");
_passwordRepromptService = ServiceContainer.Resolve<IPasswordRepromptService>("passwordRepromptService"); _passwordRepromptService = ServiceContainer.Resolve<IPasswordRepromptService>("passwordRepromptService");
_organizationService = ServiceContainer.Resolve<IOrganizationService>("organizationService");
_policyService = ServiceContainer.Resolve<IPolicyService>("policyService");
_logger = ServiceContainer.Resolve<ILogger>("logger"); _logger = ServiceContainer.Resolve<ILogger>("logger");
Loading = true; Loading = true;
PageTitle = AppResources.MyVault;
GroupedItems = new ObservableRangeCollection<IGroupingsPageListItem>(); GroupedItems = new ObservableRangeCollection<IGroupingsPageListItem>();
RefreshCommand = new Command(async () => RefreshCommand = new Command(async () =>
{ {
@@ -90,9 +87,8 @@ namespace Bit.App.Pages
public bool HasCiphers { get; set; } public bool HasCiphers { get; set; }
public bool HasFolders { get; set; } public bool HasFolders { get; set; }
public bool HasCollections { get; set; } public bool HasCollections { get; set; }
public bool ShowNoFolderCipherGroup => NoFolderCiphers != null public bool ShowNoFolderCiphers => (NoFolderCiphers?.Count ?? int.MaxValue) < NoFolderListSize &&
&& NoFolderCiphers.Count < NoFolderListSize (!Collections?.Any() ?? true);
&& (Collections is null || !Collections.Any());
public List<CipherView> Ciphers { get; set; } public List<CipherView> Ciphers { get; set; }
public List<CipherView> FavoriteCiphers { get; set; } public List<CipherView> FavoriteCiphers { get; set; }
public List<CipherView> NoFolderCiphers { get; set; } public List<CipherView> NoFolderCiphers { get; set; }
@@ -101,11 +97,6 @@ namespace Bit.App.Pages
public List<Core.Models.View.CollectionView> Collections { get; set; } public List<Core.Models.View.CollectionView> Collections { get; set; }
public List<TreeNode<Core.Models.View.CollectionView>> NestedCollections { get; set; } public List<TreeNode<Core.Models.View.CollectionView>> NestedCollections { get; set; }
protected override ICipherService cipherService => _cipherService;
protected override IPolicyService policyService => _policyService;
protected override IOrganizationService organizationService => _organizationService;
protected override ILogger logger => _logger;
public bool Refreshing public bool Refreshing
{ {
get => _refreshing; get => _refreshing;
@@ -181,12 +172,6 @@ namespace Bit.App.Pages
return; return;
} }
await InitVaultFilterAsync(MainPage);
if (MainPage)
{
PageTitle = ShowVaultFilter ? AppResources.Vaults : AppResources.MyVault;
}
_doingLoad = true; _doingLoad = true;
LoadedOnce = true; LoadedOnce = true;
ShowNoData = false; ShowNoData = false;
@@ -200,9 +185,9 @@ namespace Bit.App.Pages
try try
{ {
await LoadDataAsync(); await LoadDataAsync();
if (ShowNoFolderCipherGroup && (NestedFolders?.Any() ?? false)) if (ShowNoFolderCiphers && (NestedFolders?.Any() ?? false))
{ {
// Remove "No Folder" folder from folders group // Remove "No Folder" from folder listing
NestedFolders = NestedFolders.GetRange(0, NestedFolders.Count - 1); NestedFolders = NestedFolders.GetRange(0, NestedFolders.Count - 1);
} }
@@ -277,7 +262,7 @@ namespace Bit.App.Pages
groupedItems.Add(new GroupingsPageListGroup(ciphersListItems, AppResources.Items, groupedItems.Add(new GroupingsPageListGroup(ciphersListItems, AppResources.Items,
ciphersListItems.Count, uppercaseGroupNames, !MainPage && !groupedItems.Any())); ciphersListItems.Count, uppercaseGroupNames, !MainPage && !groupedItems.Any()));
} }
if (ShowNoFolderCipherGroup) if (ShowNoFolderCiphers)
{ {
var noFolderCiphersListItems = NoFolderCiphers.Select( var noFolderCiphersListItems = NoFolderCiphers.Select(
c => new GroupingsPageListItem { Cipher = c }).ToList(); c => new GroupingsPageListItem { Cipher = c }).ToList();
@@ -309,12 +294,6 @@ namespace Bit.App.Pages
items.AddRange(itemGroup); items.AddRange(itemGroup);
} }
if (Device.RuntimePlatform == Device.iOS)
{
// HACK: [PS-536] Fix to avoid blank list after back navigation on unlocking with previous page info
// because of update to XF v5.0.0.2401
GroupedItems.Clear();
}
GroupedItems.ReplaceRange(items); GroupedItems.ReplaceRange(items);
} }
else else
@@ -337,12 +316,6 @@ namespace Bit.App.Pages
if (groupedItems.Any()) if (groupedItems.Any())
{ {
if (Device.RuntimePlatform == Device.iOS)
{
// HACK: [PS-536] Fix to avoid blank list after back navigation on unlocking with previous page info
// because of update to XF v5.0.0.2401
GroupedItems.Clear();
}
GroupedItems.ReplaceRange(new List<IGroupingsPageListItem> { new GroupingsPageHeaderListItem(groupedItems[0].Name, groupedItems[0].ItemCount) }); GroupedItems.ReplaceRange(new List<IGroupingsPageListItem> { new GroupingsPageHeaderListItem(groupedItems[0].Name, groupedItems[0].ItemCount) });
GroupedItems.AddRange(items); GroupedItems.AddRange(items);
} }
@@ -369,11 +342,6 @@ namespace Bit.App.Pages
SyncRefreshing = false; SyncRefreshing = false;
} }
protected override async Task OnVaultFilterSelectedAsync()
{
await LoadAsync();
}
public async Task SelectCipherAsync(CipherView cipher) public async Task SelectCipherAsync(CipherView cipher)
{ {
var page = new ViewPage(cipher.Id); var page = new ViewPage(cipher.Id);
@@ -400,26 +368,25 @@ namespace Bit.App.Pages
default: default:
break; break;
} }
var page = new GroupingsPage(false, type, null, null, title, _vaultFilterSelection); var page = new GroupingsPage(false, type, null, null, title);
await Page.Navigation.PushAsync(page); await Page.Navigation.PushAsync(page);
} }
public async Task SelectFolderAsync(FolderView folder) public async Task SelectFolderAsync(FolderView folder)
{ {
var page = new GroupingsPage(false, null, folder.Id ?? "none", null, folder.Name, _vaultFilterSelection); var page = new GroupingsPage(false, null, folder.Id ?? "none", null, folder.Name);
await Page.Navigation.PushAsync(page); await Page.Navigation.PushAsync(page);
} }
public async Task SelectCollectionAsync(Core.Models.View.CollectionView collection) public async Task SelectCollectionAsync(Core.Models.View.CollectionView collection)
{ {
var page = new GroupingsPage(false, null, null, collection.Id, collection.Name, _vaultFilterSelection); var page = new GroupingsPage(false, null, null, collection.Id, collection.Name);
await Page.Navigation.PushAsync(page); await Page.Navigation.PushAsync(page);
} }
public async Task SelectTrashAsync() public async Task SelectTrashAsync()
{ {
var page = new GroupingsPage(false, null, null, null, AppResources.Trash, _vaultFilterSelection, null, var page = new GroupingsPage(false, null, null, null, AppResources.Trash, null, true);
true);
await Page.Navigation.PushAsync(page); await Page.Navigation.PushAsync(page);
} }
@@ -458,7 +425,7 @@ namespace Bit.App.Pages
private async Task LoadDataAsync() private async Task LoadDataAsync()
{ {
NoDataText = AppResources.NoItems; NoDataText = AppResources.NoItems;
_allCiphers = await GetAllCiphersAsync(); _allCiphers = await _cipherService.GetAllDecryptedAsync();
HasCiphers = _allCiphers.Any(); HasCiphers = _allCiphers.Any();
FavoriteCiphers?.Clear(); FavoriteCiphers?.Clear();
NoFolderCiphers?.Clear(); NoFolderCiphers?.Clear();
@@ -472,11 +439,12 @@ namespace Bit.App.Pages
if (MainPage) if (MainPage)
{ {
await FillFoldersAndCollectionsAsync(); Folders = await _folderService.GetAllDecryptedAsync();
NestedFolders = await _folderService.GetAllNestedAsync(Folders); NestedFolders = await _folderService.GetAllNestedAsync();
HasFolders = NestedFolders.Any(f => f.Node?.Id != null); HasFolders = NestedFolders.Any(f => f.Node?.Id != null);
NestedCollections = Collections != null ? await _collectionService.GetAllNestedAsync(Collections) : null; Collections = await _collectionService.GetAllDecryptedAsync();
HasCollections = NestedCollections?.Any() ?? false; NestedCollections = await _collectionService.GetAllNestedAsync(Collections);
HasCollections = NestedCollections.Any();
} }
else else
{ {
@@ -596,34 +564,6 @@ namespace Bit.App.Pages
} }
} }
private async Task FillFoldersAndCollectionsAsync()
{
var orgId = GetVaultFilterOrgId();
var decFolders = await _folderService.GetAllDecryptedAsync();
var decCollections = await _collectionService.GetAllDecryptedAsync();
if (IsVaultFilterMyVault)
{
Folders = BuildFolders(decFolders);
Collections = null;
}
else if (IsVaultFilterOrgVault && !string.IsNullOrWhiteSpace(orgId))
{
Folders = BuildFolders(decFolders);
Collections = decCollections?.Where(c => c.OrganizationId == orgId).ToList();
}
else
{
Folders = decFolders;
Collections = decCollections;
}
}
private List<FolderView> BuildFolders(List<FolderView> decFolders)
{
var folders = decFolders.Where(f => _allCiphers.Any(c => c.FolderId == f.Id)).ToList();
return folders.Any() ? folders : null;
}
private async void CipherOptionsAsync(CipherView cipher) private async void CipherOptionsAsync(CipherView cipher)
{ {
if ((Page as BaseContentPage).DoOnce()) if ((Page as BaseContentPage).DoOnce())

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