mirror of
https://github.com/bitwarden/mobile
synced 2026-02-25 17:03:19 +00:00
Compare commits
49 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9cc1a501a5 | ||
|
|
b0a8694801 | ||
|
|
1bbe8d8b98 | ||
|
|
cdc41d3bef | ||
|
|
769851adfe | ||
|
|
c988175e50 | ||
|
|
04539af2a6 | ||
|
|
e0efcfbe45 | ||
|
|
57213607a7 | ||
|
|
2cab62fda5 | ||
|
|
99828c7ead | ||
|
|
ab6dde4a11 | ||
|
|
80bd8ba9d1 | ||
|
|
35853a3815 | ||
|
|
cfbbea59e9 | ||
|
|
14d4b2ee29 | ||
|
|
b9c1ab7c1d | ||
|
|
4d96b091f7 | ||
|
|
b6ad3527db | ||
|
|
88fee155db | ||
|
|
f930028920 | ||
|
|
88f6b60b97 | ||
|
|
1f58b0cabe | ||
|
|
284d728023 | ||
|
|
0796bf17ce | ||
|
|
4bd06d2393 | ||
|
|
a3a508eb83 | ||
|
|
f10307c72d | ||
|
|
840925c479 | ||
|
|
fdcb2d76c9 | ||
|
|
4734fe4e43 | ||
|
|
383eee6ec7 | ||
|
|
1d9671bc5c | ||
|
|
22b00bcb33 | ||
|
|
c1748acf39 | ||
|
|
507c3faea1 | ||
|
|
020a5c072d | ||
|
|
c47aad0412 | ||
|
|
7c83d7b37b | ||
|
|
4d4e246a47 | ||
|
|
612e458071 | ||
|
|
a2ec263116 | ||
|
|
5ade10d1fe | ||
|
|
5008e1daa8 | ||
|
|
ad7c656868 | ||
|
|
bdd0ea007b | ||
|
|
bf33f23c12 | ||
|
|
c043528a16 | ||
|
|
fd74164f82 |
12
.config/dotnet-tools.json
Normal file
12
.config/dotnet-tools.json
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"isRoot": true,
|
||||||
|
"tools": {
|
||||||
|
"dotnet-format": {
|
||||||
|
"version": "5.1.250801",
|
||||||
|
"commands": [
|
||||||
|
"dotnet-format"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ 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
|
||||||
|
|||||||
2
.git-blame-ignore-revs
Normal file
2
.git-blame-ignore-revs
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
# .NET format https://github.com/bitwarden/mobile/pull/1738
|
||||||
|
04539af2a66668b6e85476d5cf318c9150ec4357
|
||||||
2
.gitattributes
vendored
2
.gitattributes
vendored
@@ -1,7 +1,7 @@
|
|||||||
###############################################################################
|
###############################################################################
|
||||||
# Set default behavior to automatically normalize line endings.
|
# Set default behavior to automatically normalize line endings.
|
||||||
###############################################################################
|
###############################################################################
|
||||||
* text=auto
|
* text=auto eol=lf
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
# Set default behavior for command prompt diff.
|
# Set default behavior for command prompt diff.
|
||||||
|
|||||||
93
.github/workflows/build.yml
vendored
93
.github/workflows/build.yml
vendored
@@ -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@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
|
uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846
|
||||||
|
|
||||||
- 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@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
|
uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846
|
||||||
|
|
||||||
- name: Check if special branches exist
|
- name: Check if special branches exist
|
||||||
id: branch-check
|
id: branch-check
|
||||||
@@ -47,7 +47,7 @@ jobs:
|
|||||||
echo "::set-output name=rc_branch_exists::0"
|
echo "::set-output name=rc_branch_exists::0"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ $(git ls-remote --heads origin hotfix) ]]; then
|
if [[ $(git ls-remote --heads origin hotfix-rc) ]]; then
|
||||||
echo "::set-output name=hotfix_branch_exists::1"
|
echo "::set-output name=hotfix_branch_exists::1"
|
||||||
else
|
else
|
||||||
echo "::set-output name=hotfix_branch_exists::0"
|
echo "::set-output name=hotfix_branch_exists::0"
|
||||||
@@ -61,7 +61,7 @@ jobs:
|
|||||||
needs: setup
|
needs: setup
|
||||||
steps:
|
steps:
|
||||||
- name: Set up MSBuild
|
- name: Set up MSBuild
|
||||||
uses: microsoft/setup-msbuild@c26a08ba26249b81327e26f6ef381897b6a8754d # v1
|
uses: microsoft/setup-msbuild@ab534842b4bdf384b8aaf93765dc6f721d9f5fab
|
||||||
|
|
||||||
- name: Print environment
|
- name: Print environment
|
||||||
run: |
|
run: |
|
||||||
@@ -72,7 +72,7 @@ jobs:
|
|||||||
echo "GitHub event: $GITHUB_EVENT"
|
echo "GitHub event: $GITHUB_EVENT"
|
||||||
|
|
||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
|
uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846
|
||||||
|
|
||||||
- name: Decrypt secrets
|
- name: Decrypt secrets
|
||||||
env:
|
env:
|
||||||
@@ -105,6 +105,14 @@ 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
|
||||||
|
|
||||||
@@ -167,14 +175,14 @@ jobs:
|
|||||||
shell: pwsh
|
shell: pwsh
|
||||||
|
|
||||||
- name: Upload Play Store .aab artifact
|
- name: Upload Play Store .aab artifact
|
||||||
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700 # v2.2.4
|
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
|
||||||
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@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700 # v2.2.4
|
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
|
||||||
with:
|
with:
|
||||||
name: com.x8bit.bitwarden.apk
|
name: com.x8bit.bitwarden.apk
|
||||||
path: ./com.x8bit.bitwarden.apk
|
path: ./com.x8bit.bitwarden.apk
|
||||||
@@ -186,7 +194,7 @@ jobs:
|
|||||||
&& 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'
|
|| github.ref == 'refs/heads/hotfix-rc'
|
||||||
run: |
|
run: |
|
||||||
PUBLISHER_PATH="$GITHUB_WORKSPACE/store/google/Publisher/bin/Release/netcoreapp3.1/Publisher.dll"
|
PUBLISHER_PATH="$GITHUB_WORKSPACE/store/google/Publisher/bin/Release/netcoreapp3.1/Publisher.dll"
|
||||||
CREDS_PATH="$HOME/secrets/play_creds.json"
|
CREDS_PATH="$HOME/secrets/play_creds.json"
|
||||||
@@ -202,7 +210,7 @@ jobs:
|
|||||||
runs-on: windows-2019
|
runs-on: windows-2019
|
||||||
steps:
|
steps:
|
||||||
- name: Set up MSBuild
|
- name: Set up MSBuild
|
||||||
uses: microsoft/setup-msbuild@c26a08ba26249b81327e26f6ef381897b6a8754d # v1
|
uses: microsoft/setup-msbuild@ab534842b4bdf384b8aaf93765dc6f721d9f5fab
|
||||||
|
|
||||||
- name: Print environment
|
- name: Print environment
|
||||||
run: |
|
run: |
|
||||||
@@ -213,7 +221,7 @@ jobs:
|
|||||||
echo "GitHub event: $GITHUB_EVENT"
|
echo "GitHub event: $GITHUB_EVENT"
|
||||||
|
|
||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
|
uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846
|
||||||
|
|
||||||
- name: Decrypt secrets
|
- name: Decrypt secrets
|
||||||
env:
|
env:
|
||||||
@@ -241,6 +249,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
$androidPath = $($env:GITHUB_WORKSPACE + "/src/Android/Android.csproj");
|
$androidPath = $($env:GITHUB_WORKSPACE + "/src/Android/Android.csproj");
|
||||||
$appPath = $($env:GITHUB_WORKSPACE + "/src/App/App.csproj");
|
$appPath = $($env:GITHUB_WORKSPACE + "/src/App/App.csproj");
|
||||||
|
$corePath = $($env:GITHUB_WORKSPACE + "/src/Core/Core.csproj");
|
||||||
|
|
||||||
$androidManifest = $($env:GITHUB_WORKSPACE + "/src/Android/Properties/AndroidManifest.xml");
|
$androidManifest = $($env:GITHUB_WORKSPACE + "/src/Android/Properties/AndroidManifest.xml");
|
||||||
|
|
||||||
@@ -306,6 +315,18 @@ jobs:
|
|||||||
$appCenterNode.ParentNode.RemoveChild($appCenterNode);
|
$appCenterNode.ParentNode.RemoveChild($appCenterNode);
|
||||||
|
|
||||||
$xml.Save($appPath);
|
$xml.Save($appPath);
|
||||||
|
|
||||||
|
Write-Output "########################################"
|
||||||
|
Write-Output "##### Uninstall from Core.csproj"
|
||||||
|
Write-Output "########################################"
|
||||||
|
|
||||||
|
$xml=New-Object XML;
|
||||||
|
$xml.Load($corePath);
|
||||||
|
|
||||||
|
$appCenterNode=$xml.SelectSingleNode("/Project/ItemGroup/PackageReference[@Include='Microsoft.AppCenter.Crashes']");
|
||||||
|
$appCenterNode.ParentNode.RemoveChild($appCenterNode);
|
||||||
|
|
||||||
|
$xml.Save($corePath);
|
||||||
shell: pwsh
|
shell: pwsh
|
||||||
|
|
||||||
- name: Restore packages
|
- name: Restore packages
|
||||||
@@ -347,7 +368,7 @@ jobs:
|
|||||||
shell: pwsh
|
shell: pwsh
|
||||||
|
|
||||||
- name: Upload F-Droid .apk artifact
|
- name: Upload F-Droid .apk artifact
|
||||||
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700 # v2.2.4
|
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
|
||||||
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
|
||||||
@@ -368,16 +389,16 @@ jobs:
|
|||||||
echo "GitHub event: $GITHUB_EVENT"
|
echo "GitHub event: $GITHUB_EVENT"
|
||||||
|
|
||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
|
uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846
|
||||||
|
|
||||||
- name: Login to Azure - Prod Subscription
|
- name: Login to Azure - Prod Subscription
|
||||||
uses: Azure/login@77f1b2e3fb80c0e8645114159d17008b8a2e475a
|
uses: Azure/login@1f63701bf3e6892515f1b7ce2d2bf1708b46beaf
|
||||||
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@80ccd3fafe5662407cc2e55f202ee34bfff8c403
|
uses: Azure/get-keyvault-secrets@b5c723b9ac7870c022b8c35befe620b7009b336f
|
||||||
with:
|
with:
|
||||||
keyvault: "bitwarden-prod-kv"
|
keyvault: "bitwarden-prod-kv"
|
||||||
secrets: "appcenter-ios-token"
|
secrets: "appcenter-ios-token"
|
||||||
@@ -399,7 +420,8 @@ 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 ./.github/secrets/dist_share_extension.mobileprovision.gpg
|
--output $HOME/secrets/dist_share_extension.mobileprovision \
|
||||||
|
./.github/secrets/dist_share_extension.mobileprovision.gpg
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
- name: Increment version
|
- name: Increment version
|
||||||
@@ -495,7 +517,7 @@ jobs:
|
|||||||
-exportOptionsPlist $EXPORT_OPTIONS_PATH
|
-exportOptionsPlist $EXPORT_OPTIONS_PATH
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
- name: Copy all dSYMs files to upload
|
- name: Copy all dSYMs files to upload
|
||||||
run: |
|
run: |
|
||||||
ARCHIVE_DSYMS_PATH="$HOME/Library/Developer/Xcode/Archives/*/*.xcarchive/dSYMs"
|
ARCHIVE_DSYMS_PATH="$HOME/Library/Developer/Xcode/Archives/*/*.xcarchive/dSYMs"
|
||||||
EXPORT_PATH="./bitwarden-export"
|
EXPORT_PATH="./bitwarden-export"
|
||||||
@@ -504,7 +526,7 @@ jobs:
|
|||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
- name: Upload App Store .ipa & dSYMs artifacts
|
- name: Upload App Store .ipa & dSYMs artifacts
|
||||||
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700 # v2.2.4
|
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
|
||||||
with:
|
with:
|
||||||
name: Bitwarden iOS
|
name: Bitwarden iOS
|
||||||
path: |
|
path: |
|
||||||
@@ -514,15 +536,12 @@ 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'
|
|| github.ref == 'refs/heads/hotfix-rc'
|
||||||
uses: actions/setup-node@v2
|
run: npm install -g appcenter-cli
|
||||||
with:
|
|
||||||
node-version: '14'
|
|
||||||
- run: npm install -g appcenter-cli
|
|
||||||
|
|
||||||
- name: Upload dSYMs to App Center
|
- name: Upload dSYMs to App Center
|
||||||
if: |
|
if: |
|
||||||
@@ -530,10 +549,10 @@ jobs:
|
|||||||
&& 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'
|
|| 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 kspearrin/bitwarden -s "./bitwarden-export/dSYMs" --token $APPCENTER_IOS_TOKEN
|
run: appcenter crashes upload-symbols -a bitwarden/bitwarden -s "./bitwarden-export/dSYMs" --token $APPCENTER_IOS_TOKEN
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
- name: Deploy to App Store
|
- name: Deploy to App Store
|
||||||
@@ -542,7 +561,7 @@ jobs:
|
|||||||
&& 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'
|
|| github.ref == 'refs/heads/hotfix-rc'
|
||||||
env:
|
env:
|
||||||
APPLE_ID_USERNAME: ${{ secrets.APPLE_ID_USERNAME }}
|
APPLE_ID_USERNAME: ${{ secrets.APPLE_ID_USERNAME }}
|
||||||
APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
|
APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
|
||||||
@@ -564,22 +583,22 @@ jobs:
|
|||||||
_CROWDIN_PROJECT_ID: "269690"
|
_CROWDIN_PROJECT_ID: "269690"
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
|
uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846
|
||||||
|
|
||||||
- name: Login to Azure
|
- name: Login to Azure
|
||||||
uses: Azure/login@77f1b2e3fb80c0e8645114159d17008b8a2e475a
|
uses: Azure/login@1f63701bf3e6892515f1b7ce2d2bf1708b46beaf
|
||||||
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@80ccd3fafe5662407cc2e55f202ee34bfff8c403
|
uses: Azure/get-keyvault-secrets@b5c723b9ac7870c022b8c35befe620b7009b336f
|
||||||
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@e39093fd75daae7859c68eded4b43d42ec78d8ea # v1.3.2
|
uses: crowdin/github-action@9237b4cb361788dfce63feb2e2f15c09e2fe7415
|
||||||
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 }}
|
||||||
@@ -605,7 +624,7 @@ jobs:
|
|||||||
if: |
|
if: |
|
||||||
(github.ref == 'refs/heads/master')
|
(github.ref == 'refs/heads/master')
|
||||||
|| (github.ref == 'refs/heads/rc')
|
|| (github.ref == 'refs/heads/rc')
|
||||||
|| (github.ref == 'refs/heads/hotfix')
|
|| (github.ref == 'refs/heads/hotfix-rc')
|
||||||
env:
|
env:
|
||||||
CLOC_STATUS: ${{ needs.cloc.result }}
|
CLOC_STATUS: ${{ needs.cloc.result }}
|
||||||
ANDROID_STATUS: ${{ needs.android.result }}
|
ANDROID_STATUS: ${{ needs.android.result }}
|
||||||
@@ -626,21 +645,21 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Login to Azure - Prod Subscription
|
- name: Login to Azure - Prod Subscription
|
||||||
uses: Azure/login@77f1b2e3fb80c0e8645114159d17008b8a2e475a
|
uses: Azure/login@1f63701bf3e6892515f1b7ce2d2bf1708b46beaf
|
||||||
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@80ccd3fafe5662407cc2e55f202ee34bfff8c403
|
uses: Azure/get-keyvault-secrets@b5c723b9ac7870c022b8c35befe620b7009b336f
|
||||||
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@e4e71685b9b239384b0f676a63c32367f59c2522 # v1.2.2
|
uses: act10ns/slack@da3191ebe2e67f49b46880b4633f5591a96d1d33
|
||||||
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 }}
|
||||||
|
|||||||
12
.github/workflows/release.yml
vendored
12
.github/workflows/release.yml
vendored
@@ -12,7 +12,7 @@ on:
|
|||||||
options:
|
options:
|
||||||
- Initial Release
|
- Initial Release
|
||||||
- Redeploy
|
- Redeploy
|
||||||
- dry-run
|
- Dry Run
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
release:
|
release:
|
||||||
@@ -22,11 +22,11 @@ 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" ]]; then
|
if [[ "$GITHUB_REF" != "refs/heads/rc" ]] && [[ "$GITHUB_REF" != "refs/heads/hotfix-rc" ]]; then
|
||||||
echo "==================================="
|
echo "==================================="
|
||||||
echo "[!] Can only release from the 'rc' or 'hotfix' branches"
|
echo "[!] Can only release from the 'rc' or 'hotfix-rc' branches"
|
||||||
echo "==================================="
|
echo "==================================="
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
@@ -75,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,
|
||||||
@@ -171,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
|
||||||
|
|||||||
@@ -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 [User-to-User Support category](https://community.bitwarden.com/c/support/) on the Community Forums
|
* **Help other users:** Go to the [Ask the Bitwarden Community 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/kspearrin).
|
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).
|
||||||
|
|
||||||
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/
|
||||||
|
|||||||
20
README.md
20
README.md
@@ -33,3 +33,23 @@ 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
|
||||||
|
```
|
||||||
|
|||||||
42
SECURITY.md
42
SECURITY.md
@@ -1,39 +1,11 @@
|
|||||||
Bitwarden believes that working with security researchers across the globe is crucial to keeping our
|
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!
|
||||||
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
|
- 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.
|
||||||
effort to quickly resolve the issue.
|
- 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.
|
||||||
- Provide us a reasonable amount of time to resolve the issue before any disclosure to the public or a
|
- 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.
|
||||||
third-party. We may publicly disclose the issue before resolving it, if appropriate.
|
- If you would like to encrypt your report, please use the PGP key with long ID `0xDE6887086F892325FEC04CC0D847525B6931381F` (available in the public keyserver pool).
|
||||||
- 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:
|
||||||
|
|
||||||
@@ -42,4 +14,8 @@ 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!
|
||||||
|
|||||||
@@ -46,6 +46,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "iOS.ShareExtension", "src\i
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "iOS.Autofill", "src\iOS.Autofill\iOS.Autofill.csproj", "{8A3ECD75-3EC8-4CB3-B3A2-A73A724C279A}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "iOS.Autofill", "src\iOS.Autofill\iOS.Autofill.csproj", "{8A3ECD75-3EC8-4CB3-B3A2-A73A724C279A}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UiTests", "src\UiTests\UiTests.csproj", "{23FB637B-1705-485F-9464-078FCAF361A8}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Ad-Hoc|Any CPU = Ad-Hoc|Any CPU
|
Ad-Hoc|Any CPU = Ad-Hoc|Any CPU
|
||||||
@@ -446,6 +448,36 @@ Global
|
|||||||
{8A3ECD75-3EC8-4CB3-B3A2-A73A724C279A}.Release|iPhone.Build.0 = Release|iPhone
|
{8A3ECD75-3EC8-4CB3-B3A2-A73A724C279A}.Release|iPhone.Build.0 = Release|iPhone
|
||||||
{8A3ECD75-3EC8-4CB3-B3A2-A73A724C279A}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator
|
{8A3ECD75-3EC8-4CB3-B3A2-A73A724C279A}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator
|
||||||
{8A3ECD75-3EC8-4CB3-B3A2-A73A724C279A}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator
|
{8A3ECD75-3EC8-4CB3-B3A2-A73A724C279A}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator
|
||||||
|
{23FB637B-1705-485F-9464-078FCAF361A8}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{23FB637B-1705-485F-9464-078FCAF361A8}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{23FB637B-1705-485F-9464-078FCAF361A8}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU
|
||||||
|
{23FB637B-1705-485F-9464-078FCAF361A8}.Ad-Hoc|iPhone.Build.0 = Release|Any CPU
|
||||||
|
{23FB637B-1705-485F-9464-078FCAF361A8}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
||||||
|
{23FB637B-1705-485F-9464-078FCAF361A8}.Ad-Hoc|iPhoneSimulator.Build.0 = Release|Any CPU
|
||||||
|
{23FB637B-1705-485F-9464-078FCAF361A8}.AppStore|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{23FB637B-1705-485F-9464-078FCAF361A8}.AppStore|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{23FB637B-1705-485F-9464-078FCAF361A8}.AppStore|iPhone.ActiveCfg = Release|Any CPU
|
||||||
|
{23FB637B-1705-485F-9464-078FCAF361A8}.AppStore|iPhone.Build.0 = Release|Any CPU
|
||||||
|
{23FB637B-1705-485F-9464-078FCAF361A8}.AppStore|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
||||||
|
{23FB637B-1705-485F-9464-078FCAF361A8}.AppStore|iPhoneSimulator.Build.0 = Release|Any CPU
|
||||||
|
{23FB637B-1705-485F-9464-078FCAF361A8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{23FB637B-1705-485F-9464-078FCAF361A8}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{23FB637B-1705-485F-9464-078FCAF361A8}.Debug|iPhone.ActiveCfg = Debug|Any CPU
|
||||||
|
{23FB637B-1705-485F-9464-078FCAF361A8}.Debug|iPhone.Build.0 = Debug|Any CPU
|
||||||
|
{23FB637B-1705-485F-9464-078FCAF361A8}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
||||||
|
{23FB637B-1705-485F-9464-078FCAF361A8}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||||
|
{23FB637B-1705-485F-9464-078FCAF361A8}.FDroid|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{23FB637B-1705-485F-9464-078FCAF361A8}.FDroid|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{23FB637B-1705-485F-9464-078FCAF361A8}.FDroid|iPhone.ActiveCfg = Release|Any CPU
|
||||||
|
{23FB637B-1705-485F-9464-078FCAF361A8}.FDroid|iPhone.Build.0 = Release|Any CPU
|
||||||
|
{23FB637B-1705-485F-9464-078FCAF361A8}.FDroid|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
||||||
|
{23FB637B-1705-485F-9464-078FCAF361A8}.FDroid|iPhoneSimulator.Build.0 = Release|Any CPU
|
||||||
|
{23FB637B-1705-485F-9464-078FCAF361A8}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{23FB637B-1705-485F-9464-078FCAF361A8}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{23FB637B-1705-485F-9464-078FCAF361A8}.Release|iPhone.ActiveCfg = Release|Any CPU
|
||||||
|
{23FB637B-1705-485F-9464-078FCAF361A8}.Release|iPhone.Build.0 = Release|Any CPU
|
||||||
|
{23FB637B-1705-485F-9464-078FCAF361A8}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
||||||
|
{23FB637B-1705-485F-9464-078FCAF361A8}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
@@ -464,6 +496,7 @@ Global
|
|||||||
{8AE548D9-A567-4E97-995E-93EC7DB0FDE0} = {8904C536-C67D-420F-9971-51B26574C3AA}
|
{8AE548D9-A567-4E97-995E-93EC7DB0FDE0} = {8904C536-C67D-420F-9971-51B26574C3AA}
|
||||||
{F8C3F648-EA5A-4719-8005-85D1690B1655} = {D10CA4A9-F866-40E1-B658-F69051236C71}
|
{F8C3F648-EA5A-4719-8005-85D1690B1655} = {D10CA4A9-F866-40E1-B658-F69051236C71}
|
||||||
{8A3ECD75-3EC8-4CB3-B3A2-A73A724C279A} = {D10CA4A9-F866-40E1-B658-F69051236C71}
|
{8A3ECD75-3EC8-4CB3-B3A2-A73A724C279A} = {D10CA4A9-F866-40E1-B658-F69051236C71}
|
||||||
|
{23FB637B-1705-485F-9464-078FCAF361A8} = {D10CA4A9-F866-40E1-B658-F69051236C71}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
SolutionGuid = {7D436EA3-8B7E-45D2-8D14-0730BD2E0410}
|
SolutionGuid = {7D436EA3-8B7E-45D2-8D14-0730BD2E0410}
|
||||||
|
|||||||
@@ -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.1</Version>
|
<Version>1.7.2</Version>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Xamarin.Firebase.Messaging">
|
<PackageReference Include="Xamarin.Firebase.Messaging">
|
||||||
<Version>122.0.0</Version>
|
<Version>122.0.0</Version>
|
||||||
|
|||||||
@@ -64,16 +64,22 @@ namespace Bit.Droid
|
|||||||
Intent?.Validate();
|
Intent?.Validate();
|
||||||
|
|
||||||
base.OnCreate(savedInstanceState);
|
base.OnCreate(savedInstanceState);
|
||||||
if (!CoreHelpers.InDebugMode())
|
//if (!CoreHelpers.InDebugMode())
|
||||||
{
|
//{
|
||||||
Window.AddFlags(Android.Views.WindowManagerFlags.Secure);
|
// Window.AddFlags(Android.Views.WindowManagerFlags.Secure);
|
||||||
}
|
//}
|
||||||
|
|
||||||
#if !DEBUG && !FDROID
|
#if !DEBUG && !FDROID
|
||||||
var appCenterHelper = new AppCenterHelper(_appIdService, _stateService);
|
var appCenterHelper = new AppCenterHelper(_appIdService, _stateService);
|
||||||
var appCenterTask = appCenterHelper.InitAsync();
|
var appCenterTask = appCenterHelper.InitAsync();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
var toplayout = Window?.DecorView?.RootView;
|
||||||
|
if (toplayout != null)
|
||||||
|
{
|
||||||
|
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();
|
||||||
|
|||||||
@@ -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="2.16.3" 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.18.1" android:installLocation="internalOnly" package="com.x8bit.bitwarden">
|
||||||
|
|
||||||
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="30"/>
|
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="30"/>
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
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 Java.Security;
|
using Java.Security;
|
||||||
using Javax.Crypto;
|
using Javax.Crypto;
|
||||||
|
#if !FDROID
|
||||||
|
using Microsoft.AppCenter.Crashes;
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace Bit.Droid.Services
|
namespace Bit.Droid.Services
|
||||||
{
|
{
|
||||||
@@ -43,23 +43,22 @@ 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)
|
||||||
@@ -75,6 +74,9 @@ 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
|
||||||
|
#if !FDROID
|
||||||
|
Crashes.TrackError(e);
|
||||||
|
#endif
|
||||||
CreateKey();
|
CreateKey();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,10 +97,13 @@ namespace Bit.Droid.Services
|
|||||||
keyGen.Init(keyGenSpec);
|
keyGen.Init(keyGenSpec);
|
||||||
keyGen.GenerateKey();
|
keyGen.GenerateKey();
|
||||||
}
|
}
|
||||||
catch
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
// 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
|
||||||
|
#if !FDROID
|
||||||
|
Crashes.TrackError(e);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -674,7 +674,7 @@ namespace Bit.Droid.Services
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
var data = new Intent();
|
var data = new Intent();
|
||||||
if (cipher == null)
|
if (cipher?.Login == null)
|
||||||
{
|
{
|
||||||
data.PutExtra("canceled", "true");
|
data.PutExtra("canceled", "true");
|
||||||
}
|
}
|
||||||
@@ -734,6 +734,11 @@ 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;
|
||||||
@@ -943,5 +948,10 @@ namespace Bit.Droid.Services
|
|||||||
var activity = CrossCurrentActivity.Current?.Activity as MainActivity;
|
var activity = CrossCurrentActivity.Current?.Activity as MainActivity;
|
||||||
return activity?.Resources?.Configuration?.FontScale ?? 1;
|
return activity?.Resources?.Configuration?.FontScale ?? 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task OnAccountSwitchCompleteAsync()
|
||||||
|
{
|
||||||
|
// for any Android-specific cleanup required after switching accounts
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
using Bit.Core.Enums;
|
using System.Threading.Tasks;
|
||||||
|
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,6 +35,7 @@ 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();
|
||||||
@@ -46,5 +47,6 @@ namespace Bit.App.Abstractions
|
|||||||
void CloseMainApp();
|
void CloseMainApp();
|
||||||
bool SupportsFido2();
|
bool SupportsFido2();
|
||||||
float GetSystemFontSizeScale();
|
float GetSystemFontSizeScale();
|
||||||
|
Task OnAccountSwitchCompleteAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ namespace Bit.App.Abstractions
|
|||||||
string[] ProtectedFields { get; }
|
string[] ProtectedFields { get; }
|
||||||
|
|
||||||
Task<bool> ShowPasswordPromptAsync();
|
Task<bool> ShowPasswordPromptAsync();
|
||||||
|
|
||||||
Task<(string password, bool valid)> ShowPasswordPromptAndGetItAsync();
|
Task<(string password, bool valid)> ShowPasswordPromptAndGetItAsync();
|
||||||
|
|
||||||
Task<bool> Enabled();
|
Task<bool> Enabled();
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
using Newtonsoft.Json.Linq;
|
using System.Threading.Tasks;
|
||||||
using System.Threading.Tasks;
|
using Newtonsoft.Json.Linq;
|
||||||
|
|
||||||
namespace Bit.App.Abstractions
|
namespace Bit.App.Abstractions
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -16,10 +16,10 @@
|
|||||||
<PackageReference Include="Microsoft.AppCenter.Crashes" Version="4.4.0" />
|
<PackageReference Include="Microsoft.AppCenter.Crashes" Version="4.4.0" />
|
||||||
<PackageReference Include="Plugin.Fingerprint" Version="2.1.4" />
|
<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.0" />
|
<PackageReference Include="Xamarin.CommunityToolkit" Version="2.0.1" />
|
||||||
<PackageReference Include="Xamarin.Essentials" Version="1.7.1" />
|
<PackageReference Include="Xamarin.Essentials" Version="1.7.2" />
|
||||||
<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.2337" />
|
<PackageReference Include="Xamarin.Forms" Version="5.0.0.2401" />
|
||||||
<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>
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
using Bit.App.Abstractions;
|
using System;
|
||||||
|
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.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;
|
||||||
|
|
||||||
@@ -73,8 +73,9 @@ namespace Bit.App
|
|||||||
}
|
}
|
||||||
else if (message.Command == "locked")
|
else if (message.Command == "locked")
|
||||||
{
|
{
|
||||||
var (userId, userInitiated) =
|
var extras = message.Data as Tuple<string, bool>;
|
||||||
message.Data as Tuple<string, bool> ?? new Tuple<string, bool>(null, false);
|
var userId = extras?.Item1;
|
||||||
|
var userInitiated = extras?.Item2 ?? false;
|
||||||
Device.BeginInvokeOnMainThread(async () => await LockedAsync(userId, userInitiated));
|
Device.BeginInvokeOnMainThread(async () => await LockedAsync(userId, userInitiated));
|
||||||
}
|
}
|
||||||
else if (message.Command == "lockVault")
|
else if (message.Command == "lockVault")
|
||||||
@@ -83,8 +84,10 @@ namespace Bit.App
|
|||||||
}
|
}
|
||||||
else if (message.Command == "logout")
|
else if (message.Command == "logout")
|
||||||
{
|
{
|
||||||
var (userId, userInitiated, expired) =
|
var extras = message.Data as Tuple<string, bool, bool>;
|
||||||
message.Data as Tuple<string, bool, bool> ?? new Tuple<string, bool, bool>(null, true, false);
|
var userId = extras?.Item1;
|
||||||
|
var userInitiated = extras?.Item2 ?? true;
|
||||||
|
var expired = extras?.Item3 ?? false;
|
||||||
Device.BeginInvokeOnMainThread(async () => await LogOutAsync(userId, userInitiated, expired));
|
Device.BeginInvokeOnMainThread(async () => await LogOutAsync(userId, userInitiated, expired));
|
||||||
}
|
}
|
||||||
else if (message.Command == "loggedOut")
|
else if (message.Command == "loggedOut")
|
||||||
@@ -205,7 +208,10 @@ namespace Bit.App
|
|||||||
{
|
{
|
||||||
await _stateService.SetLastActiveTimeAsync(_deviceActionService.GetActiveTime());
|
await _stateService.SetLastActiveTimeAsync(_deviceActionService.GetActiveTime());
|
||||||
}
|
}
|
||||||
SetTabsPageFromAutofill(isLocked);
|
if (!SetTabsPageFromAutofill(isLocked))
|
||||||
|
{
|
||||||
|
ClearAutofillUri();
|
||||||
|
}
|
||||||
await SleptAsync();
|
await SleptAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -312,7 +318,7 @@ namespace Bit.App
|
|||||||
Options.HideAccountSwitcher = await _stateService.GetActiveUserIdAsync() == null;
|
Options.HideAccountSwitcher = await _stateService.GetActiveUserIdAsync() == null;
|
||||||
Current.MainPage = new NavigationPage(new LoginPage(email, Options));
|
Current.MainPage = new NavigationPage(new LoginPage(email, Options));
|
||||||
}
|
}
|
||||||
else if (await _vaultTimeoutService.IsLockedAsync() ||
|
else if (await _vaultTimeoutService.IsLockedAsync() ||
|
||||||
await _vaultTimeoutService.ShouldLockAsync())
|
await _vaultTimeoutService.ShouldLockAsync())
|
||||||
{
|
{
|
||||||
Current.MainPage = new NavigationPage(new LockPage(Options));
|
Current.MainPage = new NavigationPage(new LockPage(Options));
|
||||||
@@ -362,7 +368,15 @@ namespace Bit.App
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SetTabsPageFromAutofill(bool isLocked)
|
private void ClearAutofillUri()
|
||||||
|
{
|
||||||
|
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)
|
||||||
@@ -382,7 +396,9 @@ namespace Bit.App
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Prime()
|
private void Prime()
|
||||||
@@ -425,7 +441,7 @@ namespace Bit.App
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task LockedAsync(string userId, bool autoPromptBiometric)
|
private async Task LockedAsync(string userId, bool userInitiated)
|
||||||
{
|
{
|
||||||
if (!await _stateService.IsActiveAccountAsync(userId))
|
if (!await _stateService.IsActiveAccountAsync(userId))
|
||||||
{
|
{
|
||||||
@@ -433,6 +449,7 @@ namespace Bit.App
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var autoPromptBiometric = !userInitiated;
|
||||||
if (autoPromptBiometric && Device.RuntimePlatform == Device.iOS)
|
if (autoPromptBiometric && Device.RuntimePlatform == Device.iOS)
|
||||||
{
|
{
|
||||||
var vaultTimeout = await _stateService.GetVaultTimeoutAsync();
|
var vaultTimeout = await _stateService.GetVaultTimeoutAsync();
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ namespace Bit.App.Controls
|
|||||||
Opacity = 0;
|
Opacity = 0;
|
||||||
IsVisible = true;
|
IsVisible = true;
|
||||||
this.FadeTo(1, 100);
|
this.FadeTo(1, 100);
|
||||||
|
|
||||||
if (Device.RuntimePlatform == Device.Android && MainFab != null)
|
if (Device.RuntimePlatform == Device.Android && MainFab != null)
|
||||||
{
|
{
|
||||||
// start fab fade-out
|
// start fab fade-out
|
||||||
|
|||||||
@@ -22,11 +22,11 @@ namespace Bit.App.Controls
|
|||||||
{
|
{
|
||||||
_stateService = stateService;
|
_stateService = stateService;
|
||||||
_messagingService = messagingService;
|
_messagingService = messagingService;
|
||||||
|
|
||||||
SelectAccountCommand = new AsyncCommand<AccountViewCellViewModel>(SelectAccountAsync,
|
SelectAccountCommand = new AsyncCommand<AccountViewCellViewModel>(SelectAccountAsync,
|
||||||
onException: ex => logger.Exception(ex),
|
onException: ex => logger.Exception(ex),
|
||||||
allowsMultipleExecutions: false);
|
allowsMultipleExecutions: false);
|
||||||
|
|
||||||
LongPressAccountCommand = new AsyncCommand<Tuple<ContentPage, AccountViewCellViewModel>>(LongPressAccountAsync,
|
LongPressAccountCommand = new AsyncCommand<Tuple<ContentPage, AccountViewCellViewModel>>(LongPressAccountAsync,
|
||||||
onException: ex => logger.Exception(ex),
|
onException: ex => logger.Exception(ex),
|
||||||
allowsMultipleExecutions: false);
|
allowsMultipleExecutions: false);
|
||||||
|
|||||||
@@ -59,26 +59,26 @@
|
|||||||
Grid.Row="0"
|
Grid.Row="0"
|
||||||
Text="{Binding AccountView.Email}"
|
Text="{Binding AccountView.Email}"
|
||||||
IsVisible="{Binding IsActive}"
|
IsVisible="{Binding IsActive}"
|
||||||
StyleClass="list-title"
|
StyleClass="accountlist-title, accountlist-title-platform"
|
||||||
LineBreakMode="TailTruncation" />
|
LineBreakMode="TailTruncation" />
|
||||||
<Label
|
<Label
|
||||||
Grid.Row="0"
|
Grid.Row="0"
|
||||||
Text="{Binding AccountView.Email}"
|
Text="{Binding AccountView.Email}"
|
||||||
IsVisible="{Binding IsActive, Converter={StaticResource inverseBool}}"
|
IsVisible="{Binding IsActive, Converter={StaticResource inverseBool}}"
|
||||||
StyleClass="list-title"
|
StyleClass="accountlist-title, accountlist-title-platform"
|
||||||
TextColor="{DynamicResource MutedColor}"
|
TextColor="{DynamicResource MutedColor}"
|
||||||
LineBreakMode="TailTruncation" />
|
LineBreakMode="TailTruncation" />
|
||||||
<Label
|
<Label
|
||||||
Grid.Row="1"
|
Grid.Row="1"
|
||||||
IsVisible="{Binding ShowHostname}"
|
IsVisible="{Binding ShowHostname}"
|
||||||
Text="{Binding AccountView.Hostname}"
|
Text="{Binding AccountView.Hostname}"
|
||||||
StyleClass="list-sub"
|
StyleClass="accountlist-sub, accountlist-sub-platform"
|
||||||
LineBreakMode="TailTruncation" />
|
LineBreakMode="TailTruncation" />
|
||||||
<Label
|
<Label
|
||||||
Grid.Row="2"
|
Grid.Row="2"
|
||||||
Text="{u:I18n AccountUnlocked}"
|
Text="{u:I18n AccountUnlocked}"
|
||||||
IsVisible="{Binding IsUnlockedAndNotActive}"
|
IsVisible="{Binding IsUnlockedAndNotActive}"
|
||||||
StyleClass="list-sub"
|
StyleClass="accountlist-sub, accountlist-sub-platform"
|
||||||
FontAttributes="Italic"
|
FontAttributes="Italic"
|
||||||
TextTransform="Lowercase"
|
TextTransform="Lowercase"
|
||||||
LineBreakMode="TailTruncation" />
|
LineBreakMode="TailTruncation" />
|
||||||
@@ -86,7 +86,7 @@
|
|||||||
Grid.Row="2"
|
Grid.Row="2"
|
||||||
Text="{u:I18n AccountLocked}"
|
Text="{u:I18n AccountLocked}"
|
||||||
IsVisible="{Binding IsLockedAndNotActive}"
|
IsVisible="{Binding IsLockedAndNotActive}"
|
||||||
StyleClass="list-sub"
|
StyleClass="accountlist-sub, accountlist-sub-platform"
|
||||||
FontAttributes="Italic"
|
FontAttributes="Italic"
|
||||||
TextTransform="Lowercase"
|
TextTransform="Lowercase"
|
||||||
LineBreakMode="TailTruncation" />
|
LineBreakMode="TailTruncation" />
|
||||||
@@ -94,7 +94,7 @@
|
|||||||
Grid.Row="2"
|
Grid.Row="2"
|
||||||
Text="{u:I18n AccountLoggedOut}"
|
Text="{u:I18n AccountLoggedOut}"
|
||||||
IsVisible="{Binding IsLoggedOutAndNotActive}"
|
IsVisible="{Binding IsLoggedOutAndNotActive}"
|
||||||
StyleClass="list-sub"
|
StyleClass="accountlist-sub, accountlist-sub-platform"
|
||||||
FontAttributes="Italic"
|
FontAttributes="Italic"
|
||||||
TextTransform="Lowercase"
|
TextTransform="Lowercase"
|
||||||
LineBreakMode="TailTruncation" />
|
LineBreakMode="TailTruncation" />
|
||||||
@@ -144,7 +144,7 @@
|
|||||||
AutomationProperties.IsInAccessibleTree="False" />
|
AutomationProperties.IsInAccessibleTree="False" />
|
||||||
<Label
|
<Label
|
||||||
Text="{u:I18n AddAccount}"
|
Text="{u:I18n AddAccount}"
|
||||||
StyleClass="list-title"
|
StyleClass="accountlist-title, accountlist-title-platform"
|
||||||
LineBreakMode="TailTruncation"
|
LineBreakMode="TailTruncation"
|
||||||
VerticalOptions="Center"
|
VerticalOptions="Center"
|
||||||
Grid.Column="1" />
|
Grid.Column="1" />
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -62,6 +62,10 @@ namespace Bit.App.Controls
|
|||||||
upperData = _data.ToUpper();
|
upperData = _data.ToUpper();
|
||||||
chars = GetFirstLetters(upperData, 2);
|
chars = GetFirstLetters(upperData, 2);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
chars = upperData = _data.ToUpper();
|
||||||
|
}
|
||||||
|
|
||||||
var bgColor = StringToColor(upperData);
|
var bgColor = StringToColor(upperData);
|
||||||
var textColor = Color.White;
|
var textColor = Color.White;
|
||||||
@@ -108,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;
|
||||||
}
|
}
|
||||||
@@ -122,7 +126,7 @@ namespace Bit.App.Controls
|
|||||||
{
|
{
|
||||||
return data.Substring(0, 2);
|
return data.Substring(0, 2);
|
||||||
}
|
}
|
||||||
return null;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Color StringToColor(string str)
|
private Color StringToColor(string str)
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ namespace Bit.App.Controls
|
|||||||
{
|
{
|
||||||
public static readonly BindableProperty StepperBackgroundColorProperty = BindableProperty.Create(
|
public static readonly BindableProperty StepperBackgroundColorProperty = BindableProperty.Create(
|
||||||
nameof(StepperBackgroundColor), typeof(Color), typeof(ExtendedStepper), Color.Default);
|
nameof(StepperBackgroundColor), typeof(Color), typeof(ExtendedStepper), Color.Default);
|
||||||
|
|
||||||
public static readonly BindableProperty StepperForegroundColorProperty = BindableProperty.Create(
|
public static readonly BindableProperty StepperForegroundColorProperty = BindableProperty.Create(
|
||||||
nameof(StepperForegroundColor), typeof(Color), typeof(ExtendedStepper), Color.Default);
|
nameof(StepperForegroundColor), typeof(Color), typeof(ExtendedStepper), Color.Default);
|
||||||
|
|
||||||
@@ -15,7 +15,7 @@ namespace Bit.App.Controls
|
|||||||
get => (Color)GetValue(StepperBackgroundColorProperty);
|
get => (Color)GetValue(StepperBackgroundColorProperty);
|
||||||
set => SetValue(StepperBackgroundColorProperty, value);
|
set => SetValue(StepperBackgroundColorProperty, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Color StepperForegroundColor
|
public Color StepperForegroundColor
|
||||||
{
|
{
|
||||||
get => (Color)GetValue(StepperForegroundColorProperty);
|
get => (Color)GetValue(StepperForegroundColorProperty);
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -13,7 +13,7 @@ namespace Bit.App.Controls
|
|||||||
|
|
||||||
public static readonly BindableProperty ButtonCommandProperty = BindableProperty.Create(
|
public static readonly BindableProperty ButtonCommandProperty = BindableProperty.Create(
|
||||||
nameof(ButtonCommand), typeof(Command<SendView>), typeof(SendViewCell));
|
nameof(ButtonCommand), typeof(Command<SendView>), typeof(SendViewCell));
|
||||||
|
|
||||||
public static readonly BindableProperty ShowOptionsProperty = BindableProperty.Create(
|
public static readonly BindableProperty ShowOptionsProperty = BindableProperty.Create(
|
||||||
nameof(ShowOptions), typeof(bool), typeof(SendViewCell), true, BindingMode.OneWay);
|
nameof(ShowOptions), typeof(bool), typeof(SendViewCell), true, BindingMode.OneWay);
|
||||||
|
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ namespace Bit.App.Effects
|
|||||||
{
|
{
|
||||||
public class FabShadowEffect : RoutingEffect
|
public class FabShadowEffect : RoutingEffect
|
||||||
{
|
{
|
||||||
public FabShadowEffect()
|
public FabShadowEffect()
|
||||||
: base("Bitwarden.FabShadowEffect")
|
: base("Bitwarden.FabShadowEffect")
|
||||||
{ }
|
{ }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
using Bit.App.Resources;
|
using System.Collections.Generic;
|
||||||
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
|
||||||
@@ -66,7 +66,7 @@ namespace Bit.App.Pages
|
|||||||
get => _policy;
|
get => _policy;
|
||||||
set => SetProperty(ref _policy, value);
|
set => SetProperty(ref _policy, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public string ShowPasswordIcon => ShowPassword ? "" : "";
|
public string ShowPasswordIcon => ShowPassword ? "" : "";
|
||||||
public string MasterPassword { get; set; }
|
public string MasterPassword { get; set; }
|
||||||
public string ConfirmMasterPassword { get; set; }
|
public string ConfirmMasterPassword { get; set; }
|
||||||
@@ -84,7 +84,7 @@ namespace Bit.App.Pages
|
|||||||
await CheckPasswordPolicy();
|
await CheckPasswordPolicy();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task CheckPasswordPolicy()
|
private async Task CheckPasswordPolicy()
|
||||||
{
|
{
|
||||||
Policy = await _policyService.GetMasterPasswordPolicyOptions();
|
Policy = await _policyService.GetMasterPasswordPolicyOptions();
|
||||||
@@ -166,7 +166,7 @@ namespace Bit.App.Pages
|
|||||||
AppResources.AnErrorHasOccurred, AppResources.Ok);
|
AppResources.AnErrorHasOccurred, AppResources.Ok);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
<ContentPage.ToolbarItems>
|
<ContentPage.ToolbarItems>
|
||||||
<ToolbarItem Text="{u:I18n Cancel}" Clicked="Close_Clicked" Order="Primary" Priority="-1" />
|
<ToolbarItem Text="{u:I18n Cancel}" Clicked="Close_Clicked" Order="Primary" Priority="-1" />
|
||||||
<ToolbarItem Text="{u:I18n Save}" Clicked="Submit_Clicked" />
|
<ToolbarItem Text="{u:I18n Save}" Clicked="Submit_Clicked" AutomationId="save_button"/>
|
||||||
</ContentPage.ToolbarItems>
|
</ContentPage.ToolbarItems>
|
||||||
|
|
||||||
<ScrollView>
|
<ScrollView>
|
||||||
@@ -34,7 +34,8 @@
|
|||||||
Placeholder="ex. https://bitwarden.company.com"
|
Placeholder="ex. https://bitwarden.company.com"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
ReturnType="Go"
|
ReturnType="Go"
|
||||||
ReturnCommand="{Binding SubmitCommand}" />
|
ReturnCommand="{Binding SubmitCommand}"
|
||||||
|
AutomationId="server_input"/>
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<Label
|
<Label
|
||||||
Text="{u:I18n SelfHostedEnvironmentFooter}"
|
Text="{u:I18n SelfHostedEnvironmentFooter}"
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
using Bit.Core.Abstractions;
|
using System;
|
||||||
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
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
using Bit.App.Resources;
|
using System;
|
||||||
|
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
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
using Bit.App.Abstractions;
|
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.Utilities;
|
using Bit.Core.Utilities;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Xamarin.Forms;
|
using Xamarin.Forms;
|
||||||
|
|
||||||
namespace Bit.App.Pages
|
namespace Bit.App.Pages
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
Priority="-1"
|
Priority="-1"
|
||||||
UseOriginalImage="True"
|
UseOriginalImage="True"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n Account}" />
|
AutomationProperties.Name="{u:I18n Account}"/>
|
||||||
<ToolbarItem
|
<ToolbarItem
|
||||||
Icon="cog_environment.png" Clicked="Environment_Clicked" Order="Primary"
|
Icon="cog_environment.png" Clicked="Environment_Clicked" Order="Primary"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
@@ -37,15 +37,18 @@
|
|||||||
<Image
|
<Image
|
||||||
x:Name="_logo"
|
x:Name="_logo"
|
||||||
Source="logo.png"
|
Source="logo.png"
|
||||||
VerticalOptions="Center" />
|
VerticalOptions="Center"
|
||||||
|
AutomationId="logo_image"
|
||||||
|
/>
|
||||||
<Label Text="{u:I18n LoginOrCreateNewAccount}"
|
<Label Text="{u:I18n LoginOrCreateNewAccount}"
|
||||||
StyleClass="text-lg"
|
StyleClass="text-lg"
|
||||||
HorizontalTextAlignment="Center">
|
HorizontalTextAlignment="Center"/>
|
||||||
</Label>
|
|
||||||
<StackLayout Spacing="5">
|
<StackLayout Spacing="5">
|
||||||
<Button Text="{u:I18n LogIn}"
|
<Button Text="{u:I18n LogIn}"
|
||||||
StyleClass="btn-primary"
|
StyleClass="btn-primary"
|
||||||
Clicked="LogIn_Clicked" />
|
Clicked="LogIn_Clicked"
|
||||||
|
AutomationId="homepage_login_button"/>
|
||||||
<Button Text="{u:I18n CreateAccount}"
|
<Button Text="{u:I18n CreateAccount}"
|
||||||
Clicked="Register_Clicked" />
|
Clicked="Register_Clicked" />
|
||||||
<Button Text="{u:I18n LogInSso}"
|
<Button Text="{u:I18n LogInSso}"
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
_logo.Source = !ThemeManager.UsingLightTheme ? "logo_white.png" : "logo.png";
|
_logo.Source = !ThemeManager.UsingLightTheme ? "logo_white.png" : "logo.png";
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Cancel_Clicked(object sender, EventArgs e)
|
private void Cancel_Clicked(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
if (DoOnce())
|
if (DoOnce())
|
||||||
@@ -117,7 +117,7 @@ namespace Bit.App.Pages
|
|||||||
_vm.StartRegisterAction();
|
_vm.StartRegisterAction();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task StartRegisterAsync()
|
private async Task StartRegisterAsync()
|
||||||
{
|
{
|
||||||
var page = new RegisterPage(this);
|
var page = new RegisterPage(this);
|
||||||
@@ -145,7 +145,7 @@ namespace Bit.App.Pages
|
|||||||
_vm.StartEnvironmentAction();
|
_vm.StartEnvironmentAction();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task StartEnvironmentAsync()
|
private async Task StartEnvironmentAsync()
|
||||||
{
|
{
|
||||||
await _accountListOverlay.HideAsync();
|
await _accountListOverlay.HideAsync();
|
||||||
|
|||||||
@@ -272,7 +272,7 @@ namespace Bit.App.Pages
|
|||||||
var key = await _cryptoService.MakeKeyAsync(MasterPassword, _email, kdf, kdfIterations);
|
var key = await _cryptoService.MakeKeyAsync(MasterPassword, _email, kdf, kdfIterations);
|
||||||
var storedKeyHash = await _cryptoService.GetKeyHashAsync();
|
var storedKeyHash = await _cryptoService.GetKeyHashAsync();
|
||||||
var passwordValid = false;
|
var passwordValid = false;
|
||||||
|
|
||||||
if (storedKeyHash != null)
|
if (storedKeyHash != null)
|
||||||
{
|
{
|
||||||
passwordValid = await _cryptoService.CompareAndUpdateKeyHashAsync(MasterPassword, key);
|
passwordValid = await _cryptoService.CompareAndUpdateKeyHashAsync(MasterPassword, key);
|
||||||
|
|||||||
@@ -56,7 +56,8 @@
|
|||||||
x:Name="_email"
|
x:Name="_email"
|
||||||
Text="{Binding Email}"
|
Text="{Binding Email}"
|
||||||
Keyboard="Email"
|
Keyboard="Email"
|
||||||
StyleClass="box-value">
|
StyleClass="box-value"
|
||||||
|
AutomationId="email_input">
|
||||||
<VisualStateManager.VisualStateGroups>
|
<VisualStateManager.VisualStateGroups>
|
||||||
<VisualStateGroup x:Name="CommonStates">
|
<VisualStateGroup x:Name="CommonStates">
|
||||||
<VisualState x:Name="Disabled">
|
<VisualState x:Name="Disabled">
|
||||||
@@ -92,7 +93,8 @@
|
|||||||
Grid.Row="1"
|
Grid.Row="1"
|
||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
ReturnType="Go"
|
ReturnType="Go"
|
||||||
ReturnCommand="{Binding LogInCommand}" />
|
ReturnCommand="{Binding LogInCommand}"
|
||||||
|
AutomationId="password_input"/>
|
||||||
<controls:IconButton
|
<controls:IconButton
|
||||||
StyleClass="box-row-button, box-row-button-platform"
|
StyleClass="box-row-button, box-row-button-platform"
|
||||||
Text="{Binding ShowPasswordIcon}"
|
Text="{Binding ShowPasswordIcon}"
|
||||||
@@ -107,10 +109,12 @@
|
|||||||
<StackLayout Padding="10, 0">
|
<StackLayout Padding="10, 0">
|
||||||
<Button Text="{u:I18n LogIn}"
|
<Button Text="{u:I18n LogIn}"
|
||||||
StyleClass="btn-primary"
|
StyleClass="btn-primary"
|
||||||
Clicked="LogIn_Clicked" />
|
Clicked="LogIn_Clicked"
|
||||||
|
AutomationId="loginpage_login_button"/>
|
||||||
<Button Text="{u:I18n Cancel}"
|
<Button Text="{u:I18n Cancel}"
|
||||||
IsVisible="{Binding ShowCancelButton}"
|
IsVisible="{Binding ShowCancelButton}"
|
||||||
Clicked="Cancel_Clicked" />
|
Clicked="Cancel_Clicked"
|
||||||
|
AutomationId="cancel_button"/>
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
|
|||||||
@@ -177,7 +177,7 @@ namespace Bit.App.Pages
|
|||||||
var previousPage = await AppHelpers.ClearPreviousPage();
|
var previousPage = await AppHelpers.ClearPreviousPage();
|
||||||
Application.Current.MainPage = new TabsPage(_appOptions, previousPage);
|
Application.Current.MainPage = new TabsPage(_appOptions, previousPage);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task UpdateTempPasswordAsync()
|
private async Task UpdateTempPasswordAsync()
|
||||||
{
|
{
|
||||||
var page = new UpdateTempPasswordPage();
|
var page = new UpdateTempPasswordPage();
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ namespace Bit.App.Pages
|
|||||||
get => _showCancelButton;
|
get => _showCancelButton;
|
||||||
set => SetProperty(ref _showCancelButton, value);
|
set => SetProperty(ref _showCancelButton, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Email
|
public string Email
|
||||||
{
|
{
|
||||||
get => _email;
|
get => _email;
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
using Bit.App.Models;
|
using System;
|
||||||
|
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
|
||||||
@@ -99,7 +99,7 @@ namespace Bit.App.Pages
|
|||||||
var page = new SetPasswordPage(_appOptions, _vm.OrgIdentifier);
|
var page = new SetPasswordPage(_appOptions, _vm.OrgIdentifier);
|
||||||
await Navigation.PushModalAsync(new NavigationPage(page));
|
await Navigation.PushModalAsync(new NavigationPage(page));
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task UpdateTempPasswordAsync()
|
private async Task UpdateTempPasswordAsync()
|
||||||
{
|
{
|
||||||
var page = new UpdateTempPasswordPage();
|
var page = new UpdateTempPasswordPage();
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
using Bit.App.Abstractions;
|
using System;
|
||||||
using Bit.App.Resources;
|
|
||||||
using Bit.Core.Abstractions;
|
|
||||||
using Bit.Core.Utilities;
|
|
||||||
using System;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Bit.App.Abstractions;
|
||||||
|
using Bit.App.Resources;
|
||||||
using Bit.App.Utilities;
|
using Bit.App.Utilities;
|
||||||
|
using Bit.Core.Abstractions;
|
||||||
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;
|
||||||
|
|
||||||
@@ -170,7 +170,7 @@ namespace Bit.App.Pages
|
|||||||
else if (response.ResetMasterPassword)
|
else if (response.ResetMasterPassword)
|
||||||
{
|
{
|
||||||
StartSetPasswordAction?.Invoke();
|
StartSetPasswordAction?.Invoke();
|
||||||
}
|
}
|
||||||
else if (response.ForcePasswordReset)
|
else if (response.ForcePasswordReset)
|
||||||
{
|
{
|
||||||
UpdateTempPasswordAction?.Invoke();
|
UpdateTempPasswordAction?.Invoke();
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ namespace Bit.App.Pages
|
|||||||
await _vm.SubmitAsync();
|
await _vm.SubmitAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task RegistrationSuccessAsync(HomePage homePage)
|
private async Task RegistrationSuccessAsync(HomePage homePage)
|
||||||
{
|
{
|
||||||
if (homePage != null)
|
if (homePage != null)
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
using Bit.App.Abstractions;
|
using System;
|
||||||
|
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
|
||||||
@@ -39,7 +39,7 @@ namespace Bit.App.Pages
|
|||||||
SubmitCommand = new Command(async () => await SubmitAsync());
|
SubmitCommand = new Command(async () => await SubmitAsync());
|
||||||
ShowTerms = !_platformUtilsService.IsSelfHost();
|
ShowTerms = !_platformUtilsService.IsSelfHost();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ICommand PoliciesClickCommand => new Command<string>((url) =>
|
public ICommand PoliciesClickCommand => new Command<string>((url) =>
|
||||||
{
|
{
|
||||||
_platformUtilsService.LaunchUri(url);
|
_platformUtilsService.LaunchUri(url);
|
||||||
@@ -54,20 +54,20 @@ namespace Bit.App.Pages
|
|||||||
nameof(ShowPasswordIcon)
|
nameof(ShowPasswordIcon)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool AcceptPolicies
|
public bool AcceptPolicies
|
||||||
{
|
{
|
||||||
get => _acceptPolicies;
|
get => _acceptPolicies;
|
||||||
set => SetProperty(ref _acceptPolicies, value);
|
set => SetProperty(ref _acceptPolicies, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Thickness SwitchMargin
|
public Thickness SwitchMargin
|
||||||
{
|
{
|
||||||
get => Device.RuntimePlatform == Device.Android
|
get => Device.RuntimePlatform == Device.Android
|
||||||
? new Thickness(0, 0, 0, 0)
|
? new Thickness(0, 0, 0, 0)
|
||||||
: new Thickness(0, 0, 10, 0);
|
: new Thickness(0, 0, 10, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool ShowTerms { get; set; }
|
public bool ShowTerms { get; set; }
|
||||||
public Command SubmitCommand { get; }
|
public Command SubmitCommand { get; }
|
||||||
public Command TogglePasswordCommand { get; }
|
public Command TogglePasswordCommand { get; }
|
||||||
@@ -136,7 +136,7 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Password strength check?
|
// TODO: Password strength check?
|
||||||
|
|
||||||
if (showLoading)
|
if (showLoading)
|
||||||
{
|
{
|
||||||
await _deviceActionService.ShowLoadingAsync(AppResources.CreatingAccount);
|
await _deviceActionService.ShowLoadingAsync(AppResources.CreatingAccount);
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
using Bit.App.Abstractions;
|
using System;
|
||||||
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;
|
||||||
|
|
||||||
@@ -63,7 +63,7 @@ namespace Bit.App.Pages
|
|||||||
get => _isPolicyInEffect;
|
get => _isPolicyInEffect;
|
||||||
set => SetProperty(ref _isPolicyInEffect, value);
|
set => SetProperty(ref _isPolicyInEffect, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool ResetPasswordAutoEnroll
|
public bool ResetPasswordAutoEnroll
|
||||||
{
|
{
|
||||||
get => _resetPasswordAutoEnroll;
|
get => _resetPasswordAutoEnroll;
|
||||||
@@ -220,7 +220,7 @@ namespace Bit.App.Pages
|
|||||||
// Enroll user
|
// Enroll user
|
||||||
await _apiService.PutOrganizationUserResetPasswordEnrollmentAsync(OrgId, userId, resetRequest);
|
await _apiService.PutOrganizationUserResetPasswordEnrollmentAsync(OrgId, userId, resetRequest);
|
||||||
}
|
}
|
||||||
|
|
||||||
await _deviceActionService.HideLoadingAsync();
|
await _deviceActionService.HideLoadingAsync();
|
||||||
SetPasswordSuccessAction?.Invoke();
|
SetPasswordSuccessAction?.Invoke();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
using Bit.App.Controls;
|
using System;
|
||||||
|
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,7 +47,8 @@ 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);
|
||||||
}
|
}
|
||||||
@@ -92,7 +93,8 @@ 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);
|
||||||
}
|
}
|
||||||
@@ -187,7 +189,7 @@ namespace Bit.App.Pages
|
|||||||
var page = new SetPasswordPage(_appOptions, _orgIdentifier);
|
var page = new SetPasswordPage(_appOptions, _orgIdentifier);
|
||||||
await Navigation.PushModalAsync(new NavigationPage(page));
|
await Navigation.PushModalAsync(new NavigationPage(page));
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task UpdateTempPasswordAsync()
|
private async Task UpdateTempPasswordAsync()
|
||||||
{
|
{
|
||||||
var page = new UpdateTempPasswordPage();
|
var page = new UpdateTempPasswordPage();
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
using Bit.App.Abstractions;
|
using System;
|
||||||
|
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;
|
||||||
@@ -113,7 +113,7 @@ namespace Bit.App.Pages
|
|||||||
public Action StartSetPasswordAction { get; set; }
|
public Action StartSetPasswordAction { get; set; }
|
||||||
public Action CloseAction { get; set; }
|
public Action CloseAction { get; set; }
|
||||||
public Action UpdateTempPasswordAction { get; set; }
|
public Action UpdateTempPasswordAction { get; set; }
|
||||||
|
|
||||||
protected override II18nService i18nService => _i18nService;
|
protected override II18nService i18nService => _i18nService;
|
||||||
protected override IEnvironmentService environmentService => _environmentService;
|
protected override IEnvironmentService environmentService => _environmentService;
|
||||||
protected override IDeviceActionService deviceActionService => _deviceActionService;
|
protected override IDeviceActionService deviceActionService => _deviceActionService;
|
||||||
@@ -293,7 +293,7 @@ namespace Bit.App.Pages
|
|||||||
await _deviceActionService.ShowLoadingAsync(AppResources.Validating);
|
await _deviceActionService.ShowLoadingAsync(AppResources.Validating);
|
||||||
}
|
}
|
||||||
var result = await _authService.LogInTwoFactorAsync(SelectedProviderType.Value, Token, _captchaToken, Remember);
|
var result = await _authService.LogInTwoFactorAsync(SelectedProviderType.Value, Token, _captchaToken, Remember);
|
||||||
|
|
||||||
if (result.CaptchaNeeded)
|
if (result.CaptchaNeeded)
|
||||||
{
|
{
|
||||||
if (await HandleCaptchaAsync(result.CaptchaSiteKey))
|
if (await HandleCaptchaAsync(result.CaptchaSiteKey))
|
||||||
@@ -304,12 +304,12 @@ namespace Bit.App.Pages
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_captchaToken = null;
|
_captchaToken = null;
|
||||||
|
|
||||||
var task = Task.Run(() => _syncService.FullSyncAsync(true));
|
var task = Task.Run(() => _syncService.FullSyncAsync(true));
|
||||||
await _deviceActionService.HideLoadingAsync();
|
await _deviceActionService.HideLoadingAsync();
|
||||||
_messagingService.Send("listenYubiKeyOTP", false);
|
_messagingService.Send("listenYubiKeyOTP", false);
|
||||||
_broadcasterService.Unsubscribe(nameof(TwoFactorPage));
|
_broadcasterService.Unsubscribe(nameof(TwoFactorPage));
|
||||||
|
|
||||||
if (_authingWithSso && result.ResetMasterPassword)
|
if (_authingWithSso && result.ResetMasterPassword)
|
||||||
{
|
{
|
||||||
StartSetPasswordAction?.Invoke();
|
StartSetPasswordAction?.Invoke();
|
||||||
@@ -317,7 +317,7 @@ namespace Bit.App.Pages
|
|||||||
else if (result.ForcePasswordReset)
|
else if (result.ForcePasswordReset)
|
||||||
{
|
{
|
||||||
UpdateTempPasswordAction?.Invoke();
|
UpdateTempPasswordAction?.Invoke();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
TwoFactorAuthSuccessAction?.Invoke();
|
TwoFactorAuthSuccessAction?.Invoke();
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -14,29 +14,29 @@ namespace Bit.App.Pages
|
|||||||
private readonly string _pageName;
|
private readonly string _pageName;
|
||||||
|
|
||||||
public UpdateTempPasswordPage()
|
public UpdateTempPasswordPage()
|
||||||
{
|
{
|
||||||
// Service Init
|
// Service Init
|
||||||
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
|
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
|
||||||
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
|
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
|
||||||
|
|
||||||
// Binding
|
// Binding
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
_pageName = string.Concat(nameof(UpdateTempPasswordPage), "_", DateTime.UtcNow.Ticks);
|
_pageName = string.Concat(nameof(UpdateTempPasswordPage), "_", DateTime.UtcNow.Ticks);
|
||||||
_vm = BindingContext as UpdateTempPasswordPageViewModel;
|
_vm = BindingContext as UpdateTempPasswordPageViewModel;
|
||||||
_vm.Page = this;
|
_vm.Page = this;
|
||||||
SetActivityIndicator();
|
SetActivityIndicator();
|
||||||
|
|
||||||
// Actions Declaration
|
// Actions Declaration
|
||||||
_vm.LogOutAction = () =>
|
_vm.LogOutAction = () =>
|
||||||
{
|
{
|
||||||
_messagingService.Send("logout");
|
_messagingService.Send("logout");
|
||||||
};
|
};
|
||||||
_vm.UpdateTempPasswordSuccessAction = () => Device.BeginInvokeOnMainThread(UpdateTempPasswordSuccess);
|
_vm.UpdateTempPasswordSuccessAction = () => Device.BeginInvokeOnMainThread(UpdateTempPasswordSuccess);
|
||||||
|
|
||||||
// Link fields that will be referenced in codebehind
|
// Link fields that will be referenced in codebehind
|
||||||
MasterPasswordEntry = _masterPassword;
|
MasterPasswordEntry = _masterPassword;
|
||||||
ConfirmMasterPasswordEntry = _confirmMasterPassword;
|
ConfirmMasterPasswordEntry = _confirmMasterPassword;
|
||||||
|
|
||||||
// Return Types and Commands
|
// Return Types and Commands
|
||||||
_masterPassword.ReturnType = ReturnType.Next;
|
_masterPassword.ReturnType = ReturnType.Next;
|
||||||
_masterPassword.ReturnCommand = new Command(() => _confirmMasterPassword.Focus());
|
_masterPassword.ReturnCommand = new Command(() => _confirmMasterPassword.Focus());
|
||||||
@@ -77,7 +77,7 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateTempPasswordSuccess()
|
private void UpdateTempPasswordSuccess()
|
||||||
{
|
{
|
||||||
_messagingService.Send("logout");
|
_messagingService.Send("logout");
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
using Bit.App.Resources;
|
using System;
|
||||||
using System;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Bit.App.Resources;
|
||||||
using Bit.Core.Exceptions;
|
using Bit.Core.Exceptions;
|
||||||
using Bit.Core.Models.Request;
|
using Bit.Core.Models.Request;
|
||||||
using Xamarin.Forms;
|
using Xamarin.Forms;
|
||||||
@@ -16,7 +16,7 @@ namespace Bit.App.Pages
|
|||||||
ToggleConfirmPasswordCommand = new Command(ToggleConfirmPassword);
|
ToggleConfirmPasswordCommand = new Command(ToggleConfirmPassword);
|
||||||
SubmitCommand = new Command(async () => await SubmitAsync());
|
SubmitCommand = new Command(async () => await SubmitAsync());
|
||||||
}
|
}
|
||||||
|
|
||||||
public Command SubmitCommand { get; }
|
public Command SubmitCommand { get; }
|
||||||
public Command TogglePasswordCommand { get; }
|
public Command TogglePasswordCommand { get; }
|
||||||
public Command ToggleConfirmPasswordCommand { get; }
|
public Command ToggleConfirmPasswordCommand { get; }
|
||||||
@@ -37,23 +37,23 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
public async Task SubmitAsync()
|
public async Task SubmitAsync()
|
||||||
{
|
{
|
||||||
if (!await ValidateMasterPasswordAsync())
|
if (!await ValidateMasterPasswordAsync())
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Retrieve details for key generation
|
// Retrieve details for key generation
|
||||||
var kdf = await _stateService.GetKdfTypeAsync();
|
var kdf = await _stateService.GetKdfTypeAsync();
|
||||||
var kdfIterations = await _stateService.GetKdfIterationsAsync();
|
var kdfIterations = await _stateService.GetKdfIterationsAsync();
|
||||||
var email = await _stateService.GetEmailAsync();
|
var email = await _stateService.GetEmailAsync();
|
||||||
|
|
||||||
// Create new key and hash new password
|
// Create new key and hash new password
|
||||||
var key = await _cryptoService.MakeKeyAsync(MasterPassword, email, kdf, kdfIterations);
|
var key = await _cryptoService.MakeKeyAsync(MasterPassword, email, kdf, kdfIterations);
|
||||||
var masterPasswordHash = await _cryptoService.HashPasswordAsync(MasterPassword, key);
|
var masterPasswordHash = await _cryptoService.HashPasswordAsync(MasterPassword, key);
|
||||||
|
|
||||||
// Create new encKey for the User
|
// Create new encKey for the User
|
||||||
var newEncKey = await _cryptoService.RemakeEncKeyAsync(key);
|
var newEncKey = await _cryptoService.RemakeEncKeyAsync(key);
|
||||||
|
|
||||||
// Create request
|
// Create request
|
||||||
var request = new UpdateTempPasswordRequest
|
var request = new UpdateTempPasswordRequest
|
||||||
{
|
{
|
||||||
@@ -61,7 +61,7 @@ namespace Bit.App.Pages
|
|||||||
NewMasterPasswordHash = masterPasswordHash,
|
NewMasterPasswordHash = masterPasswordHash,
|
||||||
MasterPasswordHint = Hint
|
MasterPasswordHint = Hint
|
||||||
};
|
};
|
||||||
|
|
||||||
// Initiate API action
|
// Initiate API action
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ namespace Bit.App.Pages
|
|||||||
_logger = ServiceContainer.Resolve<ILogger>("logger");
|
_logger = ServiceContainer.Resolve<ILogger>("logger");
|
||||||
|
|
||||||
PageTitle = AppResources.VerificationCode;
|
PageTitle = AppResources.VerificationCode;
|
||||||
|
|
||||||
TogglePasswordCommand = new Command(TogglePassword);
|
TogglePasswordCommand = new Command(TogglePassword);
|
||||||
MainActionCommand = new AsyncCommand(MainActionAsync, allowsMultipleExecutions: false);
|
MainActionCommand = new AsyncCommand(MainActionAsync, allowsMultipleExecutions: false);
|
||||||
RequestOTPCommand = new AsyncCommand(RequestOTPAsync, allowsMultipleExecutions: false);
|
RequestOTPCommand = new AsyncCommand(RequestOTPAsync, allowsMultipleExecutions: false);
|
||||||
@@ -60,7 +60,7 @@ namespace Bit.App.Pages
|
|||||||
get => _mainActionText;
|
get => _mainActionText;
|
||||||
set => SetProperty(ref _mainActionText, value);
|
set => SetProperty(ref _mainActionText, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public string SendCodeStatus
|
public string SendCodeStatus
|
||||||
{
|
{
|
||||||
get => _sendCodeStatus;
|
get => _sendCodeStatus;
|
||||||
|
|||||||
@@ -35,12 +35,12 @@ namespace Bit.App.Pages
|
|||||||
protected async override void OnAppearing()
|
protected async override void OnAppearing()
|
||||||
{
|
{
|
||||||
base.OnAppearing();
|
base.OnAppearing();
|
||||||
|
|
||||||
if (IsThemeDirty)
|
if (IsThemeDirty)
|
||||||
{
|
{
|
||||||
UpdateOnThemeChanged();
|
UpdateOnThemeChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
await SaveActivityAsync();
|
await SaveActivityAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,6 +70,7 @@ namespace Bit.App.Pages
|
|||||||
VerticalOptions = LayoutOptions.CenterAndExpand,
|
VerticalOptions = LayoutOptions.CenterAndExpand,
|
||||||
HorizontalOptions = LayoutOptions.Center,
|
HorizontalOptions = LayoutOptions.Center,
|
||||||
Color = ThemeManager.GetResourceColor("PrimaryColor"),
|
Color = ThemeManager.GetResourceColor("PrimaryColor"),
|
||||||
|
AutomationId = "activity_indicator"
|
||||||
};
|
};
|
||||||
if (targetView != null)
|
if (targetView != null)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -31,7 +31,8 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
base.OnAppearing();
|
base.OnAppearing();
|
||||||
|
|
||||||
await LoadOnAppearedAsync(_mainLayout, true, async () => {
|
await LoadOnAppearedAsync(_mainLayout, true, async () =>
|
||||||
|
{
|
||||||
await _vm.InitAsync();
|
await _vm.InitAsync();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,8 +41,11 @@ namespace Bit.App.Pages
|
|||||||
public async Task InitAsync()
|
public async Task InitAsync()
|
||||||
{
|
{
|
||||||
var history = await _passwordGenerationService.GetHistoryAsync();
|
var history = await _passwordGenerationService.GetHistoryAsync();
|
||||||
History.ResetWithRange(history ?? new List<GeneratedPasswordHistory>());
|
Device.BeginInvokeOnMainThread(() =>
|
||||||
ShowNoData = History.Count == 0;
|
{
|
||||||
|
History.ResetWithRange(history ?? new List<GeneratedPasswordHistory>());
|
||||||
|
ShowNoData = History.Count == 0;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task ClearAsync()
|
public async Task ClearAsync()
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ namespace Bit.App.Pages
|
|||||||
public partial class GeneratorPage : BaseContentPage, IThemeDirtablePage
|
public partial class GeneratorPage : BaseContentPage, IThemeDirtablePage
|
||||||
{
|
{
|
||||||
private readonly IBroadcasterService _broadcasterService;
|
private readonly IBroadcasterService _broadcasterService;
|
||||||
|
|
||||||
private GeneratorPageViewModel _vm;
|
private GeneratorPageViewModel _vm;
|
||||||
private readonly bool _fromTabPage;
|
private readonly bool _fromTabPage;
|
||||||
private readonly Action<string> _selectAction;
|
private readonly Action<string> _selectAction;
|
||||||
@@ -77,7 +77,7 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnDisappearing()
|
protected override void OnDisappearing()
|
||||||
{
|
{
|
||||||
base.OnDisappearing();
|
base.OnDisappearing();
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
using Bit.App.Resources;
|
using System.Collections.Generic;
|
||||||
|
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;
|
using Xamarin.Forms;
|
||||||
|
|
||||||
namespace Bit.App.Pages
|
namespace Bit.App.Pages
|
||||||
@@ -225,7 +225,7 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public PasswordGeneratorPolicyOptions EnforcedPolicyOptions
|
public PasswordGeneratorPolicyOptions EnforcedPolicyOptions
|
||||||
{
|
{
|
||||||
get => _enforcedPolicyOptions;
|
get => _enforcedPolicyOptions;
|
||||||
|
|||||||
@@ -256,23 +256,24 @@
|
|||||||
x:Name="_btnOptions"
|
x:Name="_btnOptions"
|
||||||
StyleClass="box-row-button"
|
StyleClass="box-row-button"
|
||||||
TextColor="{DynamicResource PrimaryColor}"
|
TextColor="{DynamicResource PrimaryColor}"
|
||||||
Margin="0" />
|
Margin="0"
|
||||||
|
Clicked="ToggleOptions_Clicked"/>
|
||||||
<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}"
|
||||||
Clicked="ToggleOptions_Clicked"
|
Clicked="ToggleOptions_Clicked"
|
||||||
IsVisible="False" />
|
IsVisible="{Binding ShowOptions}" />
|
||||||
<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}"
|
||||||
Clicked="ToggleOptions_Clicked"
|
Clicked="ToggleOptions_Clicked"
|
||||||
IsVisible="False" />
|
IsVisible="{Binding ShowOptions, Converter={StaticResource inverseBool}}" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout IsVisible="True">
|
<StackLayout IsVisible="{Binding ShowOptions}">
|
||||||
<StackLayout
|
<StackLayout
|
||||||
StyleClass="box-row"
|
StyleClass="box-row"
|
||||||
Margin="0,10,0,0">
|
Margin="0,10,0,0">
|
||||||
|
|||||||
@@ -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,10 +53,9 @@ 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);
|
||||||
// Review this when https://github.com/bitwarden/mobile/pull/1454 workaround can be reverted
|
_btnOptions.WidthRequest = 70;
|
||||||
//_btnOptions.WidthRequest = 70;
|
_btnOptionsDown.WidthRequest = 30;
|
||||||
//_btnOptionsDown.WidthRequest = 30;
|
_btnOptionsUp.WidthRequest = 30;
|
||||||
//_btnOptionsUp.WidthRequest = 30;
|
|
||||||
}
|
}
|
||||||
else if (Device.RuntimePlatform == Device.iOS)
|
else if (Device.RuntimePlatform == Device.iOS)
|
||||||
{
|
{
|
||||||
@@ -338,10 +337,10 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
_vm.IsAddFromShare = true;
|
_vm.IsAddFromShare = true;
|
||||||
_vm.CopyInsteadOfShareAfterSaving = _appOptions.CopyInsteadOfShareAfterSaving;
|
_vm.CopyInsteadOfShareAfterSaving = _appOptions.CopyInsteadOfShareAfterSaving;
|
||||||
|
|
||||||
var name = _appOptions.CreateSend.Item2;
|
var name = _appOptions.CreateSend.Item2;
|
||||||
_vm.Send.Name = name;
|
_vm.Send.Name = name;
|
||||||
|
|
||||||
var type = _appOptions.CreateSend.Item1;
|
var type = _appOptions.CreateSend.Item1;
|
||||||
if (type == SendType.File)
|
if (type == SendType.File)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ 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),
|
||||||
@@ -209,7 +209,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)
|
||||||
});
|
});
|
||||||
@@ -237,7 +237,7 @@ namespace Bit.App.Pages
|
|||||||
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;
|
||||||
}
|
}
|
||||||
@@ -381,7 +381,7 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
UpdateSendData();
|
UpdateSendData();
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(NewPassword))
|
if (string.IsNullOrWhiteSpace(NewPassword))
|
||||||
{
|
{
|
||||||
NewPassword = null;
|
NewPassword = null;
|
||||||
}
|
}
|
||||||
@@ -521,7 +521,7 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
await _platformUtilsService.ShowDialogAsync(AppResources.SendFileEmailVerificationRequired);
|
await _platformUtilsService.ShowDialogAsync(AppResources.SendFileEmailVerificationRequired);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (IsAddFromShare && Device.RuntimePlatform == Device.Android)
|
if (IsAddFromShare && Device.RuntimePlatform == Device.Android)
|
||||||
{
|
{
|
||||||
_deviceActionService.CloseMainApp();
|
_deviceActionService.CloseMainApp();
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
namespace Bit.App.Pages
|
||||||
|
{
|
||||||
|
public interface ISendGroupingsPageListItem
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -73,7 +73,29 @@
|
|||||||
</controls:ExtendedStackLayout>
|
</controls:ExtendedStackLayout>
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
|
|
||||||
|
<DataTemplate
|
||||||
|
x:Key="headerTemplate"
|
||||||
|
x:DataType="pages:SendGroupingsPageHeaderListItem">
|
||||||
|
<StackLayout
|
||||||
|
Spacing="0" Padding="0" VerticalOptions="FillAndExpand"
|
||||||
|
StyleClass="list-row-header-container, list-row-header-container-platform">
|
||||||
|
<BoxView
|
||||||
|
StyleClass="list-section-separator-top, list-section-separator-top-platform" />
|
||||||
|
<StackLayout StyleClass="list-row-header, list-row-header-platform">
|
||||||
|
<Label
|
||||||
|
Text="{Binding Title}"
|
||||||
|
StyleClass="list-header, list-header-platform" />
|
||||||
|
<Label
|
||||||
|
Text="{Binding ItemCount}"
|
||||||
|
StyleClass="list-header-sub" />
|
||||||
|
</StackLayout>
|
||||||
|
<BoxView
|
||||||
|
StyleClass="list-section-separator-bottom, list-section-separator-bottom-platform" />
|
||||||
|
</StackLayout>
|
||||||
|
</DataTemplate>
|
||||||
|
|
||||||
<pages:SendGroupingsPageListItemSelector x:Key="sendListItemDataTemplateSelector"
|
<pages:SendGroupingsPageListItemSelector x:Key="sendListItemDataTemplateSelector"
|
||||||
|
HeaderTemplate="{StaticResource headerTemplate}"
|
||||||
SendTemplate="{StaticResource sendTemplate}"
|
SendTemplate="{StaticResource sendTemplate}"
|
||||||
GroupTemplate="{StaticResource sendGroupTemplate}" />
|
GroupTemplate="{StaticResource sendGroupTemplate}" />
|
||||||
|
|
||||||
@@ -114,33 +136,9 @@
|
|||||||
ItemsSource="{Binding GroupedSends}"
|
ItemsSource="{Binding GroupedSends}"
|
||||||
VerticalOptions="FillAndExpand"
|
VerticalOptions="FillAndExpand"
|
||||||
ItemTemplate="{StaticResource sendListItemDataTemplateSelector}"
|
ItemTemplate="{StaticResource sendListItemDataTemplateSelector}"
|
||||||
IsGrouped="True"
|
|
||||||
SelectionMode="Single"
|
SelectionMode="Single"
|
||||||
SelectionChanged="RowSelected"
|
SelectionChanged="RowSelected"
|
||||||
StyleClass="list, list-platform">
|
StyleClass="list, list-platform" />
|
||||||
|
|
||||||
<CollectionView.GroupHeaderTemplate>
|
|
||||||
<DataTemplate x:DataType="pages:SendGroupingsPageListGroup">
|
|
||||||
<StackLayout
|
|
||||||
Spacing="0" Padding="0" VerticalOptions="FillAndExpand"
|
|
||||||
StyleClass="list-row-header-container, list-row-header-container-platform">
|
|
||||||
<BoxView
|
|
||||||
StyleClass="list-section-separator-top, list-section-separator-top-platform"
|
|
||||||
IsVisible="{Binding First, Converter={StaticResource inverseBool}}" />
|
|
||||||
<StackLayout StyleClass="list-row-header, list-row-header-platform">
|
|
||||||
<Label
|
|
||||||
Text="{Binding Name}"
|
|
||||||
StyleClass="list-header, list-header-platform" />
|
|
||||||
<Label
|
|
||||||
Text="{Binding ItemCount}"
|
|
||||||
StyleClass="list-header-sub" />
|
|
||||||
</StackLayout>
|
|
||||||
<BoxView
|
|
||||||
StyleClass="list-section-separator-bottom, list-section-separator-bottom-platform" />
|
|
||||||
</StackLayout>
|
|
||||||
</DataTemplate>
|
|
||||||
</CollectionView.GroupHeaderTemplate>
|
|
||||||
</controls:ExtendedCollectionView>
|
|
||||||
</RefreshView>
|
</RefreshView>
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
</ResourceDictionary>
|
</ResourceDictionary>
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -133,7 +133,7 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void RowSelected(object sender, SelectionChangedEventArgs e)
|
private async void RowSelected(object sender, SelectionChangedEventArgs e)
|
||||||
{
|
{
|
||||||
((ExtendedCollectionView)sender).SelectedItem = null;
|
((ExtendedCollectionView)sender).SelectedItem = null;
|
||||||
@@ -160,7 +160,7 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
if (DoOnce())
|
if (DoOnce())
|
||||||
{
|
{
|
||||||
var page = new SendsPage(_vm.Filter, _vm.Type != null);
|
var page = new SendsPage(_vm.Filter, _vm.Type);
|
||||||
await Navigation.PushModalAsync(new NavigationPage(page));
|
await Navigation.PushModalAsync(new NavigationPage(page));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -174,7 +174,7 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
await _vaultTimeoutService.LockAsync(true, true);
|
await _vaultTimeoutService.LockAsync(true, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void About_Clicked(object sender, EventArgs e)
|
private void About_Clicked(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
_vm.ShowAbout();
|
_vm.ShowAbout();
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
namespace Bit.App.Pages
|
||||||
|
{
|
||||||
|
public class SendGroupingsPageHeaderListItem : ISendGroupingsPageListItem
|
||||||
|
{
|
||||||
|
public SendGroupingsPageHeaderListItem(string title, string itemCount)
|
||||||
|
{
|
||||||
|
Title = title;
|
||||||
|
ItemCount = itemCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Title { get; }
|
||||||
|
public string ItemCount { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,7 +5,7 @@ using Bit.Core.Models.View;
|
|||||||
|
|
||||||
namespace Bit.App.Pages
|
namespace Bit.App.Pages
|
||||||
{
|
{
|
||||||
public class SendGroupingsPageListItem
|
public class SendGroupingsPageListItem : ISendGroupingsPageListItem
|
||||||
{
|
{
|
||||||
private string _icon;
|
private string _icon;
|
||||||
private string _name;
|
private string _name;
|
||||||
|
|||||||
@@ -4,11 +4,17 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
public class SendGroupingsPageListItemSelector : DataTemplateSelector
|
public class SendGroupingsPageListItemSelector : DataTemplateSelector
|
||||||
{
|
{
|
||||||
|
public DataTemplate HeaderTemplate { get; set; }
|
||||||
public DataTemplate SendTemplate { get; set; }
|
public DataTemplate SendTemplate { get; set; }
|
||||||
public DataTemplate GroupTemplate { get; set; }
|
public DataTemplate GroupTemplate { get; set; }
|
||||||
|
|
||||||
protected override DataTemplate OnSelectTemplate(object item, BindableObject container)
|
protected override DataTemplate OnSelectTemplate(object item, BindableObject container)
|
||||||
{
|
{
|
||||||
|
if (item is SendGroupingsPageHeaderListItem)
|
||||||
|
{
|
||||||
|
return HeaderTemplate;
|
||||||
|
}
|
||||||
|
|
||||||
if (item is SendGroupingsPageListItem listItem)
|
if (item is SendGroupingsPageListItem listItem)
|
||||||
{
|
{
|
||||||
return listItem.Send != null ? SendTemplate : GroupTemplate;
|
return listItem.Send != null ? SendTemplate : GroupTemplate;
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ using Bit.Core.Abstractions;
|
|||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Models.View;
|
using Bit.Core.Models.View;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
|
using Xamarin.CommunityToolkit.ObjectModel;
|
||||||
using Xamarin.Essentials;
|
using Xamarin.Essentials;
|
||||||
using Xamarin.Forms;
|
using Xamarin.Forms;
|
||||||
using DeviceType = Bit.Core.Enums.DeviceType;
|
using DeviceType = Bit.Core.Enums.DeviceType;
|
||||||
@@ -48,7 +49,7 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
Loading = true;
|
Loading = true;
|
||||||
PageTitle = AppResources.Send;
|
PageTitle = AppResources.Send;
|
||||||
GroupedSends = new ExtendedObservableCollection<SendGroupingsPageListGroup>();
|
GroupedSends = new ObservableRangeCollection<ISendGroupingsPageListItem>();
|
||||||
RefreshCommand = new Command(async () =>
|
RefreshCommand = new Command(async () =>
|
||||||
{
|
{
|
||||||
Refreshing = true;
|
Refreshing = true;
|
||||||
@@ -103,7 +104,7 @@ namespace Bit.App.Pages
|
|||||||
get => _showList;
|
get => _showList;
|
||||||
set => SetProperty(ref _showList, value);
|
set => SetProperty(ref _showList, value);
|
||||||
}
|
}
|
||||||
public ExtendedObservableCollection<SendGroupingsPageListGroup> GroupedSends { get; set; }
|
public ObservableRangeCollection<ISendGroupingsPageListItem> GroupedSends { get; set; }
|
||||||
public Command RefreshCommand { get; set; }
|
public Command RefreshCommand { get; set; }
|
||||||
public Command<SendView> SendOptionsCommand { get; set; }
|
public Command<SendView> SendOptionsCommand { get; set; }
|
||||||
public bool LoadedOnce { get; set; }
|
public bool LoadedOnce { get; set; }
|
||||||
@@ -135,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;
|
||||||
|
|
||||||
@@ -175,7 +176,49 @@ namespace Bit.App.Pages
|
|||||||
MainPage ? AppResources.AllSends : AppResources.Sends, sendsListItems.Count,
|
MainPage ? AppResources.AllSends : AppResources.Sends, sendsListItems.Count,
|
||||||
uppercaseGroupNames, !MainPage));
|
uppercaseGroupNames, !MainPage));
|
||||||
}
|
}
|
||||||
GroupedSends.ResetWithRange(groupedSends);
|
|
||||||
|
// TODO: refactor this
|
||||||
|
if (Device.RuntimePlatform == Device.Android
|
||||||
|
||
|
||||||
|
GroupedSends.Any())
|
||||||
|
{
|
||||||
|
var items = new List<ISendGroupingsPageListItem>();
|
||||||
|
foreach (var itemGroup in groupedSends)
|
||||||
|
{
|
||||||
|
items.Add(new SendGroupingsPageHeaderListItem(itemGroup.Name, itemGroup.ItemCount));
|
||||||
|
items.AddRange(itemGroup);
|
||||||
|
}
|
||||||
|
|
||||||
|
GroupedSends.ReplaceRange(items);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// HACK: we need this on iOS, so that it doesn't crash when adding coming from an empty list
|
||||||
|
var first = true;
|
||||||
|
var items = new List<ISendGroupingsPageListItem>();
|
||||||
|
foreach (var itemGroup in groupedSends)
|
||||||
|
{
|
||||||
|
if (!first)
|
||||||
|
{
|
||||||
|
items.Add(new SendGroupingsPageHeaderListItem(itemGroup.Name, itemGroup.ItemCount));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
|
items.AddRange(itemGroup);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (groupedSends.Any())
|
||||||
|
{
|
||||||
|
GroupedSends.ReplaceRange(new List<ISendGroupingsPageListItem> { new SendGroupingsPageHeaderListItem(groupedSends[0].Name, groupedSends[0].ItemCount) });
|
||||||
|
GroupedSends.AddRange(items);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
GroupedSends.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
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;
|
||||||
|
|
||||||
@@ -12,15 +13,22 @@ namespace Bit.App.Pages
|
|||||||
private SendsPageViewModel _vm;
|
private SendsPageViewModel _vm;
|
||||||
private bool _hasFocused;
|
private bool _hasFocused;
|
||||||
|
|
||||||
public SendsPage(Func<SendView, bool> filter, bool type = false)
|
public SendsPage(Func<SendView, bool> filter, SendType? type = null)
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
_vm = BindingContext as SendsPageViewModel;
|
_vm = BindingContext as SendsPageViewModel;
|
||||||
_vm.Page = this;
|
_vm.Page = this;
|
||||||
_vm.Filter = filter;
|
_vm.Filter = filter;
|
||||||
if (type)
|
if (type != null)
|
||||||
{
|
{
|
||||||
_vm.PageTitle = AppResources.SearchType;
|
if (type == SendType.File)
|
||||||
|
{
|
||||||
|
_vm.PageTitle = AppResources.SearchFileSends;
|
||||||
|
}
|
||||||
|
else if (type == SendType.Text)
|
||||||
|
{
|
||||||
|
_vm.PageTitle = AppResources.SearchTextSends;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -33,6 +41,7 @@ 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
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -35,11 +35,11 @@ namespace Bit.App.Pages
|
|||||||
get => _sendEnabled;
|
get => _sendEnabled;
|
||||||
set => SetProperty(ref _sendEnabled, value);
|
set => SetProperty(ref _sendEnabled, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
||||||
|
|||||||
@@ -49,12 +49,12 @@ namespace Bit.App.Pages
|
|||||||
_vm.ToggleAutofillService();
|
_vm.ToggleAutofillService();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ToggleInlineAutofill(object sender, EventArgs e)
|
private void ToggleInlineAutofill(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
_vm.ToggleInlineAutofill();
|
_vm.ToggleInlineAutofill();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ToggleAccessibility(object sender, EventArgs e)
|
private void ToggleAccessibility(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
if (DoOnce())
|
if (DoOnce())
|
||||||
@@ -62,7 +62,7 @@ namespace Bit.App.Pages
|
|||||||
_vm.ToggleAccessibility();
|
_vm.ToggleAccessibility();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ToggleDrawOver(object sender, EventArgs e)
|
private void ToggleDrawOver(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
if (DoOnce())
|
if (DoOnce())
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ namespace Bit.App.Pages
|
|||||||
private readonly IDeviceActionService _deviceActionService;
|
private readonly IDeviceActionService _deviceActionService;
|
||||||
private readonly IStateService _stateService;
|
private readonly IStateService _stateService;
|
||||||
private readonly MobileI18nService _i18nService;
|
private readonly MobileI18nService _i18nService;
|
||||||
|
|
||||||
private bool _autofillServiceToggled;
|
private bool _autofillServiceToggled;
|
||||||
private bool _inlineAutofillToggled;
|
private bool _inlineAutofillToggled;
|
||||||
private bool _accessibilityToggled;
|
private bool _accessibilityToggled;
|
||||||
@@ -26,9 +26,9 @@ namespace Bit.App.Pages
|
|||||||
_i18nService = ServiceContainer.Resolve<II18nService>("i18nService") as MobileI18nService;
|
_i18nService = ServiceContainer.Resolve<II18nService>("i18nService") as MobileI18nService;
|
||||||
PageTitle = AppResources.AutofillServices;
|
PageTitle = AppResources.AutofillServices;
|
||||||
}
|
}
|
||||||
|
|
||||||
#region Autofill Service
|
#region Autofill Service
|
||||||
|
|
||||||
public bool AutofillServiceVisible
|
public bool AutofillServiceVisible
|
||||||
{
|
{
|
||||||
get => _deviceActionService.SystemMajorVersion() >= 26;
|
get => _deviceActionService.SystemMajorVersion() >= 26;
|
||||||
@@ -43,16 +43,16 @@ namespace Bit.App.Pages
|
|||||||
nameof(InlineAutofillEnabled)
|
nameof(InlineAutofillEnabled)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Inline Autofill
|
#region Inline Autofill
|
||||||
|
|
||||||
public bool InlineAutofillVisible
|
public bool InlineAutofillVisible
|
||||||
{
|
{
|
||||||
get => _deviceActionService.SystemMajorVersion() >= 30;
|
get => _deviceActionService.SystemMajorVersion() >= 30;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool InlineAutofillEnabled
|
public bool InlineAutofillEnabled
|
||||||
{
|
{
|
||||||
get => AutofillServiceToggled;
|
get => AutofillServiceToggled;
|
||||||
@@ -69,9 +69,9 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Accessibility
|
#region Accessibility
|
||||||
|
|
||||||
public string AccessibilityDescriptionLabel
|
public string AccessibilityDescriptionLabel
|
||||||
@@ -97,7 +97,7 @@ namespace Bit.App.Pages
|
|||||||
return _i18nService.T("AccessibilityDescription4");
|
return _i18nService.T("AccessibilityDescription4");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool AccessibilityToggled
|
public bool AccessibilityToggled
|
||||||
{
|
{
|
||||||
get => _accessibilityToggled;
|
get => _accessibilityToggled;
|
||||||
@@ -109,9 +109,9 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Draw-Over
|
#region Draw-Over
|
||||||
|
|
||||||
public bool DrawOverVisible
|
public bool DrawOverVisible
|
||||||
{
|
{
|
||||||
get => _deviceActionService.SystemMajorVersion() >= 23;
|
get => _deviceActionService.SystemMajorVersion() >= 23;
|
||||||
@@ -135,12 +135,12 @@ namespace Bit.App.Pages
|
|||||||
return _i18nService.T("DrawOverDescription3");
|
return _i18nService.T("DrawOverDescription3");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool DrawOverEnabled
|
public bool DrawOverEnabled
|
||||||
{
|
{
|
||||||
get => AccessibilityToggled;
|
get => AccessibilityToggled;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool DrawOverToggled
|
public bool DrawOverToggled
|
||||||
{
|
{
|
||||||
get => _drawOverToggled;
|
get => _drawOverToggled;
|
||||||
@@ -148,7 +148,7 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
public async Task InitAsync()
|
public async Task InitAsync()
|
||||||
{
|
{
|
||||||
InlineAutofillToggled = await _stateService.GetInlineAutofillEnabledAsync() ?? true;
|
InlineAutofillToggled = await _stateService.GetInlineAutofillEnabledAsync() ?? true;
|
||||||
@@ -189,14 +189,15 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
_deviceActionService.OpenAccessibilityOverlayPermissionSettings();
|
_deviceActionService.OpenAccessibilityOverlayPermissionSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateEnabled()
|
public void UpdateEnabled()
|
||||||
{
|
{
|
||||||
AutofillServiceToggled = _deviceActionService.AutofillServiceEnabled();
|
AutofillServiceToggled =
|
||||||
|
_deviceActionService.HasAutofillService() && _deviceActionService.AutofillServiceEnabled();
|
||||||
AccessibilityToggled = _deviceActionService.AutofillAccessibilityServiceRunning();
|
AccessibilityToggled = _deviceActionService.AutofillAccessibilityServiceRunning();
|
||||||
DrawOverToggled = _deviceActionService.AutofillAccessibilityOverlayPermitted();
|
DrawOverToggled = _deviceActionService.AutofillAccessibilityOverlayPermitted();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task UpdateInlineAutofillToggledAsync()
|
private async Task UpdateInlineAutofillToggledAsync()
|
||||||
{
|
{
|
||||||
if (_inited)
|
if (_inited)
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -77,7 +77,7 @@ namespace Bit.App.Pages
|
|||||||
InstructionText = _i18nService.T("ExportVaultMasterPasswordDescription");
|
InstructionText = _i18nService.T("ExportVaultMasterPasswordDescription");
|
||||||
SecretName = _i18nService.T("MasterPassword");
|
SecretName = _i18nService.T("MasterPassword");
|
||||||
}
|
}
|
||||||
|
|
||||||
UpdateWarning();
|
UpdateWarning();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,7 +109,7 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
get => _showPassword;
|
get => _showPassword;
|
||||||
set => SetProperty(ref _showPassword, value,
|
set => SetProperty(ref _showPassword, value,
|
||||||
additionalPropertyNames: new string[] {nameof(ShowPasswordIcon)});
|
additionalPropertyNames: new string[] { nameof(ShowPasswordIcon) });
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool UseOTPVerification
|
public bool UseOTPVerification
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
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.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Bit.App.Pages
|
namespace Bit.App.Pages
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
using Bit.App.Resources;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Generic;
|
using Bit.App.Resources;
|
||||||
using Xamarin.Forms;
|
using Xamarin.Forms;
|
||||||
|
|
||||||
namespace Bit.App.Pages
|
namespace Bit.App.Pages
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
using Bit.App.Abstractions;
|
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.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
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
using Bit.Core.Models.View;
|
using System;
|
||||||
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
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
using Bit.App.Resources;
|
using System.Collections.Generic;
|
||||||
|
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
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
using Bit.App.Resources;
|
using System.Collections.Generic;
|
||||||
|
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
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
namespace Bit.App.Pages
|
||||||
|
{
|
||||||
|
public interface ISettingsPageListItem
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -82,8 +82,26 @@
|
|||||||
</controls:ExtendedStackLayout>
|
</controls:ExtendedStackLayout>
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
|
|
||||||
|
<DataTemplate
|
||||||
|
x:Key="headerTemplate"
|
||||||
|
x:DataType="pages:SettingsPageHeaderListItem">
|
||||||
|
<StackLayout
|
||||||
|
Padding="0" Spacing="0" VerticalOptions="FillAndExpand"
|
||||||
|
StyleClass="list-row-header-container, list-row-header-container-platform">
|
||||||
|
<BoxView
|
||||||
|
StyleClass="list-section-separator-top, list-section-separator-top-platform" />
|
||||||
|
<StackLayout StyleClass="list-row-header, list-row-header-platform">
|
||||||
|
<Label
|
||||||
|
Text="{Binding Title}"
|
||||||
|
StyleClass="list-header, list-header-platform" />
|
||||||
|
</StackLayout>
|
||||||
|
<BoxView StyleClass="list-section-separator-bottom, list-section-separator-bottom-platform" />
|
||||||
|
</StackLayout>
|
||||||
|
</DataTemplate>
|
||||||
|
|
||||||
<pages:SettingsPageListItemSelector
|
<pages:SettingsPageListItemSelector
|
||||||
x:Key="listItemDataTemplateSelector"
|
x:Key="listItemDataTemplateSelector"
|
||||||
|
HeaderTemplate="{StaticResource headerTemplate}"
|
||||||
RegularTemplate="{StaticResource regularTemplate}"
|
RegularTemplate="{StaticResource regularTemplate}"
|
||||||
TimePickerTemplate="{StaticResource timePickerTemplate}" />
|
TimePickerTemplate="{StaticResource timePickerTemplate}" />
|
||||||
</ResourceDictionary>
|
</ResourceDictionary>
|
||||||
@@ -93,28 +111,8 @@
|
|||||||
ItemsSource="{Binding GroupedItems}"
|
ItemsSource="{Binding GroupedItems}"
|
||||||
VerticalOptions="FillAndExpand"
|
VerticalOptions="FillAndExpand"
|
||||||
ItemTemplate="{StaticResource listItemDataTemplateSelector}"
|
ItemTemplate="{StaticResource listItemDataTemplateSelector}"
|
||||||
IsGrouped="True"
|
|
||||||
SelectionMode="Single"
|
SelectionMode="Single"
|
||||||
SelectionChanged="RowSelected"
|
SelectionChanged="RowSelected"
|
||||||
StyleClass="list, list-platform">
|
StyleClass="list, list-platform" />
|
||||||
|
|
||||||
<CollectionView.GroupHeaderTemplate>
|
|
||||||
<DataTemplate x:DataType="pages:SettingsPageListGroup">
|
|
||||||
<StackLayout
|
|
||||||
Padding="0" Spacing="0" VerticalOptions="FillAndExpand"
|
|
||||||
StyleClass="list-row-header-container, list-row-header-container-platform">
|
|
||||||
<BoxView
|
|
||||||
StyleClass="list-section-separator-top, list-section-separator-top-platform"
|
|
||||||
IsVisible="{Binding First, Converter={StaticResource inverseBool}}" />
|
|
||||||
<StackLayout StyleClass="list-row-header, list-row-header-platform">
|
|
||||||
<Label
|
|
||||||
Text="{Binding Name}"
|
|
||||||
StyleClass="list-header, list-header-platform" />
|
|
||||||
</StackLayout>
|
|
||||||
<BoxView StyleClass="list-section-separator-bottom, list-section-separator-bottom-platform" />
|
|
||||||
</StackLayout>
|
|
||||||
</DataTemplate>
|
|
||||||
</CollectionView.GroupHeaderTemplate>
|
|
||||||
</controls:ExtendedCollectionView>
|
|
||||||
|
|
||||||
</pages:BaseContentPage>
|
</pages:BaseContentPage>
|
||||||
|
|||||||
@@ -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")
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
namespace Bit.App.Pages
|
||||||
|
{
|
||||||
|
public class SettingsPageHeaderListItem : ISettingsPageListItem
|
||||||
|
{
|
||||||
|
public SettingsPageHeaderListItem(string title)
|
||||||
|
{
|
||||||
|
Title = title;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Title { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,12 +1,11 @@
|
|||||||
using System;
|
using System;
|
||||||
using Bit.App.Resources;
|
using Bit.App.Resources;
|
||||||
using Bit.App.Utilities;
|
using Bit.App.Utilities;
|
||||||
using System.Collections.Generic;
|
|
||||||
using Xamarin.Forms;
|
using Xamarin.Forms;
|
||||||
|
|
||||||
namespace Bit.App.Pages
|
namespace Bit.App.Pages
|
||||||
{
|
{
|
||||||
public class SettingsPageListItem
|
public class SettingsPageListItem : ISettingsPageListItem
|
||||||
{
|
{
|
||||||
public string Icon { get; set; }
|
public string Icon { get; set; }
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
|
|||||||
@@ -4,21 +4,19 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
public class SettingsPageListItemSelector : DataTemplateSelector
|
public class SettingsPageListItemSelector : DataTemplateSelector
|
||||||
{
|
{
|
||||||
|
public DataTemplate HeaderTemplate { get; set; }
|
||||||
public DataTemplate RegularTemplate { get; set; }
|
public DataTemplate RegularTemplate { get; set; }
|
||||||
public DataTemplate TimePickerTemplate { get; set; }
|
public DataTemplate TimePickerTemplate { get; set; }
|
||||||
|
|
||||||
protected override DataTemplate OnSelectTemplate(object item, BindableObject container)
|
protected override DataTemplate OnSelectTemplate(object item, BindableObject container)
|
||||||
{
|
{
|
||||||
|
if (item is SettingsPageHeaderListItem)
|
||||||
|
{
|
||||||
|
return HeaderTemplate;
|
||||||
|
}
|
||||||
if (item is SettingsPageListItem listItem)
|
if (item is SettingsPageListItem listItem)
|
||||||
{
|
{
|
||||||
if (listItem.ShowTimeInput)
|
return listItem.ShowTimeInput ? TimePickerTemplate : RegularTemplate;
|
||||||
{
|
|
||||||
return TimePickerTemplate;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return RegularTemplate;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
using Bit.App.Abstractions;
|
using System;
|
||||||
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.Utilities;
|
||||||
|
using Xamarin.CommunityToolkit.ObjectModel;
|
||||||
using Xamarin.Forms;
|
using Xamarin.Forms;
|
||||||
using ZXing.Client.Result;
|
using ZXing.Client.Result;
|
||||||
|
|
||||||
@@ -79,11 +80,11 @@ namespace Bit.App.Pages
|
|||||||
_keyConnectorService = ServiceContainer.Resolve<IKeyConnectorService>("keyConnectorService");
|
_keyConnectorService = ServiceContainer.Resolve<IKeyConnectorService>("keyConnectorService");
|
||||||
_clipboardService = ServiceContainer.Resolve<IClipboardService>("clipboardService");
|
_clipboardService = ServiceContainer.Resolve<IClipboardService>("clipboardService");
|
||||||
|
|
||||||
GroupedItems = new ExtendedObservableCollection<SettingsPageListGroup>();
|
GroupedItems = new ObservableRangeCollection<ISettingsPageListItem>();
|
||||||
PageTitle = AppResources.Settings;
|
PageTitle = AppResources.Settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ExtendedObservableCollection<SettingsPageListGroup> GroupedItems { get; set; }
|
public ObservableRangeCollection<ISettingsPageListItem> GroupedItems { get; set; }
|
||||||
|
|
||||||
public async Task InitAsync()
|
public async Task InitAsync()
|
||||||
{
|
{
|
||||||
@@ -427,7 +428,7 @@ namespace Bit.App.Pages
|
|||||||
var securityItems = new List<SettingsPageListItem>
|
var securityItems = new List<SettingsPageListItem>
|
||||||
{
|
{
|
||||||
new SettingsPageListItem { Name = AppResources.VaultTimeout, SubLabel = _vaultTimeoutDisplayValue },
|
new SettingsPageListItem { Name = AppResources.VaultTimeout, SubLabel = _vaultTimeoutDisplayValue },
|
||||||
new SettingsPageListItem
|
new SettingsPageListItem
|
||||||
{
|
{
|
||||||
Name = AppResources.VaultTimeoutAction,
|
Name = AppResources.VaultTimeoutAction,
|
||||||
SubLabel = _vaultTimeoutActionDisplayValue
|
SubLabel = _vaultTimeoutActionDisplayValue
|
||||||
@@ -501,7 +502,9 @@ namespace Bit.App.Pages
|
|||||||
new SettingsPageListItem { Name = AppResources.RateTheApp },
|
new SettingsPageListItem { Name = AppResources.RateTheApp },
|
||||||
new SettingsPageListItem { Name = AppResources.DeleteAccount }
|
new SettingsPageListItem { Name = AppResources.DeleteAccount }
|
||||||
};
|
};
|
||||||
GroupedItems.ResetWithRange(new List<SettingsPageListGroup>
|
|
||||||
|
// TODO: improve this. Leaving this as is to reduce error possibility on the hotfix.
|
||||||
|
var settingsListGroupItems = new List<SettingsPageListGroup>()
|
||||||
{
|
{
|
||||||
new SettingsPageListGroup(autofillItems, AppResources.Autofill, doUpper, true),
|
new SettingsPageListGroup(autofillItems, AppResources.Autofill, doUpper, true),
|
||||||
new SettingsPageListGroup(manageItems, AppResources.Manage, doUpper),
|
new SettingsPageListGroup(manageItems, AppResources.Manage, doUpper),
|
||||||
@@ -509,7 +512,50 @@ namespace Bit.App.Pages
|
|||||||
new SettingsPageListGroup(accountItems, AppResources.Account, doUpper),
|
new SettingsPageListGroup(accountItems, AppResources.Account, doUpper),
|
||||||
new SettingsPageListGroup(toolsItems, AppResources.Tools, doUpper),
|
new SettingsPageListGroup(toolsItems, AppResources.Tools, doUpper),
|
||||||
new SettingsPageListGroup(otherItems, AppResources.Other, doUpper)
|
new SettingsPageListGroup(otherItems, AppResources.Other, doUpper)
|
||||||
});
|
};
|
||||||
|
|
||||||
|
// TODO: refactor this
|
||||||
|
if (Device.RuntimePlatform == Device.Android
|
||||||
|
||
|
||||||
|
GroupedItems.Any())
|
||||||
|
{
|
||||||
|
var items = new List<ISettingsPageListItem>();
|
||||||
|
foreach (var itemGroup in settingsListGroupItems)
|
||||||
|
{
|
||||||
|
items.Add(new SettingsPageHeaderListItem(itemGroup.Name));
|
||||||
|
items.AddRange(itemGroup);
|
||||||
|
}
|
||||||
|
|
||||||
|
GroupedItems.ReplaceRange(items);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// HACK: we need this on iOS, so that it doesn't crash when adding coming from an empty list
|
||||||
|
var first = true;
|
||||||
|
var items = new List<ISettingsPageListItem>();
|
||||||
|
foreach (var itemGroup in settingsListGroupItems)
|
||||||
|
{
|
||||||
|
if (!first)
|
||||||
|
{
|
||||||
|
items.Add(new SettingsPageHeaderListItem(itemGroup.Name));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
|
items.AddRange(itemGroup);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (settingsListGroupItems.Any())
|
||||||
|
{
|
||||||
|
GroupedItems.ReplaceRange(new List<ISettingsPageListItem> { new SettingsPageHeaderListItem(settingsListGroupItems[0].Name) });
|
||||||
|
GroupedItems.AddRange(items);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
GroupedItems.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool IncludeLinksWithSubscriptionInfo()
|
private bool IncludeLinksWithSubscriptionInfo()
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
using Bit.App.Abstractions;
|
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.Utilities;
|
using Bit.Core.Utilities;
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Bit.App.Pages
|
namespace Bit.App.Pages
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
private readonly IMessagingService _messagingService;
|
private readonly IMessagingService _messagingService;
|
||||||
private readonly IKeyConnectorService _keyConnectorService;
|
private readonly IKeyConnectorService _keyConnectorService;
|
||||||
|
|
||||||
private NavigationPage _groupingsPage;
|
private NavigationPage _groupingsPage;
|
||||||
private NavigationPage _sendGroupingsPage;
|
private NavigationPage _sendGroupingsPage;
|
||||||
private NavigationPage _generatorPage;
|
private NavigationPage _generatorPage;
|
||||||
@@ -93,7 +93,7 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
CurrentPage = _generatorPage;
|
CurrentPage = _generatorPage;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ResetToSendPage()
|
public void ResetToSendPage()
|
||||||
{
|
{
|
||||||
CurrentPage = _sendGroupingsPage;
|
CurrentPage = _sendGroupingsPage;
|
||||||
|
|||||||
@@ -732,7 +732,6 @@
|
|||||||
<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>
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
using Bit.App.Abstractions;
|
using System.Collections.Generic;
|
||||||
|
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;
|
||||||
@@ -51,7 +51,6 @@ 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.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;
|
||||||
@@ -171,7 +170,6 @@ 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();
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ 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;
|
||||||
@@ -44,7 +43,6 @@ 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[]
|
||||||
{
|
{
|
||||||
@@ -67,7 +65,7 @@ namespace Bit.App.Pages
|
|||||||
new KeyValuePair<UriMatchType?, string>(UriMatchType.Exact, AppResources.Exact),
|
new KeyValuePair<UriMatchType?, string>(UriMatchType.Exact, AppResources.Exact),
|
||||||
new KeyValuePair<UriMatchType?, string>(UriMatchType.Never, AppResources.Never)
|
new KeyValuePair<UriMatchType?, string>(UriMatchType.Never, AppResources.Never)
|
||||||
};
|
};
|
||||||
|
|
||||||
public AddEditPageViewModel()
|
public AddEditPageViewModel()
|
||||||
{
|
{
|
||||||
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
|
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
|
||||||
@@ -168,7 +166,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;
|
||||||
@@ -352,7 +350,7 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
Cipher.Name += " - " + AppResources.Clone;
|
Cipher.Name += " - " + AppResources.Clone;
|
||||||
// If not allowing personal ownership, update cipher's org Id to prompt downstream changes
|
// If not allowing personal ownership, update cipher's org Id to prompt downstream changes
|
||||||
if (Cipher.OrganizationId == null && !AllowPersonal)
|
if (Cipher.OrganizationId == null && !AllowPersonal)
|
||||||
{
|
{
|
||||||
Cipher.OrganizationId = OrganizationId;
|
Cipher.OrganizationId = OrganizationId;
|
||||||
}
|
}
|
||||||
@@ -401,7 +399,7 @@ namespace Bit.App.Pages
|
|||||||
IdentityTitleOptions.FindIndex(k => k.Value == Cipher.Identity.Title);
|
IdentityTitleOptions.FindIndex(k => k.Value == Cipher.Identity.Title);
|
||||||
OwnershipSelectedIndex = string.IsNullOrWhiteSpace(Cipher.OrganizationId) ? 0 :
|
OwnershipSelectedIndex = string.IsNullOrWhiteSpace(Cipher.OrganizationId) ? 0 :
|
||||||
OwnershipOptions.FindIndex(k => k.Value == Cipher.OrganizationId);
|
OwnershipOptions.FindIndex(k => k.Value == Cipher.OrganizationId);
|
||||||
|
|
||||||
// If the selected organization is on Index 0 and we've removed the personal option, force refresh
|
// If the selected organization is on Index 0 and we've removed the personal option, force refresh
|
||||||
if (!AllowPersonal && OwnershipSelectedIndex == 0)
|
if (!AllowPersonal && OwnershipSelectedIndex == 0)
|
||||||
{
|
{
|
||||||
@@ -453,11 +451,11 @@ namespace Bit.App.Pages
|
|||||||
AppResources.Ok);
|
AppResources.Ok);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
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 +535,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,30 +819,13 @@ 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()
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
using Bit.Core.Abstractions;
|
using System;
|
||||||
|
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
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
using Bit.App.Abstractions;
|
using System.Collections.Generic;
|
||||||
|
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
|
||||||
|
|||||||
@@ -15,7 +15,18 @@
|
|||||||
</ContentPage.BindingContext>
|
</ContentPage.BindingContext>
|
||||||
|
|
||||||
<ContentPage.ToolbarItems>
|
<ContentPage.ToolbarItems>
|
||||||
<ToolbarItem Icon="search.png" Clicked="Search_Clicked" />
|
<controls:ExtendedToolbarItem
|
||||||
|
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>
|
||||||
@@ -29,8 +40,28 @@
|
|||||||
ButtonCommand="{Binding BindingContext.CipherOptionsCommand, Source={x:Reference _page}}"
|
ButtonCommand="{Binding BindingContext.CipherOptionsCommand, Source={x:Reference _page}}"
|
||||||
WebsiteIconsEnabled="{Binding BindingContext.WebsiteIconsEnabled, Source={x:Reference _page}}" />
|
WebsiteIconsEnabled="{Binding BindingContext.WebsiteIconsEnabled, Source={x:Reference _page}}" />
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
|
|
||||||
|
<DataTemplate
|
||||||
|
x:Key="headerTemplate"
|
||||||
|
x:DataType="pages:GroupingsPageHeaderListItem">
|
||||||
|
<StackLayout
|
||||||
|
Spacing="0" Padding="0" VerticalOptions="FillAndExpand"
|
||||||
|
StyleClass="list-row-header-container, list-row-header-container-platform">
|
||||||
|
<BoxView
|
||||||
|
StyleClass="list-section-separator-top, list-section-separator-top-platform" />
|
||||||
|
<StackLayout StyleClass="list-row-header, list-row-header-platform">
|
||||||
|
<Label
|
||||||
|
Text="{Binding Title}"
|
||||||
|
StyleClass="list-header, list-header-platform" />
|
||||||
|
<Label
|
||||||
|
Text="{Binding ItemCount}"
|
||||||
|
StyleClass="list-header-sub" />
|
||||||
|
</StackLayout>
|
||||||
|
</StackLayout>
|
||||||
|
</DataTemplate>
|
||||||
|
|
||||||
<pages:GroupingsPageListItemSelector x:Key="listItemDataTemplateSelector"
|
<pages:GroupingsPageListItemSelector x:Key="listItemDataTemplateSelector"
|
||||||
|
HeaderTemplate="{StaticResource headerTemplate}"
|
||||||
CipherTemplate="{StaticResource cipherTemplate}" />
|
CipherTemplate="{StaticResource cipherTemplate}" />
|
||||||
|
|
||||||
<StackLayout x:Key="mainLayout" x:Name="_mainLayout">
|
<StackLayout x:Key="mainLayout" x:Name="_mainLayout">
|
||||||
@@ -38,7 +69,7 @@
|
|||||||
VerticalOptions="CenterAndExpand"
|
VerticalOptions="CenterAndExpand"
|
||||||
Padding="20, 0"
|
Padding="20, 0"
|
||||||
Spacing="20"
|
Spacing="20"
|
||||||
IsVisible="{Binding ShowList, Converter={StaticResource inverseBool}}">
|
IsVisible="{Binding ShowNoData}">
|
||||||
<Label
|
<Label
|
||||||
Text="{Binding NoDataText}"
|
Text="{Binding NoDataText}"
|
||||||
HorizontalTextAlignment="Center"></Label>
|
HorizontalTextAlignment="Center"></Label>
|
||||||
@@ -52,30 +83,9 @@
|
|||||||
ItemsSource="{Binding GroupedItems}"
|
ItemsSource="{Binding GroupedItems}"
|
||||||
VerticalOptions="FillAndExpand"
|
VerticalOptions="FillAndExpand"
|
||||||
ItemTemplate="{StaticResource listItemDataTemplateSelector}"
|
ItemTemplate="{StaticResource listItemDataTemplateSelector}"
|
||||||
IsGrouped="True"
|
|
||||||
SelectionMode="Single"
|
SelectionMode="Single"
|
||||||
SelectionChanged="RowSelected"
|
SelectionChanged="RowSelected"
|
||||||
StyleClass="list, list-platform">
|
StyleClass="list, list-platform" />
|
||||||
|
|
||||||
<CollectionView.GroupHeaderTemplate>
|
|
||||||
<DataTemplate x:DataType="pages:GroupingsPageListGroup">
|
|
||||||
<StackLayout
|
|
||||||
Spacing="0" Padding="0" VerticalOptions="FillAndExpand"
|
|
||||||
StyleClass="list-row-header-container, list-row-header-container-platform">
|
|
||||||
<BoxView
|
|
||||||
StyleClass="list-section-separator-top, list-section-separator-top-platform" />
|
|
||||||
<StackLayout StyleClass="list-row-header, list-row-header-platform">
|
|
||||||
<Label
|
|
||||||
Text="{Binding Name}"
|
|
||||||
StyleClass="list-header, list-header-platform" />
|
|
||||||
<Label
|
|
||||||
Text="{Binding ItemCount}"
|
|
||||||
StyleClass="list-header-sub" />
|
|
||||||
</StackLayout>
|
|
||||||
</StackLayout>
|
|
||||||
</DataTemplate>
|
|
||||||
</CollectionView.GroupHeaderTemplate>
|
|
||||||
</controls:ExtendedCollectionView>
|
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
</ResourceDictionary>
|
</ResourceDictionary>
|
||||||
</ContentPage.Resources>
|
</ContentPage.Resources>
|
||||||
@@ -89,6 +99,8 @@
|
|||||||
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"
|
||||||
@@ -100,6 +112,14 @@
|
|||||||
<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>
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
using Bit.App.Models;
|
using System;
|
||||||
using Bit.App.Resources;
|
|
||||||
using Bit.Core.Abstractions;
|
|
||||||
using Bit.Core.Enums;
|
|
||||||
using Bit.Core.Utilities;
|
|
||||||
using System;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Bit.App.Controls;
|
using Bit.App.Controls;
|
||||||
|
using Bit.App.Models;
|
||||||
using Bit.App.Utilities;
|
using Bit.App.Utilities;
|
||||||
|
using Bit.Core.Abstractions;
|
||||||
|
using Bit.Core.Enums;
|
||||||
|
using Bit.Core.Utilities;
|
||||||
using Xamarin.Forms;
|
using Xamarin.Forms;
|
||||||
|
|
||||||
namespace Bit.App.Pages
|
namespace Bit.App.Pages
|
||||||
@@ -15,7 +14,8 @@ namespace Bit.App.Pages
|
|||||||
public partial class AutofillCiphersPage : BaseContentPage
|
public partial class AutofillCiphersPage : BaseContentPage
|
||||||
{
|
{
|
||||||
private readonly AppOptions _appOptions;
|
private readonly AppOptions _appOptions;
|
||||||
private readonly IPlatformUtilsService _platformUtilsService;
|
private readonly IBroadcasterService _broadcasterService;
|
||||||
|
private readonly ISyncService _syncService;
|
||||||
private readonly IVaultTimeoutService _vaultTimeoutService;
|
private readonly IVaultTimeoutService _vaultTimeoutService;
|
||||||
|
|
||||||
private AutofillCiphersPageViewModel _vm;
|
private AutofillCiphersPageViewModel _vm;
|
||||||
@@ -24,17 +24,23 @@ 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);
|
||||||
|
|
||||||
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
|
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
|
||||||
|
_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();
|
||||||
@@ -43,13 +49,37 @@ 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();
|
||||||
@@ -59,6 +89,11 @@ 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;
|
||||||
@@ -66,6 +101,13 @@ 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;
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
using Bit.App.Abstractions;
|
using System.Collections.Generic;
|
||||||
|
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;
|
||||||
@@ -8,9 +12,7 @@ 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 Xamarin.CommunityToolkit.ObjectModel;
|
||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Xamarin.Forms;
|
using Xamarin.Forms;
|
||||||
|
|
||||||
namespace Bit.App.Pages
|
namespace Bit.App.Pages
|
||||||
@@ -22,8 +24,10 @@ 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 AppOptions _appOptions;
|
private bool _showNoData;
|
||||||
private bool _showList;
|
private bool _showList;
|
||||||
private string _noDataText;
|
private string _noDataText;
|
||||||
private bool _websiteIconsEnabled;
|
private bool _websiteIconsEnabled;
|
||||||
@@ -35,15 +39,30 @@ 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 ExtendedObservableCollection<GroupingsPageListGroup>();
|
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 ExtendedObservableCollection<GroupingsPageListGroup> GroupedItems { get; set; }
|
public bool LoadedOnce { 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
|
||||||
{
|
{
|
||||||
@@ -64,10 +83,9 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
public void Init(AppOptions appOptions)
|
public void Init(AppOptions appOptions)
|
||||||
{
|
{
|
||||||
_appOptions = appOptions;
|
Uri = appOptions?.Uri;
|
||||||
Uri = appOptions.Uri;
|
|
||||||
string name = null;
|
string name = null;
|
||||||
if (Uri.StartsWith(Constants.AndroidAppProtocol))
|
if (Uri?.StartsWith(Constants.AndroidAppProtocol) ?? false)
|
||||||
{
|
{
|
||||||
name = Uri.Substring(Constants.AndroidAppProtocol.Length);
|
name = Uri.Substring(Constants.AndroidAppProtocol.Length);
|
||||||
}
|
}
|
||||||
@@ -86,8 +104,10 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
public async Task LoadAsync()
|
public async Task LoadAsync()
|
||||||
{
|
{
|
||||||
WebsiteIconsEnabled = !(await _stateService.GetDisableFaviconAsync()).GetValueOrDefault();
|
LoadedOnce = true;
|
||||||
ShowList = false;
|
ShowList = false;
|
||||||
|
ShowNoData = false;
|
||||||
|
WebsiteIconsEnabled = !(await _stateService.GetDisableFaviconAsync()).GetValueOrDefault();
|
||||||
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();
|
||||||
@@ -105,8 +125,51 @@ namespace Bit.App.Pages
|
|||||||
new GroupingsPageListGroup(fuzzy, AppResources.PossibleMatchingItems, fuzzy.Count, false,
|
new GroupingsPageListGroup(fuzzy, AppResources.PossibleMatchingItems, fuzzy.Count, false,
|
||||||
!hasMatching));
|
!hasMatching));
|
||||||
}
|
}
|
||||||
GroupedItems.ResetWithRange(groupedItems);
|
|
||||||
|
// TODO: refactor this
|
||||||
|
if (Device.RuntimePlatform == Device.Android
|
||||||
|
||
|
||||||
|
GroupedItems.Any())
|
||||||
|
{
|
||||||
|
var items = new List<IGroupingsPageListItem>();
|
||||||
|
foreach (var itemGroup in groupedItems)
|
||||||
|
{
|
||||||
|
items.Add(new GroupingsPageHeaderListItem(itemGroup.Name, itemGroup.ItemCount));
|
||||||
|
items.AddRange(itemGroup);
|
||||||
|
}
|
||||||
|
|
||||||
|
GroupedItems.ReplaceRange(items);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// HACK: we need this on iOS, so that it doesn't crash when adding coming from an empty list
|
||||||
|
var first = true;
|
||||||
|
var items = new List<IGroupingsPageListItem>();
|
||||||
|
foreach (var itemGroup in groupedItems)
|
||||||
|
{
|
||||||
|
if (!first)
|
||||||
|
{
|
||||||
|
items.Add(new GroupingsPageHeaderListItem(itemGroup.Name, itemGroup.ItemCount));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
|
items.AddRange(itemGroup);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (groupedItems.Any())
|
||||||
|
{
|
||||||
|
GroupedItems.ReplaceRange(new List<IGroupingsPageListItem> { new GroupingsPageHeaderListItem(groupedItems[0].Name, groupedItems[0].ItemCount) });
|
||||||
|
GroupedItems.AddRange(items);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
GroupedItems.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
ShowList = groupedItems.Any();
|
ShowList = groupedItems.Any();
|
||||||
|
ShowNoData = !ShowList;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SelectCipherAsync(CipherView cipher, bool fuzzy)
|
public async Task SelectCipherAsync(CipherView cipher, bool fuzzy)
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
using Bit.App.Abstractions;
|
using System;
|
||||||
|
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,7 @@ namespace Bit.App.Pages
|
|||||||
private CiphersPageViewModel _vm;
|
private CiphersPageViewModel _vm;
|
||||||
private bool _hasFocused;
|
private bool _hasFocused;
|
||||||
|
|
||||||
public CiphersPage(Func<CipherView, bool> filter, bool folder = false, bool collection = false,
|
public CiphersPage(Func<CipherView, bool> filter, string pageTitle = null, 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,21 +25,9 @@ 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 (deleted)
|
if (pageTitle != null)
|
||||||
{
|
{
|
||||||
_vm.PageTitle = AppResources.SearchTrash;
|
_vm.PageTitle = string.Format(AppResources.SearchGroup, pageTitle);
|
||||||
}
|
|
||||||
else if (folder)
|
|
||||||
{
|
|
||||||
_vm.PageTitle = AppResources.SearchFolder;
|
|
||||||
}
|
|
||||||
else if (collection)
|
|
||||||
{
|
|
||||||
_vm.PageTitle = AppResources.SearchCollection;
|
|
||||||
}
|
|
||||||
else if (type)
|
|
||||||
{
|
|
||||||
_vm.PageTitle = AppResources.SearchType;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -53,6 +40,7 @@ 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
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,4 +1,9 @@
|
|||||||
using Bit.App.Abstractions;
|
using System;
|
||||||
|
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;
|
||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
@@ -6,11 +11,6 @@ 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;
|
|
||||||
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
|
||||||
@@ -107,7 +107,7 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
ciphers = await _searchService.SearchCiphersAsync(searchText,
|
ciphers = await _searchService.SearchCiphersAsync(searchText,
|
||||||
Filter ?? (c => c.IsDeleted == Deleted), null, cts.Token);
|
Filter ?? (c => c.IsDeleted == Deleted), null, cts.Token);
|
||||||
cts.Token.ThrowIfCancellationRequested();
|
cts.Token.ThrowIfCancellationRequested();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
using Bit.App.Abstractions;
|
using System.Collections.Generic;
|
||||||
|
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
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -55,30 +55,53 @@
|
|||||||
<DataTemplate x:Key="groupTemplate"
|
<DataTemplate x:Key="groupTemplate"
|
||||||
x:DataType="pages:GroupingsPageListItem">
|
x:DataType="pages:GroupingsPageListItem">
|
||||||
<controls:ExtendedStackLayout Orientation="Horizontal"
|
<controls:ExtendedStackLayout Orientation="Horizontal"
|
||||||
StyleClass="list-row, list-row-platform">
|
StyleClass="list-row, list-row-platform">
|
||||||
<controls:IconLabel Text="{Binding Icon, Mode=OneWay}"
|
<controls:IconLabel Text="{Binding Icon, Mode=OneWay}"
|
||||||
HorizontalOptions="Start"
|
HorizontalOptions="Start"
|
||||||
VerticalOptions="Center"
|
VerticalOptions="Center"
|
||||||
StyleClass="list-icon, list-icon-platform"
|
StyleClass="list-icon, list-icon-platform"
|
||||||
ShouldUpdateFontSizeDynamicallyForAccesibility="True">
|
ShouldUpdateFontSizeDynamicallyForAccesibility="True">
|
||||||
<controls:IconLabel.Effects>
|
<controls:IconLabel.Effects>
|
||||||
<effects:FixedSizeEffect />
|
<effects:FixedSizeEffect />
|
||||||
</controls:IconLabel.Effects>
|
</controls:IconLabel.Effects>
|
||||||
</controls:IconLabel>
|
</controls:IconLabel>
|
||||||
<Label Text="{Binding Name, Mode=OneWay}"
|
<Label Text="{Binding Name, Mode=OneWay}"
|
||||||
LineBreakMode="TailTruncation"
|
LineBreakMode="TailTruncation"
|
||||||
HorizontalOptions="FillAndExpand"
|
HorizontalOptions="FillAndExpand"
|
||||||
VerticalOptions="CenterAndExpand"
|
VerticalOptions="CenterAndExpand"
|
||||||
StyleClass="list-title"/>
|
StyleClass="list-title"/>
|
||||||
<Label Text="{Binding ItemCount, Mode=OneWay}"
|
<Label Text="{Binding ItemCount, Mode=OneWay}"
|
||||||
HorizontalOptions="End"
|
HorizontalOptions="End"
|
||||||
VerticalOptions="CenterAndExpand"
|
VerticalOptions="CenterAndExpand"
|
||||||
HorizontalTextAlignment="End"
|
HorizontalTextAlignment="End"
|
||||||
StyleClass="list-sub"/>
|
StyleClass="list-sub"/>
|
||||||
</controls:ExtendedStackLayout>
|
</controls:ExtendedStackLayout>
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
|
|
||||||
|
<DataTemplate
|
||||||
|
x:Key="headerTemplate"
|
||||||
|
x:DataType="pages:GroupingsPageHeaderListItem">
|
||||||
|
<StackLayout
|
||||||
|
Spacing="0"
|
||||||
|
Padding="0"
|
||||||
|
VerticalOptions="FillAndExpand"
|
||||||
|
StyleClass="list-row-header-container, list-row-header-container-platform">
|
||||||
|
<BoxView
|
||||||
|
StyleClass="list-section-separator-top, list-section-separator-top-platform" />
|
||||||
|
<StackLayout StyleClass="list-row-header, list-row-header-platform">
|
||||||
|
<Label
|
||||||
|
Text="{Binding Title}"
|
||||||
|
StyleClass="list-header, list-header-platform" />
|
||||||
|
<Label
|
||||||
|
Text="{Binding ItemCount}"
|
||||||
|
StyleClass="list-header-sub" />
|
||||||
|
</StackLayout>
|
||||||
|
<BoxView StyleClass="list-section-separator-bottom, list-section-separator-bottom-platform" />
|
||||||
|
</StackLayout>
|
||||||
|
</DataTemplate>
|
||||||
|
|
||||||
<pages:GroupingsPageListItemSelector x:Key="listItemDataTemplateSelector"
|
<pages:GroupingsPageListItemSelector x:Key="listItemDataTemplateSelector"
|
||||||
|
HeaderTemplate="{StaticResource headerTemplate}"
|
||||||
CipherTemplate="{StaticResource cipherTemplate}"
|
CipherTemplate="{StaticResource cipherTemplate}"
|
||||||
GroupTemplate="{StaticResource groupTemplate}" />
|
GroupTemplate="{StaticResource groupTemplate}" />
|
||||||
|
|
||||||
@@ -105,32 +128,9 @@
|
|||||||
ItemsSource="{Binding GroupedItems}"
|
ItemsSource="{Binding GroupedItems}"
|
||||||
VerticalOptions="FillAndExpand"
|
VerticalOptions="FillAndExpand"
|
||||||
ItemTemplate="{StaticResource listItemDataTemplateSelector}"
|
ItemTemplate="{StaticResource listItemDataTemplateSelector}"
|
||||||
IsGrouped="True"
|
|
||||||
SelectionMode="Single"
|
SelectionMode="Single"
|
||||||
SelectionChanged="RowSelected"
|
SelectionChanged="RowSelected"
|
||||||
StyleClass="list, list-platform">
|
StyleClass="list, list-platform" />
|
||||||
|
|
||||||
<CollectionView.GroupHeaderTemplate>
|
|
||||||
<DataTemplate x:DataType="pages:GroupingsPageListGroup">
|
|
||||||
<StackLayout
|
|
||||||
Spacing="0" Padding="0" VerticalOptions="FillAndExpand"
|
|
||||||
StyleClass="list-row-header-container, list-row-header-container-platform">
|
|
||||||
<BoxView
|
|
||||||
StyleClass="list-section-separator-top, list-section-separator-top-platform"
|
|
||||||
IsVisible="{Binding First, Converter={StaticResource inverseBool}}" />
|
|
||||||
<StackLayout StyleClass="list-row-header, list-row-header-platform">
|
|
||||||
<Label
|
|
||||||
Text="{Binding Name}"
|
|
||||||
StyleClass="list-header, list-header-platform" />
|
|
||||||
<Label
|
|
||||||
Text="{Binding ItemCount}"
|
|
||||||
StyleClass="list-header-sub" />
|
|
||||||
</StackLayout>
|
|
||||||
<BoxView StyleClass="list-section-separator-bottom, list-section-separator-bottom-platform" />
|
|
||||||
</StackLayout>
|
|
||||||
</DataTemplate>
|
|
||||||
</CollectionView.GroupHeaderTemplate>
|
|
||||||
</controls:ExtendedCollectionView>
|
|
||||||
</RefreshView>
|
</RefreshView>
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
</ResourceDictionary>
|
</ResourceDictionary>
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ 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, PreviousPageInfo previousPage = null,
|
string collectionId = null, string pageTitle = 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);
|
||||||
@@ -117,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();
|
||||||
@@ -225,8 +225,7 @@ namespace Bit.App.Pages
|
|||||||
await _accountListOverlay.HideAsync();
|
await _accountListOverlay.HideAsync();
|
||||||
if (DoOnce())
|
if (DoOnce())
|
||||||
{
|
{
|
||||||
var page = new CiphersPage(_vm.Filter, _vm.FolderId != null, _vm.CollectionId != null,
|
var page = new CiphersPage(_vm.Filter, _vm.MainPage ? null : _vm.PageTitle, deleted: _vm.Deleted);
|
||||||
_vm.Type != null, deleted: _vm.Deleted);
|
|
||||||
await Navigation.PushModalAsync(new NavigationPage(page));
|
await Navigation.PushModalAsync(new NavigationPage(page));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
namespace Bit.App.Pages
|
||||||
|
{
|
||||||
|
public class GroupingsPageHeaderListItem : IGroupingsPageListItem
|
||||||
|
{
|
||||||
|
public GroupingsPageHeaderListItem(string title, string itemCount)
|
||||||
|
{
|
||||||
|
Title = title;
|
||||||
|
ItemCount = itemCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Title { get; }
|
||||||
|
public string ItemCount { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,7 +5,7 @@ using Bit.Core.Models.View;
|
|||||||
|
|
||||||
namespace Bit.App.Pages
|
namespace Bit.App.Pages
|
||||||
{
|
{
|
||||||
public class GroupingsPageListItem
|
public class GroupingsPageListItem : IGroupingsPageListItem
|
||||||
{
|
{
|
||||||
private string _icon;
|
private string _icon;
|
||||||
private string _name;
|
private string _name;
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user