merge fixes

This commit is contained in:
Kyle Spearrin 2019-05-28 12:15:38 -04:00
commit 57af4e0a03
1384 changed files with 50451 additions and 103964 deletions

3
.editorconfig Normal file
View file

@ -0,0 +1,3 @@
# All files
[*]
guidelines = 120

63
.gitattributes vendored Normal file
View 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

1
.gitignore vendored
View file

@ -198,3 +198,4 @@ FakesAssemblies/
# Other # Other
project.lock.json project.lock.json
.DS_Store .DS_Store
src/App/Css

View file

@ -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

View file

@ -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

View file

@ -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;
} }

View 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();
}
}
}

View 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);
}
}
}
}

View 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;
}
}

View file

@ -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; }

View 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();
}
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -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");

Binary file not shown.

Binary file not shown.

Binary file not shown.

View 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(Parser parser, List<FilledItem> items, bool locked)
}
public static FillResponse BuildFillResponse(Context context, 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;

View file

@ -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,7 +31,7 @@ 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)
@ -41,23 +41,22 @@ namespace Bit.Android.Autofill
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();

View file

@ -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;
}
}
} }
} }

View file

@ -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));
} }

View file

@ -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;
} }
} }

View file

@ -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;
} }

View file

@ -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
{ {

View file

@ -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);
}
}
}

View file

@ -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);
}
}
}

View file

@ -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);
}
}
}

View file

@ -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);
}
}
}
}

View file

@ -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;
}
}
}
}
}

View file

@ -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;
}
}
}
}

View file

@ -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) });
}
}
}

View file

@ -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);
}
}
}
}

View file

@ -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());
}
}
}
}

View file

@ -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;
}
}
}

View file

@ -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;
}
}
}
}

View file

@ -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;
}
}
}
}

View file

@ -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());
}
}
}
}

View file

@ -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

View file

@ -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

View file

@ -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,14 +148,13 @@ 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"));
} }
} }
@ -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);
}
}
} }
} }

View file

@ -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();
// Note: This might cause a race condition. Investigate more.
Task.Run(() =>
{
FFImageLoading.Forms.Platform.CachedImageRenderer.Init(true); FFImageLoading.Forms.Platform.CachedImageRenderer.Init(true);
ZXing.Net.Mobile.Forms.Android.Platform.Init(); 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();
} }
} }
} }

View file

@ -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);
}
}
}

View file

@ -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>());
}
}
}

View file

@ -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,8 +18,13 @@
<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: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"> android:networkSecurityConfig="@xml/network_security_config">
<provider <provider
android:name="android.support.v4.content.FileProvider" android:name="android.support.v4.content.FileProvider"
@ -24,19 +35,5 @@
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>

View file

@ -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

View 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

View file

@ -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)
{ {

View 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();
}
}
}

View 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");
}
}
}

View 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);
}
}
}

View 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);
}
}
}

View 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);
}
}
}

View 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();
}
}
}
}

View 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);
}
}
}

View 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);
}
}
}

View 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);
}
}
}

View 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);
}
}
}
}

View 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);
}
}
}
}

View 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);
}
}
}
}

View 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 { }
}
}
}
}

View file

@ -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,16 +17,13 @@ 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;
} }
} }
} }
} }
}

View 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);
}
}
}
}
}

View file

@ -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);

View 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,
});
}
}
}

View file

@ -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.

File diff suppressed because it is too large Load diff

14814
src/Android/Resources/Resource.designer.cs generated Normal file

File diff suppressed because it is too large Load diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 530 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 625 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 664 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 426 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 977 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 883 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 738 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 738 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 467 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 518 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 833 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 937 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 569 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 431 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 482 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 513 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 686 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 634 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 438 B

After

Width:  |  Height:  |  Size: 569 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 344 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 658 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 804 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 519 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 403 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1,009 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 637 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 452 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 708 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 572 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 659 B

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