merge fixes
3
.editorconfig
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
# All files
|
||||||
|
[*]
|
||||||
|
guidelines = 120
|
63
.gitattributes
vendored
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
###############################################################################
|
||||||
|
# Set default behavior to automatically normalize line endings.
|
||||||
|
###############################################################################
|
||||||
|
* text=auto
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# Set default behavior for command prompt diff.
|
||||||
|
#
|
||||||
|
# This is need for earlier builds of msysgit that does not have it on by
|
||||||
|
# default for csharp files.
|
||||||
|
# Note: This is only used by command line
|
||||||
|
###############################################################################
|
||||||
|
#*.cs diff=csharp
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# Set the merge driver for project and solution files
|
||||||
|
#
|
||||||
|
# Merging from the command prompt will add diff markers to the files if there
|
||||||
|
# are conflicts (Merging from VS is not affected by the settings below, in VS
|
||||||
|
# the diff markers are never inserted). Diff markers may cause the following
|
||||||
|
# file extensions to fail to load in VS. An alternative would be to treat
|
||||||
|
# these files as binary and thus will always conflict and require user
|
||||||
|
# intervention with every merge. To do so, just uncomment the entries below
|
||||||
|
###############################################################################
|
||||||
|
#*.sln merge=binary
|
||||||
|
#*.csproj merge=binary
|
||||||
|
#*.vbproj merge=binary
|
||||||
|
#*.vcxproj merge=binary
|
||||||
|
#*.vcproj merge=binary
|
||||||
|
#*.dbproj merge=binary
|
||||||
|
#*.fsproj merge=binary
|
||||||
|
#*.lsproj merge=binary
|
||||||
|
#*.wixproj merge=binary
|
||||||
|
#*.modelproj merge=binary
|
||||||
|
#*.sqlproj merge=binary
|
||||||
|
#*.wwaproj merge=binary
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# behavior for image files
|
||||||
|
#
|
||||||
|
# image files are treated as binary by default.
|
||||||
|
###############################################################################
|
||||||
|
#*.jpg binary
|
||||||
|
#*.png binary
|
||||||
|
#*.gif binary
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# diff behavior for common document formats
|
||||||
|
#
|
||||||
|
# Convert binary document formats to text before diffing them. This feature
|
||||||
|
# is only available from the command line. Turn it on by uncommenting the
|
||||||
|
# entries below.
|
||||||
|
###############################################################################
|
||||||
|
#*.doc diff=astextplain
|
||||||
|
#*.DOC diff=astextplain
|
||||||
|
#*.docx diff=astextplain
|
||||||
|
#*.DOCX diff=astextplain
|
||||||
|
#*.dot diff=astextplain
|
||||||
|
#*.DOT diff=astextplain
|
||||||
|
#*.pdf diff=astextplain
|
||||||
|
#*.PDF diff=astextplain
|
||||||
|
#*.rtf diff=astextplain
|
||||||
|
#*.RTF diff=astextplain
|
3
.gitignore
vendored
|
@ -197,4 +197,5 @@ FakesAssemblies/
|
||||||
|
|
||||||
# Other
|
# Other
|
||||||
project.lock.json
|
project.lock.json
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
src/App/Css
|
29
appveyor.yml
|
@ -1,29 +0,0 @@
|
||||||
#init:
|
|
||||||
# - ps: iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1'))
|
|
||||||
#on_finish:
|
|
||||||
# - ps: $blockRdp = $true; iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1'))
|
|
||||||
#install:
|
|
||||||
# - choco install cloc --no-progress
|
|
||||||
# - "cloc --vcs git --exclude-dir Resources,store,test,UWP,Properties --include-lang C#,JavaScript,TypeScript,PowerShell"
|
|
||||||
# - appveyor DownloadFile https://dist.nuget.org/win-x86-commandline/latest/nuget.exe
|
|
||||||
# - appveyor DownloadFile https://aka.ms/vs/15/release/vs_community.exe
|
|
||||||
# - vs_community.exe update --wait --quiet --norestart --installPath "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community"
|
|
||||||
# - ps: .\src\Android\update-android.ps1
|
|
||||||
before_build:
|
|
||||||
- nuget restore
|
|
||||||
- IF DEFINED keystore_dec_secret nuget install secure-file -ExcludeVersion
|
|
||||||
- IF DEFINED google_services_dec_secret secure-file\tools\secure-file -decrypt src\Android\google-services.json.enc -secret %google_services_dec_secret%
|
|
||||||
after_build:
|
|
||||||
- ps: IF($env:keystore_dec_secret) { .\src\Android\ci-build-apks.ps1 }
|
|
||||||
on_success:
|
|
||||||
- IF DEFINED play_dec_secret secure-file\tools\secure-file -decrypt store\google\Publisher\play_creds.json.enc -secret %play_dec_secret%
|
|
||||||
- IF DEFINED play_dec_secret dotnet store\google\Publisher\bin\Release\netcoreapp2.0\Publisher.dll %APPVEYOR_BUILD_FOLDER%\store\google\Publisher\play_creds.json %APPVEYOR_BUILD_FOLDER%\com.x8bit.bitwarden-%APPVEYOR_BUILD_NUMBER%.apk alpha
|
|
||||||
artifacts:
|
|
||||||
- path: com.x8bit.bitwarden-%APPVEYOR_BUILD_NUMBER%.apk
|
|
||||||
- path: com.x8bit.bitwarden-fdroid-%APPVEYOR_BUILD_NUMBER%.apk
|
|
||||||
branches:
|
|
||||||
except:
|
|
||||||
- l10n_master
|
|
||||||
skip_tags: true
|
|
||||||
configuration: Release
|
|
||||||
image: Visual Studio 2017
|
|
|
@ -1,523 +1,233 @@
|
||||||
|
|
||||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
# Visual Studio 15
|
# Visual Studio 15
|
||||||
VisualStudioVersion = 15.0.27130.2010
|
VisualStudioVersion = 15.0.28307.539
|
||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Android", "src\Android\Android.csproj", "{04B18ED2-B76D-4947-8474-191F8FD2B5E0}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Android", "src\Android\Android.csproj", "{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "iOS", "src\iOS\iOS.csproj", "{1F78403F-9A28-405B-9289-B9DBEB55F074}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "iOS", "src\iOS\iOS.csproj", "{599E0201-420A-4C3E-A7BA-5349F72E0B15}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{0D790714-ECF8-4A83-BE4A-E9C84DD1BB5D}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "App", "src\App\App.csproj", "{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{EC730FD9-F623-4B6C-B503-95CDCFBCF277}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Core", "src\Core\Core.csproj", "{4B8A8C41-9820-4341-974C-41E65B7F4366}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "App.Test", "test\App.Test\App.Test.csproj", "{A300DCE1-8D10-4267-B96A-CB01AEB7C220}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Playground", "test\Playground\Playground.csproj", "{9C8DA5A8-904D-466F-B9B0-1A4AB5A9AFC3}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "iOS.Extension", "src\iOS.Extension\iOS.Extension.csproj", "{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{D10CA4A9-F866-40E1-B658-F69051236C71}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "iOS.Core", "src\iOS.Core\iOS.Core.csproj", "{B2538ADA-B605-4D6F-ACD2-62A409680F84}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{8904C536-C67D-420F-9971-51B26574C3AA}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "store", "store", "{92470CBD-9047-4C3C-8EA3-D972D6622D84}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "iOS.Core", "src\iOS.Core\iOS.Core.csproj", "{E71F3053-056C-4381-9638-048ED73BDFF6}"
|
||||||
EndProject
|
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "google", "google", "{2E399654-26A2-46F6-B9CA-1B496A3F370A}"
|
|
||||||
EndProject
|
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "App", "src\App\App.csproj", "{8A279EE4-4537-4656-9C93-44945E594556}"
|
|
||||||
EndProject
|
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Publisher", "store\google\Publisher\Publisher.csproj", "{D5D91152-CB01-4F24-A503-304D3A94408B}"
|
|
||||||
EndProject
|
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{F0E2E596-C3DB-474A-9C88-7824662894FA}"
|
|
||||||
ProjectSection(SolutionItems) = preProject
|
|
||||||
.gitignore = .gitignore
|
|
||||||
appveyor.yml = appveyor.yml
|
|
||||||
crowdin.yml = crowdin.yml
|
|
||||||
README.md = README.md
|
|
||||||
SECURITY.md = SECURITY.md
|
|
||||||
EndProjectSection
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "iOS.Autofill", "src\iOS.Autofill\iOS.Autofill.csproj", "{C2B7ADCA-5964-43C5-A7C8-D3B6F8023F4F}"
|
|
||||||
EndProject
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Ad-Hoc|Any CPU = Ad-Hoc|Any CPU
|
Ad-Hoc|Any CPU = Ad-Hoc|Any CPU
|
||||||
Ad-Hoc|ARM = Ad-Hoc|ARM
|
|
||||||
Ad-Hoc|iPhone = Ad-Hoc|iPhone
|
Ad-Hoc|iPhone = Ad-Hoc|iPhone
|
||||||
Ad-Hoc|iPhoneSimulator = Ad-Hoc|iPhoneSimulator
|
Ad-Hoc|iPhoneSimulator = Ad-Hoc|iPhoneSimulator
|
||||||
Ad-Hoc|x64 = Ad-Hoc|x64
|
|
||||||
Ad-Hoc|x86 = Ad-Hoc|x86
|
|
||||||
AppStore|Any CPU = AppStore|Any CPU
|
AppStore|Any CPU = AppStore|Any CPU
|
||||||
AppStore|ARM = AppStore|ARM
|
|
||||||
AppStore|iPhone = AppStore|iPhone
|
AppStore|iPhone = AppStore|iPhone
|
||||||
AppStore|iPhoneSimulator = AppStore|iPhoneSimulator
|
AppStore|iPhoneSimulator = AppStore|iPhoneSimulator
|
||||||
AppStore|x64 = AppStore|x64
|
|
||||||
AppStore|x86 = AppStore|x86
|
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
Debug|ARM = Debug|ARM
|
|
||||||
Debug|iPhone = Debug|iPhone
|
Debug|iPhone = Debug|iPhone
|
||||||
Debug|iPhoneSimulator = Debug|iPhoneSimulator
|
Debug|iPhoneSimulator = Debug|iPhoneSimulator
|
||||||
Debug|x64 = Debug|x64
|
|
||||||
Debug|x86 = Debug|x86
|
|
||||||
FDroid|Any CPU = FDroid|Any CPU
|
|
||||||
FDroid|ARM = FDroid|ARM
|
|
||||||
FDroid|iPhone = FDroid|iPhone
|
|
||||||
FDroid|iPhoneSimulator = FDroid|iPhoneSimulator
|
|
||||||
FDroid|x64 = FDroid|x64
|
|
||||||
FDroid|x86 = FDroid|x86
|
|
||||||
Release|Any CPU = Release|Any CPU
|
Release|Any CPU = Release|Any CPU
|
||||||
Release|ARM = Release|ARM
|
|
||||||
Release|iPhone = Release|iPhone
|
Release|iPhone = Release|iPhone
|
||||||
Release|iPhoneSimulator = Release|iPhoneSimulator
|
Release|iPhoneSimulator = Release|iPhoneSimulator
|
||||||
Release|x64 = Release|x64
|
|
||||||
Release|x86 = Release|x86
|
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU
|
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU
|
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU
|
||||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Ad-Hoc|Any CPU.Deploy.0 = Release|Any CPU
|
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.Ad-Hoc|Any CPU.Deploy.0 = Release|Any CPU
|
||||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Ad-Hoc|ARM.ActiveCfg = Release|Any CPU
|
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU
|
||||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Ad-Hoc|ARM.Build.0 = Release|Any CPU
|
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.Ad-Hoc|iPhone.Build.0 = Release|Any CPU
|
||||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Ad-Hoc|ARM.Deploy.0 = Release|Any CPU
|
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.Ad-Hoc|iPhone.Deploy.0 = Release|Any CPU
|
||||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU
|
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
||||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.Ad-Hoc|iPhoneSimulator.Build.0 = Release|Any CPU
|
||||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Ad-Hoc|x64.ActiveCfg = Release|Any CPU
|
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.Ad-Hoc|iPhoneSimulator.Deploy.0 = Release|Any CPU
|
||||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Ad-Hoc|x64.Build.0 = Release|Any CPU
|
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.AppStore|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Ad-Hoc|x64.Deploy.0 = Release|Any CPU
|
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.AppStore|Any CPU.Build.0 = Release|Any CPU
|
||||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Ad-Hoc|x86.ActiveCfg = Release|Any CPU
|
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.AppStore|Any CPU.Deploy.0 = Release|Any CPU
|
||||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Ad-Hoc|x86.Build.0 = Release|Any CPU
|
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.AppStore|iPhone.ActiveCfg = Release|Any CPU
|
||||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Ad-Hoc|x86.Deploy.0 = Release|Any CPU
|
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.AppStore|iPhone.Build.0 = Release|Any CPU
|
||||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.AppStore|Any CPU.ActiveCfg = Release|Any CPU
|
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.AppStore|iPhone.Deploy.0 = Release|Any CPU
|
||||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.AppStore|Any CPU.Build.0 = Release|Any CPU
|
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.AppStore|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
||||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.AppStore|Any CPU.Deploy.0 = Release|Any CPU
|
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.AppStore|iPhoneSimulator.Build.0 = Release|Any CPU
|
||||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.AppStore|ARM.ActiveCfg = Release|Any CPU
|
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.AppStore|iPhoneSimulator.Deploy.0 = Release|Any CPU
|
||||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.AppStore|ARM.Build.0 = Release|Any CPU
|
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.AppStore|ARM.Deploy.0 = Release|Any CPU
|
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.AppStore|iPhone.ActiveCfg = Release|Any CPU
|
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
|
||||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.AppStore|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.Debug|iPhone.ActiveCfg = Debug|Any CPU
|
||||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.AppStore|x64.ActiveCfg = Release|Any CPU
|
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.Debug|iPhone.Build.0 = Debug|Any CPU
|
||||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.AppStore|x64.Build.0 = Release|Any CPU
|
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.Debug|iPhone.Deploy.0 = Debug|Any CPU
|
||||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.AppStore|x64.Deploy.0 = Release|Any CPU
|
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
||||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.AppStore|x86.ActiveCfg = Release|Any CPU
|
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.AppStore|x86.Build.0 = Release|Any CPU
|
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.Debug|iPhoneSimulator.Deploy.0 = Debug|Any CPU
|
||||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.AppStore|x86.Deploy.0 = Release|Any CPU
|
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.Release|Any CPU.Deploy.0 = Release|Any CPU
|
||||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
|
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.Release|iPhone.ActiveCfg = Release|Any CPU
|
||||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Debug|ARM.ActiveCfg = Debug|Any CPU
|
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.Release|iPhone.Build.0 = Release|Any CPU
|
||||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Debug|ARM.Build.0 = Debug|Any CPU
|
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.Release|iPhone.Deploy.0 = Release|Any CPU
|
||||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Debug|ARM.Deploy.0 = Debug|Any CPU
|
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
||||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Debug|iPhone.ActiveCfg = Debug|Any CPU
|
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
|
||||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.Release|iPhoneSimulator.Deploy.0 = Release|Any CPU
|
||||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Debug|x64.ActiveCfg = Debug|Any CPU
|
{599E0201-420A-4C3E-A7BA-5349F72E0B15}.Ad-Hoc|Any CPU.ActiveCfg = Ad-Hoc|iPhone
|
||||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Debug|x86.ActiveCfg = Debug|Any CPU
|
{599E0201-420A-4C3E-A7BA-5349F72E0B15}.Ad-Hoc|Any CPU.Build.0 = Ad-Hoc|iPhone
|
||||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Debug|x86.Build.0 = Debug|Any CPU
|
{599E0201-420A-4C3E-A7BA-5349F72E0B15}.Ad-Hoc|Any CPU.Deploy.0 = Ad-Hoc|iPhone
|
||||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Debug|x86.Deploy.0 = Debug|Any CPU
|
{599E0201-420A-4C3E-A7BA-5349F72E0B15}.Ad-Hoc|iPhone.ActiveCfg = Ad-Hoc|iPhone
|
||||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.FDroid|Any CPU.ActiveCfg = FDroid|Any CPU
|
{599E0201-420A-4C3E-A7BA-5349F72E0B15}.Ad-Hoc|iPhone.Build.0 = Ad-Hoc|iPhone
|
||||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.FDroid|Any CPU.Build.0 = FDroid|Any CPU
|
{599E0201-420A-4C3E-A7BA-5349F72E0B15}.Ad-Hoc|iPhone.Deploy.0 = Ad-Hoc|iPhone
|
||||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.FDroid|Any CPU.Deploy.0 = FDroid|Any CPU
|
{599E0201-420A-4C3E-A7BA-5349F72E0B15}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Ad-Hoc|iPhoneSimulator
|
||||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.FDroid|ARM.ActiveCfg = FDroid|Any CPU
|
{599E0201-420A-4C3E-A7BA-5349F72E0B15}.Ad-Hoc|iPhoneSimulator.Build.0 = Ad-Hoc|iPhoneSimulator
|
||||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.FDroid|ARM.Build.0 = FDroid|Any CPU
|
{599E0201-420A-4C3E-A7BA-5349F72E0B15}.Ad-Hoc|iPhoneSimulator.Deploy.0 = Ad-Hoc|iPhoneSimulator
|
||||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.FDroid|ARM.Deploy.0 = FDroid|Any CPU
|
{599E0201-420A-4C3E-A7BA-5349F72E0B15}.AppStore|Any CPU.ActiveCfg = AppStore|iPhone
|
||||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.FDroid|iPhone.ActiveCfg = FDroid|Any CPU
|
{599E0201-420A-4C3E-A7BA-5349F72E0B15}.AppStore|Any CPU.Build.0 = AppStore|iPhone
|
||||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.FDroid|iPhone.Build.0 = FDroid|Any CPU
|
{599E0201-420A-4C3E-A7BA-5349F72E0B15}.AppStore|Any CPU.Deploy.0 = AppStore|iPhone
|
||||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.FDroid|iPhone.Deploy.0 = FDroid|Any CPU
|
{599E0201-420A-4C3E-A7BA-5349F72E0B15}.AppStore|iPhone.ActiveCfg = AppStore|iPhone
|
||||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.FDroid|iPhoneSimulator.ActiveCfg = FDroid|Any CPU
|
{599E0201-420A-4C3E-A7BA-5349F72E0B15}.AppStore|iPhone.Build.0 = AppStore|iPhone
|
||||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.FDroid|iPhoneSimulator.Build.0 = FDroid|Any CPU
|
{599E0201-420A-4C3E-A7BA-5349F72E0B15}.AppStore|iPhone.Deploy.0 = AppStore|iPhone
|
||||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.FDroid|iPhoneSimulator.Deploy.0 = FDroid|Any CPU
|
{599E0201-420A-4C3E-A7BA-5349F72E0B15}.AppStore|iPhoneSimulator.ActiveCfg = AppStore|iPhoneSimulator
|
||||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.FDroid|x64.ActiveCfg = FDroid|Any CPU
|
{599E0201-420A-4C3E-A7BA-5349F72E0B15}.AppStore|iPhoneSimulator.Build.0 = AppStore|iPhoneSimulator
|
||||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.FDroid|x64.Build.0 = FDroid|Any CPU
|
{599E0201-420A-4C3E-A7BA-5349F72E0B15}.AppStore|iPhoneSimulator.Deploy.0 = AppStore|iPhoneSimulator
|
||||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.FDroid|x64.Deploy.0 = FDroid|Any CPU
|
{599E0201-420A-4C3E-A7BA-5349F72E0B15}.Debug|Any CPU.ActiveCfg = Debug|iPhoneSimulator
|
||||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.FDroid|x86.ActiveCfg = FDroid|Any CPU
|
{599E0201-420A-4C3E-A7BA-5349F72E0B15}.Debug|Any CPU.Build.0 = Debug|iPhoneSimulator
|
||||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.FDroid|x86.Build.0 = FDroid|Any CPU
|
{599E0201-420A-4C3E-A7BA-5349F72E0B15}.Debug|Any CPU.Deploy.0 = Debug|iPhoneSimulator
|
||||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.FDroid|x86.Deploy.0 = FDroid|Any CPU
|
{599E0201-420A-4C3E-A7BA-5349F72E0B15}.Debug|iPhone.ActiveCfg = Debug|iPhone
|
||||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{599E0201-420A-4C3E-A7BA-5349F72E0B15}.Debug|iPhone.Build.0 = Debug|iPhone
|
||||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Release|Any CPU.Build.0 = Release|Any CPU
|
{599E0201-420A-4C3E-A7BA-5349F72E0B15}.Debug|iPhone.Deploy.0 = Debug|iPhone
|
||||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Release|Any CPU.Deploy.0 = Release|Any CPU
|
{599E0201-420A-4C3E-A7BA-5349F72E0B15}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator
|
||||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Release|ARM.ActiveCfg = Release|Any CPU
|
{599E0201-420A-4C3E-A7BA-5349F72E0B15}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator
|
||||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Release|ARM.Build.0 = Release|Any CPU
|
{599E0201-420A-4C3E-A7BA-5349F72E0B15}.Debug|iPhoneSimulator.Deploy.0 = Debug|iPhoneSimulator
|
||||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Release|ARM.Deploy.0 = Release|Any CPU
|
{599E0201-420A-4C3E-A7BA-5349F72E0B15}.Release|Any CPU.ActiveCfg = Release|iPhone
|
||||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Release|iPhone.ActiveCfg = Release|Any CPU
|
{599E0201-420A-4C3E-A7BA-5349F72E0B15}.Release|Any CPU.Build.0 = Release|iPhone
|
||||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
{599E0201-420A-4C3E-A7BA-5349F72E0B15}.Release|Any CPU.Deploy.0 = Release|iPhone
|
||||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Release|x64.ActiveCfg = Release|Any CPU
|
{599E0201-420A-4C3E-A7BA-5349F72E0B15}.Release|iPhone.ActiveCfg = Release|iPhone
|
||||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Release|x64.Build.0 = Release|Any CPU
|
{599E0201-420A-4C3E-A7BA-5349F72E0B15}.Release|iPhone.Build.0 = Release|iPhone
|
||||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Release|x64.Deploy.0 = Release|Any CPU
|
{599E0201-420A-4C3E-A7BA-5349F72E0B15}.Release|iPhone.Deploy.0 = Release|iPhone
|
||||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Release|x86.ActiveCfg = Release|Any CPU
|
{599E0201-420A-4C3E-A7BA-5349F72E0B15}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator
|
||||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Release|x86.Build.0 = Release|Any CPU
|
{599E0201-420A-4C3E-A7BA-5349F72E0B15}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator
|
||||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Release|x86.Deploy.0 = Release|Any CPU
|
{599E0201-420A-4C3E-A7BA-5349F72E0B15}.Release|iPhoneSimulator.Deploy.0 = Release|iPhoneSimulator
|
||||||
{1F78403F-9A28-405B-9289-B9DBEB55F074}.Ad-Hoc|Any CPU.ActiveCfg = Ad-Hoc|iPhone
|
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{1F78403F-9A28-405B-9289-B9DBEB55F074}.Ad-Hoc|ARM.ActiveCfg = Ad-Hoc|iPhone
|
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{1F78403F-9A28-405B-9289-B9DBEB55F074}.Ad-Hoc|iPhone.ActiveCfg = Ad-Hoc|iPhone
|
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.Ad-Hoc|Any CPU.Deploy.0 = Debug|Any CPU
|
||||||
{1F78403F-9A28-405B-9289-B9DBEB55F074}.Ad-Hoc|iPhone.Build.0 = Ad-Hoc|iPhone
|
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
|
||||||
{1F78403F-9A28-405B-9289-B9DBEB55F074}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Ad-Hoc|iPhoneSimulator
|
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
|
||||||
{1F78403F-9A28-405B-9289-B9DBEB55F074}.Ad-Hoc|iPhoneSimulator.Build.0 = Ad-Hoc|iPhoneSimulator
|
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.Ad-Hoc|iPhone.Deploy.0 = Debug|Any CPU
|
||||||
{1F78403F-9A28-405B-9289-B9DBEB55F074}.Ad-Hoc|x64.ActiveCfg = Ad-Hoc|iPhone
|
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
||||||
{1F78403F-9A28-405B-9289-B9DBEB55F074}.Ad-Hoc|x86.ActiveCfg = Ad-Hoc|iPhone
|
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||||
{1F78403F-9A28-405B-9289-B9DBEB55F074}.AppStore|Any CPU.ActiveCfg = AppStore|iPhone
|
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.Ad-Hoc|iPhoneSimulator.Deploy.0 = Debug|Any CPU
|
||||||
{1F78403F-9A28-405B-9289-B9DBEB55F074}.AppStore|ARM.ActiveCfg = AppStore|iPhone
|
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{1F78403F-9A28-405B-9289-B9DBEB55F074}.AppStore|iPhone.ActiveCfg = AppStore|iPhone
|
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.AppStore|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{1F78403F-9A28-405B-9289-B9DBEB55F074}.AppStore|iPhone.Build.0 = AppStore|iPhone
|
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.AppStore|Any CPU.Deploy.0 = Debug|Any CPU
|
||||||
{1F78403F-9A28-405B-9289-B9DBEB55F074}.AppStore|iPhoneSimulator.ActiveCfg = AppStore|iPhoneSimulator
|
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
|
||||||
{1F78403F-9A28-405B-9289-B9DBEB55F074}.AppStore|iPhoneSimulator.Build.0 = AppStore|iPhoneSimulator
|
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.AppStore|iPhone.Build.0 = Debug|Any CPU
|
||||||
{1F78403F-9A28-405B-9289-B9DBEB55F074}.AppStore|x64.ActiveCfg = AppStore|iPhone
|
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.AppStore|iPhone.Deploy.0 = Debug|Any CPU
|
||||||
{1F78403F-9A28-405B-9289-B9DBEB55F074}.AppStore|x86.ActiveCfg = AppStore|iPhone
|
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
||||||
{1F78403F-9A28-405B-9289-B9DBEB55F074}.Debug|Any CPU.ActiveCfg = Debug|iPhone
|
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||||
{1F78403F-9A28-405B-9289-B9DBEB55F074}.Debug|ARM.ActiveCfg = Debug|iPhone
|
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.AppStore|iPhoneSimulator.Deploy.0 = Debug|Any CPU
|
||||||
{1F78403F-9A28-405B-9289-B9DBEB55F074}.Debug|iPhone.ActiveCfg = Debug|iPhone
|
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{1F78403F-9A28-405B-9289-B9DBEB55F074}.Debug|iPhone.Build.0 = Debug|iPhone
|
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{1F78403F-9A28-405B-9289-B9DBEB55F074}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator
|
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
|
||||||
{1F78403F-9A28-405B-9289-B9DBEB55F074}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator
|
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.Debug|iPhone.ActiveCfg = Debug|Any CPU
|
||||||
{1F78403F-9A28-405B-9289-B9DBEB55F074}.Debug|x64.ActiveCfg = Debug|iPhone
|
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.Debug|iPhone.Build.0 = Debug|Any CPU
|
||||||
{1F78403F-9A28-405B-9289-B9DBEB55F074}.Debug|x86.ActiveCfg = Debug|iPhone
|
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.Debug|iPhone.Deploy.0 = Debug|Any CPU
|
||||||
{1F78403F-9A28-405B-9289-B9DBEB55F074}.Debug|x86.Build.0 = Debug|iPhone
|
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
||||||
{1F78403F-9A28-405B-9289-B9DBEB55F074}.FDroid|Any CPU.ActiveCfg = Release|iPhoneSimulator
|
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||||
{1F78403F-9A28-405B-9289-B9DBEB55F074}.FDroid|Any CPU.Build.0 = Release|iPhoneSimulator
|
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.Debug|iPhoneSimulator.Deploy.0 = Debug|Any CPU
|
||||||
{1F78403F-9A28-405B-9289-B9DBEB55F074}.FDroid|ARM.ActiveCfg = Release|iPhoneSimulator
|
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{1F78403F-9A28-405B-9289-B9DBEB55F074}.FDroid|ARM.Build.0 = Release|iPhoneSimulator
|
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
{1F78403F-9A28-405B-9289-B9DBEB55F074}.FDroid|iPhone.ActiveCfg = Release|iPhone
|
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.Release|Any CPU.Deploy.0 = Release|Any CPU
|
||||||
{1F78403F-9A28-405B-9289-B9DBEB55F074}.FDroid|iPhone.Build.0 = Release|iPhone
|
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.Release|iPhone.ActiveCfg = Release|Any CPU
|
||||||
{1F78403F-9A28-405B-9289-B9DBEB55F074}.FDroid|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator
|
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.Release|iPhone.Build.0 = Release|Any CPU
|
||||||
{1F78403F-9A28-405B-9289-B9DBEB55F074}.FDroid|iPhoneSimulator.Build.0 = Release|iPhoneSimulator
|
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.Release|iPhone.Deploy.0 = Release|Any CPU
|
||||||
{1F78403F-9A28-405B-9289-B9DBEB55F074}.FDroid|x64.ActiveCfg = Release|iPhoneSimulator
|
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
||||||
{1F78403F-9A28-405B-9289-B9DBEB55F074}.FDroid|x64.Build.0 = Release|iPhoneSimulator
|
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
|
||||||
{1F78403F-9A28-405B-9289-B9DBEB55F074}.FDroid|x86.ActiveCfg = Release|iPhoneSimulator
|
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.Release|iPhoneSimulator.Deploy.0 = Release|Any CPU
|
||||||
{1F78403F-9A28-405B-9289-B9DBEB55F074}.FDroid|x86.Build.0 = Release|iPhoneSimulator
|
{4B8A8C41-9820-4341-974C-41E65B7F4366}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{1F78403F-9A28-405B-9289-B9DBEB55F074}.Release|Any CPU.ActiveCfg = Release|iPhone
|
{4B8A8C41-9820-4341-974C-41E65B7F4366}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{1F78403F-9A28-405B-9289-B9DBEB55F074}.Release|ARM.ActiveCfg = Release|iPhone
|
{4B8A8C41-9820-4341-974C-41E65B7F4366}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
|
||||||
{1F78403F-9A28-405B-9289-B9DBEB55F074}.Release|iPhone.ActiveCfg = Release|iPhone
|
{4B8A8C41-9820-4341-974C-41E65B7F4366}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
|
||||||
{1F78403F-9A28-405B-9289-B9DBEB55F074}.Release|iPhone.Build.0 = Release|iPhone
|
{4B8A8C41-9820-4341-974C-41E65B7F4366}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
||||||
{1F78403F-9A28-405B-9289-B9DBEB55F074}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator
|
{4B8A8C41-9820-4341-974C-41E65B7F4366}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||||
{1F78403F-9A28-405B-9289-B9DBEB55F074}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator
|
{4B8A8C41-9820-4341-974C-41E65B7F4366}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{1F78403F-9A28-405B-9289-B9DBEB55F074}.Release|x64.ActiveCfg = Release|iPhone
|
{4B8A8C41-9820-4341-974C-41E65B7F4366}.AppStore|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{1F78403F-9A28-405B-9289-B9DBEB55F074}.Release|x86.ActiveCfg = Release|iPhone
|
{4B8A8C41-9820-4341-974C-41E65B7F4366}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
|
||||||
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU
|
{4B8A8C41-9820-4341-974C-41E65B7F4366}.AppStore|iPhone.Build.0 = Debug|Any CPU
|
||||||
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU
|
{4B8A8C41-9820-4341-974C-41E65B7F4366}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
||||||
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.Ad-Hoc|ARM.ActiveCfg = Release|Any CPU
|
{4B8A8C41-9820-4341-974C-41E65B7F4366}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||||
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.Ad-Hoc|ARM.Build.0 = Release|Any CPU
|
{4B8A8C41-9820-4341-974C-41E65B7F4366}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU
|
{4B8A8C41-9820-4341-974C-41E65B7F4366}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
{4B8A8C41-9820-4341-974C-41E65B7F4366}.Debug|iPhone.ActiveCfg = Debug|Any CPU
|
||||||
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.Ad-Hoc|x64.ActiveCfg = Release|Any CPU
|
{4B8A8C41-9820-4341-974C-41E65B7F4366}.Debug|iPhone.Build.0 = Debug|Any CPU
|
||||||
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.Ad-Hoc|x64.Build.0 = Release|Any CPU
|
{4B8A8C41-9820-4341-974C-41E65B7F4366}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
||||||
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.Ad-Hoc|x86.ActiveCfg = Release|Any CPU
|
{4B8A8C41-9820-4341-974C-41E65B7F4366}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||||
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.Ad-Hoc|x86.Build.0 = Release|Any CPU
|
{4B8A8C41-9820-4341-974C-41E65B7F4366}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.AppStore|Any CPU.ActiveCfg = Release|Any CPU
|
{4B8A8C41-9820-4341-974C-41E65B7F4366}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.AppStore|Any CPU.Build.0 = Release|Any CPU
|
{4B8A8C41-9820-4341-974C-41E65B7F4366}.Release|iPhone.ActiveCfg = Release|Any CPU
|
||||||
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.AppStore|ARM.ActiveCfg = Release|Any CPU
|
{4B8A8C41-9820-4341-974C-41E65B7F4366}.Release|iPhone.Build.0 = Release|Any CPU
|
||||||
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.AppStore|ARM.Build.0 = Release|Any CPU
|
{4B8A8C41-9820-4341-974C-41E65B7F4366}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
||||||
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.AppStore|iPhone.ActiveCfg = Release|Any CPU
|
{4B8A8C41-9820-4341-974C-41E65B7F4366}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
|
||||||
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.AppStore|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
{9C8DA5A8-904D-466F-B9B0-1A4AB5A9AFC3}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.AppStore|x64.ActiveCfg = Release|Any CPU
|
{9C8DA5A8-904D-466F-B9B0-1A4AB5A9AFC3}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.AppStore|x64.Build.0 = Release|Any CPU
|
{9C8DA5A8-904D-466F-B9B0-1A4AB5A9AFC3}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
|
||||||
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.AppStore|x86.ActiveCfg = Release|Any CPU
|
{9C8DA5A8-904D-466F-B9B0-1A4AB5A9AFC3}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
|
||||||
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.AppStore|x86.Build.0 = Release|Any CPU
|
{9C8DA5A8-904D-466F-B9B0-1A4AB5A9AFC3}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
||||||
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{9C8DA5A8-904D-466F-B9B0-1A4AB5A9AFC3}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||||
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{9C8DA5A8-904D-466F-B9B0-1A4AB5A9AFC3}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.Debug|ARM.ActiveCfg = Debug|Any CPU
|
{9C8DA5A8-904D-466F-B9B0-1A4AB5A9AFC3}.AppStore|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.Debug|ARM.Build.0 = Debug|Any CPU
|
{9C8DA5A8-904D-466F-B9B0-1A4AB5A9AFC3}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
|
||||||
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.Debug|iPhone.ActiveCfg = Debug|Any CPU
|
{9C8DA5A8-904D-466F-B9B0-1A4AB5A9AFC3}.AppStore|iPhone.Build.0 = Debug|Any CPU
|
||||||
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
{9C8DA5A8-904D-466F-B9B0-1A4AB5A9AFC3}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
||||||
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.Debug|x64.ActiveCfg = Debug|Any CPU
|
{9C8DA5A8-904D-466F-B9B0-1A4AB5A9AFC3}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||||
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.Debug|x86.ActiveCfg = Debug|Any CPU
|
{9C8DA5A8-904D-466F-B9B0-1A4AB5A9AFC3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.Debug|x86.Build.0 = Debug|Any CPU
|
{9C8DA5A8-904D-466F-B9B0-1A4AB5A9AFC3}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.FDroid|Any CPU.ActiveCfg = Debug|Any CPU
|
{9C8DA5A8-904D-466F-B9B0-1A4AB5A9AFC3}.Debug|iPhone.ActiveCfg = Debug|Any CPU
|
||||||
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.FDroid|Any CPU.Build.0 = Debug|Any CPU
|
{9C8DA5A8-904D-466F-B9B0-1A4AB5A9AFC3}.Debug|iPhone.Build.0 = Debug|Any CPU
|
||||||
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.FDroid|ARM.ActiveCfg = Debug|Any CPU
|
{9C8DA5A8-904D-466F-B9B0-1A4AB5A9AFC3}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
||||||
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.FDroid|ARM.Build.0 = Debug|Any CPU
|
{9C8DA5A8-904D-466F-B9B0-1A4AB5A9AFC3}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||||
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.FDroid|iPhone.ActiveCfg = Debug|Any CPU
|
{9C8DA5A8-904D-466F-B9B0-1A4AB5A9AFC3}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.FDroid|iPhone.Build.0 = Debug|Any CPU
|
{9C8DA5A8-904D-466F-B9B0-1A4AB5A9AFC3}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.FDroid|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
{9C8DA5A8-904D-466F-B9B0-1A4AB5A9AFC3}.Release|iPhone.ActiveCfg = Release|Any CPU
|
||||||
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.FDroid|iPhoneSimulator.Build.0 = Debug|Any CPU
|
{9C8DA5A8-904D-466F-B9B0-1A4AB5A9AFC3}.Release|iPhone.Build.0 = Release|Any CPU
|
||||||
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.FDroid|x64.ActiveCfg = Debug|Any CPU
|
{9C8DA5A8-904D-466F-B9B0-1A4AB5A9AFC3}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
||||||
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.FDroid|x64.Build.0 = Debug|Any CPU
|
{9C8DA5A8-904D-466F-B9B0-1A4AB5A9AFC3}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
|
||||||
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.FDroid|x86.ActiveCfg = Debug|Any CPU
|
{E71F3053-056C-4381-9638-048ED73BDFF6}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.FDroid|x86.Build.0 = Debug|Any CPU
|
{E71F3053-056C-4381-9638-048ED73BDFF6}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU
|
||||||
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{E71F3053-056C-4381-9638-048ED73BDFF6}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU
|
||||||
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.Release|Any CPU.Build.0 = Release|Any CPU
|
{E71F3053-056C-4381-9638-048ED73BDFF6}.Ad-Hoc|iPhone.Build.0 = Release|Any CPU
|
||||||
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.Release|ARM.ActiveCfg = Release|Any CPU
|
{E71F3053-056C-4381-9638-048ED73BDFF6}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
||||||
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.Release|ARM.Build.0 = Release|Any CPU
|
{E71F3053-056C-4381-9638-048ED73BDFF6}.Ad-Hoc|iPhoneSimulator.Build.0 = Release|Any CPU
|
||||||
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.Release|iPhone.ActiveCfg = Release|Any CPU
|
{E71F3053-056C-4381-9638-048ED73BDFF6}.AppStore|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
{E71F3053-056C-4381-9638-048ED73BDFF6}.AppStore|Any CPU.Build.0 = Release|Any CPU
|
||||||
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.Release|x64.ActiveCfg = Release|Any CPU
|
{E71F3053-056C-4381-9638-048ED73BDFF6}.AppStore|iPhone.ActiveCfg = Release|Any CPU
|
||||||
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.Release|x64.Build.0 = Release|Any CPU
|
{E71F3053-056C-4381-9638-048ED73BDFF6}.AppStore|iPhone.Build.0 = Release|Any CPU
|
||||||
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.Release|x86.ActiveCfg = Release|Any CPU
|
{E71F3053-056C-4381-9638-048ED73BDFF6}.AppStore|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
||||||
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.Release|x86.Build.0 = Release|Any CPU
|
{E71F3053-056C-4381-9638-048ED73BDFF6}.AppStore|iPhoneSimulator.Build.0 = Release|Any CPU
|
||||||
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.Ad-Hoc|Any CPU.ActiveCfg = Ad-Hoc|iPhone
|
{E71F3053-056C-4381-9638-048ED73BDFF6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.Ad-Hoc|ARM.ActiveCfg = Ad-Hoc|iPhone
|
{E71F3053-056C-4381-9638-048ED73BDFF6}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.Ad-Hoc|iPhone.ActiveCfg = Ad-Hoc|iPhone
|
{E71F3053-056C-4381-9638-048ED73BDFF6}.Debug|iPhone.ActiveCfg = Debug|Any CPU
|
||||||
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.Ad-Hoc|iPhone.Build.0 = Ad-Hoc|iPhone
|
{E71F3053-056C-4381-9638-048ED73BDFF6}.Debug|iPhone.Build.0 = Debug|Any CPU
|
||||||
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Ad-Hoc|iPhoneSimulator
|
{E71F3053-056C-4381-9638-048ED73BDFF6}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
||||||
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.Ad-Hoc|iPhoneSimulator.Build.0 = Ad-Hoc|iPhoneSimulator
|
{E71F3053-056C-4381-9638-048ED73BDFF6}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||||
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.Ad-Hoc|x64.ActiveCfg = Ad-Hoc|iPhone
|
{E71F3053-056C-4381-9638-048ED73BDFF6}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.Ad-Hoc|x86.ActiveCfg = Ad-Hoc|iPhone
|
{E71F3053-056C-4381-9638-048ED73BDFF6}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.AppStore|Any CPU.ActiveCfg = AppStore|iPhone
|
{E71F3053-056C-4381-9638-048ED73BDFF6}.Release|iPhone.ActiveCfg = Release|Any CPU
|
||||||
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.AppStore|ARM.ActiveCfg = AppStore|iPhone
|
{E71F3053-056C-4381-9638-048ED73BDFF6}.Release|iPhone.Build.0 = Release|Any CPU
|
||||||
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.AppStore|iPhone.ActiveCfg = AppStore|iPhone
|
{E71F3053-056C-4381-9638-048ED73BDFF6}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
||||||
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.AppStore|iPhone.Build.0 = AppStore|iPhone
|
{E71F3053-056C-4381-9638-048ED73BDFF6}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
|
||||||
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.AppStore|iPhoneSimulator.ActiveCfg = AppStore|iPhoneSimulator
|
|
||||||
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.AppStore|iPhoneSimulator.Build.0 = AppStore|iPhoneSimulator
|
|
||||||
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.AppStore|x64.ActiveCfg = AppStore|iPhone
|
|
||||||
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.AppStore|x86.ActiveCfg = AppStore|iPhone
|
|
||||||
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.Debug|Any CPU.ActiveCfg = Debug|iPhone
|
|
||||||
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.Debug|ARM.ActiveCfg = Debug|iPhone
|
|
||||||
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.Debug|iPhone.ActiveCfg = Debug|iPhone
|
|
||||||
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.Debug|iPhone.Build.0 = Debug|iPhone
|
|
||||||
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator
|
|
||||||
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator
|
|
||||||
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.Debug|x64.ActiveCfg = Debug|iPhone
|
|
||||||
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.Debug|x86.ActiveCfg = Debug|iPhone
|
|
||||||
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.Debug|x86.Build.0 = Debug|iPhone
|
|
||||||
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.FDroid|Any CPU.ActiveCfg = Release|iPhoneSimulator
|
|
||||||
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.FDroid|Any CPU.Build.0 = Release|iPhoneSimulator
|
|
||||||
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.FDroid|ARM.ActiveCfg = Release|iPhoneSimulator
|
|
||||||
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.FDroid|ARM.Build.0 = Release|iPhoneSimulator
|
|
||||||
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.FDroid|iPhone.ActiveCfg = Release|iPhone
|
|
||||||
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.FDroid|iPhone.Build.0 = Release|iPhone
|
|
||||||
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.FDroid|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator
|
|
||||||
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.FDroid|iPhoneSimulator.Build.0 = Release|iPhoneSimulator
|
|
||||||
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.FDroid|x64.ActiveCfg = Release|iPhoneSimulator
|
|
||||||
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.FDroid|x64.Build.0 = Release|iPhoneSimulator
|
|
||||||
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.FDroid|x86.ActiveCfg = Release|iPhoneSimulator
|
|
||||||
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.FDroid|x86.Build.0 = Release|iPhoneSimulator
|
|
||||||
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.Release|Any CPU.ActiveCfg = Release|iPhone
|
|
||||||
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.Release|ARM.ActiveCfg = Release|iPhone
|
|
||||||
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.Release|iPhone.ActiveCfg = Release|iPhone
|
|
||||||
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.Release|iPhone.Build.0 = Release|iPhone
|
|
||||||
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator
|
|
||||||
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator
|
|
||||||
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.Release|x64.ActiveCfg = Release|iPhone
|
|
||||||
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.Release|x86.ActiveCfg = Release|iPhone
|
|
||||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.Ad-Hoc|ARM.ActiveCfg = Release|Any CPU
|
|
||||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.Ad-Hoc|ARM.Build.0 = Release|Any CPU
|
|
||||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU
|
|
||||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.Ad-Hoc|iPhone.Build.0 = Release|Any CPU
|
|
||||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
|
||||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.Ad-Hoc|iPhoneSimulator.Build.0 = Release|Any CPU
|
|
||||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.Ad-Hoc|x64.ActiveCfg = Release|Any CPU
|
|
||||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.Ad-Hoc|x64.Build.0 = Release|Any CPU
|
|
||||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.Ad-Hoc|x86.ActiveCfg = Release|Any CPU
|
|
||||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.Ad-Hoc|x86.Build.0 = Release|Any CPU
|
|
||||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.AppStore|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.AppStore|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.AppStore|ARM.ActiveCfg = Release|Any CPU
|
|
||||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.AppStore|ARM.Build.0 = Release|Any CPU
|
|
||||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.AppStore|iPhone.ActiveCfg = Release|Any CPU
|
|
||||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.AppStore|iPhone.Build.0 = Release|Any CPU
|
|
||||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.AppStore|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
|
||||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.AppStore|iPhoneSimulator.Build.0 = Release|Any CPU
|
|
||||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.AppStore|x64.ActiveCfg = Release|Any CPU
|
|
||||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.AppStore|x64.Build.0 = Release|Any CPU
|
|
||||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.AppStore|x86.ActiveCfg = Release|Any CPU
|
|
||||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.AppStore|x86.Build.0 = Release|Any CPU
|
|
||||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.Debug|ARM.ActiveCfg = Debug|Any CPU
|
|
||||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.Debug|iPhone.ActiveCfg = Debug|Any CPU
|
|
||||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.Debug|iPhone.Build.0 = Debug|Any CPU
|
|
||||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
|
||||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
|
|
||||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.Debug|x64.ActiveCfg = Debug|Any CPU
|
|
||||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.Debug|x86.ActiveCfg = Debug|Any CPU
|
|
||||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.Debug|x86.Build.0 = Debug|Any CPU
|
|
||||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.FDroid|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.FDroid|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.FDroid|ARM.ActiveCfg = Debug|Any CPU
|
|
||||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.FDroid|ARM.Build.0 = Debug|Any CPU
|
|
||||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.FDroid|iPhone.ActiveCfg = Debug|Any CPU
|
|
||||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.FDroid|iPhone.Build.0 = Debug|Any CPU
|
|
||||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.FDroid|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
|
||||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.FDroid|iPhoneSimulator.Build.0 = Debug|Any CPU
|
|
||||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.FDroid|x64.ActiveCfg = Debug|Any CPU
|
|
||||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.FDroid|x64.Build.0 = Debug|Any CPU
|
|
||||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.FDroid|x86.ActiveCfg = Debug|Any CPU
|
|
||||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.FDroid|x86.Build.0 = Debug|Any CPU
|
|
||||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.Release|ARM.ActiveCfg = Release|Any CPU
|
|
||||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.Release|iPhone.ActiveCfg = Release|Any CPU
|
|
||||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.Release|iPhone.Build.0 = Release|Any CPU
|
|
||||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
|
||||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
|
|
||||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.Release|x64.ActiveCfg = Release|Any CPU
|
|
||||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.Release|x64.Build.0 = Release|Any CPU
|
|
||||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.Release|x86.ActiveCfg = Release|Any CPU
|
|
||||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.Release|x86.Build.0 = Release|Any CPU
|
|
||||||
{8A279EE4-4537-4656-9C93-44945E594556}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{8A279EE4-4537-4656-9C93-44945E594556}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{8A279EE4-4537-4656-9C93-44945E594556}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU
|
|
||||||
{8A279EE4-4537-4656-9C93-44945E594556}.Ad-Hoc|ARM.Build.0 = Debug|Any CPU
|
|
||||||
{8A279EE4-4537-4656-9C93-44945E594556}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
|
|
||||||
{8A279EE4-4537-4656-9C93-44945E594556}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
|
|
||||||
{8A279EE4-4537-4656-9C93-44945E594556}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
|
||||||
{8A279EE4-4537-4656-9C93-44945E594556}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
|
|
||||||
{8A279EE4-4537-4656-9C93-44945E594556}.Ad-Hoc|x64.ActiveCfg = Debug|Any CPU
|
|
||||||
{8A279EE4-4537-4656-9C93-44945E594556}.Ad-Hoc|x64.Build.0 = Debug|Any CPU
|
|
||||||
{8A279EE4-4537-4656-9C93-44945E594556}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU
|
|
||||||
{8A279EE4-4537-4656-9C93-44945E594556}.Ad-Hoc|x86.Build.0 = Debug|Any CPU
|
|
||||||
{8A279EE4-4537-4656-9C93-44945E594556}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{8A279EE4-4537-4656-9C93-44945E594556}.AppStore|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{8A279EE4-4537-4656-9C93-44945E594556}.AppStore|ARM.ActiveCfg = Debug|Any CPU
|
|
||||||
{8A279EE4-4537-4656-9C93-44945E594556}.AppStore|ARM.Build.0 = Debug|Any CPU
|
|
||||||
{8A279EE4-4537-4656-9C93-44945E594556}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
|
|
||||||
{8A279EE4-4537-4656-9C93-44945E594556}.AppStore|iPhone.Build.0 = Debug|Any CPU
|
|
||||||
{8A279EE4-4537-4656-9C93-44945E594556}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
|
||||||
{8A279EE4-4537-4656-9C93-44945E594556}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
|
|
||||||
{8A279EE4-4537-4656-9C93-44945E594556}.AppStore|x64.ActiveCfg = Debug|Any CPU
|
|
||||||
{8A279EE4-4537-4656-9C93-44945E594556}.AppStore|x64.Build.0 = Debug|Any CPU
|
|
||||||
{8A279EE4-4537-4656-9C93-44945E594556}.AppStore|x86.ActiveCfg = Debug|Any CPU
|
|
||||||
{8A279EE4-4537-4656-9C93-44945E594556}.AppStore|x86.Build.0 = Debug|Any CPU
|
|
||||||
{8A279EE4-4537-4656-9C93-44945E594556}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{8A279EE4-4537-4656-9C93-44945E594556}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{8A279EE4-4537-4656-9C93-44945E594556}.Debug|ARM.ActiveCfg = Debug|Any CPU
|
|
||||||
{8A279EE4-4537-4656-9C93-44945E594556}.Debug|ARM.Build.0 = Debug|Any CPU
|
|
||||||
{8A279EE4-4537-4656-9C93-44945E594556}.Debug|iPhone.ActiveCfg = Debug|Any CPU
|
|
||||||
{8A279EE4-4537-4656-9C93-44945E594556}.Debug|iPhone.Build.0 = Debug|Any CPU
|
|
||||||
{8A279EE4-4537-4656-9C93-44945E594556}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
|
||||||
{8A279EE4-4537-4656-9C93-44945E594556}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
|
|
||||||
{8A279EE4-4537-4656-9C93-44945E594556}.Debug|x64.ActiveCfg = Debug|Any CPU
|
|
||||||
{8A279EE4-4537-4656-9C93-44945E594556}.Debug|x64.Build.0 = Debug|Any CPU
|
|
||||||
{8A279EE4-4537-4656-9C93-44945E594556}.Debug|x86.ActiveCfg = Debug|Any CPU
|
|
||||||
{8A279EE4-4537-4656-9C93-44945E594556}.Debug|x86.Build.0 = Debug|Any CPU
|
|
||||||
{8A279EE4-4537-4656-9C93-44945E594556}.FDroid|Any CPU.ActiveCfg = FDroid|Any CPU
|
|
||||||
{8A279EE4-4537-4656-9C93-44945E594556}.FDroid|Any CPU.Build.0 = FDroid|Any CPU
|
|
||||||
{8A279EE4-4537-4656-9C93-44945E594556}.FDroid|ARM.ActiveCfg = FDroid|Any CPU
|
|
||||||
{8A279EE4-4537-4656-9C93-44945E594556}.FDroid|ARM.Build.0 = FDroid|Any CPU
|
|
||||||
{8A279EE4-4537-4656-9C93-44945E594556}.FDroid|iPhone.ActiveCfg = FDroid|Any CPU
|
|
||||||
{8A279EE4-4537-4656-9C93-44945E594556}.FDroid|iPhone.Build.0 = FDroid|Any CPU
|
|
||||||
{8A279EE4-4537-4656-9C93-44945E594556}.FDroid|iPhoneSimulator.ActiveCfg = FDroid|Any CPU
|
|
||||||
{8A279EE4-4537-4656-9C93-44945E594556}.FDroid|iPhoneSimulator.Build.0 = FDroid|Any CPU
|
|
||||||
{8A279EE4-4537-4656-9C93-44945E594556}.FDroid|x64.ActiveCfg = FDroid|Any CPU
|
|
||||||
{8A279EE4-4537-4656-9C93-44945E594556}.FDroid|x64.Build.0 = FDroid|Any CPU
|
|
||||||
{8A279EE4-4537-4656-9C93-44945E594556}.FDroid|x86.ActiveCfg = FDroid|Any CPU
|
|
||||||
{8A279EE4-4537-4656-9C93-44945E594556}.FDroid|x86.Build.0 = FDroid|Any CPU
|
|
||||||
{8A279EE4-4537-4656-9C93-44945E594556}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{8A279EE4-4537-4656-9C93-44945E594556}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{8A279EE4-4537-4656-9C93-44945E594556}.Release|ARM.ActiveCfg = Release|Any CPU
|
|
||||||
{8A279EE4-4537-4656-9C93-44945E594556}.Release|ARM.Build.0 = Release|Any CPU
|
|
||||||
{8A279EE4-4537-4656-9C93-44945E594556}.Release|iPhone.ActiveCfg = Release|Any CPU
|
|
||||||
{8A279EE4-4537-4656-9C93-44945E594556}.Release|iPhone.Build.0 = Release|Any CPU
|
|
||||||
{8A279EE4-4537-4656-9C93-44945E594556}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
|
||||||
{8A279EE4-4537-4656-9C93-44945E594556}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
|
|
||||||
{8A279EE4-4537-4656-9C93-44945E594556}.Release|x64.ActiveCfg = Release|Any CPU
|
|
||||||
{8A279EE4-4537-4656-9C93-44945E594556}.Release|x64.Build.0 = Release|Any CPU
|
|
||||||
{8A279EE4-4537-4656-9C93-44945E594556}.Release|x86.ActiveCfg = Release|Any CPU
|
|
||||||
{8A279EE4-4537-4656-9C93-44945E594556}.Release|x86.Build.0 = Release|Any CPU
|
|
||||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU
|
|
||||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.Ad-Hoc|ARM.Build.0 = Debug|Any CPU
|
|
||||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
|
|
||||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
|
|
||||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
|
||||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
|
|
||||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.Ad-Hoc|x64.ActiveCfg = Debug|Any CPU
|
|
||||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.Ad-Hoc|x64.Build.0 = Debug|Any CPU
|
|
||||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU
|
|
||||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.Ad-Hoc|x86.Build.0 = Debug|Any CPU
|
|
||||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.AppStore|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.AppStore|ARM.ActiveCfg = Debug|Any CPU
|
|
||||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.AppStore|ARM.Build.0 = Debug|Any CPU
|
|
||||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
|
|
||||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.AppStore|iPhone.Build.0 = Debug|Any CPU
|
|
||||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
|
||||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
|
|
||||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.AppStore|x64.ActiveCfg = Debug|Any CPU
|
|
||||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.AppStore|x64.Build.0 = Debug|Any CPU
|
|
||||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.AppStore|x86.ActiveCfg = Debug|Any CPU
|
|
||||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.AppStore|x86.Build.0 = Debug|Any CPU
|
|
||||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.Debug|ARM.ActiveCfg = Debug|Any CPU
|
|
||||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.Debug|ARM.Build.0 = Debug|Any CPU
|
|
||||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.Debug|iPhone.ActiveCfg = Debug|Any CPU
|
|
||||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.Debug|iPhone.Build.0 = Debug|Any CPU
|
|
||||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
|
||||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
|
|
||||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.Debug|x64.ActiveCfg = Debug|Any CPU
|
|
||||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.Debug|x64.Build.0 = Debug|Any CPU
|
|
||||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.Debug|x86.ActiveCfg = Debug|Any CPU
|
|
||||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.Debug|x86.Build.0 = Debug|Any CPU
|
|
||||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.FDroid|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.FDroid|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.FDroid|ARM.ActiveCfg = Release|Any CPU
|
|
||||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.FDroid|ARM.Build.0 = Release|Any CPU
|
|
||||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.FDroid|iPhone.ActiveCfg = Release|Any CPU
|
|
||||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.FDroid|iPhone.Build.0 = Release|Any CPU
|
|
||||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.FDroid|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
|
||||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.FDroid|iPhoneSimulator.Build.0 = Release|Any CPU
|
|
||||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.FDroid|x64.ActiveCfg = Release|Any CPU
|
|
||||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.FDroid|x64.Build.0 = Release|Any CPU
|
|
||||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.FDroid|x86.ActiveCfg = Release|Any CPU
|
|
||||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.FDroid|x86.Build.0 = Release|Any CPU
|
|
||||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.Release|ARM.ActiveCfg = Release|Any CPU
|
|
||||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.Release|ARM.Build.0 = Release|Any CPU
|
|
||||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.Release|iPhone.ActiveCfg = Release|Any CPU
|
|
||||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.Release|iPhone.Build.0 = Release|Any CPU
|
|
||||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
|
||||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
|
|
||||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.Release|x64.ActiveCfg = Release|Any CPU
|
|
||||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.Release|x64.Build.0 = Release|Any CPU
|
|
||||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.Release|x86.ActiveCfg = Release|Any CPU
|
|
||||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.Release|x86.Build.0 = Release|Any CPU
|
|
||||||
{C2B7ADCA-5964-43C5-A7C8-D3B6F8023F4F}.Ad-Hoc|Any CPU.ActiveCfg = Ad-Hoc|iPhone
|
|
||||||
{C2B7ADCA-5964-43C5-A7C8-D3B6F8023F4F}.Ad-Hoc|ARM.ActiveCfg = Ad-Hoc|iPhone
|
|
||||||
{C2B7ADCA-5964-43C5-A7C8-D3B6F8023F4F}.Ad-Hoc|iPhone.ActiveCfg = Ad-Hoc|iPhone
|
|
||||||
{C2B7ADCA-5964-43C5-A7C8-D3B6F8023F4F}.Ad-Hoc|iPhone.Build.0 = Ad-Hoc|iPhone
|
|
||||||
{C2B7ADCA-5964-43C5-A7C8-D3B6F8023F4F}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Ad-Hoc|iPhoneSimulator
|
|
||||||
{C2B7ADCA-5964-43C5-A7C8-D3B6F8023F4F}.Ad-Hoc|iPhoneSimulator.Build.0 = Ad-Hoc|iPhoneSimulator
|
|
||||||
{C2B7ADCA-5964-43C5-A7C8-D3B6F8023F4F}.Ad-Hoc|x64.ActiveCfg = Ad-Hoc|iPhone
|
|
||||||
{C2B7ADCA-5964-43C5-A7C8-D3B6F8023F4F}.Ad-Hoc|x86.ActiveCfg = Ad-Hoc|iPhone
|
|
||||||
{C2B7ADCA-5964-43C5-A7C8-D3B6F8023F4F}.AppStore|Any CPU.ActiveCfg = AppStore|iPhone
|
|
||||||
{C2B7ADCA-5964-43C5-A7C8-D3B6F8023F4F}.AppStore|ARM.ActiveCfg = AppStore|iPhone
|
|
||||||
{C2B7ADCA-5964-43C5-A7C8-D3B6F8023F4F}.AppStore|iPhone.ActiveCfg = AppStore|iPhone
|
|
||||||
{C2B7ADCA-5964-43C5-A7C8-D3B6F8023F4F}.AppStore|iPhone.Build.0 = AppStore|iPhone
|
|
||||||
{C2B7ADCA-5964-43C5-A7C8-D3B6F8023F4F}.AppStore|iPhoneSimulator.ActiveCfg = AppStore|iPhoneSimulator
|
|
||||||
{C2B7ADCA-5964-43C5-A7C8-D3B6F8023F4F}.AppStore|iPhoneSimulator.Build.0 = AppStore|iPhoneSimulator
|
|
||||||
{C2B7ADCA-5964-43C5-A7C8-D3B6F8023F4F}.AppStore|x64.ActiveCfg = AppStore|iPhone
|
|
||||||
{C2B7ADCA-5964-43C5-A7C8-D3B6F8023F4F}.AppStore|x86.ActiveCfg = AppStore|iPhone
|
|
||||||
{C2B7ADCA-5964-43C5-A7C8-D3B6F8023F4F}.Debug|Any CPU.ActiveCfg = Debug|iPhone
|
|
||||||
{C2B7ADCA-5964-43C5-A7C8-D3B6F8023F4F}.Debug|ARM.ActiveCfg = Debug|iPhone
|
|
||||||
{C2B7ADCA-5964-43C5-A7C8-D3B6F8023F4F}.Debug|iPhone.ActiveCfg = Debug|iPhone
|
|
||||||
{C2B7ADCA-5964-43C5-A7C8-D3B6F8023F4F}.Debug|iPhone.Build.0 = Debug|iPhone
|
|
||||||
{C2B7ADCA-5964-43C5-A7C8-D3B6F8023F4F}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator
|
|
||||||
{C2B7ADCA-5964-43C5-A7C8-D3B6F8023F4F}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator
|
|
||||||
{C2B7ADCA-5964-43C5-A7C8-D3B6F8023F4F}.Debug|x64.ActiveCfg = Debug|iPhone
|
|
||||||
{C2B7ADCA-5964-43C5-A7C8-D3B6F8023F4F}.Debug|x86.ActiveCfg = Debug|iPhone
|
|
||||||
{C2B7ADCA-5964-43C5-A7C8-D3B6F8023F4F}.FDroid|Any CPU.ActiveCfg = AppStore|iPhone
|
|
||||||
{C2B7ADCA-5964-43C5-A7C8-D3B6F8023F4F}.FDroid|Any CPU.Build.0 = AppStore|iPhone
|
|
||||||
{C2B7ADCA-5964-43C5-A7C8-D3B6F8023F4F}.FDroid|ARM.ActiveCfg = AppStore|iPhone
|
|
||||||
{C2B7ADCA-5964-43C5-A7C8-D3B6F8023F4F}.FDroid|ARM.Build.0 = AppStore|iPhone
|
|
||||||
{C2B7ADCA-5964-43C5-A7C8-D3B6F8023F4F}.FDroid|iPhone.ActiveCfg = AppStore|iPhone
|
|
||||||
{C2B7ADCA-5964-43C5-A7C8-D3B6F8023F4F}.FDroid|iPhone.Build.0 = AppStore|iPhone
|
|
||||||
{C2B7ADCA-5964-43C5-A7C8-D3B6F8023F4F}.FDroid|iPhoneSimulator.ActiveCfg = AppStore|iPhoneSimulator
|
|
||||||
{C2B7ADCA-5964-43C5-A7C8-D3B6F8023F4F}.FDroid|iPhoneSimulator.Build.0 = AppStore|iPhoneSimulator
|
|
||||||
{C2B7ADCA-5964-43C5-A7C8-D3B6F8023F4F}.FDroid|x64.ActiveCfg = AppStore|iPhone
|
|
||||||
{C2B7ADCA-5964-43C5-A7C8-D3B6F8023F4F}.FDroid|x64.Build.0 = AppStore|iPhone
|
|
||||||
{C2B7ADCA-5964-43C5-A7C8-D3B6F8023F4F}.FDroid|x86.ActiveCfg = AppStore|iPhone
|
|
||||||
{C2B7ADCA-5964-43C5-A7C8-D3B6F8023F4F}.FDroid|x86.Build.0 = AppStore|iPhone
|
|
||||||
{C2B7ADCA-5964-43C5-A7C8-D3B6F8023F4F}.Release|Any CPU.ActiveCfg = Release|iPhone
|
|
||||||
{C2B7ADCA-5964-43C5-A7C8-D3B6F8023F4F}.Release|ARM.ActiveCfg = Release|iPhone
|
|
||||||
{C2B7ADCA-5964-43C5-A7C8-D3B6F8023F4F}.Release|iPhone.ActiveCfg = Release|iPhone
|
|
||||||
{C2B7ADCA-5964-43C5-A7C8-D3B6F8023F4F}.Release|iPhone.Build.0 = Release|iPhone
|
|
||||||
{C2B7ADCA-5964-43C5-A7C8-D3B6F8023F4F}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator
|
|
||||||
{C2B7ADCA-5964-43C5-A7C8-D3B6F8023F4F}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator
|
|
||||||
{C2B7ADCA-5964-43C5-A7C8-D3B6F8023F4F}.Release|x64.ActiveCfg = Release|iPhone
|
|
||||||
{C2B7ADCA-5964-43C5-A7C8-D3B6F8023F4F}.Release|x86.ActiveCfg = Release|iPhone
|
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(NestedProjects) = preSolution
|
GlobalSection(NestedProjects) = preSolution
|
||||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0} = {EC730FD9-F623-4B6C-B503-95CDCFBCF277}
|
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F} = {D10CA4A9-F866-40E1-B658-F69051236C71}
|
||||||
{1F78403F-9A28-405B-9289-B9DBEB55F074} = {EC730FD9-F623-4B6C-B503-95CDCFBCF277}
|
{599E0201-420A-4C3E-A7BA-5349F72E0B15} = {D10CA4A9-F866-40E1-B658-F69051236C71}
|
||||||
{A300DCE1-8D10-4267-B96A-CB01AEB7C220} = {0D790714-ECF8-4A83-BE4A-E9C84DD1BB5D}
|
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C} = {D10CA4A9-F866-40E1-B658-F69051236C71}
|
||||||
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422} = {EC730FD9-F623-4B6C-B503-95CDCFBCF277}
|
{4B8A8C41-9820-4341-974C-41E65B7F4366} = {D10CA4A9-F866-40E1-B658-F69051236C71}
|
||||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84} = {EC730FD9-F623-4B6C-B503-95CDCFBCF277}
|
{9C8DA5A8-904D-466F-B9B0-1A4AB5A9AFC3} = {8904C536-C67D-420F-9971-51B26574C3AA}
|
||||||
{2E399654-26A2-46F6-B9CA-1B496A3F370A} = {92470CBD-9047-4C3C-8EA3-D972D6622D84}
|
{E71F3053-056C-4381-9638-048ED73BDFF6} = {D10CA4A9-F866-40E1-B658-F69051236C71}
|
||||||
{8A279EE4-4537-4656-9C93-44945E594556} = {EC730FD9-F623-4B6C-B503-95CDCFBCF277}
|
|
||||||
{D5D91152-CB01-4F24-A503-304D3A94408B} = {2E399654-26A2-46F6-B9CA-1B496A3F370A}
|
|
||||||
{C2B7ADCA-5964-43C5-A7C8-D3B6F8023F4F} = {EC730FD9-F623-4B6C-B503-95CDCFBCF277}
|
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
SolutionGuid = {318CB2DF-0118-43A3-AC83-56BADCF71CCD}
|
SolutionGuid = {7D436EA3-8B7E-45D2-8D14-0730BD2E0410}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
EndGlobal
|
EndGlobal
|
||||||
|
|
|
@ -5,16 +5,14 @@ using Android.Runtime;
|
||||||
using Android.Views;
|
using Android.Views;
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace Bit.Android
|
namespace Bit.Droid.Accessibility
|
||||||
{
|
{
|
||||||
[Activity(Theme = "@style/BitwardenTheme.Splash", WindowSoftInputMode = SoftInput.StateHidden)]
|
[Activity(Theme = "@style/MainTheme.Splash", WindowSoftInputMode = SoftInput.StateHidden)]
|
||||||
public class AutofillActivity : Activity
|
public class AccessibilityActivity : Activity
|
||||||
{
|
{
|
||||||
private DateTime? _lastLaunch = null;
|
private DateTime? _lastLaunch = null;
|
||||||
private string _lastQueriedUri;
|
private string _lastQueriedUri;
|
||||||
|
|
||||||
public static AutofillCredentials LastCredentials { get; set; }
|
|
||||||
|
|
||||||
protected override void OnCreate(Bundle bundle)
|
protected override void OnCreate(Bundle bundle)
|
||||||
{
|
{
|
||||||
base.OnCreate(bundle);
|
base.OnCreate(bundle);
|
||||||
|
@ -40,7 +38,6 @@ namespace Bit.Android
|
||||||
Finish();
|
Finish();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Intent.RemoveExtra("uri");
|
Intent.RemoveExtra("uri");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,7 +46,7 @@ namespace Bit.Android
|
||||||
base.OnActivityResult(requestCode, resultCode, data);
|
base.OnActivityResult(requestCode, resultCode, data);
|
||||||
if(data == null)
|
if(data == null)
|
||||||
{
|
{
|
||||||
LastCredentials = null;
|
AccessibilityHelpers.LastCredentials = null;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -57,15 +54,14 @@ namespace Bit.Android
|
||||||
{
|
{
|
||||||
if(data.GetStringExtra("canceled") != null)
|
if(data.GetStringExtra("canceled") != null)
|
||||||
{
|
{
|
||||||
LastCredentials = null;
|
AccessibilityHelpers.LastCredentials = null;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var uri = data.GetStringExtra("uri");
|
var uri = data.GetStringExtra("uri");
|
||||||
var username = data.GetStringExtra("username");
|
var username = data.GetStringExtra("username");
|
||||||
var password = data.GetStringExtra("password");
|
var password = data.GetStringExtra("password");
|
||||||
|
AccessibilityHelpers.LastCredentials = new Credentials
|
||||||
LastCredentials = new AutofillCredentials
|
|
||||||
{
|
{
|
||||||
Username = username,
|
Username = username,
|
||||||
Password = password,
|
Password = password,
|
||||||
|
@ -76,10 +72,9 @@ namespace Bit.Android
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
LastCredentials = null;
|
AccessibilityHelpers.LastCredentials = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Finish();
|
Finish();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,9 +86,8 @@ namespace Bit.Android
|
||||||
Finish();
|
Finish();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var now = DateTime.UtcNow;
|
var now = DateTime.UtcNow;
|
||||||
if(_lastLaunch.HasValue && (now - _lastLaunch.Value <= TimeSpan.FromSeconds(2)))
|
if(_lastLaunch.HasValue && (now - _lastLaunch.Value) <= TimeSpan.FromSeconds(2))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
246
src/Android/Accessibility/AccessibilityHelpers.cs
Normal file
|
@ -0,0 +1,246 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Android.OS;
|
||||||
|
using Android.Views.Accessibility;
|
||||||
|
using Bit.Core;
|
||||||
|
|
||||||
|
namespace Bit.Droid.Accessibility
|
||||||
|
{
|
||||||
|
public static class AccessibilityHelpers
|
||||||
|
{
|
||||||
|
public static Credentials LastCredentials = null;
|
||||||
|
public static string SystemUiPackage = "com.android.systemui";
|
||||||
|
public static string BitwardenTag = "bw_access";
|
||||||
|
|
||||||
|
public static Dictionary<string, Browser> SupportedBrowsers => new List<Browser>
|
||||||
|
{
|
||||||
|
new Browser("com.android.chrome", "url_bar"),
|
||||||
|
new Browser("com.chrome.beta", "url_bar"),
|
||||||
|
new Browser("org.chromium.chrome", "url_bar"),
|
||||||
|
new Browser("com.android.browser", "url"),
|
||||||
|
new Browser("com.brave.browser", "url_bar"),
|
||||||
|
new Browser("com.opera.browser", "url_field"),
|
||||||
|
new Browser("com.opera.browser.beta", "url_field"),
|
||||||
|
new Browser("com.opera.mini.native", "url_field"),
|
||||||
|
new Browser("com.opera.touch", "addressbarEdit"),
|
||||||
|
new Browser("com.chrome.dev", "url_bar"),
|
||||||
|
new Browser("com.chrome.canary", "url_bar"),
|
||||||
|
new Browser("com.google.android.apps.chrome", "url_bar"),
|
||||||
|
new Browser("com.google.android.apps.chrome_dev", "url_bar"),
|
||||||
|
new Browser("org.codeaurora.swe.browser", "url_bar"),
|
||||||
|
new Browser("org.iron.srware", "url_bar"),
|
||||||
|
new Browser("com.sec.android.app.sbrowser", "location_bar_edit_text"),
|
||||||
|
new Browser("com.sec.android.app.sbrowser.beta", "location_bar_edit_text"),
|
||||||
|
new Browser("com.yandex.browser", "bro_omnibar_address_title_text",
|
||||||
|
(s) => s.Split(new char[]{' ', ' '}).FirstOrDefault()), // 0 = Regular Space, 1 = No-break space (00A0)
|
||||||
|
new Browser("org.mozilla.firefox", "url_bar_title"),
|
||||||
|
new Browser("org.mozilla.firefox_beta", "url_bar_title"),
|
||||||
|
new Browser("org.mozilla.fennec_aurora", "url_bar_title"),
|
||||||
|
new Browser("org.mozilla.focus", "display_url"),
|
||||||
|
new Browser("org.mozilla.klar", "display_url"),
|
||||||
|
new Browser("org.mozilla.fenix", "mozac_browser_toolbar_url_view"),
|
||||||
|
new Browser("org.mozilla.reference.browser", "mozac_browser_toolbar_url_view"),
|
||||||
|
new Browser("com.ghostery.android.ghostery", "search_field"),
|
||||||
|
new Browser("org.adblockplus.browser", "url_bar_title"),
|
||||||
|
new Browser("com.htc.sense.browser", "title"),
|
||||||
|
new Browser("com.amazon.cloud9", "url"),
|
||||||
|
new Browser("mobi.mgeek.TunnyBrowser", "title"),
|
||||||
|
new Browser("com.nubelacorp.javelin", "enterUrl"),
|
||||||
|
new Browser("com.jerky.browser2", "enterUrl"),
|
||||||
|
new Browser("com.mx.browser", "address_editor_with_progress"),
|
||||||
|
new Browser("com.mx.browser.tablet", "address_editor_with_progress"),
|
||||||
|
new Browser("com.linkbubble.playstore", "url_text"),
|
||||||
|
new Browser("com.ksmobile.cb", "address_bar_edit_text"),
|
||||||
|
new Browser("acr.browser.lightning", "search"),
|
||||||
|
new Browser("acr.browser.barebones", "search"),
|
||||||
|
new Browser("com.microsoft.emmx", "url_bar"),
|
||||||
|
new Browser("com.duckduckgo.mobile.android", "omnibarTextInput"),
|
||||||
|
new Browser("mark.via.gp", "aw"),
|
||||||
|
new Browser("org.bromite.bromite", "url_bar"),
|
||||||
|
new Browser("com.kiwibrowser.browser", "url_bar"),
|
||||||
|
new Browser("com.ecosia.android", "url_bar"),
|
||||||
|
new Browser("com.qwant.liberty", "url_bar_title"),
|
||||||
|
}.ToDictionary(n => n.PackageName);
|
||||||
|
|
||||||
|
// Known packages to skip
|
||||||
|
public static HashSet<string> FilteredPackageNames => new HashSet<string>
|
||||||
|
{
|
||||||
|
SystemUiPackage,
|
||||||
|
"com.google.android.googlequicksearchbox",
|
||||||
|
"com.google.android.apps.nexuslauncher",
|
||||||
|
"com.google.android.launcher",
|
||||||
|
"com.computer.desktop.ui.launcher",
|
||||||
|
"com.launcher.notelauncher",
|
||||||
|
"com.anddoes.launcher",
|
||||||
|
"com.actionlauncher.playstore",
|
||||||
|
"ch.deletescape.lawnchair.plah",
|
||||||
|
"com.microsoft.launcher",
|
||||||
|
"com.teslacoilsw.launcher",
|
||||||
|
"com.teslacoilsw.launcher.prime",
|
||||||
|
"is.shortcut",
|
||||||
|
"me.craftsapp.nlauncher",
|
||||||
|
"com.ss.squarehome2"
|
||||||
|
};
|
||||||
|
|
||||||
|
public static void PrintTestData(AccessibilityNodeInfo root, AccessibilityEvent e)
|
||||||
|
{
|
||||||
|
var testNodes = GetWindowNodes(root, e, n => n.ViewIdResourceName != null && n.Text != null, false);
|
||||||
|
var testNodesData = testNodes.Select(n => new { id = n.ViewIdResourceName, text = n.Text });
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string GetUri(AccessibilityNodeInfo root)
|
||||||
|
{
|
||||||
|
var uri = string.Concat(Constants.AndroidAppProtocol, root.PackageName);
|
||||||
|
if(SupportedBrowsers.ContainsKey(root.PackageName))
|
||||||
|
{
|
||||||
|
var browser = SupportedBrowsers[root.PackageName];
|
||||||
|
var addressNode = root.FindAccessibilityNodeInfosByViewId(
|
||||||
|
$"{root.PackageName}:id/{browser.UriViewId}").FirstOrDefault();
|
||||||
|
if(addressNode != null)
|
||||||
|
{
|
||||||
|
uri = ExtractUri(uri, addressNode, browser);
|
||||||
|
addressNode.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string ExtractUri(string uri, AccessibilityNodeInfo addressNode, Browser browser)
|
||||||
|
{
|
||||||
|
if(addressNode?.Text == null)
|
||||||
|
{
|
||||||
|
return uri;
|
||||||
|
}
|
||||||
|
if(addressNode.Text == null)
|
||||||
|
{
|
||||||
|
return uri;
|
||||||
|
}
|
||||||
|
uri = browser.GetUriFunction(addressNode.Text)?.Trim();
|
||||||
|
if(uri != null && uri.Contains("."))
|
||||||
|
{
|
||||||
|
if(!uri.Contains("://") && !uri.Contains(" "))
|
||||||
|
{
|
||||||
|
uri = string.Concat("http://", uri);
|
||||||
|
}
|
||||||
|
else if(Build.VERSION.SdkInt <= BuildVersionCodes.KitkatWatch)
|
||||||
|
{
|
||||||
|
var parts = uri.Split(new string[] { ". " }, StringSplitOptions.None);
|
||||||
|
if(parts.Length > 1)
|
||||||
|
{
|
||||||
|
var urlPart = parts.FirstOrDefault(p => p.StartsWith("http"));
|
||||||
|
if(urlPart != null)
|
||||||
|
{
|
||||||
|
uri = urlPart.Trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Check to make sure it is ok to autofill still on the current screen
|
||||||
|
/// </summary>
|
||||||
|
public static bool NeedToAutofill(Credentials credentials, string currentUriString)
|
||||||
|
{
|
||||||
|
if(credentials == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if(Uri.TryCreate(credentials.LastUri, UriKind.Absolute, out Uri lastUri) &&
|
||||||
|
Uri.TryCreate(currentUriString, UriKind.Absolute, out Uri currentUri))
|
||||||
|
{
|
||||||
|
return lastUri.Host == currentUri.Host;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool EditText(AccessibilityNodeInfo n)
|
||||||
|
{
|
||||||
|
return n?.ClassName?.Contains("EditText") ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static void FillCredentials(AccessibilityNodeInfo usernameNode,
|
||||||
|
IEnumerable<AccessibilityNodeInfo> passwordNodes)
|
||||||
|
{
|
||||||
|
FillEditText(usernameNode, LastCredentials?.Username);
|
||||||
|
foreach(var n in passwordNodes)
|
||||||
|
{
|
||||||
|
FillEditText(n, LastCredentials?.Password);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void FillEditText(AccessibilityNodeInfo editTextNode, string value)
|
||||||
|
{
|
||||||
|
if(editTextNode == null || value == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var bundle = new Bundle();
|
||||||
|
bundle.PutString(AccessibilityNodeInfo.ActionArgumentSetTextCharsequence, value);
|
||||||
|
editTextNode.PerformAction(Android.Views.Accessibility.Action.SetText, bundle);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static NodeList GetWindowNodes(AccessibilityNodeInfo n, AccessibilityEvent e,
|
||||||
|
Func<AccessibilityNodeInfo, bool> condition, bool disposeIfUnused, NodeList nodes = null,
|
||||||
|
int recursionDepth = 0)
|
||||||
|
{
|
||||||
|
if(nodes == null)
|
||||||
|
{
|
||||||
|
nodes = new NodeList();
|
||||||
|
}
|
||||||
|
var dispose = disposeIfUnused;
|
||||||
|
if(n != null && recursionDepth < 50)
|
||||||
|
{
|
||||||
|
var add = n.WindowId == e.WindowId &&
|
||||||
|
!(n.ViewIdResourceName?.StartsWith(SystemUiPackage) ?? false) &&
|
||||||
|
condition(n);
|
||||||
|
if(add)
|
||||||
|
{
|
||||||
|
dispose = false;
|
||||||
|
nodes.Add(n);
|
||||||
|
}
|
||||||
|
|
||||||
|
for(var i = 0; i < n.ChildCount; i++)
|
||||||
|
{
|
||||||
|
var childNode = n.GetChild(i);
|
||||||
|
if(i > 100)
|
||||||
|
{
|
||||||
|
Android.Util.Log.Info(BitwardenTag, "Too many child iterations.");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if(childNode.GetHashCode() == n.GetHashCode())
|
||||||
|
{
|
||||||
|
Android.Util.Log.Info(BitwardenTag, "Child node is the same as parent for some reason.");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
GetWindowNodes(childNode, e, condition, true, nodes, recursionDepth++);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(dispose)
|
||||||
|
{
|
||||||
|
n?.Dispose();
|
||||||
|
}
|
||||||
|
return nodes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void GetNodesAndFill(AccessibilityNodeInfo root, AccessibilityEvent e,
|
||||||
|
IEnumerable<AccessibilityNodeInfo> passwordNodes)
|
||||||
|
{
|
||||||
|
var allEditTexts = GetWindowNodes(root, e, n => EditText(n), false);
|
||||||
|
var usernameEditText = GetUsernameEditText(allEditTexts);
|
||||||
|
FillCredentials(usernameEditText, passwordNodes);
|
||||||
|
allEditTexts.Dispose();
|
||||||
|
usernameEditText = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AccessibilityNodeInfo GetUsernameEditText(IEnumerable<AccessibilityNodeInfo> allEditTexts)
|
||||||
|
{
|
||||||
|
return allEditTexts.TakeWhile(n => !n.Password).LastOrDefault();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
310
src/Android/Accessibility/AccessibilityService.cs
Normal file
|
@ -0,0 +1,310 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Android.App;
|
||||||
|
using Android.Content;
|
||||||
|
using Android.OS;
|
||||||
|
using Android.Runtime;
|
||||||
|
using Android.Views.Accessibility;
|
||||||
|
using Bit.App.Resources;
|
||||||
|
using Bit.Core;
|
||||||
|
using Bit.Core.Abstractions;
|
||||||
|
using Bit.Core.Utilities;
|
||||||
|
|
||||||
|
namespace Bit.Droid.Accessibility
|
||||||
|
{
|
||||||
|
[Service(Permission = Android.Manifest.Permission.BindAccessibilityService, Label = "Bitwarden")]
|
||||||
|
[IntentFilter(new string[] { "android.accessibilityservice.AccessibilityService" })]
|
||||||
|
[MetaData("android.accessibilityservice", Resource = "@xml/accessibilityservice")]
|
||||||
|
[Register("com.x8bit.bitwarden.Accessibility.AccessibilityService")]
|
||||||
|
public class AccessibilityService : Android.AccessibilityServices.AccessibilityService
|
||||||
|
{
|
||||||
|
private NotificationChannel _notificationChannel;
|
||||||
|
|
||||||
|
private const int AutoFillNotificationId = 34573;
|
||||||
|
private const string BitwardenPackage = "com.x8bit.bitwarden";
|
||||||
|
private const string BitwardenWebsite = "vault.bitwarden.com";
|
||||||
|
|
||||||
|
private IStorageService _storageService;
|
||||||
|
private bool _settingAutofillPasswordField;
|
||||||
|
private bool _settingAutofillPersistNotification;
|
||||||
|
private DateTime? _lastSettingsReload = null;
|
||||||
|
private TimeSpan _settingsReloadSpan = TimeSpan.FromMinutes(1);
|
||||||
|
private long _lastNotificationTime = 0;
|
||||||
|
private string _lastNotificationUri = null;
|
||||||
|
private HashSet<string> _launcherPackageNames = null;
|
||||||
|
private DateTime? _lastLauncherSetBuilt = null;
|
||||||
|
private TimeSpan _rebuildLauncherSpan = TimeSpan.FromHours(1);
|
||||||
|
|
||||||
|
public override void OnAccessibilityEvent(AccessibilityEvent e)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var powerManager = GetSystemService(PowerService) as PowerManager;
|
||||||
|
if(Build.VERSION.SdkInt > BuildVersionCodes.KitkatWatch && !powerManager.IsInteractive)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if(Build.VERSION.SdkInt < BuildVersionCodes.Lollipop && !powerManager.IsScreenOn)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(SkipPackage(e?.PackageName))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var root = RootInActiveWindow;
|
||||||
|
if(root == null || root.PackageName != e.PackageName)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccessibilityHelpers.PrintTestData(root, e);
|
||||||
|
LoadServices();
|
||||||
|
var settingsTask = LoadSettingsAsync();
|
||||||
|
|
||||||
|
var notificationManager = GetSystemService(NotificationService) as NotificationManager;
|
||||||
|
var cancelNotification = true;
|
||||||
|
|
||||||
|
switch(e.EventType)
|
||||||
|
{
|
||||||
|
case EventTypes.ViewFocused:
|
||||||
|
if(e.Source == null || !e.Source.Password || !_settingAutofillPasswordField)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if(e.PackageName == BitwardenPackage)
|
||||||
|
{
|
||||||
|
CancelNotification(notificationManager);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if(ScanAndAutofill(root, e, notificationManager, cancelNotification))
|
||||||
|
{
|
||||||
|
CancelNotification(notificationManager);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case EventTypes.WindowContentChanged:
|
||||||
|
case EventTypes.WindowStateChanged:
|
||||||
|
if(_settingAutofillPasswordField && e.Source.Password)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if(_settingAutofillPasswordField && AccessibilityHelpers.LastCredentials == null)
|
||||||
|
{
|
||||||
|
if(string.IsNullOrWhiteSpace(_lastNotificationUri))
|
||||||
|
{
|
||||||
|
CancelNotification(notificationManager);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
var uri = AccessibilityHelpers.GetUri(root);
|
||||||
|
if(uri != _lastNotificationUri)
|
||||||
|
{
|
||||||
|
CancelNotification(notificationManager);
|
||||||
|
}
|
||||||
|
else if(uri.StartsWith(Constants.AndroidAppProtocol))
|
||||||
|
{
|
||||||
|
CancelNotification(notificationManager, 30000);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(e.PackageName == BitwardenPackage)
|
||||||
|
{
|
||||||
|
CancelNotification(notificationManager);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(_settingAutofillPersistNotification)
|
||||||
|
{
|
||||||
|
var uri = AccessibilityHelpers.GetUri(root);
|
||||||
|
if(uri != null && !uri.Contains(BitwardenWebsite))
|
||||||
|
{
|
||||||
|
var needToFill = AccessibilityHelpers.NeedToAutofill(
|
||||||
|
AccessibilityHelpers.LastCredentials, uri);
|
||||||
|
if(needToFill)
|
||||||
|
{
|
||||||
|
var passwordNodes = AccessibilityHelpers.GetWindowNodes(root, e,
|
||||||
|
n => n.Password, false);
|
||||||
|
needToFill = passwordNodes.Any();
|
||||||
|
if(needToFill)
|
||||||
|
{
|
||||||
|
AccessibilityHelpers.GetNodesAndFill(root, e, passwordNodes);
|
||||||
|
}
|
||||||
|
passwordNodes.Dispose();
|
||||||
|
}
|
||||||
|
if(!needToFill)
|
||||||
|
{
|
||||||
|
NotifyToAutofill(uri, notificationManager);
|
||||||
|
cancelNotification = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AccessibilityHelpers.LastCredentials = null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
cancelNotification = ScanAndAutofill(root, e, notificationManager, cancelNotification);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(cancelNotification)
|
||||||
|
{
|
||||||
|
CancelNotification(notificationManager);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
notificationManager?.Dispose();
|
||||||
|
root.Dispose();
|
||||||
|
e.Dispose();
|
||||||
|
}
|
||||||
|
// Suppress exceptions so that service doesn't crash.
|
||||||
|
catch { }
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnInterrupt()
|
||||||
|
{
|
||||||
|
// Do nothing.
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ScanAndAutofill(AccessibilityNodeInfo root, AccessibilityEvent e,
|
||||||
|
NotificationManager notificationManager, bool cancelNotification)
|
||||||
|
{
|
||||||
|
var passwordNodes = AccessibilityHelpers.GetWindowNodes(root, e, n => n.Password, false);
|
||||||
|
if(passwordNodes.Count > 0)
|
||||||
|
{
|
||||||
|
var uri = AccessibilityHelpers.GetUri(root);
|
||||||
|
if(uri != null && !uri.Contains(BitwardenWebsite))
|
||||||
|
{
|
||||||
|
if(AccessibilityHelpers.NeedToAutofill(AccessibilityHelpers.LastCredentials, uri))
|
||||||
|
{
|
||||||
|
AccessibilityHelpers.GetNodesAndFill(root, e, passwordNodes);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
NotifyToAutofill(uri, notificationManager);
|
||||||
|
cancelNotification = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AccessibilityHelpers.LastCredentials = null;
|
||||||
|
}
|
||||||
|
else if(AccessibilityHelpers.LastCredentials != null)
|
||||||
|
{
|
||||||
|
Task.Run(async () =>
|
||||||
|
{
|
||||||
|
await Task.Delay(1000);
|
||||||
|
AccessibilityHelpers.LastCredentials = null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
passwordNodes.Dispose();
|
||||||
|
return cancelNotification;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CancelNotification(NotificationManager notificationManager, long limit = 250)
|
||||||
|
{
|
||||||
|
if(Java.Lang.JavaSystem.CurrentTimeMillis() - _lastNotificationTime < limit)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_lastNotificationUri = null;
|
||||||
|
notificationManager?.Cancel(AutoFillNotificationId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void NotifyToAutofill(string uri, NotificationManager notificationManager)
|
||||||
|
{
|
||||||
|
if(notificationManager == null || string.IsNullOrWhiteSpace(uri))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var now = Java.Lang.JavaSystem.CurrentTimeMillis();
|
||||||
|
var intent = new Intent(this, typeof(AccessibilityActivity));
|
||||||
|
intent.PutExtra("uri", uri);
|
||||||
|
intent.SetFlags(ActivityFlags.NewTask | ActivityFlags.SingleTop | ActivityFlags.ClearTop);
|
||||||
|
var pendingIntent = PendingIntent.GetActivity(this, 0, intent, PendingIntentFlags.UpdateCurrent);
|
||||||
|
|
||||||
|
var notificationContent = Build.VERSION.SdkInt > BuildVersionCodes.KitkatWatch ?
|
||||||
|
AppResources.BitwardenAutofillServiceNotificationContent :
|
||||||
|
AppResources.BitwardenAutofillServiceNotificationContentOld;
|
||||||
|
|
||||||
|
var builder = new Notification.Builder(this);
|
||||||
|
builder.SetSmallIcon(Resource.Drawable.notification_sm)
|
||||||
|
.SetContentTitle(AppResources.BitwardenAutofillService)
|
||||||
|
.SetContentText(notificationContent)
|
||||||
|
.SetTicker(notificationContent)
|
||||||
|
.SetWhen(now)
|
||||||
|
.SetContentIntent(pendingIntent);
|
||||||
|
|
||||||
|
if(Build.VERSION.SdkInt > BuildVersionCodes.KitkatWatch)
|
||||||
|
{
|
||||||
|
builder.SetVisibility(NotificationVisibility.Secret)
|
||||||
|
.SetColor(Android.Support.V4.Content.ContextCompat.GetColor(ApplicationContext,
|
||||||
|
Resource.Color.primary));
|
||||||
|
}
|
||||||
|
if(Build.VERSION.SdkInt >= BuildVersionCodes.O)
|
||||||
|
{
|
||||||
|
if(_notificationChannel == null)
|
||||||
|
{
|
||||||
|
_notificationChannel = new NotificationChannel("bitwarden_autofill_service",
|
||||||
|
AppResources.AutofillService, NotificationImportance.Low);
|
||||||
|
notificationManager.CreateNotificationChannel(_notificationChannel);
|
||||||
|
}
|
||||||
|
builder.SetChannelId(_notificationChannel.Id);
|
||||||
|
}
|
||||||
|
if(/*Build.VERSION.SdkInt <= BuildVersionCodes.N && */_settingAutofillPersistNotification)
|
||||||
|
{
|
||||||
|
builder.SetPriority(-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
_lastNotificationTime = now;
|
||||||
|
_lastNotificationUri = uri;
|
||||||
|
notificationManager.Notify(AutoFillNotificationId, builder.Build());
|
||||||
|
builder.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool SkipPackage(string eventPackageName)
|
||||||
|
{
|
||||||
|
if(string.IsNullOrWhiteSpace(eventPackageName) ||
|
||||||
|
AccessibilityHelpers.FilteredPackageNames.Contains(eventPackageName) ||
|
||||||
|
eventPackageName.Contains("launcher"))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if(_launcherPackageNames == null || _lastLauncherSetBuilt == null ||
|
||||||
|
(DateTime.Now - _lastLauncherSetBuilt.Value) > _rebuildLauncherSpan)
|
||||||
|
{
|
||||||
|
// refresh launcher list every now and then
|
||||||
|
_lastLauncherSetBuilt = DateTime.Now;
|
||||||
|
var intent = new Intent(Intent.ActionMain);
|
||||||
|
intent.AddCategory(Intent.CategoryHome);
|
||||||
|
var resolveInfo = PackageManager.QueryIntentActivities(intent, 0);
|
||||||
|
_launcherPackageNames = resolveInfo.Select(ri => ri.ActivityInfo.PackageName).ToHashSet();
|
||||||
|
}
|
||||||
|
return _launcherPackageNames.Contains(eventPackageName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LoadServices()
|
||||||
|
{
|
||||||
|
if(_storageService == null)
|
||||||
|
{
|
||||||
|
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task LoadSettingsAsync()
|
||||||
|
{
|
||||||
|
var now = DateTime.UtcNow;
|
||||||
|
if(_lastSettingsReload == null || (now - _lastSettingsReload.Value) > _settingsReloadSpan)
|
||||||
|
{
|
||||||
|
_lastSettingsReload = now;
|
||||||
|
_settingAutofillPasswordField = await _storageService.GetAsync<bool>(
|
||||||
|
Constants.AccessibilityAutofillPasswordFieldKey);
|
||||||
|
_settingAutofillPersistNotification = await _storageService.GetAsync<bool>(
|
||||||
|
Constants.AccessibilityAutofillPersistNotificationKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
23
src/Android/Accessibility/Browser.cs
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Bit.Droid.Accessibility
|
||||||
|
{
|
||||||
|
public class Browser
|
||||||
|
{
|
||||||
|
public Browser(string packageName, string uriViewId)
|
||||||
|
{
|
||||||
|
PackageName = packageName;
|
||||||
|
UriViewId = uriViewId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Browser(string packageName, string uriViewId, Func<string, string> getUriFunction)
|
||||||
|
: this(packageName, uriViewId)
|
||||||
|
{
|
||||||
|
GetUriFunction = getUriFunction;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string PackageName { get; set; }
|
||||||
|
public string UriViewId { get; set; }
|
||||||
|
public Func<string, string> GetUriFunction { get; set; } = (s) => s;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
namespace Bit.Android
|
namespace Bit.Droid.Accessibility
|
||||||
{
|
{
|
||||||
public class AutofillCredentials
|
public class Credentials
|
||||||
{
|
{
|
||||||
public string Username { get; set; }
|
public string Username { get; set; }
|
||||||
public string Password { get; set; }
|
public string Password { get; set; }
|
17
src/Android/Accessibility/NodeList.cs
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
using Android.Views.Accessibility;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Bit.Droid.Accessibility
|
||||||
|
{
|
||||||
|
public class NodeList : List<AccessibilityNodeInfo>, IDisposable
|
||||||
|
{
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
foreach(var item in this)
|
||||||
|
{
|
||||||
|
item.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,19 +0,0 @@
|
||||||
Any raw assets you want to be deployed with your application can be placed in
|
|
||||||
this directory (and child directories) and given a Build Action of "AndroidAsset".
|
|
||||||
|
|
||||||
These files will be deployed with you package and will be accessible using Android's
|
|
||||||
AssetManager, like this:
|
|
||||||
|
|
||||||
public class ReadAsset : Activity
|
|
||||||
{
|
|
||||||
protected override void OnCreate (Bundle bundle)
|
|
||||||
{
|
|
||||||
base.OnCreate (bundle);
|
|
||||||
|
|
||||||
InputStream input = Assets.Open ("my_asset.txt");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Additionally, some Android functions will automatically load asset files:
|
|
||||||
|
|
||||||
Typeface tf = Typeface.CreateFromAsset (Context.Assets, "fonts/samplefont.ttf");
|
|
BIN
src/Android/Assets/FontAwesome.ttf
Normal file
BIN
src/Android/Assets/MaterialIcons_Regular.ttf
Normal file
BIN
src/Android/Assets/RobotoMono_Regular.ttf
Normal file
|
@ -1,17 +1,16 @@
|
||||||
using System;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Generic;
|
|
||||||
using Android.Content;
|
using Android.Content;
|
||||||
using Android.Service.Autofill;
|
using Android.Service.Autofill;
|
||||||
using Android.Widget;
|
using Android.Widget;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Android.App;
|
using Android.App;
|
||||||
using Bit.App.Abstractions;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Bit.App.Resources;
|
using Bit.App.Resources;
|
||||||
using Bit.App.Enums;
|
using Bit.Core.Enums;
|
||||||
using Android.Views.Autofill;
|
using Android.Views.Autofill;
|
||||||
|
using Bit.Core.Abstractions;
|
||||||
|
|
||||||
namespace Bit.Android.Autofill
|
namespace Bit.Droid.Autofill
|
||||||
{
|
{
|
||||||
public static class AutofillHelpers
|
public static class AutofillHelpers
|
||||||
{
|
{
|
||||||
|
@ -20,74 +19,91 @@ namespace Bit.Android.Autofill
|
||||||
// These browser work natively with the autofill framework
|
// These browser work natively with the autofill framework
|
||||||
public static HashSet<string> TrustedBrowsers = new HashSet<string>
|
public static HashSet<string> TrustedBrowsers = new HashSet<string>
|
||||||
{
|
{
|
||||||
"org.mozilla.focus","org.mozilla.klar","com.duckduckgo.mobile.android"
|
"org.mozilla.focus",
|
||||||
|
"org.mozilla.klar",
|
||||||
|
"com.duckduckgo.mobile.android",
|
||||||
};
|
};
|
||||||
|
|
||||||
// These browsers work using the compatibility shim for the autofill framework
|
// These browsers work using the compatibility shim for the autofill framework
|
||||||
public static HashSet<string> CompatBrowsers = new HashSet<string>
|
public static HashSet<string> CompatBrowsers = new HashSet<string>
|
||||||
{
|
{
|
||||||
"org.mozilla.firefox","org.mozilla.firefox_beta","com.microsoft.emmx","com.android.chrome",
|
"org.mozilla.firefox",
|
||||||
"com.chrome.beta","com.android.browser","com.brave.browser","com.opera.browser",
|
"org.mozilla.firefox_beta",
|
||||||
"com.opera.browser.beta","com.opera.mini.native","com.chrome.dev","com.chrome.canary",
|
"com.microsoft.emmx",
|
||||||
"com.google.android.apps.chrome","com.google.android.apps.chrome_dev","com.yandex.browser",
|
"com.android.chrome",
|
||||||
"com.sec.android.app.sbrowser","com.sec.android.app.sbrowser.beta","org.codeaurora.swe.browser",
|
"com.chrome.beta",
|
||||||
"com.amazon.cloud9","mark.via.gp","org.bromite.bromite","org.chromium.chrome","com.kiwibrowser.browser",
|
"com.android.browser",
|
||||||
"com.ecosia.android","com.opera.mini.native.beta","org.mozilla.fennec_aurora","org.mozilla.fennec_fdroid",
|
"com.brave.browser",
|
||||||
"com.qwant.liberty", "com.opera.touch","org.mozilla.fenix","org.mozilla.reference.browser",
|
"com.opera.browser",
|
||||||
"org.mozilla.rocket"
|
"com.opera.browser.beta",
|
||||||
|
"com.opera.mini.native",
|
||||||
|
"com.chrome.dev",
|
||||||
|
"com.chrome.canary",
|
||||||
|
"com.google.android.apps.chrome",
|
||||||
|
"com.google.android.apps.chrome_dev",
|
||||||
|
"com.yandex.browser",
|
||||||
|
"com.sec.android.app.sbrowser",
|
||||||
|
"com.sec.android.app.sbrowser.beta",
|
||||||
|
"org.codeaurora.swe.browser",
|
||||||
|
"com.amazon.cloud9",
|
||||||
|
"mark.via.gp",
|
||||||
|
"org.bromite.bromite",
|
||||||
|
"org.chromium.chrome",
|
||||||
|
"com.kiwibrowser.browser",
|
||||||
|
"com.ecosia.android",
|
||||||
|
"com.opera.mini.native.beta",
|
||||||
|
"org.mozilla.fennec_aurora",
|
||||||
|
"com.qwant.liberty",
|
||||||
|
"com.opera.touch",
|
||||||
|
"org.mozilla.fenix",
|
||||||
|
"org.mozilla.reference.browser",
|
||||||
|
"org.mozilla.rocket",
|
||||||
};
|
};
|
||||||
|
|
||||||
// The URLs are blacklisted from autofilling
|
// The URLs are blacklisted from autofilling
|
||||||
public static HashSet<string> BlacklistedUris = new HashSet<string>
|
public static HashSet<string> BlacklistedUris = new HashSet<string>
|
||||||
{
|
{
|
||||||
"androidapp://android", "androidapp://com.x8bit.bitwarden", "androidapp://com.oneplus.applocker"
|
"androidapp://android",
|
||||||
|
"androidapp://com.x8bit.bitwarden",
|
||||||
|
"androidapp://com.oneplus.applocker",
|
||||||
};
|
};
|
||||||
|
|
||||||
public static async Task<List<FilledItem>> GetFillItemsAsync(Parser parser, ICipherService service)
|
public static async Task<List<FilledItem>> GetFillItemsAsync(Parser parser, ICipherService cipherService)
|
||||||
{
|
{
|
||||||
var items = new List<FilledItem>();
|
|
||||||
|
|
||||||
if(parser.FieldCollection.FillableForLogin)
|
if(parser.FieldCollection.FillableForLogin)
|
||||||
{
|
{
|
||||||
var ciphers = await service.GetAllAsync(parser.Uri);
|
var ciphers = await cipherService.GetAllDecryptedByUrlAsync(parser.Uri);
|
||||||
if(ciphers.Item1.Any() || ciphers.Item2.Any())
|
if(ciphers.Item1.Any() || ciphers.Item2.Any())
|
||||||
{
|
{
|
||||||
var allCiphers = ciphers.Item1.ToList();
|
var allCiphers = ciphers.Item1.ToList();
|
||||||
allCiphers.AddRange(ciphers.Item2.ToList());
|
allCiphers.AddRange(ciphers.Item2.ToList());
|
||||||
foreach(var cipher in allCiphers)
|
return allCiphers.Select(c => new FilledItem(c)).ToList();
|
||||||
{
|
|
||||||
items.Add(new FilledItem(cipher));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if(parser.FieldCollection.FillableForCard)
|
else if(parser.FieldCollection.FillableForCard)
|
||||||
{
|
{
|
||||||
var ciphers = await service.GetAllAsync();
|
var ciphers = await cipherService.GetAllDecryptedAsync();
|
||||||
foreach(var cipher in ciphers.Where(c => c.Type == CipherType.Card))
|
return ciphers.Where(c => c.Type == CipherType.Card).Select(c => new FilledItem(c)).ToList();
|
||||||
{
|
|
||||||
items.Add(new FilledItem(cipher));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return new List<FilledItem>();
|
||||||
return items;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static FillResponse BuildFillResponse(Context context, Parser parser, List<FilledItem> items, bool locked)
|
public static FillResponse BuildFillResponse(Parser parser, List<FilledItem> items, bool locked)
|
||||||
{
|
{
|
||||||
var responseBuilder = new FillResponse.Builder();
|
var responseBuilder = new FillResponse.Builder();
|
||||||
if(items != null && items.Count > 0)
|
if(items != null && items.Count > 0)
|
||||||
{
|
{
|
||||||
foreach(var item in items)
|
foreach(var item in items)
|
||||||
{
|
{
|
||||||
var dataset = BuildDataset(context, parser.FieldCollection, item);
|
var dataset = BuildDataset(parser.ApplicationContext, parser.FieldCollection, item);
|
||||||
if(dataset != null)
|
if(dataset != null)
|
||||||
{
|
{
|
||||||
responseBuilder.AddDataset(dataset);
|
responseBuilder.AddDataset(dataset);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
responseBuilder.AddDataset(BuildVaultDataset(parser.ApplicationContext, parser.FieldCollection,
|
||||||
responseBuilder.AddDataset(BuildVaultDataset(context, parser.FieldCollection, parser.Uri, locked));
|
parser.Uri, locked));
|
||||||
AddSaveInfo(parser, responseBuilder, parser.FieldCollection);
|
AddSaveInfo(parser, responseBuilder, parser.FieldCollection);
|
||||||
responseBuilder.SetIgnoredIds(parser.FieldCollection.IgnoreAutofillIds.ToArray());
|
responseBuilder.SetIgnoredIds(parser.FieldCollection.IgnoreAutofillIds.ToArray());
|
||||||
return responseBuilder.Build();
|
return responseBuilder.Build();
|
||||||
|
@ -96,7 +112,7 @@ namespace Bit.Android.Autofill
|
||||||
public static Dataset BuildDataset(Context context, FieldCollection fields, FilledItem filledItem)
|
public static Dataset BuildDataset(Context context, FieldCollection fields, FilledItem filledItem)
|
||||||
{
|
{
|
||||||
var datasetBuilder = new Dataset.Builder(
|
var datasetBuilder = new Dataset.Builder(
|
||||||
BuildListView(context.PackageName, filledItem.Name, filledItem.Subtitle, filledItem.Icon));
|
BuildListView(filledItem.Name, filledItem.Subtitle, filledItem.Icon, context));
|
||||||
if(filledItem.ApplyToFields(fields, datasetBuilder))
|
if(filledItem.ApplyToFields(fields, datasetBuilder))
|
||||||
{
|
{
|
||||||
return datasetBuilder.Build();
|
return datasetBuilder.Build();
|
||||||
|
@ -128,8 +144,11 @@ namespace Bit.Android.Autofill
|
||||||
var pendingIntent = PendingIntent.GetActivity(context, ++_pendingIntentId, intent,
|
var pendingIntent = PendingIntent.GetActivity(context, ++_pendingIntentId, intent,
|
||||||
PendingIntentFlags.CancelCurrent);
|
PendingIntentFlags.CancelCurrent);
|
||||||
|
|
||||||
var view = BuildListView(context.PackageName, AppResources.AutofillWithBitwarden,
|
var view = BuildListView(
|
||||||
locked ? AppResources.VaultIsLocked : AppResources.GoToMyVault, Resource.Drawable.icon);
|
AppResources.AutofillWithBitwarden,
|
||||||
|
locked ? AppResources.VaultIsLocked : AppResources.GoToMyVault,
|
||||||
|
Resource.Drawable.icon,
|
||||||
|
context);
|
||||||
|
|
||||||
var datasetBuilder = new Dataset.Builder(view);
|
var datasetBuilder = new Dataset.Builder(view);
|
||||||
datasetBuilder.SetAuthentication(pendingIntent.IntentSender);
|
datasetBuilder.SetAuthentication(pendingIntent.IntentSender);
|
||||||
|
@ -139,14 +158,14 @@ namespace Bit.Android.Autofill
|
||||||
{
|
{
|
||||||
datasetBuilder.SetValue(autofillId, AutofillValue.ForText("PLACEHOLDER"));
|
datasetBuilder.SetValue(autofillId, AutofillValue.ForText("PLACEHOLDER"));
|
||||||
}
|
}
|
||||||
|
|
||||||
return datasetBuilder.Build();
|
return datasetBuilder.Build();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static RemoteViews BuildListView(string packageName, string text, string subtext, int iconId)
|
public static RemoteViews BuildListView(string text, string subtext, int iconId, Context context)
|
||||||
{
|
{
|
||||||
|
var packageName = context.PackageName;
|
||||||
var view = new RemoteViews(packageName, Resource.Layout.autofill_listitem);
|
var view = new RemoteViews(packageName, Resource.Layout.autofill_listitem);
|
||||||
view.SetTextViewText(Resource.Id.text, text);
|
view.SetTextViewText(Resource.Id.text1, text);
|
||||||
view.SetTextViewText(Resource.Id.text2, subtext);
|
view.SetTextViewText(Resource.Id.text2, subtext);
|
||||||
view.SetImageViewResource(Resource.Id.icon, iconId);
|
view.SetImageViewResource(Resource.Id.icon, iconId);
|
||||||
return view;
|
return view;
|
||||||
|
|
|
@ -5,20 +5,20 @@ using Android.OS;
|
||||||
using Android.Runtime;
|
using Android.Runtime;
|
||||||
using Android.Service.Autofill;
|
using Android.Service.Autofill;
|
||||||
using Android.Widget;
|
using Android.Widget;
|
||||||
using Bit.App;
|
using Bit.Core;
|
||||||
using Bit.App.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
using Bit.App.Enums;
|
using Bit.Core.Enums;
|
||||||
|
using Bit.Core.Utilities;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using XLabs.Ioc;
|
|
||||||
|
|
||||||
namespace Bit.Android.Autofill
|
namespace Bit.Droid.Autofill
|
||||||
{
|
{
|
||||||
[Service(Permission = Manifest.Permission.BindAutofillService, Label = "Bitwarden")]
|
[Service(Permission = Manifest.Permission.BindAutofillService, Label = "Bitwarden")]
|
||||||
[IntentFilter(new string[] { "android.service.autofill.AutofillService" })]
|
[IntentFilter(new string[] { "android.service.autofill.AutofillService" })]
|
||||||
[MetaData("android.autofill", Resource = "@xml/autofillservice")]
|
[MetaData("android.autofill", Resource = "@xml/autofillservice")]
|
||||||
[Register("com.x8bit.bitwarden.Autofill.AutofillService")]
|
[Register("com.x8bit.bitwarden.Autofill.AutofillService")]
|
||||||
public class AutofillService : global::Android.Service.Autofill.AutofillService
|
public class AutofillService : Android.Service.Autofill.AutofillService
|
||||||
{
|
{
|
||||||
private ICipherService _cipherService;
|
private ICipherService _cipherService;
|
||||||
private ILockService _lockService;
|
private ILockService _lockService;
|
||||||
|
@ -31,33 +31,32 @@ namespace Bit.Android.Autofill
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var parser = new Parser(structure);
|
var parser = new Parser(structure, ApplicationContext);
|
||||||
parser.Parse();
|
parser.Parse();
|
||||||
|
|
||||||
if(!parser.ShouldAutofill)
|
if(!parser.ShouldAutofill)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(_lockService == null)
|
if(_lockService == null)
|
||||||
{
|
{
|
||||||
_lockService = Resolver.Resolve<ILockService>();
|
_lockService = ServiceContainer.Resolve<ILockService>("lockService");
|
||||||
}
|
}
|
||||||
|
|
||||||
List<FilledItem> items = null;
|
List<FilledItem> items = null;
|
||||||
var locked = (await _lockService.GetLockTypeAsync(false)) != LockType.None;
|
var locked = await _lockService.IsLockedAsync();
|
||||||
if(!locked)
|
if(!locked)
|
||||||
{
|
{
|
||||||
if(_cipherService == null)
|
if(_cipherService == null)
|
||||||
{
|
{
|
||||||
_cipherService = Resolver.Resolve<ICipherService>();
|
_cipherService = ServiceContainer.Resolve<ICipherService>("cipherService");
|
||||||
}
|
}
|
||||||
|
|
||||||
items = await AutofillHelpers.GetFillItemsAsync(parser, _cipherService);
|
items = await AutofillHelpers.GetFillItemsAsync(parser, _cipherService);
|
||||||
}
|
}
|
||||||
|
|
||||||
// build response
|
// build response
|
||||||
var response = AutofillHelpers.BuildFillResponse(this, parser, items, locked);
|
var response = AutofillHelpers.BuildFillResponse(parser, items, locked);
|
||||||
callback.OnSuccess(response);
|
callback.OnSuccess(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,7 +68,7 @@ namespace Bit.Android.Autofill
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var parser = new Parser(structure);
|
var parser = new Parser(structure, ApplicationContext);
|
||||||
parser.Parse();
|
parser.Parse();
|
||||||
|
|
||||||
var savedItem = parser.FieldCollection.GetSavedItem();
|
var savedItem = parser.FieldCollection.GetSavedItem();
|
||||||
|
|
|
@ -7,7 +7,7 @@ using static Android.App.Assist.AssistStructure;
|
||||||
using Android.Text;
|
using Android.Text;
|
||||||
using static Android.Views.ViewStructure;
|
using static Android.Views.ViewStructure;
|
||||||
|
|
||||||
namespace Bit.Android.Autofill
|
namespace Bit.Droid.Autofill
|
||||||
{
|
{
|
||||||
public class Field
|
public class Field
|
||||||
{
|
{
|
||||||
|
@ -86,6 +86,69 @@ namespace Bit.Android.Autofill
|
||||||
public HtmlInfo HtmlInfo { get; private set; }
|
public HtmlInfo HtmlInfo { get; private set; }
|
||||||
public ViewNode Node { get; private set; }
|
public ViewNode Node { get; private set; }
|
||||||
|
|
||||||
|
public bool ValueIsNull()
|
||||||
|
{
|
||||||
|
return TextValue == null && DateValue == null && ToggleValue == null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Equals(object obj)
|
||||||
|
{
|
||||||
|
if(this == obj)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if(obj == null || GetType() != obj.GetType())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
var field = obj as Field;
|
||||||
|
if(TextValue != null ? !TextValue.Equals(field.TextValue) : field.TextValue != null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if(DateValue != null ? !DateValue.Equals(field.DateValue) : field.DateValue != null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return ToggleValue != null ? ToggleValue.Equals(field.ToggleValue) : field.ToggleValue == null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
var result = TextValue != null ? TextValue.GetHashCode() : 0;
|
||||||
|
result = 31 * result + (DateValue != null ? DateValue.GetHashCode() : 0);
|
||||||
|
result = 31 * result + (ToggleValue != null ? ToggleValue.GetHashCode() : 0);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<string> FilterForSupportedHints(string[] hints)
|
||||||
|
{
|
||||||
|
return hints?.Where(h => IsValidHint(h)).ToList() ?? new List<string>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsValidHint(string hint)
|
||||||
|
{
|
||||||
|
switch(hint)
|
||||||
|
{
|
||||||
|
case View.AutofillHintCreditCardExpirationDate:
|
||||||
|
case View.AutofillHintCreditCardExpirationDay:
|
||||||
|
case View.AutofillHintCreditCardExpirationMonth:
|
||||||
|
case View.AutofillHintCreditCardExpirationYear:
|
||||||
|
case View.AutofillHintCreditCardNumber:
|
||||||
|
case View.AutofillHintCreditCardSecurityCode:
|
||||||
|
case View.AutofillHintEmailAddress:
|
||||||
|
case View.AutofillHintPhone:
|
||||||
|
case View.AutofillHintName:
|
||||||
|
case View.AutofillHintPassword:
|
||||||
|
case View.AutofillHintPostalAddress:
|
||||||
|
case View.AutofillHintPostalCode:
|
||||||
|
case View.AutofillHintUsername:
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void UpdateSaveTypeFromHints()
|
private void UpdateSaveTypeFromHints()
|
||||||
{
|
{
|
||||||
SaveType = SaveDataType.Generic;
|
SaveType = SaveDataType.Generic;
|
||||||
|
@ -128,72 +191,5 @@ namespace Bit.Android.Autofill
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool ValueIsNull()
|
|
||||||
{
|
|
||||||
return TextValue == null && DateValue == null && ToggleValue == null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool Equals(object obj)
|
|
||||||
{
|
|
||||||
if(this == obj)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(obj == null || GetType() != obj.GetType())
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var field = obj as Field;
|
|
||||||
if(TextValue != null ? !TextValue.Equals(field.TextValue) : field.TextValue != null)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(DateValue != null ? !DateValue.Equals(field.DateValue) : field.DateValue != null)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ToggleValue != null ? ToggleValue.Equals(field.ToggleValue) : field.ToggleValue == null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override int GetHashCode()
|
|
||||||
{
|
|
||||||
var result = TextValue != null ? TextValue.GetHashCode() : 0;
|
|
||||||
result = 31 * result + (DateValue != null ? DateValue.GetHashCode() : 0);
|
|
||||||
result = 31 * result + (ToggleValue != null ? ToggleValue.GetHashCode() : 0);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static List<string> FilterForSupportedHints(string[] hints)
|
|
||||||
{
|
|
||||||
return hints?.Where(h => IsValidHint(h)).ToList() ?? new List<string>();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool IsValidHint(string hint)
|
|
||||||
{
|
|
||||||
switch(hint)
|
|
||||||
{
|
|
||||||
case View.AutofillHintCreditCardExpirationDate:
|
|
||||||
case View.AutofillHintCreditCardExpirationDay:
|
|
||||||
case View.AutofillHintCreditCardExpirationMonth:
|
|
||||||
case View.AutofillHintCreditCardExpirationYear:
|
|
||||||
case View.AutofillHintCreditCardNumber:
|
|
||||||
case View.AutofillHintCreditCardSecurityCode:
|
|
||||||
case View.AutofillHintEmailAddress:
|
|
||||||
case View.AutofillHintPhone:
|
|
||||||
case View.AutofillHintName:
|
|
||||||
case View.AutofillHintPassword:
|
|
||||||
case View.AutofillHintPostalAddress:
|
|
||||||
case View.AutofillHintPostalCode:
|
|
||||||
case View.AutofillHintUsername:
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ using System.Linq;
|
||||||
using Android.Text;
|
using Android.Text;
|
||||||
using Android.Views;
|
using Android.Views;
|
||||||
|
|
||||||
namespace Bit.Android.Autofill
|
namespace Bit.Droid.Autofill
|
||||||
{
|
{
|
||||||
public class FieldCollection
|
public class FieldCollection
|
||||||
{
|
{
|
||||||
|
@ -47,7 +47,6 @@ namespace Bit.Android.Autofill
|
||||||
{
|
{
|
||||||
return _passwordFields;
|
return _passwordFields;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(Hints.Any())
|
if(Hints.Any())
|
||||||
{
|
{
|
||||||
_passwordFields = new List<Field>();
|
_passwordFields = new List<Field>();
|
||||||
|
@ -64,7 +63,6 @@ namespace Bit.Android.Autofill
|
||||||
_passwordFields = Fields.Where(f => FieldHasPasswordTerms(f)).ToList();
|
_passwordFields = Fields.Where(f => FieldHasPasswordTerms(f)).ToList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return _passwordFields;
|
return _passwordFields;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -77,7 +75,6 @@ namespace Bit.Android.Autofill
|
||||||
{
|
{
|
||||||
return _usernameFields;
|
return _usernameFields;
|
||||||
}
|
}
|
||||||
|
|
||||||
_usernameFields = new List<Field>();
|
_usernameFields = new List<Field>();
|
||||||
if(Hints.Any())
|
if(Hints.Any())
|
||||||
{
|
{
|
||||||
|
@ -102,20 +99,29 @@ namespace Bit.Android.Autofill
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return _usernameFields;
|
return _usernameFields;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool FillableForLogin => FocusedHintsContain(
|
public bool FillableForLogin => FocusedHintsContain(new string[] {
|
||||||
new string[] { View.AutofillHintUsername, View.AutofillHintEmailAddress, View.AutofillHintPassword }) ||
|
View.AutofillHintUsername,
|
||||||
UsernameFields.Any(f => f.Focused) || PasswordFields.Any(f => f.Focused);
|
View.AutofillHintEmailAddress,
|
||||||
public bool FillableForCard => FocusedHintsContain(
|
View.AutofillHintPassword
|
||||||
new string[] { View.AutofillHintCreditCardNumber, View.AutofillHintCreditCardExpirationMonth,
|
}) || UsernameFields.Any(f => f.Focused) || PasswordFields.Any(f => f.Focused);
|
||||||
View.AutofillHintCreditCardExpirationYear, View.AutofillHintCreditCardSecurityCode});
|
|
||||||
public bool FillableForIdentity => FocusedHintsContain(
|
public bool FillableForCard => FocusedHintsContain(new string[] {
|
||||||
new string[] { View.AutofillHintName, View.AutofillHintPhone, View.AutofillHintPostalAddress,
|
View.AutofillHintCreditCardNumber,
|
||||||
View.AutofillHintPostalCode });
|
View.AutofillHintCreditCardExpirationMonth,
|
||||||
|
View.AutofillHintCreditCardExpirationYear,
|
||||||
|
View.AutofillHintCreditCardSecurityCode
|
||||||
|
});
|
||||||
|
|
||||||
|
public bool FillableForIdentity => FocusedHintsContain(new string[] {
|
||||||
|
View.AutofillHintName,
|
||||||
|
View.AutofillHintPhone,
|
||||||
|
View.AutofillHintPostalAddress,
|
||||||
|
View.AutofillHintPostalCode
|
||||||
|
});
|
||||||
|
|
||||||
public bool Fillable => FillableForLogin || FillableForCard || FillableForIdentity;
|
public bool Fillable => FillableForLogin || FillableForCard || FillableForIdentity;
|
||||||
|
|
||||||
|
@ -127,7 +133,6 @@ namespace Bit.Android.Autofill
|
||||||
}
|
}
|
||||||
|
|
||||||
_passwordFields = _usernameFields = null;
|
_passwordFields = _usernameFields = null;
|
||||||
|
|
||||||
FieldTrackingIds.Add(field.TrackingId);
|
FieldTrackingIds.Add(field.TrackingId);
|
||||||
Fields.Add(field);
|
Fields.Add(field);
|
||||||
AutofillIds.Add(field.AutofillId);
|
AutofillIds.Add(field.AutofillId);
|
||||||
|
@ -141,12 +146,10 @@ namespace Bit.Android.Autofill
|
||||||
{
|
{
|
||||||
FocusedHints.Add(hint);
|
FocusedHints.Add(hint);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!HintToFieldsMap.ContainsKey(hint))
|
if(!HintToFieldsMap.ContainsKey(hint))
|
||||||
{
|
{
|
||||||
HintToFieldsMap.Add(hint, new List<Field>());
|
HintToFieldsMap.Add(hint, new List<Field>());
|
||||||
}
|
}
|
||||||
|
|
||||||
HintToFieldsMap[hint].Add(field);
|
HintToFieldsMap[hint].Add(field);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -164,7 +167,7 @@ namespace Bit.Android.Autofill
|
||||||
|
|
||||||
var savedItem = new SavedItem
|
var savedItem = new SavedItem
|
||||||
{
|
{
|
||||||
Type = App.Enums.CipherType.Login,
|
Type = Core.Enums.CipherType.Login,
|
||||||
Login = new SavedItem.LoginItem
|
Login = new SavedItem.LoginItem
|
||||||
{
|
{
|
||||||
Password = GetFieldValue(passwordField)
|
Password = GetFieldValue(passwordField)
|
||||||
|
@ -173,14 +176,13 @@ namespace Bit.Android.Autofill
|
||||||
|
|
||||||
var usernameField = Fields.TakeWhile(f => f.AutofillId != passwordField.AutofillId).LastOrDefault();
|
var usernameField = Fields.TakeWhile(f => f.AutofillId != passwordField.AutofillId).LastOrDefault();
|
||||||
savedItem.Login.Username = GetFieldValue(usernameField);
|
savedItem.Login.Username = GetFieldValue(usernameField);
|
||||||
|
|
||||||
return savedItem;
|
return savedItem;
|
||||||
}
|
}
|
||||||
else if(SaveType == SaveDataType.CreditCard)
|
else if(SaveType == SaveDataType.CreditCard)
|
||||||
{
|
{
|
||||||
var savedItem = new SavedItem
|
var savedItem = new SavedItem
|
||||||
{
|
{
|
||||||
Type = App.Enums.CipherType.Card,
|
Type = Core.Enums.CipherType.Card,
|
||||||
Card = new SavedItem.CardItem
|
Card = new SavedItem.CardItem
|
||||||
{
|
{
|
||||||
Number = GetFieldValue(View.AutofillHintCreditCardNumber),
|
Number = GetFieldValue(View.AutofillHintCreditCardNumber),
|
||||||
|
@ -190,10 +192,8 @@ namespace Bit.Android.Autofill
|
||||||
Code = GetFieldValue(View.AutofillHintCreditCardSecurityCode)
|
Code = GetFieldValue(View.AutofillHintCreditCardSecurityCode)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return savedItem;
|
return savedItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -224,7 +224,6 @@ namespace Bit.Android.Autofill
|
||||||
}
|
}
|
||||||
return fieldList.Select(f => f.AutofillId).ToArray();
|
return fieldList.Select(f => f.AutofillId).ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
return new AutofillId[0];
|
return new AutofillId[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -238,7 +237,6 @@ namespace Bit.Android.Autofill
|
||||||
{
|
{
|
||||||
return HintToFieldsMap[View.AutofillHintCreditCardNumber].Select(f => f.AutofillId).ToArray();
|
return HintToFieldsMap[View.AutofillHintCreditCardNumber].Select(f => f.AutofillId).ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
return new AutofillId[0];
|
return new AutofillId[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -260,7 +258,6 @@ namespace Bit.Android.Autofill
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -270,7 +267,6 @@ namespace Bit.Android.Autofill
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!string.IsNullOrWhiteSpace(field.TextValue))
|
if(!string.IsNullOrWhiteSpace(field.TextValue))
|
||||||
{
|
{
|
||||||
if(field.AutofillType == AutofillType.List && field.ListValue.HasValue && monthValue)
|
if(field.AutofillType == AutofillType.List && field.ListValue.HasValue && monthValue)
|
||||||
|
@ -294,7 +290,6 @@ namespace Bit.Android.Autofill
|
||||||
{
|
{
|
||||||
return field.ToggleValue.Value.ToString();
|
return field.ToggleValue.Value.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -340,7 +335,6 @@ namespace Bit.Android.Autofill
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var lowerValue = value.ToLowerInvariant();
|
var lowerValue = value.ToLowerInvariant();
|
||||||
return terms.Any(t => lowerValue.Contains(t));
|
return terms.Any(t => lowerValue.Contains(t));
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,107 +1,56 @@
|
||||||
using System;
|
using Android.Service.Autofill;
|
||||||
using Android.Service.Autofill;
|
|
||||||
using Android.Views.Autofill;
|
using Android.Views.Autofill;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Bit.App.Models;
|
using Bit.Core.Enums;
|
||||||
using Bit.App.Enums;
|
|
||||||
using Android.Views;
|
using Android.Views;
|
||||||
|
using Bit.Core.Models.View;
|
||||||
|
|
||||||
namespace Bit.Android.Autofill
|
namespace Bit.Droid.Autofill
|
||||||
{
|
{
|
||||||
public class FilledItem
|
public class FilledItem
|
||||||
{
|
{
|
||||||
private Lazy<string> _password;
|
private string _password;
|
||||||
private Lazy<string> _cardName;
|
private string _cardName;
|
||||||
private string _cardNumber;
|
private string _cardNumber;
|
||||||
private Lazy<string> _cardExpMonth;
|
private string _cardExpMonth;
|
||||||
private Lazy<string> _cardExpYear;
|
private string _cardExpYear;
|
||||||
private Lazy<string> _cardCode;
|
private string _cardCode;
|
||||||
private Lazy<string> _idPhone;
|
private string _idPhone;
|
||||||
private Lazy<string> _idEmail;
|
private string _idEmail;
|
||||||
private Lazy<string> _idUsername;
|
private string _idUsername;
|
||||||
private Lazy<string> _idAddress;
|
private string _idAddress;
|
||||||
private Lazy<string> _idPostalCode;
|
private string _idPostalCode;
|
||||||
|
|
||||||
public FilledItem(Cipher cipher)
|
public FilledItem(CipherView cipher)
|
||||||
{
|
{
|
||||||
Name = cipher.Name?.Decrypt(cipher.OrganizationId) ?? "--";
|
Name = cipher.Name;
|
||||||
Type = cipher.Type;
|
Type = cipher.Type;
|
||||||
|
Subtitle = cipher.SubTitle;
|
||||||
|
|
||||||
switch(Type)
|
switch(Type)
|
||||||
{
|
{
|
||||||
case CipherType.Login:
|
case CipherType.Login:
|
||||||
Subtitle = cipher.Login.Username?.Decrypt(cipher.OrganizationId) ?? string.Empty;
|
|
||||||
Icon = Resource.Drawable.login;
|
Icon = Resource.Drawable.login;
|
||||||
_password = new Lazy<string>(() => cipher.Login.Password?.Decrypt(cipher.OrganizationId));
|
_password = cipher.Login.Password;
|
||||||
break;
|
break;
|
||||||
case CipherType.Card:
|
case CipherType.Card:
|
||||||
Subtitle = cipher.Card.Brand?.Decrypt(cipher.OrganizationId);
|
_cardNumber = cipher.Card.Number;
|
||||||
_cardNumber = cipher.Card.Number?.Decrypt(cipher.OrganizationId);
|
|
||||||
if(!string.IsNullOrWhiteSpace(_cardNumber) && _cardNumber.Length >= 4)
|
|
||||||
{
|
|
||||||
if(!string.IsNullOrWhiteSpace(_cardNumber))
|
|
||||||
{
|
|
||||||
Subtitle += ", ";
|
|
||||||
}
|
|
||||||
Subtitle += ("*" + _cardNumber.Substring(_cardNumber.Length - 4));
|
|
||||||
}
|
|
||||||
Icon = Resource.Drawable.card;
|
Icon = Resource.Drawable.card;
|
||||||
_cardName = new Lazy<string>(() => cipher.Card.CardholderName?.Decrypt(cipher.OrganizationId));
|
_cardName = cipher.Card.CardholderName;
|
||||||
_cardCode = new Lazy<string>(() => cipher.Card.Code?.Decrypt(cipher.OrganizationId));
|
_cardCode = cipher.Card.Code;
|
||||||
_cardExpMonth = new Lazy<string>(() => cipher.Card.ExpMonth?.Decrypt(cipher.OrganizationId));
|
_cardExpMonth = cipher.Card.ExpMonth;
|
||||||
_cardExpYear = new Lazy<string>(() => cipher.Card.ExpYear?.Decrypt(cipher.OrganizationId));
|
_cardExpYear = cipher.Card.ExpYear;
|
||||||
break;
|
break;
|
||||||
case CipherType.Identity:
|
case CipherType.Identity:
|
||||||
var firstName = cipher.Identity?.FirstName?.Decrypt(cipher.OrganizationId) ?? " ";
|
|
||||||
var lastName = cipher.Identity?.LastName?.Decrypt(cipher.OrganizationId) ?? " ";
|
|
||||||
Subtitle = " ";
|
|
||||||
if(!string.IsNullOrWhiteSpace(firstName))
|
|
||||||
{
|
|
||||||
Subtitle = firstName;
|
|
||||||
}
|
|
||||||
if(!string.IsNullOrWhiteSpace(lastName))
|
|
||||||
{
|
|
||||||
if(!string.IsNullOrWhiteSpace(Subtitle))
|
|
||||||
{
|
|
||||||
Subtitle += " ";
|
|
||||||
}
|
|
||||||
Subtitle += lastName;
|
|
||||||
}
|
|
||||||
Icon = Resource.Drawable.id;
|
Icon = Resource.Drawable.id;
|
||||||
_idPhone = new Lazy<string>(() => cipher.Identity.Phone?.Decrypt(cipher.OrganizationId));
|
_idPhone = cipher.Identity.Phone;
|
||||||
_idEmail = new Lazy<string>(() => cipher.Identity.Email?.Decrypt(cipher.OrganizationId));
|
_idEmail = cipher.Identity.Email;
|
||||||
_idUsername = new Lazy<string>(() => cipher.Identity.Username?.Decrypt(cipher.OrganizationId));
|
_idUsername = cipher.Identity.Username;
|
||||||
_idAddress = new Lazy<string>(() =>
|
_idAddress = cipher.Identity.FullAddress;
|
||||||
{
|
_idPostalCode = cipher.Identity.PostalCode;
|
||||||
var address = cipher.Identity.Address1?.Decrypt(cipher.OrganizationId);
|
|
||||||
|
|
||||||
var address2 = cipher.Identity.Address2?.Decrypt(cipher.OrganizationId);
|
|
||||||
if(!string.IsNullOrWhiteSpace(address2))
|
|
||||||
{
|
|
||||||
if(!string.IsNullOrWhiteSpace(address))
|
|
||||||
{
|
|
||||||
address += ", ";
|
|
||||||
}
|
|
||||||
|
|
||||||
address += address2;
|
|
||||||
}
|
|
||||||
|
|
||||||
var address3 = cipher.Identity.Address3?.Decrypt(cipher.OrganizationId);
|
|
||||||
if(!string.IsNullOrWhiteSpace(address3))
|
|
||||||
{
|
|
||||||
if(!string.IsNullOrWhiteSpace(address))
|
|
||||||
{
|
|
||||||
address += ", ";
|
|
||||||
}
|
|
||||||
|
|
||||||
address += address3;
|
|
||||||
}
|
|
||||||
|
|
||||||
return address;
|
|
||||||
});
|
|
||||||
_idPostalCode = new Lazy<string>(() => cipher.Identity.PostalCode?.Decrypt(cipher.OrganizationId));
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
Icon = Resource.Drawable.login;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -121,11 +70,11 @@ namespace Bit.Android.Autofill
|
||||||
var setValues = false;
|
var setValues = false;
|
||||||
if(Type == CipherType.Login)
|
if(Type == CipherType.Login)
|
||||||
{
|
{
|
||||||
if(fieldCollection.PasswordFields.Any() && !string.IsNullOrWhiteSpace(_password.Value))
|
if(fieldCollection.PasswordFields.Any() && !string.IsNullOrWhiteSpace(_password))
|
||||||
{
|
{
|
||||||
foreach(var f in fieldCollection.PasswordFields)
|
foreach(var f in fieldCollection.PasswordFields)
|
||||||
{
|
{
|
||||||
var val = ApplyValue(f, _password.Value);
|
var val = ApplyValue(f, _password);
|
||||||
if(val != null)
|
if(val != null)
|
||||||
{
|
{
|
||||||
setValues = true;
|
setValues = true;
|
||||||
|
@ -133,7 +82,6 @@ namespace Bit.Android.Autofill
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(fieldCollection.UsernameFields.Any() && !string.IsNullOrWhiteSpace(Subtitle))
|
if(fieldCollection.UsernameFields.Any() && !string.IsNullOrWhiteSpace(Subtitle))
|
||||||
{
|
{
|
||||||
foreach(var f in fieldCollection.UsernameFields)
|
foreach(var f in fieldCollection.UsernameFields)
|
||||||
|
@ -149,68 +97,73 @@ namespace Bit.Android.Autofill
|
||||||
}
|
}
|
||||||
else if(Type == CipherType.Card)
|
else if(Type == CipherType.Card)
|
||||||
{
|
{
|
||||||
if(ApplyValue(datasetBuilder, fieldCollection, View.AutofillHintCreditCardNumber,
|
if(ApplyValue(datasetBuilder, fieldCollection, Android.Views.View.AutofillHintCreditCardNumber,
|
||||||
new Lazy<string>(() => _cardNumber)))
|
_cardNumber))
|
||||||
{
|
{
|
||||||
setValues = true;
|
setValues = true;
|
||||||
}
|
}
|
||||||
if(ApplyValue(datasetBuilder, fieldCollection, View.AutofillHintCreditCardSecurityCode, _cardCode))
|
if(ApplyValue(datasetBuilder, fieldCollection, Android.Views.View.AutofillHintCreditCardSecurityCode,
|
||||||
|
_cardCode))
|
||||||
{
|
{
|
||||||
setValues = true;
|
setValues = true;
|
||||||
}
|
}
|
||||||
if(ApplyValue(datasetBuilder, fieldCollection, View.AutofillHintCreditCardExpirationMonth, _cardExpMonth, true))
|
if(ApplyValue(datasetBuilder, fieldCollection,
|
||||||
|
Android.Views.View.AutofillHintCreditCardExpirationMonth, _cardExpMonth, true))
|
||||||
{
|
{
|
||||||
setValues = true;
|
setValues = true;
|
||||||
}
|
}
|
||||||
if(ApplyValue(datasetBuilder, fieldCollection, View.AutofillHintCreditCardExpirationYear, _cardExpYear))
|
if(ApplyValue(datasetBuilder, fieldCollection, Android.Views.View.AutofillHintCreditCardExpirationYear,
|
||||||
|
_cardExpYear))
|
||||||
{
|
{
|
||||||
setValues = true;
|
setValues = true;
|
||||||
}
|
}
|
||||||
if(ApplyValue(datasetBuilder, fieldCollection, View.AutofillHintName, _cardName))
|
if(ApplyValue(datasetBuilder, fieldCollection, Android.Views.View.AutofillHintName, _cardName))
|
||||||
{
|
{
|
||||||
setValues = true;
|
setValues = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if(Type == CipherType.Identity)
|
else if(Type == CipherType.Identity)
|
||||||
{
|
{
|
||||||
if(ApplyValue(datasetBuilder, fieldCollection, View.AutofillHintPhone, _idPhone))
|
if(ApplyValue(datasetBuilder, fieldCollection, Android.Views.View.AutofillHintPhone, _idPhone))
|
||||||
{
|
{
|
||||||
setValues = true;
|
setValues = true;
|
||||||
}
|
}
|
||||||
if(ApplyValue(datasetBuilder, fieldCollection, View.AutofillHintEmailAddress, _idEmail))
|
if(ApplyValue(datasetBuilder, fieldCollection, Android.Views.View.AutofillHintEmailAddress, _idEmail))
|
||||||
{
|
{
|
||||||
setValues = true;
|
setValues = true;
|
||||||
}
|
}
|
||||||
if(ApplyValue(datasetBuilder, fieldCollection, View.AutofillHintUsername, _idUsername))
|
if(ApplyValue(datasetBuilder, fieldCollection, Android.Views.View.AutofillHintUsername,
|
||||||
|
_idUsername))
|
||||||
{
|
{
|
||||||
setValues = true;
|
setValues = true;
|
||||||
}
|
}
|
||||||
if(ApplyValue(datasetBuilder, fieldCollection, View.AutofillHintPostalAddress, _idAddress))
|
if(ApplyValue(datasetBuilder, fieldCollection, Android.Views.View.AutofillHintPostalAddress,
|
||||||
|
_idAddress))
|
||||||
{
|
{
|
||||||
setValues = true;
|
setValues = true;
|
||||||
}
|
}
|
||||||
if(ApplyValue(datasetBuilder, fieldCollection, View.AutofillHintPostalCode, _idPostalCode))
|
if(ApplyValue(datasetBuilder, fieldCollection, Android.Views.View.AutofillHintPostalCode,
|
||||||
|
_idPostalCode))
|
||||||
{
|
{
|
||||||
setValues = true;
|
setValues = true;
|
||||||
}
|
}
|
||||||
if(ApplyValue(datasetBuilder, fieldCollection, View.AutofillHintName, new Lazy<string>(() => Subtitle)))
|
if(ApplyValue(datasetBuilder, fieldCollection, Android.Views.View.AutofillHintName, Subtitle))
|
||||||
{
|
{
|
||||||
setValues = true;
|
setValues = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return setValues;
|
return setValues;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool ApplyValue(Dataset.Builder builder, FieldCollection fieldCollection,
|
private static bool ApplyValue(Dataset.Builder builder, FieldCollection fieldCollection,
|
||||||
string hint, Lazy<string> value, bool monthValue = false)
|
string hint, string value, bool monthValue = false)
|
||||||
{
|
{
|
||||||
bool setValues = false;
|
bool setValues = false;
|
||||||
if(fieldCollection.HintToFieldsMap.ContainsKey(hint) && !string.IsNullOrWhiteSpace(value.Value))
|
if(fieldCollection.HintToFieldsMap.ContainsKey(hint) && !string.IsNullOrWhiteSpace(value))
|
||||||
{
|
{
|
||||||
foreach(var f in fieldCollection.HintToFieldsMap[hint])
|
foreach(var f in fieldCollection.HintToFieldsMap[hint])
|
||||||
{
|
{
|
||||||
var val = ApplyValue(f, value.Value, monthValue);
|
var val = ApplyValue(f, value, monthValue);
|
||||||
if(val != null)
|
if(val != null)
|
||||||
{
|
{
|
||||||
setValues = true;
|
setValues = true;
|
||||||
|
@ -245,7 +198,6 @@ namespace Bit.Android.Autofill
|
||||||
return AutofillValue.ForList(monthIndex - 1);
|
return AutofillValue.ForList(monthIndex - 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for(var i = 0; i < field.AutofillOptions.Count; i++)
|
for(var i = 0; i < field.AutofillOptions.Count; i++)
|
||||||
{
|
{
|
||||||
if(field.AutofillOptions[i].Equals(value))
|
if(field.AutofillOptions[i].Equals(value))
|
||||||
|
@ -266,7 +218,6 @@ namespace Bit.Android.Autofill
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,29 +1,31 @@
|
||||||
using static Android.App.Assist.AssistStructure;
|
using static Android.App.Assist.AssistStructure;
|
||||||
using Android.App.Assist;
|
using Android.App.Assist;
|
||||||
using Bit.App;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using Bit.Core;
|
||||||
|
using Android.Content;
|
||||||
|
|
||||||
namespace Bit.Android.Autofill
|
namespace Bit.Droid.Autofill
|
||||||
{
|
{
|
||||||
public class Parser
|
public class Parser
|
||||||
{
|
{
|
||||||
|
public static HashSet<string> _excludedPackageIds = new HashSet<string>
|
||||||
public static HashSet<string> ExcludedPackageIds = new HashSet<string>
|
|
||||||
{
|
{
|
||||||
"android"
|
"android"
|
||||||
};
|
};
|
||||||
|
|
||||||
private readonly AssistStructure _structure;
|
private readonly AssistStructure _structure;
|
||||||
private string _uri;
|
private string _uri;
|
||||||
private string _packageName;
|
private string _packageName;
|
||||||
private string _webDomain;
|
private string _webDomain;
|
||||||
|
|
||||||
public Parser(AssistStructure structure)
|
public Parser(AssistStructure structure, Context applicationContext)
|
||||||
{
|
{
|
||||||
_structure = structure;
|
_structure = structure;
|
||||||
|
ApplicationContext = applicationContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Context ApplicationContext { get; set; }
|
||||||
public FieldCollection FieldCollection { get; private set; } = new FieldCollection();
|
public FieldCollection FieldCollection { get; private set; } = new FieldCollection();
|
||||||
|
|
||||||
public string Uri
|
public string Uri
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
@ -32,12 +34,12 @@ namespace Bit.Android.Autofill
|
||||||
{
|
{
|
||||||
return _uri;
|
return _uri;
|
||||||
}
|
}
|
||||||
|
var webDomainNull = string.IsNullOrWhiteSpace(WebDomain);
|
||||||
if(string.IsNullOrWhiteSpace(WebDomain) && string.IsNullOrWhiteSpace(PackageName))
|
if(webDomainNull && string.IsNullOrWhiteSpace(PackageName))
|
||||||
{
|
{
|
||||||
_uri = null;
|
_uri = null;
|
||||||
}
|
}
|
||||||
else if(!string.IsNullOrWhiteSpace(WebDomain))
|
else if(!webDomainNull)
|
||||||
{
|
{
|
||||||
_uri = string.Concat("http://", WebDomain);
|
_uri = string.Concat("http://", WebDomain);
|
||||||
}
|
}
|
||||||
|
@ -45,10 +47,10 @@ namespace Bit.Android.Autofill
|
||||||
{
|
{
|
||||||
_uri = string.Concat(Constants.AndroidAppProtocol, PackageName);
|
_uri = string.Concat(Constants.AndroidAppProtocol, PackageName);
|
||||||
}
|
}
|
||||||
|
|
||||||
return _uri;
|
return _uri;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public string PackageName
|
public string PackageName
|
||||||
{
|
{
|
||||||
get => _packageName;
|
get => _packageName;
|
||||||
|
@ -58,10 +60,10 @@ namespace Bit.Android.Autofill
|
||||||
{
|
{
|
||||||
_packageName = _uri = null;
|
_packageName = _uri = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
_packageName = value;
|
_packageName = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public string WebDomain
|
public string WebDomain
|
||||||
{
|
{
|
||||||
get => _webDomain;
|
get => _webDomain;
|
||||||
|
@ -71,19 +73,12 @@ namespace Bit.Android.Autofill
|
||||||
{
|
{
|
||||||
_webDomain = _uri = null;
|
_webDomain = _uri = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
_webDomain = value;
|
_webDomain = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool ShouldAutofill
|
public bool ShouldAutofill => !string.IsNullOrWhiteSpace(Uri) &&
|
||||||
{
|
!AutofillHelpers.BlacklistedUris.Contains(Uri) && FieldCollection != null && FieldCollection.Fillable;
|
||||||
get
|
|
||||||
{
|
|
||||||
return !string.IsNullOrWhiteSpace(Uri) && !AutofillHelpers.BlacklistedUris.Contains(Uri) &&
|
|
||||||
FieldCollection != null && FieldCollection.Fillable;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Parse()
|
public void Parse()
|
||||||
{
|
{
|
||||||
|
@ -92,7 +87,6 @@ namespace Bit.Android.Autofill
|
||||||
var node = _structure.GetWindowNodeAt(i);
|
var node = _structure.GetWindowNodeAt(i);
|
||||||
ParseNode(node.RootViewNode);
|
ParseNode(node.RootViewNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!AutofillHelpers.TrustedBrowsers.Contains(PackageName) &&
|
if(!AutofillHelpers.TrustedBrowsers.Contains(PackageName) &&
|
||||||
!AutofillHelpers.CompatBrowsers.Contains(PackageName))
|
!AutofillHelpers.CompatBrowsers.Contains(PackageName))
|
||||||
{
|
{
|
||||||
|
@ -123,7 +117,7 @@ namespace Bit.Android.Autofill
|
||||||
private void SetPackageAndDomain(ViewNode node)
|
private void SetPackageAndDomain(ViewNode node)
|
||||||
{
|
{
|
||||||
if(string.IsNullOrWhiteSpace(PackageName) && !string.IsNullOrWhiteSpace(node.IdPackage) &&
|
if(string.IsNullOrWhiteSpace(PackageName) && !string.IsNullOrWhiteSpace(node.IdPackage) &&
|
||||||
!ExcludedPackageIds.Contains(node.IdPackage))
|
!_excludedPackageIds.Contains(node.IdPackage))
|
||||||
{
|
{
|
||||||
PackageName = node.IdPackage;
|
PackageName = node.IdPackage;
|
||||||
}
|
}
|
||||||
|
@ -133,4 +127,4 @@ namespace Bit.Android.Autofill
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
using Bit.App.Enums;
|
using Bit.Core.Enums;
|
||||||
|
|
||||||
namespace Bit.Android.Autofill
|
namespace Bit.Droid.Autofill
|
||||||
{
|
{
|
||||||
public class SavedItem
|
public class SavedItem
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,35 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.ComponentModel;
|
|
||||||
using Android.Content;
|
|
||||||
using Bit.Android.Controls;
|
|
||||||
using Xamarin.Forms;
|
|
||||||
using Xamarin.Forms.Platform.Android;
|
|
||||||
|
|
||||||
[assembly: ExportRenderer(typeof(Button), typeof(CustomButtonRenderer))]
|
|
||||||
namespace Bit.Android.Controls
|
|
||||||
{
|
|
||||||
public class CustomButtonRenderer : ButtonRenderer
|
|
||||||
{
|
|
||||||
public CustomButtonRenderer(Context context)
|
|
||||||
: base(context)
|
|
||||||
{ }
|
|
||||||
|
|
||||||
protected override void OnElementChanged(ElementChangedEventArgs<Button> e)
|
|
||||||
{
|
|
||||||
base.OnElementChanged(e);
|
|
||||||
if(Control.TextSize == (float)Device.GetNamedSize(NamedSize.Default, typeof(Button)))
|
|
||||||
{
|
|
||||||
Control.TextSize = (float)Device.GetNamedSize(NamedSize.Medium, typeof(Button));
|
|
||||||
}
|
|
||||||
|
|
||||||
// This will prevent all screen overlay apps from being able to interact with buttons.
|
|
||||||
// Ex: apps that change the screen color for "night mode"
|
|
||||||
// Control.FilterTouchesWhenObscured = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
|
|
||||||
{
|
|
||||||
base.OnElementPropertyChanged(sender, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,21 +0,0 @@
|
||||||
using Android.Content;
|
|
||||||
using Bit.Android.Controls;
|
|
||||||
using Xamarin.Forms;
|
|
||||||
using Xamarin.Forms.Platform.Android;
|
|
||||||
|
|
||||||
[assembly: ExportRenderer(typeof(Label), typeof(CustomLabelRenderer))]
|
|
||||||
namespace Bit.Android.Controls
|
|
||||||
{
|
|
||||||
public class CustomLabelRenderer : LabelRenderer
|
|
||||||
{
|
|
||||||
public CustomLabelRenderer(Context context)
|
|
||||||
: base(context)
|
|
||||||
{ }
|
|
||||||
|
|
||||||
protected override void OnElementChanged(ElementChangedEventArgs<Label> e)
|
|
||||||
{
|
|
||||||
base.OnElementChanged(e);
|
|
||||||
Control.SetMaxLines(int.MaxValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,23 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.ComponentModel;
|
|
||||||
using Android.Content;
|
|
||||||
using Bit.Android.Controls;
|
|
||||||
using Xamarin.Forms;
|
|
||||||
using Xamarin.Forms.Platform.Android;
|
|
||||||
|
|
||||||
[assembly: ExportRenderer(typeof(SearchBar), typeof(CustomSearchBarRenderer))]
|
|
||||||
namespace Bit.Android.Controls
|
|
||||||
{
|
|
||||||
public class CustomSearchBarRenderer : SearchBarRenderer
|
|
||||||
{
|
|
||||||
public CustomSearchBarRenderer(Context context)
|
|
||||||
: base(context)
|
|
||||||
{ }
|
|
||||||
|
|
||||||
protected override void OnElementChanged(ElementChangedEventArgs<SearchBar> e)
|
|
||||||
{
|
|
||||||
base.OnElementChanged(e);
|
|
||||||
Control.SetPadding((int)global::Android.App.Application.Context.ToPixels(-8), 0, 0, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,27 +0,0 @@
|
||||||
using System;
|
|
||||||
using Bit.Android.Controls;
|
|
||||||
using Xamarin.Forms;
|
|
||||||
using Android.Content;
|
|
||||||
using Xamarin.Forms.Platform.Android;
|
|
||||||
using Android.Support.V4.Content.Res;
|
|
||||||
|
|
||||||
[assembly: ExportRenderer(typeof(Slider), typeof(CustomSliderRenderer))]
|
|
||||||
namespace Bit.Android.Controls
|
|
||||||
{
|
|
||||||
public class CustomSliderRenderer : SliderRenderer
|
|
||||||
{
|
|
||||||
public CustomSliderRenderer(Context context)
|
|
||||||
: base(context)
|
|
||||||
{ }
|
|
||||||
|
|
||||||
protected override void OnElementChanged(ElementChangedEventArgs<Slider> e)
|
|
||||||
{
|
|
||||||
base.OnElementChanged(e);
|
|
||||||
if(Control != null)
|
|
||||||
{
|
|
||||||
var thumb = ResourcesCompat.GetDrawable(Resources, Resource.Drawable.slider_thumb, null);
|
|
||||||
Control.SetThumb(thumb);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,67 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.ComponentModel;
|
|
||||||
using Android.Content;
|
|
||||||
using Bit.Android.Controls;
|
|
||||||
using Bit.App.Controls;
|
|
||||||
using Xamarin.Forms;
|
|
||||||
using Xamarin.Forms.Platform.Android;
|
|
||||||
|
|
||||||
[assembly: ExportRenderer(typeof(ExtendedButton), typeof(ExtendedButtonRenderer))]
|
|
||||||
namespace Bit.Android.Controls
|
|
||||||
{
|
|
||||||
public class ExtendedButtonRenderer : CustomButtonRenderer
|
|
||||||
{
|
|
||||||
public ExtendedButtonRenderer(Context context)
|
|
||||||
: base(context)
|
|
||||||
{ }
|
|
||||||
|
|
||||||
protected override void OnElementChanged(ElementChangedEventArgs<Button> e)
|
|
||||||
{
|
|
||||||
base.OnElementChanged(e);
|
|
||||||
SetPadding();
|
|
||||||
SetUppercase();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
|
|
||||||
{
|
|
||||||
base.OnElementPropertyChanged(sender, e);
|
|
||||||
if(e.PropertyName == ExtendedButton.PaddingProperty.PropertyName)
|
|
||||||
{
|
|
||||||
SetPadding();
|
|
||||||
}
|
|
||||||
else if(e.PropertyName == ExtendedButton.UppercaseProperty.PropertyName)
|
|
||||||
{
|
|
||||||
SetUppercase();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void SetPadding()
|
|
||||||
{
|
|
||||||
var element = Element as ExtendedButton;
|
|
||||||
if(element != null)
|
|
||||||
{
|
|
||||||
Control.SetPadding(
|
|
||||||
(int)element.Padding.Left,
|
|
||||||
(int)element.Padding.Top,
|
|
||||||
(int)element.Padding.Right,
|
|
||||||
(int)element.Padding.Bottom);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void SetUppercase()
|
|
||||||
{
|
|
||||||
var element = Element as ExtendedButton;
|
|
||||||
if(element != null && !string.IsNullOrWhiteSpace(element.Text))
|
|
||||||
{
|
|
||||||
if(element.Uppercase)
|
|
||||||
{
|
|
||||||
element.Text = element.Text.ToUpperInvariant();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Control.TransformationMethod = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,86 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.ComponentModel;
|
|
||||||
using Bit.Android.Controls;
|
|
||||||
using Bit.App.Controls;
|
|
||||||
using Xamarin.Forms;
|
|
||||||
using Xamarin.Forms.Platform.Android;
|
|
||||||
using Android.Text.Method;
|
|
||||||
using Android.Views;
|
|
||||||
using Android.Content;
|
|
||||||
|
|
||||||
[assembly: ExportRenderer(typeof(ExtendedEditor), typeof(ExtendedEditorRenderer))]
|
|
||||||
namespace Bit.Android.Controls
|
|
||||||
{
|
|
||||||
public class ExtendedEditorRenderer : EditorRenderer
|
|
||||||
{
|
|
||||||
public ExtendedEditorRenderer(Context context)
|
|
||||||
: base(context)
|
|
||||||
{ }
|
|
||||||
|
|
||||||
protected override void OnElementChanged(ElementChangedEventArgs<Editor> e)
|
|
||||||
{
|
|
||||||
base.OnElementChanged(e);
|
|
||||||
|
|
||||||
var view = (ExtendedEditor)Element;
|
|
||||||
|
|
||||||
SetBorder(view);
|
|
||||||
SetScrollable();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
|
|
||||||
{
|
|
||||||
var view = (ExtendedEditor)Element;
|
|
||||||
|
|
||||||
if(e.PropertyName == ExtendedEditor.HasBorderProperty.PropertyName)
|
|
||||||
{
|
|
||||||
SetBorder(view);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
base.OnElementPropertyChanged(sender, e);
|
|
||||||
if(e.PropertyName == VisualElement.BackgroundColorProperty.PropertyName)
|
|
||||||
{
|
|
||||||
Control.SetBackgroundColor(view.BackgroundColor.ToAndroid());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void SetBorder(ExtendedEditor view)
|
|
||||||
{
|
|
||||||
if(!view.HasBorder)
|
|
||||||
{
|
|
||||||
Control.SetBackgroundColor(global::Android.Graphics.Color.Transparent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void SetScrollable()
|
|
||||||
{
|
|
||||||
// While scrolling inside Editor stop scrolling parent view.
|
|
||||||
Control.OverScrollMode = OverScrollMode.Always;
|
|
||||||
Control.ScrollBarStyle = ScrollbarStyles.InsideInset;
|
|
||||||
Control.SetOnTouchListener(new EditorTouchListener());
|
|
||||||
|
|
||||||
// For Scrolling in Editor innner area
|
|
||||||
Control.VerticalScrollBarEnabled = true;
|
|
||||||
Control.ScrollBarStyle = ScrollbarStyles.InsideInset;
|
|
||||||
|
|
||||||
// Force scrollbars to be displayed
|
|
||||||
var arr = Control.Context.Theme.ObtainStyledAttributes(new int[0]);
|
|
||||||
InitializeScrollbars(arr);
|
|
||||||
arr.Recycle();
|
|
||||||
}
|
|
||||||
|
|
||||||
public class EditorTouchListener : Java.Lang.Object, IOnTouchListener
|
|
||||||
{
|
|
||||||
public bool OnTouch(global::Android.Views.View v, MotionEvent e)
|
|
||||||
{
|
|
||||||
v.Parent?.RequestDisallowInterceptTouchEvent(true);
|
|
||||||
if((e.Action & MotionEventActions.Up) != 0 && (e.ActionMasked & MotionEventActions.Up) != 0)
|
|
||||||
{
|
|
||||||
v.Parent?.RequestDisallowInterceptTouchEvent(false);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,217 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.ComponentModel;
|
|
||||||
using Android.Content;
|
|
||||||
using Android.Graphics;
|
|
||||||
using Android.Text;
|
|
||||||
using Android.Text.Method;
|
|
||||||
using Android.Views.InputMethods;
|
|
||||||
using Android.Widget;
|
|
||||||
using Bit.Android.Controls;
|
|
||||||
using Bit.App.Controls;
|
|
||||||
using Bit.App.Enums;
|
|
||||||
using Plugin.CurrentActivity;
|
|
||||||
using Xamarin.Forms;
|
|
||||||
using Xamarin.Forms.Platform.Android;
|
|
||||||
|
|
||||||
[assembly: ExportRenderer(typeof(ExtendedEntry), typeof(ExtendedEntryRenderer))]
|
|
||||||
namespace Bit.Android.Controls
|
|
||||||
{
|
|
||||||
public class ExtendedEntryRenderer : EntryRenderer
|
|
||||||
{
|
|
||||||
public ExtendedEntryRenderer(Context context)
|
|
||||||
: base(context)
|
|
||||||
{ }
|
|
||||||
|
|
||||||
private bool _isPassword;
|
|
||||||
private bool _toggledPassword;
|
|
||||||
private bool _isDisposed;
|
|
||||||
private ExtendedEntry _view;
|
|
||||||
|
|
||||||
protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
|
|
||||||
{
|
|
||||||
base.OnElementChanged(e);
|
|
||||||
|
|
||||||
_view = (ExtendedEntry)Element;
|
|
||||||
_isPassword = _view.IsPassword;
|
|
||||||
|
|
||||||
if(Control != null)
|
|
||||||
{
|
|
||||||
Control.SetIncludeFontPadding(false);
|
|
||||||
if(e.NewElement != null && e.NewElement.IsPassword)
|
|
||||||
{
|
|
||||||
Control.SetTypeface(Typeface.Default, TypefaceStyle.Normal);
|
|
||||||
Control.TransformationMethod = new PasswordTransformationMethod();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SetBorder(_view);
|
|
||||||
SetMaxLength(_view);
|
|
||||||
SetReturnType(_view);
|
|
||||||
|
|
||||||
// Editor Action is called when the return button is pressed
|
|
||||||
Control.EditorAction += Control_EditorAction;
|
|
||||||
|
|
||||||
if(_view.DisableAutocapitalize)
|
|
||||||
{
|
|
||||||
Control.SetRawInputType(Control.InputType |= InputTypes.TextVariationEmailAddress);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(_view.Autocorrect.HasValue)
|
|
||||||
{
|
|
||||||
Control.SetRawInputType(Control.InputType |= InputTypes.TextFlagNoSuggestions);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(_view.IsPassword)
|
|
||||||
{
|
|
||||||
Control.SetRawInputType(InputTypes.TextFlagNoSuggestions | InputTypes.TextVariationVisiblePassword);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(_view.NumbersOnly)
|
|
||||||
{
|
|
||||||
Control.SetRawInputType(InputTypes.ClassNumber | InputTypes.NumberVariationPassword);
|
|
||||||
}
|
|
||||||
|
|
||||||
_view.ToggleIsPassword += ToggleIsPassword;
|
|
||||||
|
|
||||||
if(_view.FontFamily == "monospace")
|
|
||||||
{
|
|
||||||
Control.Typeface = Typeface.Monospace;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(_view.HideCursor)
|
|
||||||
{
|
|
||||||
Control.SetCursorVisible(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ToggleIsPassword(object sender, EventArgs e)
|
|
||||||
{
|
|
||||||
var cursorStart = Control.SelectionStart;
|
|
||||||
var cursorEnd = Control.SelectionEnd;
|
|
||||||
|
|
||||||
Control.TransformationMethod = _isPassword ? null : new PasswordTransformationMethod();
|
|
||||||
Control.SetRawInputType(InputTypes.TextFlagNoSuggestions | InputTypes.TextVariationVisiblePassword);
|
|
||||||
|
|
||||||
// set focus
|
|
||||||
Control.RequestFocus();
|
|
||||||
|
|
||||||
if(_toggledPassword)
|
|
||||||
{
|
|
||||||
// restore cursor position
|
|
||||||
Control.SetSelection(cursorStart, cursorEnd);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// set cursor to end
|
|
||||||
Control.SetSelection(Control.Text.Length);
|
|
||||||
}
|
|
||||||
|
|
||||||
// show keyboard
|
|
||||||
var imm = CrossCurrentActivity.Current.Activity.GetSystemService(Context.InputMethodService) as InputMethodManager;
|
|
||||||
imm.ShowSoftInput(Control, ShowFlags.Forced);
|
|
||||||
imm.ToggleSoftInput(ShowFlags.Forced, HideSoftInputFlags.ImplicitOnly);
|
|
||||||
|
|
||||||
_isPassword = _view.IsPasswordFromToggled = !_isPassword;
|
|
||||||
_toggledPassword = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Control_EditorAction(object sender, TextView.EditorActionEventArgs e)
|
|
||||||
{
|
|
||||||
if(_view.TargetReturnType != Bit.App.Enums.ReturnType.Next)
|
|
||||||
{
|
|
||||||
_view.Unfocus();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Call all the methods attached to base_entry event handler Completed
|
|
||||||
_view.InvokeCompleted();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
|
|
||||||
{
|
|
||||||
var view = (ExtendedEntry)Element;
|
|
||||||
|
|
||||||
if(e.PropertyName == ExtendedEntry.HasBorderProperty.PropertyName
|
|
||||||
|| e.PropertyName == ExtendedEntry.HasOnlyBottomBorderProperty.PropertyName
|
|
||||||
|| e.PropertyName == ExtendedEntry.BottomBorderColorProperty.PropertyName)
|
|
||||||
{
|
|
||||||
SetBorder(view);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
base.OnElementPropertyChanged(sender, e);
|
|
||||||
if(e.PropertyName == VisualElement.BackgroundColorProperty.PropertyName)
|
|
||||||
{
|
|
||||||
Control.SetBackgroundColor(view.BackgroundColor.ToAndroid());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(view.FontFamily == "monospace")
|
|
||||||
{
|
|
||||||
Control.Typeface = Typeface.Monospace;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void Dispose(bool disposing)
|
|
||||||
{
|
|
||||||
if(_isDisposed)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_isDisposed = true;
|
|
||||||
if(disposing && Control != null)
|
|
||||||
{
|
|
||||||
_view.ToggleIsPassword -= ToggleIsPassword;
|
|
||||||
Control.EditorAction -= Control_EditorAction;
|
|
||||||
}
|
|
||||||
|
|
||||||
base.Dispose(disposing);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void SetReturnType(ExtendedEntry view)
|
|
||||||
{
|
|
||||||
if(view.TargetReturnType.HasValue)
|
|
||||||
{
|
|
||||||
switch(view.TargetReturnType.Value)
|
|
||||||
{
|
|
||||||
case App.Enums.ReturnType.Go:
|
|
||||||
Control.ImeOptions = ImeAction.Go;
|
|
||||||
Control.SetImeActionLabel("Go", ImeAction.Go);
|
|
||||||
break;
|
|
||||||
case App.Enums.ReturnType.Next:
|
|
||||||
Control.ImeOptions = ImeAction.Next;
|
|
||||||
Control.SetImeActionLabel("Next", ImeAction.Next);
|
|
||||||
break;
|
|
||||||
case App.Enums.ReturnType.Search:
|
|
||||||
Control.ImeOptions = ImeAction.Search;
|
|
||||||
Control.SetImeActionLabel("Search", ImeAction.Search);
|
|
||||||
break;
|
|
||||||
case App.Enums.ReturnType.Send:
|
|
||||||
Control.ImeOptions = ImeAction.Send;
|
|
||||||
Control.SetImeActionLabel("Send", ImeAction.Send);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
Control.SetImeActionLabel("Done", ImeAction.Done);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void SetBorder(ExtendedEntry view)
|
|
||||||
{
|
|
||||||
if(!view.HasBorder)
|
|
||||||
{
|
|
||||||
Control.SetBackgroundColor(global::Android.Graphics.Color.Transparent);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Control.SetBackgroundColor(view.BottomBorderColor.ToAndroid());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void SetMaxLength(ExtendedEntry view)
|
|
||||||
{
|
|
||||||
Control.SetFilters(new IInputFilter[] { new InputFilterLengthFilter(view.TargetMaxLength) });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,54 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.ComponentModel;
|
|
||||||
using Android.Content;
|
|
||||||
using Bit.Android.Controls;
|
|
||||||
using Bit.App.Controls;
|
|
||||||
using Xamarin.Forms;
|
|
||||||
using Xamarin.Forms.Platform.Android;
|
|
||||||
|
|
||||||
[assembly: ExportRenderer(typeof(ExtendedPicker), typeof(ExtendedPickerRenderer))]
|
|
||||||
namespace Bit.Android.Controls
|
|
||||||
{
|
|
||||||
public class ExtendedPickerRenderer : PickerRenderer
|
|
||||||
{
|
|
||||||
public ExtendedPickerRenderer(Context context)
|
|
||||||
: base(context)
|
|
||||||
{ }
|
|
||||||
|
|
||||||
protected override void OnElementChanged(ElementChangedEventArgs<Picker> e)
|
|
||||||
{
|
|
||||||
base.OnElementChanged(e);
|
|
||||||
|
|
||||||
var view = (ExtendedPicker)Element;
|
|
||||||
|
|
||||||
Control.TextSize = (float)Device.GetNamedSize(NamedSize.Medium, typeof(Picker));
|
|
||||||
SetBorder(view);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
|
|
||||||
{
|
|
||||||
var view = (ExtendedPicker)Element;
|
|
||||||
|
|
||||||
if(e.PropertyName == ExtendedPicker.HasBorderProperty.PropertyName)
|
|
||||||
{
|
|
||||||
SetBorder(view);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
base.OnElementPropertyChanged(sender, e);
|
|
||||||
if(e.PropertyName == VisualElement.BackgroundColorProperty.PropertyName)
|
|
||||||
{
|
|
||||||
Control.SetBackgroundColor(view.BackgroundColor.ToAndroid());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void SetBorder(ExtendedPicker view)
|
|
||||||
{
|
|
||||||
if(!view.HasBorder)
|
|
||||||
{
|
|
||||||
Control.SetBackgroundColor(global::Android.Graphics.Color.Transparent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,72 +0,0 @@
|
||||||
using Android.Content;
|
|
||||||
using System.ComponentModel;
|
|
||||||
using Android.Views;
|
|
||||||
using Bit.Android.Controls;
|
|
||||||
using Bit.App.Controls;
|
|
||||||
using Xamarin.Forms;
|
|
||||||
using Xamarin.Forms.Platform.Android;
|
|
||||||
using AView = Android.Views.View;
|
|
||||||
using Android.Widget;
|
|
||||||
|
|
||||||
[assembly: ExportRenderer(typeof(ExtendedSwitchCell), typeof(ExtendedSwitchCellRenderer))]
|
|
||||||
namespace Bit.Android.Controls
|
|
||||||
{
|
|
||||||
public class ExtendedSwitchCellRenderer : SwitchCellRenderer
|
|
||||||
{
|
|
||||||
protected BaseCellView View { get; private set; }
|
|
||||||
|
|
||||||
protected override AView GetCellCore(Cell item, AView convertView, ViewGroup parent, Context context)
|
|
||||||
{
|
|
||||||
var View = base.GetCellCore(item, convertView, parent, context) as SwitchCellView;
|
|
||||||
var extendedCell = (ExtendedSwitchCell)item;
|
|
||||||
|
|
||||||
if(View != null)
|
|
||||||
{
|
|
||||||
if(extendedCell.BackgroundColor != Color.White)
|
|
||||||
{
|
|
||||||
View.SetBackgroundColor(extendedCell.BackgroundColor.ToAndroid());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
View.SetBackgroundResource(Resource.Drawable.list_selector);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(item.IsEnabled)
|
|
||||||
{
|
|
||||||
View.SetMainTextColor(Color.Black);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
View.SetMainTextColor(Color.FromHex("777777"));
|
|
||||||
}
|
|
||||||
|
|
||||||
if(View.ChildCount > 1)
|
|
||||||
{
|
|
||||||
var layout = View.GetChildAt(1) as LinearLayout;
|
|
||||||
if(layout != null && layout.ChildCount > 0)
|
|
||||||
{
|
|
||||||
var textView = layout.GetChildAt(0) as TextView;
|
|
||||||
if(textView != null)
|
|
||||||
{
|
|
||||||
textView.TextSize = (float)Device.GetNamedSize(NamedSize.Medium, typeof(Label));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return View;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnCellPropertyChanged(object sender, PropertyChangedEventArgs args)
|
|
||||||
{
|
|
||||||
base.OnCellPropertyChanged(sender, args);
|
|
||||||
|
|
||||||
var cell = (ExtendedSwitchCell)Cell;
|
|
||||||
|
|
||||||
if(args.PropertyName == ExtendedSwitchCell.BackgroundColorProperty.PropertyName)
|
|
||||||
{
|
|
||||||
View.SetBackgroundColor(cell.BackgroundColor.ToAndroid());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,441 +0,0 @@
|
||||||
using System;
|
|
||||||
using Bit.Android.Controls;
|
|
||||||
using Bit.App.Controls;
|
|
||||||
using Xamarin.Forms;
|
|
||||||
using Com.Ittianyu.Bottomnavigationviewex;
|
|
||||||
using Xamarin.Forms.Platform.Android;
|
|
||||||
using RelativeLayout = Android.Widget.RelativeLayout;
|
|
||||||
using Platform = Xamarin.Forms.Platform.Android.Platform;
|
|
||||||
using Android.Content;
|
|
||||||
using Android.Views;
|
|
||||||
using Android.Widget;
|
|
||||||
using Android.Support.Design.Internal;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.ComponentModel;
|
|
||||||
using Android.Support.Design.Widget;
|
|
||||||
|
|
||||||
[assembly: ExportRenderer(typeof(ExtendedTabbedPage), typeof(ExtendedTabbedPageRenderer))]
|
|
||||||
namespace Bit.Android.Controls
|
|
||||||
{
|
|
||||||
public class ExtendedTabbedPageRenderer : VisualElementRenderer<ExtendedTabbedPage>,
|
|
||||||
BottomNavigationView.IOnNavigationItemSelectedListener
|
|
||||||
{
|
|
||||||
public static bool ShouldUpdateSelectedIcon;
|
|
||||||
public static Action<IMenuItem, FileImageSource, bool> MenuItemIconSetter;
|
|
||||||
public static float? BottomBarHeight = 50;
|
|
||||||
|
|
||||||
private RelativeLayout _rootLayout;
|
|
||||||
private FrameLayout _pageContainer;
|
|
||||||
private BottomNavigationViewEx _bottomNav;
|
|
||||||
private readonly int _barId;
|
|
||||||
|
|
||||||
public static global::Android.Graphics.Color? BackgroundColor;
|
|
||||||
|
|
||||||
public ExtendedTabbedPageRenderer(Context context)
|
|
||||||
: base(context)
|
|
||||||
{
|
|
||||||
AutoPackage = false;
|
|
||||||
_barId = GenerateViewId();
|
|
||||||
}
|
|
||||||
|
|
||||||
IPageController TabbedController => Element as IPageController;
|
|
||||||
public int LastSelectedIndex { get; internal set; }
|
|
||||||
|
|
||||||
public bool OnNavigationItemSelected(IMenuItem item)
|
|
||||||
{
|
|
||||||
this.SwitchPage(item);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal void SetupTabItems()
|
|
||||||
{
|
|
||||||
this.SetupTabItems(_bottomNav);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal void SetupBottomBar()
|
|
||||||
{
|
|
||||||
_bottomNav = this.SetupBottomBar(_rootLayout, _bottomNav, _barId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static readonly Action<IMenuItem, FileImageSource, bool> DefaultMenuItemIconSetter = (menuItem, icon, selected) =>
|
|
||||||
{
|
|
||||||
var tabIconId = ResourceUtils.IdFromTitle(icon, ResourceManager.DrawableClass);
|
|
||||||
menuItem.SetIcon(tabIconId);
|
|
||||||
};
|
|
||||||
|
|
||||||
protected override void OnElementChanged(ElementChangedEventArgs<ExtendedTabbedPage> e)
|
|
||||||
{
|
|
||||||
base.OnElementChanged(e);
|
|
||||||
|
|
||||||
if(e.OldElement != null)
|
|
||||||
{
|
|
||||||
e.OldElement.ChildAdded -= PagesChanged;
|
|
||||||
e.OldElement.ChildRemoved -= PagesChanged;
|
|
||||||
e.OldElement.ChildrenReordered -= PagesChanged;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(e.NewElement == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
UpdateIgnoreContainerAreas();
|
|
||||||
|
|
||||||
if(_rootLayout == null)
|
|
||||||
{
|
|
||||||
SetupNativeView();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.HandlePagesChanged();
|
|
||||||
SwitchContent(Element.CurrentPage);
|
|
||||||
|
|
||||||
Element.ChildAdded += PagesChanged;
|
|
||||||
Element.ChildRemoved += PagesChanged;
|
|
||||||
Element.ChildrenReordered += PagesChanged;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
|
|
||||||
{
|
|
||||||
base.OnElementPropertyChanged(sender, e);
|
|
||||||
if(e.PropertyName == nameof(TabbedPage.CurrentPage))
|
|
||||||
{
|
|
||||||
SwitchContent(Element.CurrentPage);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void PagesChanged(object sender, EventArgs e)
|
|
||||||
{
|
|
||||||
this.HandlePagesChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnAttachedToWindow()
|
|
||||||
{
|
|
||||||
base.OnAttachedToWindow();
|
|
||||||
TabbedController?.SendAppearing();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnDetachedFromWindow()
|
|
||||||
{
|
|
||||||
base.OnDetachedFromWindow();
|
|
||||||
TabbedController?.SendDisappearing();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnLayout(bool changed, int left, int top, int right, int bottom)
|
|
||||||
{
|
|
||||||
var width = right - left;
|
|
||||||
var height = bottom - top;
|
|
||||||
|
|
||||||
base.OnLayout(changed, left, top, right, bottom);
|
|
||||||
if(width <= 0 || height <= 0)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.Layout(width, height);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void Dispose(bool disposing)
|
|
||||||
{
|
|
||||||
if(disposing)
|
|
||||||
{
|
|
||||||
Element.ChildAdded -= PagesChanged;
|
|
||||||
Element.ChildRemoved -= PagesChanged;
|
|
||||||
Element.ChildrenReordered -= PagesChanged;
|
|
||||||
|
|
||||||
if(_rootLayout != null)
|
|
||||||
{
|
|
||||||
RemoveAllViews();
|
|
||||||
foreach(Page pageToRemove in Element.Children)
|
|
||||||
{
|
|
||||||
var pageRenderer = Platform.GetRenderer(pageToRemove);
|
|
||||||
if(pageRenderer != null)
|
|
||||||
{
|
|
||||||
pageRenderer.View.RemoveFromParent();
|
|
||||||
pageRenderer.Dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(_bottomNav != null)
|
|
||||||
{
|
|
||||||
_bottomNav.SetOnNavigationItemSelectedListener(null);
|
|
||||||
_bottomNav.Dispose();
|
|
||||||
_bottomNav = null;
|
|
||||||
}
|
|
||||||
_rootLayout.Dispose();
|
|
||||||
_rootLayout = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
base.Dispose(disposing);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal void SetupNativeView()
|
|
||||||
{
|
|
||||||
_rootLayout = this.CreateRoot(_barId, GenerateViewId(), out _pageContainer);
|
|
||||||
AddView(_rootLayout);
|
|
||||||
}
|
|
||||||
|
|
||||||
void SwitchContent(Page page)
|
|
||||||
{
|
|
||||||
this.ChangePage(_pageContainer, page);
|
|
||||||
}
|
|
||||||
|
|
||||||
void UpdateIgnoreContainerAreas()
|
|
||||||
{
|
|
||||||
foreach(var child in Element.Children)
|
|
||||||
{
|
|
||||||
child.IgnoresContainerArea = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static class TabExtensions
|
|
||||||
{
|
|
||||||
public static Rectangle CreateRect(this Context context, int width, int height)
|
|
||||||
{
|
|
||||||
return new Rectangle(0, 0, context.FromPixels(width), context.FromPixels(height));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void HandlePagesChanged(this ExtendedTabbedPageRenderer renderer)
|
|
||||||
{
|
|
||||||
renderer.SetupBottomBar();
|
|
||||||
renderer.SetupTabItems();
|
|
||||||
|
|
||||||
if(renderer.Element.Children.Count == 0)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
EnsureTabIndex(renderer);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void EnsureTabIndex(ExtendedTabbedPageRenderer renderer)
|
|
||||||
{
|
|
||||||
var rootLayout = (RelativeLayout)renderer.GetChildAt(0);
|
|
||||||
var bottomNav = (BottomNavigationViewEx)rootLayout.GetChildAt(1);
|
|
||||||
var menu = (BottomNavigationMenu)bottomNav.Menu;
|
|
||||||
|
|
||||||
var itemIndex = menu.FindItemIndex(bottomNav.SelectedItemId);
|
|
||||||
var pageIndex = renderer.Element.Children.IndexOf(renderer.Element.CurrentPage);
|
|
||||||
if(pageIndex >= 0 && pageIndex != itemIndex && pageIndex < bottomNav.ItemCount)
|
|
||||||
{
|
|
||||||
var menuItem = menu.GetItem(pageIndex);
|
|
||||||
bottomNav.SelectedItemId = menuItem.ItemId;
|
|
||||||
|
|
||||||
if(ExtendedTabbedPageRenderer.ShouldUpdateSelectedIcon && ExtendedTabbedPageRenderer.MenuItemIconSetter != null)
|
|
||||||
{
|
|
||||||
ExtendedTabbedPageRenderer.MenuItemIconSetter?.Invoke(menuItem, renderer.Element.CurrentPage.Icon, true);
|
|
||||||
|
|
||||||
if(renderer.LastSelectedIndex != pageIndex)
|
|
||||||
{
|
|
||||||
var lastSelectedPage = renderer.Element.Children[renderer.LastSelectedIndex];
|
|
||||||
var lastSelectedMenuItem = menu.GetItem(renderer.LastSelectedIndex);
|
|
||||||
ExtendedTabbedPageRenderer.MenuItemIconSetter?.Invoke(lastSelectedMenuItem, lastSelectedPage.Icon, false);
|
|
||||||
renderer.LastSelectedIndex = pageIndex;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void SwitchPage(this ExtendedTabbedPageRenderer renderer, IMenuItem item)
|
|
||||||
{
|
|
||||||
var rootLayout = (RelativeLayout)renderer.GetChildAt(0);
|
|
||||||
var bottomNav = (BottomNavigationViewEx)rootLayout.GetChildAt(1);
|
|
||||||
var menu = (BottomNavigationMenu)bottomNav.Menu;
|
|
||||||
|
|
||||||
var index = menu.FindItemIndex(item.ItemId);
|
|
||||||
var pageIndex = index % renderer.Element.Children.Count;
|
|
||||||
var currentPageIndex = renderer.Element.Children.IndexOf(renderer.Element.CurrentPage);
|
|
||||||
|
|
||||||
if(currentPageIndex != pageIndex)
|
|
||||||
{
|
|
||||||
renderer.Element.CurrentPage = renderer.Element.Children[pageIndex];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void Layout(this ExtendedTabbedPageRenderer renderer, int width, int height)
|
|
||||||
{
|
|
||||||
var rootLayout = (RelativeLayout)renderer.GetChildAt(0);
|
|
||||||
var bottomNav = (BottomNavigationViewEx)rootLayout.GetChildAt(1);
|
|
||||||
|
|
||||||
var Context = renderer.Context;
|
|
||||||
|
|
||||||
rootLayout.Measure(MakeMeasureSpec(width, MeasureSpecMode.Exactly),
|
|
||||||
MakeMeasureSpec(height, MeasureSpecMode.AtMost));
|
|
||||||
|
|
||||||
((IPageController)renderer.Element).ContainerArea =
|
|
||||||
Context.CreateRect(rootLayout.MeasuredWidth, rootLayout.GetChildAt(0).MeasuredHeight);
|
|
||||||
|
|
||||||
rootLayout.Measure(MakeMeasureSpec(width, MeasureSpecMode.Exactly),
|
|
||||||
MakeMeasureSpec(height, MeasureSpecMode.Exactly));
|
|
||||||
rootLayout.Layout(0, 0, rootLayout.MeasuredWidth, rootLayout.MeasuredHeight);
|
|
||||||
|
|
||||||
if(renderer.Element.Children.Count == 0)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int tabsHeight = bottomNav.MeasuredHeight;
|
|
||||||
|
|
||||||
var item = (ViewGroup)bottomNav.GetChildAt(0);
|
|
||||||
item.Measure(MakeMeasureSpec(width, MeasureSpecMode.Exactly),
|
|
||||||
MakeMeasureSpec(tabsHeight, MeasureSpecMode.Exactly));
|
|
||||||
|
|
||||||
item.Layout(0, 0, width, tabsHeight);
|
|
||||||
int item_w = width / item.ChildCount;
|
|
||||||
for(int i = 0; i < item.ChildCount; i++)
|
|
||||||
{
|
|
||||||
var frame = (FrameLayout)item.GetChildAt(i);
|
|
||||||
frame.Measure(MakeMeasureSpec(item_w, MeasureSpecMode.Exactly),
|
|
||||||
MakeMeasureSpec(tabsHeight, MeasureSpecMode.Exactly));
|
|
||||||
frame.Layout(i * item_w, 0, i * item_w + item_w, tabsHeight);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void SetupTabItems(this ExtendedTabbedPageRenderer renderer, BottomNavigationViewEx bottomNav)
|
|
||||||
{
|
|
||||||
var element = renderer.Element;
|
|
||||||
var menu = (BottomNavigationMenu)bottomNav.Menu;
|
|
||||||
menu.ClearAll();
|
|
||||||
|
|
||||||
var tabsCount = Math.Min(element.Children.Count, bottomNav.MaxItemCount);
|
|
||||||
for(int i = 0; i < tabsCount; i++)
|
|
||||||
{
|
|
||||||
var page = element.Children[i];
|
|
||||||
var menuItem = menu.Add(0, i, 0, page.Title);
|
|
||||||
var setter = ExtendedTabbedPageRenderer.MenuItemIconSetter ?? ExtendedTabbedPageRenderer.DefaultMenuItemIconSetter;
|
|
||||||
setter.Invoke(menuItem, page.Icon, renderer.LastSelectedIndex == i);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(element.Children.Count > 0)
|
|
||||||
{
|
|
||||||
bottomNav.EnableShiftingMode(false);
|
|
||||||
bottomNav.EnableItemShiftingMode(false);
|
|
||||||
bottomNav.EnableAnimation(false);
|
|
||||||
bottomNav.SetTextVisibility(false);
|
|
||||||
bottomNav.SetBackgroundResource(Resource.Drawable.bottom_nav_bg);
|
|
||||||
bottomNav.SetIconSize(24, 24);
|
|
||||||
bottomNav.SetIconsMarginTop(32);
|
|
||||||
|
|
||||||
if(element.Children.Count > 3)
|
|
||||||
{
|
|
||||||
bottomNav.SetIconSizeAt(3, 29, 29);
|
|
||||||
bottomNav.SetIconMarginTop(3, 28);
|
|
||||||
}
|
|
||||||
|
|
||||||
var stateList = new global::Android.Content.Res.ColorStateList(
|
|
||||||
new int[][] {
|
|
||||||
new int[] { global::Android.Resource.Attribute.StateChecked },
|
|
||||||
new int[] { global::Android.Resource.Attribute.StateEnabled}
|
|
||||||
},
|
|
||||||
new int[] {
|
|
||||||
element.TintColor.ToAndroid(), // Selected
|
|
||||||
Color.FromHex("A1A1A1").ToAndroid() // Normal
|
|
||||||
});
|
|
||||||
|
|
||||||
bottomNav.ItemIconTintList = stateList;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static BottomNavigationViewEx SetupBottomBar(this ExtendedTabbedPageRenderer renderer,
|
|
||||||
global::Android.Widget.RelativeLayout rootLayout, BottomNavigationViewEx bottomNav, int barId)
|
|
||||||
{
|
|
||||||
if(bottomNav != null)
|
|
||||||
{
|
|
||||||
rootLayout.RemoveView(bottomNav);
|
|
||||||
bottomNav.SetOnNavigationItemSelectedListener(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
var barParams = new global::Android.Widget.RelativeLayout.LayoutParams(
|
|
||||||
ViewGroup.LayoutParams.MatchParent,
|
|
||||||
ExtendedTabbedPageRenderer.BottomBarHeight.HasValue ?
|
|
||||||
(int)rootLayout.Context.ToPixels(ExtendedTabbedPageRenderer.BottomBarHeight.Value) :
|
|
||||||
ViewGroup.LayoutParams.WrapContent);
|
|
||||||
barParams.AddRule(LayoutRules.AlignParentBottom);
|
|
||||||
bottomNav = new BottomNavigationViewEx(rootLayout.Context)
|
|
||||||
{
|
|
||||||
LayoutParameters = barParams,
|
|
||||||
Id = barId
|
|
||||||
};
|
|
||||||
if(ExtendedTabbedPageRenderer.BackgroundColor.HasValue)
|
|
||||||
{
|
|
||||||
bottomNav.SetBackgroundColor(ExtendedTabbedPageRenderer.BackgroundColor.Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
bottomNav.SetOnNavigationItemSelectedListener(renderer);
|
|
||||||
rootLayout.AddView(bottomNav, 1, barParams);
|
|
||||||
|
|
||||||
return bottomNav;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void ChangePage(this ExtendedTabbedPageRenderer renderer, FrameLayout pageContainer, Page page)
|
|
||||||
{
|
|
||||||
renderer.Context.HideKeyboard(renderer);
|
|
||||||
if(page == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(Platform.GetRenderer(page) == null)
|
|
||||||
{
|
|
||||||
Platform.SetRenderer(page, Platform.CreateRendererWithContext(page, renderer.Context));
|
|
||||||
}
|
|
||||||
|
|
||||||
var pageContent = Platform.GetRenderer(page).View;
|
|
||||||
pageContainer.AddView(pageContent);
|
|
||||||
if(pageContainer.ChildCount > 1)
|
|
||||||
{
|
|
||||||
pageContainer.RemoveViewAt(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
EnsureTabIndex(renderer);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static RelativeLayout CreateRoot(this ExtendedTabbedPageRenderer renderer, int barId, int pageContainerId, out FrameLayout pageContainer)
|
|
||||||
{
|
|
||||||
var rootLayout = new RelativeLayout(renderer.Context)
|
|
||||||
{
|
|
||||||
LayoutParameters = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MatchParent, ViewGroup.LayoutParams.MatchParent),
|
|
||||||
};
|
|
||||||
|
|
||||||
var pageParams = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MatchParent, ViewGroup.LayoutParams.MatchParent);
|
|
||||||
pageParams.AddRule(LayoutRules.Above, barId);
|
|
||||||
pageContainer = new FrameLayout(renderer.Context)
|
|
||||||
{
|
|
||||||
LayoutParameters = pageParams,
|
|
||||||
Id = pageContainerId
|
|
||||||
};
|
|
||||||
|
|
||||||
rootLayout.AddView(pageContainer, 0, pageParams);
|
|
||||||
return rootLayout;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int MakeMeasureSpec(int size, MeasureSpecMode mode)
|
|
||||||
{
|
|
||||||
return size + (int)mode;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class ResourceUtils
|
|
||||||
{
|
|
||||||
public static int IdFromTitle(string title, Type type)
|
|
||||||
{
|
|
||||||
var name = Path.GetFileNameWithoutExtension(title);
|
|
||||||
var id = GetId(type, name);
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int GetId(Type type, string propertyName)
|
|
||||||
{
|
|
||||||
var props = type.GetFields();
|
|
||||||
var prop = props.Select(p => p).FirstOrDefault(p => p.Name == propertyName);
|
|
||||||
if(prop != null)
|
|
||||||
{
|
|
||||||
return (int)prop.GetValue(type);
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,184 +0,0 @@
|
||||||
using System;
|
|
||||||
using Android.Widget;
|
|
||||||
using Bit.Android.Controls;
|
|
||||||
using Bit.App.Controls;
|
|
||||||
using Xamarin.Forms;
|
|
||||||
using Xamarin.Forms.Platform.Android;
|
|
||||||
using Android.Content;
|
|
||||||
using AView = Android.Views.View;
|
|
||||||
using AListView = Android.Widget.ListView;
|
|
||||||
using Android.Views;
|
|
||||||
|
|
||||||
[assembly: ExportRenderer(typeof(ExtendedTableView), typeof(ExtendedTableViewRenderer))]
|
|
||||||
namespace Bit.Android.Controls
|
|
||||||
{
|
|
||||||
public class ExtendedTableViewRenderer : TableViewRenderer
|
|
||||||
{
|
|
||||||
public ExtendedTableViewRenderer(Context context)
|
|
||||||
: base(context)
|
|
||||||
{ }
|
|
||||||
|
|
||||||
protected override void OnElementChanged(ElementChangedEventArgs<TableView> e)
|
|
||||||
{
|
|
||||||
base.OnElementChanged(e);
|
|
||||||
Control.Divider = null;
|
|
||||||
Control.DividerHeight = 0;
|
|
||||||
|
|
||||||
if(e.NewElement is ExtendedTableView tableView)
|
|
||||||
{
|
|
||||||
if(tableView.BottomPadding > 0)
|
|
||||||
{
|
|
||||||
Control.SetPadding(0, 0, 0, tableView.BottomPadding);
|
|
||||||
Control.SetClipToPadding(false);
|
|
||||||
Control.ScrollBarStyle = ScrollbarStyles.OutsideOverlay;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override TableViewModelRenderer GetModelRenderer(AListView listView, TableView view)
|
|
||||||
{
|
|
||||||
return new CustomTableViewModelRenderer(Context, listView, view);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override SizeRequest GetDesiredSize(int widthConstraint, int heightConstraint)
|
|
||||||
{
|
|
||||||
var baseSize = base.GetDesiredSize(widthConstraint, heightConstraint);
|
|
||||||
var height = ComputeHeight(Control, Convert.ToInt32(baseSize.Request.Width));
|
|
||||||
return new SizeRequest(new Size(baseSize.Request.Width, height));
|
|
||||||
}
|
|
||||||
|
|
||||||
private int ComputeHeight(AListView listView, int width)
|
|
||||||
{
|
|
||||||
var element = Element as ExtendedTableView;
|
|
||||||
|
|
||||||
var adapter = listView.Adapter;
|
|
||||||
var totalHeight = listView.PaddingTop + listView.PaddingBottom;
|
|
||||||
var desiredWidth = MeasureSpec.MakeMeasureSpec(width, MeasureSpecMode.AtMost);
|
|
||||||
for(var i = 0; i < adapter.Count; i++)
|
|
||||||
{
|
|
||||||
if(i == 0 && (element?.NoHeader ?? false))
|
|
||||||
{
|
|
||||||
totalHeight += 1;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var view = adapter.GetView(i, null, listView);
|
|
||||||
view.LayoutParameters = new LayoutParams(LayoutParams.WrapContent, LayoutParams.WrapContent);
|
|
||||||
view.Measure(desiredWidth, MeasureSpec.MakeMeasureSpec(0, MeasureSpecMode.Unspecified));
|
|
||||||
totalHeight += view.MeasuredHeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
return totalHeight + (listView.DividerHeight * (adapter.Count - 1));
|
|
||||||
}
|
|
||||||
|
|
||||||
private class CustomTableViewModelRenderer : TableViewModelRenderer
|
|
||||||
{
|
|
||||||
private readonly ExtendedTableView _view;
|
|
||||||
private readonly AListView _listView;
|
|
||||||
|
|
||||||
public CustomTableViewModelRenderer(Context context, AListView listView, TableView view)
|
|
||||||
: base(context, listView, view)
|
|
||||||
{
|
|
||||||
_view = view as ExtendedTableView;
|
|
||||||
_listView = listView;
|
|
||||||
}
|
|
||||||
|
|
||||||
private ITableViewController Controller => _view;
|
|
||||||
|
|
||||||
// ref http://bit.ly/2b9cjnQ
|
|
||||||
public override AView GetView(int position, AView convertView, ViewGroup parent)
|
|
||||||
{
|
|
||||||
var baseView = base.GetView(position, convertView, parent);
|
|
||||||
var layout = baseView as LinearLayout;
|
|
||||||
if(layout == null)
|
|
||||||
{
|
|
||||||
return baseView;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool isHeader, nextIsHeader;
|
|
||||||
var cell = GetCellForPosition(position, out isHeader, out nextIsHeader);
|
|
||||||
var cellView = CellFactory.GetCell(cell, convertView, parent, Context, _view);
|
|
||||||
if(layout.ChildCount > 0)
|
|
||||||
{
|
|
||||||
layout.RemoveViewAt(0);
|
|
||||||
layout.AddView(cellView, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(cellView.HasFocus)
|
|
||||||
{
|
|
||||||
cellView.ClearFocus();
|
|
||||||
}
|
|
||||||
|
|
||||||
if(isHeader)
|
|
||||||
{
|
|
||||||
var textCell = layout.GetChildAt(0) as BaseCellView;
|
|
||||||
if(textCell != null)
|
|
||||||
{
|
|
||||||
if(position == 0 && _view.NoHeader)
|
|
||||||
{
|
|
||||||
textCell.Visibility = ViewStates.Gone;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
textCell.MainText = textCell.MainText?.ToUpperInvariant();
|
|
||||||
textCell.SetMainTextColor(Color.FromHex("777777"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var bline = layout.GetChildAt(1);
|
|
||||||
if(bline != null)
|
|
||||||
{
|
|
||||||
bline.SetBackgroundColor(_view.SeparatorColor.ToAndroid());
|
|
||||||
}
|
|
||||||
|
|
||||||
return layout;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy/pasted from Xamarin source. Invoke via reflection instead maybe?
|
|
||||||
private Cell GetCellForPosition(int position, out bool isHeader, out bool nextIsHeader)
|
|
||||||
{
|
|
||||||
isHeader = false;
|
|
||||||
nextIsHeader = false;
|
|
||||||
|
|
||||||
var model = Controller.Model;
|
|
||||||
var sectionCount = model.GetSectionCount();
|
|
||||||
|
|
||||||
for(var sectionIndex = 0; sectionIndex < sectionCount; sectionIndex++)
|
|
||||||
{
|
|
||||||
var size = model.GetRowCount(sectionIndex) + 1;
|
|
||||||
if(position == 0)
|
|
||||||
{
|
|
||||||
isHeader = true;
|
|
||||||
nextIsHeader = size == 0 && sectionIndex < sectionCount - 1;
|
|
||||||
|
|
||||||
var header = model.GetHeaderCell(sectionIndex);
|
|
||||||
Cell resultCell = null;
|
|
||||||
if(header != null)
|
|
||||||
{
|
|
||||||
resultCell = header;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(resultCell == null)
|
|
||||||
{
|
|
||||||
resultCell = new TextCell { Text = model.GetSectionTitle(sectionIndex) };
|
|
||||||
}
|
|
||||||
|
|
||||||
resultCell.Parent = _view;
|
|
||||||
return resultCell;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(position < size)
|
|
||||||
{
|
|
||||||
nextIsHeader = position == size - 1;
|
|
||||||
return (Cell)model.GetItem(sectionIndex, position - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
position -= size;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,155 +0,0 @@
|
||||||
using Android.Content;
|
|
||||||
using System.ComponentModel;
|
|
||||||
using Android.Views;
|
|
||||||
using Bit.Android.Controls;
|
|
||||||
using Bit.App.Controls;
|
|
||||||
using Xamarin.Forms;
|
|
||||||
using Xamarin.Forms.Platform.Android;
|
|
||||||
using AView = Android.Views.View;
|
|
||||||
using Android.Widget;
|
|
||||||
using Android.Text;
|
|
||||||
|
|
||||||
[assembly: ExportRenderer(typeof(ExtendedTextCell), typeof(ExtendedTextCellRenderer))]
|
|
||||||
namespace Bit.Android.Controls
|
|
||||||
{
|
|
||||||
public class ExtendedTextCellRenderer : TextCellRenderer
|
|
||||||
{
|
|
||||||
protected AView View { get; private set; }
|
|
||||||
|
|
||||||
protected override AView GetCellCore(Cell item, AView convertView, ViewGroup parent, Context context)
|
|
||||||
{
|
|
||||||
var View = (BaseCellView)base.GetCellCore(item, convertView, parent, context);
|
|
||||||
var extendedCell = (ExtendedTextCell)item;
|
|
||||||
|
|
||||||
if(View != null)
|
|
||||||
{
|
|
||||||
if(extendedCell.BackgroundColor != Color.White)
|
|
||||||
{
|
|
||||||
View.SetBackgroundColor(extendedCell.BackgroundColor.ToAndroid());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
View.SetBackgroundResource(Resource.Drawable.list_selector);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(extendedCell.ShowDisclousure)
|
|
||||||
{
|
|
||||||
var resourceId = Resource.Drawable.ion_chevron_right;
|
|
||||||
if(!string.IsNullOrWhiteSpace(extendedCell.DisclousureImage))
|
|
||||||
{
|
|
||||||
var fileName = System.IO.Path.GetFileNameWithoutExtension(extendedCell.DisclousureImage);
|
|
||||||
resourceId = context.Resources.GetIdentifier(fileName, "drawable", context.PackageName);
|
|
||||||
}
|
|
||||||
|
|
||||||
var image = new DisclosureImage(context, extendedCell);
|
|
||||||
image.SetImageResource(resourceId);
|
|
||||||
image.SetPadding(10, 10, 30, 10);
|
|
||||||
View.SetAccessoryView(image);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(View.ChildCount > 1)
|
|
||||||
{
|
|
||||||
var layout = View.GetChildAt(1) as LinearLayout;
|
|
||||||
if(layout != null)
|
|
||||||
{
|
|
||||||
if(layout.ChildCount > 0)
|
|
||||||
{
|
|
||||||
var textView = layout.GetChildAt(0) as TextView;
|
|
||||||
if(textView != null)
|
|
||||||
{
|
|
||||||
textView.TextSize = (float)Device.GetNamedSize(NamedSize.Medium, typeof(Label));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(layout.ChildCount > 1)
|
|
||||||
{
|
|
||||||
var detailView = layout.GetChildAt(1) as TextView;
|
|
||||||
if(detailView != null)
|
|
||||||
{
|
|
||||||
UpdateLineBreakMode(detailView, extendedCell.DetailLineBreakMode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return View;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnCellPropertyChanged(object sender, PropertyChangedEventArgs args)
|
|
||||||
{
|
|
||||||
base.OnCellPropertyChanged(sender, args);
|
|
||||||
|
|
||||||
var cell = (ExtendedTextCell)Cell;
|
|
||||||
|
|
||||||
if(args.PropertyName == ExtendedTextCell.BackgroundColorProperty.PropertyName)
|
|
||||||
{
|
|
||||||
View.SetBackgroundColor(cell.BackgroundColor.ToAndroid());
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: other properties
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UpdateLineBreakMode(TextView view, LineBreakMode lineBreakMode)
|
|
||||||
{
|
|
||||||
if(view == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch(lineBreakMode)
|
|
||||||
{
|
|
||||||
case LineBreakMode.NoWrap:
|
|
||||||
view.SetSingleLine(true);
|
|
||||||
view.Ellipsize = null;
|
|
||||||
break;
|
|
||||||
case LineBreakMode.WordWrap:
|
|
||||||
view.SetSingleLine(false);
|
|
||||||
view.Ellipsize = null;
|
|
||||||
view.SetMaxLines(100);
|
|
||||||
break;
|
|
||||||
case LineBreakMode.CharacterWrap:
|
|
||||||
view.SetSingleLine(false);
|
|
||||||
view.Ellipsize = null;
|
|
||||||
view.SetMaxLines(100);
|
|
||||||
break;
|
|
||||||
case LineBreakMode.HeadTruncation:
|
|
||||||
view.SetSingleLine(true);
|
|
||||||
view.Ellipsize = TextUtils.TruncateAt.Start;
|
|
||||||
break;
|
|
||||||
case LineBreakMode.TailTruncation:
|
|
||||||
view.SetSingleLine(true);
|
|
||||||
view.Ellipsize = TextUtils.TruncateAt.End;
|
|
||||||
break;
|
|
||||||
case LineBreakMode.MiddleTruncation:
|
|
||||||
view.SetSingleLine(true);
|
|
||||||
view.Ellipsize = TextUtils.TruncateAt.Middle;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class DisclosureImage : ImageView
|
|
||||||
{
|
|
||||||
private ExtendedTextCell _cell;
|
|
||||||
|
|
||||||
public DisclosureImage(Context context, ExtendedTextCell cell) : base(context)
|
|
||||||
{
|
|
||||||
_cell = cell;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool OnTouchEvent(MotionEvent e)
|
|
||||||
{
|
|
||||||
switch(e.Action)
|
|
||||||
{
|
|
||||||
case MotionEventActions.Up:
|
|
||||||
_cell.OnDisclousureTapped();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,49 +0,0 @@
|
||||||
using Android.Content;
|
|
||||||
using System.ComponentModel;
|
|
||||||
using Android.Views;
|
|
||||||
using Bit.Android.Controls;
|
|
||||||
using Bit.App.Controls;
|
|
||||||
using Xamarin.Forms;
|
|
||||||
using Xamarin.Forms.Platform.Android;
|
|
||||||
using AView = Android.Views.View;
|
|
||||||
|
|
||||||
[assembly: ExportRenderer(typeof(ExtendedViewCell), typeof(ExtendedViewCellRenderer))]
|
|
||||||
namespace Bit.Android.Controls
|
|
||||||
{
|
|
||||||
public class ExtendedViewCellRenderer : ViewCellRenderer
|
|
||||||
{
|
|
||||||
protected AView View { get; private set; }
|
|
||||||
|
|
||||||
protected override AView GetCellCore(Cell item, AView convertView, ViewGroup parent, Context context)
|
|
||||||
{
|
|
||||||
var View = base.GetCellCore(item, convertView, parent, context);
|
|
||||||
var extendedCell = (ExtendedViewCell)item;
|
|
||||||
|
|
||||||
if(View != null)
|
|
||||||
{
|
|
||||||
if(extendedCell.BackgroundColor != Color.White)
|
|
||||||
{
|
|
||||||
View.SetBackgroundColor(extendedCell.BackgroundColor.ToAndroid());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
View.SetBackgroundResource(Resource.Drawable.list_selector);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return View;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnCellPropertyChanged(object sender, PropertyChangedEventArgs args)
|
|
||||||
{
|
|
||||||
base.OnCellPropertyChanged(sender, args);
|
|
||||||
|
|
||||||
var cell = (ExtendedViewCell)Cell;
|
|
||||||
|
|
||||||
if(args.PropertyName == ExtendedViewCell.BackgroundColorProperty.PropertyName)
|
|
||||||
{
|
|
||||||
View.SetBackgroundColor(cell.BackgroundColor.ToAndroid());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,25 +0,0 @@
|
||||||
#if !FDROID
|
|
||||||
using System;
|
|
||||||
using Android.App;
|
|
||||||
using Android.Content;
|
|
||||||
using Bit.App;
|
|
||||||
using Bit.App.Abstractions;
|
|
||||||
using Firebase.Iid;
|
|
||||||
using Plugin.Settings.Abstractions;
|
|
||||||
using XLabs.Ioc;
|
|
||||||
|
|
||||||
namespace Bit.Android
|
|
||||||
{
|
|
||||||
[Service]
|
|
||||||
[IntentFilter(new[] { "com.google.firebase.INSTANCE_ID_EVENT" })]
|
|
||||||
public class FirebaseInstanceIdService : Firebase.Iid.FirebaseInstanceIdService
|
|
||||||
{
|
|
||||||
public override void OnTokenRefresh()
|
|
||||||
{
|
|
||||||
var settings = Resolver.Resolve<ISettings>();
|
|
||||||
settings.AddOrUpdateValue(Constants.PushRegisteredToken, FirebaseInstanceId.Instance.Token);
|
|
||||||
Resolver.Resolve<IPushNotificationService>()?.Register();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
|
@ -1,56 +0,0 @@
|
||||||
#if !FDROID
|
|
||||||
using HockeyApp.Android;
|
|
||||||
using Bit.App.Abstractions;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using Android.Runtime;
|
|
||||||
|
|
||||||
namespace Bit.Android
|
|
||||||
{
|
|
||||||
public class HockeyAppCrashManagerListener : CrashManagerListener
|
|
||||||
{
|
|
||||||
private readonly IAppIdService _appIdService;
|
|
||||||
private readonly IAuthService _authService;
|
|
||||||
|
|
||||||
public HockeyAppCrashManagerListener()
|
|
||||||
{ }
|
|
||||||
|
|
||||||
public HockeyAppCrashManagerListener(System.IntPtr javaRef, JniHandleOwnership transfer)
|
|
||||||
: base(javaRef, transfer)
|
|
||||||
{ }
|
|
||||||
|
|
||||||
public HockeyAppCrashManagerListener(
|
|
||||||
IAppIdService appIdService,
|
|
||||||
IAuthService authService)
|
|
||||||
{
|
|
||||||
_appIdService = appIdService;
|
|
||||||
_authService = authService;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string Description
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if(_appIdService != null && _authService != null)
|
|
||||||
{
|
|
||||||
var log = new
|
|
||||||
{
|
|
||||||
AppId = _appIdService.AppId,
|
|
||||||
UserId = _authService.UserId
|
|
||||||
};
|
|
||||||
|
|
||||||
return JsonConvert.SerializeObject(log, Formatting.Indented);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool ShouldAutoUploadCrashes()
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
|
@ -1,200 +1,146 @@
|
||||||
using System;
|
using Android.App;
|
||||||
using Android.App;
|
|
||||||
using Android.Content.PM;
|
using Android.Content.PM;
|
||||||
using Android.Views;
|
using Android.Runtime;
|
||||||
using Android.OS;
|
using Android.OS;
|
||||||
using Bit.App.Abstractions;
|
using Bit.Core;
|
||||||
using XLabs.Ioc;
|
|
||||||
using Plugin.Settings.Abstractions;
|
|
||||||
using Plugin.Connectivity.Abstractions;
|
|
||||||
using Android.Content;
|
|
||||||
using System.Reflection;
|
|
||||||
using Xamarin.Forms.Platform.Android;
|
|
||||||
using Xamarin.Forms;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Bit.App;
|
|
||||||
using Android.Nfc;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Bit.App.Abstractions;
|
||||||
|
using Bit.Core.Utilities;
|
||||||
|
using Bit.Core.Abstractions;
|
||||||
|
using System.IO;
|
||||||
|
using System;
|
||||||
|
using Android.Content;
|
||||||
|
using Bit.Droid.Utilities;
|
||||||
|
using Bit.Droid.Receivers;
|
||||||
using Bit.App.Models;
|
using Bit.App.Models;
|
||||||
using Bit.App.Enums;
|
using Bit.Core.Enums;
|
||||||
|
using Android.Nfc;
|
||||||
|
|
||||||
namespace Bit.Android
|
namespace Bit.Droid
|
||||||
{
|
{
|
||||||
[Activity(ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation, Exported = false)]
|
[Activity(
|
||||||
public class MainActivity : FormsAppCompatActivity
|
Label = "Bitwarden",
|
||||||
|
Icon = "@mipmap/ic_launcher",
|
||||||
|
Theme = "@style/MainTheme",
|
||||||
|
Exported = false,
|
||||||
|
ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)]
|
||||||
|
[Register("com.x8bit.bitwarden.MainActivity")]
|
||||||
|
public class MainActivity : Xamarin.Forms.Platform.Android.FormsAppCompatActivity
|
||||||
{
|
{
|
||||||
private const string HockeyAppId = "d3834185b4a643479047b86c65293d42";
|
|
||||||
private Java.Util.Regex.Pattern _otpPattern = Java.Util.Regex.Pattern.Compile("^.*?([cbdefghijklnrtuv]{32,64})$");
|
|
||||||
private IDeviceActionService _deviceActionService;
|
private IDeviceActionService _deviceActionService;
|
||||||
private IDeviceInfoService _deviceInfoService;
|
private IMessagingService _messagingService;
|
||||||
private IAppSettingsService _appSettingsService;
|
private IBroadcasterService _broadcasterService;
|
||||||
private ISettings _settings;
|
private PendingIntent _lockAlarmPendingIntent;
|
||||||
private AppOptions _appOptions;
|
private AppOptions _appOptions;
|
||||||
|
private Java.Util.Regex.Pattern _otpPattern =
|
||||||
|
Java.Util.Regex.Pattern.Compile("^.*?([cbdefghijklnrtuv]{32,64})$");
|
||||||
|
|
||||||
protected override void OnCreate(Bundle bundle)
|
protected override void OnCreate(Bundle savedInstanceState)
|
||||||
{
|
{
|
||||||
if(!Resolver.IsSet)
|
var alarmIntent = new Intent(this, typeof(LockAlarmReceiver));
|
||||||
{
|
_lockAlarmPendingIntent = PendingIntent.GetBroadcast(this, 0, alarmIntent,
|
||||||
MainApplication.SetIoc(Application);
|
PendingIntentFlags.UpdateCurrent);
|
||||||
}
|
|
||||||
|
|
||||||
var policy = new StrictMode.ThreadPolicy.Builder().PermitAll().Build();
|
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
|
||||||
StrictMode.SetThreadPolicy(policy);
|
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
|
||||||
|
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
|
||||||
|
|
||||||
ToolbarResource = Resource.Layout.toolbar;
|
TabLayoutResource = Resource.Layout.Tabbar;
|
||||||
TabLayoutResource = Resource.Layout.tabs;
|
ToolbarResource = Resource.Layout.Toolbar;
|
||||||
|
|
||||||
base.OnCreate(bundle);
|
base.OnCreate(savedInstanceState);
|
||||||
|
Xamarin.Essentials.Platform.Init(this, savedInstanceState);
|
||||||
// workaround for app compat bug
|
Xamarin.Forms.Forms.Init(this, savedInstanceState);
|
||||||
// ref https://forums.xamarin.com/discussion/62414/app-resuming-results-in-crash-with-formsappcompatactivity
|
|
||||||
Task.Delay(10).Wait();
|
|
||||||
|
|
||||||
Console.WriteLine("A OnCreate");
|
|
||||||
if(!App.Utilities.Helpers.InDebugMode())
|
|
||||||
{
|
|
||||||
Window.AddFlags(WindowManagerFlags.Secure);
|
|
||||||
}
|
|
||||||
|
|
||||||
var appIdService = Resolver.Resolve<IAppIdService>();
|
|
||||||
var authService = Resolver.Resolve<IAuthService>();
|
|
||||||
|
|
||||||
#if !FDROID
|
|
||||||
HockeyApp.Android.CrashManager.Register(this, HockeyAppId,
|
|
||||||
new HockeyAppCrashManagerListener(appIdService, authService));
|
|
||||||
#endif
|
|
||||||
|
|
||||||
Forms.Init(this, bundle);
|
|
||||||
|
|
||||||
typeof(Color).GetProperty("Accent", BindingFlags.Public | BindingFlags.Static)
|
|
||||||
.SetValue(null, Color.FromHex("d2d6de"));
|
|
||||||
|
|
||||||
_deviceActionService = Resolver.Resolve<IDeviceActionService>();
|
|
||||||
_deviceInfoService = Resolver.Resolve<IDeviceInfoService>();
|
|
||||||
_appSettingsService = Resolver.Resolve<IAppSettingsService>();
|
|
||||||
_settings = Resolver.Resolve<ISettings>();
|
|
||||||
_appOptions = GetOptions();
|
_appOptions = GetOptions();
|
||||||
LoadApplication(new App.App(
|
LoadApplication(new App.App(_appOptions));
|
||||||
_appOptions,
|
|
||||||
Resolver.Resolve<IAuthService>(),
|
|
||||||
Resolver.Resolve<IConnectivity>(),
|
|
||||||
Resolver.Resolve<IDatabaseService>(),
|
|
||||||
Resolver.Resolve<ISyncService>(),
|
|
||||||
_settings,
|
|
||||||
Resolver.Resolve<ILockService>(),
|
|
||||||
Resolver.Resolve<ILocalizeService>(),
|
|
||||||
Resolver.Resolve<IAppInfoService>(),
|
|
||||||
_appSettingsService,
|
|
||||||
_deviceActionService));
|
|
||||||
|
|
||||||
if(_appOptions?.Uri == null)
|
_broadcasterService.Subscribe(nameof(MainActivity), (message) =>
|
||||||
{
|
{
|
||||||
MessagingCenter.Subscribe<Xamarin.Forms.Application, bool>(Xamarin.Forms.Application.Current,
|
if(message.Command == "scheduleLockTimer")
|
||||||
"ListenYubiKeyOTP", (sender, listen) => ListenYubiKey(listen));
|
{
|
||||||
|
var lockOptionMs = (int)message.Data * 1000;
|
||||||
MessagingCenter.Subscribe<Xamarin.Forms.Application>(Xamarin.Forms.Application.Current,
|
var triggerMs = Java.Lang.JavaSystem.CurrentTimeMillis() + lockOptionMs + 10;
|
||||||
"FinishMainActivity", (sender) => Finish());
|
var alarmManager = GetSystemService(AlarmService) as AlarmManager;
|
||||||
}
|
alarmManager.Set(AlarmType.RtcWakeup, triggerMs, _lockAlarmPendingIntent);
|
||||||
|
}
|
||||||
|
else if(message.Command == "cancelLockTimer")
|
||||||
|
{
|
||||||
|
var alarmManager = GetSystemService(AlarmService) as AlarmManager;
|
||||||
|
alarmManager.Cancel(_lockAlarmPendingIntent);
|
||||||
|
}
|
||||||
|
else if(message.Command == "finishMainActivity")
|
||||||
|
{
|
||||||
|
Finish();
|
||||||
|
}
|
||||||
|
else if(message.Command == "listenYubiKeyOTP")
|
||||||
|
{
|
||||||
|
ListenYubiKey((bool)message.Data);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnPause()
|
protected override void OnPause()
|
||||||
{
|
{
|
||||||
Console.WriteLine("A OnPause");
|
|
||||||
base.OnPause();
|
base.OnPause();
|
||||||
ListenYubiKey(false);
|
ListenYubiKey(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnDestroy()
|
|
||||||
{
|
|
||||||
Console.WriteLine("A OnDestroy");
|
|
||||||
base.OnDestroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnRestart()
|
|
||||||
{
|
|
||||||
Console.WriteLine("A OnRestart");
|
|
||||||
base.OnRestart();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnStart()
|
|
||||||
{
|
|
||||||
Console.WriteLine("A OnStart");
|
|
||||||
base.OnStart();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnStop()
|
|
||||||
{
|
|
||||||
Console.WriteLine("A OnStop");
|
|
||||||
base.OnStop();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnResume()
|
protected override void OnResume()
|
||||||
{
|
{
|
||||||
base.OnResume();
|
base.OnResume();
|
||||||
Console.WriteLine("A OnResume");
|
if(_deviceActionService.SupportsNfc())
|
||||||
|
|
||||||
// workaround for app compat bug
|
|
||||||
// ref https://bugzilla.xamarin.com/show_bug.cgi?id=36907
|
|
||||||
Task.Delay(10).Wait();
|
|
||||||
|
|
||||||
if(_deviceInfoService.NfcEnabled)
|
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
MessagingCenter.Send(Xamarin.Forms.Application.Current, "ResumeYubiKey");
|
_messagingService.Send("resumeYubiKey");
|
||||||
}
|
}
|
||||||
catch(Exception e)
|
catch { }
|
||||||
{
|
|
||||||
System.Diagnostics.Debug.WriteLine(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(_appSettingsService.Locked)
|
|
||||||
{
|
|
||||||
MessagingCenter.Send(Xamarin.Forms.Application.Current, "Resumed", false);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnNewIntent(Intent intent)
|
protected override void OnNewIntent(Intent intent)
|
||||||
{
|
{
|
||||||
base.OnNewIntent(intent);
|
base.OnNewIntent(intent);
|
||||||
Console.WriteLine("A OnNewIntent");
|
|
||||||
ParseYubiKey(intent.DataString);
|
ParseYubiKey(intent.DataString);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async override void OnRequestPermissionsResult(int requestCode, string[] permissions, Permission[] grantResults)
|
public async override void OnRequestPermissionsResult(int requestCode, string[] permissions,
|
||||||
|
[GeneratedEnum] Permission[] grantResults)
|
||||||
{
|
{
|
||||||
if(requestCode == Constants.SelectFilePermissionRequestCode)
|
if(requestCode == Constants.SelectFilePermissionRequestCode)
|
||||||
{
|
{
|
||||||
if(grantResults.Any(r => r != Permission.Granted))
|
if(grantResults.Any(r => r != Permission.Granted))
|
||||||
{
|
{
|
||||||
MessagingCenter.Send(Xamarin.Forms.Application.Current, "SelectFileCameraPermissionDenied");
|
_messagingService.Send("selectFileCameraPermissionDenied");
|
||||||
}
|
}
|
||||||
await _deviceActionService.SelectFileAsync();
|
await _deviceActionService.SelectFileAsync();
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
ZXing.Net.Mobile.Forms.Android.PermissionsHandler.OnRequestPermissionsResult(requestCode, permissions, grantResults);
|
{
|
||||||
|
Xamarin.Essentials.Platform.OnRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||||
|
ZXing.Net.Mobile.Forms.Android.PermissionsHandler.OnRequestPermissionsResult(
|
||||||
|
requestCode, permissions, grantResults);
|
||||||
|
}
|
||||||
|
base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
|
protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
|
||||||
{
|
{
|
||||||
if(requestCode == Constants.SelectFileRequestCode && resultCode == Result.Ok)
|
if(requestCode == Constants.SelectFileRequestCode && resultCode == Result.Ok)
|
||||||
{
|
{
|
||||||
global::Android.Net.Uri uri = null;
|
Android.Net.Uri uri = null;
|
||||||
string fileName = null;
|
string fileName = null;
|
||||||
if(data != null && data.Data != null)
|
if(data != null && data.Data != null)
|
||||||
{
|
{
|
||||||
uri = data.Data;
|
uri = data.Data;
|
||||||
fileName = Utilities.GetFileName(ApplicationContext, uri);
|
fileName = AndroidHelpers.GetFileName(ApplicationContext, uri);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// camera
|
// camera
|
||||||
var root = new Java.IO.File(global::Android.OS.Environment.ExternalStorageDirectory, "bitwarden");
|
var root = new Java.IO.File(Android.OS.Environment.ExternalStorageDirectory, "bitwarden");
|
||||||
var file = new Java.IO.File(root, "temp_camera_photo.jpg");
|
var file = new Java.IO.File(root, "temp_camera_photo.jpg");
|
||||||
uri = global::Android.Net.Uri.FromFile(file);
|
uri = Android.Net.Uri.FromFile(file);
|
||||||
fileName = $"photo_{DateTime.UtcNow.ToString("yyyyMMddHHmmss")}.jpg";
|
fileName = $"photo_{DateTime.UtcNow.ToString("yyyyMMddHHmmss")}.jpg";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -202,18 +148,17 @@ namespace Bit.Android
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using(var stream = ContentResolver.OpenInputStream(uri))
|
using(var stream = ContentResolver.OpenInputStream(uri))
|
||||||
using(var memoryStream = new MemoryStream())
|
using(var memoryStream = new MemoryStream())
|
||||||
{
|
{
|
||||||
stream.CopyTo(memoryStream);
|
stream.CopyTo(memoryStream);
|
||||||
MessagingCenter.Send(Xamarin.Forms.Application.Current, "SelectFileResult",
|
_messagingService.Send("selectFileResult",
|
||||||
new Tuple<byte[], string>(memoryStream.ToArray(), fileName ?? "unknown_file_name"));
|
new Tuple<byte[], string>(memoryStream.ToArray(), fileName ?? "unknown_file_name"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Java.IO.FileNotFoundException)
|
catch(Java.IO.FileNotFoundException)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -222,24 +167,21 @@ namespace Bit.Android
|
||||||
|
|
||||||
private void ListenYubiKey(bool listen)
|
private void ListenYubiKey(bool listen)
|
||||||
{
|
{
|
||||||
if(!_deviceInfoService.NfcEnabled)
|
if(!_deviceActionService.SupportsNfc())
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var adapter = NfcAdapter.GetDefaultAdapter(this);
|
var adapter = NfcAdapter.GetDefaultAdapter(this);
|
||||||
if(listen)
|
if(listen)
|
||||||
{
|
{
|
||||||
var intent = new Intent(this, Class);
|
var intent = new Intent(this, Class);
|
||||||
intent.AddFlags(ActivityFlags.SingleTop);
|
intent.AddFlags(ActivityFlags.SingleTop);
|
||||||
var pendingIntent = PendingIntent.GetActivity(this, 0, intent, 0);
|
var pendingIntent = PendingIntent.GetActivity(this, 0, intent, 0);
|
||||||
|
|
||||||
// register for all NDEF tags starting with http och https
|
// register for all NDEF tags starting with http och https
|
||||||
var ndef = new IntentFilter(NfcAdapter.ActionNdefDiscovered);
|
var ndef = new IntentFilter(NfcAdapter.ActionNdefDiscovered);
|
||||||
ndef.AddDataScheme("http");
|
ndef.AddDataScheme("http");
|
||||||
ndef.AddDataScheme("https");
|
ndef.AddDataScheme("https");
|
||||||
var filters = new IntentFilter[] { ndef };
|
var filters = new IntentFilter[] { ndef };
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// register for foreground dispatch so we'll receive tags according to our intent filters
|
// register for foreground dispatch so we'll receive tags according to our intent filters
|
||||||
|
@ -253,21 +195,6 @@ namespace Bit.Android
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ParseYubiKey(string data)
|
|
||||||
{
|
|
||||||
if(data == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var otpMatch = _otpPattern.Matcher(data);
|
|
||||||
if(otpMatch.Matches())
|
|
||||||
{
|
|
||||||
var otp = otpMatch.Group(1);
|
|
||||||
MessagingCenter.Send(Xamarin.Forms.Application.Current, "GotYubiKeyOTP", otp);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private AppOptions GetOptions()
|
private AppOptions GetOptions()
|
||||||
{
|
{
|
||||||
var options = new AppOptions
|
var options = new AppOptions
|
||||||
|
@ -276,13 +203,11 @@ namespace Bit.Android
|
||||||
MyVaultTile = Intent.GetBooleanExtra("myVaultTile", false),
|
MyVaultTile = Intent.GetBooleanExtra("myVaultTile", false),
|
||||||
FromAutofillFramework = Intent.GetBooleanExtra("autofillFramework", false)
|
FromAutofillFramework = Intent.GetBooleanExtra("autofillFramework", false)
|
||||||
};
|
};
|
||||||
|
|
||||||
var fillType = Intent.GetIntExtra("autofillFrameworkFillType", 0);
|
var fillType = Intent.GetIntExtra("autofillFrameworkFillType", 0);
|
||||||
if(fillType > 0)
|
if(fillType > 0)
|
||||||
{
|
{
|
||||||
options.FillType = (CipherType)fillType;
|
options.FillType = (CipherType)fillType;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(Intent.GetBooleanExtra("autofillFrameworkSave", false))
|
if(Intent.GetBooleanExtra("autofillFrameworkSave", false))
|
||||||
{
|
{
|
||||||
options.SaveType = (CipherType)Intent.GetIntExtra("autofillFrameworkType", 0);
|
options.SaveType = (CipherType)Intent.GetIntExtra("autofillFrameworkType", 0);
|
||||||
|
@ -295,8 +220,21 @@ namespace Bit.Android
|
||||||
options.SaveCardExpYear = Intent.GetStringExtra("autofillFrameworkCardExpYear");
|
options.SaveCardExpYear = Intent.GetStringExtra("autofillFrameworkCardExpYear");
|
||||||
options.SaveCardCode = Intent.GetStringExtra("autofillFrameworkCardCode");
|
options.SaveCardCode = Intent.GetStringExtra("autofillFrameworkCardCode");
|
||||||
}
|
}
|
||||||
|
|
||||||
return options;
|
return options;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ParseYubiKey(string data)
|
||||||
|
{
|
||||||
|
if(data == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var otpMatch = _otpPattern.Matcher(data);
|
||||||
|
if(otpMatch.Matches())
|
||||||
|
{
|
||||||
|
var otp = otpMatch.Group(1);
|
||||||
|
_messagingService.Send("gotYubiKeyOTP", otp);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,147 +1,108 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Android.App;
|
using Android.App;
|
||||||
using Android.Content;
|
|
||||||
using Android.OS;
|
|
||||||
using Android.Runtime;
|
using Android.Runtime;
|
||||||
using Bit.Android.Services;
|
|
||||||
using Bit.App.Abstractions;
|
using Bit.App.Abstractions;
|
||||||
using Bit.App.Repositories;
|
|
||||||
using Bit.App.Services;
|
using Bit.App.Services;
|
||||||
using Plugin.Connectivity;
|
using Bit.Core.Abstractions;
|
||||||
|
using Bit.Core.Services;
|
||||||
|
using Bit.Core.Utilities;
|
||||||
|
using Bit.Droid.Services;
|
||||||
using Plugin.CurrentActivity;
|
using Plugin.CurrentActivity;
|
||||||
using Plugin.Fingerprint;
|
using Plugin.Fingerprint;
|
||||||
using Plugin.Settings;
|
using Plugin.Fingerprint.Abstractions;
|
||||||
using XLabs.Ioc;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using XLabs.Ioc.SimpleInjectorContainer;
|
|
||||||
using SimpleInjector;
|
|
||||||
using Android.Gms.Security;
|
|
||||||
|
|
||||||
namespace Bit.Android
|
namespace Bit.Droid
|
||||||
{
|
{
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
[Application(Debuggable = true)]
|
[Application(Debuggable = true)]
|
||||||
#else
|
#else
|
||||||
[Application(Debuggable = false)]
|
[Application(Debuggable = false)]
|
||||||
#endif
|
#endif
|
||||||
public class MainApplication : Application, ProviderInstaller.IProviderInstallListener
|
[Register("com.x8bit.bitwarden.MainApplication")]
|
||||||
|
public class MainApplication : Application
|
||||||
{
|
{
|
||||||
public MainApplication(IntPtr handle, JniHandleOwnership transer)
|
public MainApplication(IntPtr handle, JniHandleOwnership transer)
|
||||||
: base(handle, transer)
|
: base(handle, transer)
|
||||||
{
|
{
|
||||||
//AndroidEnvironment.UnhandledExceptionRaiser += AndroidEnvironment_UnhandledExceptionRaiser;
|
if(ServiceContainer.RegisteredServices.Count == 0)
|
||||||
|
|
||||||
if(!Resolver.IsSet)
|
|
||||||
{
|
{
|
||||||
SetIoc(this);
|
RegisterLocalServices();
|
||||||
|
ServiceContainer.Init();
|
||||||
}
|
}
|
||||||
|
|
||||||
if(Build.VERSION.SdkInt <= BuildVersionCodes.Kitkat)
|
|
||||||
{
|
|
||||||
ProviderInstaller.InstallIfNeededAsync(ApplicationContext, this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void AndroidEnvironment_UnhandledExceptionRaiser(object sender, RaiseThrowableEventArgs e)
|
|
||||||
{
|
|
||||||
var message = Utilities.AppendExceptionToMessage("", e.Exception);
|
|
||||||
//Utilities.SaveCrashFile(message, true);
|
|
||||||
Utilities.SendCrashEmail(message, false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void OnCreate()
|
public override void OnCreate()
|
||||||
{
|
{
|
||||||
base.OnCreate();
|
base.OnCreate();
|
||||||
|
Bootstrap();
|
||||||
// workaround for app compat bug
|
|
||||||
// ref https://forums.xamarin.com/discussion/62414/app-resuming-results-in-crash-with-formsappcompatactivity
|
|
||||||
Task.Delay(10).Wait();
|
|
||||||
CrossCurrentActivity.Current.Init(this);
|
CrossCurrentActivity.Current.Init(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void SetIoc(Application application)
|
private void RegisterLocalServices()
|
||||||
{
|
{
|
||||||
Refractored.FabControl.Droid.FloatingActionButtonViewRenderer.Init();
|
Refractored.FabControl.Droid.FloatingActionButtonViewRenderer.Init();
|
||||||
FFImageLoading.Forms.Platform.CachedImageRenderer.Init(true);
|
// Note: This might cause a race condition. Investigate more.
|
||||||
ZXing.Net.Mobile.Forms.Android.Platform.Init();
|
Task.Run(() =>
|
||||||
|
{
|
||||||
|
FFImageLoading.Forms.Platform.CachedImageRenderer.Init(true);
|
||||||
|
ZXing.Net.Mobile.Forms.Android.Platform.Init();
|
||||||
|
});
|
||||||
CrossFingerprint.SetCurrentActivityResolver(() => CrossCurrentActivity.Current.Activity);
|
CrossFingerprint.SetCurrentActivityResolver(() => CrossCurrentActivity.Current.Activity);
|
||||||
|
|
||||||
//var container = new UnityContainer();
|
var preferencesStorage = new PreferencesStorageService(null);
|
||||||
var container = new Container();
|
var documentsPath = Environment.GetFolderPath(Environment.SpecialFolder.Personal);
|
||||||
|
var liteDbStorage = new LiteDbStorageService(Path.Combine(documentsPath, "bitwarden.db"));
|
||||||
|
liteDbStorage.InitAsync();
|
||||||
|
var localizeService = new LocalizeService();
|
||||||
|
var broadcasterService = new BroadcasterService();
|
||||||
|
var messagingService = new MobileBroadcasterMessagingService(broadcasterService);
|
||||||
|
var i18nService = new MobileI18nService(localizeService.GetCurrentCultureInfo());
|
||||||
|
var secureStorageService = new SecureStorageService();
|
||||||
|
var cryptoPrimitiveService = new CryptoPrimitiveService();
|
||||||
|
var mobileStorageService = new MobileStorageService(preferencesStorage, liteDbStorage);
|
||||||
|
var deviceActionService = new DeviceActionService(mobileStorageService, messagingService,
|
||||||
|
broadcasterService);
|
||||||
|
var platformUtilsService = new MobilePlatformUtilsService(deviceActionService, messagingService,
|
||||||
|
broadcasterService);
|
||||||
|
|
||||||
// Android Stuff
|
ServiceContainer.Register<IBroadcasterService>("broadcasterService", broadcasterService);
|
||||||
container.RegisterInstance(application.ApplicationContext);
|
ServiceContainer.Register<IMessagingService>("messagingService", messagingService);
|
||||||
container.RegisterInstance<Application>(application);
|
ServiceContainer.Register<ILocalizeService>("localizeService", localizeService);
|
||||||
|
ServiceContainer.Register<II18nService>("i18nService", i18nService);
|
||||||
// Services
|
ServiceContainer.Register<ICryptoPrimitiveService>("cryptoPrimitiveService", cryptoPrimitiveService);
|
||||||
container.RegisterSingleton<IDatabaseService, DatabaseService>();
|
ServiceContainer.Register<IStorageService>("storageService", mobileStorageService);
|
||||||
container.RegisterSingleton<ISqlService, SqlService>();
|
ServiceContainer.Register<IStorageService>("secureStorageService", secureStorageService);
|
||||||
container.RegisterSingleton<ISecureStorageService, AndroidKeyStoreStorageService>();
|
ServiceContainer.Register<IDeviceActionService>("deviceActionService", deviceActionService);
|
||||||
container.RegisterSingleton<ICryptoService, CryptoService>();
|
ServiceContainer.Register<IPlatformUtilsService>("platformUtilsService", platformUtilsService);
|
||||||
container.RegisterSingleton<IKeyDerivationService, BouncyCastleKeyDerivationService>();
|
|
||||||
container.RegisterSingleton<IAuthService, AuthService>();
|
|
||||||
container.RegisterSingleton<IFolderService, FolderService>();
|
|
||||||
container.RegisterSingleton<ICollectionService, CollectionService>();
|
|
||||||
container.RegisterSingleton<ICipherService, CipherService>();
|
|
||||||
container.RegisterSingleton<ISyncService, SyncService>();
|
|
||||||
container.RegisterSingleton<IDeviceActionService, DeviceActionService>();
|
|
||||||
container.RegisterSingleton<IAppIdService, AppIdService>();
|
|
||||||
container.RegisterSingleton<IPasswordGenerationService, PasswordGenerationService>();
|
|
||||||
container.RegisterSingleton<ILockService, LockService>();
|
|
||||||
container.RegisterSingleton<IAppInfoService, AppInfoService>();
|
|
||||||
#if FDROID
|
|
||||||
container.RegisterSingleton<IGoogleAnalyticsService, NoopGoogleAnalyticsService>();
|
|
||||||
#else
|
|
||||||
container.RegisterSingleton<IGoogleAnalyticsService, GoogleAnalyticsService>();
|
|
||||||
#endif
|
|
||||||
container.RegisterSingleton<IDeviceInfoService, DeviceInfoService>();
|
|
||||||
container.RegisterSingleton<ILocalizeService, LocalizeService>();
|
|
||||||
container.RegisterSingleton<ILogService, LogService>();
|
|
||||||
container.RegisterSingleton<IHttpService, HttpService>();
|
|
||||||
container.RegisterSingleton<ITokenService, TokenService>();
|
|
||||||
container.RegisterSingleton<ISettingsService, SettingsService>();
|
|
||||||
container.RegisterSingleton<IAppSettingsService, AppSettingsService>();
|
|
||||||
|
|
||||||
// Repositories
|
|
||||||
container.RegisterSingleton<IFolderRepository, FolderRepository>();
|
|
||||||
container.RegisterSingleton<IFolderApiRepository, FolderApiRepository>();
|
|
||||||
container.RegisterSingleton<ICipherRepository, CipherRepository>();
|
|
||||||
container.RegisterSingleton<IAttachmentRepository, AttachmentRepository>();
|
|
||||||
container.RegisterSingleton<IConnectApiRepository, ConnectApiRepository>();
|
|
||||||
container.RegisterSingleton<IDeviceApiRepository, DeviceApiRepository>();
|
|
||||||
container.RegisterSingleton<IAccountsApiRepository, AccountsApiRepository>();
|
|
||||||
container.RegisterSingleton<ICipherApiRepository, CipherApiRepository>();
|
|
||||||
container.RegisterSingleton<ISettingsRepository, SettingsRepository>();
|
|
||||||
container.RegisterSingleton<ISettingsApiRepository, SettingsApiRepository>();
|
|
||||||
container.RegisterSingleton<ITwoFactorApiRepository, TwoFactorApiRepository>();
|
|
||||||
container.RegisterSingleton<ISyncApiRepository, SyncApiRepository>();
|
|
||||||
container.RegisterSingleton<ICollectionRepository, CollectionRepository>();
|
|
||||||
container.RegisterSingleton<ICipherCollectionRepository, CipherCollectionRepository>();
|
|
||||||
|
|
||||||
// Other
|
|
||||||
container.RegisterInstance(CrossSettings.Current);
|
|
||||||
container.RegisterInstance(CrossConnectivity.Current);
|
|
||||||
container.RegisterInstance(CrossFingerprint.Current);
|
|
||||||
|
|
||||||
// Push
|
// Push
|
||||||
#if FDROID
|
#if FDROID
|
||||||
container.RegisterSingleton<IPushNotificationListener, NoopPushNotificationListener>();
|
container.RegisterSingleton<IPushNotificationListener, NoopPushNotificationListener>();
|
||||||
container.RegisterSingleton<IPushNotificationService, NoopPushNotificationService>();
|
container.RegisterSingleton<IPushNotificationService, NoopPushNotificationService>();
|
||||||
#else
|
#else
|
||||||
container.RegisterSingleton<IPushNotificationListener, PushNotificationListener>();
|
var notificationListenerService = new PushNotificationListenerService();
|
||||||
container.RegisterSingleton<IPushNotificationService, AndroidPushNotificationService>();
|
ServiceContainer.Register<IPushNotificationListenerService>(
|
||||||
|
"pushNotificationListenerService", notificationListenerService);
|
||||||
|
var androidPushNotificationService = new AndroidPushNotificationService(
|
||||||
|
mobileStorageService, notificationListenerService);
|
||||||
|
ServiceContainer.Register<IPushNotificationService>(
|
||||||
|
"pushNotificationService", androidPushNotificationService);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
container.Verify();
|
|
||||||
Resolver.SetResolver(new SimpleInjectorResolver(container));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OnProviderInstallFailed(int errorCode, Intent recoveryIntent)
|
private void Bootstrap()
|
||||||
{
|
{
|
||||||
|
(ServiceContainer.Resolve<II18nService>("i18nService") as MobileI18nService).Init();
|
||||||
|
ServiceContainer.Resolve<IAuthService>("authService").Init();
|
||||||
|
// Note: This is not awaited
|
||||||
|
var bootstrapTask = BootstrapAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OnProviderInstalled()
|
private async Task BootstrapAsync()
|
||||||
{
|
{
|
||||||
|
await ServiceContainer.Resolve<IEnvironmentService>("environmentService").SetUrlsFromStorageAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,58 +0,0 @@
|
||||||
using Android.App;
|
|
||||||
using Android.Content;
|
|
||||||
using Android.Service.QuickSettings;
|
|
||||||
using Java.Lang;
|
|
||||||
|
|
||||||
namespace Bit.Android
|
|
||||||
{
|
|
||||||
[Service(Permission = global::Android.Manifest.Permission.BindQuickSettingsTile,
|
|
||||||
Label = "@string/MyVault", Icon = "@drawable/shield")]
|
|
||||||
[IntentFilter(new string[] { ActionQsTile })]
|
|
||||||
public class MyVaultTileService : TileService
|
|
||||||
{
|
|
||||||
public override void OnTileAdded()
|
|
||||||
{
|
|
||||||
base.OnTileAdded();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void OnStartListening()
|
|
||||||
{
|
|
||||||
base.OnStartListening();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void OnStopListening()
|
|
||||||
{
|
|
||||||
base.OnStopListening();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void OnTileRemoved()
|
|
||||||
{
|
|
||||||
base.OnTileRemoved();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void OnClick()
|
|
||||||
{
|
|
||||||
base.OnClick();
|
|
||||||
|
|
||||||
if(IsLocked)
|
|
||||||
{
|
|
||||||
UnlockAndRun(new Runnable(() =>
|
|
||||||
{
|
|
||||||
LaunchMyVault();
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
LaunchMyVault();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void LaunchMyVault()
|
|
||||||
{
|
|
||||||
var intent = new Intent(this, typeof(SplashActivity));
|
|
||||||
intent.SetFlags(ActivityFlags.NewTask | ActivityFlags.SingleTop | ActivityFlags.ClearTop);
|
|
||||||
intent.PutExtra("myVaultTile", true);
|
|
||||||
StartActivityAndCollapse(intent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,23 +0,0 @@
|
||||||
using Android.App;
|
|
||||||
using Android.Content;
|
|
||||||
using Bit.App.Abstractions;
|
|
||||||
using Bit.App.Utilities;
|
|
||||||
using Plugin.Settings.Abstractions;
|
|
||||||
using System;
|
|
||||||
using XLabs.Ioc;
|
|
||||||
|
|
||||||
namespace Bit.Android
|
|
||||||
{
|
|
||||||
[BroadcastReceiver(Name = "com.x8bit.bitwarden.PackageReplacedReceiver", Exported = false)]
|
|
||||||
[IntentFilter(new[] { Intent.ActionMyPackageReplaced })]
|
|
||||||
public class PackageReplacedReceiver : BroadcastReceiver
|
|
||||||
{
|
|
||||||
public override void OnReceive(Context context, Intent intent)
|
|
||||||
{
|
|
||||||
Console.WriteLine("Bitwarden App Updated!!");
|
|
||||||
Helpers.PerformUpdateTasks(Resolver.Resolve<ISettings>(),
|
|
||||||
Resolver.Resolve<IAppInfoService>(), Resolver.Resolve<IDatabaseService>(),
|
|
||||||
Resolver.Resolve<ISyncService>());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,6 +1,12 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.x8bit.bitwarden" android:versionName="1.22.0" android:installLocation="auto" android:versionCode="502" xmlns:tools="http://schemas.android.com/tools">
|
<manifest
|
||||||
<uses-sdk android:minSdkVersion="19" android:targetSdkVersion="26" />
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:versionCode="1"
|
||||||
|
android:versionName="2.0.0"
|
||||||
|
package="com.x8bit.bitwarden">
|
||||||
|
|
||||||
|
<uses-sdk android:minSdkVersion="19" android:targetSdkVersion="28" />
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<uses-permission android:name="android.permission.NFC" />
|
<uses-permission android:name="android.permission.NFC" />
|
||||||
|
@ -12,31 +18,22 @@
|
||||||
<uses-feature android:name="android.hardware.camera" android:required="false" />
|
<uses-feature android:name="android.hardware.camera" android:required="false" />
|
||||||
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false" />
|
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false" />
|
||||||
|
|
||||||
<application android:label="Bitwarden" android:theme="@style/BitwardenTheme" android:allowBackup="false"
|
<application
|
||||||
android:icon="@mipmap/ic_launcher" android:roundIcon="@mipmap/ic_launcher_round"
|
android:label="Bitwarden"
|
||||||
android:networkSecurityConfig="@xml/network_security_config">
|
android:theme="@style/MainTheme"
|
||||||
|
android:allowBackup="false"
|
||||||
|
tools:replace="android:allowBackup"
|
||||||
|
android:icon="@mipmap/ic_launcher"
|
||||||
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
|
android:networkSecurityConfig="@xml/network_security_config">
|
||||||
<provider
|
<provider
|
||||||
android:name="android.support.v4.content.FileProvider"
|
android:name="android.support.v4.content.FileProvider"
|
||||||
android:authorities="com.x8bit.bitwarden.fileprovider"
|
android:authorities="com.x8bit.bitwarden.fileprovider"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
android:grantUriPermissions="true">
|
android:grantUriPermissions="true">
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||||
android:resource="@xml/filepaths" />
|
android:resource="@xml/filepaths" />
|
||||||
</provider>
|
</provider>
|
||||||
|
|
||||||
<receiver android:name="com.google.firebase.iid.FirebaseInstanceIdInternalReceiver"
|
|
||||||
android:exported="false" />
|
|
||||||
<receiver android:name="com.google.firebase.iid.FirebaseInstanceIdReceiver"
|
|
||||||
android:exported="true" android:permission="com.google.android.c2dm.permission.SEND">
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
|
|
||||||
<action android:name="com.google.android.c2dm.intent.REGISTRATION" />
|
|
||||||
<category android:name="com.x8bit.bitwarden" />
|
|
||||||
</intent-filter>
|
|
||||||
</receiver>
|
|
||||||
|
|
||||||
<activity android:name="net.hockeyapp.android.UpdateActivity" android:exported="false" />
|
|
||||||
<meta-data android:name="android.max_aspect" android:value="2.1" />
|
|
||||||
</application>
|
</application>
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using Android.App;
|
|
||||||
|
|
||||||
// General Information about an assembly is controlled through the following
|
// General Information about an assembly is controlled through the following
|
||||||
// set of attributes. Change these attribute values to modify the information
|
// set of attributes. Change these attribute values to modify the information
|
||||||
|
@ -11,7 +9,7 @@ using Android.App;
|
||||||
[assembly: AssemblyConfiguration("")]
|
[assembly: AssemblyConfiguration("")]
|
||||||
[assembly: AssemblyCompany("8bit Solutions LLC")]
|
[assembly: AssemblyCompany("8bit Solutions LLC")]
|
||||||
[assembly: AssemblyProduct("Bitwarden")]
|
[assembly: AssemblyProduct("Bitwarden")]
|
||||||
[assembly: AssemblyCopyright("Copyright © 2016")]
|
[assembly: AssemblyCopyright("Copyright © 2016")]
|
||||||
[assembly: AssemblyTrademark("")]
|
[assembly: AssemblyTrademark("")]
|
||||||
[assembly: AssemblyCulture("")]
|
[assembly: AssemblyCulture("")]
|
||||||
[assembly: ComVisible(false)]
|
[assembly: ComVisible(false)]
|
||||||
|
|
25
src/Android/Push/FirebaseInstanceIdService.cs
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
#if !FDROID
|
||||||
|
using Android.App;
|
||||||
|
using Android.Content;
|
||||||
|
using Bit.App.Abstractions;
|
||||||
|
using Bit.Core;
|
||||||
|
using Bit.Core.Abstractions;
|
||||||
|
using Bit.Core.Utilities;
|
||||||
|
using Firebase.Iid;
|
||||||
|
|
||||||
|
namespace Bit.Droid.Push
|
||||||
|
{
|
||||||
|
[Service]
|
||||||
|
[IntentFilter(new[] { "com.google.firebase.INSTANCE_ID_EVENT" })]
|
||||||
|
public class FirebaseInstanceIdService : Firebase.Iid.FirebaseInstanceIdService
|
||||||
|
{
|
||||||
|
public override void OnTokenRefresh()
|
||||||
|
{
|
||||||
|
var storageService = ServiceContainer.Resolve<IStorageService>("storageService");
|
||||||
|
var pushNotificationService = ServiceContainer.Resolve<IPushNotificationService>("pushNotificationService");
|
||||||
|
storageService.SaveAsync(Constants.PushRegisteredTokenKey, FirebaseInstanceId.Instance.Token);
|
||||||
|
pushNotificationService.RegisterAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
|
@ -1,15 +1,14 @@
|
||||||
#if !FDROID
|
#if !FDROID
|
||||||
using System;
|
|
||||||
using Android.App;
|
using Android.App;
|
||||||
using Android.Content;
|
using Android.Content;
|
||||||
using Bit.App.Abstractions;
|
using Bit.App.Abstractions;
|
||||||
|
using Bit.Core.Utilities;
|
||||||
using Firebase.Messaging;
|
using Firebase.Messaging;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
using Xamarin.Forms;
|
using Xamarin.Forms;
|
||||||
using XLabs.Ioc;
|
|
||||||
|
|
||||||
namespace Bit.Android
|
namespace Bit.Droid.Push
|
||||||
{
|
{
|
||||||
[Service]
|
[Service]
|
||||||
[IntentFilter(new[] { "com.google.firebase.MESSAGING_EVENT" })]
|
[IntentFilter(new[] { "com.google.firebase.MESSAGING_EVENT" })]
|
||||||
|
@ -21,18 +20,17 @@ namespace Bit.Android
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var data = message.Data.ContainsKey("data") ? message.Data["data"] : null;
|
var data = message.Data.ContainsKey("data") ? message.Data["data"] : null;
|
||||||
if(data == null)
|
if(data == null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var obj = JObject.Parse(data);
|
var obj = JObject.Parse(data);
|
||||||
var listener = Resolver.Resolve<IPushNotificationListener>();
|
var listener = ServiceContainer.Resolve<IPushNotificationListenerService>(
|
||||||
listener.OnMessage(obj, Device.Android);
|
"pushNotificationListenerService");
|
||||||
|
listener.OnMessageAsync(obj, Device.Android);
|
||||||
}
|
}
|
||||||
catch(JsonReaderException ex)
|
catch(JsonReaderException ex)
|
||||||
{
|
{
|
17
src/Android/Receivers/LockAlarmReceiver.cs
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
using Android.Content;
|
||||||
|
using Bit.Core.Abstractions;
|
||||||
|
using Bit.Core.Utilities;
|
||||||
|
|
||||||
|
namespace Bit.Droid.Receivers
|
||||||
|
{
|
||||||
|
[BroadcastReceiver(Name = "com.x8bit.bitwarden.LockAlarmReceiver", Exported = false)]
|
||||||
|
public class LockAlarmReceiver : BroadcastReceiver
|
||||||
|
{
|
||||||
|
public async override void OnReceive(Context context, Intent intent)
|
||||||
|
{
|
||||||
|
System.Diagnostics.Debug.WriteLine("LockAlarmReceiver OnReceive");
|
||||||
|
var lockService = ServiceContainer.Resolve<ILockService>("lockService");
|
||||||
|
await lockService.CheckLockAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
16
src/Android/Receivers/PackageReplacedReceiver.cs
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
using System;
|
||||||
|
using Android.App;
|
||||||
|
using Android.Content;
|
||||||
|
|
||||||
|
namespace Bit.Droid.Receivers
|
||||||
|
{
|
||||||
|
[BroadcastReceiver(Name = "com.x8bit.bitwarden.PackageReplacedReceiver", Exported = false)]
|
||||||
|
[IntentFilter(new[] { Intent.ActionMyPackageReplaced })]
|
||||||
|
public class PackageReplacedReceiver : BroadcastReceiver
|
||||||
|
{
|
||||||
|
public override void OnReceive(Context context, Intent intent)
|
||||||
|
{
|
||||||
|
System.Diagnostics.Debug.WriteLine("PackageReplacedReceiver OnReceive");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
526
src/Android/Renderers/BoxedView/BoxedViewRecyclerAdapter.cs
Normal file
|
@ -0,0 +1,526 @@
|
||||||
|
using Android.Content;
|
||||||
|
using Android.Runtime;
|
||||||
|
using Android.Support.V7.Widget;
|
||||||
|
using Android.Text;
|
||||||
|
using Android.Views;
|
||||||
|
using Android.Widget;
|
||||||
|
using Bit.App.Controls.BoxedView;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Xamarin.Forms;
|
||||||
|
using Xamarin.Forms.Platform.Android;
|
||||||
|
using AView = Android.Views.View;
|
||||||
|
|
||||||
|
namespace Bit.Droid.Renderers.BoxedView
|
||||||
|
{
|
||||||
|
[Preserve(AllMembers = true)]
|
||||||
|
public class BoxedViewRecyclerAdapter : RecyclerView.Adapter, AView.IOnClickListener
|
||||||
|
{
|
||||||
|
private const int ViewTypeHeader = 0;
|
||||||
|
private const int ViewTypeFooter = 1;
|
||||||
|
|
||||||
|
private Dictionary<Type, int> _viewTypes;
|
||||||
|
private List<CellCache> _cellCaches;
|
||||||
|
|
||||||
|
internal List<CellCache> CellCaches
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if(_cellCaches == null)
|
||||||
|
{
|
||||||
|
FillCache();
|
||||||
|
}
|
||||||
|
return _cellCaches;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Item click. correspond to AdapterView.IOnItemClickListener
|
||||||
|
private int _selectedIndex = -1;
|
||||||
|
private AView _preSelectedCell = null;
|
||||||
|
|
||||||
|
Context _context;
|
||||||
|
App.Controls.BoxedView.BoxedView _boxedView;
|
||||||
|
RecyclerView _recyclerView;
|
||||||
|
|
||||||
|
List<ViewHolder> _viewHolders = new List<ViewHolder>();
|
||||||
|
|
||||||
|
public BoxedViewRecyclerAdapter(Context context, App.Controls.BoxedView.BoxedView boxedView,
|
||||||
|
RecyclerView recyclerView)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
_boxedView = boxedView;
|
||||||
|
_recyclerView = recyclerView;
|
||||||
|
_boxedView.ModelChanged += BoxedView_ModelChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
private float MinRowHeight => _context.ToPixels(44);
|
||||||
|
|
||||||
|
public override int ItemCount => CellCaches.Count;
|
||||||
|
|
||||||
|
public override long GetItemId(int position)
|
||||||
|
{
|
||||||
|
return position;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override RecyclerView.ViewHolder OnCreateViewHolder(ViewGroup parent, int viewType)
|
||||||
|
{
|
||||||
|
ViewHolder viewHolder;
|
||||||
|
switch(viewType)
|
||||||
|
{
|
||||||
|
case ViewTypeHeader:
|
||||||
|
viewHolder = new HeaderViewHolder(
|
||||||
|
LayoutInflater.FromContext(_context).Inflate(Resource.Layout.HeaderCell, parent, false),
|
||||||
|
_boxedView);
|
||||||
|
break;
|
||||||
|
case ViewTypeFooter:
|
||||||
|
viewHolder = new FooterViewHolder(
|
||||||
|
LayoutInflater.FromContext(_context).Inflate(Resource.Layout.FooterCell, parent, false),
|
||||||
|
_boxedView);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
viewHolder = new ContentViewHolder(
|
||||||
|
LayoutInflater.FromContext(_context).Inflate(Resource.Layout.ContentCell, parent, false));
|
||||||
|
viewHolder.ItemView.SetOnClickListener(this);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
_viewHolders.Add(viewHolder);
|
||||||
|
return viewHolder;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnClick(AView view)
|
||||||
|
{
|
||||||
|
var position = _recyclerView.GetChildAdapterPosition(view);
|
||||||
|
|
||||||
|
// TODO: It is desirable that the forms side has Selected property and reflects it.
|
||||||
|
// But do it at a later as iOS side doesn't have that process.
|
||||||
|
DeselectRow();
|
||||||
|
|
||||||
|
var cell = view.FindViewById<LinearLayout>(Resource.Id.ContentCellBody).GetChildAt(0) as BaseCellView;
|
||||||
|
if(cell == null || !CellCaches[position].Cell.IsEnabled)
|
||||||
|
{
|
||||||
|
// If FormsCell IsEnable is false, does nothing.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_boxedView.Model.RowSelected(CellCaches[position].Cell);
|
||||||
|
cell.RowSelected(this, position);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnBindViewHolder(RecyclerView.ViewHolder holder, int position)
|
||||||
|
{
|
||||||
|
var cellInfo = CellCaches[position];
|
||||||
|
switch(holder.ItemViewType)
|
||||||
|
{
|
||||||
|
case ViewTypeHeader:
|
||||||
|
BindHeaderView((HeaderViewHolder)holder, (TextCell)cellInfo.Cell);
|
||||||
|
break;
|
||||||
|
case ViewTypeFooter:
|
||||||
|
BindFooterView((FooterViewHolder)holder, (TextCell)cellInfo.Cell);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
BindContentView((ContentViewHolder)holder, cellInfo.Cell, position);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetItemViewType(int position)
|
||||||
|
{
|
||||||
|
var cellInfo = CellCaches[position];
|
||||||
|
if(cellInfo.IsHeader)
|
||||||
|
{
|
||||||
|
return ViewTypeHeader;
|
||||||
|
}
|
||||||
|
else if(cellInfo.IsFooter)
|
||||||
|
{
|
||||||
|
return ViewTypeFooter;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return _viewTypes[cellInfo.Cell.GetType()];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DeselectRow()
|
||||||
|
{
|
||||||
|
if(_preSelectedCell != null)
|
||||||
|
{
|
||||||
|
_preSelectedCell.Selected = false;
|
||||||
|
_preSelectedCell = null;
|
||||||
|
}
|
||||||
|
_selectedIndex = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SelectedRow(AView cell, int position)
|
||||||
|
{
|
||||||
|
_preSelectedCell = cell;
|
||||||
|
_selectedIndex = position;
|
||||||
|
cell.Selected = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if(disposing)
|
||||||
|
{
|
||||||
|
_boxedView.ModelChanged -= BoxedView_ModelChanged;
|
||||||
|
_cellCaches?.Clear();
|
||||||
|
_cellCaches = null;
|
||||||
|
_boxedView = null;
|
||||||
|
_viewTypes = null;
|
||||||
|
foreach(var holder in _viewHolders)
|
||||||
|
{
|
||||||
|
holder.Dispose();
|
||||||
|
}
|
||||||
|
_viewHolders.Clear();
|
||||||
|
_viewHolders = null;
|
||||||
|
}
|
||||||
|
base.Dispose(disposing);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BoxedView_ModelChanged(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if(_recyclerView != null)
|
||||||
|
{
|
||||||
|
_cellCaches = null;
|
||||||
|
NotifyDataSetChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BindHeaderView(HeaderViewHolder holder, TextCell formsCell)
|
||||||
|
{
|
||||||
|
var view = holder.ItemView;
|
||||||
|
|
||||||
|
// Judging cell height
|
||||||
|
int cellHeight = (int)_context.ToPixels(44);
|
||||||
|
var individualHeight = formsCell.Height;
|
||||||
|
|
||||||
|
if(individualHeight > 0d)
|
||||||
|
{
|
||||||
|
cellHeight = (int)_context.ToPixels(individualHeight);
|
||||||
|
}
|
||||||
|
else if(_boxedView.HeaderHeight > -1)
|
||||||
|
{
|
||||||
|
cellHeight = (int)_context.ToPixels(_boxedView.HeaderHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
view.SetMinimumHeight(cellHeight);
|
||||||
|
view.LayoutParameters.Height = cellHeight;
|
||||||
|
|
||||||
|
holder.TextView.SetPadding(
|
||||||
|
(int)view.Context.ToPixels(_boxedView.HeaderPadding.Left),
|
||||||
|
(int)view.Context.ToPixels(_boxedView.HeaderPadding.Top),
|
||||||
|
(int)view.Context.ToPixels(_boxedView.HeaderPadding.Right),
|
||||||
|
(int)view.Context.ToPixels(_boxedView.HeaderPadding.Bottom));
|
||||||
|
|
||||||
|
holder.TextView.Gravity = _boxedView.HeaderTextVerticalAlign.ToAndroidVertical() | GravityFlags.Left;
|
||||||
|
holder.TextView.TextAlignment = Android.Views.TextAlignment.Gravity;
|
||||||
|
holder.TextView.SetTextSize(Android.Util.ComplexUnitType.Sp, (float)_boxedView.HeaderFontSize);
|
||||||
|
holder.TextView.SetBackgroundColor(_boxedView.HeaderBackgroundColor.ToAndroid());
|
||||||
|
holder.TextView.SetMaxLines(1);
|
||||||
|
holder.TextView.SetMinLines(1);
|
||||||
|
holder.TextView.SetTypeface(null, Android.Graphics.TypefaceStyle.Bold);
|
||||||
|
holder.TextView.Ellipsize = TextUtils.TruncateAt.End;
|
||||||
|
|
||||||
|
if(_boxedView.HeaderTextColor != Color.Default)
|
||||||
|
{
|
||||||
|
holder.TextView.SetTextColor(_boxedView.HeaderTextColor.ToAndroid());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Border setting
|
||||||
|
if(_boxedView.ShowSectionTopBottomBorder)
|
||||||
|
{
|
||||||
|
holder.Border.SetBackgroundColor(_boxedView.SeparatorColor.ToAndroid());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
holder.Border.SetBackgroundColor(Android.Graphics.Color.Transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update text
|
||||||
|
holder.TextView.Text = formsCell.Text;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BindFooterView(FooterViewHolder holder, TextCell formsCell)
|
||||||
|
{
|
||||||
|
var view = holder.ItemView;
|
||||||
|
|
||||||
|
// Footer visible setting
|
||||||
|
if(string.IsNullOrEmpty(formsCell.Text))
|
||||||
|
{
|
||||||
|
//if text is empty, hidden (height 0)
|
||||||
|
holder.TextView.Visibility = ViewStates.Gone;
|
||||||
|
view.Visibility = ViewStates.Gone;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
holder.TextView.Visibility = ViewStates.Visible;
|
||||||
|
view.Visibility = ViewStates.Visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
holder.TextView.SetPadding(
|
||||||
|
(int)view.Context.ToPixels(_boxedView.FooterPadding.Left),
|
||||||
|
(int)view.Context.ToPixels(_boxedView.FooterPadding.Top),
|
||||||
|
(int)view.Context.ToPixels(_boxedView.FooterPadding.Right),
|
||||||
|
(int)view.Context.ToPixels(_boxedView.FooterPadding.Bottom));
|
||||||
|
|
||||||
|
holder.TextView.SetTextSize(Android.Util.ComplexUnitType.Sp, (float)_boxedView.FooterFontSize);
|
||||||
|
holder.TextView.SetBackgroundColor(_boxedView.FooterBackgroundColor.ToAndroid());
|
||||||
|
if(_boxedView.FooterTextColor != Color.Default)
|
||||||
|
{
|
||||||
|
holder.TextView.SetTextColor(_boxedView.FooterTextColor.ToAndroid());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update text
|
||||||
|
holder.TextView.Text = formsCell.Text;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BindContentView(ContentViewHolder holder, Cell formsCell, int position)
|
||||||
|
{
|
||||||
|
AView nativeCell = null;
|
||||||
|
AView layout = holder.ItemView;
|
||||||
|
|
||||||
|
holder.SectionIndex = CellCaches[position].SectionIndex;
|
||||||
|
holder.RowIndex = CellCaches[position].RowIndex;
|
||||||
|
|
||||||
|
nativeCell = holder.Body.GetChildAt(0);
|
||||||
|
if(nativeCell != null)
|
||||||
|
{
|
||||||
|
holder.Body.RemoveViewAt(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
nativeCell = CellFactory.GetCell(formsCell, nativeCell, _recyclerView, _context, _boxedView);
|
||||||
|
|
||||||
|
if(position == _selectedIndex)
|
||||||
|
{
|
||||||
|
DeselectRow();
|
||||||
|
nativeCell.Selected = true;
|
||||||
|
_preSelectedCell = nativeCell;
|
||||||
|
}
|
||||||
|
|
||||||
|
var minHeight = (int)Math.Max(_context.ToPixels(_boxedView.RowHeight), MinRowHeight);
|
||||||
|
|
||||||
|
// It is necessary to set both
|
||||||
|
layout.SetMinimumHeight(minHeight);
|
||||||
|
nativeCell.SetMinimumHeight(minHeight);
|
||||||
|
|
||||||
|
if(!_boxedView.HasUnevenRows)
|
||||||
|
{
|
||||||
|
// If not Uneven, set the larger one of RowHeight and MinRowHeight.
|
||||||
|
layout.LayoutParameters.Height = minHeight;
|
||||||
|
}
|
||||||
|
else if(formsCell.Height > -1)
|
||||||
|
{
|
||||||
|
// If the cell itself was specified height, set it.
|
||||||
|
layout.SetMinimumHeight((int)_context.ToPixels(formsCell.Height));
|
||||||
|
layout.LayoutParameters.Height = (int)_context.ToPixels(formsCell.Height);
|
||||||
|
}
|
||||||
|
else if(formsCell is ViewCell viewCell)
|
||||||
|
{
|
||||||
|
// If used a viewcell, calculate the size and layout it.
|
||||||
|
var size = viewCell.View.Measure(_boxedView.Width, double.PositiveInfinity);
|
||||||
|
viewCell.View.Layout(new Rectangle(0, 0, size.Request.Width, size.Request.Height));
|
||||||
|
layout.LayoutParameters.Height = (int)_context.ToPixels(size.Request.Height);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
layout.LayoutParameters.Height = -2; // wrap_content
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!CellCaches[position].IsLastCell || _boxedView.ShowSectionTopBottomBorder)
|
||||||
|
{
|
||||||
|
holder.Border.SetBackgroundColor(_boxedView.SeparatorColor.ToAndroid());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
holder.Border.SetBackgroundColor(Android.Graphics.Color.Transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
holder.Body.AddView(nativeCell, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void FillCache()
|
||||||
|
{
|
||||||
|
var model = _boxedView.Model;
|
||||||
|
int sectionCount = model.GetSectionCount();
|
||||||
|
|
||||||
|
var newCellCaches = new List<CellCache>();
|
||||||
|
for(var sectionIndex = 0; sectionIndex < sectionCount; sectionIndex++)
|
||||||
|
{
|
||||||
|
var sectionTitle = model.GetSectionTitle(sectionIndex);
|
||||||
|
var sectionRowCount = model.GetRowCount(sectionIndex);
|
||||||
|
|
||||||
|
Cell headerCell = new TextCell { Text = sectionTitle, Height = model.GetHeaderHeight(sectionIndex) };
|
||||||
|
headerCell.Parent = _boxedView;
|
||||||
|
|
||||||
|
newCellCaches.Add(new CellCache
|
||||||
|
{
|
||||||
|
Cell = headerCell,
|
||||||
|
IsHeader = true,
|
||||||
|
SectionIndex = sectionIndex,
|
||||||
|
});
|
||||||
|
|
||||||
|
for(int i = 0; i < sectionRowCount; i++)
|
||||||
|
{
|
||||||
|
newCellCaches.Add(new CellCache
|
||||||
|
{
|
||||||
|
Cell = model.GetCell(sectionIndex, i),
|
||||||
|
IsLastCell = i == sectionRowCount - 1,
|
||||||
|
SectionIndex = sectionIndex,
|
||||||
|
RowIndex = i
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var footerCell = new TextCell { Text = model.GetFooterText(sectionIndex) };
|
||||||
|
footerCell.Parent = _boxedView;
|
||||||
|
|
||||||
|
newCellCaches.Add(new CellCache
|
||||||
|
{
|
||||||
|
Cell = footerCell,
|
||||||
|
IsFooter = true,
|
||||||
|
SectionIndex = sectionIndex,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_cellCaches = newCellCaches;
|
||||||
|
|
||||||
|
if(_viewTypes == null)
|
||||||
|
{
|
||||||
|
_viewTypes = _cellCaches
|
||||||
|
.Select(x => x.Cell.GetType())
|
||||||
|
.Distinct()
|
||||||
|
.Select((x, idx) => new { x, index = idx })
|
||||||
|
.ToDictionary(key => key.x, val => val.index + 2);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var idx = _viewTypes.Values.Max() + 1;
|
||||||
|
foreach(var t in _cellCaches.Select(x => x.Cell.GetType()).Distinct().Except(_viewTypes.Keys).ToList())
|
||||||
|
{
|
||||||
|
_viewTypes.Add(t, idx++);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CellMoved(int fromPos, int toPos)
|
||||||
|
{
|
||||||
|
var tmp = CellCaches[fromPos];
|
||||||
|
CellCaches.RemoveAt(fromPos);
|
||||||
|
CellCaches.Insert(toPos, tmp);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Preserve(AllMembers = true)]
|
||||||
|
internal class CellCache
|
||||||
|
{
|
||||||
|
public Cell Cell { get; set; }
|
||||||
|
public bool IsHeader { get; set; } = false;
|
||||||
|
public bool IsFooter { get; set; } = false;
|
||||||
|
public bool IsLastCell { get; set; } = false;
|
||||||
|
public int SectionIndex { get; set; }
|
||||||
|
public int RowIndex { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Preserve(AllMembers = true)]
|
||||||
|
internal class ViewHolder : RecyclerView.ViewHolder
|
||||||
|
{
|
||||||
|
public ViewHolder(AView view)
|
||||||
|
: base(view) { }
|
||||||
|
|
||||||
|
protected override void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if(disposing)
|
||||||
|
{
|
||||||
|
ItemView?.Dispose();
|
||||||
|
ItemView = null;
|
||||||
|
}
|
||||||
|
base.Dispose(disposing);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Preserve(AllMembers = true)]
|
||||||
|
internal class HeaderViewHolder : ViewHolder
|
||||||
|
{
|
||||||
|
public HeaderViewHolder(AView view, App.Controls.BoxedView.BoxedView boxedView)
|
||||||
|
: base(view)
|
||||||
|
{
|
||||||
|
TextView = view.FindViewById<TextView>(Resource.Id.HeaderCellText);
|
||||||
|
Border = view.FindViewById<LinearLayout>(Resource.Id.HeaderCellBorder);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TextView TextView { get; private set; }
|
||||||
|
public LinearLayout Border { get; private set; }
|
||||||
|
|
||||||
|
protected override void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if(disposing)
|
||||||
|
{
|
||||||
|
TextView?.Dispose();
|
||||||
|
TextView = null;
|
||||||
|
Border?.Dispose();
|
||||||
|
Border = null;
|
||||||
|
}
|
||||||
|
base.Dispose(disposing);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Preserve(AllMembers = true)]
|
||||||
|
internal class FooterViewHolder : ViewHolder
|
||||||
|
{
|
||||||
|
public TextView TextView { get; private set; }
|
||||||
|
|
||||||
|
public FooterViewHolder(AView view, App.Controls.BoxedView.BoxedView boxedView)
|
||||||
|
: base(view)
|
||||||
|
{
|
||||||
|
TextView = view.FindViewById<TextView>(Resource.Id.FooterCellText);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if(disposing)
|
||||||
|
{
|
||||||
|
TextView?.Dispose();
|
||||||
|
TextView = null;
|
||||||
|
}
|
||||||
|
base.Dispose(disposing);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Preserve(AllMembers = true)]
|
||||||
|
internal class ContentViewHolder : ViewHolder
|
||||||
|
{
|
||||||
|
public LinearLayout Body { get; private set; }
|
||||||
|
public AView Border { get; private set; }
|
||||||
|
public int SectionIndex { get; set; }
|
||||||
|
public int RowIndex { get; set; }
|
||||||
|
|
||||||
|
public ContentViewHolder(AView view)
|
||||||
|
: base(view)
|
||||||
|
{
|
||||||
|
Body = view.FindViewById<LinearLayout>(Resource.Id.ContentCellBody);
|
||||||
|
Border = view.FindViewById(Resource.Id.ContentCellBorder);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if(disposing)
|
||||||
|
{
|
||||||
|
var nativeCell = Body.GetChildAt(0);
|
||||||
|
if(nativeCell is INativeElementView nativeElementView)
|
||||||
|
{
|
||||||
|
// If a ViewCell is used, it stops the ViewCellContainer from executing the dispose method.
|
||||||
|
// Because if the AiForms.Effects is used and a ViewCellContainer is disposed, it crashes.
|
||||||
|
if(!(nativeElementView.Element is ViewCell))
|
||||||
|
{
|
||||||
|
nativeCell?.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Border?.Dispose();
|
||||||
|
Border = null;
|
||||||
|
Body?.Dispose();
|
||||||
|
Body = null;
|
||||||
|
ItemView.SetOnClickListener(null);
|
||||||
|
}
|
||||||
|
base.Dispose(disposing);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
196
src/Android/Renderers/BoxedView/BoxedViewRenderer.cs
Normal file
|
@ -0,0 +1,196 @@
|
||||||
|
using Android.Content;
|
||||||
|
using Android.Runtime;
|
||||||
|
using Android.Support.V7.Widget;
|
||||||
|
using Android.Support.V7.Widget.Helper;
|
||||||
|
using Android.Views;
|
||||||
|
using Bit.App.Controls.BoxedView;
|
||||||
|
using Bit.Droid.Renderers.BoxedView;
|
||||||
|
using System;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using Xamarin.Forms;
|
||||||
|
using Xamarin.Forms.Platform.Android;
|
||||||
|
|
||||||
|
[assembly: ExportRenderer(typeof(BoxedView), typeof(BoxedViewRenderer))]
|
||||||
|
namespace Bit.Droid.Renderers.BoxedView
|
||||||
|
{
|
||||||
|
[Preserve(AllMembers = true)]
|
||||||
|
public class BoxedViewRenderer : ViewRenderer<App.Controls.BoxedView.BoxedView, RecyclerView>
|
||||||
|
{
|
||||||
|
private Page _parentPage;
|
||||||
|
private LinearLayoutManager _layoutManager;
|
||||||
|
private ItemTouchHelper _itemTouchhelper;
|
||||||
|
private BoxedViewRecyclerAdapter _adapter;
|
||||||
|
private BoxedViewSimpleCallback _simpleCallback;
|
||||||
|
|
||||||
|
public BoxedViewRenderer(Context context)
|
||||||
|
: base(context)
|
||||||
|
{
|
||||||
|
AutoPackage = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnElementChanged(ElementChangedEventArgs<App.Controls.BoxedView.BoxedView> e)
|
||||||
|
{
|
||||||
|
base.OnElementChanged(e);
|
||||||
|
if(e.NewElement != null)
|
||||||
|
{
|
||||||
|
var recyclerView = new RecyclerView(Context);
|
||||||
|
_layoutManager = new LinearLayoutManager(Context);
|
||||||
|
recyclerView.SetLayoutManager(_layoutManager);
|
||||||
|
|
||||||
|
SetNativeControl(recyclerView);
|
||||||
|
|
||||||
|
Control.Focusable = false;
|
||||||
|
Control.DescendantFocusability = DescendantFocusability.AfterDescendants;
|
||||||
|
|
||||||
|
UpdateBackgroundColor();
|
||||||
|
UpdateRowHeight();
|
||||||
|
|
||||||
|
_adapter = new BoxedViewRecyclerAdapter(Context, e.NewElement, recyclerView);
|
||||||
|
Control.SetAdapter(_adapter);
|
||||||
|
|
||||||
|
_simpleCallback = new BoxedViewSimpleCallback(
|
||||||
|
e.NewElement, ItemTouchHelper.Up | ItemTouchHelper.Down, 0);
|
||||||
|
_itemTouchhelper = new ItemTouchHelper(_simpleCallback);
|
||||||
|
_itemTouchhelper.AttachToRecyclerView(Control);
|
||||||
|
|
||||||
|
Element elm = Element;
|
||||||
|
while(elm != null)
|
||||||
|
{
|
||||||
|
elm = elm.Parent;
|
||||||
|
if(elm is Page)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_parentPage = elm as Page;
|
||||||
|
_parentPage.Appearing += ParentPageAppearing;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ParentPageAppearing(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
Device.BeginInvokeOnMainThread(() => _adapter.DeselectRow());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnLayout(bool changed, int left, int top, int right, int bottom)
|
||||||
|
{
|
||||||
|
base.OnLayout(changed, left, top, right, bottom);
|
||||||
|
if(!changed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var startPos = _layoutManager.FindFirstCompletelyVisibleItemPosition();
|
||||||
|
var endPos = _layoutManager.FindLastCompletelyVisibleItemPosition();
|
||||||
|
|
||||||
|
var totalH = 0;
|
||||||
|
for(var i = startPos; i <= endPos; i++)
|
||||||
|
{
|
||||||
|
var child = _layoutManager.GetChildAt(i);
|
||||||
|
if(child == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
totalH += _layoutManager.GetChildAt(i).Height;
|
||||||
|
}
|
||||||
|
Element.VisibleContentHeight = Context.FromPixels(Math.Min(totalH, Control.Height));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
|
||||||
|
{
|
||||||
|
base.OnElementPropertyChanged(sender, e);
|
||||||
|
if(e.PropertyName == App.Controls.BoxedView.BoxedView.SeparatorColorProperty.PropertyName)
|
||||||
|
{
|
||||||
|
_adapter.NotifyDataSetChanged();
|
||||||
|
}
|
||||||
|
else if(e.PropertyName == App.Controls.BoxedView.BoxedView.BackgroundColorProperty.PropertyName)
|
||||||
|
{
|
||||||
|
UpdateBackgroundColor();
|
||||||
|
}
|
||||||
|
else if(e.PropertyName == TableView.RowHeightProperty.PropertyName)
|
||||||
|
{
|
||||||
|
UpdateRowHeight();
|
||||||
|
}
|
||||||
|
else if(e.PropertyName == App.Controls.BoxedView.BoxedView.SelectedColorProperty.PropertyName)
|
||||||
|
{
|
||||||
|
//_adapter.NotifyDataSetChanged();
|
||||||
|
}
|
||||||
|
else if(e.PropertyName == App.Controls.BoxedView.BoxedView.ShowSectionTopBottomBorderProperty.PropertyName)
|
||||||
|
{
|
||||||
|
_adapter.NotifyDataSetChanged();
|
||||||
|
}
|
||||||
|
else if(e.PropertyName == TableView.HasUnevenRowsProperty.PropertyName)
|
||||||
|
{
|
||||||
|
_adapter.NotifyDataSetChanged();
|
||||||
|
}
|
||||||
|
else if(e.PropertyName == App.Controls.BoxedView.BoxedView.ScrollToTopProperty.PropertyName)
|
||||||
|
{
|
||||||
|
UpdateScrollToTop();
|
||||||
|
}
|
||||||
|
else if(e.PropertyName == App.Controls.BoxedView.BoxedView.ScrollToBottomProperty.PropertyName)
|
||||||
|
{
|
||||||
|
UpdateScrollToBottom();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateRowHeight()
|
||||||
|
{
|
||||||
|
if(Element.RowHeight == -1)
|
||||||
|
{
|
||||||
|
Element.RowHeight = 60;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_adapter?.NotifyDataSetChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateScrollToTop()
|
||||||
|
{
|
||||||
|
if(Element.ScrollToTop)
|
||||||
|
{
|
||||||
|
_layoutManager.ScrollToPosition(0);
|
||||||
|
Element.ScrollToTop = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateScrollToBottom()
|
||||||
|
{
|
||||||
|
if(Element.ScrollToBottom)
|
||||||
|
{
|
||||||
|
if(_adapter != null)
|
||||||
|
{
|
||||||
|
_layoutManager.ScrollToPosition(_adapter.ItemCount - 1);
|
||||||
|
}
|
||||||
|
Element.ScrollToBottom = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected new void UpdateBackgroundColor()
|
||||||
|
{
|
||||||
|
if(Element.BackgroundColor != Color.Default)
|
||||||
|
{
|
||||||
|
Control.SetBackgroundColor(Element.BackgroundColor.ToAndroid());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if(disposing)
|
||||||
|
{
|
||||||
|
_parentPage.Appearing -= ParentPageAppearing;
|
||||||
|
_adapter?.Dispose();
|
||||||
|
_adapter = null;
|
||||||
|
_layoutManager?.Dispose();
|
||||||
|
_layoutManager = null;
|
||||||
|
_simpleCallback?.Dispose();
|
||||||
|
_simpleCallback = null;
|
||||||
|
_itemTouchhelper?.Dispose();
|
||||||
|
_itemTouchhelper = null;
|
||||||
|
}
|
||||||
|
base.Dispose(disposing);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
104
src/Android/Renderers/BoxedView/BoxedViewSimpleCallback.cs
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
using Android.Runtime;
|
||||||
|
using Android.Support.V7.Widget;
|
||||||
|
using Android.Support.V7.Widget.Helper;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Bit.Droid.Renderers.BoxedView
|
||||||
|
{
|
||||||
|
[Preserve(AllMembers = true)]
|
||||||
|
public class BoxedViewSimpleCallback : ItemTouchHelper.SimpleCallback
|
||||||
|
{
|
||||||
|
private App.Controls.BoxedView.BoxedView _boxedView;
|
||||||
|
private int _offset = 0;
|
||||||
|
|
||||||
|
public BoxedViewSimpleCallback(App.Controls.BoxedView.BoxedView boxedView, int dragDirs, int swipeDirs)
|
||||||
|
: base(dragDirs, swipeDirs)
|
||||||
|
{
|
||||||
|
_boxedView = boxedView;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool OnMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder,
|
||||||
|
RecyclerView.ViewHolder target)
|
||||||
|
{
|
||||||
|
if(!(viewHolder is ContentViewHolder fromContentHolder))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if(!(target is ContentViewHolder toContentHolder))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if(fromContentHolder.SectionIndex != toContentHolder.SectionIndex)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
var section = _boxedView.Model.GetSection(fromContentHolder.SectionIndex);
|
||||||
|
if(section == null || !section.UseDragSort)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var fromPos = viewHolder.AdapterPosition;
|
||||||
|
var toPos = target.AdapterPosition;
|
||||||
|
_offset += toPos - fromPos;
|
||||||
|
var settingsAdapter = recyclerView.GetAdapter() as BoxedViewRecyclerAdapter;
|
||||||
|
settingsAdapter.NotifyItemMoved(fromPos, toPos); // rows update
|
||||||
|
settingsAdapter.CellMoved(fromPos, toPos); // caches update
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void ClearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder)
|
||||||
|
{
|
||||||
|
base.ClearView(recyclerView, viewHolder);
|
||||||
|
if(!(viewHolder is ContentViewHolder contentHolder))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var section = _boxedView.Model.GetSection(contentHolder.SectionIndex);
|
||||||
|
var pos = contentHolder.RowIndex;
|
||||||
|
if(section.ItemsSource == null)
|
||||||
|
{
|
||||||
|
var tmp = section[pos];
|
||||||
|
section.RemoveAt(pos);
|
||||||
|
section.Insert(pos + _offset, tmp);
|
||||||
|
}
|
||||||
|
else if(section.ItemsSource != null)
|
||||||
|
{
|
||||||
|
// must update DataSource at this timing.
|
||||||
|
var tmp = section.ItemsSource[pos];
|
||||||
|
section.ItemsSource.RemoveAt(pos);
|
||||||
|
section.ItemsSource.Insert(pos + _offset, tmp);
|
||||||
|
}
|
||||||
|
_offset = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetDragDirs(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder)
|
||||||
|
{
|
||||||
|
if(!(viewHolder is ContentViewHolder contentHolder))
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
var section = _boxedView.Model.GetSection(contentHolder.SectionIndex);
|
||||||
|
if(section == null || !section.UseDragSort)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return base.GetDragDirs(recyclerView, viewHolder);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnSwiped(RecyclerView.ViewHolder viewHolder, int direction)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if(disposing)
|
||||||
|
{
|
||||||
|
_boxedView = null;
|
||||||
|
}
|
||||||
|
base.Dispose(disposing);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
75
src/Android/Renderers/BoxedView/Cells/BaseCellRenderer.cs
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
using Android.Content;
|
||||||
|
using Android.Runtime;
|
||||||
|
using Android.Views;
|
||||||
|
using Bit.App.Controls.BoxedView;
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Linq.Expressions;
|
||||||
|
using System.Reflection;
|
||||||
|
using Xamarin.Forms.Platform.Android;
|
||||||
|
|
||||||
|
namespace Bit.Droid.Renderers.BoxedView
|
||||||
|
{
|
||||||
|
[Preserve(AllMembers = true)]
|
||||||
|
public class BaseCellRenderer<TNativeCell> : CellRenderer where TNativeCell : BaseCellView
|
||||||
|
{
|
||||||
|
protected override View GetCellCore(Xamarin.Forms.Cell item, View convertView, ViewGroup parent,
|
||||||
|
Context context)
|
||||||
|
{
|
||||||
|
if(!(convertView is TNativeCell nativeCell))
|
||||||
|
{
|
||||||
|
nativeCell = InstanceCreator<Context, Xamarin.Forms.Cell, TNativeCell>.Create(context, item);
|
||||||
|
}
|
||||||
|
ClearPropertyChanged(nativeCell);
|
||||||
|
nativeCell.Cell = item;
|
||||||
|
SetUpPropertyChanged(nativeCell);
|
||||||
|
nativeCell.UpdateCell();
|
||||||
|
return nativeCell;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void SetUpPropertyChanged(BaseCellView nativeCell)
|
||||||
|
{
|
||||||
|
var formsCell = nativeCell.Cell as BaseCell;
|
||||||
|
formsCell.PropertyChanged += nativeCell.CellPropertyChanged;
|
||||||
|
if(formsCell.Parent is App.Controls.BoxedView.BoxedView parentElement)
|
||||||
|
{
|
||||||
|
parentElement.PropertyChanged += nativeCell.ParentPropertyChanged;
|
||||||
|
var section = parentElement.Model.GetSection(BoxedModel.GetPath(formsCell).Item1);
|
||||||
|
if(section != null)
|
||||||
|
{
|
||||||
|
formsCell.Section = section;
|
||||||
|
formsCell.Section.PropertyChanged += nativeCell.SectionPropertyChanged;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ClearPropertyChanged(BaseCellView nativeCell)
|
||||||
|
{
|
||||||
|
var formsCell = nativeCell.Cell as BaseCell;
|
||||||
|
formsCell.PropertyChanged -= nativeCell.CellPropertyChanged;
|
||||||
|
if(formsCell.Parent is App.Controls.BoxedView.BoxedView parentElement)
|
||||||
|
{
|
||||||
|
parentElement.PropertyChanged -= nativeCell.ParentPropertyChanged;
|
||||||
|
if(formsCell.Section != null)
|
||||||
|
{
|
||||||
|
formsCell.Section.PropertyChanged -= nativeCell.SectionPropertyChanged;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static class InstanceCreator<T1, T2, TInstance>
|
||||||
|
{
|
||||||
|
public static Func<T1, T2, TInstance> Create { get; } = CreateInstance();
|
||||||
|
|
||||||
|
private static Func<T1, T2, TInstance> CreateInstance()
|
||||||
|
{
|
||||||
|
var argsTypes = new[] { typeof(T1), typeof(T2) };
|
||||||
|
var constructor = typeof(TInstance).GetConstructor(
|
||||||
|
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance,
|
||||||
|
Type.DefaultBinder, argsTypes, null);
|
||||||
|
var args = argsTypes.Select(Expression.Parameter).ToArray();
|
||||||
|
return Expression.Lambda<Func<T1, T2, TInstance>>(Expression.New(constructor, args), args).Compile();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
373
src/Android/Renderers/BoxedView/Cells/BaseCellView.cs
Normal file
|
@ -0,0 +1,373 @@
|
||||||
|
using Android.Content;
|
||||||
|
using Android.Graphics.Drawables;
|
||||||
|
using Android.Runtime;
|
||||||
|
using Android.Util;
|
||||||
|
using Android.Views;
|
||||||
|
using Android.Widget;
|
||||||
|
using Bit.App.Controls.BoxedView;
|
||||||
|
using System;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Threading;
|
||||||
|
using Xamarin.Forms;
|
||||||
|
using Xamarin.Forms.Platform.Android;
|
||||||
|
using ARelativeLayout = Android.Widget.RelativeLayout;
|
||||||
|
|
||||||
|
namespace Bit.Droid.Renderers.BoxedView
|
||||||
|
{
|
||||||
|
[Preserve(AllMembers = true)]
|
||||||
|
public class BaseCellView : ARelativeLayout, INativeElementView
|
||||||
|
{
|
||||||
|
private bool _debugWithColors = false;
|
||||||
|
private CancellationTokenSource _iconTokenSource;
|
||||||
|
private Android.Graphics.Color _defaultTextColor;
|
||||||
|
private ColorDrawable _backgroundColor;
|
||||||
|
private ColorDrawable _selectedColor;
|
||||||
|
private RippleDrawable _ripple;
|
||||||
|
private float _defaultFontSize;
|
||||||
|
|
||||||
|
protected Context _Context;
|
||||||
|
|
||||||
|
public BaseCellView(Context context, Cell cell)
|
||||||
|
: base(context)
|
||||||
|
{
|
||||||
|
_Context = context;
|
||||||
|
Cell = cell;
|
||||||
|
CreateContentView();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Cell Cell { get; set; }
|
||||||
|
public Element Element => Cell;
|
||||||
|
protected BaseCell CellBase => Cell as BaseCell;
|
||||||
|
public App.Controls.BoxedView.BoxedView CellParent => Cell.Parent as App.Controls.BoxedView.BoxedView;
|
||||||
|
public TextView CellTitle { get; set; }
|
||||||
|
public LinearLayout CellTitleContent { get; set; }
|
||||||
|
public LinearLayout CellContent { get; set; }
|
||||||
|
public LinearLayout CellButtonContent { get; set; }
|
||||||
|
public Android.Widget.ImageButton CellButton1 { get; set; }
|
||||||
|
public Android.Widget.ImageButton CellButton2 { get; set; }
|
||||||
|
public Android.Widget.ImageButton CellButton3 { get; set; }
|
||||||
|
public LinearLayout CellAccessory { get; set; }
|
||||||
|
|
||||||
|
private void CreateContentView()
|
||||||
|
{
|
||||||
|
var contentView = (_Context as FormsAppCompatActivity)
|
||||||
|
.LayoutInflater
|
||||||
|
.Inflate(Resource.Layout.CellBaseView, this, true);
|
||||||
|
|
||||||
|
contentView.LayoutParameters = new ViewGroup.LayoutParams(-1, -1);
|
||||||
|
|
||||||
|
CellTitle = contentView.FindViewById<TextView>(Resource.Id.CellTitle);
|
||||||
|
CellContent = contentView.FindViewById<LinearLayout>(Resource.Id.CellContent);
|
||||||
|
CellTitleContent = contentView.FindViewById<LinearLayout>(Resource.Id.CellTitleContent);
|
||||||
|
CellButtonContent = contentView.FindViewById<LinearLayout>(Resource.Id.CellButtonContent);
|
||||||
|
CellButton1 = contentView.FindViewById<Android.Widget.ImageButton>(Resource.Id.CellButton1);
|
||||||
|
CellButton1.Click += CellButton1_Click;
|
||||||
|
CellButton2 = contentView.FindViewById<Android.Widget.ImageButton>(Resource.Id.CellButton2);
|
||||||
|
CellButton2.Click += CellButton2_Click;
|
||||||
|
CellButton3 = contentView.FindViewById<Android.Widget.ImageButton>(Resource.Id.CellButton3);
|
||||||
|
CellButton3.Click += CellButton3_Click;
|
||||||
|
CellAccessory = contentView.FindViewById<LinearLayout>(Resource.Id.CellAccessory);
|
||||||
|
|
||||||
|
_backgroundColor = new ColorDrawable();
|
||||||
|
_selectedColor = new ColorDrawable(Android.Graphics.Color.Argb(125, 180, 180, 180));
|
||||||
|
|
||||||
|
var sel = new StateListDrawable();
|
||||||
|
|
||||||
|
sel.AddState(new int[] { Android.Resource.Attribute.StateSelected }, _selectedColor);
|
||||||
|
sel.AddState(new int[] { -Android.Resource.Attribute.StateSelected }, _backgroundColor);
|
||||||
|
sel.SetExitFadeDuration(250);
|
||||||
|
sel.SetEnterFadeDuration(250);
|
||||||
|
|
||||||
|
var rippleColor = Android.Graphics.Color.Rgb(180, 180, 180);
|
||||||
|
if(CellParent.SelectedColor != Color.Default)
|
||||||
|
{
|
||||||
|
rippleColor = CellParent.SelectedColor.ToAndroid();
|
||||||
|
}
|
||||||
|
_ripple = RendererUtils.CreateRipple(rippleColor, sel);
|
||||||
|
Background = _ripple;
|
||||||
|
|
||||||
|
_defaultTextColor = new Android.Graphics.Color(CellTitle.CurrentTextColor);
|
||||||
|
_defaultFontSize = CellTitle.TextSize;
|
||||||
|
|
||||||
|
if(_debugWithColors)
|
||||||
|
{
|
||||||
|
contentView.Background = _Context.GetDrawable(Android.Resource.Color.HoloGreenLight);
|
||||||
|
CellContent.Background = _Context.GetDrawable(Android.Resource.Color.HoloOrangeLight);
|
||||||
|
CellButtonContent.Background = _Context.GetDrawable(Android.Resource.Color.HoloOrangeDark);
|
||||||
|
CellTitle.Background = _Context.GetDrawable(Android.Resource.Color.HoloBlueLight);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void CellPropertyChanged(object sender, PropertyChangedEventArgs e)
|
||||||
|
{
|
||||||
|
if(e.PropertyName == BaseCell.TitleProperty.PropertyName)
|
||||||
|
{
|
||||||
|
UpdateTitleText();
|
||||||
|
}
|
||||||
|
else if(e.PropertyName == BaseCell.TitleColorProperty.PropertyName)
|
||||||
|
{
|
||||||
|
UpdateTitleColor();
|
||||||
|
}
|
||||||
|
else if(e.PropertyName == BaseCell.TitleFontSizeProperty.PropertyName)
|
||||||
|
{
|
||||||
|
UpdateTitleFontSize();
|
||||||
|
}
|
||||||
|
else if(e.PropertyName == BaseCell.BackgroundColorProperty.PropertyName)
|
||||||
|
{
|
||||||
|
UpdateBackgroundColor();
|
||||||
|
}
|
||||||
|
else if(e.PropertyName == Cell.IsEnabledProperty.PropertyName)
|
||||||
|
{
|
||||||
|
UpdateIsEnabled();
|
||||||
|
}
|
||||||
|
else if(e.PropertyName == BaseCell.Button1IconProperty.PropertyName)
|
||||||
|
{
|
||||||
|
UpdateButtonIcon(CellButton1, CellBase.Button1Icon);
|
||||||
|
}
|
||||||
|
else if(e.PropertyName == BaseCell.Button2IconProperty.PropertyName)
|
||||||
|
{
|
||||||
|
UpdateButtonIcon(CellButton2, CellBase.Button2Icon);
|
||||||
|
}
|
||||||
|
else if(e.PropertyName == BaseCell.Button3IconProperty.PropertyName)
|
||||||
|
{
|
||||||
|
UpdateButtonIcon(CellButton3, CellBase.Button3Icon);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void ParentPropertyChanged(object sender, PropertyChangedEventArgs e)
|
||||||
|
{
|
||||||
|
// Avoid running the vain process when popping a page.
|
||||||
|
if((sender as BindableObject)?.BindingContext == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(e.PropertyName == App.Controls.BoxedView.BoxedView.CellTitleColorProperty.PropertyName)
|
||||||
|
{
|
||||||
|
UpdateTitleColor();
|
||||||
|
}
|
||||||
|
else if(e.PropertyName == App.Controls.BoxedView.BoxedView.CellTitleFontSizeProperty.PropertyName)
|
||||||
|
{
|
||||||
|
UpdateWithForceLayout(UpdateTitleFontSize);
|
||||||
|
}
|
||||||
|
else if(e.PropertyName == App.Controls.BoxedView.BoxedView.CellBackgroundColorProperty.PropertyName)
|
||||||
|
{
|
||||||
|
UpdateBackgroundColor();
|
||||||
|
}
|
||||||
|
else if(e.PropertyName == App.Controls.BoxedView.BoxedView.SelectedColorProperty.PropertyName)
|
||||||
|
{
|
||||||
|
UpdateWithForceLayout(UpdateSelectedColor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void SectionPropertyChanged(object sender, PropertyChangedEventArgs e)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
public virtual void RowSelected(BoxedViewRecyclerAdapter adapter, int position)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
protected void UpdateWithForceLayout(Action updateAction)
|
||||||
|
{
|
||||||
|
updateAction();
|
||||||
|
Invalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void UpdateCell()
|
||||||
|
{
|
||||||
|
UpdateButtonIcon(CellButton1, CellBase.Button1Icon);
|
||||||
|
UpdateButtonIcon(CellButton2, CellBase.Button2Icon);
|
||||||
|
UpdateButtonIcon(CellButton3, CellBase.Button3Icon);
|
||||||
|
UpdateBackgroundColor();
|
||||||
|
UpdateSelectedColor();
|
||||||
|
UpdateTitleText();
|
||||||
|
UpdateTitleColor();
|
||||||
|
UpdateTitleFontSize();
|
||||||
|
|
||||||
|
UpdateIsEnabled();
|
||||||
|
|
||||||
|
Invalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateButtonIcon(Android.Widget.ImageButton cellButton, string icon)
|
||||||
|
{
|
||||||
|
if(string.IsNullOrWhiteSpace(icon))
|
||||||
|
{
|
||||||
|
cellButton.Visibility = ViewStates.Gone;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
cellButton.SetImageDrawable(_Context.GetDrawable(icon));
|
||||||
|
cellButton.SetImageDrawable(_Context.GetDrawable(icon));
|
||||||
|
cellButton.Visibility = ViewStates.Visible;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CellButton1_Click(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if(CellBase.Button1Command?.CanExecute(CellBase.Button1CommandParameter) ?? false)
|
||||||
|
{
|
||||||
|
CellBase.Button1Command.Execute(CellBase.Button1CommandParameter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CellButton2_Click(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if(CellBase.Button2Command?.CanExecute(CellBase.Button2CommandParameter) ?? false)
|
||||||
|
{
|
||||||
|
CellBase.Button2Command.Execute(CellBase.Button2CommandParameter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CellButton3_Click(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if(CellBase.Button3Command?.CanExecute(CellBase.Button3CommandParameter) ?? false)
|
||||||
|
{
|
||||||
|
CellBase.Button3Command.Execute(CellBase.Button3CommandParameter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateBackgroundColor()
|
||||||
|
{
|
||||||
|
Selected = false;
|
||||||
|
if(CellBase.BackgroundColor != Color.Default)
|
||||||
|
{
|
||||||
|
_backgroundColor.Color = CellBase.BackgroundColor.ToAndroid();
|
||||||
|
}
|
||||||
|
else if(CellParent != null && CellParent.CellBackgroundColor != Color.Default)
|
||||||
|
{
|
||||||
|
_backgroundColor.Color = CellParent.CellBackgroundColor.ToAndroid();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_backgroundColor.Color = Android.Graphics.Color.Transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateSelectedColor()
|
||||||
|
{
|
||||||
|
if(CellParent != null && CellParent.SelectedColor != Color.Default)
|
||||||
|
{
|
||||||
|
_selectedColor.Color = CellParent.SelectedColor.MultiplyAlpha(0.5).ToAndroid();
|
||||||
|
_ripple.SetColor(RendererUtils.GetPressedColorSelector(CellParent.SelectedColor.ToAndroid()));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_selectedColor.Color = Android.Graphics.Color.Argb(125, 180, 180, 180);
|
||||||
|
_ripple.SetColor(RendererUtils.GetPressedColorSelector(Android.Graphics.Color.Rgb(180, 180, 180)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateTitleText()
|
||||||
|
{
|
||||||
|
CellTitle.Text = CellBase.Title;
|
||||||
|
// Hide TextView right padding when TextView.Text empty.
|
||||||
|
CellTitle.Visibility = string.IsNullOrEmpty(CellTitle.Text) ? ViewStates.Gone : ViewStates.Visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateTitleColor()
|
||||||
|
{
|
||||||
|
if(CellBase.TitleColor != Color.Default)
|
||||||
|
{
|
||||||
|
CellTitle.SetTextColor(CellBase.TitleColor.ToAndroid());
|
||||||
|
}
|
||||||
|
else if(CellParent != null && CellParent.CellTitleColor != Color.Default)
|
||||||
|
{
|
||||||
|
CellTitle.SetTextColor(CellParent.CellTitleColor.ToAndroid());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
CellTitle.SetTextColor(_defaultTextColor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateTitleFontSize()
|
||||||
|
{
|
||||||
|
if(CellBase.TitleFontSize > 0)
|
||||||
|
{
|
||||||
|
CellTitle.SetTextSize(ComplexUnitType.Sp, (float)CellBase.TitleFontSize);
|
||||||
|
}
|
||||||
|
else if(CellParent != null)
|
||||||
|
{
|
||||||
|
CellTitle.SetTextSize(ComplexUnitType.Sp, (float)CellParent.CellTitleFontSize);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
CellTitle.SetTextSize(ComplexUnitType.Sp, _defaultFontSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void UpdateIsEnabled()
|
||||||
|
{
|
||||||
|
SetEnabledAppearance(CellBase.IsEnabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void SetEnabledAppearance(bool isEnabled)
|
||||||
|
{
|
||||||
|
if(isEnabled)
|
||||||
|
{
|
||||||
|
Focusable = false;
|
||||||
|
DescendantFocusability = DescendantFocusability.AfterDescendants;
|
||||||
|
CellTitle.Alpha = 1f;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// not to invoke a ripple effect and not to selected
|
||||||
|
Focusable = true;
|
||||||
|
DescendantFocusability = DescendantFocusability.BlockDescendants;
|
||||||
|
// to turn like disabled
|
||||||
|
CellTitle.Alpha = 0.3f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if(disposing)
|
||||||
|
{
|
||||||
|
CellBase.PropertyChanged -= CellPropertyChanged;
|
||||||
|
CellParent.PropertyChanged -= ParentPropertyChanged;
|
||||||
|
CellButton1.Click -= CellButton1_Click;
|
||||||
|
CellButton2.Click -= CellButton2_Click;
|
||||||
|
CellButton3.Click -= CellButton3_Click;
|
||||||
|
|
||||||
|
if(CellBase.Section != null)
|
||||||
|
{
|
||||||
|
CellBase.Section.PropertyChanged -= SectionPropertyChanged;
|
||||||
|
CellBase.Section = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
CellTitle?.Dispose();
|
||||||
|
CellTitle = null;
|
||||||
|
CellTitleContent?.Dispose();
|
||||||
|
CellTitleContent = null;
|
||||||
|
CellAccessory?.Dispose();
|
||||||
|
CellAccessory = null;
|
||||||
|
CellButton1?.Dispose();
|
||||||
|
CellButton1 = null;
|
||||||
|
CellButton2?.Dispose();
|
||||||
|
CellButton2 = null;
|
||||||
|
CellButton3?.Dispose();
|
||||||
|
CellButton3 = null;
|
||||||
|
CellButtonContent?.Dispose();
|
||||||
|
CellButtonContent = null;
|
||||||
|
CellContent?.Dispose();
|
||||||
|
CellContent = null;
|
||||||
|
Cell = null;
|
||||||
|
|
||||||
|
_iconTokenSource?.Dispose();
|
||||||
|
_iconTokenSource = null;
|
||||||
|
_Context = null;
|
||||||
|
|
||||||
|
_backgroundColor?.Dispose();
|
||||||
|
_backgroundColor = null;
|
||||||
|
_selectedColor?.Dispose();
|
||||||
|
_selectedColor = null;
|
||||||
|
_ripple?.Dispose();
|
||||||
|
_ripple = null;
|
||||||
|
|
||||||
|
Background?.Dispose();
|
||||||
|
Background = null;
|
||||||
|
}
|
||||||
|
base.Dispose(disposing);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
282
src/Android/Renderers/BoxedView/Cells/EntryCellRenderer.cs
Normal file
|
@ -0,0 +1,282 @@
|
||||||
|
using Android.Content;
|
||||||
|
using Android.OS;
|
||||||
|
using Android.Runtime;
|
||||||
|
using Android.Text;
|
||||||
|
using Android.Text.Method;
|
||||||
|
using Android.Views;
|
||||||
|
using Android.Views.InputMethods;
|
||||||
|
using Android.Widget;
|
||||||
|
using Java.Lang;
|
||||||
|
using System;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using Xamarin.Forms;
|
||||||
|
using Xamarin.Forms.Platform.Android;
|
||||||
|
|
||||||
|
[assembly: ExportRenderer(typeof(Bit.App.Controls.BoxedView.EntryCell),
|
||||||
|
typeof(Bit.Droid.Renderers.BoxedView.EntryCellRenderer))]
|
||||||
|
namespace Bit.Droid.Renderers.BoxedView
|
||||||
|
{
|
||||||
|
[Preserve(AllMembers = true)]
|
||||||
|
public class EntryCellRenderer : BaseCellRenderer<EntryCellView>
|
||||||
|
{ }
|
||||||
|
|
||||||
|
[Preserve(AllMembers = true)]
|
||||||
|
public class EntryCellView : BaseCellView, ITextWatcher, Android.Views.View.IOnFocusChangeListener,
|
||||||
|
TextView.IOnEditorActionListener
|
||||||
|
{
|
||||||
|
private bool _debugWithColors = false;
|
||||||
|
private CustomEditText _editText;
|
||||||
|
|
||||||
|
public EntryCellView(Context context, Cell cell)
|
||||||
|
: base(context, cell)
|
||||||
|
{
|
||||||
|
_editText = new CustomEditText(context)
|
||||||
|
{
|
||||||
|
Focusable = true,
|
||||||
|
ImeOptions = ImeAction.Done,
|
||||||
|
OnFocusChangeListener = this,
|
||||||
|
Ellipsize = TextUtils.TruncateAt.End,
|
||||||
|
ClearFocusAction = DoneEdit,
|
||||||
|
Background = _Context.GetDrawable(Android.Resource.Color.Transparent)
|
||||||
|
};
|
||||||
|
_editText.SetPadding(0, 0, 0, 0);
|
||||||
|
_editText.SetOnEditorActionListener(this);
|
||||||
|
_editText.SetSingleLine(true);
|
||||||
|
_editText.InputType |= InputTypes.TextFlagNoSuggestions; // Disabled spell check
|
||||||
|
|
||||||
|
Click += EntryCellView_Click;
|
||||||
|
|
||||||
|
using(var lParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MatchParent,
|
||||||
|
ViewGroup.LayoutParams.WrapContent))
|
||||||
|
{
|
||||||
|
CellContent.AddView(_editText, lParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(_debugWithColors)
|
||||||
|
{
|
||||||
|
_editText.Background = _Context.GetDrawable(Android.Resource.Color.HoloRedLight);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
App.Controls.BoxedView.EntryCell _EntryCell => Cell as App.Controls.BoxedView.EntryCell;
|
||||||
|
|
||||||
|
public override void UpdateCell()
|
||||||
|
{
|
||||||
|
UpdateValueText();
|
||||||
|
UpdateValueTextColor();
|
||||||
|
UpdateValueTextFontSize();
|
||||||
|
UpdateKeyboard();
|
||||||
|
UpdatePlaceholder();
|
||||||
|
UpdateTextAlignment();
|
||||||
|
UpdateIsPassword();
|
||||||
|
base.UpdateCell();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void CellPropertyChanged(object sender, PropertyChangedEventArgs e)
|
||||||
|
{
|
||||||
|
base.CellPropertyChanged(sender, e);
|
||||||
|
if(e.PropertyName == App.Controls.BoxedView.EntryCell.ValueTextProperty.PropertyName)
|
||||||
|
{
|
||||||
|
UpdateValueText();
|
||||||
|
}
|
||||||
|
else if(e.PropertyName == App.Controls.BoxedView.EntryCell.ValueTextFontSizeProperty.PropertyName)
|
||||||
|
{
|
||||||
|
UpdateWithForceLayout(UpdateValueTextFontSize);
|
||||||
|
}
|
||||||
|
else if(e.PropertyName == App.Controls.BoxedView.EntryCell.ValueTextColorProperty.PropertyName)
|
||||||
|
{
|
||||||
|
UpdateWithForceLayout(UpdateValueTextColor);
|
||||||
|
}
|
||||||
|
else if(e.PropertyName == App.Controls.BoxedView.EntryCell.KeyboardProperty.PropertyName)
|
||||||
|
{
|
||||||
|
UpdateKeyboard();
|
||||||
|
}
|
||||||
|
else if(e.PropertyName == App.Controls.BoxedView.EntryCell.PlaceholderProperty.PropertyName)
|
||||||
|
{
|
||||||
|
UpdatePlaceholder();
|
||||||
|
}
|
||||||
|
else if(e.PropertyName == App.Controls.BoxedView.EntryCell.TextAlignmentProperty.PropertyName)
|
||||||
|
{
|
||||||
|
UpdateTextAlignment();
|
||||||
|
}
|
||||||
|
else if(e.PropertyName == App.Controls.BoxedView.EntryCell.IsPasswordProperty.PropertyName)
|
||||||
|
{
|
||||||
|
UpdateIsPassword();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void ParentPropertyChanged(object sender, PropertyChangedEventArgs e)
|
||||||
|
{
|
||||||
|
base.ParentPropertyChanged(sender, e);
|
||||||
|
if(e.PropertyName == App.Controls.BoxedView.BoxedView.CellValueTextColorProperty.PropertyName)
|
||||||
|
{
|
||||||
|
UpdateValueTextColor();
|
||||||
|
}
|
||||||
|
else if(e.PropertyName == App.Controls.BoxedView.BoxedView.CellValueTextFontSizeProperty.PropertyName)
|
||||||
|
{
|
||||||
|
UpdateWithForceLayout(UpdateValueTextFontSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if(disposing)
|
||||||
|
{
|
||||||
|
Click -= EntryCellView_Click;
|
||||||
|
_editText.RemoveFromParent();
|
||||||
|
_editText.SetOnEditorActionListener(null);
|
||||||
|
_editText.RemoveTextChangedListener(this);
|
||||||
|
_editText.OnFocusChangeListener = null;
|
||||||
|
_editText.ClearFocusAction = null;
|
||||||
|
_editText.Dispose();
|
||||||
|
_editText = null;
|
||||||
|
}
|
||||||
|
base.Dispose(disposing);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EntryCellView_Click(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
_editText.RequestFocus();
|
||||||
|
ShowKeyboard(_editText);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateValueText()
|
||||||
|
{
|
||||||
|
_editText.RemoveTextChangedListener(this);
|
||||||
|
if(_editText.Text != _EntryCell.ValueText)
|
||||||
|
{
|
||||||
|
_editText.Text = _EntryCell.ValueText;
|
||||||
|
}
|
||||||
|
_editText.AddTextChangedListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateValueTextFontSize()
|
||||||
|
{
|
||||||
|
if(_EntryCell.ValueTextFontSize > 0)
|
||||||
|
{
|
||||||
|
_editText.SetTextSize(Android.Util.ComplexUnitType.Sp, (float)_EntryCell.ValueTextFontSize);
|
||||||
|
}
|
||||||
|
else if(CellParent != null)
|
||||||
|
{
|
||||||
|
_editText.SetTextSize(Android.Util.ComplexUnitType.Sp, (float)CellParent.CellValueTextFontSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateValueTextColor()
|
||||||
|
{
|
||||||
|
if(_EntryCell.ValueTextColor != Color.Default)
|
||||||
|
{
|
||||||
|
_editText.SetTextColor(_EntryCell.ValueTextColor.ToAndroid());
|
||||||
|
}
|
||||||
|
else if(CellParent != null && CellParent.CellValueTextColor != Color.Default)
|
||||||
|
{
|
||||||
|
_editText.SetTextColor(CellParent.CellValueTextColor.ToAndroid());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateKeyboard()
|
||||||
|
{
|
||||||
|
_editText.InputType = _EntryCell.Keyboard.ToInputType() | InputTypes.TextFlagNoSuggestions;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateIsPassword()
|
||||||
|
{
|
||||||
|
_editText.TransformationMethod = _EntryCell.IsPassword ? new PasswordTransformationMethod() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdatePlaceholder()
|
||||||
|
{
|
||||||
|
_editText.Hint = _EntryCell.Placeholder;
|
||||||
|
_editText.SetHintTextColor(Android.Graphics.Color.Rgb(210, 210, 210));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateTextAlignment()
|
||||||
|
{
|
||||||
|
_editText.Gravity = _EntryCell.TextAlignment.ToAndroidHorizontal();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DoneEdit()
|
||||||
|
{
|
||||||
|
var entryCell = (IEntryCellController)Cell;
|
||||||
|
entryCell.SendCompleted();
|
||||||
|
_editText.ClearFocus();
|
||||||
|
ClearFocus();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HideKeyboard(Android.Views.View inputView)
|
||||||
|
{
|
||||||
|
using(var inputMethodManager = (InputMethodManager)_Context.GetSystemService(Context.InputMethodService))
|
||||||
|
{
|
||||||
|
IBinder windowToken = inputView.WindowToken;
|
||||||
|
if(windowToken != null)
|
||||||
|
{
|
||||||
|
inputMethodManager.HideSoftInputFromWindow(windowToken, HideSoftInputFlags.None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ShowKeyboard(Android.Views.View inputView)
|
||||||
|
{
|
||||||
|
using(var inputMethodManager = (InputMethodManager)_Context.GetSystemService(Context.InputMethodService))
|
||||||
|
{
|
||||||
|
inputMethodManager.ShowSoftInput(inputView, ShowFlags.Forced);
|
||||||
|
inputMethodManager.ToggleSoftInput(ShowFlags.Forced, HideSoftInputFlags.ImplicitOnly);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TextView.IOnEditorActionListener.OnEditorAction(TextView v, ImeAction actionId, KeyEvent e)
|
||||||
|
{
|
||||||
|
if(actionId == ImeAction.Done || (actionId == ImeAction.ImeNull && e.KeyCode == Keycode.Enter))
|
||||||
|
{
|
||||||
|
HideKeyboard(v);
|
||||||
|
DoneEdit();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ITextWatcher.AfterTextChanged(IEditable s)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
void ITextWatcher.BeforeTextChanged(ICharSequence s, int start, int count, int after)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
void ITextWatcher.OnTextChanged(ICharSequence s, int start, int before, int count)
|
||||||
|
{
|
||||||
|
_EntryCell.ValueText = s?.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
void IOnFocusChangeListener.OnFocusChange(Android.Views.View v, bool hasFocus)
|
||||||
|
{
|
||||||
|
if(hasFocus)
|
||||||
|
{
|
||||||
|
// Show underline when on focus.
|
||||||
|
_editText.Background.Alpha = 100;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Hide underline
|
||||||
|
_editText.Background.Alpha = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Preserve(AllMembers = true)]
|
||||||
|
internal class CustomEditText : EditText
|
||||||
|
{
|
||||||
|
public CustomEditText(Context context)
|
||||||
|
: base(context)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
public Action ClearFocusAction { get; set; }
|
||||||
|
|
||||||
|
public override bool OnKeyPreIme(Keycode keyCode, KeyEvent e)
|
||||||
|
{
|
||||||
|
if(keyCode == Keycode.Back && e.Action == KeyEventActions.Up)
|
||||||
|
{
|
||||||
|
ClearFocus();
|
||||||
|
ClearFocusAction?.Invoke();
|
||||||
|
}
|
||||||
|
return base.OnKeyPreIme(keyCode, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
127
src/Android/Renderers/BoxedView/Cells/LabelCellRenderer.cs
Normal file
|
@ -0,0 +1,127 @@
|
||||||
|
using Android.Content;
|
||||||
|
using Android.Runtime;
|
||||||
|
using Android.Text;
|
||||||
|
using Android.Views;
|
||||||
|
using Android.Widget;
|
||||||
|
using Bit.App.Controls.BoxedView;
|
||||||
|
using Bit.Droid.Renderers.BoxedView;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using Xamarin.Forms;
|
||||||
|
using Xamarin.Forms.Platform.Android;
|
||||||
|
|
||||||
|
[assembly: ExportRenderer(typeof(LabelCell), typeof(LabelCellRenderer))]
|
||||||
|
namespace Bit.Droid.Renderers.BoxedView
|
||||||
|
{
|
||||||
|
[Preserve(AllMembers = true)]
|
||||||
|
public class LabelCellRenderer : BaseCellRenderer<LabelCellView>
|
||||||
|
{ }
|
||||||
|
|
||||||
|
[Preserve(AllMembers = true)]
|
||||||
|
public class LabelCellView : BaseCellView
|
||||||
|
{
|
||||||
|
private bool _debugWithColors = false;
|
||||||
|
private TextView _valueLabel;
|
||||||
|
|
||||||
|
public LabelCellView(Context context, Cell cell)
|
||||||
|
: base(context, cell)
|
||||||
|
{
|
||||||
|
_valueLabel = new TextView(context)
|
||||||
|
{
|
||||||
|
Ellipsize = TextUtils.TruncateAt.End,
|
||||||
|
Gravity = GravityFlags.Left,
|
||||||
|
};
|
||||||
|
_valueLabel.SetSingleLine(true);
|
||||||
|
|
||||||
|
using(var lParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WrapContent,
|
||||||
|
ViewGroup.LayoutParams.WrapContent))
|
||||||
|
{
|
||||||
|
CellContent.AddView(_valueLabel, lParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(_debugWithColors)
|
||||||
|
{
|
||||||
|
_valueLabel.Background = _Context.GetDrawable(Android.Resource.Color.HoloRedLight);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private LabelCell LabelCell => Cell as LabelCell;
|
||||||
|
|
||||||
|
public override void CellPropertyChanged(object sender, PropertyChangedEventArgs e)
|
||||||
|
{
|
||||||
|
base.CellPropertyChanged(sender, e);
|
||||||
|
if(e.PropertyName == LabelCell.ValueTextProperty.PropertyName)
|
||||||
|
{
|
||||||
|
UpdateValueText();
|
||||||
|
}
|
||||||
|
else if(e.PropertyName == LabelCell.ValueTextFontSizeProperty.PropertyName)
|
||||||
|
{
|
||||||
|
UpdateValueTextFontSize();
|
||||||
|
}
|
||||||
|
else if(e.PropertyName == LabelCell.ValueTextColorProperty.PropertyName)
|
||||||
|
{
|
||||||
|
UpdateValueTextColor();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void ParentPropertyChanged(object sender, PropertyChangedEventArgs e)
|
||||||
|
{
|
||||||
|
base.ParentPropertyChanged(sender, e);
|
||||||
|
if(e.PropertyName == App.Controls.BoxedView.BoxedView.CellValueTextColorProperty.PropertyName)
|
||||||
|
{
|
||||||
|
UpdateValueTextColor();
|
||||||
|
}
|
||||||
|
else if(e.PropertyName == App.Controls.BoxedView.BoxedView.CellValueTextFontSizeProperty.PropertyName)
|
||||||
|
{
|
||||||
|
UpdateValueTextFontSize();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void UpdateCell()
|
||||||
|
{
|
||||||
|
base.UpdateCell();
|
||||||
|
UpdateValueText();
|
||||||
|
UpdateValueTextColor();
|
||||||
|
UpdateValueTextFontSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void UpdateValueText()
|
||||||
|
{
|
||||||
|
_valueLabel.Text = LabelCell.ValueText;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateValueTextFontSize()
|
||||||
|
{
|
||||||
|
if(LabelCell.ValueTextFontSize > 0)
|
||||||
|
{
|
||||||
|
_valueLabel.SetTextSize(Android.Util.ComplexUnitType.Sp, (float)LabelCell.ValueTextFontSize);
|
||||||
|
}
|
||||||
|
else if(CellParent != null)
|
||||||
|
{
|
||||||
|
_valueLabel.SetTextSize(Android.Util.ComplexUnitType.Sp, (float)CellParent.CellValueTextFontSize);
|
||||||
|
}
|
||||||
|
Invalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateValueTextColor()
|
||||||
|
{
|
||||||
|
if(LabelCell.ValueTextColor != Color.Default)
|
||||||
|
{
|
||||||
|
_valueLabel.SetTextColor(LabelCell.ValueTextColor.ToAndroid());
|
||||||
|
}
|
||||||
|
else if(CellParent != null && CellParent.CellValueTextColor != Color.Default)
|
||||||
|
{
|
||||||
|
_valueLabel.SetTextColor(CellParent.CellValueTextColor.ToAndroid());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if(disposing)
|
||||||
|
{
|
||||||
|
_valueLabel?.Dispose();
|
||||||
|
_valueLabel = null;
|
||||||
|
}
|
||||||
|
base.Dispose(disposing);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
25
src/Android/Renderers/CustomEditorRenderer.cs
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
using Android.Content;
|
||||||
|
using Bit.Droid.Renderers;
|
||||||
|
using Xamarin.Forms;
|
||||||
|
using Xamarin.Forms.Platform.Android;
|
||||||
|
|
||||||
|
[assembly: ExportRenderer(typeof(Entry), typeof(CustomEditorRenderer))]
|
||||||
|
namespace Bit.Droid.Renderers
|
||||||
|
{
|
||||||
|
public class CustomEditorRenderer : EditorRenderer
|
||||||
|
{
|
||||||
|
public CustomEditorRenderer(Context context)
|
||||||
|
: base(context)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
protected override void OnElementChanged(ElementChangedEventArgs<Editor> e)
|
||||||
|
{
|
||||||
|
base.OnElementChanged(e);
|
||||||
|
if(Control != null && e.NewElement != null)
|
||||||
|
{
|
||||||
|
Control.SetPadding(Control.PaddingLeft, Control.PaddingTop - 10, Control.PaddingRight,
|
||||||
|
Control.PaddingBottom + 20);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
25
src/Android/Renderers/CustomEntryRenderer.cs
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
using Android.Content;
|
||||||
|
using Bit.Droid.Renderers;
|
||||||
|
using Xamarin.Forms;
|
||||||
|
using Xamarin.Forms.Platform.Android;
|
||||||
|
|
||||||
|
[assembly: ExportRenderer(typeof(Entry), typeof(CustomEntryRenderer))]
|
||||||
|
namespace Bit.Droid.Renderers
|
||||||
|
{
|
||||||
|
public class CustomEntryRenderer : EntryRenderer
|
||||||
|
{
|
||||||
|
public CustomEntryRenderer(Context context)
|
||||||
|
: base(context)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
|
||||||
|
{
|
||||||
|
base.OnElementChanged(e);
|
||||||
|
if(Control != null && e.NewElement != null)
|
||||||
|
{
|
||||||
|
Control.SetPadding(Control.PaddingLeft, Control.PaddingTop - 10, Control.PaddingRight,
|
||||||
|
Control.PaddingBottom + 20);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
25
src/Android/Renderers/CustomPickerRenderer.cs
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
using Android.Content;
|
||||||
|
using Bit.Droid.Renderers;
|
||||||
|
using Xamarin.Forms;
|
||||||
|
using Xamarin.Forms.Platform.Android;
|
||||||
|
|
||||||
|
[assembly: ExportRenderer(typeof(Picker), typeof(CustomPickerRenderer))]
|
||||||
|
namespace Bit.Droid.Renderers
|
||||||
|
{
|
||||||
|
public class CustomPickerRenderer : PickerRenderer
|
||||||
|
{
|
||||||
|
public CustomPickerRenderer(Context context)
|
||||||
|
: base(context)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
protected override void OnElementChanged(ElementChangedEventArgs<Picker> e)
|
||||||
|
{
|
||||||
|
base.OnElementChanged(e);
|
||||||
|
if(Control != null && e.NewElement != null)
|
||||||
|
{
|
||||||
|
Control.SetPadding(Control.PaddingLeft, Control.PaddingTop - 10, Control.PaddingRight,
|
||||||
|
Control.PaddingBottom + 20);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
30
src/Android/Renderers/CustomSearchBarRenderer.cs
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
using Android.Content;
|
||||||
|
using Bit.Droid.Renderers;
|
||||||
|
using Xamarin.Forms;
|
||||||
|
using Xamarin.Forms.Platform.Android;
|
||||||
|
|
||||||
|
[assembly: ExportRenderer(typeof(SearchBar), typeof(CustomSearchBarRenderer))]
|
||||||
|
namespace Bit.Droid.Renderers
|
||||||
|
{
|
||||||
|
public class CustomSearchBarRenderer : SearchBarRenderer
|
||||||
|
{
|
||||||
|
public CustomSearchBarRenderer(Context context)
|
||||||
|
: base(context)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
protected override void OnElementChanged(ElementChangedEventArgs<SearchBar> e)
|
||||||
|
{
|
||||||
|
base.OnElementChanged(e);
|
||||||
|
if(Control != null && e.NewElement != null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var magId = Resources.GetIdentifier("android:id/search_mag_icon", null, null);
|
||||||
|
var magImage = (Android.Widget.ImageView)Control.FindViewById(magId);
|
||||||
|
magImage.LayoutParameters = new Android.Widget.LinearLayout.LayoutParams(0, 0);
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,13 +1,12 @@
|
||||||
using System;
|
using Android.Content;
|
||||||
using Bit.Android.Controls;
|
using Android.Views;
|
||||||
using Bit.App.Controls;
|
using Bit.App.Controls;
|
||||||
|
using Bit.Droid.Renderers;
|
||||||
using Xamarin.Forms;
|
using Xamarin.Forms;
|
||||||
using Xamarin.Forms.Platform.Android;
|
using Xamarin.Forms.Platform.Android;
|
||||||
using Android.Content;
|
|
||||||
using Android.Views;
|
|
||||||
|
|
||||||
[assembly: ExportRenderer(typeof(ExtendedListView), typeof(ExtendedListViewRenderer))]
|
[assembly: ExportRenderer(typeof(ExtendedListView), typeof(ExtendedListViewRenderer))]
|
||||||
namespace Bit.Android.Controls
|
namespace Bit.Droid.Renderers
|
||||||
{
|
{
|
||||||
public class ExtendedListViewRenderer : ListViewRenderer
|
public class ExtendedListViewRenderer : ListViewRenderer
|
||||||
{
|
{
|
||||||
|
@ -18,15 +17,12 @@ namespace Bit.Android.Controls
|
||||||
protected override void OnElementChanged(ElementChangedEventArgs<ListView> e)
|
protected override void OnElementChanged(ElementChangedEventArgs<ListView> e)
|
||||||
{
|
{
|
||||||
base.OnElementChanged(e);
|
base.OnElementChanged(e);
|
||||||
|
if(Control != null && e.NewElement != null && e.NewElement is ExtendedListView listView)
|
||||||
if(e.NewElement is ExtendedListView listView)
|
|
||||||
{
|
{
|
||||||
if(listView.BottomPadding > 0)
|
// Pad for FAB
|
||||||
{
|
Control.SetPadding(0, 0, 0, 170);
|
||||||
Control.SetPadding(0, 0, 0, listView.BottomPadding);
|
Control.SetClipToPadding(false);
|
||||||
Control.SetClipToPadding(false);
|
Control.ScrollBarStyle = ScrollbarStyles.OutsideOverlay;
|
||||||
Control.ScrollBarStyle = ScrollbarStyles.OutsideOverlay;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
33
src/Android/Renderers/ExtendedSliderRenderer.cs
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
using Android.Content;
|
||||||
|
using Android.Graphics.Drawables;
|
||||||
|
using Android.Support.V4.Content.Res;
|
||||||
|
using Bit.App.Controls;
|
||||||
|
using Bit.Droid.Renderers;
|
||||||
|
using Xamarin.Forms;
|
||||||
|
using Xamarin.Forms.Platform.Android;
|
||||||
|
|
||||||
|
[assembly: ExportRenderer(typeof(ExtendedSlider), typeof(ExtendedSliderRenderer))]
|
||||||
|
namespace Bit.Droid.Renderers
|
||||||
|
{
|
||||||
|
public class ExtendedSliderRenderer : SliderRenderer
|
||||||
|
{
|
||||||
|
public ExtendedSliderRenderer(Context context)
|
||||||
|
: base(context)
|
||||||
|
{}
|
||||||
|
|
||||||
|
protected override void OnElementChanged(ElementChangedEventArgs<Slider> e)
|
||||||
|
{
|
||||||
|
base.OnElementChanged(e);
|
||||||
|
if(Control != null && Element is ExtendedSlider view)
|
||||||
|
{
|
||||||
|
var t = ResourcesCompat.GetDrawable(Resources, Resource.Drawable.slider_thumb, null);
|
||||||
|
if(t is GradientDrawable thumb)
|
||||||
|
{
|
||||||
|
thumb.SetColor(view.ThumbColor.ToAndroid());
|
||||||
|
thumb.SetStroke(1, view.ThumbBorderColor.ToAndroid());
|
||||||
|
Control.SetThumb(thumb);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,4 @@
|
||||||
using System;
|
using System;
|
||||||
using Bit.Android.Controls;
|
|
||||||
using Bit.App.Controls;
|
using Bit.App.Controls;
|
||||||
using Xamarin.Forms;
|
using Xamarin.Forms;
|
||||||
using Xamarin.Forms.Platform.Android;
|
using Xamarin.Forms.Platform.Android;
|
||||||
|
@ -7,9 +6,11 @@ using Android.Webkit;
|
||||||
using AWebkit = Android.Webkit;
|
using AWebkit = Android.Webkit;
|
||||||
using Java.Interop;
|
using Java.Interop;
|
||||||
using Android.Content;
|
using Android.Content;
|
||||||
|
using Bit.Droid.Renderers;
|
||||||
|
using System.ComponentModel;
|
||||||
|
|
||||||
[assembly: ExportRenderer(typeof(HybridWebView), typeof(HybridWebViewRenderer))]
|
[assembly: ExportRenderer(typeof(HybridWebView), typeof(HybridWebViewRenderer))]
|
||||||
namespace Bit.Android.Controls
|
namespace Bit.Droid.Renderers
|
||||||
{
|
{
|
||||||
public class HybridWebViewRenderer : ViewRenderer<HybridWebView, AWebkit.WebView>
|
public class HybridWebViewRenderer : ViewRenderer<HybridWebView, AWebkit.WebView>
|
||||||
{
|
{
|
||||||
|
@ -34,14 +35,12 @@ namespace Bit.Android.Controls
|
||||||
webView.SetWebViewClient(new JSWebViewClient(string.Format("javascript: {0}", JSFunction)));
|
webView.SetWebViewClient(new JSWebViewClient(string.Format("javascript: {0}", JSFunction)));
|
||||||
SetNativeControl(webView);
|
SetNativeControl(webView);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(e.OldElement != null)
|
if(e.OldElement != null)
|
||||||
{
|
{
|
||||||
Control.RemoveJavascriptInterface("jsBridge");
|
Control.RemoveJavascriptInterface("jsBridge");
|
||||||
var hybridWebView = e.OldElement as HybridWebView;
|
var hybridWebView = e.OldElement as HybridWebView;
|
||||||
hybridWebView.Cleanup();
|
hybridWebView.Cleanup();
|
||||||
}
|
}
|
||||||
|
|
||||||
if(e.NewElement != null)
|
if(e.NewElement != null)
|
||||||
{
|
{
|
||||||
Control.AddJavascriptInterface(new JSBridge(this), "jsBridge");
|
Control.AddJavascriptInterface(new JSBridge(this), "jsBridge");
|
||||||
|
@ -49,6 +48,15 @@ namespace Bit.Android.Controls
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
|
||||||
|
{
|
||||||
|
base.OnElementPropertyChanged(sender, e);
|
||||||
|
if(e.PropertyName == HybridWebView.UriProperty.PropertyName)
|
||||||
|
{
|
||||||
|
Control.LoadUrl(Element.Uri);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public class JSBridge : Java.Lang.Object
|
public class JSBridge : Java.Lang.Object
|
||||||
{
|
{
|
||||||
private readonly WeakReference<HybridWebViewRenderer> _hybridWebViewRenderer;
|
private readonly WeakReference<HybridWebViewRenderer> _hybridWebViewRenderer;
|
||||||
|
@ -62,8 +70,8 @@ namespace Bit.Android.Controls
|
||||||
[Export("invokeAction")]
|
[Export("invokeAction")]
|
||||||
public void InvokeAction(string data)
|
public void InvokeAction(string data)
|
||||||
{
|
{
|
||||||
HybridWebViewRenderer hybridRenderer;
|
if(_hybridWebViewRenderer != null &&
|
||||||
if(_hybridWebViewRenderer != null && _hybridWebViewRenderer.TryGetTarget(out hybridRenderer))
|
_hybridWebViewRenderer.TryGetTarget(out HybridWebViewRenderer hybridRenderer))
|
||||||
{
|
{
|
||||||
hybridRenderer.Element.InvokeAction(data);
|
hybridRenderer.Element.InvokeAction(data);
|
||||||
}
|
}
|
||||||
|
@ -79,6 +87,7 @@ namespace Bit.Android.Controls
|
||||||
_javascript = javascript;
|
_javascript = javascript;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void OnPageFinished(AWebkit.WebView view, string url)
|
public override void OnPageFinished(AWebkit.WebView view, string url)
|
||||||
{
|
{
|
||||||
base.OnPageFinished(view, url);
|
base.OnPageFinished(view, url);
|
79
src/Android/Renderers/RendererUtils.cs
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
using Android.Content.Res;
|
||||||
|
using Android.Graphics.Drawables;
|
||||||
|
using Android.Views;
|
||||||
|
using Xamarin.Forms;
|
||||||
|
|
||||||
|
namespace Bit.Droid.Renderers
|
||||||
|
{
|
||||||
|
[Android.Runtime.Preserve(AllMembers = true)]
|
||||||
|
public static class RendererUtils
|
||||||
|
{
|
||||||
|
public static GravityFlags ToAndroidVertical(this LayoutAlignment formsAlignment)
|
||||||
|
{
|
||||||
|
switch(formsAlignment)
|
||||||
|
{
|
||||||
|
case LayoutAlignment.Start:
|
||||||
|
return GravityFlags.Top;
|
||||||
|
case LayoutAlignment.Center:
|
||||||
|
return GravityFlags.CenterVertical;
|
||||||
|
case LayoutAlignment.End:
|
||||||
|
return GravityFlags.Bottom;
|
||||||
|
default:
|
||||||
|
return GravityFlags.FillHorizontal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static GravityFlags ToAndroidHorizontal(this LayoutAlignment formsAlignment)
|
||||||
|
{
|
||||||
|
switch(formsAlignment)
|
||||||
|
{
|
||||||
|
case LayoutAlignment.Start:
|
||||||
|
return GravityFlags.Start;
|
||||||
|
case LayoutAlignment.Center:
|
||||||
|
return GravityFlags.CenterHorizontal;
|
||||||
|
case LayoutAlignment.End:
|
||||||
|
return GravityFlags.End;
|
||||||
|
default:
|
||||||
|
return GravityFlags.FillVertical;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static GravityFlags ToAndroidHorizontal(this Xamarin.Forms.TextAlignment formsAlignment)
|
||||||
|
{
|
||||||
|
switch(formsAlignment)
|
||||||
|
{
|
||||||
|
case Xamarin.Forms.TextAlignment.Start:
|
||||||
|
return GravityFlags.Left | GravityFlags.CenterVertical;
|
||||||
|
case Xamarin.Forms.TextAlignment.Center:
|
||||||
|
return GravityFlags.Center | GravityFlags.CenterVertical;
|
||||||
|
case Xamarin.Forms.TextAlignment.End:
|
||||||
|
return GravityFlags.Right | GravityFlags.CenterVertical;
|
||||||
|
default:
|
||||||
|
return GravityFlags.Left | GravityFlags.CenterVertical;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static RippleDrawable CreateRipple(Android.Graphics.Color color, Drawable background = null)
|
||||||
|
{
|
||||||
|
if(background == null)
|
||||||
|
{
|
||||||
|
var mask = new ColorDrawable(Android.Graphics.Color.White);
|
||||||
|
return new RippleDrawable(GetPressedColorSelector(color), null, mask);
|
||||||
|
}
|
||||||
|
return new RippleDrawable(GetPressedColorSelector(color), background, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ColorStateList GetPressedColorSelector(int pressedColor)
|
||||||
|
{
|
||||||
|
return new ColorStateList(
|
||||||
|
new int[][]
|
||||||
|
{
|
||||||
|
new int[]{}
|
||||||
|
},
|
||||||
|
new int[]
|
||||||
|
{
|
||||||
|
pressedColor,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,50 +0,0 @@
|
||||||
Images, layout descriptions, binary blobs and string dictionaries can be included
|
|
||||||
in your application as resource files. Various Android APIs are designed to
|
|
||||||
operate on the resource IDs instead of dealing with images, strings or binary blobs
|
|
||||||
directly.
|
|
||||||
|
|
||||||
For example, a sample Android app that contains a user interface layout (main.xml),
|
|
||||||
an internationalization string table (strings.xml) and some icons (drawable-XXX/icon.png)
|
|
||||||
would keep its resources in the "Resources" directory of the application:
|
|
||||||
|
|
||||||
Resources/
|
|
||||||
drawable-hdpi/
|
|
||||||
icon.png
|
|
||||||
|
|
||||||
drawable-ldpi/
|
|
||||||
icon.png
|
|
||||||
|
|
||||||
drawable-mdpi/
|
|
||||||
icon.png
|
|
||||||
|
|
||||||
layout/
|
|
||||||
main.xml
|
|
||||||
|
|
||||||
values/
|
|
||||||
strings.xml
|
|
||||||
|
|
||||||
In order to get the build system to recognize Android resources, set the build action to
|
|
||||||
"AndroidResource". The native Android APIs do not operate directly with filenames, but
|
|
||||||
instead operate on resource IDs. When you compile an Android application that uses resources,
|
|
||||||
the build system will package the resources for distribution and generate a class called
|
|
||||||
"Resource" that contains the tokens for each one of the resources included. For example,
|
|
||||||
for the above Resources layout, this is what the Resource class would expose:
|
|
||||||
|
|
||||||
public class Resource {
|
|
||||||
public class drawable {
|
|
||||||
public const int icon = 0x123;
|
|
||||||
}
|
|
||||||
|
|
||||||
public class layout {
|
|
||||||
public const int main = 0x456;
|
|
||||||
}
|
|
||||||
|
|
||||||
public class strings {
|
|
||||||
public const int first_string = 0xabc;
|
|
||||||
public const int second_string = 0xbcd;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
You would then use R.drawable.icon to reference the drawable/icon.png file, or Resource.layout.main
|
|
||||||
to reference the layout/main.xml file, or Resource.strings.first_string to reference the first
|
|
||||||
string in the dictionary file values/strings.xml.
|
|
10763
src/Android/Resources/Resource.Designer.cs
generated
14814
src/Android/Resources/Resource.designer.cs
generated
Normal file
Before Width: | Height: | Size: 530 B |
Before Width: | Height: | Size: 625 B |
Before Width: | Height: | Size: 664 B |
Before Width: | Height: | Size: 426 B |
Before Width: | Height: | Size: 977 B |
Before Width: | Height: | Size: 883 B |
Before Width: | Height: | Size: 738 B |
Before Width: | Height: | Size: 738 B |
Before Width: | Height: | Size: 467 B |
Before Width: | Height: | Size: 518 B |
Before Width: | Height: | Size: 833 B |
Before Width: | Height: | Size: 937 B |
Before Width: | Height: | Size: 569 B |
Before Width: | Height: | Size: 6 KiB |
Before Width: | Height: | Size: 431 B |
Before Width: | Height: | Size: 482 B |
Before Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 513 B |
Before Width: | Height: | Size: 686 B |
Before Width: | Height: | Size: 634 B |
Before Width: | Height: | Size: 438 B After Width: | Height: | Size: 569 B |
Before Width: | Height: | Size: 344 B |
Before Width: | Height: | Size: 658 B |
Before Width: | Height: | Size: 804 B |
Before Width: | Height: | Size: 519 B |
Before Width: | Height: | Size: 403 B |
Before Width: | Height: | Size: 1,009 B |
Before Width: | Height: | Size: 637 B |
Before Width: | Height: | Size: 452 B |
Before Width: | Height: | Size: 708 B |
Before Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 572 B |
Before Width: | Height: | Size: 659 B |