mirror of
https://github.com/bitwarden/mobile
synced 2025-12-05 23:53:33 +00:00
Compare commits
129 Commits
fedemkr-pa
...
feature/ma
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ca95ada8e8 | ||
|
|
fa022a1a4f | ||
|
|
6011b63958 | ||
|
|
7d79b98bf2 | ||
|
|
bf35d1f2dc | ||
|
|
c01a8f8d93 | ||
|
|
8484b4af30 | ||
|
|
6b9faed45f | ||
|
|
770a1c5dfe | ||
|
|
3a40a4cda8 | ||
|
|
9bcd2e51f7 | ||
|
|
741214a1cc | ||
|
|
aad87dfdce | ||
|
|
8fc1e9a3b9 | ||
|
|
2a8e15146e | ||
|
|
8559d5908e | ||
|
|
f60c4d94fe | ||
|
|
4bf695d18c | ||
|
|
9ccd0834ff | ||
|
|
a806f17d3b | ||
|
|
436a162df2 | ||
|
|
b5dbb9ae5e | ||
|
|
7a5f7c0274 | ||
|
|
5803635f44 | ||
|
|
19c393842f | ||
|
|
15a306490d | ||
|
|
a4a3d31c19 | ||
|
|
922dc683af | ||
|
|
bae1b3e891 | ||
|
|
a5888827c9 | ||
|
|
0348940a12 | ||
|
|
4c2998337d | ||
|
|
7ea86380f4 | ||
|
|
406f4425c8 | ||
|
|
95ca911444 | ||
|
|
fa62510e09 | ||
|
|
65dc73495d | ||
|
|
02a2e41118 | ||
|
|
bd6f8295e7 | ||
|
|
0a0cb7093b | ||
|
|
465e5eff76 | ||
|
|
5b756aaf7a | ||
|
|
d168a7b750 | ||
|
|
7f4bbafe3c | ||
|
|
a5804df6a3 | ||
|
|
bfa2a51608 | ||
|
|
32be08daae | ||
|
|
0a628cc8a8 | ||
|
|
80c424ed03 | ||
|
|
99fb5463cf | ||
|
|
c5d941e1df | ||
|
|
3edfef6169 | ||
|
|
1c8742511a | ||
|
|
8e424d6c05 | ||
|
|
390c303b90 | ||
|
|
443f7282b8 | ||
|
|
50109ee70b | ||
|
|
9ffdfd51cc | ||
|
|
04e409f3c6 | ||
|
|
6c143bad57 | ||
|
|
286e18059a | ||
|
|
553bf9ed0a | ||
|
|
ddb27b52d3 | ||
|
|
6c504aa710 | ||
|
|
62254aef8d | ||
|
|
06a0195a6d | ||
|
|
df2b0b21d5 | ||
|
|
e6b1bab860 | ||
|
|
ce41eb0578 | ||
|
|
1a0b52d644 | ||
|
|
16ada4993c | ||
|
|
3795f3aa17 | ||
|
|
eceb506c77 | ||
|
|
2c7870d660 | ||
|
|
f02b3415a3 | ||
|
|
beda4e9ff8 | ||
|
|
df4d89cd52 | ||
|
|
5f12bb9747 | ||
|
|
5712639492 | ||
|
|
e0a3c301fb | ||
|
|
27306fe353 | ||
|
|
a31f15559f | ||
|
|
0e75f3f5c8 | ||
|
|
363da063fa | ||
|
|
974a571455 | ||
|
|
e0c721098c | ||
|
|
a86f6e3034 | ||
|
|
fe17288b99 | ||
|
|
7324da9d47 | ||
|
|
69aa6fc044 | ||
|
|
e840dc2e30 | ||
|
|
eb25ee5d1b | ||
|
|
840f24dbe5 | ||
|
|
c6309173ba | ||
|
|
946c465f0c | ||
|
|
e90409d842 | ||
|
|
484b5a5160 | ||
|
|
2688209752 | ||
|
|
53e0e55915 | ||
|
|
ca57948d9f | ||
|
|
aaf082faba | ||
|
|
e7aeb08cae | ||
|
|
f177968958 | ||
|
|
f1d59210f9 | ||
|
|
62213c0aaf | ||
|
|
8be8abb8fe | ||
|
|
174acbc558 | ||
|
|
4bcc7c0d71 | ||
|
|
14b2960f30 | ||
|
|
455c3a257c | ||
|
|
8c623a2067 | ||
|
|
3cdf1c2f0e | ||
|
|
ce9503fa0c | ||
|
|
2e4da1b87d | ||
|
|
d63a219272 | ||
|
|
c92cd90a97 | ||
|
|
1dcd3a3daa | ||
|
|
efb8763d3c | ||
|
|
90649d1c8b | ||
|
|
828055791f | ||
|
|
87eebda55f | ||
|
|
7542d1ae1c | ||
|
|
990de4ea4e | ||
|
|
0dbc23f734 | ||
|
|
9f6c8601d3 | ||
|
|
8b7f9b9fb3 | ||
|
|
d17789d5ee | ||
|
|
b8f0747dd4 | ||
|
|
8ef9443b1e |
19
.github/CODEOWNERS
vendored
19
.github/CODEOWNERS
vendored
@@ -1,21 +1,12 @@
|
||||
# Please sort into logical groups with comment headers. Sort groups in order of specificity.
|
||||
# For example, default owners should always be the first group.
|
||||
# Sort lines alphabetically within these groups to avoid accidentally adding duplicates.
|
||||
# Please sort lines alphabetically, this will ensure we don't accidentally add duplicates.
|
||||
#
|
||||
# https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners
|
||||
|
||||
# Default file owners
|
||||
* @bitwarden/dept-development-mobile
|
||||
# The following owners will be the default owners for everything in the repo.
|
||||
# Unless a later match takes precedence
|
||||
# @bitwarden/tech-leads
|
||||
|
||||
# DevOps for Actions and other workflow changes
|
||||
.github/workflows @bitwarden/dept-devops
|
||||
|
||||
# DevOps for Version Bumping
|
||||
src/Android/Properties/AndroidManifest.xml
|
||||
src/iOS.Autofill/Info.plist
|
||||
src/iOS.Extension/Info.plist
|
||||
src/iOS.ShareExtension/Info.plist
|
||||
src/iOS/Info.plist
|
||||
@bitwarden/dept-development-mobile
|
||||
|
||||
## Auth team files ##
|
||||
|
||||
|
||||
17
.github/renovate.json
vendored
17
.github/renovate.json
vendored
@@ -2,21 +2,22 @@
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": [
|
||||
"config:base",
|
||||
"github>bitwarden/renovate-config:pin-actions",
|
||||
":combinePatchMinorReleases",
|
||||
":dependencyDashboard",
|
||||
":maintainLockFilesWeekly",
|
||||
":pinAllExceptPeerDependencies",
|
||||
":prConcurrentLimit10",
|
||||
":rebaseStalePrs",
|
||||
":separateMajorReleases",
|
||||
"group:monorepos",
|
||||
"schedule:weekends"
|
||||
"schedule:weekends",
|
||||
":separateMajorReleases"
|
||||
],
|
||||
"enabledManagers": ["github-actions", "npm", "nuget"],
|
||||
"commitMessagePrefix": "[deps]:",
|
||||
"commitMessageTopic": "{{depName}}",
|
||||
"enabledManagers": ["cargo", "github-actions", "npm", "nuget"],
|
||||
"packageRules": [
|
||||
{
|
||||
"groupName": "cargo minor",
|
||||
"matchManagers": ["cargo"],
|
||||
"matchUpdateTypes": ["minor", "patch"]
|
||||
},
|
||||
{
|
||||
"groupName": "gh minor",
|
||||
"matchManagers": ["github-actions"],
|
||||
@@ -31,6 +32,6 @@
|
||||
"groupName": "nuget minor",
|
||||
"matchManagers": ["nuget"],
|
||||
"matchUpdateTypes": ["minor", "patch"]
|
||||
}
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
29
.github/workflows/_cut_rc.yml
vendored
Normal file
29
.github/workflows/_cut_rc.yml
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
---
|
||||
name: Cut RC Branch
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
|
||||
jobs:
|
||||
cut-rc:
|
||||
name: Cut RC branch
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Checkout Branch
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
with:
|
||||
ref: main
|
||||
|
||||
- name: Check if RC branch exists
|
||||
run: |
|
||||
remote_rc_branch_check=$(git ls-remote --heads origin rc | wc -l)
|
||||
if [[ "${remote_rc_branch_check}" -gt 0 ]]; then
|
||||
echo "Remote RC branch exists."
|
||||
echo "Please delete current RC branch before running again."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Cut RC branch
|
||||
run: |
|
||||
git switch --quiet --create rc
|
||||
git push --quiet --set-upstream origin rc
|
||||
99
.github/workflows/build.yml
vendored
99
.github/workflows/build.yml
vendored
@@ -75,12 +75,12 @@ jobs:
|
||||
nuget-version: 6.4.0
|
||||
|
||||
- name: Set up .NET
|
||||
uses: actions/setup-dotnet@4d6c8fcf3c8f7a60068d26b594648e99df24cee3 # v4.0.0
|
||||
uses: actions/setup-dotnet@3447fd6a9f9e57506b15f895c5b76d3b197dc7c2 # v3.2.0
|
||||
with:
|
||||
dotnet-version: '8.0.x'
|
||||
|
||||
- name: Set up MSBuild
|
||||
uses: microsoft/setup-msbuild@ede762b26a2de8d110bb5a3db4d7e0e080c0e917 # v1.3.3
|
||||
uses: microsoft/setup-msbuild@1ff57057b5cfdc39105cd07a01d78e9b0ea0c14c # v1.3.1
|
||||
|
||||
# This step might be obsolete at some point as .NET MAUI workloads
|
||||
# are starting to come pre-installed on the GH Actions build agents.
|
||||
@@ -153,17 +153,17 @@ jobs:
|
||||
# - name: Verify Format
|
||||
# run: dotnet tool run dotnet-format --check
|
||||
|
||||
- name: Run Core tests
|
||||
run: dotnet test test/Core.Test/Core.Test.csproj --logger "trx;LogFileName=test-results.trx" /p:CustomConstants=UT
|
||||
# - name: Run Core tests
|
||||
# run: dotnet test test/Core.Test/Core.Test.csproj --logger "trx;LogFileName=test-results.trx"
|
||||
|
||||
- name: Report test results
|
||||
uses: dorny/test-reporter@eaa763f6ffc21c7a37837f56cd5f9737f27fc6c8 # v1.8.0
|
||||
if: always()
|
||||
with:
|
||||
name: Test Results
|
||||
path: "**/test-results.trx"
|
||||
reporter: dotnet-trx
|
||||
fail-on-error: true
|
||||
#- name: Report test results
|
||||
# uses: dorny/test-reporter@c9b3d0e2bd2a4e96aaf424dbaa31c46b42318226 # v1.6.0
|
||||
# if: always()
|
||||
# with:
|
||||
# name: Test Results
|
||||
# path: "**/test-results.trx"
|
||||
# reporter: dotnet-trx
|
||||
# fail-on-error: true
|
||||
|
||||
- name: Build Play Store publisher
|
||||
if: ${{ matrix.variant == 'prod' }}
|
||||
@@ -226,7 +226,7 @@ jobs:
|
||||
|
||||
- name: Upload Prod .aab artifact
|
||||
if: ${{ matrix.variant == 'prod' }}
|
||||
uses: actions/upload-artifact@26f96dfa697d77e81fd5907df203aa23a56210a8 # v4.3.0
|
||||
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
|
||||
with:
|
||||
name: com.x8bit.bitwarden.aab
|
||||
path: ./com.x8bit.bitwarden.aab
|
||||
@@ -234,7 +234,7 @@ jobs:
|
||||
|
||||
- name: Upload Prod .apk artifact
|
||||
if: ${{ matrix.variant == 'prod' }}
|
||||
uses: actions/upload-artifact@26f96dfa697d77e81fd5907df203aa23a56210a8 # v4.3.0
|
||||
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
|
||||
with:
|
||||
name: com.x8bit.bitwarden.apk
|
||||
path: ./com.x8bit.bitwarden.apk
|
||||
@@ -242,7 +242,7 @@ jobs:
|
||||
|
||||
- name: Upload Other .apk artifact
|
||||
if: ${{ matrix.variant != 'prod' }}
|
||||
uses: actions/upload-artifact@26f96dfa697d77e81fd5907df203aa23a56210a8 # v4.3.0
|
||||
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
|
||||
with:
|
||||
name: com.x8bit.bitwarden.${{ matrix.variant }}.apk
|
||||
path: ./com.x8bit.bitwarden.${{ matrix.variant }}.apk
|
||||
@@ -262,7 +262,7 @@ jobs:
|
||||
|
||||
- name: Upload .apk sha file for prod
|
||||
if: ${{ matrix.variant == 'prod' }}
|
||||
uses: actions/upload-artifact@26f96dfa697d77e81fd5907df203aa23a56210a8 # v4.3.0
|
||||
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
|
||||
with:
|
||||
name: bw-android-apk-sha256.txt
|
||||
path: ./bw-android-apk-sha256.txt
|
||||
@@ -270,7 +270,7 @@ jobs:
|
||||
|
||||
- name: Upload .apk sha file for other
|
||||
if: ${{ matrix.variant != 'prod' }}
|
||||
uses: actions/upload-artifact@26f96dfa697d77e81fd5907df203aa23a56210a8 # v4.3.0
|
||||
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
|
||||
with:
|
||||
name: bw-android-${{ matrix.variant }}-apk-sha256.txt
|
||||
path: ./bw-android-${{ matrix.variant }}-apk-sha256.txt
|
||||
@@ -310,7 +310,7 @@ jobs:
|
||||
dotnet-version: '8.0.x'
|
||||
|
||||
- name: Set up MSBuild
|
||||
uses: microsoft/setup-msbuild@ede762b26a2de8d110bb5a3db4d7e0e080c0e917 # v1.3.3
|
||||
uses: microsoft/setup-msbuild@1ff57057b5cfdc39105cd07a01d78e9b0ea0c14c # v1.3.1
|
||||
|
||||
# This step might be obsolete at some point as .NET MAUI workloads
|
||||
# are starting to come pre-installed on the GH Actions build agents.
|
||||
@@ -366,6 +366,13 @@ jobs:
|
||||
|
||||
$androidManifest = $($env:GITHUB_WORKSPACE + "/${{ env.android_manifest_path }}");
|
||||
|
||||
# Write-Output "########################################"
|
||||
# Write-Output "##### Clean Android and App"
|
||||
# Write-Output "########################################"
|
||||
|
||||
# msbuild "$($androidPath)" "/t:Clean" "/p:Configuration=FDroid"
|
||||
# msbuild "$($appPath)" "/t:Clean" "/p:Configuration=FDroid"
|
||||
|
||||
Write-Output "########################################"
|
||||
Write-Output "##### Backup project files"
|
||||
Write-Output "########################################"
|
||||
@@ -385,6 +392,42 @@ jobs:
|
||||
|
||||
$xml.Save($androidManifest);
|
||||
|
||||
# Write-Output "########################################"
|
||||
# Write-Output "##### Uninstall from App.csproj"
|
||||
# Write-Output "########################################"
|
||||
|
||||
# $xml=New-Object XML;
|
||||
# $xml.Load($appPath);
|
||||
|
||||
# $ns=New-Object System.Xml.XmlNamespaceManager($xml.NameTable);
|
||||
# $ns.AddNamespace("ns", $xml.DocumentElement.NamespaceURI);
|
||||
|
||||
# $firebaseNode=$xml.SelectSingleNode(`
|
||||
# "/ns:Project/ns:ItemGroup/ns:PackageReference[@Include='Xamarin.Firebase.Messaging']", $ns);
|
||||
# $firebaseNode.ParentNode.RemoveChild($firebaseNode);
|
||||
|
||||
# $daggerNode=$xml.SelectSingleNode(`
|
||||
# "/ns:Project/ns:ItemGroup/ns:PackageReference[@Include='Xamarin.Google.Dagger']", $ns);
|
||||
# $daggerNode.ParentNode.RemoveChild($daggerNode);
|
||||
|
||||
# $safetyNetNode=$xml.SelectSingleNode(`
|
||||
# "/ns:Project/ns:ItemGroup/ns:PackageReference[@Include='Xamarin.GooglePlayServices.SafetyNet']", $ns);
|
||||
# $safetyNetNode.ParentNode.RemoveChild($safetyNetNode);
|
||||
|
||||
# $xml.Save($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);
|
||||
|
||||
- name: Restore packages
|
||||
run: dotnet restore
|
||||
|
||||
@@ -422,7 +465,7 @@ jobs:
|
||||
Copy-Item $signedApkPath $signedApkDestPath
|
||||
|
||||
- name: Upload F-Droid .apk artifact
|
||||
uses: actions/upload-artifact@26f96dfa697d77e81fd5907df203aa23a56210a8 # v4.3.0
|
||||
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
|
||||
with:
|
||||
name: com.x8bit.bitwarden-fdroid.apk
|
||||
path: ./com.x8bit.bitwarden-fdroid.apk
|
||||
@@ -434,7 +477,7 @@ jobs:
|
||||
-t sha256 | Out-File -Encoding ASCII ./bw-fdroid-apk-sha256.txt
|
||||
|
||||
- name: Upload F-Droid sha file
|
||||
uses: actions/upload-artifact@26f96dfa697d77e81fd5907df203aa23a56210a8 # v4.3.0
|
||||
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
|
||||
with:
|
||||
name: bw-fdroid-apk-sha256.txt
|
||||
path: ./bw-fdroid-apk-sha256.txt
|
||||
@@ -452,7 +495,7 @@ jobs:
|
||||
- name: Set XCode version
|
||||
uses: maxim-lobanov/setup-xcode@60606e260d2fc5762a71e64e74b2174e8ea3c8bd # v1.6.0
|
||||
with:
|
||||
xcode-version: 15.1
|
||||
xcode-version: 15.0.1
|
||||
|
||||
- name: Setup NuGet
|
||||
uses: nuget/setup-nuget@296fd3ccf8528660c91106efefe2364482f86d6f # v1.2.0
|
||||
@@ -482,7 +525,7 @@ jobs:
|
||||
submodules: 'true'
|
||||
|
||||
- name: Login to Azure - CI Subscription
|
||||
uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0
|
||||
uses: Azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 # v1.4.6
|
||||
with:
|
||||
creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
|
||||
|
||||
@@ -529,8 +572,6 @@ jobs:
|
||||
echo "##### Setting CFBundleVersion $BUILD_NUMBER"
|
||||
echo "########################################"
|
||||
|
||||
echo "### CFBundleVersion $BUILD_NUMBER" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
perl -0777 -pi.bak -e 's/<key>CFBundleVersion<\/key>\s*<string>1<\/string>/<key>CFBundleVersion<\/key>\n\t<string>'"$BUILD_NUMBER"'<\/string>/' ./${{ env.ios_folder_path }}/Info.plist
|
||||
perl -0777 -pi.bak -e 's/<key>CFBundleVersion<\/key>\s*<string>1<\/string>/<key>CFBundleVersion<\/key>\n\t<string>'"$BUILD_NUMBER"'<\/string>/' ./src/iOS.Extension/Info.plist
|
||||
perl -0777 -pi.bak -e 's/<key>CFBundleVersion<\/key>\s*<string>1<\/string>/<key>CFBundleVersion<\/key>\n\t<string>'"$BUILD_NUMBER"'<\/string>/' ./src/iOS.Autofill/Info.plist
|
||||
@@ -664,7 +705,7 @@ jobs:
|
||||
cp -r -v $WATCH_ARCHIVE_DSYMS_PATH $WATCH_DSYMS_EXPORT_PATH
|
||||
|
||||
- name: Upload App Store .ipa & dSYMs artifacts
|
||||
uses: actions/upload-artifact@26f96dfa697d77e81fd5907df203aa23a56210a8 # v4.3.0
|
||||
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
|
||||
with:
|
||||
name: Bitwarden iOS
|
||||
path: |
|
||||
@@ -673,7 +714,7 @@ jobs:
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Upload .app file for Automation CI
|
||||
uses: actions/upload-artifact@26f96dfa697d77e81fd5907df203aa23a56210a8 # v4.3.0
|
||||
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
|
||||
with:
|
||||
name: ${{ env.app_ci_output_filename }}.app.zip
|
||||
path: ./bitwarden-export/${{ env.app_ci_output_filename }}.app.zip
|
||||
@@ -758,7 +799,7 @@ jobs:
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
|
||||
- name: Login to Azure - CI Subscription
|
||||
uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0
|
||||
uses: Azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 # v1.4.6
|
||||
with:
|
||||
creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
|
||||
|
||||
@@ -770,7 +811,7 @@ jobs:
|
||||
secrets: "crowdin-api-token"
|
||||
|
||||
- name: Upload Sources
|
||||
uses: crowdin/github-action@97bef4fd3f1b853eb105bc99b8d0d563760e024c # v1.17.0
|
||||
uses: crowdin/github-action@965d501f160af7b1f88aed4c29154b0caf1e94b9 # v1.9.0
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
CROWDIN_API_TOKEN: ${{ steps.retrieve-secrets.outputs.crowdin-api-token }}
|
||||
@@ -817,7 +858,7 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: Login to Azure - CI Subscription
|
||||
uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0
|
||||
uses: Azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 # v1.4.6
|
||||
if: failure()
|
||||
with:
|
||||
creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
|
||||
|
||||
6
.github/workflows/crowdin-pull.yml
vendored
6
.github/workflows/crowdin-pull.yml
vendored
@@ -15,10 +15,10 @@ jobs:
|
||||
_CROWDIN_PROJECT_ID: "269690"
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
|
||||
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
|
||||
|
||||
- name: Login to Azure - CI Subscription
|
||||
uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0
|
||||
uses: Azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 # v1.4.6
|
||||
with:
|
||||
creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
|
||||
|
||||
@@ -30,7 +30,7 @@ jobs:
|
||||
secrets: "crowdin-api-token, github-gpg-private-key, github-gpg-private-key-passphrase"
|
||||
|
||||
- name: Download translations
|
||||
uses: crowdin/github-action@97bef4fd3f1b853eb105bc99b8d0d563760e024c # v1.17.0
|
||||
uses: crowdin/github-action@965d501f160af7b1f88aed4c29154b0caf1e94b9 # v1.9.0
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
CROWDIN_API_TOKEN: ${{ steps.retrieve-secrets.outputs.crowdin-api-token }}
|
||||
|
||||
14
.github/workflows/release.yml
vendored
14
.github/workflows/release.yml
vendored
@@ -38,7 +38,7 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
|
||||
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
|
||||
|
||||
- name: Check Release Version
|
||||
id: version
|
||||
@@ -68,7 +68,7 @@ jobs:
|
||||
|
||||
- name: Download all artifacts
|
||||
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
|
||||
uses: dawidd6/action-download-artifact@e7466d1a7587ed14867642c2ca74b5bcc1e19a2d # v3.0.0
|
||||
uses: dawidd6/action-download-artifact@246dbf436b23d7c49e21a7ab8204ca9ecd1fe615 # v2.27.0
|
||||
with:
|
||||
workflow: build.yml
|
||||
workflow_conclusion: success
|
||||
@@ -76,7 +76,7 @@ jobs:
|
||||
|
||||
- name: Dry Run - Download all artifacts
|
||||
if: ${{ github.event.inputs.release_type == 'Dry Run' }}
|
||||
uses: dawidd6/action-download-artifact@e7466d1a7587ed14867642c2ca74b5bcc1e19a2d # v3.0.0
|
||||
uses: dawidd6/action-download-artifact@246dbf436b23d7c49e21a7ab8204ca9ecd1fe615 # v2.27.0
|
||||
with:
|
||||
workflow: build.yml
|
||||
workflow_conclusion: success
|
||||
@@ -126,11 +126,11 @@ jobs:
|
||||
if: inputs.fdroid_publish
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
|
||||
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
|
||||
|
||||
- name: Download F-Droid .apk artifact
|
||||
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
|
||||
uses: dawidd6/action-download-artifact@e7466d1a7587ed14867642c2ca74b5bcc1e19a2d # v3.0.0
|
||||
uses: dawidd6/action-download-artifact@246dbf436b23d7c49e21a7ab8204ca9ecd1fe615 # v2.27.0
|
||||
with:
|
||||
workflow: build.yml
|
||||
workflow_conclusion: success
|
||||
@@ -139,7 +139,7 @@ jobs:
|
||||
|
||||
- name: Dry Run - Download F-Droid .apk artifact
|
||||
if: ${{ github.event.inputs.release_type == 'Dry Run' }}
|
||||
uses: dawidd6/action-download-artifact@e7466d1a7587ed14867642c2ca74b5bcc1e19a2d # v3.0.0
|
||||
uses: dawidd6/action-download-artifact@246dbf436b23d7c49e21a7ab8204ca9ecd1fe615 # v2.27.0
|
||||
with:
|
||||
workflow: build.yml
|
||||
workflow_conclusion: success
|
||||
@@ -147,7 +147,7 @@ jobs:
|
||||
name: com.x8bit.bitwarden-fdroid.apk
|
||||
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@b39b52d1213e96004bfcb1c61a8a6fa8ab84f3e8 # v4.0.1
|
||||
uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3.8.1
|
||||
with:
|
||||
node-version: '16.x'
|
||||
|
||||
|
||||
2
.github/workflows/stale-bot.yml
vendored
2
.github/workflows/stale-bot.yml
vendored
@@ -11,7 +11,7 @@ jobs:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- name: 'Run stale action'
|
||||
uses: actions/stale@28ca1036281a5e5922ead5184a1bbf96e5fc984e # v9.0.0
|
||||
uses: actions/stale@f7176fd3007623b69d27091f9b9d4ab7995f0a06 # v5.2.1
|
||||
with:
|
||||
stale-issue-label: 'needs-reply'
|
||||
stale-pr-label: 'needs-changes'
|
||||
|
||||
37
.github/workflows/version-auto-bump.yml
vendored
37
.github/workflows/version-auto-bump.yml
vendored
@@ -1,5 +1,5 @@
|
||||
---
|
||||
name: Auto Bump Mobile Version
|
||||
name: Version Auto Bump
|
||||
|
||||
on:
|
||||
push:
|
||||
@@ -7,12 +7,14 @@ on:
|
||||
- v**
|
||||
|
||||
jobs:
|
||||
bump-version:
|
||||
name: Bump Mobile Version
|
||||
setup:
|
||||
name: "Setup"
|
||||
runs-on: ubuntu-22.04
|
||||
outputs:
|
||||
version_number: ${{ steps.version.outputs.new-version }}
|
||||
steps:
|
||||
- name: Checkout Branch
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
|
||||
|
||||
- name: Calculate bumped version
|
||||
id: version
|
||||
@@ -27,23 +29,12 @@ jobs:
|
||||
NEW_PATCH=$((CURR_PATCH+1))
|
||||
NEW_VER=$CURR_MAJOR.$NEW_PATCH
|
||||
echo "New Version: $NEW_VER"
|
||||
echo "new_version=$NEW_VER" >> $GITHUB_OUTPUT
|
||||
echo "new-version=$NEW_VER" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Login to Azure - CI Subscription
|
||||
uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0
|
||||
with:
|
||||
creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
|
||||
|
||||
- name: Retrieve bot secrets
|
||||
id: retrieve-bot-secrets
|
||||
uses: bitwarden/gh-actions/get-keyvault-secrets@main
|
||||
with:
|
||||
keyvault: bitwarden-ci
|
||||
secrets: "github-pat-bitwarden-devops-bot-repo-scope"
|
||||
|
||||
- name: "Bump version to ${{ steps.version.outputs.new_version }}"
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.retrieve-bot-secrets.outputs.github-pat-bitwarden-devops-bot-repo-scope }}
|
||||
run: |
|
||||
echo '{"cut_rc_branch": "false", "version_number": "${{ steps.version.outputs.new_version }}"}' | \
|
||||
gh workflow run version-bump.yml --json --repo bitwarden/mobile
|
||||
trigger_version_bump:
|
||||
name: Bump version to ${{ needs.setup.outputs.version_number }}
|
||||
needs: setup
|
||||
uses: ./.github/workflows/version-bump.yml
|
||||
with:
|
||||
version_number: ${{ needs.setup.outputs.version_number }}
|
||||
secrets: inherit
|
||||
55
.github/workflows/version-bump.yml
vendored
55
.github/workflows/version-bump.yml
vendored
@@ -19,7 +19,7 @@ jobs:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Login to Azure - CI Subscription
|
||||
uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0
|
||||
uses: Azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 # v1.4.7
|
||||
with:
|
||||
creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
|
||||
|
||||
@@ -33,22 +33,13 @@ jobs:
|
||||
github-pat-bitwarden-devops-bot-repo-scope"
|
||||
|
||||
- name: Checkout Branch
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0
|
||||
with:
|
||||
ref: main
|
||||
|
||||
- name: Check if RC branch exists
|
||||
if: ${{ inputs.cut_rc_branch == true }}
|
||||
run: |
|
||||
remote_rc_branch_check=$(git ls-remote --heads origin rc | wc -l)
|
||||
if [[ "${remote_rc_branch_check}" -gt 0 ]]; then
|
||||
echo "Remote RC branch exists."
|
||||
echo "Please delete current RC branch before running again."
|
||||
exit 1
|
||||
fi
|
||||
repository: bitwarden/mobile
|
||||
|
||||
- name: Import GPG key
|
||||
uses: crazy-max/ghaction-import-gpg@01dd5d3ca463c7f10f7f4f7b4f177225ac661ee4 # v6.1.0
|
||||
uses: crazy-max/ghaction-import-gpg@d6f3f49f3345e29369fe57596a3ca8f94c4d2ca7 # v5.4.0
|
||||
with:
|
||||
gpg_private_key: ${{ steps.retrieve-secrets.outputs.github-gpg-private-key }}
|
||||
passphrase: ${{ steps.retrieve-secrets.outputs.github-gpg-private-key-passphrase }}
|
||||
@@ -72,7 +63,7 @@ jobs:
|
||||
CURRENT_VERSION=$(xmllint --xpath '
|
||||
string(/manifest/@*[local-name()="versionName"
|
||||
and namespace-uri()="http://schemas.android.com/apk/res/android"])
|
||||
' src/App/Platforms/Android/AndroidManifest.xml)
|
||||
' src/Android/Properties/AndroidManifest.xml)
|
||||
|
||||
# Error if version has not changed.
|
||||
if [[ "$NEW_VERSION" == "$CURRENT_VERSION" ]]; then
|
||||
@@ -93,7 +84,7 @@ jobs:
|
||||
uses: bitwarden/gh-actions/version-bump@main
|
||||
with:
|
||||
version: ${{ inputs.version_number }}
|
||||
file_path: "src/App/Platforms/Android/AndroidManifest.xml"
|
||||
file_path: "src/Android/Properties/AndroidManifest.xml"
|
||||
|
||||
- name: Bump Version - iOS.Autofill
|
||||
uses: bitwarden/gh-actions/version-bump@main
|
||||
@@ -117,7 +108,7 @@ jobs:
|
||||
uses: bitwarden/gh-actions/version-bump@main
|
||||
with:
|
||||
version: ${{ inputs.version_number }}
|
||||
file_path: "src/App/Platforms/iOS/Info.plist"
|
||||
file_path: "src/iOS/Info.plist"
|
||||
|
||||
- name: Setup git
|
||||
run: |
|
||||
@@ -182,32 +173,8 @@ jobs:
|
||||
run: gh pr merge $PR_NUMBER --squash --auto --delete-branch
|
||||
|
||||
cut_rc:
|
||||
name: Cut RC branch
|
||||
needs: bump_version
|
||||
name: Cut RC Branch
|
||||
if: ${{ inputs.cut_rc_branch == true }}
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Checkout Branch
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
with:
|
||||
ref: main
|
||||
|
||||
- name: Verify version has been updated
|
||||
env:
|
||||
NEW_VERSION: ${{ inputs.version_number }}
|
||||
run: |
|
||||
# Wait for version to change.
|
||||
do
|
||||
echo "Waiting for version to be updated..."
|
||||
git pull --force
|
||||
CURRENT_VERSION=$(xmllint --xpath '
|
||||
string(/manifest/@*[local-name()="versionName"
|
||||
and namespace-uri()="http://schemas.android.com/apk/res/android"])
|
||||
' src/App/Platforms/Android/AndroidManifest.xml)
|
||||
sleep 10
|
||||
done while [[ "$NEW_VERSION" != "$CURRENT_VERSION" ]]
|
||||
|
||||
- name: Cut RC branch
|
||||
run: |
|
||||
git switch --quiet --create rc
|
||||
git push --quiet --set-upstream origin rc
|
||||
needs: bump_version
|
||||
uses: ./.github/workflows/_cut_rc.yml
|
||||
secrets: inherit
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<MauiVersion>8.0.4-nightly.*</MauiVersion>
|
||||
<ReleaseCodesignProvision>Automatic:AppStore</ReleaseCodesignProvision>
|
||||
<ReleaseCodesignKey>iPhone Distribution</ReleaseCodesignKey>
|
||||
<IncludeBitwardeniOSExtensions>True</IncludeBitwardeniOSExtensions>
|
||||
<IncludeBitwardenWatchOSApp>True</IncludeBitwardenWatchOSApp>
|
||||
<Argon2IdLoadMtouchExtraArgs>-gcc_flags "-L$(ProjectDir)../../lib/ios -largon2 -force_load $(ProjectDir)../../lib/ios/libargon2.a"</Argon2IdLoadMtouchExtraArgs>
|
||||
|
||||
<!-- Uncomment this when Unit Testing-->
|
||||
<!-- <CustomConstants>UT</CustomConstants> -->
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@@ -1,4 +1,4 @@
|
||||
[](https://github.com/bitwarden/mobile/actions/workflows/build.yml?query=branch:main)
|
||||
[](https://github.com/bitwarden/mobile/actions/workflows/build.yml?query=branch:master)
|
||||
[](https://crowdin.com/project/bitwarden-mobile)
|
||||
[](https://gitter.im/bitwarden/Lobby)
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
<a href="https://play.google.com/store/apps/details?id=com.x8bit.bitwarden" target="_blank"><img alt="Get it on Google Play" src="https://imgur.com/YQzmZi9.png" width="153" height="46"></a> <a href="https://mobileapp.bitwarden.com/fdroid/" target="_blank"><img alt="Get it on F-Droid" src="https://i.imgur.com/HDicnzz.png" width="154" height="46"></a> <a href="https://itunes.apple.com/us/app/bitwarden-free-password-manager/id1137397744?mt=8" target="_blank"><img src="https://imgur.com/GdGqPMY.png" width="135" height="40"></a>
|
||||
|
||||
The Bitwarden mobile application is written in C# using .NET MAUI.
|
||||
The Bitwarden mobile application is written in C# with Xamarin Android, Xamarin iOS, and Xamarin Forms.
|
||||
|
||||
<img src="https://raw.githubusercontent.com/bitwarden/brand/master/screenshots/mobile-android-myvault.png" alt="" width="325" height="650" /> <img src="https://raw.githubusercontent.com/bitwarden/brand/master/screenshots/mobile-ios-myvault.png" alt="" width="300" height="650" />
|
||||
|
||||
@@ -20,6 +20,6 @@ Interested in contributing in a big way? Consider joining our team! We're hiring
|
||||
|
||||
# Contribute
|
||||
|
||||
Code contributions are welcome! Please commit any pull requests against the `main` branch. Learn more about how to contribute by reading the [Contributing Guidelines](https://contributing.bitwarden.com/contributing/). Check out the [Contributing Documentation](https://contributing.bitwarden.com/) for how to get started with your first contribution.
|
||||
Code contributions are welcome! Please commit any pull requests against the `master` branch. Learn more about how to contribute by reading the [Contributing Guidelines](https://contributing.bitwarden.com/contributing/). Check out the [Contributing Documentation](https://contributing.bitwarden.com/) for how to get started with your first contribution.
|
||||
|
||||
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.
|
||||
|
||||
@@ -5,7 +5,7 @@ VisualStudioVersion = 17.8.34112.27
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "App", "src\App\App.csproj", "{971FDF07-E288-4239-B47A-E9E7E912193B}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Core", "src\Core\Core.csproj", "{11DBC05E-F8B4-49ED-AAC9-96D92336D21C}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Core", "src\Core\Core.csproj", "{11DBC05E-F8B4-49ED-AAC9-96D92336D21C}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "iOS.Core", "src\iOS.Core\iOS.Core.csproj", "{E71F3053-056C-4381-9638-048ED73BDFF6}"
|
||||
EndProject
|
||||
@@ -15,14 +15,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "iOS.ShareExtension", "src\i
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "iOS.Autofill", "src\iOS.Autofill\iOS.Autofill.csproj", "{83449CC4-1F76-4CFE-92B1-D2E13A62506F}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{BB702EBD-3B79-4ECA-A2A6-1237B07F0AF0}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{B972BBFA-917F-4A10-B07E-B89CFEC6BBDC}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Core.Test", "test\Core.Test\Core.Test.csproj", "{137959BD-073B-4EC7-8ED5-31D73FA7DBC6}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Common", "test\Common\Common.csproj", "{1AC5ED7F-301E-4B3C-ACDE-C0EADFA5AE44}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@@ -35,7 +27,6 @@ Global
|
||||
AppStore|iPhone = AppStore|iPhone
|
||||
Ad-Hoc|iPhoneSimulator = Ad-Hoc|iPhoneSimulator
|
||||
Ad-Hoc|iPhone = Ad-Hoc|iPhone
|
||||
FDroid|Any CPU = FDroid|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{971FDF07-E288-4239-B47A-E9E7E912193B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
@@ -60,8 +51,6 @@ Global
|
||||
{971FDF07-E288-4239-B47A-E9E7E912193B}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||
{971FDF07-E288-4239-B47A-E9E7E912193B}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
|
||||
{971FDF07-E288-4239-B47A-E9E7E912193B}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
|
||||
{971FDF07-E288-4239-B47A-E9E7E912193B}.FDroid|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{971FDF07-E288-4239-B47A-E9E7E912193B}.FDroid|Any CPU.Build.0 = Debug|Any CPU
|
||||
{11DBC05E-F8B4-49ED-AAC9-96D92336D21C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{11DBC05E-F8B4-49ED-AAC9-96D92336D21C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{11DBC05E-F8B4-49ED-AAC9-96D92336D21C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
@@ -82,8 +71,6 @@ Global
|
||||
{11DBC05E-F8B4-49ED-AAC9-96D92336D21C}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||
{11DBC05E-F8B4-49ED-AAC9-96D92336D21C}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
|
||||
{11DBC05E-F8B4-49ED-AAC9-96D92336D21C}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
|
||||
{11DBC05E-F8B4-49ED-AAC9-96D92336D21C}.FDroid|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{11DBC05E-F8B4-49ED-AAC9-96D92336D21C}.FDroid|Any CPU.Build.0 = Debug|Any CPU
|
||||
{E71F3053-056C-4381-9638-048ED73BDFF6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{E71F3053-056C-4381-9638-048ED73BDFF6}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{E71F3053-056C-4381-9638-048ED73BDFF6}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
@@ -104,8 +91,6 @@ Global
|
||||
{E71F3053-056C-4381-9638-048ED73BDFF6}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||
{E71F3053-056C-4381-9638-048ED73BDFF6}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
|
||||
{E71F3053-056C-4381-9638-048ED73BDFF6}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
|
||||
{E71F3053-056C-4381-9638-048ED73BDFF6}.FDroid|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{E71F3053-056C-4381-9638-048ED73BDFF6}.FDroid|Any CPU.Build.0 = Debug|Any CPU
|
||||
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
@@ -126,8 +111,6 @@ Global
|
||||
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU
|
||||
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.Ad-Hoc|iPhone.Build.0 = Release|Any CPU
|
||||
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.FDroid|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.FDroid|Any CPU.Build.0 = Debug|Any CPU
|
||||
{F8C3F648-EA5A-4719-8005-85D1690B1655}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{F8C3F648-EA5A-4719-8005-85D1690B1655}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{F8C3F648-EA5A-4719-8005-85D1690B1655}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
@@ -148,8 +131,6 @@ Global
|
||||
{F8C3F648-EA5A-4719-8005-85D1690B1655}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||
{F8C3F648-EA5A-4719-8005-85D1690B1655}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU
|
||||
{F8C3F648-EA5A-4719-8005-85D1690B1655}.Ad-Hoc|iPhone.Build.0 = Release|Any CPU
|
||||
{F8C3F648-EA5A-4719-8005-85D1690B1655}.FDroid|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{F8C3F648-EA5A-4719-8005-85D1690B1655}.FDroid|Any CPU.Build.0 = Debug|Any CPU
|
||||
{83449CC4-1F76-4CFE-92B1-D2E13A62506F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{83449CC4-1F76-4CFE-92B1-D2E13A62506F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{83449CC4-1F76-4CFE-92B1-D2E13A62506F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
@@ -162,52 +143,6 @@ Global
|
||||
{83449CC4-1F76-4CFE-92B1-D2E13A62506F}.AppStore|iPhone.Build.0 = Release|Any CPU
|
||||
{83449CC4-1F76-4CFE-92B1-D2E13A62506F}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
||||
{83449CC4-1F76-4CFE-92B1-D2E13A62506F}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||
{83449CC4-1F76-4CFE-92B1-D2E13A62506F}.FDroid|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{83449CC4-1F76-4CFE-92B1-D2E13A62506F}.FDroid|Any CPU.Build.0 = Debug|Any CPU
|
||||
{137959BD-073B-4EC7-8ED5-31D73FA7DBC6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{137959BD-073B-4EC7-8ED5-31D73FA7DBC6}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{137959BD-073B-4EC7-8ED5-31D73FA7DBC6}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{137959BD-073B-4EC7-8ED5-31D73FA7DBC6}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{137959BD-073B-4EC7-8ED5-31D73FA7DBC6}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
||||
{137959BD-073B-4EC7-8ED5-31D73FA7DBC6}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||
{137959BD-073B-4EC7-8ED5-31D73FA7DBC6}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
||||
{137959BD-073B-4EC7-8ED5-31D73FA7DBC6}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
|
||||
{137959BD-073B-4EC7-8ED5-31D73FA7DBC6}.Debug|iPhone.ActiveCfg = Debug|Any CPU
|
||||
{137959BD-073B-4EC7-8ED5-31D73FA7DBC6}.Debug|iPhone.Build.0 = Debug|Any CPU
|
||||
{137959BD-073B-4EC7-8ED5-31D73FA7DBC6}.Release|iPhone.ActiveCfg = Release|Any CPU
|
||||
{137959BD-073B-4EC7-8ED5-31D73FA7DBC6}.Release|iPhone.Build.0 = Release|Any CPU
|
||||
{137959BD-073B-4EC7-8ED5-31D73FA7DBC6}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
||||
{137959BD-073B-4EC7-8ED5-31D73FA7DBC6}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||
{137959BD-073B-4EC7-8ED5-31D73FA7DBC6}.AppStore|iPhone.ActiveCfg = Release|Any CPU
|
||||
{137959BD-073B-4EC7-8ED5-31D73FA7DBC6}.AppStore|iPhone.Build.0 = Release|Any CPU
|
||||
{137959BD-073B-4EC7-8ED5-31D73FA7DBC6}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
||||
{137959BD-073B-4EC7-8ED5-31D73FA7DBC6}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||
{137959BD-073B-4EC7-8ED5-31D73FA7DBC6}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
|
||||
{137959BD-073B-4EC7-8ED5-31D73FA7DBC6}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
|
||||
{137959BD-073B-4EC7-8ED5-31D73FA7DBC6}.FDroid|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{137959BD-073B-4EC7-8ED5-31D73FA7DBC6}.FDroid|Any CPU.Build.0 = Debug|Any CPU
|
||||
{1AC5ED7F-301E-4B3C-ACDE-C0EADFA5AE44}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{1AC5ED7F-301E-4B3C-ACDE-C0EADFA5AE44}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{1AC5ED7F-301E-4B3C-ACDE-C0EADFA5AE44}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{1AC5ED7F-301E-4B3C-ACDE-C0EADFA5AE44}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{1AC5ED7F-301E-4B3C-ACDE-C0EADFA5AE44}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
||||
{1AC5ED7F-301E-4B3C-ACDE-C0EADFA5AE44}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||
{1AC5ED7F-301E-4B3C-ACDE-C0EADFA5AE44}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
||||
{1AC5ED7F-301E-4B3C-ACDE-C0EADFA5AE44}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
|
||||
{1AC5ED7F-301E-4B3C-ACDE-C0EADFA5AE44}.Debug|iPhone.ActiveCfg = Debug|Any CPU
|
||||
{1AC5ED7F-301E-4B3C-ACDE-C0EADFA5AE44}.Debug|iPhone.Build.0 = Debug|Any CPU
|
||||
{1AC5ED7F-301E-4B3C-ACDE-C0EADFA5AE44}.Release|iPhone.ActiveCfg = Release|Any CPU
|
||||
{1AC5ED7F-301E-4B3C-ACDE-C0EADFA5AE44}.Release|iPhone.Build.0 = Release|Any CPU
|
||||
{1AC5ED7F-301E-4B3C-ACDE-C0EADFA5AE44}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
||||
{1AC5ED7F-301E-4B3C-ACDE-C0EADFA5AE44}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||
{1AC5ED7F-301E-4B3C-ACDE-C0EADFA5AE44}.AppStore|iPhone.ActiveCfg = Release|Any CPU
|
||||
{1AC5ED7F-301E-4B3C-ACDE-C0EADFA5AE44}.AppStore|iPhone.Build.0 = Release|Any CPU
|
||||
{1AC5ED7F-301E-4B3C-ACDE-C0EADFA5AE44}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
||||
{1AC5ED7F-301E-4B3C-ACDE-C0EADFA5AE44}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||
{1AC5ED7F-301E-4B3C-ACDE-C0EADFA5AE44}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
|
||||
{1AC5ED7F-301E-4B3C-ACDE-C0EADFA5AE44}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
|
||||
{1AC5ED7F-301E-4B3C-ACDE-C0EADFA5AE44}.FDroid|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{1AC5ED7F-301E-4B3C-ACDE-C0EADFA5AE44}.FDroid|Any CPU.Build.0 = Debug|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
@@ -220,14 +155,4 @@ Global
|
||||
$0.DotNetNamingPolicy = $1
|
||||
$1.DirectoryNamespaceAssociation = PrefixedHierarchical
|
||||
EndGlobalSection
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
{971FDF07-E288-4239-B47A-E9E7E912193B} = {B972BBFA-917F-4A10-B07E-B89CFEC6BBDC}
|
||||
{11DBC05E-F8B4-49ED-AAC9-96D92336D21C} = {B972BBFA-917F-4A10-B07E-B89CFEC6BBDC}
|
||||
{83449CC4-1F76-4CFE-92B1-D2E13A62506F} = {B972BBFA-917F-4A10-B07E-B89CFEC6BBDC}
|
||||
{E71F3053-056C-4381-9638-048ED73BDFF6} = {B972BBFA-917F-4A10-B07E-B89CFEC6BBDC}
|
||||
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545} = {B972BBFA-917F-4A10-B07E-B89CFEC6BBDC}
|
||||
{F8C3F648-EA5A-4719-8005-85D1690B1655} = {B972BBFA-917F-4A10-B07E-B89CFEC6BBDC}
|
||||
{137959BD-073B-4EC7-8ED5-31D73FA7DBC6} = {BB702EBD-3B79-4ECA-A2A6-1237B07F0AF0}
|
||||
{1AC5ED7F-301E-4B3C-ACDE-C0EADFA5AE44} = {BB702EBD-3B79-4ECA-A2A6-1237B07F0AF0}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<packageSources>
|
||||
<add key="MAUI Nightly builds" value="https://pkgs.dev.azure.com/xamarin/public/_packaging/maui-nightly/nuget/v3/index.json" />
|
||||
</packageSources>
|
||||
</configuration>
|
||||
2
package-lock.json
generated
2
package-lock.json
generated
@@ -8,7 +8,7 @@
|
||||
"name": "bitwarden-mobile",
|
||||
"version": "0.0.0",
|
||||
"devDependencies": {
|
||||
"gh-pages": "3.2.3"
|
||||
"gh-pages": "^3.2.3"
|
||||
}
|
||||
},
|
||||
"node_modules/array-union": {
|
||||
|
||||
@@ -6,6 +6,6 @@
|
||||
"clean:l10n": "git push origin --delete l10n_master"
|
||||
},
|
||||
"devDependencies": {
|
||||
"gh-pages": "3.2.3"
|
||||
"gh-pages": "^3.2.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,20 +56,18 @@
|
||||
<CodesignKey>iPhone Developer</CodesignKey>
|
||||
<CodesignEntitlements>Platforms\iOS\Entitlements.plist</CodesignEntitlements>
|
||||
<UseInterpreter>true</UseInterpreter>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(RuntimeIdentifier)'=='Debug|net8.0-ios|iossimulator-x64'">
|
||||
<MtouchExtraArgs>$(Argon2IdLoadMtouchExtraArgs)</MtouchExtraArgs>
|
||||
<!--TODO: add argon2id load when library is built with the corresponding architecture for iOS Simulator-->
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(RuntimeIdentifier)'=='Debug|net8.0-ios|ios-arm64'">
|
||||
<MtouchExtraArgs>$(Argon2IdLoadMtouchExtraArgs)</MtouchExtraArgs>
|
||||
<MtouchExtraArgs>-gcc_flags "-L$(ProjectDir)../../lib/ios -largon2 -force_load $(ProjectDir)../../lib/ios/libargon2.a"</MtouchExtraArgs>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|net8.0-ios|AnyCPU'">
|
||||
<CreatePackage>false</CreatePackage>
|
||||
<CodesignProvision>$(ReleaseCodesignProvision)</CodesignProvision>
|
||||
<CodesignKey>$(ReleaseCodesignKey)</CodesignKey>
|
||||
<CodesignProvision>Automatic:AppStore</CodesignProvision>
|
||||
<CodesignKey>iPhone Distribution</CodesignKey>
|
||||
<CodesignEntitlements>Platforms\iOS\Entitlements.plist</CodesignEntitlements>
|
||||
<UseInterpreter>true</UseInterpreter>
|
||||
<MtouchExtraArgs>$(Argon2IdLoadMtouchExtraArgs)</MtouchExtraArgs>
|
||||
<MtouchExtraArgs>-gcc_flags "-L$(ProjectDir)../../lib/ios -largon2 -force_load $(ProjectDir)../../lib/ios/libargon2.a"</MtouchExtraArgs>
|
||||
</PropertyGroup>
|
||||
<ItemGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">
|
||||
<!--This is needed for PCLCrypto to work correctly-->
|
||||
@@ -157,15 +155,6 @@
|
||||
<BundleResource Include="Platforms\Android\Resources\drawable-hdpi\logo_white_legacy.png" />
|
||||
<BundleResource Include="Platforms\Android\Resources\mipmap-xhdpi\ic_launcher.png" />
|
||||
<BundleResource Include="Platforms\Android\Resources\mipmap-xhdpi\ic_launcher_round.png" />
|
||||
<BundleResource Include="Platforms\iOS\Resources\logo.png" />
|
||||
<BundleResource Include="Platforms\iOS\Resources\logo_white%402x.png" />
|
||||
<BundleResource Include="Platforms\iOS\Resources\more_vert%402x.png" />
|
||||
<BundleResource Include="Platforms\iOS\Resources\logo_white%403x.png" />
|
||||
<BundleResource Include="Platforms\iOS\Resources\logo%403x.png" />
|
||||
<BundleResource Include="Platforms\iOS\Resources\more_vert%403x.png" />
|
||||
<BundleResource Include="Platforms\iOS\Resources\more_vert.png" />
|
||||
<BundleResource Include="Platforms\iOS\Resources\logo_white.png" />
|
||||
<BundleResource Include="Platforms\iOS\Resources\logo%402x.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\iOS.Core\iOS.Core.csproj" Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'" />
|
||||
@@ -205,12 +194,15 @@
|
||||
<MauiImage Include="Resources\plus.svg" TintColor="#FFFFFFFF">
|
||||
<BaseSize>24,24</BaseSize>
|
||||
</MauiImage>
|
||||
<MauiImage Include="Resources\search.svg" TintColor="#FFFFFFFF">
|
||||
<BaseSize>24,24</BaseSize>
|
||||
</MauiImage>
|
||||
<MauiImage Include="Resources\send.svg">
|
||||
<BaseSize>24,24</BaseSize>
|
||||
</MauiImage>
|
||||
<MauiImage Include="Resources\yubikey.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios' AND '$(IncludeBitwardeniOSExtensions)' == 'True'">
|
||||
<ItemGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">
|
||||
<ProjectReference Include="..\iOS.Autofill\iOS.Autofill.csproj">
|
||||
<IsAppExtension>true</IsAppExtension>
|
||||
<IsWatchApp>false</IsWatchApp>
|
||||
@@ -224,15 +216,15 @@
|
||||
<IsWatchApp>false</IsWatchApp>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Condition="'$(TargetFramework)'=='net8.0-ios' AND '$(IncludeBitwardenWatchOSApp)' == 'True'">
|
||||
<WatchAppBuildPath Condition=" '$(Configuration)' == 'Debug' ">$(Home)/Library/Developer/Xcode/DerivedData/bitwarden-acgkbpwvmebfiofokotvoerzkqcl/Build/Products</WatchAppBuildPath>
|
||||
<WatchAppBuildPath Condition=" '$(Configuration)' != 'Debug' ">$([System.IO.Path]::GetFullPath('$(MSBuildProjectDirectory)\..'))/watchOS/bitwarden.xcarchive/Products/Applications/bitwarden.app/Watch</WatchAppBuildPath>
|
||||
<WatchAppBundle>Bitwarden.app</WatchAppBundle>
|
||||
<WatchAppConfiguration Condition="'$(RuntimeIdentifier)'!='ios-arm64'">watchsimulator</WatchAppConfiguration>
|
||||
<WatchAppConfiguration Condition="'$(RuntimeIdentifier)'=='ios-arm64'">watchos</WatchAppConfiguration>
|
||||
<WatchAppBundleFullPath Condition=" '$(Configuration)' == 'Debug' ">$(WatchAppBuildPath)/$(Configuration)-$(WatchAppConfiguration)/$(WatchAppBundle)</WatchAppBundleFullPath>
|
||||
<WatchAppBundleFullPath Condition=" '$(Configuration)' != 'Debug' ">$(WatchAppBuildPath)/$(WatchAppBundle)</WatchAppBundleFullPath>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(TargetFramework)'=='net8.0-ios'">
|
||||
<WatchAppBuildPath Condition=" '$(Configuration)' == 'Debug' ">$(Home)/Library/Developer/Xcode/DerivedData/bitwarden-acgkbpwvmebfiofokotvoerzkqcl/Build/Products</WatchAppBuildPath>
|
||||
<WatchAppBuildPath Condition=" '$(Configuration)' != 'Debug' ">$([System.IO.Path]::GetFullPath('$(MSBuildProjectDirectory)\..'))/watchOS/bitwarden.xcarchive/Products/Applications/bitwarden.app/Watch</WatchAppBuildPath>
|
||||
<WatchAppBundle>Bitwarden.app</WatchAppBundle>
|
||||
<WatchAppConfiguration Condition="'$(RuntimeIdentifier)'!='ios-arm64'">watchsimulator</WatchAppConfiguration>
|
||||
<WatchAppConfiguration Condition="'$(RuntimeIdentifier)'=='ios-arm64'">watchos</WatchAppConfiguration>
|
||||
<WatchAppBundleFullPath Condition=" '$(Configuration)' == 'Debug' ">$(WatchAppBuildPath)/$(Configuration)-$(WatchAppConfiguration)/$(WatchAppBundle)</WatchAppBundleFullPath>
|
||||
<WatchAppBundleFullPath Condition=" '$(Configuration)' != 'Debug' ">$(WatchAppBuildPath)/$(WatchAppBundle)</WatchAppBundleFullPath>
|
||||
</PropertyGroup>
|
||||
<ItemGroup Condition="'$(TargetFramework)'=='net8.0-ios' AND Exists('$(WatchAppBundleFullPath)') ">
|
||||
<_ResolvedWatchAppReferences Include="$(WatchAppBundleFullPath)" />
|
||||
</ItemGroup>
|
||||
@@ -246,15 +238,4 @@
|
||||
<GoogleServicesJson Include="Platforms\Android\google-services.json" />
|
||||
<GoogleServicesJson Include="Platforms\Android\google-services.json.enc" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Remove="Platforms\iOS\Resources\logo.png" />
|
||||
<None Remove="Platforms\iOS\Resources\logo_white%402x.png" />
|
||||
<None Remove="Platforms\iOS\Resources\more_vert%402x.png" />
|
||||
<None Remove="Platforms\iOS\Resources\logo_white%403x.png" />
|
||||
<None Remove="Platforms\iOS\Resources\logo%403x.png" />
|
||||
<None Remove="Platforms\iOS\Resources\more_vert%403x.png" />
|
||||
<None Remove="Platforms\iOS\Resources\more_vert.png" />
|
||||
<None Remove="Platforms\iOS\Resources\logo_white.png" />
|
||||
<None Remove="Platforms\iOS\Resources\logo%402x.png" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
25
src/App/Handlers/HybridWebViewHandler.cs
Normal file
25
src/App/Handlers/HybridWebViewHandler.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
#if IOS || MACCATALYST
|
||||
using PlatformView = WebKit.WKWebView;
|
||||
#elif ANDROID
|
||||
using PlatformView = Android.Webkit.WebView;
|
||||
#elif (NETSTANDARD || !PLATFORM) || (NET6_0_OR_GREATER && !IOS && !ANDROID)
|
||||
using PlatformView = System.Object;
|
||||
#endif
|
||||
|
||||
using Bit.App.Controls;
|
||||
using Microsoft.Maui.Handlers;
|
||||
|
||||
namespace Bit.App.Handlers
|
||||
{
|
||||
public partial class HybridWebViewHandler
|
||||
{
|
||||
public static PropertyMapper<HybridWebView, HybridWebViewHandler> PropertyMapper = new PropertyMapper<HybridWebView, HybridWebViewHandler>(ViewHandler.ViewMapper)
|
||||
{
|
||||
[nameof(HybridWebView.Uri)] = MapUri
|
||||
};
|
||||
|
||||
public HybridWebViewHandler() : base(PropertyMapper)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@
|
||||
},
|
||||
handlers =>
|
||||
{
|
||||
handlers.AddHandler(typeof(Bit.App.Controls.HybridWebView), typeof(Bit.App.Handlers.HybridWebViewHandler));
|
||||
#if ANDROID
|
||||
Bit.App.Handlers.EntryHandlerMappings.Setup();
|
||||
Bit.App.Handlers.EditorHandlerMappings.Setup();
|
||||
@@ -27,7 +28,6 @@
|
||||
Bit.App.Handlers.ButtonHandlerMappings.Setup();
|
||||
Bit.App.Handlers.ToolbarHandlerMappings.Setup();
|
||||
|
||||
handlers.AddHandler(typeof(Bit.App.Controls.HybridWebView), typeof(Bit.App.Handlers.HybridWebViewHandler));
|
||||
handlers.AddHandler(typeof(Bit.App.Pages.TabsPage), typeof(Bit.App.Handlers.CustomTabbedPageHandler));
|
||||
handlers.AddHandler(typeof(Bit.App.Controls.ExtendedDatePicker), typeof(Bit.App.Handlers.ExtendedDatePickerHandler));
|
||||
#else
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?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="2024.2.1" android:installLocation="internalOnly" package="com.x8bit.bitwarden">
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:versionCode="1" android:versionName="2023.12.1" android:installLocation="internalOnly" package="com.x8bit.bitwarden">
|
||||
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="34" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.NFC" />
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using AndroidX.AppCompat.View.Menu;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Utilities;
|
||||
using Google.Android.Material.BottomNavigation;
|
||||
using Microsoft.Maui.Handlers;
|
||||
@@ -91,17 +90,7 @@ namespace Bit.App.Handlers
|
||||
if(e.Item is MenuItemImpl item)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"Tab '{item.Title}' was reselected so we'll PopToRoot.");
|
||||
MainThread.BeginInvokeOnMainThread(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await _tabbedPage.CurrentPage.Navigation.PopToRootAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LoggerHelper.LogEvenIfCantBeResolved(ex);
|
||||
}
|
||||
});
|
||||
MainThread.BeginInvokeOnMainThread(async () => await _tabbedPage.CurrentPage.Navigation.PopToRootAsync());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,19 +6,10 @@ using AWebkit = Android.Webkit;
|
||||
|
||||
namespace Bit.App.Handlers
|
||||
{
|
||||
public class HybridWebViewHandler : ViewHandler<HybridWebView, AWebkit.WebView>
|
||||
public partial class HybridWebViewHandler : ViewHandler<HybridWebView, AWebkit.WebView>
|
||||
{
|
||||
private const string JSFunction = "function invokeCSharpAction(data){jsBridge.invokeAction(data);}";
|
||||
|
||||
public static PropertyMapper<HybridWebView, HybridWebViewHandler> PropertyMapper = new PropertyMapper<HybridWebView, HybridWebViewHandler>(ViewHandler.ViewMapper)
|
||||
{
|
||||
[nameof(HybridWebView.Uri)] = MapUri
|
||||
};
|
||||
|
||||
public HybridWebViewHandler() : base(PropertyMapper)
|
||||
{
|
||||
}
|
||||
|
||||
public HybridWebViewHandler([NotNull] IPropertyMapper mapper, CommandMapper commandMapper = null) : base(mapper, commandMapper)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -2,10 +2,7 @@
|
||||
using Android.Graphics.Drawables;
|
||||
using Android.OS;
|
||||
using AndroidX.Core.Content.Resources;
|
||||
using AndroidX.Core.Graphics;
|
||||
using Bit.App.Droid.Utilities;
|
||||
using Bit.App.Utilities;
|
||||
using Microsoft.Maui.Platform;
|
||||
|
||||
namespace Bit.App.Handlers
|
||||
{
|
||||
@@ -40,31 +37,6 @@ namespace Bit.App.Handlers
|
||||
};
|
||||
handler.PlatformView.ThumbTintList = new ColorStateList(thumbStates, thumbColors);
|
||||
});
|
||||
|
||||
Microsoft.Maui.Handlers.SwitchHandler.Mapper.AppendToMapping(nameof(ISwitch.TrackColor), (handler, mauiSwitch) =>
|
||||
{
|
||||
var trackStates = new[]
|
||||
{
|
||||
new[] { Android.Resource.Attribute.StateChecked }, // checked
|
||||
new[] { -Android.Resource.Attribute.StateChecked }, // unchecked
|
||||
};
|
||||
|
||||
var selectedColor = ColorUtils.BlendARGB(ThemeHelpers.SwitchOnColor.ToArgb(), Colors.Black.ToPlatform().ToArgb(), 0.5f);
|
||||
var unselectedColor = ColorUtils.BlendARGB(ThemeHelpers.SwitchThumbColor.ToArgb(), Colors.Black.ToPlatform().ToArgb(), 0.7f);
|
||||
if (ThemeManager.UsingLightTheme)
|
||||
{
|
||||
selectedColor = ColorUtils.BlendARGB(ThemeHelpers.SwitchOnColor.ToArgb(), Colors.White.ToPlatform().ToArgb(), 0.7f);
|
||||
unselectedColor = ColorUtils.BlendARGB(ThemeHelpers.SwitchThumbColor.ToArgb(), Colors.Black.ToPlatform().ToArgb(), 0.3f);
|
||||
}
|
||||
|
||||
var trackColors = new int[]
|
||||
{
|
||||
selectedColor,
|
||||
unselectedColor
|
||||
};
|
||||
|
||||
handler.PlatformView.TrackTintList = new ColorStateList(trackStates, trackColors);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,11 +74,6 @@ namespace Bit.Droid
|
||||
// this needs to be called here before base.OnCreate(...)
|
||||
Intent?.Validate();
|
||||
|
||||
//We need to get and set the Options before calling OnCreate as that will "trigger" CreateWindow on App.xaml.cs
|
||||
_appOptions = GetOptions();
|
||||
//This does not replace existing Options in App.xaml.cs if it exists already. It only updates properties in Options related with Autofill/CreateSend/etc..
|
||||
((Bit.App.App)Microsoft.Maui.Controls.Application.Current).SetAndroidOptions(_appOptions);
|
||||
|
||||
base.OnCreate(savedInstanceState);
|
||||
|
||||
_deviceActionService.SetScreenCaptureAllowedAsync().FireAndForget(_ =>
|
||||
@@ -94,6 +89,7 @@ namespace Bit.Droid
|
||||
toplayout.FilterTouchesWhenObscured = true;
|
||||
}
|
||||
|
||||
_appOptions = GetOptions();
|
||||
CreateNotificationChannel();
|
||||
DisableAndroidFontScale();
|
||||
|
||||
|
||||
@@ -158,7 +158,7 @@ namespace Bit.Droid
|
||||
var autofillHandler = new AutofillHandler(stateService, messagingService, clipboardService,
|
||||
platformUtilsService, new LazyResolve<IEventService>());
|
||||
var cryptoFunctionService = new PclCryptoFunctionService(cryptoPrimitiveService);
|
||||
var cryptoService = new CryptoService(stateService, cryptoFunctionService, logger);
|
||||
var cryptoService = new CryptoService(stateService, cryptoFunctionService);
|
||||
var biometricService = new BiometricService(stateService, cryptoService);
|
||||
var userPinService = new UserPinService(stateService, cryptoService);
|
||||
var passwordRepromptService = new MobilePasswordRepromptService(platformUtilsService, cryptoService, stateService);
|
||||
|
||||
@@ -15,7 +15,6 @@ using CoreNFC;
|
||||
using Foundation;
|
||||
using Microsoft.Maui.Platform;
|
||||
using UIKit;
|
||||
using UserNotifications;
|
||||
using WatchConnectivity;
|
||||
|
||||
namespace Bit.iOS
|
||||
@@ -42,78 +41,77 @@ namespace Bit.iOS
|
||||
private IStateService _stateService;
|
||||
private IEventService _eventService;
|
||||
|
||||
private readonly LazyResolve<IDeepLinkContext> _deepLinkContext = new LazyResolve<IDeepLinkContext>();
|
||||
private LazyResolve<IDeepLinkContext> _deepLinkContext = new LazyResolve<IDeepLinkContext>();
|
||||
|
||||
public override bool FinishedLaunching(UIApplication app, NSDictionary options)
|
||||
{
|
||||
try
|
||||
InitApp();
|
||||
|
||||
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
|
||||
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
|
||||
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
|
||||
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
|
||||
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
|
||||
_eventService = ServiceContainer.Resolve<IEventService>("eventService");
|
||||
|
||||
//LoadApplication(new App.App(null));
|
||||
//iOSCoreHelpers.AppearanceAdjustments();
|
||||
//ZXing.Net.Mobile.Forms.iOS.Platform.Init();
|
||||
|
||||
ConnectToWatchIfNeededAsync().FireAndForget();
|
||||
|
||||
_broadcasterService.Subscribe(nameof(AppDelegate), async (message) =>
|
||||
{
|
||||
InitApp();
|
||||
|
||||
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
|
||||
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
|
||||
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
|
||||
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
|
||||
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
|
||||
_eventService = ServiceContainer.Resolve<IEventService>("eventService");
|
||||
|
||||
ConnectToWatchIfNeededAsync().FireAndForget();
|
||||
|
||||
_broadcasterService.Subscribe(nameof(AppDelegate), async (message) =>
|
||||
try
|
||||
{
|
||||
try
|
||||
if (message.Command == "startEventTimer")
|
||||
{
|
||||
if (message.Command == "startEventTimer")
|
||||
StartEventTimer();
|
||||
}
|
||||
else if (message.Command == "stopEventTimer")
|
||||
{
|
||||
var task = StopEventTimerAsync();
|
||||
}
|
||||
else if (message.Command is ThemeManager.UPDATED_THEME_MESSAGE_KEY)
|
||||
{
|
||||
MainThread.BeginInvokeOnMainThread(() =>
|
||||
{
|
||||
StartEventTimer();
|
||||
iOSCoreHelpers.AppearanceAdjustments();
|
||||
});
|
||||
}
|
||||
else if (message.Command == "listenYubiKeyOTP")
|
||||
{
|
||||
iOSCoreHelpers.ListenYubiKey((bool)message.Data, _deviceActionService, _nfcSession, _nfcDelegate);
|
||||
}
|
||||
else if (message.Command == "unlocked")
|
||||
{
|
||||
var needsAutofillReplacement = await _storageService.GetAsync<bool?>(
|
||||
Core.Constants.AutofillNeedsIdentityReplacementKey);
|
||||
if (needsAutofillReplacement.GetValueOrDefault())
|
||||
{
|
||||
await ASHelpers.ReplaceAllIdentities();
|
||||
}
|
||||
else if (message.Command == "stopEventTimer")
|
||||
}
|
||||
else if (message.Command == "showAppExtension")
|
||||
{
|
||||
MainThread.BeginInvokeOnMainThread(() => ShowAppExtension((ExtensionPageViewModel)message.Data));
|
||||
}
|
||||
else if (message.Command == "syncCompleted")
|
||||
{
|
||||
if (message.Data is Dictionary<string, object> data && data.ContainsKey("successfully"))
|
||||
{
|
||||
var task = StopEventTimerAsync();
|
||||
}
|
||||
else if (message.Command is ThemeManager.UPDATED_THEME_MESSAGE_KEY)
|
||||
{
|
||||
await MainThread.InvokeOnMainThreadAsync(() =>
|
||||
{
|
||||
iOSCoreHelpers.AppearanceAdjustments();
|
||||
});
|
||||
}
|
||||
else if (message.Command == "listenYubiKeyOTP" && message.Data is bool listen)
|
||||
{
|
||||
iOSCoreHelpers.ListenYubiKey(listen, _deviceActionService, _nfcSession, _nfcDelegate);
|
||||
}
|
||||
else if (message.Command == "unlocked")
|
||||
{
|
||||
var needsAutofillReplacement = await _storageService.GetAsync<bool?>(
|
||||
Core.Constants.AutofillNeedsIdentityReplacementKey);
|
||||
if (needsAutofillReplacement.GetValueOrDefault())
|
||||
var success = data["successfully"] as bool?;
|
||||
if (success.GetValueOrDefault() && _deviceActionService.SystemMajorVersion() >= 12)
|
||||
{
|
||||
await ASHelpers.ReplaceAllIdentities();
|
||||
}
|
||||
}
|
||||
else if (message.Command == "showAppExtension")
|
||||
}
|
||||
else if (message.Command == "addedCipher" || message.Command == "editedCipher" ||
|
||||
message.Command == "restoredCipher")
|
||||
{
|
||||
if (_deviceActionService.SystemMajorVersion() >= 12)
|
||||
{
|
||||
await MainThread.InvokeOnMainThreadAsync(() => ShowAppExtension((ExtensionPageViewModel)message.Data));
|
||||
}
|
||||
else if (message.Command == "syncCompleted")
|
||||
{
|
||||
if (message.Data is Dictionary<string, object> data && data.TryGetValue("successfully", out var value))
|
||||
{
|
||||
var success = value as bool?;
|
||||
if (success.GetValueOrDefault() && _deviceActionService.SystemMajorVersion() >= 12)
|
||||
{
|
||||
await ASHelpers.ReplaceAllIdentities();
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (message.Command == "addedCipher" || message.Command == "editedCipher" ||
|
||||
message.Command == "restoredCipher")
|
||||
{
|
||||
if (!UIDevice.CurrentDevice.CheckSystemVersion(12, 0))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (await ASHelpers.IdentitiesCanIncremental())
|
||||
{
|
||||
var cipherId = message.Data as string;
|
||||
@@ -131,13 +129,11 @@ namespace Bit.iOS
|
||||
}
|
||||
await ASHelpers.ReplaceAllIdentities();
|
||||
}
|
||||
else if (message.Command == "deletedCipher" || message.Command == "softDeletedCipher")
|
||||
}
|
||||
else if (message.Command == "deletedCipher" || message.Command == "softDeletedCipher")
|
||||
{
|
||||
if (_deviceActionService.SystemMajorVersion() >= 12)
|
||||
{
|
||||
if (!UIDevice.CurrentDevice.CheckSystemVersion(12, 0))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (await ASHelpers.IdentitiesCanIncremental())
|
||||
{
|
||||
var identity = ASHelpers.ToCredentialIdentity(
|
||||
@@ -152,145 +148,97 @@ namespace Bit.iOS
|
||||
}
|
||||
await ASHelpers.ReplaceAllIdentities();
|
||||
}
|
||||
else if (message.Command == "logout" && UIDevice.CurrentDevice.CheckSystemVersion(12, 0))
|
||||
}
|
||||
else if (message.Command == "logout")
|
||||
{
|
||||
if (_deviceActionService.SystemMajorVersion() >= 12)
|
||||
{
|
||||
await ASCredentialIdentityStore.SharedStore?.RemoveAllCredentialIdentitiesAsync();
|
||||
}
|
||||
else if ((message.Command == "softDeletedCipher" || message.Command == "restoredCipher")
|
||||
&& UIDevice.CurrentDevice.CheckSystemVersion(12, 0))
|
||||
}
|
||||
else if ((message.Command == "softDeletedCipher" || message.Command == "restoredCipher")
|
||||
&& _deviceActionService.SystemMajorVersion() >= 12)
|
||||
{
|
||||
await ASHelpers.ReplaceAllIdentities();
|
||||
}
|
||||
else if (message.Command == AppHelpers.VAULT_TIMEOUT_ACTION_CHANGED_MESSAGE_COMMAND)
|
||||
{
|
||||
var timeoutAction = await _stateService.GetVaultTimeoutActionAsync();
|
||||
if (timeoutAction == VaultTimeoutAction.Logout)
|
||||
{
|
||||
await ASCredentialIdentityStore.SharedStore?.RemoveAllCredentialIdentitiesAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
await ASHelpers.ReplaceAllIdentities();
|
||||
}
|
||||
else if (message.Command == AppHelpers.VAULT_TIMEOUT_ACTION_CHANGED_MESSAGE_COMMAND)
|
||||
{
|
||||
var timeoutAction = await _stateService.GetVaultTimeoutActionAsync();
|
||||
if (timeoutAction == VaultTimeoutAction.Logout)
|
||||
{
|
||||
if (UIDevice.CurrentDevice.CheckSystemVersion(12, 0))
|
||||
{
|
||||
await ASCredentialIdentityStore.SharedStore?.RemoveAllCredentialIdentitiesAsync();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
await ASHelpers.ReplaceAllIdentities();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LoggerHelper.LogEvenIfCantBeResolved(ex);
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LoggerHelper.LogEvenIfCantBeResolved(ex);
|
||||
}
|
||||
});
|
||||
|
||||
var finishedLaunching = base.FinishedLaunching(app, options);
|
||||
var finishedLaunching = base.FinishedLaunching(app, options);
|
||||
|
||||
ThemeManager.SetTheme(Microsoft.Maui.Controls.Application.Current.Resources);
|
||||
iOSCoreHelpers.AppearanceAdjustments();
|
||||
ThemeManager.SetTheme(Microsoft.Maui.Controls.Application.Current.Resources);
|
||||
iOSCoreHelpers.AppearanceAdjustments();
|
||||
|
||||
return finishedLaunching;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LoggerHelper.LogEvenIfCantBeResolved(ex);
|
||||
throw;
|
||||
}
|
||||
return finishedLaunching;
|
||||
}
|
||||
|
||||
public override void OnResignActivation(UIApplication uiApplication)
|
||||
{
|
||||
try
|
||||
if (UIApplication.SharedApplication.KeyWindow != null)
|
||||
{
|
||||
if (UIApplication.SharedApplication.KeyWindow != null)
|
||||
var view = new UIView(UIApplication.SharedApplication.KeyWindow.Frame)
|
||||
{
|
||||
var view = new UIView(UIApplication.SharedApplication.KeyWindow.Frame)
|
||||
{
|
||||
Tag = SPLASH_VIEW_TAG
|
||||
};
|
||||
var backgroundView = new UIView(UIApplication.SharedApplication.KeyWindow.Frame)
|
||||
{
|
||||
BackgroundColor = ThemeManager.GetResourceColor("SplashBackgroundColor").ToPlatform()
|
||||
};
|
||||
var logo = new UIImage(!ThemeManager.UsingLightTheme ? "logo_white.png" : "logo.png");
|
||||
var frame = new CGRect(0, 0, 280, 100); //Setting image width to avoid it being larger and getting cropped on smaller devices. This harcoded size should be good even for very small devices.
|
||||
var imageView = new UIImageView(frame)
|
||||
{
|
||||
Image = logo,
|
||||
Center = new CGPoint(view.Center.X, view.Center.Y - 30),
|
||||
ContentMode = UIViewContentMode.ScaleAspectFit
|
||||
};
|
||||
view.AddSubview(backgroundView);
|
||||
view.AddSubview(imageView);
|
||||
UIApplication.SharedApplication.KeyWindow.AddSubview(view);
|
||||
UIApplication.SharedApplication.KeyWindow.BringSubviewToFront(view);
|
||||
UIApplication.SharedApplication.KeyWindow.EndEditing(true);
|
||||
}
|
||||
base.OnResignActivation(uiApplication);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LoggerHelper.LogEvenIfCantBeResolved(ex);
|
||||
throw;
|
||||
Tag = SPLASH_VIEW_TAG
|
||||
};
|
||||
var backgroundView = new UIView(UIApplication.SharedApplication.KeyWindow.Frame)
|
||||
{
|
||||
BackgroundColor = ThemeManager.GetResourceColor("SplashBackgroundColor").ToPlatform()
|
||||
};
|
||||
var logo = new UIImage(!ThemeManager.UsingLightTheme ? "logo_white.png" : "logo.png");
|
||||
var frame = new CGRect(0, 0, 280, 100); //Setting image width to avoid it being larger and getting cropped on smaller devices. This harcoded size should be good even for very small devices.
|
||||
var imageView = new UIImageView(frame)
|
||||
{
|
||||
Image = logo,
|
||||
Center = new CGPoint(view.Center.X, view.Center.Y - 30),
|
||||
ContentMode = UIViewContentMode.ScaleAspectFit
|
||||
};
|
||||
view.AddSubview(backgroundView);
|
||||
view.AddSubview(imageView);
|
||||
UIApplication.SharedApplication.KeyWindow.AddSubview(view);
|
||||
UIApplication.SharedApplication.KeyWindow.BringSubviewToFront(view);
|
||||
UIApplication.SharedApplication.KeyWindow.EndEditing(true);
|
||||
}
|
||||
base.OnResignActivation(uiApplication);
|
||||
}
|
||||
|
||||
public override void DidEnterBackground(UIApplication uiApplication)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_stateService != null && _deviceActionService != null)
|
||||
{
|
||||
_stateService.SetLastActiveTimeAsync(_deviceActionService.GetActiveTime());
|
||||
}
|
||||
|
||||
_messagingService?.Send("slept");
|
||||
base.DidEnterBackground(uiApplication);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LoggerHelper.LogEvenIfCantBeResolved(ex);
|
||||
throw;
|
||||
}
|
||||
_stateService?.SetLastActiveTimeAsync(_deviceActionService.GetActiveTime());
|
||||
_messagingService?.Send("slept");
|
||||
base.DidEnterBackground(uiApplication);
|
||||
}
|
||||
|
||||
public override async void OnActivated(UIApplication uiApplication)
|
||||
public override void OnActivated(UIApplication uiApplication)
|
||||
{
|
||||
try
|
||||
{
|
||||
base.OnActivated(uiApplication);
|
||||
base.OnActivated(uiApplication);
|
||||
UIApplication.SharedApplication.ApplicationIconBadgeNumber = 0;
|
||||
UIApplication.SharedApplication.KeyWindow?
|
||||
.ViewWithTag(SPLASH_VIEW_TAG)?
|
||||
.RemoveFromSuperview();
|
||||
|
||||
if (UIDevice.CurrentDevice.CheckSystemVersion(17, 0))
|
||||
{
|
||||
await UNUserNotificationCenter.Current.SetBadgeCountAsync(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
UIApplication.SharedApplication.ApplicationIconBadgeNumber = 0;
|
||||
}
|
||||
|
||||
UIApplication.SharedApplication.KeyWindow?
|
||||
.ViewWithTag(SPLASH_VIEW_TAG)?
|
||||
.RemoveFromSuperview();
|
||||
|
||||
ThemeManager.UpdateThemeOnPagesAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LoggerHelper.LogEvenIfCantBeResolved(ex);
|
||||
}
|
||||
ThemeManager.UpdateThemeOnPagesAsync();
|
||||
}
|
||||
|
||||
public override void WillEnterForeground(UIApplication uiApplication)
|
||||
{
|
||||
try
|
||||
{
|
||||
_messagingService?.Send(AppHelpers.RESUMED_MESSAGE_COMMAND);
|
||||
base.WillEnterForeground(uiApplication);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LoggerHelper.LogEvenIfCantBeResolved(ex);
|
||||
}
|
||||
_messagingService?.Send(AppHelpers.RESUMED_MESSAGE_COMMAND);
|
||||
base.WillEnterForeground(uiApplication);
|
||||
}
|
||||
|
||||
[Export("application:openURL:sourceApplication:annotation:")]
|
||||
@@ -301,30 +249,15 @@ namespace Bit.iOS
|
||||
|
||||
public override bool OpenUrl(UIApplication app, NSUrl url, NSDictionary options)
|
||||
{
|
||||
try
|
||||
{
|
||||
return _deepLinkContext.Value.OnNewUri(url) || base.OpenUrl(app, url, options);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LoggerHelper.LogEvenIfCantBeResolved(ex);
|
||||
return false;
|
||||
}
|
||||
return _deepLinkContext.Value.OnNewUri(url) || base.OpenUrl(app, url, options);
|
||||
}
|
||||
|
||||
public override bool ContinueUserActivity(UIApplication application, NSUserActivity userActivity,
|
||||
UIApplicationRestorationHandler completionHandler)
|
||||
{
|
||||
try
|
||||
if (Microsoft.Maui.ApplicationModel.Platform.ContinueUserActivity(application, userActivity, completionHandler))
|
||||
{
|
||||
if (Microsoft.Maui.ApplicationModel.Platform.ContinueUserActivity(application, userActivity, completionHandler))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LoggerHelper.LogEvenIfCantBeResolved(ex);
|
||||
return true;
|
||||
}
|
||||
return base.ContinueUserActivity(application, userActivity, completionHandler);
|
||||
}
|
||||
@@ -332,68 +265,33 @@ namespace Bit.iOS
|
||||
[Export("application:didFailToRegisterForRemoteNotificationsWithError:")]
|
||||
public void FailedToRegisterForRemoteNotifications(UIApplication application, NSError error)
|
||||
{
|
||||
try
|
||||
{
|
||||
_pushHandler?.OnErrorReceived(error);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LoggerHelper.LogEvenIfCantBeResolved(ex);
|
||||
}
|
||||
_pushHandler?.OnErrorReceived(error);
|
||||
}
|
||||
|
||||
[Export("application:didRegisterForRemoteNotificationsWithDeviceToken:")]
|
||||
public void RegisteredForRemoteNotifications(UIApplication application, NSData deviceToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
_pushHandler?.OnRegisteredSuccess(deviceToken);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LoggerHelper.LogEvenIfCantBeResolved(ex);
|
||||
}
|
||||
_pushHandler?.OnRegisteredSuccess(deviceToken);
|
||||
}
|
||||
|
||||
[Export("application:didRegisterUserNotificationSettings:")]
|
||||
public void DidRegisterUserNotificationSettings(UIApplication application,
|
||||
UIUserNotificationSettings notificationSettings)
|
||||
{
|
||||
try
|
||||
{
|
||||
application.RegisterForRemoteNotifications();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LoggerHelper.LogEvenIfCantBeResolved(ex);
|
||||
}
|
||||
application.RegisterForRemoteNotifications();
|
||||
}
|
||||
|
||||
[Export("application:didReceiveRemoteNotification:fetchCompletionHandler:")]
|
||||
public void DidReceiveRemoteNotification(UIApplication application, NSDictionary userInfo,
|
||||
Action<UIBackgroundFetchResult> completionHandler)
|
||||
{
|
||||
try
|
||||
{
|
||||
_pushHandler?.OnMessageReceived(userInfo);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LoggerHelper.LogEvenIfCantBeResolved(ex);
|
||||
}
|
||||
_pushHandler?.OnMessageReceived(userInfo);
|
||||
}
|
||||
|
||||
[Export("application:didReceiveRemoteNotification:")]
|
||||
public void ReceivedRemoteNotification(UIApplication application, NSDictionary userInfo)
|
||||
{
|
||||
try
|
||||
{
|
||||
_pushHandler?.OnMessageReceived(userInfo);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LoggerHelper.LogEvenIfCantBeResolved(ex);
|
||||
}
|
||||
_pushHandler?.OnMessageReceived(userInfo);
|
||||
}
|
||||
|
||||
public void InitApp()
|
||||
@@ -406,6 +304,17 @@ namespace Bit.iOS
|
||||
// Migration services
|
||||
ServiceContainer.Register<INativeLogService>("nativeLogService", new ConsoleLogService());
|
||||
|
||||
// Note: This might cause a race condition. Investigate more.
|
||||
//Task.Run(() =>
|
||||
//{
|
||||
// FFImageLoading.Forms.Platform.CachedImageRenderer.Init();
|
||||
// FFImageLoading.ImageService.Instance.Initialize(new FFImageLoading.Config.Configuration
|
||||
// {
|
||||
// FadeAnimationEnabled = false,
|
||||
// FadeAnimationForCachedImages = false
|
||||
// });
|
||||
//});
|
||||
|
||||
iOSCoreHelpers.RegisterLocalServices();
|
||||
RegisterPush();
|
||||
var deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
|
||||
@@ -419,7 +328,7 @@ namespace Bit.iOS
|
||||
_nfcDelegate = new Core.NFCReaderDelegate((success, message) =>
|
||||
_messagingService.Send("gotYubiKeyOTP", message));
|
||||
|
||||
iOSCoreHelpers.Bootstrap(ApplyManagedSettingsAsync);
|
||||
iOSCoreHelpers.Bootstrap(async () => await ApplyManagedSettingsAsync());
|
||||
}
|
||||
|
||||
private void RegisterPush()
|
||||
@@ -464,45 +373,31 @@ namespace Bit.iOS
|
||||
_eventTimer = null;
|
||||
MainThread.BeginInvokeOnMainThread(() =>
|
||||
{
|
||||
try
|
||||
_eventTimer = NSTimer.CreateScheduledTimer(60, true, timer =>
|
||||
{
|
||||
_eventTimer = NSTimer.CreateScheduledTimer(60, true, timer =>
|
||||
{
|
||||
_eventService?.UploadEventsAsync().FireAndForget();
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LoggerHelper.LogEvenIfCantBeResolved(ex);
|
||||
}
|
||||
var task = Task.Run(() => _eventService.UploadEventsAsync());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private async Task StopEventTimerAsync()
|
||||
{
|
||||
try
|
||||
_eventTimer?.Invalidate();
|
||||
_eventTimer?.Dispose();
|
||||
_eventTimer = null;
|
||||
if (_eventBackgroundTaskId > 0)
|
||||
{
|
||||
_eventTimer?.Invalidate();
|
||||
_eventTimer?.Dispose();
|
||||
_eventTimer = null;
|
||||
if (_eventBackgroundTaskId > 0)
|
||||
{
|
||||
UIApplication.SharedApplication.EndBackgroundTask(_eventBackgroundTaskId);
|
||||
_eventBackgroundTaskId = 0;
|
||||
}
|
||||
_eventBackgroundTaskId = UIApplication.SharedApplication.BeginBackgroundTask(() =>
|
||||
{
|
||||
UIApplication.SharedApplication.EndBackgroundTask(_eventBackgroundTaskId);
|
||||
_eventBackgroundTaskId = 0;
|
||||
});
|
||||
await _eventService.UploadEventsAsync();
|
||||
UIApplication.SharedApplication.EndBackgroundTask(_eventBackgroundTaskId);
|
||||
_eventBackgroundTaskId = 0;
|
||||
}
|
||||
catch (Exception ex)
|
||||
_eventBackgroundTaskId = UIApplication.SharedApplication.BeginBackgroundTask(() =>
|
||||
{
|
||||
LoggerHelper.LogEvenIfCantBeResolved(ex);
|
||||
}
|
||||
UIApplication.SharedApplication.EndBackgroundTask(_eventBackgroundTaskId);
|
||||
_eventBackgroundTaskId = 0;
|
||||
});
|
||||
await _eventService.UploadEventsAsync();
|
||||
UIApplication.SharedApplication.EndBackgroundTask(_eventBackgroundTaskId);
|
||||
_eventBackgroundTaskId = 0;
|
||||
}
|
||||
|
||||
private async Task ApplyManagedSettingsAsync()
|
||||
|
||||
@@ -5,24 +5,15 @@ using Foundation;
|
||||
using Microsoft.Maui.Handlers;
|
||||
using WebKit;
|
||||
|
||||
namespace Bit.iOS.Core.Handlers
|
||||
namespace Bit.App.Handlers
|
||||
{
|
||||
public class HybridWebViewHandler : ViewHandler<HybridWebView, WebKit.WKWebView>
|
||||
public partial class HybridWebViewHandler : ViewHandler<HybridWebView, WebKit.WKWebView>
|
||||
{
|
||||
private const string JSFunction =
|
||||
"function invokeCSharpAction(data){window.webkit.messageHandlers.invokeAction.postMessage(data);}";
|
||||
|
||||
private WKUserContentController _userController;
|
||||
|
||||
public static PropertyMapper<HybridWebView, HybridWebViewHandler> PropertyMapper = new PropertyMapper<HybridWebView, HybridWebViewHandler>(ViewHandler.ViewMapper)
|
||||
{
|
||||
[nameof(HybridWebView.Uri)] = MapUri
|
||||
};
|
||||
|
||||
public HybridWebViewHandler() : base(PropertyMapper)
|
||||
{
|
||||
}
|
||||
|
||||
public HybridWebViewHandler([NotNull] IPropertyMapper mapper, CommandMapper commandMapper = null) : base(mapper, commandMapper)
|
||||
{
|
||||
}
|
||||
@@ -11,7 +11,7 @@
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>com.8bit.bitwarden</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>2024.2.1</string>
|
||||
<string>2023.12.1</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<key>CFBundleIconName</key>
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
{
|
||||
"images": [
|
||||
{
|
||||
"appearances": [],
|
||||
"scale": "1x",
|
||||
"idiom": "universal",
|
||||
"filename": "search.png"
|
||||
},
|
||||
{
|
||||
"appearances": [],
|
||||
"scale": "2x",
|
||||
"idiom": "universal",
|
||||
"filename": "search@2x.png"
|
||||
},
|
||||
{
|
||||
"appearances": [],
|
||||
"scale": "3x",
|
||||
"idiom": "universal",
|
||||
"filename": "search@3x.png"
|
||||
}
|
||||
],
|
||||
"properties": {},
|
||||
"info": {
|
||||
"version": 1,
|
||||
"author": ""
|
||||
}
|
||||
}
|
||||
@@ -63,7 +63,5 @@ namespace Bit.Core.Abstractions
|
||||
Task<UserKey> DecryptAndMigrateOldPinKeyAsync(bool masterPasswordOnRestart, string pin, string email, KdfConfig kdfConfig, EncString oldPinKey);
|
||||
Task<MasterKey> GetOrDeriveMasterKeyAsync(string password, string userId = null);
|
||||
Task UpdateMasterKeyAndUserKeyAsync(MasterKey masterKey);
|
||||
Task<string> HashAsync(string value, CryptoHashAlgorithm hashAlgorithm);
|
||||
Task<bool> ValidateUriChecksumAsync(EncString remoteUriChecksum, string rawUri, string orgId, SymmetricCryptoKey key);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ using Bit.Core.Abstractions;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.Data;
|
||||
using Bit.Core.Models.Response;
|
||||
using Bit.Core.Pages;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Utilities;
|
||||
|
||||
@@ -35,8 +34,6 @@ namespace Bit.App
|
||||
private readonly IAccountsManager _accountsManager;
|
||||
private readonly IPushNotificationService _pushNotificationService;
|
||||
private readonly IConfigService _configService;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
private static bool _isResumed;
|
||||
// these variables are static because the app is launching new activities on notification click, creating new instances of App.
|
||||
private static bool _pendingCheckPasswordlessLoginRequests;
|
||||
@@ -46,133 +43,6 @@ namespace Bit.App
|
||||
// This queue keeps those actions so that when the app has resumed they can still be executed.
|
||||
// Links: https://github.com/dotnet/maui/issues/11501 and https://bitwarden.atlassian.net/wiki/spaces/NMME/pages/664862722/MainPage+Assignments+not+working+on+Android+on+Background+or+App+resume
|
||||
private readonly Queue<Action> _onResumeActions = new Queue<Action>();
|
||||
private bool _hasNavigatedToAutofillWindow;
|
||||
|
||||
#if ANDROID
|
||||
|
||||
/*
|
||||
* ** Workaround for our Android crashes when trying to use Autofill **
|
||||
*
|
||||
* This workaround works by managing the "Window Creation" ourselves.
|
||||
* - If we get an AutofillExternalActivity we just create a "dummy" window/navigation page so that the activity can run without crashing. (no visible UI is needed)
|
||||
* - If we get an FromAutofillFramework/Uri/Otp/CreateSend special Option request we create an Autofill Window
|
||||
* - For everything else we use the default "mainWindow"
|
||||
*/
|
||||
|
||||
public new static Page MainPage
|
||||
{
|
||||
get
|
||||
{
|
||||
return CurrentWindow?.Page;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (CurrentWindow != null)
|
||||
{
|
||||
CurrentWindow.Page = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find the Current Active Window. There should only be one at any point in Android
|
||||
/// </summary>
|
||||
public static ResumeWindow CurrentWindow
|
||||
{
|
||||
get
|
||||
{
|
||||
return Application.Current?.Windows.OfType<ResumeWindow>().FirstOrDefault(w => w.IsActive);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Allows setting Options from MainActivity before base.OnCreate
|
||||
/// Note 1: This is only be used by Android due to way it's Lifecycle works
|
||||
/// Note 2: This method does not replace existing Options in App.xaml.cs if it exists already.
|
||||
/// It only updates properties in Options related with Autofill/CreateSend/etc..
|
||||
/// </summary>
|
||||
/// <param name="appOptions">Options created in Android MainActivity.cs</param>
|
||||
public void SetAndroidOptions(AppOptions appOptions)
|
||||
{
|
||||
if (Options == null)
|
||||
{
|
||||
Options = appOptions ?? new AppOptions();
|
||||
}
|
||||
else if(appOptions != null)
|
||||
{
|
||||
Options.Uri = appOptions.Uri;
|
||||
Options.MyVaultTile = appOptions.MyVaultTile;
|
||||
Options.GeneratorTile = appOptions.GeneratorTile;
|
||||
Options.FromAutofillFramework = appOptions.FromAutofillFramework;
|
||||
Options.CreateSend = appOptions.CreateSend;
|
||||
}
|
||||
}
|
||||
|
||||
protected override Window CreateWindow(IActivationState activationState)
|
||||
{
|
||||
//When executing from AutofillExternalActivity we don't have "Options" so we need to filter "manually"
|
||||
//In the AutofillExternalActivity we don't need to show any Page, so we just create a "dummy" Window with a NavigationPage to avoid crashing.
|
||||
if (activationState != null
|
||||
&& activationState.State.TryGetValue("autofillFramework", out string autofillFramework)
|
||||
&& autofillFramework == "true"
|
||||
&& activationState.State.ContainsKey("autofillFrameworkCipherId"))
|
||||
{
|
||||
return new Window(new NavigationPage()); //No actual page needed. Only used for auto-filling the fields directly (externally)
|
||||
}
|
||||
|
||||
//"Internal" Autofill and Uri/Otp/CreateSend. This is where we create the autofill specific Window
|
||||
if (Options != null && (Options.FromAutofillFramework || Options.Uri != null || Options.OtpData != null || Options.CreateSend != null))
|
||||
{
|
||||
_isResumed = true; //Specifically for the Autofill scenario we need to manually set the _isResumed here
|
||||
_hasNavigatedToAutofillWindow = true;
|
||||
return new AutoFillWindow(new NavigationPage(new AndroidNavigationRedirectPage()));
|
||||
}
|
||||
|
||||
var homePage = new HomePage(Options);
|
||||
// WORKAROUND: If the user autofills with Accessibility Services enabled and goes back to the application then there is currently an issue
|
||||
// where this method is called again
|
||||
// thus it goes through here and the user goes to HomePage as we see here.
|
||||
// So to solve this, the next flag check has been added which then turns on a flag on the home page
|
||||
// that will trigger a navigation on the accounts manager when it loads; workarounding this behavior and navigating the user
|
||||
// to the proper page depending on its state.
|
||||
// WARNING: this doens't navigate the user to where they were but it acts as if the user had changed their account.
|
||||
if(_hasNavigatedToAutofillWindow)
|
||||
{
|
||||
homePage.PerformNavigationOnAccountChangedOnLoad = true;
|
||||
// this is needed because when coming back from AutofillWindow OnResume won't be called and we need this flag
|
||||
// so that void Navigate(NavigationTarget navTarget, INavigationParams navParams) doesn't enqueue the navigation
|
||||
// and it performs it directly.
|
||||
_isResumed = true;
|
||||
_hasNavigatedToAutofillWindow = false;
|
||||
}
|
||||
|
||||
//If we have an existing MainAppWindow we can use that one
|
||||
var mainAppWindow = Windows.OfType<MainAppWindow>().FirstOrDefault();
|
||||
if (mainAppWindow != null)
|
||||
{
|
||||
mainAppWindow.PendingPage = new NavigationPage(homePage);
|
||||
}
|
||||
|
||||
//Create new main window
|
||||
return new MainAppWindow(new NavigationPage(homePage));
|
||||
}
|
||||
#else
|
||||
//iOS doesn't use the CreateWindow override used in Android so we just set the Application.Current.MainPage directly
|
||||
public new static Page MainPage
|
||||
{
|
||||
get
|
||||
{
|
||||
return Application.Current?.MainPage;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (Application.Current != null)
|
||||
{
|
||||
Application.Current.MainPage = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
public App() : this(null)
|
||||
{
|
||||
@@ -197,7 +67,6 @@ namespace Bit.App
|
||||
_accountsManager = ServiceContainer.Resolve<IAccountsManager>("accountsManager");
|
||||
_pushNotificationService = ServiceContainer.Resolve<IPushNotificationService>();
|
||||
_configService = ServiceContainer.Resolve<IConfigService>();
|
||||
_logger = ServiceContainer.Resolve<ILogger>();
|
||||
|
||||
_accountsManager.Init(() => Options, this);
|
||||
|
||||
@@ -212,7 +81,7 @@ namespace Bit.App
|
||||
var confirmed = true;
|
||||
var confirmText = string.IsNullOrWhiteSpace(details.ConfirmText) ?
|
||||
AppResources.Ok : details.ConfirmText;
|
||||
await MainThread.InvokeOnMainThreadAsync(async () =>
|
||||
MainThread.BeginInvokeOnMainThread(async () =>
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(details.CancelText))
|
||||
{
|
||||
@@ -226,16 +95,20 @@ namespace Bit.App
|
||||
_messagingService.Send("showDialogResolve", new Tuple<int, bool>(details.DialogId, confirmed));
|
||||
});
|
||||
}
|
||||
#if IOS
|
||||
else if (message.Command == AppHelpers.RESUMED_MESSAGE_COMMAND)
|
||||
{
|
||||
ResumedAsync().FireAndForget();
|
||||
if (DeviceInfo.Platform == DevicePlatform.iOS)
|
||||
{
|
||||
ResumedAsync().FireAndForget();
|
||||
}
|
||||
}
|
||||
else if (message.Command == "slept")
|
||||
{
|
||||
await SleptAsync();
|
||||
if (DeviceInfo.Platform == DevicePlatform.iOS)
|
||||
{
|
||||
await SleptAsync();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
else if (message.Command == "migrated")
|
||||
{
|
||||
await Task.Delay(1000);
|
||||
@@ -252,7 +125,7 @@ namespace Bit.App
|
||||
Options.OtpData = new OtpData((string)message.Data);
|
||||
}
|
||||
|
||||
await MainThread.InvokeOnMainThreadAsync(async () =>
|
||||
MainThread.InvokeOnMainThreadAsync(async () =>
|
||||
{
|
||||
if (MainPage is TabsPage tabsPage)
|
||||
{
|
||||
@@ -288,7 +161,7 @@ namespace Bit.App
|
||||
}
|
||||
else if (message.Command == "convertAccountToKeyConnector")
|
||||
{
|
||||
await MainThread.InvokeOnMainThreadAsync(async () =>
|
||||
MainThread.BeginInvokeOnMainThread(async () =>
|
||||
{
|
||||
await MainPage.Navigation.PushModalAsync(
|
||||
new NavigationPage(new RemoveMasterPasswordPage()));
|
||||
@@ -296,7 +169,7 @@ namespace Bit.App
|
||||
}
|
||||
else if (message.Command == Constants.ForceUpdatePassword)
|
||||
{
|
||||
await MainThread.InvokeOnMainThreadAsync(async () =>
|
||||
MainThread.BeginInvokeOnMainThread(async () =>
|
||||
{
|
||||
await MainPage.Navigation.PushModalAsync(
|
||||
new NavigationPage(new UpdateTempPasswordPage()));
|
||||
@@ -412,52 +285,40 @@ namespace Bit.App
|
||||
|
||||
protected override async void OnStart()
|
||||
{
|
||||
try
|
||||
System.Diagnostics.Debug.WriteLine("XF App: OnStart");
|
||||
_isResumed = true;
|
||||
await ClearCacheIfNeededAsync();
|
||||
Prime();
|
||||
if (string.IsNullOrWhiteSpace(Options.Uri))
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine("XF App: OnStart");
|
||||
_isResumed = true;
|
||||
await ClearCacheIfNeededAsync();
|
||||
Prime();
|
||||
if (string.IsNullOrWhiteSpace(Options.Uri))
|
||||
var updated = await AppHelpers.PerformUpdateTasksAsync(_syncService, _deviceActionService,
|
||||
_stateService);
|
||||
if (!updated)
|
||||
{
|
||||
var updated = await AppHelpers.PerformUpdateTasksAsync(_syncService, _deviceActionService,
|
||||
_stateService);
|
||||
if (!updated)
|
||||
{
|
||||
SyncIfNeeded();
|
||||
}
|
||||
SyncIfNeeded();
|
||||
}
|
||||
if (_pendingCheckPasswordlessLoginRequests)
|
||||
{
|
||||
_messagingService.Send(Constants.PasswordlessLoginRequestKey);
|
||||
}
|
||||
#if ANDROID
|
||||
}
|
||||
if (_pendingCheckPasswordlessLoginRequests)
|
||||
{
|
||||
_messagingService.Send(Constants.PasswordlessLoginRequestKey);
|
||||
}
|
||||
if (DeviceInfo.Platform == DevicePlatform.Android)
|
||||
{
|
||||
await _vaultTimeoutService.CheckVaultTimeoutAsync();
|
||||
// Reset delay on every start
|
||||
_vaultTimeoutService.DelayLockAndLogoutMs = null;
|
||||
#endif
|
||||
}
|
||||
|
||||
await _configService.GetAsync();
|
||||
_messagingService.Send("startEventTimer");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger?.Exception(ex);
|
||||
throw;
|
||||
}
|
||||
await _configService.GetAsync();
|
||||
_messagingService.Send("startEventTimer");
|
||||
}
|
||||
|
||||
#if ANDROID
|
||||
protected override async void OnSleep()
|
||||
#else
|
||||
protected override void OnSleep()
|
||||
#endif
|
||||
{
|
||||
try
|
||||
System.Diagnostics.Debug.WriteLine("XF App: OnSleep");
|
||||
_isResumed = false;
|
||||
if (DeviceInfo.Platform == DevicePlatform.Android)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine("XF App: OnSleep");
|
||||
_isResumed = false;
|
||||
#if ANDROID
|
||||
var isLocked = await _vaultTimeoutService.IsLockedAsync();
|
||||
if (!isLocked)
|
||||
{
|
||||
@@ -468,34 +329,20 @@ namespace Bit.App
|
||||
ClearAutofillUri();
|
||||
}
|
||||
await SleptAsync();
|
||||
#endif
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger?.Exception(ex);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnResume()
|
||||
{
|
||||
try
|
||||
System.Diagnostics.Debug.WriteLine("XF App: OnResume");
|
||||
_isResumed = true;
|
||||
if (_pendingCheckPasswordlessLoginRequests)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine("XF App: OnResume");
|
||||
_isResumed = true;
|
||||
if (_pendingCheckPasswordlessLoginRequests)
|
||||
{
|
||||
_messagingService.Send(Constants.PasswordlessLoginRequestKey);
|
||||
}
|
||||
#if ANDROID
|
||||
ResumedAsync().FireAndForget();
|
||||
#endif
|
||||
|
||||
_messagingService.Send(Constants.PasswordlessLoginRequestKey);
|
||||
}
|
||||
catch (Exception ex)
|
||||
if (DeviceInfo.Platform == DevicePlatform.Android)
|
||||
{
|
||||
_logger?.Exception(ex);
|
||||
throw;
|
||||
ResumedAsync().FireAndForget();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -582,22 +429,14 @@ namespace Bit.App
|
||||
{
|
||||
MainThread.BeginInvokeOnMainThread(() =>
|
||||
{
|
||||
try
|
||||
Options.Uri = null;
|
||||
if (isLocked)
|
||||
{
|
||||
Options.Uri = null;
|
||||
if (isLocked)
|
||||
{
|
||||
App.MainPage = new NavigationPage(new LockPage());
|
||||
}
|
||||
else
|
||||
{
|
||||
App.MainPage = new TabsPage();
|
||||
}
|
||||
MainPage = new NavigationPage(new LockPage());
|
||||
}
|
||||
catch (Exception ex)
|
||||
else
|
||||
{
|
||||
LoggerHelper.LogEvenIfCantBeResolved(ex);
|
||||
throw;
|
||||
MainPage = new TabsPage();
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -622,13 +461,10 @@ namespace Bit.App
|
||||
ThemeManager.SetTheme(Resources);
|
||||
RequestedThemeChanged += (s, a) =>
|
||||
{
|
||||
UpdateThemeAsync().FireAndForget();
|
||||
UpdateThemeAsync();
|
||||
};
|
||||
_isResumed = true;
|
||||
#if IOS
|
||||
//We only set the MainPage here for iOS. Android is using the CreateWindow override for the initial page.
|
||||
App.MainPage = new NavigationPage(new HomePage(Options));
|
||||
#endif
|
||||
MainPage = new NavigationPage(new HomePage(Options));
|
||||
_accountsManager.NavigateOnAccountChangeAsync().FireAndForget();
|
||||
ServiceContainer.Resolve<MobilePlatformUtilsService>("platformUtilsService").Init();
|
||||
}
|
||||
@@ -641,18 +477,11 @@ namespace Bit.App
|
||||
}
|
||||
Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
var lastSync = await _syncService.GetLastSyncAsync();
|
||||
if (lastSync == null || ((DateTime.UtcNow - lastSync) > TimeSpan.FromMinutes(30)))
|
||||
{
|
||||
var lastSync = await _syncService.GetLastSyncAsync();
|
||||
if (lastSync == null || ((DateTime.UtcNow - lastSync) > TimeSpan.FromMinutes(30)))
|
||||
{
|
||||
await Task.Delay(1000);
|
||||
await _syncService.FullSyncAsync(false);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Exception(ex);
|
||||
await Task.Delay(1000);
|
||||
await _syncService.FullSyncAsync(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -707,36 +536,36 @@ namespace Bit.App
|
||||
switch (navTarget)
|
||||
{
|
||||
case NavigationTarget.HomeLogin:
|
||||
App.MainPage = new NavigationPage(new HomePage(Options));
|
||||
MainPage = new NavigationPage(new HomePage(Options));
|
||||
break;
|
||||
case NavigationTarget.Login:
|
||||
if (navParams is LoginNavigationParams loginParams)
|
||||
{
|
||||
App.MainPage = new NavigationPage(new LoginPage(loginParams.Email, Options));
|
||||
MainPage = new NavigationPage(new LoginPage(loginParams.Email, Options));
|
||||
}
|
||||
break;
|
||||
case NavigationTarget.Lock:
|
||||
if (navParams is LockNavigationParams lockParams)
|
||||
{
|
||||
App.MainPage = new NavigationPage(new LockPage(Options, lockParams.AutoPromptBiometric));
|
||||
MainPage = new NavigationPage(new LockPage(Options, lockParams.AutoPromptBiometric));
|
||||
}
|
||||
else
|
||||
{
|
||||
App.MainPage = new NavigationPage(new LockPage(Options));
|
||||
MainPage = new NavigationPage(new LockPage(Options));
|
||||
}
|
||||
break;
|
||||
case NavigationTarget.Home:
|
||||
App.MainPage = new TabsPage(Options);
|
||||
MainPage = new TabsPage(Options);
|
||||
break;
|
||||
case NavigationTarget.AddEditCipher:
|
||||
App.MainPage = new NavigationPage(new CipherAddEditPage(appOptions: Options));
|
||||
MainPage = new NavigationPage(new CipherAddEditPage(appOptions: Options));
|
||||
break;
|
||||
case NavigationTarget.AutofillCiphers:
|
||||
case NavigationTarget.OtpCipherSelection:
|
||||
App.MainPage = new NavigationPage(new CipherSelectionPage(Options));
|
||||
MainPage = new NavigationPage(new CipherSelectionPage(Options));
|
||||
break;
|
||||
case NavigationTarget.SendAddEdit:
|
||||
App.MainPage = new NavigationPage(new SendAddEditPage(Options));
|
||||
MainPage = new NavigationPage(new SendAddEditPage(Options));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,11 +70,10 @@ namespace Bit.Core
|
||||
public const int Argon2Parallelism = 4;
|
||||
public const int MasterPasswordMinimumChars = 12;
|
||||
public const int CipherKeyRandomBytesLength = 64;
|
||||
public const string CipherKeyEncryptionMinServerVersion = "2024.2.0";
|
||||
public const string CipherKeyEncryptionMinServerVersion = "2023.9.1";
|
||||
public const string DefaultFido2CredentialType = "public-key";
|
||||
public const string DefaultFido2CredentialAlgorithm = "ECDSA";
|
||||
public const string DefaultFido2CredentialCurve = "P-256";
|
||||
public const int LatestStateVersion = 7;
|
||||
|
||||
public static readonly string[] AndroidAllClearCipherCacheKeys =
|
||||
{
|
||||
|
||||
@@ -12,14 +12,12 @@
|
||||
BackgroundColor="#22000000"
|
||||
Padding="0"
|
||||
IsVisible="False">
|
||||
<Grid
|
||||
<VerticalStackLayout
|
||||
x:Name="_accountListContainer"
|
||||
VerticalOptions="Fill"
|
||||
HorizontalOptions="Fill"
|
||||
BackgroundColor="Transparent"
|
||||
RowDefinitions="Auto, *">
|
||||
HorizontalOptions="FillAndExpand"
|
||||
BackgroundColor="Transparent">
|
||||
<Frame
|
||||
Grid.Row="0"
|
||||
Padding="0"
|
||||
HorizontalOptions="Fill"
|
||||
VerticalOptions="Start">
|
||||
@@ -51,13 +49,12 @@
|
||||
</ListView>
|
||||
</Frame>
|
||||
<BoxView
|
||||
Grid.Row="1"
|
||||
BackgroundColor="Transparent"
|
||||
HorizontalOptions="Fill"
|
||||
VerticalOptions="Fill">
|
||||
VerticalOptions="FillAndExpand">
|
||||
<BoxView.GestureRecognizers>
|
||||
<TapGestureRecognizer Tapped="FreeSpaceOverlay_Tapped" />
|
||||
</BoxView.GestureRecognizers>
|
||||
</BoxView>
|
||||
</Grid>
|
||||
</VerticalStackLayout>
|
||||
</ContentView>
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<controls:BaseCipherViewCell xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||
<controls:ExtendedGrid xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
x:Class="Bit.App.Controls.AuthenticatorViewCell"
|
||||
xmlns:controls="clr-namespace:Bit.App.Controls"
|
||||
xmlns:pages="clr-namespace:Bit.App.Pages"
|
||||
xmlns:u="clr-namespace:Bit.App.Utilities"
|
||||
xmlns:ff="clr-namespace:FFImageLoading.Maui;assembly=FFImageLoading.Compat.Maui"
|
||||
xmlns:core="clr-namespace:Bit.Core"
|
||||
StyleClass="list-row, list-row-platform"
|
||||
HorizontalOptions="FillAndExpand"
|
||||
@@ -13,32 +14,34 @@
|
||||
RowSpacing="0"
|
||||
Padding="0,10,0,0"
|
||||
RowDefinitions="*,*">
|
||||
<controls:BaseCipherViewCell.Resources>
|
||||
|
||||
<Grid.Resources>
|
||||
<u:IconGlyphConverter x:Key="iconGlyphConverter" />
|
||||
<u:InverseBoolConverter x:Key="inverseBool" />
|
||||
</controls:BaseCipherViewCell.Resources>
|
||||
</Grid.Resources>
|
||||
|
||||
<controls:CachedImage
|
||||
x:Name="_iconImage"
|
||||
<controls:IconLabel
|
||||
Grid.Column="0"
|
||||
HorizontalOptions="Center"
|
||||
VerticalOptions="Center"
|
||||
StyleClass="list-icon, list-icon-platform"
|
||||
Grid.RowSpan="2"
|
||||
IsVisible="{Binding ShowIconImage, Converter={StaticResource inverseBool}}"
|
||||
Text="{Binding Cipher, Converter={StaticResource iconGlyphConverter}}"
|
||||
AutomationProperties.IsInAccessibleTree="False" />
|
||||
|
||||
<ff:CachedImage
|
||||
Grid.Column="0"
|
||||
BitmapOptimizations="True"
|
||||
ErrorPlaceholder="login.png"
|
||||
LoadingPlaceholder="login.png"
|
||||
HorizontalOptions="Center"
|
||||
VerticalOptions="Center"
|
||||
WidthRequest="22"
|
||||
HeightRequest="22"
|
||||
Success="Icon_Success"
|
||||
Error="Icon_Error"
|
||||
AutomationProperties.IsInAccessibleTree="False" />
|
||||
|
||||
<controls:IconLabel
|
||||
x:Name="_iconPlaceholderImage"
|
||||
Grid.Column="0"
|
||||
Grid.RowSpan="2"
|
||||
HorizontalOptions="Center"
|
||||
VerticalOptions="Center"
|
||||
StyleClass="list-icon, list-icon-platform"
|
||||
Text="{Binding Cipher, Converter={StaticResource iconGlyphConverter}}"
|
||||
IsVisible="{Binding ShowIconImage}"
|
||||
Source="{Binding IconImageSource, Mode=OneTime}"
|
||||
AutomationProperties.IsInAccessibleTree="False" />
|
||||
|
||||
<Label
|
||||
@@ -46,7 +49,7 @@
|
||||
Grid.Column="1"
|
||||
Grid.Row="0"
|
||||
VerticalTextAlignment="Center"
|
||||
VerticalOptions="End"
|
||||
VerticalOptions="Fill"
|
||||
StyleClass="list-title, list-title-platform"
|
||||
Text="{Binding Cipher.Name}" />
|
||||
|
||||
@@ -55,7 +58,7 @@
|
||||
Grid.Column="1"
|
||||
Grid.Row="1"
|
||||
VerticalTextAlignment="Center"
|
||||
VerticalOptions="Start"
|
||||
VerticalOptions="Fill"
|
||||
StyleClass="list-subtitle, list-subtitle-platform"
|
||||
Text="{Binding Cipher.SubTitle}" />
|
||||
|
||||
@@ -65,14 +68,11 @@
|
||||
Grid.Column="2"
|
||||
Grid.RowSpan="2"
|
||||
HorizontalOptions="Fill"
|
||||
WidthRequest="50"
|
||||
HeightRequest="50"
|
||||
VerticalOptions="CenterAndExpand" />
|
||||
|
||||
<Label
|
||||
Text="{Binding TotpSec, Mode=OneWay}"
|
||||
Style="{DynamicResource textTotp}"
|
||||
BackgroundColor="Transparent"
|
||||
Grid.Row="0"
|
||||
Grid.Column="2"
|
||||
Grid.RowSpan="2"
|
||||
@@ -123,4 +123,4 @@
|
||||
HorizontalOptions="Center"
|
||||
VerticalOptions="Center"
|
||||
SemanticProperties.Description="{u:I18n CopyTotp}" />
|
||||
</controls:BaseCipherViewCell>
|
||||
</controls:ExtendedGrid>
|
||||
@@ -1,14 +1,10 @@
|
||||
namespace Bit.App.Controls
|
||||
{
|
||||
public partial class AuthenticatorViewCell : BaseCipherViewCell
|
||||
public partial class AuthenticatorViewCell : ExtendedGrid
|
||||
{
|
||||
public AuthenticatorViewCell()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
protected override CachedImage Icon => _iconImage;
|
||||
|
||||
protected override IconLabel IconPlaceholder => _iconPlaceholderImage;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Bit.App.Controls
|
||||
{
|
||||
public class AvatarImageSource : StreamImageSource
|
||||
{
|
||||
private readonly string _text;
|
||||
private readonly string _id;
|
||||
private readonly string _color;
|
||||
private readonly AvatarInfo _avatarInfo;
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (obj is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (obj is AvatarImageSource avatar)
|
||||
{
|
||||
return avatar._id == _id && avatar._text == _text && avatar._color == _color;
|
||||
}
|
||||
|
||||
return base.Equals(obj);
|
||||
}
|
||||
|
||||
public override int GetHashCode() => _id?.GetHashCode() ?? _text?.GetHashCode() ?? -1;
|
||||
|
||||
public AvatarImageSource(string userId = null, string name = null, string email = null, string color = null)
|
||||
{
|
||||
_id = userId;
|
||||
_text = name;
|
||||
if (string.IsNullOrWhiteSpace(_text))
|
||||
{
|
||||
_text = email;
|
||||
}
|
||||
_color = color;
|
||||
|
||||
//Workaround: [MAUI-Migration] There is currently a bug in MAUI where the actual size of the image is used instead of the size it should occupy in the Toolbar.
|
||||
//This causes some issues with the position of the icon. As a workaround we make the icon smaller until this is fixed.
|
||||
//Github issues: https://github.com/dotnet/maui/issues/12359 and https://github.com/dotnet/maui/pull/17120
|
||||
_avatarInfo = new AvatarInfo(userId, name, email, color, DeviceInfo.Platform == DevicePlatform.iOS ? 20 : 50);
|
||||
}
|
||||
|
||||
public override Func<CancellationToken, Task<Stream>> Stream => GetStreamAsync;
|
||||
|
||||
private Task<Stream> GetStreamAsync(CancellationToken userToken = new CancellationToken())
|
||||
{
|
||||
var result = Draw();
|
||||
return Task.FromResult(result);
|
||||
}
|
||||
|
||||
private Stream Draw()
|
||||
{
|
||||
using (var img = SKAvatarImageHelper.Draw(_avatarInfo))
|
||||
{
|
||||
var data = img.Encode(SKEncodedImageFormat.Png, 100);
|
||||
return data?.AsStream(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
using Bit.Core.Utilities;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace Bit.App.Controls
|
||||
{
|
||||
public struct AvatarInfo
|
||||
{
|
||||
private const string DEFAULT_BACKGROUND_COLOR = "#33ffffff";
|
||||
|
||||
public AvatarInfo(string? userId = null, string? name = null, string? email = null, string? color = null, int size = 50)
|
||||
{
|
||||
Size = size;
|
||||
var text = string.IsNullOrWhiteSpace(name) ? email : name;
|
||||
|
||||
string? upperCaseText = null;
|
||||
|
||||
if (string.IsNullOrEmpty(text))
|
||||
{
|
||||
CharsToDraw = "..";
|
||||
}
|
||||
else if (text.Length > 1)
|
||||
{
|
||||
upperCaseText = text.ToUpper();
|
||||
CharsToDraw = GetFirstLetters(upperCaseText, 2);
|
||||
}
|
||||
else
|
||||
{
|
||||
CharsToDraw = upperCaseText = text.ToUpper();
|
||||
}
|
||||
|
||||
BackgroundColor = color ?? CoreHelpers.StringToColor(userId ?? upperCaseText, DEFAULT_BACKGROUND_COLOR);
|
||||
TextColor = CoreHelpers.TextColorFromBgColor(BackgroundColor);
|
||||
}
|
||||
|
||||
public string CharsToDraw { get; }
|
||||
public string BackgroundColor { get; }
|
||||
public string TextColor { get; }
|
||||
public int Size { get; }
|
||||
|
||||
private static string GetFirstLetters(string data, int charCount)
|
||||
{
|
||||
var sanitizedData = data.Trim();
|
||||
var parts = sanitizedData.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
if (parts.Length > 1 && charCount <= 2)
|
||||
{
|
||||
var text = string.Empty;
|
||||
for (var i = 0; i < charCount; i++)
|
||||
{
|
||||
text += parts[i][0];
|
||||
}
|
||||
return text;
|
||||
}
|
||||
if (sanitizedData.Length > 2)
|
||||
{
|
||||
return sanitizedData.Substring(0, 2);
|
||||
}
|
||||
return sanitizedData;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Bit.App.Controls
|
||||
{
|
||||
public static class SKAvatarImageHelper
|
||||
{
|
||||
public static SKImage Draw(AvatarInfo avatarInfo)
|
||||
{
|
||||
using (var bitmap = new SKBitmap(avatarInfo.Size * 2,
|
||||
avatarInfo.Size * 2,
|
||||
SKImageInfo.PlatformColorType,
|
||||
SKAlphaType.Premul))
|
||||
{
|
||||
using (var canvas = new SKCanvas(bitmap))
|
||||
{
|
||||
canvas.Clear(SKColors.Transparent);
|
||||
using (var paint = new SKPaint
|
||||
{
|
||||
IsAntialias = true,
|
||||
Style = SKPaintStyle.Fill,
|
||||
StrokeJoin = SKStrokeJoin.Miter,
|
||||
Color = SKColor.Parse(avatarInfo.BackgroundColor)
|
||||
})
|
||||
{
|
||||
var midX = canvas.LocalClipBounds.Size.ToSizeI().Width / 2;
|
||||
var midY = canvas.LocalClipBounds.Size.ToSizeI().Height / 2;
|
||||
var radius = midX - midX / 5;
|
||||
|
||||
using (var circlePaint = new SKPaint
|
||||
{
|
||||
IsAntialias = true,
|
||||
Style = SKPaintStyle.Fill,
|
||||
StrokeJoin = SKStrokeJoin.Miter,
|
||||
Color = SKColor.Parse(avatarInfo.BackgroundColor)
|
||||
})
|
||||
{
|
||||
canvas.DrawCircle(midX, midY, radius, circlePaint);
|
||||
|
||||
var typeface = SKTypeface.FromFamilyName("Arial", SKFontStyle.Normal);
|
||||
var textSize = midX / 1.3f;
|
||||
using (var textPaint = new SKPaint
|
||||
{
|
||||
IsAntialias = true,
|
||||
Style = SKPaintStyle.Fill,
|
||||
Color = SKColor.Parse(avatarInfo.TextColor),
|
||||
TextSize = textSize,
|
||||
TextAlign = SKTextAlign.Center,
|
||||
Typeface = typeface
|
||||
})
|
||||
{
|
||||
var rect = new SKRect();
|
||||
textPaint.MeasureText(avatarInfo.CharsToDraw, ref rect);
|
||||
canvas.DrawText(avatarInfo.CharsToDraw, midX, midY + rect.Height / 2, textPaint);
|
||||
|
||||
return SKImage.FromBitmap(bitmap);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
179
src/Core/Controls/AvatarImageSource.cs
Normal file
179
src/Core/Controls/AvatarImageSource.cs
Normal file
@@ -0,0 +1,179 @@
|
||||
using Bit.Core.Utilities;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Bit.App.Controls
|
||||
{
|
||||
public class AvatarImageSource : StreamImageSource
|
||||
{
|
||||
private readonly string _text;
|
||||
private readonly string _id;
|
||||
private readonly string _color;
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (obj is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (obj is AvatarImageSource avatar)
|
||||
{
|
||||
return avatar._id == _id && avatar._text == _text && avatar._color == _color;
|
||||
}
|
||||
|
||||
return base.Equals(obj);
|
||||
}
|
||||
|
||||
public override int GetHashCode() => _id?.GetHashCode() ?? _text?.GetHashCode() ?? -1;
|
||||
|
||||
public AvatarImageSource(string userId = null, string name = null, string email = null, string color = null)
|
||||
{
|
||||
_id = userId;
|
||||
_text = name;
|
||||
if (string.IsNullOrWhiteSpace(_text))
|
||||
{
|
||||
_text = email;
|
||||
}
|
||||
_color = color;
|
||||
}
|
||||
|
||||
public override Func<CancellationToken, Task<Stream>> Stream => GetStreamAsync;
|
||||
|
||||
private Task<Stream> GetStreamAsync(CancellationToken userToken = new CancellationToken())
|
||||
{
|
||||
var result = Draw();
|
||||
return Task.FromResult(result);
|
||||
}
|
||||
|
||||
private Stream Draw()
|
||||
{
|
||||
string chars;
|
||||
string upperCaseText = null;
|
||||
|
||||
if (string.IsNullOrEmpty(_text))
|
||||
{
|
||||
chars = "..";
|
||||
}
|
||||
else if (_text?.Length > 1)
|
||||
{
|
||||
upperCaseText = _text.ToUpper();
|
||||
chars = GetFirstLetters(upperCaseText, 2);
|
||||
}
|
||||
else
|
||||
{
|
||||
chars = upperCaseText = _text.ToUpper();
|
||||
}
|
||||
|
||||
var bgColor = _color ?? CoreHelpers.StringToColor(_id ?? upperCaseText, "#33ffffff");
|
||||
var textColor = CoreHelpers.TextColorFromBgColor(bgColor);
|
||||
var size = 50;
|
||||
|
||||
//Workaround: [MAUI-Migration] There is currently a bug in MAUI where the actual size of the image is used instead of the size it should occupy in the Toolbar.
|
||||
//This causes some issues with the position of the icon. As a workaround we make the icon smaller until this is fixed.
|
||||
//Github issues: https://github.com/dotnet/maui/issues/12359 and https://github.com/dotnet/maui/pull/17120
|
||||
if (DeviceInfo.Platform == DevicePlatform.iOS)
|
||||
{
|
||||
size = 20;
|
||||
}
|
||||
|
||||
using (var bitmap = new SKBitmap(size * 2,
|
||||
size * 2,
|
||||
SKImageInfo.PlatformColorType,
|
||||
SKAlphaType.Premul))
|
||||
{
|
||||
using (var canvas = new SKCanvas(bitmap))
|
||||
{
|
||||
canvas.Clear(SKColors.Transparent);
|
||||
using (var paint = new SKPaint
|
||||
{
|
||||
IsAntialias = true,
|
||||
Style = SKPaintStyle.Fill,
|
||||
StrokeJoin = SKStrokeJoin.Miter,
|
||||
Color = SKColor.Parse(bgColor)
|
||||
})
|
||||
{
|
||||
var midX = canvas.LocalClipBounds.Size.ToSizeI().Width / 2;
|
||||
var midY = canvas.LocalClipBounds.Size.ToSizeI().Height / 2;
|
||||
var radius = midX - midX / 5;
|
||||
|
||||
using (var circlePaint = new SKPaint
|
||||
{
|
||||
IsAntialias = true,
|
||||
Style = SKPaintStyle.Fill,
|
||||
StrokeJoin = SKStrokeJoin.Miter,
|
||||
Color = SKColor.Parse(bgColor)
|
||||
})
|
||||
{
|
||||
canvas.DrawCircle(midX, midY, radius, circlePaint);
|
||||
|
||||
var typeface = SKTypeface.FromFamilyName("Arial", SKFontStyle.Normal);
|
||||
var textSize = midX / 1.3f;
|
||||
using (var textPaint = new SKPaint
|
||||
{
|
||||
IsAntialias = true,
|
||||
Style = SKPaintStyle.Fill,
|
||||
Color = SKColor.Parse(textColor),
|
||||
TextSize = textSize,
|
||||
TextAlign = SKTextAlign.Center,
|
||||
Typeface = typeface
|
||||
})
|
||||
{
|
||||
var rect = new SKRect();
|
||||
textPaint.MeasureText(chars, ref rect);
|
||||
canvas.DrawText(chars, midX, midY + rect.Height / 2, textPaint);
|
||||
|
||||
using (var img = SKImage.FromBitmap(bitmap))
|
||||
{
|
||||
var data = img.Encode(SKEncodedImageFormat.Png, 100);
|
||||
return data?.AsStream(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private string GetFirstLetters(string data, int charCount)
|
||||
{
|
||||
var sanitizedData = data.Trim();
|
||||
var parts = sanitizedData.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
if (parts.Length > 1 && charCount <= 2)
|
||||
{
|
||||
var text = string.Empty;
|
||||
for (var i = 0; i < charCount; i++)
|
||||
{
|
||||
text += parts[i][0];
|
||||
}
|
||||
return text;
|
||||
}
|
||||
if (sanitizedData.Length > 2)
|
||||
{
|
||||
return sanitizedData.Substring(0, 2);
|
||||
}
|
||||
return sanitizedData;
|
||||
}
|
||||
|
||||
private Color StringToColor(string str)
|
||||
{
|
||||
if (str == null)
|
||||
{
|
||||
return Color.FromArgb("#33ffffff");
|
||||
}
|
||||
var hash = 0;
|
||||
for (var i = 0; i < str.Length; i++)
|
||||
{
|
||||
hash = str[i] + ((hash << 5) - hash);
|
||||
}
|
||||
var color = "#FF";
|
||||
for (var i = 0; i < 3; i++)
|
||||
{
|
||||
var value = (hash >> (i * 8)) & 0xff;
|
||||
var base16 = "00" + Convert.ToString(value, 16);
|
||||
color += base16.Substring(base16.Length - 2);
|
||||
}
|
||||
return Color.FromArgb(color);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
namespace Bit.App.Controls
|
||||
{
|
||||
#if !UT
|
||||
public class CachedImage : FFImageLoading.Maui.CachedImage
|
||||
{
|
||||
}
|
||||
#else
|
||||
/// <summary>
|
||||
/// Given that FFImageLoading package doesn't support net8.0 then for Unit tests projects to build and run correctly
|
||||
/// we need to not include the reference to FFImageLoading and therefore wrap this class
|
||||
/// to provide a stub one that does nothing so this project doesn't break and we can run the tests.
|
||||
/// </summary>
|
||||
public class CachedImage : View
|
||||
{
|
||||
public static readonly BindableProperty SourceProperty = BindableProperty.Create(
|
||||
nameof(Source), typeof(ImageSource), typeof(CachedImage));
|
||||
|
||||
public static readonly BindableProperty AspectProperty = BindableProperty.Create(
|
||||
nameof(Aspect), typeof(Aspect), typeof(CachedImage));
|
||||
|
||||
public bool BitmapOptimizations { get; set; }
|
||||
public string ErrorPlaceholder { get; set; }
|
||||
public string LoadingPlaceholder { get; set; }
|
||||
|
||||
public ImageSource Source
|
||||
{
|
||||
get { return (ImageSource)GetValue(SourceProperty); }
|
||||
set { SetValue(SourceProperty, value); }
|
||||
}
|
||||
public Aspect Aspect
|
||||
{
|
||||
get { return (Aspect)GetValue(AspectProperty); }
|
||||
set { SetValue(AspectProperty, value); }
|
||||
}
|
||||
|
||||
public bool IsLoading { get; set; }
|
||||
|
||||
public event EventHandler Success;
|
||||
public event EventHandler Error;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -1,110 +0,0 @@
|
||||
using Bit.App.Pages;
|
||||
|
||||
namespace Bit.App.Controls
|
||||
{
|
||||
public abstract class BaseCipherViewCell : ExtendedGrid
|
||||
{
|
||||
protected virtual CachedImage Icon { get; }
|
||||
|
||||
protected virtual IconLabel IconPlaceholder { get; }
|
||||
|
||||
// HACK: PM-5896 Fix for Background Crash on iOS
|
||||
// While loading the cipher icon and the user sent the app to background
|
||||
// the app was crashing sometimes when the "LoadingPlaceholder" or "ErrorPlaceholder"
|
||||
// were being accessed, thus locked, and as soon the app got suspended by the OS
|
||||
// the app would crash because there can't be any lock files by the app when it gets suspended.
|
||||
// So, the approach has changed to reuse the IconLabel default icon to use it for these placeholders
|
||||
// as well. In order to do that both icon controls change their visibility dynamically here reacting to
|
||||
// CachedImage events and binding context changes.
|
||||
|
||||
protected override void OnBindingContextChanged()
|
||||
{
|
||||
Icon.Source = null;
|
||||
if (BindingContext is CipherItemViewModel cipherItemVM)
|
||||
{
|
||||
Icon.Source = cipherItemVM.IconImageSource;
|
||||
if (!cipherItemVM.IconImageSuccesfullyLoaded)
|
||||
{
|
||||
UpdateIconImages(cipherItemVM.ShowIconImage);
|
||||
}
|
||||
}
|
||||
|
||||
base.OnBindingContextChanged();
|
||||
}
|
||||
|
||||
private void UpdateIconImages(bool showIcon)
|
||||
{
|
||||
MainThread.BeginInvokeOnMainThread(() =>
|
||||
{
|
||||
if (!showIcon)
|
||||
{
|
||||
Icon.IsVisible = false;
|
||||
IconPlaceholder.IsVisible = true;
|
||||
return;
|
||||
}
|
||||
|
||||
IconPlaceholder.IsVisible = Icon.IsLoading;
|
||||
});
|
||||
}
|
||||
|
||||
#if !UT
|
||||
public void Icon_Success(object sender, FFImageLoading.Maui.CachedImageEvents.SuccessEventArgs e)
|
||||
{
|
||||
if (BindingContext is CipherItemViewModel cipherItemVM)
|
||||
{
|
||||
cipherItemVM.IconImageSuccesfullyLoaded = true;
|
||||
}
|
||||
MainThread.BeginInvokeOnMainThread(() =>
|
||||
{
|
||||
Icon.IsVisible = true;
|
||||
IconPlaceholder.IsVisible = false;
|
||||
});
|
||||
}
|
||||
|
||||
public void Icon_Error(object sender, FFImageLoading.Maui.CachedImageEvents.ErrorEventArgs e)
|
||||
{
|
||||
if (BindingContext is CipherItemViewModel cipherItemVM)
|
||||
{
|
||||
cipherItemVM.IconImageSuccesfullyLoaded = false;
|
||||
}
|
||||
MainThread.BeginInvokeOnMainThread(() =>
|
||||
{
|
||||
Icon.IsVisible = false;
|
||||
IconPlaceholder.IsVisible = true;
|
||||
});
|
||||
}
|
||||
#else
|
||||
private void Icon_Success(object sender, EventArgs e) {}
|
||||
private void Icon_Error(object sender, EventArgs e) {}
|
||||
#endif
|
||||
}
|
||||
|
||||
public class StubBaseCipherViewCellSoLinkerDoesntRemoveMethods : BaseCipherViewCell
|
||||
{
|
||||
protected override CachedImage Icon => new CachedImage();
|
||||
protected override IconLabel IconPlaceholder => new IconLabel();
|
||||
|
||||
public static void CallThisSoLinkerDoesntRemoveMethods()
|
||||
{
|
||||
#if !UT
|
||||
var stub = new StubBaseCipherViewCellSoLinkerDoesntRemoveMethods();
|
||||
|
||||
try
|
||||
{
|
||||
stub.Icon_Success(stub, new FFImageLoading.Maui.CachedImageEvents.SuccessEventArgs(new FFImageLoading.Work.ImageInformation(), FFImageLoading.Work.LoadingResult.Disk));
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
stub.Icon_Error(stub, new FFImageLoading.Maui.CachedImageEvents.ErrorEventArgs(new InvalidOperationException("stub")));
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<controls:BaseCipherViewCell xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||
<controls:ExtendedGrid xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
x:Class="Bit.App.Controls.CipherViewCell"
|
||||
xmlns:controls="clr-namespace:Bit.App.Controls"
|
||||
xmlns:pages="clr-namespace:Bit.App.Pages"
|
||||
xmlns:u="clr-namespace:Bit.App.Utilities"
|
||||
xmlns:ff="clr-namespace:FFImageLoading.Maui;assembly=FFImageLoading.Compat.Maui"
|
||||
xmlns:core="clr-namespace:Bit.Core"
|
||||
StyleClass="list-row, list-row-platform"
|
||||
RowSpacing="0"
|
||||
@@ -29,32 +30,34 @@
|
||||
<ColumnDefinition Width="60" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<controls:CachedImage
|
||||
<controls:IconLabel
|
||||
Grid.Column="0"
|
||||
HorizontalOptions="Center"
|
||||
VerticalOptions="Center"
|
||||
StyleClass="list-icon, list-icon-platform"
|
||||
IsVisible="{Binding ShowIconImage, Converter={StaticResource inverseBool}}"
|
||||
Text="{Binding Cipher, Converter={StaticResource iconGlyphConverter}}"
|
||||
ShouldUpdateFontSizeDynamicallyForAccesibility="True"
|
||||
AutomationProperties.IsInAccessibleTree="False"
|
||||
AutomationId="CipherTypeIcon" />
|
||||
|
||||
<ff:CachedImage
|
||||
x:Name="_iconImage"
|
||||
Grid.Column="0"
|
||||
BitmapOptimizations="True"
|
||||
ErrorPlaceholder="login.png"
|
||||
LoadingPlaceholder="login.png"
|
||||
HorizontalOptions="CenterAndExpand"
|
||||
VerticalOptions="CenterAndExpand"
|
||||
Margin="9"
|
||||
WidthRequest="22"
|
||||
HeightRequest="22"
|
||||
Aspect="AspectFit"
|
||||
Success="Icon_Success"
|
||||
Error="Icon_Error"
|
||||
IsVisible="{Binding ShowIconImage}"
|
||||
Source="{Binding IconImageSource, Mode=OneTime}"
|
||||
AutomationProperties.IsInAccessibleTree="False"
|
||||
AutomationId="CipherWebsiteIcon" />
|
||||
|
||||
<controls:IconLabel
|
||||
x:Name="_iconPlaceholderImage"
|
||||
Grid.Column="0"
|
||||
HorizontalOptions="Center"
|
||||
VerticalOptions="Center"
|
||||
StyleClass="list-icon, list-icon-platform"
|
||||
Text="{Binding Cipher, Converter={StaticResource iconGlyphConverter}}"
|
||||
ShouldUpdateFontSizeDynamicallyForAccesibility="True"
|
||||
AutomationProperties.IsInAccessibleTree="False"
|
||||
AutomationId="CipherTypeIcon" />
|
||||
|
||||
<Grid RowSpacing="0" ColumnSpacing="0" Grid.Row="0" Grid.Column="1" VerticalOptions="Center" Padding="0, 7">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
@@ -119,4 +122,4 @@
|
||||
SemanticProperties.Description="{u:I18n Options}"
|
||||
AutomationId="CipherOptionsButton" />
|
||||
|
||||
</controls:BaseCipherViewCell>
|
||||
</controls:ExtendedGrid>
|
||||
@@ -5,7 +5,7 @@ using Bit.Core.Utilities;
|
||||
|
||||
namespace Bit.App.Controls
|
||||
{
|
||||
public partial class CipherViewCell : BaseCipherViewCell
|
||||
public partial class CipherViewCell : ExtendedGrid
|
||||
{
|
||||
private const int ICON_COLUMN_DEFAULT_WIDTH = 40;
|
||||
private const int ICON_IMAGE_DEFAULT_WIDTH = 22;
|
||||
@@ -23,10 +23,6 @@ namespace Bit.App.Controls
|
||||
_iconImage.HeightRequest = ICON_IMAGE_DEFAULT_WIDTH * fontScale;
|
||||
}
|
||||
|
||||
protected override CachedImage Icon => _iconImage;
|
||||
|
||||
protected override IconLabel IconPlaceholder => _iconPlaceholderImage;
|
||||
|
||||
public ICommand ButtonCommand
|
||||
{
|
||||
get => GetValue(ButtonCommandProperty) as ICommand;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<ContentView
|
||||
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
@@ -17,7 +17,7 @@
|
||||
LineBreakMode="TailTruncation" />
|
||||
|
||||
<controls:IconLabel
|
||||
Text="{Binding Source={x:Static core:BitwardenIcons.ExternalLink}}"
|
||||
Text="{Binding Source={x:Static core:BitwardenIcons.ShareSquare}}"
|
||||
TextColor="{DynamicResource TextColor}"
|
||||
HorizontalOptions="End"
|
||||
VerticalOptions="Center"
|
||||
|
||||
@@ -1,299 +0,0 @@
|
||||
#if ANDROID
|
||||
using System;
|
||||
using System.Collections.Specialized;
|
||||
using Android.App;
|
||||
using Android.Content.PM;
|
||||
using Android.Content.Res;
|
||||
using Android.Graphics.Drawables;
|
||||
using Android.Text;
|
||||
using Android.Text.Style;
|
||||
using Android.Widget;
|
||||
using Microsoft.Maui;
|
||||
using Microsoft.Maui.Handlers;
|
||||
using Microsoft.Maui.Platform;
|
||||
using AGravityFlags = Android.Views.GravityFlags;
|
||||
using ALayoutDirection = Android.Views.LayoutDirection;
|
||||
using AppCompatAlertDialog = AndroidX.AppCompat.App.AlertDialog;
|
||||
using AResource = Android.Resource;
|
||||
using ATextAlignment = Android.Views.TextAlignment;
|
||||
using ATextDirection = Android.Views.TextDirection;
|
||||
|
||||
namespace Bit.Core.Controls.Picker
|
||||
{
|
||||
// HACK: Due to https://github.com/dotnet/maui/issues/19681 and not willing to use reflection to access
|
||||
// the alert dialog, we need to redefine the PickerHandler implementation for a custom one of ours
|
||||
// which handles showing the current selected item. Remove this workaround when MAUI releases a fix for this.
|
||||
// This is an adapted copy from https://github.com/dotnet/maui/blob/main/src/Core/src/Handlers/Picker/PickerHandler.Android.cs
|
||||
public partial class PickerHandler : ViewHandler<IPicker, MauiPicker>
|
||||
{
|
||||
AppCompatAlertDialog? _dialog;
|
||||
|
||||
protected override MauiPicker CreatePlatformView() =>
|
||||
new MauiPicker(Context);
|
||||
|
||||
protected override void ConnectHandler(MauiPicker platformView)
|
||||
{
|
||||
platformView.FocusChange += OnFocusChange;
|
||||
platformView.Click += OnClick;
|
||||
|
||||
base.ConnectHandler(platformView);
|
||||
}
|
||||
|
||||
protected override void DisconnectHandler(MauiPicker platformView)
|
||||
{
|
||||
platformView.FocusChange -= OnFocusChange;
|
||||
platformView.Click -= OnClick;
|
||||
|
||||
base.DisconnectHandler(platformView);
|
||||
}
|
||||
|
||||
// This is a Android-specific mapping
|
||||
public static void MapBackground(IPickerHandler handler, IPicker picker)
|
||||
{
|
||||
handler.PlatformView?.UpdateBackground(picker);
|
||||
}
|
||||
|
||||
// TODO Uncomment me on NET8 [Obsolete]
|
||||
public static void MapReload(IPickerHandler handler, IPicker picker, object? args) => Reload(handler);
|
||||
|
||||
internal static void MapItems(IPickerHandler handler, IPicker picker) => Reload(handler);
|
||||
|
||||
public static void MapTitle(IPickerHandler handler, IPicker picker)
|
||||
{
|
||||
handler.PlatformView?.UpdateTitle(picker);
|
||||
}
|
||||
|
||||
public static void MapTitleColor(IPickerHandler handler, IPicker picker)
|
||||
{
|
||||
handler.PlatformView?.UpdateTitleColor(picker);
|
||||
}
|
||||
|
||||
public static void MapSelectedIndex(IPickerHandler handler, IPicker picker)
|
||||
{
|
||||
handler.PlatformView?.UpdateSelectedIndex(picker);
|
||||
}
|
||||
|
||||
public static void MapCharacterSpacing(IPickerHandler handler, IPicker picker)
|
||||
{
|
||||
handler.PlatformView?.UpdateCharacterSpacing(picker);
|
||||
}
|
||||
|
||||
public static void MapFont(IPickerHandler handler, IPicker picker)
|
||||
{
|
||||
var fontManager = handler.GetRequiredService<IFontManager>();
|
||||
|
||||
handler.PlatformView?.UpdateFont(picker, fontManager);
|
||||
}
|
||||
|
||||
public static void MapHorizontalTextAlignment(IPickerHandler handler, IPicker picker)
|
||||
{
|
||||
handler.PlatformView?.UpdateHorizontalAlignment(picker.HorizontalTextAlignment);
|
||||
}
|
||||
|
||||
public static void MapTextColor(IPickerHandler handler, IPicker picker)
|
||||
{
|
||||
handler.PlatformView.UpdateTextColor(picker);
|
||||
}
|
||||
|
||||
public static void MapVerticalTextAlignment(IPickerHandler handler, IPicker picker)
|
||||
{
|
||||
handler.PlatformView?.UpdateVerticalAlignment(picker.VerticalTextAlignment);
|
||||
}
|
||||
|
||||
void OnFocusChange(object? sender, global::Android.Views.View.FocusChangeEventArgs e)
|
||||
{
|
||||
if (PlatformView == null)
|
||||
return;
|
||||
|
||||
if (e.HasFocus)
|
||||
{
|
||||
if (PlatformView.Clickable)
|
||||
PlatformView.CallOnClick();
|
||||
else
|
||||
OnClick(PlatformView, EventArgs.Empty);
|
||||
}
|
||||
else if (_dialog != null)
|
||||
{
|
||||
_dialog.Hide();
|
||||
_dialog = null;
|
||||
}
|
||||
}
|
||||
|
||||
void OnClick(object? sender, EventArgs e)
|
||||
{
|
||||
if (_dialog == null && VirtualView != null)
|
||||
{
|
||||
using (var builder = new AppCompatAlertDialog.Builder(Context))
|
||||
{
|
||||
if (VirtualView.TitleColor == null)
|
||||
{
|
||||
builder.SetTitle(VirtualView.Title ?? string.Empty);
|
||||
}
|
||||
else
|
||||
{
|
||||
var title = new SpannableString(VirtualView.Title ?? string.Empty);
|
||||
#pragma warning disable CA1416 // https://github.com/xamarin/xamarin-android/issues/6962
|
||||
title.SetSpan(new ForegroundColorSpan(VirtualView.TitleColor.ToPlatform()), 0, title.Length(), SpanTypes.ExclusiveExclusive);
|
||||
#pragma warning restore CA1416
|
||||
builder.SetTitle(title);
|
||||
}
|
||||
|
||||
string[] items = VirtualView.GetItemsAsArray();
|
||||
|
||||
for (var i = 0; i < items.Length; i++)
|
||||
{
|
||||
var item = items[i];
|
||||
if (item == null)
|
||||
items[i] = String.Empty;
|
||||
}
|
||||
|
||||
builder.SetSingleChoiceItems(items, VirtualView.SelectedIndex, (s, e) =>
|
||||
{
|
||||
var selectedIndex = e.Which;
|
||||
VirtualView.SelectedIndex = selectedIndex;
|
||||
base.PlatformView?.UpdatePicker(VirtualView);
|
||||
_dialog.Dismiss();
|
||||
});
|
||||
|
||||
builder.SetNegativeButton(AResource.String.Cancel, (o, args) => { });
|
||||
|
||||
_dialog = builder.Create();
|
||||
}
|
||||
|
||||
if (_dialog == null)
|
||||
return;
|
||||
|
||||
_dialog.UpdateFlowDirection(PlatformView);
|
||||
|
||||
_dialog.SetCanceledOnTouchOutside(true);
|
||||
|
||||
_dialog.DismissEvent += (sender, args) =>
|
||||
{
|
||||
_dialog = null;
|
||||
};
|
||||
|
||||
_dialog.Show();
|
||||
}
|
||||
}
|
||||
|
||||
static void Reload(IPickerHandler handler)
|
||||
{
|
||||
handler.PlatformView.UpdatePicker(handler.VirtualView);
|
||||
}
|
||||
}
|
||||
|
||||
public static class PickerExtensions
|
||||
{
|
||||
const AGravityFlags HorizontalGravityMask = AGravityFlags.CenterHorizontal | AGravityFlags.End | AGravityFlags.Start;
|
||||
|
||||
internal static void UpdatePicker(this MauiPicker platformPicker, IPicker picker)
|
||||
{
|
||||
platformPicker.Hint = picker.Title;
|
||||
|
||||
if (picker.SelectedIndex == -1 || picker.SelectedIndex >= picker.GetCount())
|
||||
platformPicker.Text = null;
|
||||
else
|
||||
platformPicker.Text = picker.GetItem(picker.SelectedIndex);
|
||||
}
|
||||
|
||||
internal static void UpdateHorizontalAlignment(this EditText view, TextAlignment alignment, AGravityFlags orMask = AGravityFlags.NoGravity)
|
||||
{
|
||||
if (!Rtl.IsSupported)
|
||||
{
|
||||
view.Gravity = (view.Gravity & ~HorizontalGravityMask) | alignment.ToHorizontalGravityFlags() | orMask;
|
||||
}
|
||||
else
|
||||
view.TextAlignment = alignment.ToTextAlignment();
|
||||
}
|
||||
|
||||
internal static AGravityFlags ToHorizontalGravityFlags(this TextAlignment alignment)
|
||||
{
|
||||
switch (alignment)
|
||||
{
|
||||
case TextAlignment.Center:
|
||||
return AGravityFlags.CenterHorizontal;
|
||||
case TextAlignment.End:
|
||||
return AGravityFlags.End;
|
||||
default:
|
||||
return AGravityFlags.Start;
|
||||
}
|
||||
}
|
||||
|
||||
internal static ATextAlignment ToTextAlignment(this TextAlignment alignment)
|
||||
{
|
||||
switch (alignment)
|
||||
{
|
||||
case TextAlignment.Center:
|
||||
return ATextAlignment.Center;
|
||||
case TextAlignment.End:
|
||||
return ATextAlignment.ViewEnd;
|
||||
default:
|
||||
return ATextAlignment.ViewStart;
|
||||
}
|
||||
}
|
||||
|
||||
internal static void UpdateFlowDirection(this AndroidX.AppCompat.App.AlertDialog alertDialog, MauiPicker platformPicker)
|
||||
{
|
||||
var platformLayoutDirection = platformPicker.LayoutDirection;
|
||||
|
||||
// Propagate the MauiPicker LayoutDirection to the AlertDialog
|
||||
var dv = alertDialog.Window?.DecorView;
|
||||
|
||||
if (dv is not null)
|
||||
dv.LayoutDirection = platformLayoutDirection;
|
||||
|
||||
var lv = alertDialog?.ListView;
|
||||
|
||||
if (lv is not null)
|
||||
{
|
||||
lv.LayoutDirection = platformLayoutDirection;
|
||||
lv.TextDirection = platformLayoutDirection.ToTextDirection();
|
||||
}
|
||||
}
|
||||
|
||||
internal static ATextDirection ToTextDirection(this ALayoutDirection direction)
|
||||
{
|
||||
switch (direction)
|
||||
{
|
||||
case ALayoutDirection.Ltr:
|
||||
return ATextDirection.Ltr;
|
||||
case ALayoutDirection.Rtl:
|
||||
return ATextDirection.Rtl;
|
||||
default:
|
||||
return ATextDirection.Inherit;
|
||||
}
|
||||
}
|
||||
|
||||
public static T GetRequiredService<T>(this IElementHandler handler)
|
||||
where T : notnull
|
||||
{
|
||||
var services = handler.GetServiceProvider();
|
||||
|
||||
var service = services.GetRequiredService<T>();
|
||||
|
||||
return service;
|
||||
}
|
||||
|
||||
public static IServiceProvider GetServiceProvider(this IElementHandler handler)
|
||||
{
|
||||
var context = handler.MauiContext ??
|
||||
throw new InvalidOperationException($"Unable to find the context. The {nameof(ElementHandler.MauiContext)} property should have been set by the host.");
|
||||
|
||||
var services = context?.Services ??
|
||||
throw new InvalidOperationException($"Unable to find the service provider. The {nameof(ElementHandler.MauiContext)} property should have been set by the host.");
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
|
||||
static class Rtl
|
||||
{
|
||||
/// <summary>
|
||||
/// True if /manifest/application@android:supportsRtl="true"
|
||||
/// </summary>
|
||||
public static readonly bool IsSupported =
|
||||
(Android.App.Application.Context?.ApplicationInfo?.Flags & ApplicationInfoFlags.SupportsRtl) != 0;
|
||||
}
|
||||
|
||||
}
|
||||
#endif
|
||||
@@ -1,53 +0,0 @@
|
||||
#if ANDROID
|
||||
using Microsoft.Maui.Handlers;
|
||||
using Microsoft.Maui.Platform;
|
||||
|
||||
namespace Bit.Core.Controls.Picker
|
||||
{
|
||||
// HACK: Due to https://github.com/dotnet/maui/issues/19681 and not willing to use reflection to access
|
||||
// the alert dialog, we need to redefine the PickerHandler implementation for a custom one of ours
|
||||
// which handles showing the current selected item. Remove this workaround when MAUI releases a fix for this.
|
||||
// This is a copy from https://github.com/dotnet/maui/blob/main/src/Core/src/Handlers/Picker/PickerHandler.cs
|
||||
public partial class PickerHandler : ViewHandler<IPicker, MauiPicker>, IPickerHandler
|
||||
{
|
||||
public static IPropertyMapper<IPicker, IPickerHandler> Mapper = new PropertyMapper<IPicker, PickerHandler>(ViewMapper)
|
||||
{
|
||||
#if __ANDROID__ || WINDOWS
|
||||
[nameof(IPicker.Background)] = MapBackground,
|
||||
#endif
|
||||
[nameof(IPicker.CharacterSpacing)] = MapCharacterSpacing,
|
||||
[nameof(IPicker.Font)] = MapFont,
|
||||
[nameof(IPicker.SelectedIndex)] = MapSelectedIndex,
|
||||
[nameof(IPicker.TextColor)] = MapTextColor,
|
||||
[nameof(IPicker.Title)] = MapTitle,
|
||||
[nameof(IPicker.TitleColor)] = MapTitleColor,
|
||||
[nameof(ITextAlignment.HorizontalTextAlignment)] = MapHorizontalTextAlignment,
|
||||
[nameof(ITextAlignment.VerticalTextAlignment)] = MapVerticalTextAlignment,
|
||||
[nameof(IPicker.Items)] = MapItems,
|
||||
};
|
||||
|
||||
public static CommandMapper<IPicker, IPickerHandler> CommandMapper = new(ViewCommandMapper)
|
||||
{
|
||||
};
|
||||
|
||||
public PickerHandler() : base(Mapper, CommandMapper)
|
||||
{
|
||||
}
|
||||
|
||||
public PickerHandler(IPropertyMapper? mapper)
|
||||
: base(mapper ?? Mapper, CommandMapper)
|
||||
{
|
||||
}
|
||||
|
||||
public PickerHandler(IPropertyMapper? mapper, CommandMapper? commandMapper)
|
||||
: base(mapper ?? Mapper, commandMapper ?? CommandMapper)
|
||||
{
|
||||
}
|
||||
|
||||
IPicker IPickerHandler.VirtualView => VirtualView;
|
||||
|
||||
Microsoft.Maui.Platform.MauiPicker IPickerHandler.PlatformView => PlatformView;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,8 +1,6 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<!--When running Unit tests we'll have the custom constant "UT" added, so in this manner we can add the net8.0 target we need for UT -->
|
||||
<TargetFrameworks Condition="$(CustomConstants.Contains(UT))">net8.0;net8.0-android;net8.0-ios</TargetFrameworks>
|
||||
<TargetFrameworks Condition="!$(CustomConstants.Contains(UT))">net8.0-android;net8.0-ios</TargetFrameworks>
|
||||
<TargetFrameworks>net8.0-android;net8.0-ios</TargetFrameworks>
|
||||
<RootNamespace>Bit.Core</RootNamespace>
|
||||
|
||||
<UseMaui>true</UseMaui>
|
||||
@@ -43,11 +41,10 @@
|
||||
<PackageReference Include="Plugin.Fingerprint" Version="3.0.0-beta.1" />
|
||||
<PackageReference Include="SkiaSharp.Views.Maui.Controls" Version="2.88.4-preview.84" />
|
||||
<PackageReference Include="SkiaSharp.Views.Maui.Controls.Compatibility" Version="2.88.4-preview.84" />
|
||||
<PackageReference Include="FFImageLoadingCompat.Maui" Version="0.1.1" />
|
||||
<PackageReference Include="AsyncAwaitBestPractices.MVVM" Version="6.0.6" />
|
||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.1" />
|
||||
<PackageReference Include="Portable.BouncyCastle" Version="1.9.0" />
|
||||
<!-- HACK: When running Unit Tests we cannot load FFImageLoading because it doesn't support "raw" net8.0 -->
|
||||
<PackageReference Condition="!$(CustomConstants.Contains(UT))" Include="FFImageLoadingCompat.Maui" Version="0.1.1" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">
|
||||
<PackageReference Include="Xamarin.AndroidX.AutoFill" Version="1.1.0.18" />
|
||||
@@ -67,6 +64,7 @@
|
||||
<Folder Include="Resources\Fonts\" />
|
||||
<Folder Include="Effects\" />
|
||||
<Folder Include="Resources\Raw\" />
|
||||
<Folder Include="Pages\" />
|
||||
<Folder Include="Behaviors\" />
|
||||
<Folder Include="Controls\" />
|
||||
<Folder Include="Lists\" />
|
||||
@@ -75,8 +73,6 @@
|
||||
<Folder Include="Utilities\Automation\" />
|
||||
<Folder Include="Utilities\Prompts\" />
|
||||
<Folder Include="Resources\Localization\" />
|
||||
<Folder Include="Controls\Picker\" />
|
||||
<Folder Include="Controls\Avatar\" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<MauiImage Include="Resources\Images\dotnet_bot.svg">
|
||||
@@ -90,22 +86,10 @@
|
||||
<LastGenOutput>AppResources.Designer.cs</LastGenOutput>
|
||||
<Generator>PublicResXFileCodeGenerator</Generator>
|
||||
</EmbeddedResource>
|
||||
<Compile Update="Pages\AndroidNavigationRedirectPage.xaml.cs">
|
||||
<DependentUpon>AndroidNavigationRedirectPage.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Resources\Localization\AppResources.Designer.cs">
|
||||
<DependentUpon>AppResources.resx</DependentUpon>
|
||||
<DesignTime>True</DesignTime>
|
||||
<AutoGen>True</AutoGen>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<MauiXaml Update="Pages\AndroidNavigationRedirectPage.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</MauiXaml>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Remove="Controls\Picker\" />
|
||||
<None Remove="Controls\Avatar\" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -6,18 +6,13 @@
|
||||
public const string HELP_ABOUT_ORGANIZATIONS = "https://bitwarden.com/help/about-organizations/";
|
||||
public const string HELP_FINGERPRINT_PHRASE = "https://bitwarden.com/help/fingerprint-phrase/";
|
||||
|
||||
public const string PRIVACY_POLICY = "https://bitwarden.com/privacy/";
|
||||
public const string CONTACT_SUPPORT = "https://bitwarden.com/contact/";
|
||||
|
||||
/// <summary>
|
||||
/// Link to go to settings website. Requires to pass website URL as parameter.
|
||||
/// </summary>
|
||||
public const string WEB_VAULT_SETTINGS_FORMAT = "{0}/#/settings";
|
||||
|
||||
/// <summary>
|
||||
/// Link to go to individual vault import page. Requires to pass vault URL as parameter.
|
||||
/// </summary>
|
||||
public const string WEB_VAULT_TOOLS_IMPORT_FORMAT = "{0}/#/tools/import";
|
||||
|
||||
/// <summary>
|
||||
/// General website, not in the full format of a URL given that this is used as parameter of string resources to be shown to the user.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,12 +1,8 @@
|
||||
using Bit.App.Controls;
|
||||
using Camera.MAUI;
|
||||
using Camera.MAUI;
|
||||
using CommunityToolkit.Maui;
|
||||
#if !UT
|
||||
using FFImageLoading.Maui;
|
||||
#endif
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Maui.Controls.Compatibility.Hosting;
|
||||
using Microsoft.Maui.Handlers;
|
||||
using SkiaSharp.Views.Maui.Controls.Hosting;
|
||||
using AppEffects = Bit.App.Effects;
|
||||
|
||||
@@ -26,9 +22,7 @@ public static class MauiProgram
|
||||
.UseMauiCompatibility()
|
||||
.UseMauiCameraView()
|
||||
.UseSkiaSharp()
|
||||
#if !UT
|
||||
.UseFFImageLoading()
|
||||
#endif
|
||||
.ConfigureEffects(effects =>
|
||||
{
|
||||
#if ANDROID
|
||||
@@ -46,16 +40,6 @@ public static class MauiProgram
|
||||
})
|
||||
.ConfigureMauiHandlers(handlers =>
|
||||
{
|
||||
#if ANDROID
|
||||
// HACK: Due to https://github.com/dotnet/maui/issues/19681 and not willing to use reflection to access
|
||||
// the alert dialog, we need to redefine the PickerHandler implementation for a custom one of ours
|
||||
// which handles showing the current selected item. Remove this workaround when MAUI releases a fix for this.
|
||||
if (handlers.FirstOrDefault(h => h.ServiceType == typeof(Picker)) is ServiceDescriptor sd)
|
||||
{
|
||||
handlers.Remove(sd);
|
||||
handlers.AddHandler(typeof(IPicker), typeof(Controls.Picker.PickerHandler));
|
||||
}
|
||||
#endif
|
||||
customHandlers?.Invoke(handlers);
|
||||
});
|
||||
|
||||
@@ -63,13 +47,6 @@ public static class MauiProgram
|
||||
builder.Logging.AddDebug();
|
||||
#endif
|
||||
|
||||
ExplicitlyPreventThingsGetRemovedBecauseOfLinker();
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
private static void ExplicitlyPreventThingsGetRemovedBecauseOfLinker()
|
||||
{
|
||||
StubBaseCipherViewCellSoLinkerDoesntRemoveMethods.CallThisSoLinkerDoesntRemoveMethods();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,5 @@ namespace Bit.Core.Models.Api
|
||||
{
|
||||
public string Uri { get; set; }
|
||||
public UriMatchType? Match { get; set; }
|
||||
public string UriChecksum { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,11 +11,9 @@ namespace Bit.Core.Models.Data
|
||||
{
|
||||
Uri = data.Uri;
|
||||
Match = data.Match;
|
||||
UriChecksum = data.UriChecksum;
|
||||
}
|
||||
|
||||
public string Uri { get; set; }
|
||||
public UriMatchType? Match { get; set; }
|
||||
public string UriChecksum { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,7 +115,7 @@ namespace Bit.Core.Models.Domain
|
||||
switch (Type)
|
||||
{
|
||||
case Enums.CipherType.Login:
|
||||
model.Login = await Login.DecryptAsync(OrganizationId, Key == null, model.Key);
|
||||
model.Login = await Login.DecryptAsync(OrganizationId, model.Key);
|
||||
break;
|
||||
case Enums.CipherType.SecureNote:
|
||||
model.SecureNote = await SecureNote.DecryptAsync(OrganizationId, model.Key);
|
||||
|
||||
@@ -2,10 +2,8 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Models.Data;
|
||||
using Bit.Core.Models.View;
|
||||
using Bit.Core.Utilities;
|
||||
|
||||
namespace Bit.Core.Models.Domain
|
||||
{
|
||||
@@ -33,7 +31,7 @@ namespace Bit.Core.Models.Domain
|
||||
public EncString Totp { get; set; }
|
||||
public List<Fido2Credential> Fido2Credentials { get; set; }
|
||||
|
||||
public async Task<LoginView> DecryptAsync(string orgId, bool bypassUriChecksumValidation, SymmetricCryptoKey key = null)
|
||||
public async Task<LoginView> DecryptAsync(string orgId, SymmetricCryptoKey key = null)
|
||||
{
|
||||
var view = await DecryptObjAsync(new LoginView(this), this, new HashSet<string>
|
||||
{
|
||||
@@ -43,15 +41,10 @@ namespace Bit.Core.Models.Domain
|
||||
}, orgId, key);
|
||||
if (Uris != null)
|
||||
{
|
||||
var cryptoService = ServiceContainer.Resolve<ICryptoService>();
|
||||
view.Uris = new List<LoginUriView>();
|
||||
foreach (var uri in Uris)
|
||||
{
|
||||
var loginUriView = await uri.DecryptAsync(orgId, key);
|
||||
if (bypassUriChecksumValidation || await cryptoService.ValidateUriChecksumAsync(uri.UriChecksum, loginUriView.Uri, orgId, key))
|
||||
{
|
||||
view.Uris.Add(loginUriView);
|
||||
}
|
||||
view.Uris.Add(await uri.DecryptAsync(orgId, key));
|
||||
}
|
||||
}
|
||||
if (Fido2Credentials != null)
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.Data;
|
||||
@@ -11,8 +10,7 @@ namespace Bit.Core.Models.Domain
|
||||
{
|
||||
private HashSet<string> _map = new HashSet<string>
|
||||
{
|
||||
nameof(Uri),
|
||||
nameof(UriChecksum)
|
||||
"Uri"
|
||||
};
|
||||
|
||||
public LoginUri() { }
|
||||
@@ -25,11 +23,10 @@ namespace Bit.Core.Models.Domain
|
||||
|
||||
public EncString Uri { get; set; }
|
||||
public UriMatchType? Match { get; set; }
|
||||
public EncString UriChecksum { get; set; }
|
||||
|
||||
public Task<LoginUriView> DecryptAsync(string orgId, SymmetricCryptoKey key = null)
|
||||
{
|
||||
return DecryptObjAsync(new LoginUriView(this), this, _map.Where(m => m != nameof(UriChecksum)).ToHashSet<string>(), orgId, key);
|
||||
return DecryptObjAsync(new LoginUriView(this), this, _map, orgId, key);
|
||||
}
|
||||
|
||||
public LoginUriData ToLoginUriData()
|
||||
|
||||
@@ -17,12 +17,10 @@ namespace Bit.Core.Models.Export
|
||||
{
|
||||
Match = obj.Match;
|
||||
Uri = obj.Uri?.EncryptedString;
|
||||
UriChecksum = obj.UriChecksum?.EncryptedString;
|
||||
}
|
||||
|
||||
public UriMatchType? Match { get; set; }
|
||||
public string Uri { get; set; }
|
||||
public string UriChecksum { get; set; }
|
||||
|
||||
public static LoginUriView ToView(LoginUri req, LoginUriView view = null)
|
||||
{
|
||||
|
||||
@@ -27,7 +27,7 @@ namespace Bit.Core.Models.Request
|
||||
Login = new LoginApi
|
||||
{
|
||||
Uris = cipher.Login.Uris?.Select(
|
||||
u => new LoginUriApi { Match = u.Match, Uri = u.Uri?.EncryptedString, UriChecksum = u.UriChecksum?.EncryptedString }).ToList(),
|
||||
u => new LoginUriApi { Match = u.Match, Uri = u.Uri?.EncryptedString }).ToList(),
|
||||
Username = cipher.Login.Username?.EncryptedString,
|
||||
Password = cipher.Login.Password?.EncryptedString,
|
||||
PasswordRevisionDate = cipher.Login.PasswordRevisionDate,
|
||||
|
||||
@@ -61,7 +61,6 @@
|
||||
VerticalOptions="Start"
|
||||
Margin="0,20,6,0"
|
||||
Padding="16,0"
|
||||
MinimumHeightRequest="45"
|
||||
CornerRadius="2"
|
||||
TextTransform="Uppercase"
|
||||
Clicked="DeleteAccount_Clicked"/>
|
||||
@@ -74,7 +73,6 @@
|
||||
VerticalOptions="Start"
|
||||
Margin="0,20,0,0"
|
||||
Padding="16,0"
|
||||
MinimumHeightRequest="45"
|
||||
CornerRadius="2"
|
||||
TextTransform="Uppercase"
|
||||
Clicked="Close_Clicked" />
|
||||
|
||||
@@ -21,6 +21,10 @@
|
||||
<ScrollView>
|
||||
<StackLayout Spacing="20">
|
||||
<StackLayout StyleClass="box">
|
||||
<StackLayout StyleClass="box-row-header">
|
||||
<Label Text="MAUI APP"
|
||||
StyleClass="box-header, box-header-platform" />
|
||||
</StackLayout>
|
||||
<StackLayout StyleClass="box-row-header">
|
||||
<Label Text="{u:I18n SelfHostedEnvironment, Header=True}"
|
||||
StyleClass="box-header, box-header-platform" />
|
||||
@@ -33,7 +37,7 @@
|
||||
Text="{Binding BaseUrl}"
|
||||
Keyboard="Url"
|
||||
Placeholder="ex. https://bitwarden.company.com"
|
||||
StyleClass="box-value, no-keyboard-auto-help"
|
||||
StyleClass="box-value"
|
||||
ReturnType="Go"
|
||||
ReturnCommand="{Binding SubmitCommand}"
|
||||
AutomationId="ServerUrlEntry"/>
|
||||
@@ -55,7 +59,7 @@
|
||||
x:Name="_webVaultEntry"
|
||||
Text="{Binding WebVaultUrl}"
|
||||
Keyboard="Url"
|
||||
StyleClass="box-value, no-keyboard-auto-help"
|
||||
StyleClass="box-value"
|
||||
AutomationId="WebVaultUrlEntry"/>
|
||||
</StackLayout>
|
||||
<StackLayout StyleClass="box-row">
|
||||
@@ -66,7 +70,7 @@
|
||||
x:Name="_apiEntry"
|
||||
Text="{Binding ApiUrl}"
|
||||
Keyboard="Url"
|
||||
StyleClass="box-value, no-keyboard-auto-help"
|
||||
StyleClass="box-value"
|
||||
AutomationId="ApiUrlEntry"/>
|
||||
</StackLayout>
|
||||
<StackLayout StyleClass="box-row">
|
||||
@@ -77,7 +81,7 @@
|
||||
x:Name="_identityEntry"
|
||||
Text="{Binding IdentityUrl}"
|
||||
Keyboard="Url"
|
||||
StyleClass="box-value, no-keyboard-auto-help"
|
||||
StyleClass="box-value"
|
||||
AutomationId="IdentityUrlEntry"/>
|
||||
</StackLayout>
|
||||
<StackLayout StyleClass="box-row">
|
||||
@@ -88,7 +92,7 @@
|
||||
x:Name="_iconsEntry"
|
||||
Text="{Binding IconsUrl}"
|
||||
Keyboard="Url"
|
||||
StyleClass="box-value, no-keyboard-auto-help"
|
||||
StyleClass="box-value"
|
||||
ReturnType="Go"
|
||||
ReturnCommand="{Binding SubmitCommand}"
|
||||
AutomationId="IconsUrlEntry"/>
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
x:DataType="pages:HomeViewModel"
|
||||
HideSoftInputOnTapped="True"
|
||||
x:Name="_page"
|
||||
Loaded="HomePage_Loaded"
|
||||
Title="{Binding PageTitle}">
|
||||
<ContentPage.BindingContext>
|
||||
<pages:HomeViewModel />
|
||||
@@ -50,10 +49,11 @@
|
||||
x:Name="_email"
|
||||
Text="{Binding Email}"
|
||||
Keyboard="Email"
|
||||
StyleClass="box-value, no-keyboard-auto-help"
|
||||
StyleClass="box-value"
|
||||
ReturnType="Go"
|
||||
ReturnCommand="{Binding ContinueCommand}"
|
||||
AutomationId="EmailAddressEntry">
|
||||
AutomationId="EmailAddressEntry"
|
||||
>
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup x:Name="CommonStates">
|
||||
<VisualState x:Name="Disabled">
|
||||
@@ -72,7 +72,7 @@
|
||||
Command="{Binding ShowEnvironmentPickerCommand}" />
|
||||
</StackLayout.GestureRecognizers>
|
||||
<Label
|
||||
Margin="{OnPlatform Android='0,0,6,1', iOS='0,0,6,0'}"
|
||||
Margin="0,0,6,0"
|
||||
Text="{Binding RegionText}"
|
||||
FontSize="13"
|
||||
TextColor="{DynamicResource MutedColor}"
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.App.Models;
|
||||
using Bit.App.Models;
|
||||
using Bit.App.Utilities;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Utilities;
|
||||
using Microsoft.Maui.Platform;
|
||||
|
||||
namespace Bit.App.Pages
|
||||
{
|
||||
@@ -44,21 +44,6 @@ namespace Bit.App.Pages
|
||||
}
|
||||
}
|
||||
|
||||
public bool PerformNavigationOnAccountChangedOnLoad { get; internal set; }
|
||||
|
||||
void HomePage_Loaded(System.Object sender, System.EventArgs e)
|
||||
{
|
||||
#if ANDROID
|
||||
// WORKAROUND: This is needed to fix the navigation when coming back from autofill when Accessibility Services is enabled
|
||||
// See App.xaml.cs -> CreateWindow(...) for more info.
|
||||
if (PerformNavigationOnAccountChangedOnLoad && ServiceContainer.TryResolve<IAccountsManager>(out var accountsManager))
|
||||
{
|
||||
PerformNavigationOnAccountChangedOnLoad = false;
|
||||
accountsManager.NavigateOnAccountChangeAsync().FireAndForget();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
public async Task DismissRegisterPageAndLogInAsync(string email)
|
||||
{
|
||||
await Navigation.PopModalAsync();
|
||||
@@ -153,7 +138,7 @@ namespace Bit.App.Pages
|
||||
|
||||
private async Task StartEnvironmentAsync()
|
||||
{
|
||||
await _accountListOverlay.HideAsync();
|
||||
await _accountListOverlay.HideAsync();
|
||||
var page = new EnvironmentPage();
|
||||
await Navigation.PushModalAsync(new NavigationPage(page));
|
||||
}
|
||||
|
||||
@@ -165,7 +165,7 @@ namespace Bit.App.Pages
|
||||
|
||||
await MainThread.InvokeOnMainThreadAsync(async () =>
|
||||
{
|
||||
var result = await _deviceActionService.Value.DisplayActionSheetAsync(AppResources.LoggingInOn, AppResources.Cancel, null, options);
|
||||
var result = await Page.DisplayActionSheet(AppResources.LoggingInOn, AppResources.Cancel, null, options);
|
||||
|
||||
if (result is null || result == AppResources.Cancel)
|
||||
{
|
||||
|
||||
@@ -4,7 +4,6 @@ using Bit.App.Utilities;
|
||||
using Bit.Core;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Utilities;
|
||||
using Bit.Core.Services;
|
||||
|
||||
namespace Bit.App.Pages
|
||||
{
|
||||
@@ -27,24 +26,16 @@ namespace Bit.App.Pages
|
||||
_vm = BindingContext as LockPageViewModel;
|
||||
_vm.CheckPendingAuthRequests = checkPendingAuthRequests;
|
||||
_vm.Page = this;
|
||||
_vm.UnlockedAction = () => MainThread.BeginInvokeOnMainThread(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await UnlockedAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LoggerHelper.LogEvenIfCantBeResolved(ex);
|
||||
throw;
|
||||
}
|
||||
});
|
||||
_vm.UnlockedAction = () => MainThread.BeginInvokeOnMainThread(async () => await UnlockedAsync());
|
||||
|
||||
#if IOS
|
||||
ToolbarItems.Add(_moreItem);
|
||||
#else
|
||||
ToolbarItems.Add(_logOut);
|
||||
#endif
|
||||
if (DeviceInfo.Platform == DevicePlatform.iOS)
|
||||
{
|
||||
ToolbarItems.Add(_moreItem);
|
||||
}
|
||||
else
|
||||
{
|
||||
ToolbarItems.Add(_logOut);
|
||||
}
|
||||
}
|
||||
|
||||
public Entry SecretEntry
|
||||
@@ -74,60 +65,52 @@ namespace Bit.App.Pages
|
||||
|
||||
protected override async void OnAppearing()
|
||||
{
|
||||
try
|
||||
base.OnAppearing();
|
||||
_broadcasterService.Subscribe(nameof(LockPage), message =>
|
||||
{
|
||||
base.OnAppearing();
|
||||
_broadcasterService.Subscribe(nameof(LockPage), message =>
|
||||
if (message.Command == Constants.ClearSensitiveFields)
|
||||
{
|
||||
if (message.Command == Constants.ClearSensitiveFields)
|
||||
{
|
||||
MainThread.BeginInvokeOnMainThread(() => _vm?.ResetPinPasswordFields());
|
||||
}
|
||||
});
|
||||
if (_appeared)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_appeared = true;
|
||||
_mainContent.Content = _mainLayout;
|
||||
|
||||
//Workaround: This delay allows the Avatar to correctly load on iOS. The cause of this issue is also likely connected with the race conditions issue when using loading modals in iOS
|
||||
await Task.Delay(50);
|
||||
|
||||
_accountAvatar?.OnAppearing();
|
||||
|
||||
_vm.AvatarImageSource = await GetAvatarImageSourceAsync();
|
||||
|
||||
await _vm.InitAsync();
|
||||
|
||||
_vm.FocusSecretEntry += PerformFocusSecretEntry;
|
||||
|
||||
if (!_vm.BiometricEnabled)
|
||||
{
|
||||
RequestFocus(SecretEntry);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!_vm.HasMasterPassword && !_vm.PinEnabled)
|
||||
{
|
||||
_passwordGrid.IsVisible = false;
|
||||
_unlockButton.IsVisible = false;
|
||||
}
|
||||
if (_autoPromptBiometric)
|
||||
{
|
||||
var tasks = Task.Run(async () =>
|
||||
{
|
||||
await Task.Delay(500);
|
||||
await MainThread.InvokeOnMainThreadAsync(async () => await _vm.PromptBiometricAsync());
|
||||
});
|
||||
}
|
||||
MainThread.BeginInvokeOnMainThread(_vm.ResetPinPasswordFields);
|
||||
}
|
||||
});
|
||||
if (_appeared)
|
||||
{
|
||||
return;
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
_appeared = true;
|
||||
_mainContent.Content = _mainLayout;
|
||||
|
||||
//Workaround: This delay allows the Avatar to correctly load on iOS. The cause of this issue is also likely connected with the race conditions issue when using loading modals in iOS
|
||||
await Task.Delay(50);
|
||||
|
||||
_accountAvatar?.OnAppearing();
|
||||
|
||||
_vm.AvatarImageSource = await GetAvatarImageSourceAsync();
|
||||
|
||||
await _vm.InitAsync();
|
||||
|
||||
_vm.FocusSecretEntry += PerformFocusSecretEntry;
|
||||
|
||||
if (!_vm.BiometricEnabled)
|
||||
{
|
||||
LoggerHelper.LogEvenIfCantBeResolved(ex);
|
||||
throw;
|
||||
RequestFocus(SecretEntry);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!_vm.HasMasterPassword && !_vm.PinEnabled)
|
||||
{
|
||||
_passwordGrid.IsVisible = false;
|
||||
_unlockButton.IsVisible = false;
|
||||
}
|
||||
if (_autoPromptBiometric)
|
||||
{
|
||||
var tasks = Task.Run(async () =>
|
||||
{
|
||||
await Task.Delay(500);
|
||||
MainThread.BeginInvokeOnMainThread(async () => await _vm.PromptBiometricAsync());
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -184,44 +167,27 @@ namespace Bit.App.Pages
|
||||
|
||||
private async void Biometric_Clicked(object sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
if (DoOnce())
|
||||
{
|
||||
if (DoOnce())
|
||||
{
|
||||
await _vm.PromptBiometricAsync();
|
||||
}
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LoggerHelper.LogEvenIfCantBeResolved(ex);
|
||||
throw;
|
||||
await _vm.PromptBiometricAsync();
|
||||
}
|
||||
}
|
||||
|
||||
private async void More_Clicked(object sender, System.EventArgs e)
|
||||
{
|
||||
try
|
||||
await _accountListOverlay.HideAsync();
|
||||
|
||||
if (!DoOnce())
|
||||
{
|
||||
await _accountListOverlay.HideAsync();
|
||||
|
||||
if (!DoOnce())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var selection = await DisplayActionSheet(AppResources.Options,
|
||||
AppResources.Cancel, null, AppResources.LogOut);
|
||||
|
||||
if (selection == AppResources.LogOut)
|
||||
{
|
||||
await _vm.LogOutAsync();
|
||||
}
|
||||
return;
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
var selection = await DisplayActionSheet(AppResources.Options,
|
||||
AppResources.Cancel, null, AppResources.LogOut);
|
||||
|
||||
if (selection == AppResources.LogOut)
|
||||
{
|
||||
LoggerHelper.LogEvenIfCantBeResolved(ex);
|
||||
throw;
|
||||
await _vm.LogOutAsync();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -233,7 +199,7 @@ namespace Bit.App.Pages
|
||||
}
|
||||
var previousPage = await AppHelpers.ClearPreviousPage();
|
||||
|
||||
App.MainPage = new TabsPage(_appOptions, previousPage);
|
||||
Application.Current.MainPage = new TabsPage(_appOptions, previousPage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -245,9 +245,9 @@ namespace Bit.App.Pages
|
||||
|
||||
public async Task SubmitAsync()
|
||||
{
|
||||
ShowPassword = false;
|
||||
try
|
||||
{
|
||||
ShowPassword = false;
|
||||
var kdfConfig = await _stateService.GetActiveUserCustomDataAsync(a => new KdfConfig(a?.Profile));
|
||||
if (PinEnabled)
|
||||
{
|
||||
@@ -257,15 +257,12 @@ namespace Bit.App.Pages
|
||||
{
|
||||
await UnlockWithMasterPasswordAsync(kdfConfig);
|
||||
}
|
||||
|
||||
}
|
||||
catch (LegacyUserException)
|
||||
{
|
||||
await HandleLegacyUserAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
HandleException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task UnlockWithPinAsync(KdfConfig kdfConfig)
|
||||
|
||||
@@ -36,7 +36,7 @@ namespace Bit.App.Pages
|
||||
return;
|
||||
}
|
||||
var previousPage = await AppHelpers.ClearPreviousPage();
|
||||
App.MainPage = new TabsPage(_appOptions, previousPage);
|
||||
Application.Current.MainPage = new TabsPage(_appOptions, previousPage);
|
||||
}
|
||||
|
||||
private async Task StartLogInWithMasterPasswordAsync()
|
||||
|
||||
@@ -3,7 +3,6 @@ using Bit.App.Utilities;
|
||||
using Bit.Core;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Utilities;
|
||||
|
||||
namespace Bit.App.Pages
|
||||
@@ -75,7 +74,7 @@ namespace Bit.App.Pages
|
||||
{
|
||||
if (message.Command == Constants.ClearSensitiveFields)
|
||||
{
|
||||
MainThread.BeginInvokeOnMainThread(() => _vm?.ResetPasswordField());
|
||||
MainThread.BeginInvokeOnMainThread(_vm.ResetPasswordField);
|
||||
}
|
||||
});
|
||||
_mainContent.Content = _mainLayout;
|
||||
@@ -189,20 +188,12 @@ namespace Bit.App.Pages
|
||||
|
||||
private async Task LogInSuccessAsync()
|
||||
{
|
||||
try
|
||||
if (AppHelpers.SetAlternateMainPage(_appOptions))
|
||||
{
|
||||
if (AppHelpers.SetAlternateMainPage(_appOptions))
|
||||
{
|
||||
return;
|
||||
}
|
||||
var previousPage = await AppHelpers.ClearPreviousPage();
|
||||
App.MainPage = new TabsPage(_appOptions, previousPage);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LoggerHelper.LogEvenIfCantBeResolved(ex);
|
||||
throw;
|
||||
return;
|
||||
}
|
||||
var previousPage = await AppHelpers.ClearPreviousPage();
|
||||
Application.Current.MainPage = new TabsPage(_appOptions, previousPage);
|
||||
}
|
||||
|
||||
private async Task UpdateTempPasswordAsync()
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using Bit.App.Models;
|
||||
using Bit.App.Utilities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Services;
|
||||
|
||||
namespace Bit.App.Pages
|
||||
{
|
||||
@@ -49,20 +48,12 @@ namespace Bit.App.Pages
|
||||
|
||||
private async Task LogInSuccessAsync()
|
||||
{
|
||||
try
|
||||
if (AppHelpers.SetAlternateMainPage(_appOptions))
|
||||
{
|
||||
if (AppHelpers.SetAlternateMainPage(_appOptions))
|
||||
{
|
||||
return;
|
||||
}
|
||||
var previousPage = await AppHelpers.ClearPreviousPage();
|
||||
App.MainPage = new TabsPage(_appOptions, previousPage);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LoggerHelper.LogEvenIfCantBeResolved(ex);
|
||||
throw;
|
||||
return;
|
||||
}
|
||||
var previousPage = await AppHelpers.ClearPreviousPage();
|
||||
Application.Current.MainPage = new TabsPage(_appOptions, previousPage);
|
||||
}
|
||||
|
||||
private async Task UpdateTempPasswordAsync()
|
||||
|
||||
@@ -77,7 +77,6 @@ namespace Bit.App.Pages
|
||||
{
|
||||
_requestTimeCts?.Cancel();
|
||||
_requestTimeCts?.Dispose();
|
||||
_requestTimeCts = null;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using Bit.App.Models;
|
||||
using Bit.App.Utilities;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Utilities;
|
||||
|
||||
namespace Bit.App.Pages
|
||||
@@ -90,30 +89,16 @@ namespace Bit.App.Pages
|
||||
|
||||
private async Task StartTwoFactorAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
RestoreAppOptionsFromCopy();
|
||||
var page = new TwoFactorPage(true, _appOptions, _vm.OrgIdentifier);
|
||||
await Navigation.PushModalAsync(new NavigationPage(page));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LoggerHelper.LogEvenIfCantBeResolved(ex);
|
||||
}
|
||||
RestoreAppOptionsFromCopy();
|
||||
var page = new TwoFactorPage(true, _appOptions, _vm.OrgIdentifier);
|
||||
await Navigation.PushModalAsync(new NavigationPage(page));
|
||||
}
|
||||
|
||||
private async Task StartSetPasswordAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
RestoreAppOptionsFromCopy();
|
||||
var page = new SetPasswordPage(_appOptions, _vm.OrgIdentifier);
|
||||
await Navigation.PushModalAsync(new NavigationPage(page));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LoggerHelper.LogEvenIfCantBeResolved(ex);
|
||||
}
|
||||
RestoreAppOptionsFromCopy();
|
||||
var page = new SetPasswordPage(_appOptions, _vm.OrgIdentifier);
|
||||
await Navigation.PushModalAsync(new NavigationPage(page));
|
||||
}
|
||||
|
||||
private async Task UpdateTempPasswordAsync()
|
||||
@@ -130,23 +115,16 @@ namespace Bit.App.Pages
|
||||
|
||||
private async Task SsoAuthSuccessAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
RestoreAppOptionsFromCopy();
|
||||
await AppHelpers.ClearPreviousPage();
|
||||
RestoreAppOptionsFromCopy();
|
||||
await AppHelpers.ClearPreviousPage();
|
||||
|
||||
if (await _vaultTimeoutService.IsLockedAsync())
|
||||
{
|
||||
App.MainPage = new NavigationPage(new LockPage(_appOptions));
|
||||
}
|
||||
else
|
||||
{
|
||||
App.MainPage = new TabsPage(_appOptions, null);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
if (await _vaultTimeoutService.IsLockedAsync())
|
||||
{
|
||||
LoggerHelper.LogEvenIfCantBeResolved(ex);
|
||||
Application.Current.MainPage = new NavigationPage(new LockPage(_appOptions));
|
||||
}
|
||||
else
|
||||
{
|
||||
Application.Current.MainPage = new TabsPage(_appOptions, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@ using Bit.Core.Utilities;
|
||||
|
||||
using Microsoft.Maui.Authentication;
|
||||
using Microsoft.Maui.Networking;
|
||||
using NetworkAccess = Microsoft.Maui.Networking.NetworkAccess;
|
||||
|
||||
namespace Bit.App.Pages
|
||||
{
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using Bit.App.Models;
|
||||
using Bit.App.Utilities;
|
||||
using Bit.Core.Services;
|
||||
|
||||
namespace Bit.App.Pages
|
||||
{
|
||||
@@ -65,19 +64,12 @@ namespace Bit.App.Pages
|
||||
|
||||
private async Task SetPasswordSuccessAsync()
|
||||
{
|
||||
try
|
||||
if (AppHelpers.SetAlternateMainPage(_appOptions))
|
||||
{
|
||||
if (AppHelpers.SetAlternateMainPage(_appOptions))
|
||||
{
|
||||
return;
|
||||
}
|
||||
var previousPage = await AppHelpers.ClearPreviousPage();
|
||||
App.MainPage = new TabsPage(_appOptions, previousPage);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LoggerHelper.LogEvenIfCantBeResolved(ex);
|
||||
return;
|
||||
}
|
||||
var previousPage = await AppHelpers.ClearPreviousPage();
|
||||
Application.Current.MainPage = new TabsPage(_appOptions, previousPage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,12 +133,12 @@
|
||||
</StackLayout>
|
||||
</StackLayout>
|
||||
<StackLayout Spacing="0" Padding="0" IsVisible="{Binding DuoMethod, Mode=OneWay}"
|
||||
VerticalOptions="StartAndExpand">
|
||||
VerticalOptions="FillAndExpand">
|
||||
<controls:HybridWebView
|
||||
x:Name="_duoWebView"
|
||||
HorizontalOptions="FillAndExpand"
|
||||
VerticalOptions="FillAndExpand"
|
||||
HeightRequest="{Binding DuoWebViewHeight, Mode=OneWay}" />
|
||||
MinimumHeightRequest="400" />
|
||||
<StackLayout StyleClass="box" VerticalOptions="End">
|
||||
<StackLayout StyleClass="box-row, box-row-switch">
|
||||
<Label
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
using Bit.App.Models;
|
||||
using Bit.App.Utilities;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Utilities;
|
||||
|
||||
namespace Bit.App.Pages
|
||||
@@ -64,11 +63,11 @@ namespace Bit.App.Pages
|
||||
if (_vm.YubikeyMethod && !string.IsNullOrWhiteSpace(token) &&
|
||||
token.Length == 44 && !token.Contains(" "))
|
||||
{
|
||||
MainThread.BeginInvokeOnMainThread(() =>
|
||||
MainThread.BeginInvokeOnMainThread(async () =>
|
||||
{
|
||||
_vm.Token = token;
|
||||
await _vm.SubmitAsync();
|
||||
});
|
||||
_vm.SubmitCommand.Execute(null);
|
||||
}
|
||||
}
|
||||
else if (message.Command == "resumeYubiKey")
|
||||
@@ -125,9 +124,12 @@ namespace Bit.App.Pages
|
||||
return base.OnBackButtonPressed();
|
||||
}
|
||||
|
||||
private void Continue_Clicked(object sender, EventArgs e)
|
||||
private async void Continue_Clicked(object sender, EventArgs e)
|
||||
{
|
||||
_vm.SubmitCommand.Execute(null);
|
||||
if (DoOnce())
|
||||
{
|
||||
await _vm.SubmitAsync();
|
||||
}
|
||||
}
|
||||
|
||||
private async void Methods_Clicked(object sender, EventArgs e)
|
||||
@@ -156,23 +158,16 @@ namespace Bit.App.Pages
|
||||
|
||||
private async void TryAgain_Clicked(object sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
if (DoOnce())
|
||||
{
|
||||
if (DoOnce())
|
||||
if (_vm.Fido2Method)
|
||||
{
|
||||
if (_vm.Fido2Method)
|
||||
{
|
||||
await _vm.Fido2AuthenticateAsync();
|
||||
}
|
||||
else if (_vm.YubikeyMethod)
|
||||
{
|
||||
_messagingService.Send("listenYubiKeyOTP", true);
|
||||
}
|
||||
await _vm.Fido2AuthenticateAsync();
|
||||
}
|
||||
else if (_vm.YubikeyMethod)
|
||||
{
|
||||
_messagingService.Send("listenYubiKeyOTP", true);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LoggerHelper.LogEvenIfCantBeResolved(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -197,7 +192,7 @@ namespace Bit.App.Pages
|
||||
|
||||
private void TwoFactorAuthSuccessWithSSOLocked()
|
||||
{
|
||||
App.MainPage = new NavigationPage(new LockPage(_appOptions));
|
||||
Application.Current.MainPage = new NavigationPage(new LockPage(_appOptions));
|
||||
}
|
||||
|
||||
private async Task TwoFactorAuthSuccessToMainAsync()
|
||||
@@ -207,7 +202,7 @@ namespace Bit.App.Pages
|
||||
return;
|
||||
}
|
||||
var previousPage = await AppHelpers.ClearPreviousPage();
|
||||
App.MainPage = new TabsPage(_appOptions, previousPage);
|
||||
Application.Current.MainPage = new TabsPage(_appOptions, previousPage);
|
||||
}
|
||||
|
||||
private void Token_TextChanged(object sender, TextChangedEventArgs e)
|
||||
|
||||
@@ -1,16 +1,25 @@
|
||||
using System.Net;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Input;
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.Core.Resources.Localization;
|
||||
using Bit.App.Utilities;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Models.Domain;
|
||||
using Bit.Core.Models.Request;
|
||||
using Bit.Core.Resources.Localization;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Utilities;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
using Microsoft.Maui.Authentication;
|
||||
using Microsoft.Maui.Controls;
|
||||
using Microsoft.Maui;
|
||||
|
||||
namespace Bit.App.Pages
|
||||
{
|
||||
public class TwoFactorPageViewModel : CaptchaProtectedViewModel
|
||||
@@ -34,7 +43,6 @@ namespace Bit.App.Pages
|
||||
private string _webVaultUrl = "https://vault.bitwarden.com";
|
||||
private bool _enableContinue = false;
|
||||
private bool _showContinue = true;
|
||||
private double _duoWebViewHeight;
|
||||
|
||||
public TwoFactorPageViewModel()
|
||||
{
|
||||
@@ -54,7 +62,7 @@ namespace Bit.App.Pages
|
||||
_deviceTrustCryptoService = ServiceContainer.Resolve<IDeviceTrustCryptoService>();
|
||||
|
||||
PageTitle = AppResources.TwoStepLogin;
|
||||
SubmitCommand = CreateDefaultAsyncRelayCommand(() => MainThread.InvokeOnMainThreadAsync(async () => await SubmitAsync()), allowsMultipleExecutions: false);
|
||||
SubmitCommand = new Command(async () => await SubmitAsync());
|
||||
MoreCommand = CreateDefaultAsyncRelayCommand(MoreAsync, onException: _logger.Exception, allowsMultipleExecutions: false);
|
||||
}
|
||||
|
||||
@@ -64,12 +72,6 @@ namespace Bit.App.Pages
|
||||
set => SetProperty(ref _totpInstruction, value);
|
||||
}
|
||||
|
||||
public double DuoWebViewHeight
|
||||
{
|
||||
get => _duoWebViewHeight;
|
||||
set => SetProperty(ref _duoWebViewHeight, value);
|
||||
}
|
||||
|
||||
public bool Remember { get; set; }
|
||||
|
||||
public bool AuthingWithSso { get; set; }
|
||||
@@ -89,7 +91,8 @@ namespace Bit.App.Pages
|
||||
|
||||
public bool TotpMethod => AuthenticatorMethod || EmailMethod;
|
||||
|
||||
public bool ShowTryAgain => (YubikeyMethod && DeviceInfo.Platform == DevicePlatform.iOS) || Fido2Method;
|
||||
public bool ShowTryAgain => (YubikeyMethod && // TODO Xamarin.Forms.Device.RuntimePlatform is no longer supported. Use Microsoft.Maui.Devices.DeviceInfo.Platform instead. For more details see https://learn.microsoft.com/en-us/dotnet/maui/migration/forms-projects#device-changes
|
||||
Device.RuntimePlatform == Device.iOS) || Fido2Method;
|
||||
|
||||
public bool ShowContinue
|
||||
{
|
||||
@@ -103,11 +106,9 @@ namespace Bit.App.Pages
|
||||
set => SetProperty(ref _enableContinue, value);
|
||||
}
|
||||
|
||||
#if IOS
|
||||
public string YubikeyInstruction => AppResources.YubiKeyInstructionIos;
|
||||
#else
|
||||
public string YubikeyInstruction => AppResources.YubiKeyInstruction;
|
||||
#endif
|
||||
public string YubikeyInstruction => // TODO Xamarin.Forms.Device.RuntimePlatform is no longer supported. Use Microsoft.Maui.Devices.DeviceInfo.Platform instead. For more details see https://learn.microsoft.com/en-us/dotnet/maui/migration/forms-projects#device-changes
|
||||
Device.RuntimePlatform == Device.iOS ? AppResources.YubiKeyInstructionIos :
|
||||
AppResources.YubiKeyInstruction;
|
||||
|
||||
public TwoFactorProviderType? SelectedProviderType
|
||||
{
|
||||
@@ -123,7 +124,7 @@ namespace Bit.App.Pages
|
||||
nameof(ShowTryAgain),
|
||||
});
|
||||
}
|
||||
public ICommand SubmitCommand { get; }
|
||||
public Command SubmitCommand { get; }
|
||||
public ICommand MoreCommand { get; }
|
||||
public Action TwoFactorAuthSuccessAction { get; set; }
|
||||
public Action LockAction { get; set; }
|
||||
@@ -179,14 +180,13 @@ namespace Bit.App.Pages
|
||||
break;
|
||||
case TwoFactorProviderType.Duo:
|
||||
case TwoFactorProviderType.OrganizationDuo:
|
||||
SetDuoWebViewHeight();
|
||||
var host = WebUtility.UrlEncode(providerData["Host"] as string);
|
||||
var req = WebUtility.UrlEncode(providerData["Signature"] as string);
|
||||
page.DuoWebView.Uri = $"{_webVaultUrl}/duo-connector.html?host={host}&request={req}";
|
||||
page.DuoWebView.RegisterAction(sig =>
|
||||
{
|
||||
Token = sig;
|
||||
SubmitCommand.Execute(null);
|
||||
Device.BeginInvokeOnMainThread(async () => await SubmitAsync());
|
||||
});
|
||||
break;
|
||||
case TwoFactorProviderType.Email:
|
||||
@@ -211,84 +211,70 @@ namespace Bit.App.Pages
|
||||
ShowContinue = !(SelectedProviderType == null || DuoMethod || Fido2Method);
|
||||
}
|
||||
|
||||
public void SetDuoWebViewHeight()
|
||||
{
|
||||
var screenHeight = DeviceDisplay.MainDisplayInfo.Height / DeviceDisplay.MainDisplayInfo.Density;
|
||||
DuoWebViewHeight = screenHeight > 0 ? (screenHeight / 8) * 6 : 400;
|
||||
}
|
||||
|
||||
public async Task Fido2AuthenticateAsync(Dictionary<string, object> providerData = null)
|
||||
{
|
||||
await _deviceActionService.ShowLoadingAsync(AppResources.Validating);
|
||||
|
||||
if (providerData == null)
|
||||
{
|
||||
providerData = _authService.TwoFactorProvidersData[TwoFactorProviderType.Fido2WebAuthn];
|
||||
}
|
||||
|
||||
var callbackUri = "bitwarden://webauthn-callback";
|
||||
var data = AppHelpers.EncodeDataParameter(new
|
||||
{
|
||||
callbackUri = callbackUri,
|
||||
data = JsonConvert.SerializeObject(providerData),
|
||||
headerText = AppResources.Fido2Title,
|
||||
btnText = AppResources.Fido2AuthenticateWebAuthn,
|
||||
btnReturnText = AppResources.Fido2ReturnToApp,
|
||||
});
|
||||
|
||||
var url = _webVaultUrl + "/webauthn-mobile-connector.html?" + "data=" + data +
|
||||
"&parent=" + Uri.EscapeDataString(callbackUri) + "&v=2";
|
||||
|
||||
WebAuthenticatorResult authResult = null;
|
||||
try
|
||||
{
|
||||
await _deviceActionService.ShowLoadingAsync(AppResources.Validating);
|
||||
var options = new WebAuthenticatorOptions
|
||||
{
|
||||
Url = new Uri(url),
|
||||
CallbackUrl = new Uri(callbackUri),
|
||||
PrefersEphemeralWebBrowserSession = true,
|
||||
};
|
||||
authResult = await WebAuthenticator.AuthenticateAsync(options);
|
||||
}
|
||||
catch (TaskCanceledException)
|
||||
{
|
||||
// user canceled
|
||||
await _deviceActionService.HideLoadingAsync();
|
||||
return;
|
||||
}
|
||||
|
||||
if (providerData == null)
|
||||
string response = null;
|
||||
if (authResult != null && authResult.Properties.TryGetValue("data", out var resultData))
|
||||
{
|
||||
response = Uri.UnescapeDataString(resultData);
|
||||
}
|
||||
if (!string.IsNullOrWhiteSpace(response))
|
||||
{
|
||||
Token = response;
|
||||
await SubmitAsync(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
await _deviceActionService.HideLoadingAsync();
|
||||
if (authResult != null && authResult.Properties.TryGetValue("error", out var resultError))
|
||||
{
|
||||
providerData = _authService.TwoFactorProvidersData[TwoFactorProviderType.Fido2WebAuthn];
|
||||
}
|
||||
|
||||
var callbackUri = "bitwarden://webauthn-callback";
|
||||
var data = AppHelpers.EncodeDataParameter(new
|
||||
{
|
||||
callbackUri = callbackUri,
|
||||
data = JsonConvert.SerializeObject(providerData),
|
||||
headerText = AppResources.Fido2Title,
|
||||
btnText = AppResources.Fido2AuthenticateWebAuthn,
|
||||
btnReturnText = AppResources.Fido2ReturnToApp,
|
||||
});
|
||||
|
||||
var url = _webVaultUrl + "/webauthn-mobile-connector.html?" + "data=" + data +
|
||||
"&parent=" + Uri.EscapeDataString(callbackUri) + "&v=2";
|
||||
|
||||
WebAuthenticatorResult authResult = null;
|
||||
try
|
||||
{
|
||||
var options = new WebAuthenticatorOptions
|
||||
{
|
||||
Url = new Uri(url),
|
||||
CallbackUrl = new Uri(callbackUri),
|
||||
PrefersEphemeralWebBrowserSession = true,
|
||||
};
|
||||
authResult = await WebAuthenticator.AuthenticateAsync(options);
|
||||
}
|
||||
catch (TaskCanceledException)
|
||||
{
|
||||
// user canceled
|
||||
await _deviceActionService.HideLoadingAsync();
|
||||
return;
|
||||
}
|
||||
|
||||
string response = null;
|
||||
if (authResult != null && authResult.Properties.TryGetValue("data", out var resultData))
|
||||
{
|
||||
response = Uri.UnescapeDataString(resultData);
|
||||
}
|
||||
if (!string.IsNullOrWhiteSpace(response))
|
||||
{
|
||||
Token = response;
|
||||
await SubmitAsync(false);
|
||||
var message = AppResources.Fido2CheckBrowser + "\n\n" + resultError;
|
||||
await _platformUtilsService.ShowDialogAsync(message, AppResources.AnErrorHasOccurred,
|
||||
AppResources.Ok);
|
||||
}
|
||||
else
|
||||
{
|
||||
await _deviceActionService.HideLoadingAsync();
|
||||
if (authResult != null && authResult.Properties.TryGetValue("error", out var resultError))
|
||||
{
|
||||
var message = AppResources.Fido2CheckBrowser + "\n\n" + resultError;
|
||||
await _platformUtilsService.ShowDialogAsync(message, AppResources.AnErrorHasOccurred,
|
||||
AppResources.Ok);
|
||||
}
|
||||
else
|
||||
{
|
||||
await _platformUtilsService.ShowDialogAsync(AppResources.Fido2CheckBrowser,
|
||||
AppResources.AnErrorHasOccurred, AppResources.Ok);
|
||||
}
|
||||
await _platformUtilsService.ShowDialogAsync(AppResources.Fido2CheckBrowser,
|
||||
AppResources.AnErrorHasOccurred, AppResources.Ok);
|
||||
}
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
HandleException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
x:Class="Bit.Core.Pages.AndroidNavigationRedirectPage"
|
||||
Loaded="AndroidNavigationRedirectPage_OnLoaded">
|
||||
<Grid>
|
||||
<ActivityIndicator VerticalOptions="Center" HorizontalOptions="Center" IsRunning="True" />
|
||||
</Grid>
|
||||
</ContentPage>
|
||||
@@ -1,20 +0,0 @@
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.Core.Utilities;
|
||||
|
||||
namespace Bit.Core.Pages;
|
||||
|
||||
public partial class AndroidNavigationRedirectPage : ContentPage
|
||||
{
|
||||
private readonly IAccountsManager _accountsManager;
|
||||
|
||||
public AndroidNavigationRedirectPage()
|
||||
{
|
||||
_accountsManager = ServiceContainer.Resolve<IAccountsManager>("accountsManager");
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void AndroidNavigationRedirectPage_OnLoaded(object sender, EventArgs e)
|
||||
{
|
||||
_accountsManager.NavigateOnAccountChangeAsync().FireAndForget();
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,6 @@ namespace Bit.App.Pages
|
||||
{
|
||||
private readonly LazyResolve<IStateService> _stateService = new LazyResolve<IStateService>();
|
||||
private readonly LazyResolve<IDeviceActionService> _deviceActionService = new LazyResolve<IDeviceActionService>();
|
||||
private readonly LazyResolve<ILogger> _logger = new LazyResolve<ILogger>();
|
||||
|
||||
protected int ShowModalAnimationDelay = 400;
|
||||
protected int ShowPageAnimationDelay = 100;
|
||||
@@ -49,38 +48,22 @@ namespace Bit.App.Pages
|
||||
|
||||
protected override async void OnNavigatedTo(NavigatedToEventArgs args)
|
||||
{
|
||||
try
|
||||
base.OnNavigatedTo(args);
|
||||
|
||||
if (IsThemeDirty)
|
||||
{
|
||||
base.OnNavigatedTo(args);
|
||||
|
||||
if (IsThemeDirty)
|
||||
{
|
||||
try
|
||||
{
|
||||
await UpdateOnThemeChanged();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Core.Services.LoggerHelper.LogEvenIfCantBeResolved(ex);
|
||||
// Don't rethrow on theme changed so the user can still continue on the app.
|
||||
}
|
||||
}
|
||||
|
||||
await SaveActivityAsync();
|
||||
|
||||
if (ShouldCheckToPreventOnNavigatedToCalledTwice && _hasInitedOnNavigatedTo)
|
||||
{
|
||||
return;
|
||||
}
|
||||
_hasInitedOnNavigatedTo = true;
|
||||
|
||||
await InitOnNavigatedToAsync();
|
||||
UpdateOnThemeChanged();
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
await SaveActivityAsync();
|
||||
|
||||
if (ShouldCheckToPreventOnNavigatedToCalledTwice && _hasInitedOnNavigatedTo)
|
||||
{
|
||||
Core.Services.LoggerHelper.LogEvenIfCantBeResolved(ex);
|
||||
throw;
|
||||
return;
|
||||
}
|
||||
_hasInitedOnNavigatedTo = true;
|
||||
|
||||
await InitOnNavigatedToAsync();
|
||||
}
|
||||
|
||||
protected virtual Task InitOnNavigatedToAsync() => Task.CompletedTask;
|
||||
@@ -133,52 +116,37 @@ namespace Bit.App.Pages
|
||||
{
|
||||
async Task DoWorkAsync()
|
||||
{
|
||||
try
|
||||
await workFunction.Invoke();
|
||||
if (sourceView != null)
|
||||
{
|
||||
await workFunction.Invoke();
|
||||
if (sourceView != null)
|
||||
if (targetView != null)
|
||||
{
|
||||
if (targetView != null)
|
||||
{
|
||||
targetView.Content = sourceView;
|
||||
}
|
||||
else
|
||||
{
|
||||
Content = sourceView;
|
||||
}
|
||||
targetView.Content = sourceView;
|
||||
}
|
||||
else
|
||||
{
|
||||
Content = sourceView;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Value.Exception(ex);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
#if IOS
|
||||
await DoWorkAsync();
|
||||
#else
|
||||
if (DeviceInfo.Platform == DevicePlatform.iOS)
|
||||
{
|
||||
await DoWorkAsync();
|
||||
return;
|
||||
}
|
||||
await Task.Run(async () =>
|
||||
{
|
||||
await Task.Delay(fromModal ? ShowModalAnimationDelay : ShowPageAnimationDelay);
|
||||
MainThread.BeginInvokeOnMainThread(async () => await DoWorkAsync());
|
||||
});
|
||||
#endif
|
||||
}
|
||||
|
||||
protected void RequestFocus(InputView input)
|
||||
{
|
||||
Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await Task.Delay(ShowModalAnimationDelay);
|
||||
MainThread.BeginInvokeOnMainThread(() => input.Focus());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Value.Exception(ex);
|
||||
}
|
||||
await Task.Delay(ShowModalAnimationDelay);
|
||||
MainThread.BeginInvokeOnMainThread(() => input.Focus());
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ namespace Bit.App.Pages
|
||||
public async Task InitAsync()
|
||||
{
|
||||
var history = await _passwordGenerationService.GetHistoryAsync();
|
||||
MainThread.BeginInvokeOnMainThread(() =>
|
||||
Device.BeginInvokeOnMainThread(() =>
|
||||
{
|
||||
History.ResetWithRange(history ?? new List<GeneratedPasswordHistory>());
|
||||
ShowNoData = History.Count == 0;
|
||||
@@ -66,7 +66,7 @@ namespace Bit.App.Pages
|
||||
{
|
||||
try
|
||||
{
|
||||
await MainThread.InvokeOnMainThreadAsync(() => History.ResetWithRange(new List<GeneratedPasswordHistory>()));
|
||||
await Device.InvokeOnMainThreadAsync(() => History.ResetWithRange(new List<GeneratedPasswordHistory>()));
|
||||
|
||||
await InitAsync();
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@ namespace Bit.App.Pages
|
||||
public partial class GeneratorPage : BaseContentPage, IThemeDirtablePage
|
||||
{
|
||||
private readonly IBroadcasterService _broadcasterService;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
private GeneratorPageViewModel _vm;
|
||||
private readonly bool _fromTabPage;
|
||||
@@ -27,8 +26,6 @@ namespace Bit.App.Pages
|
||||
_tabsPage = tabsPage;
|
||||
InitializeComponent();
|
||||
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>();
|
||||
_logger = ServiceContainer.Resolve<ILogger>();
|
||||
|
||||
_vm = BindingContext as GeneratorPageViewModel;
|
||||
_vm.Page = this;
|
||||
_fromTabPage = fromTabPage;
|
||||
@@ -38,21 +35,26 @@ namespace Bit.App.Pages
|
||||
_vm.EmailWebsite = emailWebsite;
|
||||
_vm.EditMode = editMode;
|
||||
_vm.IosExtension = appOptions?.IosExtension ?? false;
|
||||
|
||||
// TODO Xamarin.Forms.Device.RuntimePlatform is no longer supported. Use Microsoft.Maui.Devices.DeviceInfo.Platform instead. For more details see https://learn.microsoft.com/en-us/dotnet/maui/migration/forms-projects#device-changes
|
||||
var isIos = Device.RuntimePlatform == Device.iOS;
|
||||
if (selectAction != null)
|
||||
{
|
||||
#if IOS
|
||||
ToolbarItems.Add(_closeItem);
|
||||
#endif
|
||||
if (isIos)
|
||||
{
|
||||
ToolbarItems.Add(_closeItem);
|
||||
}
|
||||
ToolbarItems.Add(_selectItem);
|
||||
}
|
||||
else
|
||||
{
|
||||
#if IOS
|
||||
ToolbarItems.Add(_moreItem);
|
||||
#else
|
||||
ToolbarItems.Add(_historyItem);
|
||||
#endif
|
||||
if (isIos)
|
||||
{
|
||||
ToolbarItems.Add(_moreItem);
|
||||
}
|
||||
else
|
||||
{
|
||||
ToolbarItems.Add(_historyItem);
|
||||
}
|
||||
}
|
||||
_typePicker.On<Microsoft.Maui.Controls.PlatformConfiguration.iOS>().SetUpdateMode(UpdateMode.WhenFinished);
|
||||
_passwordTypePicker.On<Microsoft.Maui.Controls.PlatformConfiguration.iOS>().SetUpdateMode(UpdateMode.WhenFinished);
|
||||
@@ -69,30 +71,22 @@ namespace Bit.App.Pages
|
||||
|
||||
protected async override void OnAppearing()
|
||||
{
|
||||
try
|
||||
base.OnAppearing();
|
||||
|
||||
lblPassword.IsVisible = true;
|
||||
|
||||
if (!_fromTabPage)
|
||||
{
|
||||
base.OnAppearing();
|
||||
await InitAsync();
|
||||
}
|
||||
|
||||
lblPassword.IsVisible = true;
|
||||
|
||||
if (!_fromTabPage)
|
||||
_broadcasterService.Subscribe(nameof(GeneratorPage), (message) =>
|
||||
{
|
||||
if (message.Command is ThemeManager.UPDATED_THEME_MESSAGE_KEY)
|
||||
{
|
||||
await InitAsync();
|
||||
Device.BeginInvokeOnMainThread(() => _vm.RedrawPassword());
|
||||
}
|
||||
|
||||
_broadcasterService.Subscribe(nameof(GeneratorPage), (message) =>
|
||||
{
|
||||
if (message.Command is ThemeManager.UPDATED_THEME_MESSAGE_KEY)
|
||||
{
|
||||
MainThread.BeginInvokeOnMainThread(() => _vm.RedrawPassword());
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Exception(ex);
|
||||
throw;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected override void OnDisappearing()
|
||||
@@ -106,35 +100,27 @@ namespace Bit.App.Pages
|
||||
|
||||
protected override bool OnBackButtonPressed()
|
||||
{
|
||||
#if ANDROID
|
||||
if (_tabsPage != null)
|
||||
// TODO Xamarin.Forms.Device.RuntimePlatform is no longer supported. Use Microsoft.Maui.Devices.DeviceInfo.Platform instead. For more details see https://learn.microsoft.com/en-us/dotnet/maui/migration/forms-projects#device-changes
|
||||
if (Device.RuntimePlatform == Device.Android && _tabsPage != null)
|
||||
{
|
||||
_tabsPage.ResetToVaultPage();
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
return base.OnBackButtonPressed();
|
||||
}
|
||||
|
||||
private async void More_Clicked(object sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
if (!DoOnce())
|
||||
{
|
||||
if (!DoOnce())
|
||||
{
|
||||
return;
|
||||
}
|
||||
var selection = await DisplayActionSheet(AppResources.Options, AppResources.Cancel,
|
||||
null, AppResources.PasswordHistory);
|
||||
if (selection == AppResources.PasswordHistory)
|
||||
{
|
||||
var page = new GeneratorHistoryPage();
|
||||
await Navigation.PushModalAsync(new Microsoft.Maui.Controls.NavigationPage(page));
|
||||
}
|
||||
return;
|
||||
}
|
||||
catch (Exception ex)
|
||||
var selection = await DisplayActionSheet(AppResources.Options, AppResources.Cancel,
|
||||
null, AppResources.PasswordHistory);
|
||||
if (selection == AppResources.PasswordHistory)
|
||||
{
|
||||
_logger.Exception(ex);
|
||||
var page = new GeneratorHistoryPage();
|
||||
await Navigation.PushModalAsync(new Microsoft.Maui.Controls.NavigationPage(page));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,7 +144,7 @@ namespace Bit.App.Pages
|
||||
{
|
||||
await base.UpdateOnThemeChanged();
|
||||
|
||||
await MainThread.InvokeOnMainThreadAsync(() =>
|
||||
await Device.InvokeOnMainThreadAsync(() =>
|
||||
{
|
||||
if (_vm != null)
|
||||
{
|
||||
|
||||
@@ -181,16 +181,6 @@ namespace Bit.App.Pages
|
||||
|
||||
private void OnMaxAccessCountTextChanged(object sender, TextChangedEventArgs e)
|
||||
{
|
||||
var maxAccessEntry = (Microsoft.Maui.Controls.Entry)sender;
|
||||
|
||||
#if IOS
|
||||
// HACK: To avoid a bug that incorrectly sets the TextColor when changing text
|
||||
// programatically we need to set it to null and back to the correct color
|
||||
// MAUI issue https://github.com/dotnet/maui/pull/20100
|
||||
maxAccessEntry.TextColor = null;
|
||||
maxAccessEntry.TextColor = ThemeManager.GetResourceColor("TextColor");
|
||||
#endif
|
||||
|
||||
if (string.IsNullOrWhiteSpace(e.NewTextValue))
|
||||
{
|
||||
_vm.MaxAccessCount = null;
|
||||
@@ -200,7 +190,7 @@ namespace Bit.App.Pages
|
||||
// accept only digits
|
||||
if (!int.TryParse(e.NewTextValue, out int _))
|
||||
{
|
||||
maxAccessEntry.Text = e.OldTextValue;
|
||||
((Microsoft.Maui.Controls.Entry)sender).Text = e.OldTextValue;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,6 @@ namespace Bit.App.Pages
|
||||
private readonly ISyncService _syncService;
|
||||
private readonly IVaultTimeoutService _vaultTimeoutService;
|
||||
private readonly ISendService _sendService;
|
||||
private readonly ILogger _logger;
|
||||
private readonly SendGroupingsPageViewModel _vm;
|
||||
private readonly string _pageName;
|
||||
|
||||
@@ -34,8 +33,6 @@ namespace Bit.App.Pages
|
||||
_syncService = ServiceContainer.Resolve<ISyncService>("syncService");
|
||||
_vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
|
||||
_sendService = ServiceContainer.Resolve<ISendService>("sendService");
|
||||
_logger = ServiceContainer.Resolve<ILogger>();
|
||||
|
||||
_vm = BindingContext as SendGroupingsPageViewModel;
|
||||
_vm.Page = this;
|
||||
_vm.MainPage = mainPage;
|
||||
@@ -46,89 +43,85 @@ namespace Bit.App.Pages
|
||||
_vm.PageTitle = pageTitle;
|
||||
}
|
||||
|
||||
#if IOS
|
||||
_absLayout.Children.Remove(_fab);
|
||||
if (type == null)
|
||||
// TODO Xamarin.Forms.Device.RuntimePlatform is no longer supported. Use Microsoft.Maui.Devices.DeviceInfo.Platform instead. For more details see https://learn.microsoft.com/en-us/dotnet/maui/migration/forms-projects#device-changes
|
||||
if (Device.RuntimePlatform == Device.iOS)
|
||||
{
|
||||
ToolbarItems.Add(_aboutIconItem);
|
||||
_absLayout.Children.Remove(_fab);
|
||||
if (type == null)
|
||||
{
|
||||
ToolbarItems.Add(_aboutIconItem);
|
||||
}
|
||||
ToolbarItems.Add(_addItem);
|
||||
}
|
||||
else
|
||||
{
|
||||
ToolbarItems.Add(_syncItem);
|
||||
ToolbarItems.Add(_lockItem);
|
||||
ToolbarItems.Add(_aboutTextItem);
|
||||
}
|
||||
ToolbarItems.Add(_addItem);
|
||||
#else
|
||||
ToolbarItems.Add(_syncItem);
|
||||
ToolbarItems.Add(_lockItem);
|
||||
ToolbarItems.Add(_aboutTextItem);
|
||||
#endif
|
||||
}
|
||||
|
||||
protected override async void OnAppearing()
|
||||
{
|
||||
try
|
||||
base.OnAppearing();
|
||||
if (_syncService.SyncInProgress)
|
||||
{
|
||||
base.OnAppearing();
|
||||
if (_syncService.SyncInProgress)
|
||||
{
|
||||
IsBusy = true;
|
||||
}
|
||||
IsBusy = true;
|
||||
}
|
||||
|
||||
_broadcasterService.Subscribe(_pageName, async (message) =>
|
||||
_broadcasterService.Subscribe(_pageName, async (message) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (message.Command == "syncStarted")
|
||||
{
|
||||
Device.BeginInvokeOnMainThread(() => IsBusy = true);
|
||||
}
|
||||
else if (message.Command == "syncCompleted" || message.Command == "sendUpdated")
|
||||
{
|
||||
await Task.Delay(500);
|
||||
Device.BeginInvokeOnMainThread(() =>
|
||||
{
|
||||
IsBusy = false;
|
||||
if (_vm.LoadedOnce)
|
||||
{
|
||||
var task = _vm.LoadAsync();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LoggerHelper.LogEvenIfCantBeResolved(ex);
|
||||
}
|
||||
});
|
||||
|
||||
await LoadOnAppearedAsync(_mainLayout, false, async () =>
|
||||
{
|
||||
if (!_syncService.SyncInProgress || (await _sendService.GetAllAsync()).Any())
|
||||
{
|
||||
try
|
||||
{
|
||||
if (message.Command == "syncStarted")
|
||||
{
|
||||
MainThread.BeginInvokeOnMainThread(() => IsBusy = true);
|
||||
}
|
||||
else if (message.Command == "syncCompleted" || message.Command == "sendUpdated")
|
||||
{
|
||||
await Task.Delay(500);
|
||||
await MainThread.InvokeOnMainThreadAsync(() =>
|
||||
{
|
||||
IsBusy = false;
|
||||
if (_vm.LoadedOnce)
|
||||
{
|
||||
var task = _vm.LoadAsync();
|
||||
}
|
||||
});
|
||||
}
|
||||
await _vm.LoadAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
catch (Exception e) when (e.Message.Contains("No key."))
|
||||
{
|
||||
LoggerHelper.LogEvenIfCantBeResolved(ex);
|
||||
await Task.Delay(1000);
|
||||
await _vm.LoadAsync();
|
||||
}
|
||||
});
|
||||
|
||||
await LoadOnAppearedAsync(_mainLayout, false, async () =>
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!_syncService.SyncInProgress || (await _sendService.GetAllAsync()).Any())
|
||||
await Task.Delay(5000);
|
||||
if (!_vm.Loaded)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _vm.LoadAsync();
|
||||
}
|
||||
catch (Exception e) when (e.Message.Contains("No key."))
|
||||
{
|
||||
await Task.Delay(1000);
|
||||
await _vm.LoadAsync();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
await Task.Delay(5000);
|
||||
if (!_vm.Loaded)
|
||||
{
|
||||
await _vm.LoadAsync();
|
||||
}
|
||||
await _vm.LoadAsync();
|
||||
}
|
||||
}
|
||||
|
||||
AdjustToolbar();
|
||||
await CheckAddRequest();
|
||||
}, _mainContent);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Exception(ex);
|
||||
throw;
|
||||
}
|
||||
AdjustToolbar();
|
||||
await CheckAddRequest();
|
||||
}, _mainContent);
|
||||
}
|
||||
|
||||
protected override void OnDisappearing()
|
||||
|
||||
@@ -1,11 +1,22 @@
|
||||
using Bit.App.Abstractions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.Core.Resources.Localization;
|
||||
using Bit.App.Utilities;
|
||||
using Bit.Core;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.View;
|
||||
using Bit.Core.Resources.Localization;
|
||||
using Bit.Core.Utilities;
|
||||
|
||||
using DeviceType = Bit.Core.Enums.DeviceType;
|
||||
using Microsoft.Maui.Networking;
|
||||
using Microsoft.Maui.Devices;
|
||||
using Microsoft.Maui.Controls;
|
||||
using Microsoft.Maui;
|
||||
|
||||
namespace Bit.App.Pages
|
||||
{
|
||||
public class SendGroupingsPageViewModel : BaseViewModel
|
||||
@@ -106,38 +117,28 @@ namespace Bit.App.Pages
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
var authed = await _stateService.IsAuthenticatedAsync();
|
||||
if (!authed)
|
||||
{
|
||||
var authed = await _stateService.IsAuthenticatedAsync();
|
||||
if (!authed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (await _vaultTimeoutService.IsLockedAsync())
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (await _stateService.GetSyncOnRefreshAsync() && Refreshing && !SyncRefreshing)
|
||||
{
|
||||
SyncRefreshing = true;
|
||||
await _syncService.FullSyncAsync(false);
|
||||
return;
|
||||
}
|
||||
|
||||
_doingLoad = true;
|
||||
LoadedOnce = true;
|
||||
ShowNoData = false;
|
||||
Loading = true;
|
||||
ShowList = false;
|
||||
SendEnabled = !await AppHelpers.IsSendDisabledByPolicyAsync();
|
||||
return;
|
||||
}
|
||||
catch (Exception ex)
|
||||
if (await _vaultTimeoutService.IsLockedAsync())
|
||||
{
|
||||
_logger.Value.Exception(ex);
|
||||
throw;
|
||||
return;
|
||||
}
|
||||
if (await _stateService.GetSyncOnRefreshAsync() && Refreshing && !SyncRefreshing)
|
||||
{
|
||||
SyncRefreshing = true;
|
||||
await _syncService.FullSyncAsync(false);
|
||||
return;
|
||||
}
|
||||
|
||||
_doingLoad = true;
|
||||
LoadedOnce = true;
|
||||
ShowNoData = false;
|
||||
Loading = true;
|
||||
ShowList = false;
|
||||
SendEnabled = !await AppHelpers.IsSendDisabledByPolicyAsync();
|
||||
var groupedSends = new List<SendGroupingsPageListGroup>();
|
||||
var page = Page as SendGroupingsPage;
|
||||
|
||||
@@ -145,11 +146,8 @@ namespace Bit.App.Pages
|
||||
{
|
||||
await LoadDataAsync();
|
||||
|
||||
#if IOS
|
||||
var uppercaseGroupNames = true;
|
||||
#else
|
||||
var uppercaseGroupNames = false;
|
||||
#endif
|
||||
// TODO Xamarin.Forms.Device.RuntimePlatform is no longer supported. Use Microsoft.Maui.Devices.DeviceInfo.Platform instead. For more details see https://learn.microsoft.com/en-us/dotnet/maui/migration/forms-projects#device-changes
|
||||
var uppercaseGroupNames = Device.RuntimePlatform == Device.iOS;
|
||||
if (MainPage)
|
||||
{
|
||||
groupedSends.Add(new SendGroupingsPageListGroup(
|
||||
@@ -210,11 +208,6 @@ namespace Bit.App.Pages
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Value.Exception(ex);
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_doingLoad = false;
|
||||
@@ -322,22 +315,14 @@ namespace Bit.App.Pages
|
||||
|
||||
private async void SendOptionsAsync(SendView send)
|
||||
{
|
||||
try
|
||||
if ((Page as BaseContentPage).DoOnce())
|
||||
{
|
||||
if ((Page as BaseContentPage).DoOnce())
|
||||
var selection = await AppHelpers.SendListOptions(Page, send);
|
||||
if (selection == AppResources.RemovePassword || selection == AppResources.Delete)
|
||||
{
|
||||
var selection = await AppHelpers.SendListOptions(Page, send);
|
||||
if (selection == AppResources.RemovePassword || selection == AppResources.Delete)
|
||||
{
|
||||
await LoadAsync();
|
||||
}
|
||||
await LoadAsync();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Value.Exception(ex);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,8 +31,8 @@
|
||||
<BoxView StyleClass="box-row-separator" />
|
||||
|
||||
<controls:ExternalLinkItemView
|
||||
Title="{u:I18n PrivacyPolicy}"
|
||||
GoToLinkCommand="{Binding GoToPrivacyPolicyCommand}"
|
||||
Title="{u:I18n ContactBitwardenSupport}"
|
||||
GoToLinkCommand="{Binding ContactBitwardenSupportCommand}"
|
||||
StyleClass="settings-external-link-item"
|
||||
HorizontalOptions="FillAndExpand" />
|
||||
<BoxView StyleClass="box-row-separator" />
|
||||
|
||||
@@ -33,10 +33,10 @@ namespace Bit.App.Pages
|
||||
AppResources.ContinueToHelpCenter,
|
||||
ExternalLinksConstants.HELP_CENTER), allowsMultipleExecutions: false);
|
||||
|
||||
GoToPrivacyPolicyCommand = CreateDefaultAsyncRelayCommand(
|
||||
() => LaunchUriAsync(AppResources.PrivacyPolicyDescriptionLong,
|
||||
AppResources.ContinueToPrivacyPolicy,
|
||||
ExternalLinksConstants.PRIVACY_POLICY), allowsMultipleExecutions: false);
|
||||
ContactBitwardenSupportCommand = CreateDefaultAsyncRelayCommand(
|
||||
() => LaunchUriAsync(AppResources.ContactSupportDescriptionLong,
|
||||
AppResources.ContinueToContactSupport,
|
||||
ExternalLinksConstants.CONTACT_SUPPORT), allowsMultipleExecutions: false);
|
||||
|
||||
GoToWebVaultCommand = CreateDefaultAsyncRelayCommand(
|
||||
() => LaunchUriAsync(AppResources.ExploreMoreFeaturesOfYourBitwardenAccountOnTheWebApp,
|
||||
@@ -68,7 +68,8 @@ namespace Bit.App.Pages
|
||||
{
|
||||
get
|
||||
{
|
||||
var appInfo = string.Format("{0}: {1} ({2})",
|
||||
// TODO: REMOVE WHEN MERGED INTO MAIN BRANCH
|
||||
var appInfo = string.Format("MAUI {0}: {1} ({2})",
|
||||
AppResources.Version,
|
||||
_platformUtilsService.GetApplicationVersion(),
|
||||
_deviceActionService.GetBuildNumber());
|
||||
@@ -79,7 +80,7 @@ namespace Bit.App.Pages
|
||||
|
||||
public AsyncRelayCommand ToggleSubmitCrashLogsCommand { get; }
|
||||
public ICommand GoToHelpCenterCommand { get; }
|
||||
public ICommand GoToPrivacyPolicyCommand { get; }
|
||||
public ICommand ContactBitwardenSupportCommand { get; }
|
||||
public ICommand GoToWebVaultCommand { get; }
|
||||
public ICommand GoToLearnAboutOrgsCommand { get; }
|
||||
public ICommand RateTheAppCommand { get; }
|
||||
|
||||
@@ -29,7 +29,7 @@ namespace Bit.App.Pages
|
||||
{
|
||||
if (message.Command == "selectSaveFileResult")
|
||||
{
|
||||
MainThread.BeginInvokeOnMainThread(() =>
|
||||
Device.BeginInvokeOnMainThread(() =>
|
||||
{
|
||||
var data = message.Data as Tuple<string, string>;
|
||||
if (data == null)
|
||||
|
||||
@@ -93,7 +93,6 @@
|
||||
<controls:CustomLabel
|
||||
StyleClass="box-label-regular"
|
||||
Text="{u:I18n NoPendingRequests}"
|
||||
IsVisible="{Binding HasLoginRequests, Converter={StaticResource inverseBool}}"
|
||||
FontAttributes="{OnPlatform iOS=Bold}"
|
||||
FontWeight="500"
|
||||
HorizontalTextAlignment="Center"
|
||||
|
||||
@@ -49,10 +49,12 @@ namespace Bit.App.Pages
|
||||
|
||||
private void UpdatePlaceholder()
|
||||
{
|
||||
#if ANDROID
|
||||
MainThread.BeginInvokeOnMainThread(() =>
|
||||
_emptyPlaceholder.Source = ImageSource.FromFile(ThemeManager.UsingLightTheme ? "empty_login_requests" : "empty_login_requests_dark"));
|
||||
#endif
|
||||
// TODO Xamarin.Forms.Device.RuntimePlatform is no longer supported. Use Microsoft.Maui.Devices.DeviceInfo.Platform instead. For more details see https://learn.microsoft.com/en-us/dotnet/maui/migration/forms-projects#device-changes
|
||||
if (Device.RuntimePlatform == Device.Android)
|
||||
{
|
||||
MainThread.BeginInvokeOnMainThread(() =>
|
||||
_emptyPlaceholder.Source = ImageSource.FromFile(ThemeManager.UsingLightTheme ? "empty_login_requests" : "empty_login_requests_dark"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,11 +41,11 @@ namespace Bit.App.Pages
|
||||
|
||||
private async Task GoToImportItemsAsync()
|
||||
{
|
||||
var toolsImportUrl = string.Format(ExternalLinksConstants.WEB_VAULT_TOOLS_IMPORT_FORMAT, _environmentService.GetWebVaultUrl());
|
||||
var body = string.Format(AppResources.YouCanImportDataToYourVaultOnX, toolsImportUrl);
|
||||
var webVaultUrl = _environmentService.GetWebVaultUrl();
|
||||
var body = string.Format(AppResources.YouCanImportDataToYourVaultOnX, webVaultUrl);
|
||||
if (await _platformUtilsService.ShowDialogAsync(body, AppResources.ContinueToWebApp, AppResources.Continue, AppResources.Cancel))
|
||||
{
|
||||
_platformUtilsService.LaunchUri(toolsImportUrl);
|
||||
_platformUtilsService.LaunchUri(webVaultUrl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ using Bit.Core.Abstractions;
|
||||
using Bit.Core.Models.Data;
|
||||
using Bit.Core.Models.Domain;
|
||||
using Bit.Core.Utilities;
|
||||
using Bit.Core.Services;
|
||||
|
||||
namespace Bit.App.Pages
|
||||
{
|
||||
@@ -88,29 +87,21 @@ namespace Bit.App.Pages
|
||||
|
||||
protected override async void OnAppearing()
|
||||
{
|
||||
try
|
||||
base.OnAppearing();
|
||||
_broadcasterService.Subscribe(nameof(TabsPage), async (message) =>
|
||||
{
|
||||
base.OnAppearing();
|
||||
_broadcasterService.Subscribe(nameof(TabsPage), async (message) =>
|
||||
if (message.Command == "syncCompleted")
|
||||
{
|
||||
if (message.Command == "syncCompleted")
|
||||
{
|
||||
MainThread.BeginInvokeOnMainThread(async () => await UpdateVaultButtonTitleAsync());
|
||||
}
|
||||
});
|
||||
await UpdateVaultButtonTitleAsync();
|
||||
if (await _keyConnectorService.UserNeedsMigrationAsync())
|
||||
{
|
||||
_messagingService.Send("convertAccountToKeyConnector");
|
||||
MainThread.BeginInvokeOnMainThread(async () => await UpdateVaultButtonTitleAsync());
|
||||
}
|
||||
|
||||
await ForcePasswordResetIfNeededAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
});
|
||||
await UpdateVaultButtonTitleAsync();
|
||||
if (await _keyConnectorService.UserNeedsMigrationAsync())
|
||||
{
|
||||
LoggerHelper.LogEvenIfCantBeResolved(ex);
|
||||
throw;
|
||||
_messagingService.Send("convertAccountToKeyConnector");
|
||||
}
|
||||
|
||||
await ForcePasswordResetIfNeededAsync();
|
||||
}
|
||||
|
||||
private async Task ForcePasswordResetIfNeededAsync()
|
||||
@@ -153,17 +144,7 @@ namespace Bit.App.Pages
|
||||
|
||||
private void OnUnloaded(object sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
Handler?.DisconnectHandler();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
//Workaround: Currently the Disconnect Handler needs to be manually called from the App: https://github.com/dotnet/maui/issues/3604
|
||||
// In some specific edges cases the MauiContext can be gone when we call this. (for example filling a field using Accessibility)
|
||||
// In those scenarios the app should just be "closing" anyway, so we just want to avoid the exception.
|
||||
System.Diagnostics.Debug.WriteLine(ex.Message);
|
||||
}
|
||||
Handler?.DisconnectHandler();
|
||||
}
|
||||
|
||||
public void ResetToVaultPage()
|
||||
@@ -183,30 +164,22 @@ namespace Bit.App.Pages
|
||||
|
||||
protected override async void OnCurrentPageChanged()
|
||||
{
|
||||
try
|
||||
if (CurrentPage is NavigationPage navPage)
|
||||
{
|
||||
if (CurrentPage is NavigationPage navPage)
|
||||
if (_groupingsPage?.RootPage is GroupingsPage groupingsPage)
|
||||
{
|
||||
if (_groupingsPage?.RootPage is GroupingsPage groupingsPage)
|
||||
{
|
||||
await groupingsPage.HideAccountSwitchingOverlayAsync();
|
||||
}
|
||||
|
||||
_messagingService.Send(ThemeManager.UPDATED_THEME_MESSAGE_KEY);
|
||||
if (navPage.RootPage is GroupingsPage)
|
||||
{
|
||||
// Load something?
|
||||
}
|
||||
else if (navPage.RootPage is GeneratorPage genPage)
|
||||
{
|
||||
await genPage.InitAsync();
|
||||
}
|
||||
await groupingsPage.HideAccountSwitchingOverlayAsync();
|
||||
}
|
||||
|
||||
_messagingService.Send(ThemeManager.UPDATED_THEME_MESSAGE_KEY);
|
||||
if (navPage.RootPage is GroupingsPage)
|
||||
{
|
||||
// Load something?
|
||||
}
|
||||
else if (navPage.RootPage is GeneratorPage genPage)
|
||||
{
|
||||
await genPage.InitAsync();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LoggerHelper.LogEvenIfCantBeResolved(ex);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ namespace Bit.App.Pages
|
||||
{
|
||||
if (message.Command == "selectFileResult")
|
||||
{
|
||||
MainThread.BeginInvokeOnMainThread(() =>
|
||||
Device.BeginInvokeOnMainThread(() =>
|
||||
{
|
||||
var data = message.Data as Tuple<byte[], string>;
|
||||
_vm.FileData = data.Item1;
|
||||
|
||||
@@ -7,7 +7,6 @@ using Bit.Core.Enums;
|
||||
using Bit.Core.Utilities;
|
||||
using Microsoft.Maui.Controls.PlatformConfiguration;
|
||||
using Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific;
|
||||
using Bit.Core.Services;
|
||||
|
||||
namespace Bit.App.Pages
|
||||
{
|
||||
@@ -185,7 +184,7 @@ namespace Bit.App.Pages
|
||||
{
|
||||
if (FromAutofillFramework)
|
||||
{
|
||||
App.MainPage = new TabsPage();
|
||||
Microsoft.Maui.Controls.Application.Current.MainPage = new TabsPage();
|
||||
return true;
|
||||
}
|
||||
return base.OnBackButtonPressed();
|
||||
@@ -264,24 +263,8 @@ namespace Bit.App.Pages
|
||||
{
|
||||
MainThread.BeginInvokeOnMainThread(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await Navigation.PopModalAsync();
|
||||
await _vm.UpdateTotpKeyAsync(key);
|
||||
|
||||
#if IOS
|
||||
// HACK: To avoid a bug that incorrectly sets the TextColor when changing text
|
||||
// programatically we need to set it to null and back to the correct color
|
||||
// MAUI issue https://github.com/dotnet/maui/pull/20100
|
||||
_loginTotpEntry.TextColor = null;
|
||||
_loginTotpEntry.TextColor = ThemeManager.GetResourceColor("TextColor");
|
||||
#endif
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LoggerHelper.LogEvenIfCantBeResolved(ex);
|
||||
throw;
|
||||
}
|
||||
await Navigation.PopModalAsync();
|
||||
await _vm.UpdateTotpKeyAsync(key);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -63,12 +63,12 @@ namespace Bit.App.Pages
|
||||
{
|
||||
if (message.Command == "syncStarted")
|
||||
{
|
||||
MainThread.BeginInvokeOnMainThread(() => IsBusy = true);
|
||||
Device.BeginInvokeOnMainThread(() => IsBusy = true);
|
||||
}
|
||||
else if (message.Command == "syncCompleted")
|
||||
{
|
||||
await Task.Delay(500);
|
||||
MainThread.BeginInvokeOnMainThread(() =>
|
||||
Device.BeginInvokeOnMainThread(() =>
|
||||
{
|
||||
IsBusy = false;
|
||||
if (message.Data is Dictionary<string, object> data && data.ContainsKey("successfully"))
|
||||
@@ -83,7 +83,7 @@ namespace Bit.App.Pages
|
||||
}
|
||||
else if (message.Command == "selectSaveFileResult")
|
||||
{
|
||||
MainThread.BeginInvokeOnMainThread(() =>
|
||||
Device.BeginInvokeOnMainThread(() =>
|
||||
{
|
||||
var data = message.Data as Tuple<string, string>;
|
||||
if (data == null)
|
||||
|
||||
@@ -38,11 +38,5 @@ namespace Bit.App.Pages
|
||||
return _iconImageSource;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Flag that indicates if FFImageLoading has successfully finished loading the image.
|
||||
/// This is useful to check when the cell is being reused.
|
||||
/// </summary>
|
||||
public bool IconImageSuccesfullyLoaded { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,59 +109,52 @@ namespace Bit.App.Pages
|
||||
var cts = new CancellationTokenSource();
|
||||
Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
List<CipherView> ciphers = null;
|
||||
var searchable = !string.IsNullOrWhiteSpace(searchText) && searchText.Length > 1;
|
||||
var shouldShowAllWhenEmpty = ShowAllIfSearchTextEmpty && string.IsNullOrEmpty(searchText);
|
||||
if (searchable || shouldShowAllWhenEmpty)
|
||||
{
|
||||
List<CipherView> ciphers = null;
|
||||
var searchable = !string.IsNullOrWhiteSpace(searchText) && searchText.Length > 1;
|
||||
var shouldShowAllWhenEmpty = ShowAllIfSearchTextEmpty && string.IsNullOrEmpty(searchText);
|
||||
if (searchable || shouldShowAllWhenEmpty)
|
||||
if (timeout != null)
|
||||
{
|
||||
if (timeout != null)
|
||||
{
|
||||
await Task.Delay(timeout.Value);
|
||||
}
|
||||
if (searchText != (Page as CiphersPage).SearchBar.Text
|
||||
&&
|
||||
!shouldShowAllWhenEmpty)
|
||||
{
|
||||
return;
|
||||
}
|
||||
await Task.Delay(timeout.Value);
|
||||
}
|
||||
if (searchText != (Page as CiphersPage).SearchBar.Text
|
||||
&&
|
||||
!shouldShowAllWhenEmpty)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
previousCts?.Cancel();
|
||||
try
|
||||
{
|
||||
var vaultFilteredCiphers = await GetAllCiphersAsync();
|
||||
if (!shouldShowAllWhenEmpty)
|
||||
{
|
||||
ciphers = await _searchService.SearchCiphersAsync(searchText,
|
||||
Filter ?? (c => c.IsDeleted == Deleted), vaultFilteredCiphers, cts.Token);
|
||||
}
|
||||
else
|
||||
{
|
||||
ciphers = vaultFilteredCiphers;
|
||||
}
|
||||
cts.Token.ThrowIfCancellationRequested();
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (ciphers == null)
|
||||
previousCts?.Cancel();
|
||||
try
|
||||
{
|
||||
ciphers = new List<CipherView>();
|
||||
var vaultFilteredCiphers = await GetAllCiphersAsync();
|
||||
if (!shouldShowAllWhenEmpty)
|
||||
{
|
||||
ciphers = await _searchService.SearchCiphersAsync(searchText,
|
||||
Filter ?? (c => c.IsDeleted == Deleted), vaultFilteredCiphers, cts.Token);
|
||||
}
|
||||
else
|
||||
{
|
||||
ciphers = vaultFilteredCiphers;
|
||||
}
|
||||
cts.Token.ThrowIfCancellationRequested();
|
||||
}
|
||||
MainThread.BeginInvokeOnMainThread(() =>
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
Ciphers.ResetWithRange(ciphers.Select(c => new CipherItemViewModel(c, _websiteIconsEnabled)).ToList());
|
||||
ShowNoData = !shouldShowAllWhenEmpty && searchable && Ciphers.Count == 0;
|
||||
ShowList = (searchable || shouldShowAllWhenEmpty) && !ShowNoData;
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
if (ciphers == null)
|
||||
{
|
||||
_logger.Exception(ex);
|
||||
ciphers = new List<CipherView>();
|
||||
}
|
||||
MainThread.BeginInvokeOnMainThread(() =>
|
||||
{
|
||||
Ciphers.ResetWithRange(ciphers.Select(c => new CipherItemViewModel(c, _websiteIconsEnabled)).ToList());
|
||||
ShowNoData = !shouldShowAllWhenEmpty && searchable && Ciphers.Count == 0;
|
||||
ShowList = (searchable || shouldShowAllWhenEmpty) && !ShowNoData;
|
||||
});
|
||||
}, cts.Token);
|
||||
_searchCancellationTokenSource = cts;
|
||||
}
|
||||
|
||||
@@ -61,7 +61,6 @@
|
||||
<controls:IconLabel Text="{Binding Icon, Mode=OneWay}"
|
||||
HorizontalOptions="Start"
|
||||
VerticalOptions="Center"
|
||||
Margin="0,0,7,0"
|
||||
StyleClass="list-icon, list-icon-platform"
|
||||
ShouldUpdateFontSizeDynamicallyForAccesibility="True">
|
||||
<controls:IconLabel.Effects>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.App.Controls;
|
||||
using Bit.Core.Resources.Localization;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.Data;
|
||||
using Bit.Core.Resources.Localization;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Utilities;
|
||||
|
||||
@@ -19,7 +19,6 @@ namespace Bit.App.Pages
|
||||
private readonly ICipherService _cipherService;
|
||||
private readonly IDeviceActionService _deviceActionService;
|
||||
private readonly IPlatformUtilsService _platformUtilsService;
|
||||
private readonly ILogger _logger;
|
||||
private readonly GroupingsPageViewModel _vm;
|
||||
private readonly string _pageName;
|
||||
|
||||
@@ -40,8 +39,6 @@ namespace Bit.App.Pages
|
||||
_cipherService = ServiceContainer.Resolve<ICipherService>("cipherService");
|
||||
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
|
||||
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
|
||||
_logger = ServiceContainer.Resolve<ILogger>();
|
||||
|
||||
_vm = BindingContext as GroupingsPageViewModel;
|
||||
_vm.Page = this;
|
||||
_vm.MainPage = mainPage;
|
||||
@@ -60,14 +57,17 @@ namespace Bit.App.Pages
|
||||
_vm.VaultFilterDescription = vaultFilterSelection;
|
||||
}
|
||||
|
||||
#if IOS
|
||||
_absLayout.Children.Remove(_fab);
|
||||
ToolbarItems.Add(_addItem);
|
||||
#else
|
||||
ToolbarItems.Add(_syncItem);
|
||||
ToolbarItems.Add(_lockItem);
|
||||
ToolbarItems.Add(_exitItem);
|
||||
#endif
|
||||
if (DeviceInfo.Platform == DevicePlatform.iOS)
|
||||
{
|
||||
_absLayout.Children.Remove(_fab);
|
||||
ToolbarItems.Add(_addItem);
|
||||
}
|
||||
else
|
||||
{
|
||||
ToolbarItems.Add(_syncItem);
|
||||
ToolbarItems.Add(_lockItem);
|
||||
ToolbarItems.Add(_exitItem);
|
||||
}
|
||||
if (deleted || showTotp)
|
||||
{
|
||||
_absLayout.Children.Remove(_fab);
|
||||
@@ -81,115 +81,107 @@ namespace Bit.App.Pages
|
||||
|
||||
protected override async void OnAppearing()
|
||||
{
|
||||
try
|
||||
base.OnAppearing();
|
||||
if (_syncService.SyncInProgress)
|
||||
{
|
||||
base.OnAppearing();
|
||||
if (_syncService.SyncInProgress)
|
||||
{
|
||||
IsBusy = true;
|
||||
}
|
||||
IsBusy = true;
|
||||
}
|
||||
|
||||
_accountAvatar?.OnAppearing();
|
||||
if (_vm.MainPage)
|
||||
{
|
||||
_vm.AvatarImageSource = await GetAvatarImageSourceAsync();
|
||||
}
|
||||
_accountAvatar?.OnAppearing();
|
||||
if (_vm.MainPage)
|
||||
{
|
||||
_vm.AvatarImageSource = await GetAvatarImageSourceAsync();
|
||||
}
|
||||
|
||||
_broadcasterService.Subscribe(_pageName, async (message) =>
|
||||
_broadcasterService.Subscribe(_pageName, async (message) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
try
|
||||
if (message.Command == "syncStarted")
|
||||
{
|
||||
if (message.Command == "syncStarted")
|
||||
{
|
||||
MainThread.BeginInvokeOnMainThread(() => IsBusy = true);
|
||||
}
|
||||
else if (message.Command == "syncCompleted")
|
||||
{
|
||||
await Task.Delay(500);
|
||||
if (_vm.MainPage)
|
||||
{
|
||||
_vm.AvatarImageSource = await GetAvatarImageSourceAsync();
|
||||
}
|
||||
MainThread.BeginInvokeOnMainThread(() =>
|
||||
{
|
||||
IsBusy = false;
|
||||
if (_vm.LoadedOnce)
|
||||
{
|
||||
var task = _vm.LoadAsync();
|
||||
}
|
||||
});
|
||||
}
|
||||
MainThread.BeginInvokeOnMainThread(() => IsBusy = true);
|
||||
}
|
||||
catch (Exception ex)
|
||||
else if (message.Command == "syncCompleted")
|
||||
{
|
||||
LoggerHelper.LogEvenIfCantBeResolved(ex);
|
||||
}
|
||||
});
|
||||
|
||||
await LoadOnAppearedAsync(_mainLayout, false, async () =>
|
||||
{
|
||||
if (_previousPage == null)
|
||||
{
|
||||
if (!_syncService.SyncInProgress || (await _cipherService.GetAllAsync()).Any())
|
||||
await Task.Delay(500);
|
||||
if (_vm.MainPage)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _vm.LoadAsync();
|
||||
}
|
||||
catch (Exception e) when (e.Message.Contains("No key."))
|
||||
{
|
||||
await Task.Delay(1000);
|
||||
await _vm.LoadAsync();
|
||||
}
|
||||
_vm.AvatarImageSource = await GetAvatarImageSourceAsync();
|
||||
}
|
||||
else
|
||||
MainThread.BeginInvokeOnMainThread(() =>
|
||||
{
|
||||
await Task.Delay(5000);
|
||||
if (!_vm.Loaded)
|
||||
IsBusy = false;
|
||||
if (_vm.LoadedOnce)
|
||||
{
|
||||
await _vm.LoadAsync();
|
||||
var task = _vm.LoadAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
await ShowPreviousPageAsync();
|
||||
AdjustToolbar();
|
||||
}, _mainContent);
|
||||
|
||||
if (!_vm.MainPage)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Push registration
|
||||
var lastPushRegistration = await _stateService.GetPushLastRegistrationDateAsync();
|
||||
lastPushRegistration = lastPushRegistration.GetValueOrDefault(DateTime.MinValue);
|
||||
if (DeviceInfo.Platform == DevicePlatform.iOS)
|
||||
{
|
||||
var pushPromptShow = await _stateService.GetPushInitialPromptShownAsync();
|
||||
if (!pushPromptShow.GetValueOrDefault(false))
|
||||
{
|
||||
await _stateService.SetPushInitialPromptShownAsync(true);
|
||||
await DisplayAlert(AppResources.EnableAutomaticSyncing, AppResources.PushNotificationAlert,
|
||||
AppResources.OkGotIt);
|
||||
}
|
||||
if (!pushPromptShow.GetValueOrDefault(false) ||
|
||||
DateTime.UtcNow - lastPushRegistration > TimeSpan.FromDays(1))
|
||||
{
|
||||
await _pushNotificationService.RegisterAsync();
|
||||
});
|
||||
}
|
||||
}
|
||||
else if (DeviceInfo.Platform == DevicePlatform.Android)
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (DateTime.UtcNow - lastPushRegistration > TimeSpan.FromDays(1))
|
||||
LoggerHelper.LogEvenIfCantBeResolved(ex);
|
||||
}
|
||||
});
|
||||
|
||||
await LoadOnAppearedAsync(_mainLayout, false, async () =>
|
||||
{
|
||||
if (_previousPage == null)
|
||||
{
|
||||
if (!_syncService.SyncInProgress || (await _cipherService.GetAllAsync()).Any())
|
||||
{
|
||||
await _pushNotificationService.RegisterAsync();
|
||||
try
|
||||
{
|
||||
await _vm.LoadAsync();
|
||||
}
|
||||
catch (Exception e) when (e.Message.Contains("No key."))
|
||||
{
|
||||
await Task.Delay(1000);
|
||||
await _vm.LoadAsync();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
await Task.Delay(5000);
|
||||
if (!_vm.Loaded)
|
||||
{
|
||||
await _vm.LoadAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
await ShowPreviousPageAsync();
|
||||
AdjustToolbar();
|
||||
}, _mainContent);
|
||||
|
||||
if (!_vm.MainPage)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Push registration
|
||||
var lastPushRegistration = await _stateService.GetPushLastRegistrationDateAsync();
|
||||
lastPushRegistration = lastPushRegistration.GetValueOrDefault(DateTime.MinValue);
|
||||
if (DeviceInfo.Platform == DevicePlatform.iOS)
|
||||
{
|
||||
var pushPromptShow = await _stateService.GetPushInitialPromptShownAsync();
|
||||
if (!pushPromptShow.GetValueOrDefault(false))
|
||||
{
|
||||
await _stateService.SetPushInitialPromptShownAsync(true);
|
||||
await DisplayAlert(AppResources.EnableAutomaticSyncing, AppResources.PushNotificationAlert,
|
||||
AppResources.OkGotIt);
|
||||
}
|
||||
if (!pushPromptShow.GetValueOrDefault(false) ||
|
||||
DateTime.UtcNow - lastPushRegistration > TimeSpan.FromDays(1))
|
||||
{
|
||||
await _pushNotificationService.RegisterAsync();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
else if (DeviceInfo.Platform == DevicePlatform.Android)
|
||||
{
|
||||
_logger.Exception(ex);
|
||||
throw;
|
||||
if (DateTime.UtcNow - lastPushRegistration > TimeSpan.FromDays(1))
|
||||
{
|
||||
await _pushNotificationService.RegisterAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -203,22 +195,14 @@ namespace Bit.App.Pages
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override void OnDisappearing()
|
||||
protected override async void OnDisappearing()
|
||||
{
|
||||
try
|
||||
{
|
||||
base.OnDisappearing();
|
||||
IsBusy = false;
|
||||
_vm.StopCiphersTotpTick().FireAndForget();
|
||||
_broadcasterService.Unsubscribe(_pageName);
|
||||
_vm.DisableRefreshing();
|
||||
_accountAvatar?.OnDisappearing();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Exception(ex);
|
||||
throw;
|
||||
}
|
||||
base.OnDisappearing();
|
||||
IsBusy = false;
|
||||
_vm.StopCiphersTotpTick().FireAndForget();
|
||||
_broadcasterService.Unsubscribe(_pageName);
|
||||
_vm.DisableRefreshing();
|
||||
_accountAvatar?.OnDisappearing();
|
||||
}
|
||||
|
||||
private async void RowSelected(object sender, SelectionChangedEventArgs e)
|
||||
@@ -280,80 +264,45 @@ namespace Bit.App.Pages
|
||||
|
||||
private async void Search_Clicked(object sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
await _accountListOverlay.HideAsync();
|
||||
if (DoOnce())
|
||||
{
|
||||
await _accountListOverlay.HideAsync();
|
||||
if (DoOnce())
|
||||
{
|
||||
var page = new CiphersPage(_vm.Filter, _vm.MainPage ? null : _vm.PageTitle, deleted: _vm.Deleted);
|
||||
await Navigation.PushModalAsync(new NavigationPage(page));
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Exception(ex);
|
||||
var page = new CiphersPage(_vm.Filter, _vm.MainPage ? null : _vm.PageTitle, deleted: _vm.Deleted);
|
||||
await Navigation.PushModalAsync(new NavigationPage(page));
|
||||
}
|
||||
}
|
||||
|
||||
private async void Sync_Clicked(object sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _accountListOverlay.HideAsync();
|
||||
await _vm.SyncAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Exception(ex);
|
||||
}
|
||||
await _accountListOverlay.HideAsync();
|
||||
await _vm.SyncAsync();
|
||||
}
|
||||
|
||||
private async void Lock_Clicked(object sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _accountListOverlay.HideAsync();
|
||||
await _vaultTimeoutService.LockAsync(true, true);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Exception(ex);
|
||||
}
|
||||
await _accountListOverlay.HideAsync();
|
||||
await _vaultTimeoutService.LockAsync(true, true);
|
||||
}
|
||||
|
||||
private async void Exit_Clicked(object sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _accountListOverlay.HideAsync();
|
||||
await _vm.ExitAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Exception(ex);
|
||||
}
|
||||
await _accountListOverlay.HideAsync();
|
||||
await _vm.ExitAsync();
|
||||
}
|
||||
|
||||
private async void AddButton_Clicked(object sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
var skipAction = _accountListOverlay.IsVisible && DeviceInfo.Platform == DevicePlatform.Android;
|
||||
await _accountListOverlay.HideAsync();
|
||||
if (skipAction)
|
||||
{
|
||||
var skipAction = _accountListOverlay.IsVisible && DeviceInfo.Platform == DevicePlatform.Android;
|
||||
await _accountListOverlay.HideAsync();
|
||||
if (skipAction)
|
||||
{
|
||||
// Account list in the process of closing via tapping on invisible FAB, skip this attempt
|
||||
return;
|
||||
}
|
||||
if (!_vm.Deleted && DoOnce())
|
||||
{
|
||||
var page = new CipherAddEditPage(null, _vm.Type, _vm.FolderId, _vm.CollectionId, _vm.GetVaultFilterOrgId());
|
||||
await Navigation.PushModalAsync(new NavigationPage(page));
|
||||
}
|
||||
// Account list in the process of closing via tapping on invisible FAB, skip this attempt
|
||||
return;
|
||||
}
|
||||
catch (Exception ex)
|
||||
if (!_vm.Deleted && DoOnce())
|
||||
{
|
||||
_logger.Exception(ex);
|
||||
var page = new CipherAddEditPage(null, _vm.Type, _vm.FolderId, _vm.CollectionId, _vm.GetVaultFilterOrgId());
|
||||
await Navigation.PushModalAsync(new NavigationPage(page));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -166,52 +166,41 @@ namespace Bit.App.Pages
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
var authed = await _stateService.IsAuthenticatedAsync();
|
||||
if (!authed)
|
||||
{
|
||||
var authed = await _stateService.IsAuthenticatedAsync();
|
||||
if (!authed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (await _vaultTimeoutService.IsLockedAsync())
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (await _stateService.GetSyncOnRefreshAsync() && Refreshing && !SyncRefreshing)
|
||||
{
|
||||
SyncRefreshing = true;
|
||||
await _syncService.SyncPasswordlessLoginRequestsAsync();
|
||||
await _syncService.FullSyncAsync(false);
|
||||
return;
|
||||
}
|
||||
|
||||
_deviceActionService.SetScreenCaptureAllowedAsync().FireAndForget();
|
||||
|
||||
await InitVaultFilterAsync(MainPage);
|
||||
if (MainPage)
|
||||
{
|
||||
PageTitle = ShowVaultFilter ? AppResources.Vaults : AppResources.MyVault;
|
||||
}
|
||||
|
||||
_doingLoad = true;
|
||||
LoadedOnce = true;
|
||||
ShowNoData = false;
|
||||
Loading = true;
|
||||
ShowList = false;
|
||||
ShowAddCipherButton = !Deleted;
|
||||
|
||||
_websiteIconsEnabled = await _stateService.GetDisableFaviconAsync() != true;
|
||||
return;
|
||||
}
|
||||
catch (Exception ex)
|
||||
if (await _vaultTimeoutService.IsLockedAsync())
|
||||
{
|
||||
_logger.Exception(ex);
|
||||
throw;
|
||||
return;
|
||||
}
|
||||
if (await _stateService.GetSyncOnRefreshAsync() && Refreshing && !SyncRefreshing)
|
||||
{
|
||||
SyncRefreshing = true;
|
||||
await _syncService.SyncPasswordlessLoginRequestsAsync();
|
||||
await _syncService.FullSyncAsync(false);
|
||||
return;
|
||||
}
|
||||
|
||||
_deviceActionService.SetScreenCaptureAllowedAsync().FireAndForget();
|
||||
|
||||
await InitVaultFilterAsync(MainPage);
|
||||
if (MainPage)
|
||||
{
|
||||
PageTitle = ShowVaultFilter ? AppResources.Vaults : AppResources.MyVault;
|
||||
}
|
||||
_doingLoad = true;
|
||||
LoadedOnce = true;
|
||||
ShowNoData = false;
|
||||
Loading = true;
|
||||
ShowList = false;
|
||||
ShowAddCipherButton = !Deleted;
|
||||
|
||||
var groupedItems = new List<GroupingsPageListGroup>();
|
||||
var page = Page as GroupingsPage;
|
||||
|
||||
_websiteIconsEnabled = await _stateService.GetDisableFaviconAsync() != true;
|
||||
try
|
||||
{
|
||||
await LoadDataAsync();
|
||||
@@ -318,6 +307,11 @@ namespace Bit.App.Pages
|
||||
|
||||
await MainThread.InvokeOnMainThreadAsync(() =>
|
||||
{
|
||||
#if IOS
|
||||
// HACK: [PS-536] Fix to avoid blank list after back navigation on unlocking with previous page info
|
||||
// because of update to XF v5.0.0.2401
|
||||
GroupedItems.Clear();
|
||||
#endif
|
||||
GroupedItems.ReplaceRange(items);
|
||||
});
|
||||
}
|
||||
@@ -341,22 +335,23 @@ namespace Bit.App.Pages
|
||||
|
||||
await MainThread.InvokeOnMainThreadAsync(() =>
|
||||
{
|
||||
if (!groupedItems.Any())
|
||||
if (groupedItems.Any())
|
||||
{
|
||||
#if IOS
|
||||
// HACK: [PS-536] Fix to avoid blank list after back navigation on unlocking with previous page info
|
||||
// because of update to XF v5.0.0.2401
|
||||
GroupedItems.Clear();
|
||||
#endif
|
||||
GroupedItems.ReplaceRange(new List<IGroupingsPageListItem> { new GroupingsPageHeaderListItem(groupedItems[0].Name, groupedItems[0].ItemCount) });
|
||||
GroupedItems.AddRange(items);
|
||||
}
|
||||
else
|
||||
{
|
||||
GroupedItems.Clear();
|
||||
return;
|
||||
}
|
||||
|
||||
GroupedItems.ReplaceRange(new List<IGroupingsPageListItem> { new GroupingsPageHeaderListItem(groupedItems[0].Name, groupedItems[0].ItemCount) });
|
||||
GroupedItems.AddRange(items);
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Exception(ex);
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_doingLoad = false;
|
||||
|
||||
@@ -42,7 +42,7 @@ namespace Bit.App.Pages
|
||||
{
|
||||
var cipher = await _cipherService.GetAsync(CipherId);
|
||||
var decCipher = await cipher.DecryptAsync();
|
||||
MainThread.BeginInvokeOnMainThread(() =>
|
||||
Device.BeginInvokeOnMainThread(() =>
|
||||
{
|
||||
History.ResetWithRange(decCipher.PasswordHistory ?? new List<PasswordHistoryView>());
|
||||
ShowNoData = History.Count == 0;
|
||||
|
||||
@@ -47,9 +47,6 @@
|
||||
Camera="{Binding Camera}"
|
||||
AutoStartPreview="{Binding AutoStartPreview}"
|
||||
NumCamerasDetected="{Binding NumCameras, Mode=OneWayToSource}"
|
||||
WidthRequest="{OnPlatform Android=150}"
|
||||
HeightRequest="{OnPlatform Android=150}"
|
||||
Scale="{OnPlatform Android=4}"
|
||||
Grid.Column="0"
|
||||
Grid.Row="0"
|
||||
Grid.RowSpan="3" />
|
||||
|
||||
@@ -1714,15 +1714,6 @@ namespace Bit.Core.Resources.Localization {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Continue to privacy policy?.
|
||||
/// </summary>
|
||||
public static string ContinueToPrivacyPolicy {
|
||||
get {
|
||||
return ResourceManager.GetString("ContinueToPrivacyPolicy", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Continue to web app?.
|
||||
/// </summary>
|
||||
@@ -5525,15 +5516,6 @@ namespace Bit.Core.Resources.Localization {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Check out our privacy policy on bitwarden.com..
|
||||
/// </summary>
|
||||
public static string PrivacyPolicyDescriptionLong {
|
||||
get {
|
||||
return ResourceManager.GetString("PrivacyPolicyDescriptionLong", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Bitwarden keeps your vault automatically synced by using push notifications. For the best possible experience, please select "Allow" on the following prompt when asked to allow push notifications..
|
||||
/// </summary>
|
||||
|
||||
@@ -2820,9 +2820,6 @@ Wil u na die rekening omskakel?</value>
|
||||
<data name="ContinueToContactSupport" xml:space="preserve">
|
||||
<value>Continue to contact support?</value>
|
||||
</data>
|
||||
<data name="ContinueToPrivacyPolicy" xml:space="preserve">
|
||||
<value>Continue to privacy policy?</value>
|
||||
</data>
|
||||
<data name="ContinueToAppStore" xml:space="preserve">
|
||||
<value>Continue to app store?</value>
|
||||
</data>
|
||||
@@ -2842,9 +2839,6 @@ Wil u na die rekening omskakel?</value>
|
||||
<data name="ContactSupportDescriptionLong" xml:space="preserve">
|
||||
<value>Can’t find what you are looking for? Reach out to Bitwarden support on bitwarden.com.</value>
|
||||
</data>
|
||||
<data name="PrivacyPolicyDescriptionLong" xml:space="preserve">
|
||||
<value>Check out our privacy policy on bitwarden.com.</value>
|
||||
</data>
|
||||
<data name="ExploreMoreFeaturesOfYourBitwardenAccountOnTheWebApp" xml:space="preserve">
|
||||
<value>Explore more features of your Bitwarden account on the web app.</value>
|
||||
</data>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user