COVIDSafe code from version 1.0.28 (#4)

This commit is contained in:
COVIDSafe Support 2020-06-19 17:41:15 +10:00 committed by GitHub
parent 05a2ca94a6
commit c16533add6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
35 changed files with 1653 additions and 413 deletions

View file

@ -40,8 +40,8 @@ android {
Before you increase the targetSdkVersion make sure that all its usage are still working
*/
targetSdkVersion 28
versionCode 21
versionName "1.0.21"
versionCode 28
versionName "1.0.28"
buildConfigField "String", "GITHASH", "\"${getGitHash()}\""
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
@ -243,6 +243,10 @@ dependencies {
implementation "androidx.lifecycle:lifecycle-service:2.2.0"
implementation 'com.github.razir.progressbutton:progressbutton:2.0.1'
// flags
implementation 'com.michaelfotiadis:android-country-flags:1.0.3'
androidTestImplementation "androidx.room:room-testing:2.2.5"
}

View file

@ -1,12 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="au.gov.health.covidsafe">
<uses-feature
android:name="android.hardware.bluetooth_le"
android:required="true" />
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
@ -19,13 +20,14 @@
<application
android:name="au.gov.health.covidsafe.TracerApp"
tools:replace="android:supportsRtl"
android:allowBackup="false"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:networkSecurityConfig="@xml/network_security_config"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/MyTheme.DayNight"
android:networkSecurityConfig="@xml/network_security_config">
android:theme="@style/MyTheme.DayNight">
<activity
android:name="au.gov.health.covidsafe.SplashActivity"
@ -38,16 +40,21 @@
</activity>
<activity
android:name="au.gov.health.covidsafe.ui.onboarding.OnboardingActivity"
android:windowSoftInputMode="adjustPan"
android:screenOrientation="portrait" />
android:screenOrientation="portrait"
android:windowSoftInputMode="adjustPan" />
<activity
android:name="au.gov.health.covidsafe.ui.onboarding.CountryCodeSelectionActivity"
android:screenOrientation="portrait"
android:windowSoftInputMode="adjustPan" />
<activity
android:name="au.gov.health.covidsafe.WebViewActivity"
android:screenOrientation="portrait" />
<activity
android:name="au.gov.health.covidsafe.HomeActivity"
android:windowSoftInputMode="adjustPan"
android:screenOrientation="portrait" />
android:screenOrientation="portrait"
android:windowSoftInputMode="adjustPan" />
<receiver android:name="au.gov.health.covidsafe.boot.StartOnBootReceiver">
<intent-filter>

View file

@ -9,6 +9,12 @@ import au.gov.health.covidsafe.security.crypto.AESEncryptionForPreAndroidM
object Preference {
private const val PREF_ID = "Tracer_pref"
private const val IS_ONBOARDED = "IS_ONBOARDED"
private const val CALLING_CODE = "CALLING_CODE"
private const val AUSTRALIA_CALLING_CODE = 61
private const val COUNTRY_NAME_RES_ID = "COUNTRY_NAME"
private const val AUSTRALIA_COUNTRY_NAME_RES_ID = R.string.country_au
private const val NATIONAL_FLAG_RES_ID = "NATIONAL_FLAG_RES_ID"
private const val AUSTRALIA_NATIONAL_FLAG_RES_ID = R.drawable.ic_list_country_au
private const val PHONE_NUMBER = "PHONE_NUMBER"
private const val HANDSHAKE_PIN = "HANDSHAKE_PIN"
private const val DEVICE_ID = "DEVICE_ID"
@ -41,7 +47,7 @@ object Preference {
.edit().putString(AES_IV, value)?.apply()
}
fun getEncodedAESInitialisationVector(context: Context) : String? {
fun getEncodedAESInitialisationVector(context: Context): String? {
return context.getSharedPreferences(PREF_ID, Context.MODE_PRIVATE)
.getString(AES_IV, null)
}
@ -119,6 +125,43 @@ object Preference {
.edit().putString(PHONE_NUMBER, value).apply()
}
fun getPhoneNumber(context: Context): String {
return context.getSharedPreferences(PREF_ID, Context.MODE_PRIVATE)
?.getString(PHONE_NUMBER, "") ?: ""
}
fun putCallingCode(context: Context, value: Int) {
context.getSharedPreferences(PREF_ID, Context.MODE_PRIVATE)
.edit().putInt(CALLING_CODE, value).apply()
}
fun getCallingCode(context: Context): Int {
return context.getSharedPreferences(PREF_ID, Context.MODE_PRIVATE)
?.getInt(CALLING_CODE, AUSTRALIA_CALLING_CODE) ?: AUSTRALIA_CALLING_CODE
}
fun putCountryNameResID(context: Context, value: Int) {
context.getSharedPreferences(PREF_ID, Context.MODE_PRIVATE)
.edit().putInt(COUNTRY_NAME_RES_ID, value).apply()
}
fun getCountryNameResID(context: Context): Int {
return context.getSharedPreferences(PREF_ID, Context.MODE_PRIVATE)
?.getInt(COUNTRY_NAME_RES_ID, AUSTRALIA_COUNTRY_NAME_RES_ID)
?: AUSTRALIA_COUNTRY_NAME_RES_ID
}
fun putNationalFlagResID(context: Context, value: Int) {
context.getSharedPreferences(PREF_ID, Context.MODE_PRIVATE)
.edit().putInt(NATIONAL_FLAG_RES_ID, value).apply()
}
fun getNationalFlagResID(context: Context): Int {
return context.getSharedPreferences(PREF_ID, Context.MODE_PRIVATE)
?.getInt(NATIONAL_FLAG_RES_ID, AUSTRALIA_NATIONAL_FLAG_RES_ID)
?: AUSTRALIA_NATIONAL_FLAG_RES_ID
}
fun putNextFetchTimeInMillis(context: Context, time: Long) {
context.getSharedPreferences(PREF_ID, Context.MODE_PRIVATE)
.edit().putLong(NEXT_FETCH_TIME, time).apply()

View file

@ -7,14 +7,15 @@ import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.PowerManager
import android.provider.Settings
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.NotificationManagerCompat
import androidx.fragment.app.Fragment
import au.gov.health.covidsafe.R
import au.gov.health.covidsafe.Utils
import pub.devrel.easypermissions.AppSettingsDialog
import pub.devrel.easypermissions.EasyPermissions
import pub.devrel.easypermissions.PermissionRequest
import au.gov.health.covidsafe.R
import au.gov.health.covidsafe.Utils
const val REQUEST_ENABLE_BT = 123
const val LOCATION = 345
@ -85,6 +86,22 @@ fun Fragment.excludeFromBatteryOptimization(onEndCallback: (() -> Unit)? = null)
}
fun Fragment.gotoPushNotificationSettings() {
val context = requireContext()
val intent = Intent()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
intent.action = Settings.ACTION_APP_NOTIFICATION_SETTINGS
intent.putExtra(Settings.EXTRA_APP_PACKAGE, context.packageName)
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
intent.action = "android.settings.APP_NOTIFICATION_SETTINGS"
intent.putExtra("app_package", context.packageName)
intent.putExtra("app_uid", context.applicationInfo.uid)
}
context.startActivity(intent)
}
fun Fragment.isBlueToothEnabled(): Boolean? {
val bluetoothManager = activity?.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager?
return bluetoothManager?.adapter?.isEnabled

View file

@ -17,7 +17,9 @@ class GetOnboardingOtp(private val awsClient: AwsClient, lifecycle: Lifecycle) :
override suspend fun run(params: GetOtpParams): Either<Exception, OTPChallengeResponse> {
return try {
val response = awsClient.initiateAuth(
OTPChallengeRequest(params.phoneNumber,
OTPChallengeRequest(
params.countryCode,
params.phoneNumber,
params.deviceId,
params.postCode,
params.age,
@ -48,11 +50,14 @@ class GetOnboardingOtp(private val awsClient: AwsClient, lifecycle: Lifecycle) :
}
}
data class GetOtpParams(internal val phoneNumber: String,
data class GetOtpParams(
internal val countryCode: String,
internal val phoneNumber: String,
internal val deviceId: String,
internal val postCode: String?,
internal val age: String?,
internal val name: String?)
internal val name: String?
)
sealed class GetOnboardingOtpException : Exception() {
class GetOtpServiceException(val code: Int? = null) : GetOnboardingOtpException()

View file

@ -3,8 +3,11 @@ package au.gov.health.covidsafe.networking.request
import androidx.annotation.Keep
@Keep
data class OTPChallengeRequest(val phone_number: String,
data class OTPChallengeRequest(
val country_code: String,
val phone_number: String,
val device_id: String,
val postcode: String?,
val age: String?,
val name: String?)
val name: String?
)

View file

@ -3,8 +3,9 @@ package au.gov.health.covidsafe.streetpass
import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothGatt
import android.content.Context
import com.google.gson.Gson
import android.os.Build
import au.gov.health.covidsafe.logging.CentralLog
import com.google.gson.Gson
import kotlin.properties.Delegates
class Work constructor(
@ -16,7 +17,7 @@ class Work constructor(
var checklist = WorkCheckList()
var gatt: BluetoothGatt? = null
var finished = false
var timeout : Long = 0
var timeout: Long = 0
private val TAG = "Work"
@ -36,7 +37,12 @@ class Work constructor(
context: Context,
gattCallback: StreetPassWorker.StreetPassGattCallback
) {
gatt = device.connectGatt(context, false, gattCallback)
gatt = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
device.connectGatt(context, false, gattCallback, BluetoothDevice.TRANSPORT_LE)
} else {
device.connectGatt(context, false, gattCallback)
}
if (gatt == null) {
CentralLog.e(TAG, "Unable to connect to ${device.address}")
}

View file

@ -98,7 +98,6 @@ object Encryption {
private val NONCE_PADDING = ByteArray(14) { 0x0E.toByte() }
private val serverPubKey: PublicKey = readKey()
private val symCipher: Cipher = makeSymCipher()
private var cachedEphPubKey: ByteArray? = null
private var cachedAesKey: SecretKey? = null
@ -131,6 +130,7 @@ object Encryption {
// IV = AES(ctr, iv=null), AES(plaintext, iv=IV) === AES(ctr_with_padding || plaintext, iv=null)
// Using the latter construction to reduce key expansions
val ivParams = IvParameterSpec(ByteArray(16)) // null IV
val symCipher: Cipher = makeSymCipher()
symCipher.init(Cipher.ENCRYPT_MODE, keys.aesKey, ivParams)
val ciphertextWithIV: ByteArray = symCipher.doFinal(keys.nonce.plus(NONCE_PADDING).plus(data))

View file

@ -94,6 +94,8 @@ class HomeFragment : BaseFragment(), EasyPermissions.PermissionCallbacks {
bluetooth_card_view.setOnClickListener { requestBlueToothPermissionThenNextPermission() }
location_card_view.setOnClickListener { askForLocationPermission() }
battery_card_view.setOnClickListener { excludeFromBatteryOptimization() }
push_card_view.setOnClickListener { gotoPushNotificationSettings() }
home_been_tested_button.setOnClickListener {
navigateTo(R.id.action_home_to_selfIsolate)
}
@ -264,7 +266,11 @@ class HomeFragment : BaseFragment(), EasyPermissions.PermissionCallbacks {
private fun updatePushNotificationStatus() {
isPushNotificationEnabled()?.let {
push_card_view.visibility = VISIBLE
push_card_view.render(formatPushNotificationTitle(it), it)
push_card_view.render(
formatPushNotificationTitle(it),
it,
getString(R.string.home_app_permission_push_notification_prompt)
)
} ?: run {
push_card_view.visibility = GONE
}

View file

@ -4,9 +4,12 @@ import android.content.Context
import android.content.res.TypedArray
import android.util.AttributeSet
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
import androidx.core.content.ContextCompat
import au.gov.health.covidsafe.R
import au.gov.health.covidsafe.TracerApp
import kotlinx.android.synthetic.main.view_card_permission_card.view.*
class PermissionStatusCard @JvmOverloads constructor(
@ -28,10 +31,20 @@ class PermissionStatusCard @JvmOverloads constructor(
layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, height)
}
fun render(text: String, correct: Boolean) {
fun render(title: String, correct: Boolean, body: String? = null) {
val errorTextColor = ContextCompat.getColor(TracerApp.AppContext, R.color.error)
val normalTextColor = ContextCompat.getColor(TracerApp.AppContext, R.color.slack_black)
permission_icon.isSelected = correct
permission_title.text = text
permission_title.text = title
permission_title.setTextColor(if (correct) normalTextColor else errorTextColor)
if (correct || body == null) {
permission_body.visibility = View.GONE
} else {
permission_body.visibility = View.VISIBLE
permission_body.text = body
permission_body.setTextColor(if (correct) normalTextColor else errorTextColor)
}
}
}

View file

@ -0,0 +1,193 @@
package au.gov.health.covidsafe.ui.onboarding
import android.app.Activity
import android.content.Intent
import android.os.Bundle
import android.speech.RecognizerIntent
import android.speech.RecognizerIntent.EXTRA_RESULTS
import android.text.Editable
import android.text.TextWatcher
import android.view.View
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.LinearSmoothScroller
import androidx.recyclerview.widget.RecyclerView
import au.gov.health.covidsafe.R
import kotlinx.android.synthetic.main.activity_country_code_selection.*
fun RecyclerView.smoothSnapToPosition(
position: Int,
snapMode: Int = LinearSmoothScroller.SNAP_TO_START
) {
val smoothScroller = object : LinearSmoothScroller(this.context) {
override fun getVerticalSnapPreference(): Int = snapMode
override fun getHorizontalSnapPreference(): Int = snapMode
}
smoothScroller.targetPosition = position
layoutManager?.startSmoothScroll(smoothScroller)
}
const val VOICE_TO_TEXT_REQUEST_CODE = 2020
class CountryCodeSelectionActivity : Activity() {
val countryListItem = CountryList.getCountryList()
private fun setupToolbar() {
countrySelectionToolbar.setNavigationOnClickListener {
super.onBackPressed()
}
countrySelectionToolbar.title = getString(R.string.select_country_or_region)
}
private fun setupCountryListRecyclerView() {
val linearLayoutManager = LinearLayoutManager(this)
countryListRecyclerView.layoutManager = linearLayoutManager
val dividerItemDecoration = DividerItemDecoration(
this,
linearLayoutManager.orientation
)
countryListRecyclerView.addItemDecoration(dividerItemDecoration)
countryListRecyclerView.adapter = CountryListRecyclerViewAdapter(
this,
countryListItem
) {
super.onBackPressed()
}
}
private fun countryListScrollToPosition(positionOfLetter: Int) {
countryListRecyclerView.scrollToPosition(positionOfLetter)
Thread {
Thread.sleep(100)
runOnUiThread {
countryListRecyclerView.smoothSnapToPosition(positionOfLetter)
}
}.start()
}
private fun setupSearchFunctions() {
// text based search
countryRegionNameEditText.setOnFocusChangeListener { _, hasFocus ->
countrySearchImageView.visibility = if (hasFocus) View.GONE else View.VISIBLE
}
countryRegionNameEditText.addTextChangedListener(object : TextWatcher {
override fun afterTextChanged(s: Editable?) {
fun getPositionOfCountryName(searchText: String): Int {
countryListItem.forEachIndexed { index, countryListItemInterface ->
if (countryListItemInterface is CountryGroupTitle) {
val groupTitle = getString(countryListItemInterface.titleResId)
if (groupTitle.startsWith(searchText, ignoreCase = true)
) {
return index
}
} else if (countryListItemInterface is CountryListItem) {
val countryName = getString(countryListItemInterface.countryNameResId)
if (countryName.contains(searchText, ignoreCase = true)
) {
return index
}
val callingCode = countryListItemInterface.callingCode
if ("$callingCode".startsWith(searchText, ignoreCase = true)
) {
return index
}
}
}
return -1
}
s?.toString()?.let { enteredText ->
val positionOfCountryName = getPositionOfCountryName(enteredText)
if (positionOfCountryName != -1) {
countryListScrollToPosition(positionOfCountryName)
}
}
}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
// do nothing
}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
// do nothing
}
})
// voice to text search
microphoneImageView.setOnClickListener {
startActivityForResult(
Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH),
VOICE_TO_TEXT_REQUEST_CODE
)
}
}
private fun setupInitialLetterRecyclerView() {
val alphabet = ArrayList<String>()
var letter = 'A'
while (letter <= 'Z') {
if (letter != 'W' && letter != 'X') {
alphabet.add(letter.toString())
}
++letter
}
countryInitialLetterRecyclerView.layoutManager = LinearLayoutManager(this)
countryInitialLetterRecyclerView.adapter = CountryInitialLetterRecyclerViewAdapter(
this,
alphabet
) { letterClicked ->
fun getPositionOfLetter(letter: String): Int {
countryListItem.forEachIndexed { index, countryListItemInterface ->
if (countryListItemInterface is CountryGroupTitle) {
val groupTitle = getString(countryListItemInterface.titleResId)
if (groupTitle.startsWith(letter, ignoreCase = true)) {
return index
}
}
}
return 0
}
val positionOfLetter = getPositionOfLetter(letterClicked)
countryListScrollToPosition(positionOfLetter)
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == VOICE_TO_TEXT_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
data?.getStringArrayListExtra(EXTRA_RESULTS)?.let {
countryRegionNameEditText.setText(it.first())
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_country_code_selection)
setupToolbar()
setupCountryListRecyclerView()
setupInitialLetterRecyclerView()
// set up the search functions
setupSearchFunctions()
}
}

View file

@ -0,0 +1,48 @@
package au.gov.health.covidsafe.ui.onboarding
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import au.gov.health.covidsafe.R
class CountryInitialLetterHolder(
itemView: View,
private val onLetterClicked: (letter: String) -> Unit
) : RecyclerView.ViewHolder(itemView) {
fun setLetter(letter: String) {
val letterTextView = itemView.findViewById<TextView>(R.id.country_initial_letter)
letterTextView.text = letter
letterTextView.setOnClickListener {
onLetterClicked(letter)
}
}
}
class CountryInitialLetterRecyclerViewAdapter(
private val context: Context,
private val initialLetters: List<String>,
private val onLetterClicked: (letter: String) -> Unit
) : RecyclerView.Adapter<CountryInitialLetterHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CountryInitialLetterHolder {
return CountryInitialLetterHolder(
LayoutInflater.from(context).inflate(
R.layout.view_list_item_country_initial_letter,
parent,
false
),
onLetterClicked
)
}
override fun getItemCount(): Int {
return initialLetters.size
}
override fun onBindViewHolder(holder: CountryInitialLetterHolder, position: Int) {
holder.setLetter(initialLetters[position])
}
}

View file

@ -0,0 +1,253 @@
package au.gov.health.covidsafe.ui.onboarding
import au.gov.health.covidsafe.R
object CountryList {
fun getCountryList() : List<CountryListItemInterface>{
// for now, it returns the grouping for English only, in the future, it should return
// different groupings according to different language
return groupingForEnglish
}
private val groupingForEnglish = listOf(
CountryGroupTitle(R.string.options_for_australia),
CountryListItem(R.string.country_au, 61, R.drawable.ic_list_country_au),
CountryListItem(R.string.country_nf, 672, R.drawable.ic_list_country_nf),
CountryGroupTitle(R.string.group_title_a),
// CountryListItem(R.string.country_af, 93, R.drawable.ic_list_country_af),
CountryListItem(R.string.country_al, 355, R.drawable.ic_list_country_al),
CountryListItem(R.string.country_dz, 213, R.drawable.ic_list_country_dz),
// CountryListItem(R.string.country_ad, 376, R.drawable.ic_list_country_ad),
CountryListItem(R.string.country_ao, 244, R.drawable.ic_list_country_ao),
CountryListItem(R.string.country_ai, 1, R.drawable.ic_list_country_ai),
CountryListItem(R.string.country_ag, 1, R.drawable.ic_list_country_ag),
CountryListItem(R.string.country_ar, 54, R.drawable.ic_list_country_ar),
CountryListItem(R.string.country_am, 374, R.drawable.ic_list_country_am),
CountryListItem(R.string.country_aw, 297, R.drawable.ic_list_country_aw),
CountryListItem(R.string.country_au, 61, R.drawable.ic_list_country_au),
CountryListItem(R.string.country_at, 43, R.drawable.ic_list_country_at),
CountryListItem(R.string.country_az, 994, R.drawable.ic_list_country_az),
CountryGroupTitle(R.string.group_title_b),
CountryListItem(R.string.country_bs, 1, R.drawable.ic_list_country_bs),
CountryListItem(R.string.country_bh, 973, R.drawable.ic_list_country_bh),
CountryListItem(R.string.country_bd, 880, R.drawable.ic_list_country_bd),
CountryListItem(R.string.country_bb, 1, R.drawable.ic_list_country_bb),
CountryListItem(R.string.country_by, 375, R.drawable.ic_list_country_by),
CountryListItem(R.string.country_be, 32, R.drawable.ic_list_country_be),
CountryListItem(R.string.country_bz, 501, R.drawable.ic_list_country_bz),
CountryListItem(R.string.country_bj, 229, R.drawable.ic_list_country_bj),
CountryListItem(R.string.country_bm, 1, R.drawable.ic_list_country_bm),
// CountryListItem(R.string.country_bt, 975, R.drawable.ic_list_country_bt),
CountryListItem(R.string.country_bo, 591, R.drawable.ic_list_country_bo),
CountryListItem(R.string.country_ba, 387, R.drawable.ic_list_country_ba),
CountryListItem(R.string.country_bw, 267, R.drawable.ic_list_country_bw),
CountryListItem(R.string.country_br, 55, R.drawable.ic_list_country_br),
// CountryListItem(R.string.country_bn, 673, R.drawable.ic_list_country_bn),
CountryListItem(R.string.country_vg, 1, R.drawable.ic_list_country_vg),
CountryListItem(R.string.country_bg, 359, R.drawable.ic_list_country_bg),
CountryListItem(R.string.country_bf, 226, R.drawable.ic_list_country_bf),
// CountryListItem(R.string.country_bi, 257, R.drawable.ic_list_country_bi),
CountryGroupTitle(R.string.group_title_c),
CountryListItem(R.string.country_kh, 855, R.drawable.ic_list_country_kh),
CountryListItem(R.string.country_cm, 237, R.drawable.ic_list_country_cm),
CountryListItem(R.string.country_ca, 1, R.drawable.ic_list_country_ca),
CountryListItem(R.string.country_cv, 238, R.drawable.ic_list_country_cv),
CountryListItem(R.string.country_ky, 1, R.drawable.ic_list_country_ky),
// CountryListItem(R.string.country_cf, 236, R.drawable.ic_list_country_cf),
// CountryListItem(R.string.country_td, 235, R.drawable.ic_list_country_td),
CountryListItem(R.string.country_cl, 56, R.drawable.ic_list_country_cl),
CountryListItem(R.string.country_cn, 86, R.drawable.ic_list_country_cn),
CountryListItem(R.string.country_co, 57, R.drawable.ic_list_country_co),
// CountryListItem(R.string.country_km, 269, R.drawable.ic_list_country_km),
// CountryListItem(R.string.country_ck, 682, R.drawable.ic_list_country_ck),
CountryListItem(R.string.country_cr, 506, R.drawable.ic_list_country_cr),
CountryListItem(R.string.country_hr, 385, R.drawable.ic_list_country_hr),
CountryListItem(R.string.country_cu, 53, R.drawable.ic_list_country_cu),
CountryListItem(R.string.country_cw, 599, R.drawable.ic_list_country_cw),
CountryListItem(R.string.country_cy, 357, R.drawable.ic_list_country_cy),
CountryListItem(R.string.country_cz, 420, R.drawable.ic_list_country_cz),
// CountryListItem(R.string.country_cd, 243, R.drawable.ic_list_country_cd),
CountryGroupTitle(R.string.group_title_d),
CountryListItem(R.string.country_dk, 45, R.drawable.ic_list_country_dk),
// CountryListItem(R.string.country_dj, 253, R.drawable.ic_list_country_dj),
CountryListItem(R.string.country_dm, 1, R.drawable.ic_list_country_dm),
CountryListItem(R.string.country_do, 1, R.drawable.ic_list_country_do),
CountryGroupTitle(R.string.group_title_e),
CountryListItem(R.string.country_ec, 593, R.drawable.ic_list_country_ec),
// CountryListItem(R.string.country_eg, 20, R.drawable.ic_list_country_eg),
CountryListItem(R.string.country_sv, 503, R.drawable.ic_list_country_sv),
// CountryListItem(R.string.country_gq, 240, R.drawable.ic_list_country_gq),
CountryListItem(R.string.country_ee, 372, R.drawable.ic_list_country_ee),
// CountryListItem(R.string.country_et, 251, R.drawable.ic_list_country_et),
CountryGroupTitle(R.string.group_title_f),
// CountryListItem(R.string.country_fo, 298, R.drawable.ic_list_country_fo),
CountryListItem(R.string.country_fj, 679, R.drawable.ic_list_country_fj),
CountryListItem(R.string.country_fi, 358, R.drawable.ic_list_country_fi),
CountryListItem(R.string.country_fr, 33, R.drawable.ic_list_country_fr),
CountryGroupTitle(R.string.group_title_g),
// CountryListItem(R.string.country_gf, 995, R.drawable.ic_list_country_fr),
CountryListItem(R.string.country_ga, 241, R.drawable.ic_list_country_ga),
// CountryListItem(R.string.country_gm, 220, R.drawable.ic_list_country_gm),
CountryListItem(R.string.country_ge, 995, R.drawable.ic_list_country_ge),
CountryListItem(R.string.country_de, 49, R.drawable.ic_list_country_de),
CountryListItem(R.string.country_gh, 233, R.drawable.ic_list_country_gh),
// CountryListItem(R.string.country_gi, 350, R.drawable.ic_list_country_gi),
CountryListItem(R.string.country_gr, 30, R.drawable.ic_list_country_gr),
// CountryListItem(R.string.country_gl, 299, R.drawable.ic_list_country_gl),
CountryListItem(R.string.country_gd, 1, R.drawable.ic_list_country_gd),
// CountryListItem(R.string.country_gp, 224, R.drawable.ic_list_country_fr),
CountryListItem(R.string.country_gu, 1, R.drawable.ic_list_country_gu),
CountryListItem(R.string.country_gt, 502, R.drawable.ic_list_country_gt),
// CountryListItem(R.string.country_gn, 224, R.drawable.ic_list_country_gn),
CountryListItem(R.string.country_gw, 245, R.drawable.ic_list_country_gw),
// CountryListItem(R.string.country_gy, 592, R.drawable.ic_list_country_gy),
CountryGroupTitle(R.string.group_title_h),
CountryListItem(R.string.country_ht, 509, R.drawable.ic_list_country_ht),
CountryListItem(R.string.country_hn, 504, R.drawable.ic_list_country_hn),
CountryListItem(R.string.country_hk, 852, R.drawable.ic_list_country_hk),
CountryListItem(R.string.country_hu, 36, R.drawable.ic_list_country_hu),
CountryGroupTitle(R.string.group_title_i),
CountryListItem(R.string.country_is, 354, R.drawable.ic_list_country_is),
CountryListItem(R.string.country_in, 91, R.drawable.ic_list_country_in),
CountryListItem(R.string.country_id, 62, R.drawable.ic_list_country_id),
CountryListItem(R.string.country_ir, 964, R.drawable.ic_list_country_ir),
CountryListItem(R.string.country_iq, 964, R.drawable.ic_list_country_iq),
CountryListItem(R.string.country_ie, 353, R.drawable.ic_list_country_ie),
CountryListItem(R.string.country_il, 972, R.drawable.ic_list_country_il),
// CountryListItem(R.string.country_it, 39, R.drawable.ic_list_country_it),
CountryListItem(R.string.country_ci, 225, R.drawable.ic_list_country_ci),
CountryGroupTitle(R.string.group_title_j),
CountryListItem(R.string.country_jm, 1, R.drawable.ic_list_country_jm),
CountryListItem(R.string.country_jp, 81, R.drawable.ic_list_country_jp),
CountryListItem(R.string.country_jo, 962, R.drawable.ic_list_country_jo),
CountryGroupTitle(R.string.group_title_k),
CountryListItem(R.string.country_kz, 7, R.drawable.ic_list_country_kz),
CountryListItem(R.string.country_ke, 254, R.drawable.ic_list_country_ke),
// CountryListItem(R.string.country_ki, 686, R.drawable.ic_list_country_ki),
CountryListItem(R.string.country_kw, 965, R.drawable.ic_list_country_kw),
CountryListItem(R.string.country_kg, 996, R.drawable.ic_list_country_kg),
CountryGroupTitle(R.string.group_title_l),
CountryListItem(R.string.country_la, 856, R.drawable.ic_list_country_la),
CountryListItem(R.string.country_lv, 371, R.drawable.ic_list_country_lv),
CountryListItem(R.string.country_lb, 961, R.drawable.ic_list_country_lb),
// CountryListItem(R.string.country_ls, 266, R.drawable.ic_list_country_ls),
// CountryListItem(R.string.country_lr, 231, R.drawable.ic_list_country_lr),
// CountryListItem(R.string.country_ly, 218, R.drawable.ic_list_country_ly),
CountryListItem(R.string.country_li, 423, R.drawable.ic_list_country_li),
CountryListItem(R.string.country_lt, 370, R.drawable.ic_list_country_lt),
CountryListItem(R.string.country_lu, 352, R.drawable.ic_list_country_lu),
CountryGroupTitle(R.string.group_title_m),
CountryListItem(R.string.country_mo, 853, R.drawable.ic_list_country_mo),
// CountryListItem(R.string.country_mg, 261, R.drawable.ic_list_country_mg),
// CountryListItem(R.string.country_mw, 265, R.drawable.ic_list_country_mw),
CountryListItem(R.string.country_my, 60, R.drawable.ic_list_country_my),
// CountryListItem(R.string.country_mv, 960, R.drawable.ic_list_country_mv),
CountryListItem(R.string.country_ml, 223, R.drawable.ic_list_country_ml),
CountryListItem(R.string.country_mt, 356, R.drawable.ic_list_country_mt),
CountryListItem(R.string.country_mq, 1, R.drawable.ic_list_country_mq),
// CountryListItem(R.string.country_mr, 222, R.drawable.ic_list_country_mr),
CountryListItem(R.string.country_mu, 230, R.drawable.ic_list_country_mu),
CountryListItem(R.string.country_mx, 52, R.drawable.ic_list_country_mx),
CountryListItem(R.string.country_md, 373, R.drawable.ic_list_country_md),
// CountryListItem(R.string.country_mc, 377, R.drawable.ic_list_country_mc),
// CountryListItem(R.string.country_mn, 976, R.drawable.ic_list_country_mn),
// CountryListItem(R.string.country_me, 382, R.drawable.ic_list_country_me),
CountryListItem(R.string.country_ms, 1, R.drawable.ic_list_country_ms),
CountryListItem(R.string.country_ma, 212, R.drawable.ic_list_country_ma),
CountryListItem(R.string.country_mz, 258, R.drawable.ic_list_country_mz),
CountryListItem(R.string.country_mm, 95, R.drawable.ic_list_country_mm),
CountryGroupTitle(R.string.group_title_n),
CountryListItem(R.string.country_na, 264, R.drawable.ic_list_country_na),
CountryListItem(R.string.country_np, 977, R.drawable.ic_list_country_np),
CountryListItem(R.string.country_nl, 31, R.drawable.ic_list_country_nl),
// CountryListItem(R.string.country_an, 599, R.drawable.ic_list_country_an),
// CountryListItem(R.string.country_nc, 687, R.drawable.ic_list_country_nc),
CountryListItem(R.string.country_nz, 64, R.drawable.ic_list_country_nz),
CountryListItem(R.string.country_ni, 505, R.drawable.ic_list_country_ni),
CountryListItem(R.string.country_ne, 227, R.drawable.ic_list_country_ne),
CountryListItem(R.string.country_ng, 234, R.drawable.ic_list_country_ng),
CountryListItem(R.string.country_nf, 672, R.drawable.ic_list_country_nf),
CountryListItem(R.string.country_mk, 389, R.drawable.ic_list_country_mk),
CountryListItem(R.string.country_no, 47, R.drawable.ic_list_country_no),
CountryGroupTitle(R.string.group_title_o),
CountryListItem(R.string.country_om, 968, R.drawable.ic_list_country_om),
CountryGroupTitle(R.string.group_title_p),
CountryListItem(R.string.country_pk, 92, R.drawable.ic_list_country_pk),
// CountryListItem(R.string.country_pw, 680, R.drawable.ic_list_country_pw),
// CountryListItem(R.string.country_ps, 970, R.drawable.ic_list_country_ps),
CountryListItem(R.string.country_pa, 507, R.drawable.ic_list_country_pa),
CountryListItem(R.string.country_pg, 675, R.drawable.ic_list_country_pg),
CountryListItem(R.string.country_py, 595, R.drawable.ic_list_country_py),
CountryListItem(R.string.country_pe, 51, R.drawable.ic_list_country_pe),
CountryListItem(R.string.country_ph, 63, R.drawable.ic_list_country_ph),
CountryListItem(R.string.country_pl, 48, R.drawable.ic_list_country_pl),
CountryListItem(R.string.country_pt, 351, R.drawable.ic_list_country_pt),
CountryListItem(R.string.country_pr, 1, R.drawable.ic_list_country_pr),
CountryGroupTitle(R.string.group_title_q),
CountryListItem(R.string.country_qa, 974, R.drawable.ic_list_country_qa),
CountryGroupTitle(R.string.group_title_r),
// CountryListItem(R.string.country_cg, 242, R.drawable.ic_list_country_cg),
// CountryListItem(R.string.country_re, 262, R.drawable.ic_list_country_fr),
// CountryListItem(R.string.country_ro, 40, R.drawable.ic_list_country_ro),
CountryListItem(R.string.country_ru, 7, R.drawable.ic_list_country_ru),
CountryListItem(R.string.country_rw, 250, R.drawable.ic_list_country_rw),
CountryGroupTitle(R.string.group_title_s),
CountryListItem(R.string.country_kn, 1, R.drawable.ic_list_country_kn),
CountryListItem(R.string.country_lc, 1, R.drawable.ic_list_country_lc),
CountryListItem(R.string.country_vc, 1, R.drawable.ic_list_country_vc),
// CountryListItem(R.string.country_ws, 685, R.drawable.ic_list_country_ws),
// CountryListItem(R.string.country_st, 239, R.drawable.ic_list_country_st),
// CountryListItem(R.string.country_sa, 966, R.drawable.ic_list_country_sa),
CountryListItem(R.string.country_sn, 221, R.drawable.ic_list_country_sn),
CountryListItem(R.string.country_rs, 381, R.drawable.ic_list_country_rs),
// CountryListItem(R.string.country_sc, 248, R.drawable.ic_list_country_sc),
// CountryListItem(R.string.country_sl, 232, R.drawable.ic_list_country_sl),
CountryListItem(R.string.country_sg, 65, R.drawable.ic_list_country_sg),
CountryListItem(R.string.country_sk, 421, R.drawable.ic_list_country_sk),
CountryListItem(R.string.country_si, 386, R.drawable.ic_list_country_si),
CountryListItem(R.string.country_sb, 677, R.drawable.ic_list_country_sb),
// CountryListItem(R.string.country_so, 252, R.drawable.ic_list_country_so),
CountryListItem(R.string.country_za, 27, R.drawable.ic_list_country_za),
CountryListItem(R.string.country_kr, 82, R.drawable.ic_list_country_kr),
// CountryListItem(R.string.country_ss, 211, R.drawable.ic_list_country_ss),
CountryListItem(R.string.country_es, 34, R.drawable.ic_list_country_es),
CountryListItem(R.string.country_lk, 94, R.drawable.ic_list_country_lk),
CountryListItem(R.string.country_sd, 249, R.drawable.ic_list_country_sd),
// CountryListItem(R.string.country_sr, 597, R.drawable.ic_list_country_sr),
// CountryListItem(R.string.country_sz, 268, R.drawable.ic_list_country_sz),
CountryListItem(R.string.country_se, 46, R.drawable.ic_list_country_se),
CountryListItem(R.string.country_ch, 41, R.drawable.ic_list_country_ch),
CountryGroupTitle(R.string.group_title_t),
CountryListItem(R.string.country_tw, 886, R.drawable.ic_list_country_tw),
CountryListItem(R.string.country_tj, 992, R.drawable.ic_list_country_tj),
CountryListItem(R.string.country_tz, 255, R.drawable.ic_list_country_tz),
CountryListItem(R.string.country_th, 66, R.drawable.ic_list_country_th),
// CountryListItem(R.string.country_tl, 670, R.drawable.ic_list_country_tl),
CountryListItem(R.string.country_tg, 228, R.drawable.ic_list_country_tg),
// CountryListItem(R.string.country_to, 676, R.drawable.ic_list_country_to),
CountryListItem(R.string.country_tt, 1, R.drawable.ic_list_country_tt),
CountryListItem(R.string.country_tn, 216, R.drawable.ic_list_country_tn),
CountryListItem(R.string.country_tr, 90, R.drawable.ic_list_country_tr),
CountryListItem(R.string.country_tm, 993, R.drawable.ic_list_country_tm),
CountryListItem(R.string.country_tc, 1, R.drawable.ic_list_country_tc),
CountryGroupTitle(R.string.group_title_u),
CountryListItem(R.string.country_ug, 256, R.drawable.ic_list_country_ug),
CountryListItem(R.string.country_ua, 380, R.drawable.ic_list_country_ua),
CountryListItem(R.string.country_ae, 971, R.drawable.ic_list_country_ae),
CountryListItem(R.string.country_gb, 44, R.drawable.ic_list_country_gb),
CountryListItem(R.string.country_us, 1, R.drawable.ic_list_country_us),
CountryListItem(R.string.country_uy, 598, R.drawable.ic_list_country_uy),
CountryListItem(R.string.country_uz, 998, R.drawable.ic_list_country_uz),
CountryGroupTitle(R.string.group_title_v),
// CountryListItem(R.string.country_vu, 678, R.drawable.ic_list_country_vu),
CountryListItem(R.string.country_ve, 58, R.drawable.ic_list_country_ve),
CountryListItem(R.string.country_vn, 84, R.drawable.ic_list_country_vn),
CountryListItem(R.string.country_vi, 1, R.drawable.ic_list_country_vi),
CountryGroupTitle(R.string.group_title_y),
CountryListItem(R.string.country_ye, 967, R.drawable.ic_list_country_ye),
CountryGroupTitle(R.string.group_title_z),
CountryListItem(R.string.country_zm, 260, R.drawable.ic_list_country_zm),
CountryListItem(R.string.country_zw, 263, R.drawable.ic_list_country_zw)
)
}

View file

@ -0,0 +1,127 @@
package au.gov.health.covidsafe.ui.onboarding
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import au.gov.health.covidsafe.Preference
import au.gov.health.covidsafe.R
import au.gov.health.covidsafe.TracerApp
const val VIEW_TYPE_GROUP_TITLE = 1
const val VIEW_TYPE_COUNTRY = 2
interface CountryListItemInterface
class CountryListItem(
val countryNameResId: Int,
val callingCode: Int,
val flagResID: Int
) : CountryListItemInterface
class CountryGroupTitle(
val titleResId: Int
) : CountryListItemInterface
class CountryGroupTitleHolder(
itemView: View
) : RecyclerView.ViewHolder(itemView) {
fun setCountryGroupTitle(title: String) {
itemView.findViewById<TextView>(R.id.country_group_title).text = title
}
}
class CountryListItemHolder(
itemView: View,
private val onCountryClicked: () -> Unit
) : RecyclerView.ViewHolder(itemView) {
private var countryNameResId: Int = 0
private var callingCode: Int = 0
private var flagResID: Int = 0
fun setCountryListItem(countryNameResId: Int,
countryName: String,
callingCode: Int,
flagResID: Int) {
this.countryNameResId = countryNameResId
this.callingCode = callingCode
this.flagResID = flagResID
itemView.findViewById<TextView>(R.id.country_list_name).text = countryName
itemView.findViewById<TextView>(R.id.country_list_calling_code).text = "+$callingCode"
itemView.findViewById<ImageView>(R.id.country_list_flag).setImageResource(flagResID)
itemView.findViewById<View>(R.id.country_list_item).setOnClickListener {
Preference.putCountryNameResID(TracerApp.AppContext, countryNameResId)
Preference.putCallingCode(TracerApp.AppContext, callingCode)
Preference.putNationalFlagResID(TracerApp.AppContext, flagResID)
onCountryClicked()
}
}
}
class CountryListRecyclerViewAdapter(
private val context: Context,
private val countryListItem: List<CountryListItemInterface>,
private val onCountryClicked: () -> Unit
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): RecyclerView.ViewHolder {
return when (viewType) {
VIEW_TYPE_GROUP_TITLE -> CountryGroupTitleHolder(
LayoutInflater.from(context).inflate(
R.layout.view_list_item_group_title,
parent,
false
)
)
else -> CountryListItemHolder(
LayoutInflater.from(context).inflate(
R.layout.view_list_item_country,
parent,
false
),
onCountryClicked
)
}
}
override fun getItemViewType(position: Int): Int {
return when (countryListItem[position]) {
is CountryGroupTitle -> VIEW_TYPE_GROUP_TITLE
else -> VIEW_TYPE_COUNTRY
}
}
override fun getItemCount(): Int {
return countryListItem.size
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (holder) {
is CountryGroupTitleHolder -> {
val title = context.getString((countryListItem[position] as CountryGroupTitle).titleResId)
holder.setCountryGroupTitle(title)
}
is CountryListItemHolder -> {
val countryListItem = (countryListItem[position] as CountryListItem)
val countryName = context.getString(countryListItem.countryNameResId)
holder.setCountryListItem(
countryListItem.countryNameResId,
countryName,
countryListItem.callingCode,
countryListItem.flagResID
)
}
}
}
}

View file

@ -1,28 +1,35 @@
package au.gov.health.covidsafe.ui.onboarding.fragment.enternumber
import android.annotation.SuppressLint
import android.app.AlertDialog
import android.content.Intent
import android.os.Bundle
import android.text.Editable
import android.text.InputFilter
import android.text.TextWatcher
import android.text.method.LinkMovementMethod
import android.view.KeyEvent
import android.view.LayoutInflater
import android.view.View
import android.view.View.GONE
import android.view.View.VISIBLE
import android.view.ViewGroup
import android.view.inputmethod.EditorInfo
import android.widget.TextView
import android.widget.TextView.OnEditorActionListener
import androidx.annotation.NavigationRes
import androidx.core.content.ContextCompat
import androidx.core.os.bundleOf
import androidx.core.widget.addTextChangedListener
import au.gov.health.covidsafe.Preference
import au.gov.health.covidsafe.R
import au.gov.health.covidsafe.TracerApp
import au.gov.health.covidsafe.ui.PagerChildFragment
import au.gov.health.covidsafe.ui.UploadButtonLayout
import au.gov.health.covidsafe.ui.onboarding.CountryCodeSelectionActivity
import au.gov.health.covidsafe.ui.onboarding.fragment.enterpin.EnterPinFragment.Companion.ENTER_PIN_CALLING_CODE
import au.gov.health.covidsafe.ui.onboarding.fragment.enterpin.EnterPinFragment.Companion.ENTER_PIN_CHALLENGE_NAME
import au.gov.health.covidsafe.ui.onboarding.fragment.enterpin.EnterPinFragment.Companion.ENTER_PIN_DESTINATION_ID
import au.gov.health.covidsafe.ui.onboarding.fragment.enterpin.EnterPinFragment.Companion.ENTER_PIN_PHONE_NUMBER
import au.gov.health.covidsafe.ui.onboarding.fragment.enterpin.EnterPinFragment.Companion.ENTER_PIN_PROGRESS
import au.gov.health.covidsafe.ui.onboarding.fragment.enterpin.EnterPinFragment.Companion.ENTER_PIN_SESSION
import kotlinx.android.synthetic.main.fragment_enter_number.*
import kotlinx.android.synthetic.main.fragment_enter_number.view.*
class EnterNumberFragment : PagerChildFragment() {
@ -36,30 +43,21 @@ class EnterNumberFragment : PagerChildFragment() {
private val enterNumberPresenter = EnterNumberPresenter(this)
private var alertDialog: AlertDialog? = null
@NavigationRes
private var destinationId: Int? = null
private val phoneNumberTextWatcher: TextWatcher = object : TextWatcher {
override fun afterTextChanged(s: Editable?) {
// change LengthFilter if user making a mistake of entering phone number starting with 0
val phoneNumberLength = TracerApp.AppContext.resources.getInteger(R.integer.australian_phone_number_length)
val filters = enter_number_phone_number.filters
val newFilterLength = if (s?.toString()?.startsWith("0") == true) {
phoneNumberLength + 1
} else {
phoneNumberLength
}
enter_number_phone_number.filters = filters.filterNot { it is InputFilter.LengthFilter }.toTypedArray() +
InputFilter.LengthFilter(newFilterLength)
private lateinit var countryName: String
private var callingCode: Int = 0
private var nationalFlagResID: Int = 0
updateButtonState()
}
private val errorTextColor = ContextCompat.getColor(TracerApp.AppContext, R.color.error)
private val normalTextColor = ContextCompat.getColor(TracerApp.AppContext, R.color.slack_black)
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
}
private fun updateSelectedCountry() {
countryName = getString(Preference.getCountryNameResID(this.requireContext()))
callingCode = Preference.getCallingCode(this.requireContext())
nationalFlagResID = Preference.getNationalFlagResID(this.requireContext())
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?)
@ -67,7 +65,7 @@ class EnterNumberFragment : PagerChildFragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
view.use_oz_phone_number.movementMethod = LinkMovementMethod.getInstance()
arguments?.let {
destinationId = it.getInt(ENTER_NUMBER_DESTINATION_ID)
stepProgress = if (it.containsKey(ENTER_NUMBER_PROGRESS)) it.getInt(ENTER_PIN_PROGRESS) else null
@ -76,26 +74,74 @@ class EnterNumberFragment : PagerChildFragment() {
override fun onResume() {
super.onResume()
enter_number_phone_number.selectAll()
enter_number_phone_number.addTextChangedListener(phoneNumberTextWatcher)
updateSelectedCountry()
enter_number_phone_number.addTextChangedListener {
enter_number_phone_number.setTextColor(normalTextColor)
}
enter_number_phone_number.setOnEditorActionListener { _, actionId, event ->
if (actionId == EditorInfo.IME_ACTION_SEARCH ||
actionId == EditorInfo.IME_ACTION_DONE ||
event != null &&
event.action == KeyEvent.ACTION_DOWN &&
event.keyCode == KeyEvent.KEYCODE_ENTER) {
if (event == null || !event.isShiftPressed) {
// user has done typing.
updateButtonState()
}
override fun onPause() {
super.onPause()
enter_number_phone_number.removeTextChangedListener(phoneNumberTextWatcher)
}
fun showInvalidPhoneNumber() {
invalid_phone_number.visibility = VISIBLE
false // pass on to other listeners.
}
updateButtonState()
displaySelectedCountryOrRegion()
}
@SuppressLint("SetTextI18n")
private fun displaySelectedCountryOrRegion() {
country_name_code.text = "$countryName(+$callingCode)"
national_flag.setImageResource(nationalFlagResID)
country_selection_box.setOnClickListener {
startActivity(Intent(this.requireContext(), CountryCodeSelectionActivity::class.java))
}
}
private fun hideInvalidPhoneNumberPrompt() {
enter_number_headline.setTextColor(normalTextColor)
enter_number_phone_number.background = context?.getDrawable(R.drawable.edittext_modified_states)
enter_number_phone_number.setTextColor(normalTextColor)
invalid_phone_number.visibility = GONE
}
fun showInvalidPhoneNumberPrompt(errorMessageResID: Int) {
enter_number_headline.setTextColor(errorTextColor)
enter_number_phone_number.background = context?.getDrawable(R.drawable.phone_number_invalid_background)
enter_number_phone_number.setTextColor(errorTextColor)
invalid_phone_number.visibility = VISIBLE
invalid_phone_number.setText(errorMessageResID)
}
override fun updateButtonState() {
if (enterNumberPresenter.validateAuNumber(enter_number_phone_number?.text?.toString())) {
val phoneNumberValidity = enterNumberPresenter.validatePhoneNumber(
callingCode,
enter_number_phone_number.text.toString().trim()
)
if (phoneNumberValidity.first) {
enableContinueButton()
hideInvalidPhoneNumberPrompt()
} else {
disableContinueButton()
if (enter_number_phone_number.text.toString().isNotEmpty()) {
showInvalidPhoneNumberPrompt(phoneNumberValidity.second)
}
}
}
@ -110,10 +156,12 @@ class EnterNumberFragment : PagerChildFragment() {
fun navigateToOTPPage(
session: String?,
challengeName: String?,
callingCode: Int,
phoneNumber: String) {
val bundle = bundleOf(
ENTER_PIN_SESSION to session,
ENTER_PIN_CHALLENGE_NAME to challengeName,
ENTER_PIN_CALLING_CODE to callingCode,
ENTER_PIN_PHONE_NUMBER to phoneNumber,
ENTER_PIN_DESTINATION_ID to destinationId).also { bundle ->
stepProgress?.let {
@ -130,7 +178,7 @@ class EnterNumberFragment : PagerChildFragment() {
}
override fun getUploadButtonLayout() = UploadButtonLayout.ContinueLayout(R.string.enter_number_button) {
enterNumberPresenter.requestOTP(enter_number_phone_number.text.toString().trim())
enterNumberPresenter.requestOTP(callingCode, enter_number_phone_number.text.toString().trim())
}
fun showCheckInternetError() {

View file

@ -6,19 +6,21 @@ import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.OnLifecycleEvent
import au.gov.health.covidsafe.Preference
import au.gov.health.covidsafe.R
import au.gov.health.covidsafe.TracerApp
import au.gov.health.covidsafe.extensions.isInternetAvailable
import au.gov.health.covidsafe.factory.NetworkFactory
import au.gov.health.covidsafe.interactor.usecase.GetOnboardingOtp
import au.gov.health.covidsafe.interactor.usecase.GetOnboardingOtpException
import au.gov.health.covidsafe.interactor.usecase.GetOtpParams
const val AUSTRALIA_CALLING_CODE = 61
const val AUSTRALIA_MOBILE_NUMBER_LENGTH = 9
const val AUSTRALIA_MOBILE_NUMBER_PREFIX_DIGIT = "0"
const val NORFOLK_ISLAND_CALLING_CODE = 672
const val NORFOLK_ISLAND_MOBILE_NUMBER_LENGTH = 5
const val NORFOLK_ISLAND_MOBILE_PREFIX_DIGIT = "3"
class EnterNumberPresenter(private val enterNumberFragment: EnterNumberFragment) : LifecycleObserver {
private val TAG = this.javaClass.simpleName
private lateinit var phoneNumber: String
private lateinit var getOnboardingOtp: GetOnboardingOtp
init {
@ -30,47 +32,49 @@ class EnterNumberPresenter(private val enterNumberFragment: EnterNumberFragment)
getOnboardingOtp = GetOnboardingOtp(NetworkFactory.awsClient, enterNumberFragment.lifecycle)
}
internal fun requestOTP(phoneNumber: String) {
fun requestOTP(callingCode: Int, phoneNumber: String) {
val prefixZeroRemovedPhoneNumber =
adjustPrefixForAustralianAndNorfolkPhoneNumber(callingCode, phoneNumber)
when {
enterNumberFragment.activity?.isInternetAvailable() == false -> {
enterNumberFragment.showCheckInternetError()
}
validateAuNumber(phoneNumber) -> {
val cleansedNumber = if (phoneNumber.startsWith("0")) {
phoneNumber.takeLast(TracerApp.AppContext.resources.getInteger(R.integer.australian_phone_number_length))
} else phoneNumber
val fullNumber = "${enterNumberFragment.resources.getString(R.string.enter_number_prefix)}$cleansedNumber"
Preference.putPhoneNumber(TracerApp.AppContext, fullNumber)
this.phoneNumber = cleansedNumber
makeOTPCall(cleansedNumber)
}
else -> {
enterNumberFragment.showInvalidPhoneNumber()
}
else -> makeOTPCall(callingCode, prefixZeroRemovedPhoneNumber)
}
}
/**
* @param phoneNumber cleansed phone number, 9 digits, doesn't start with 0
* if [callingCode] is 61 for Australia, then [phoneNumber] should be a prefix removed
* Australian phone number: 9 digits, doesn't start with 0.
* Otherwise [phoneNumber] can be any number and the validation should be done in the backend
*/
private fun makeOTPCall(phoneNumber: String) {
private fun makeOTPCall(callingCode: Int, phoneNumber: String) {
enterNumberFragment.activity?.let {
enterNumberFragment.disableContinueButton()
enterNumberFragment.showLoading()
getOnboardingOtp.invoke(GetOtpParams(phoneNumber,
Preference.getDeviceID(enterNumberFragment.requireContext()),
Preference.getPostCode(enterNumberFragment.requireContext()),
Preference.getAge(enterNumberFragment.requireContext()),
Preference.getName(enterNumberFragment.requireContext())),
val context = enterNumberFragment.requireContext()
getOnboardingOtp.invoke(
GetOtpParams(
countryCode = "+$callingCode",
phoneNumber = phoneNumber,
deviceId = Preference.getDeviceID(context),
postCode = Preference.getPostCode(context),
age = Preference.getAge(context),
name = Preference.getName(context)
),
onSuccess = {
enterNumberFragment.navigateToOTPPage(
it.session,
it.challengeName,
callingCode,
phoneNumber)
},
onFailure = {
if (it is GetOnboardingOtpException.GetOtpInvalidNumberException) {
enterNumberFragment.showInvalidPhoneNumber()
enterNumberFragment.showInvalidPhoneNumberPrompt(R.string.invalid_phone_number)
} else {
enterNumberFragment.showGenericError()
}
@ -80,12 +84,46 @@ class EnterNumberPresenter(private val enterNumberFragment: EnterNumberFragment)
}
}
internal fun validateAuNumber(phoneNumber: String?): Boolean {
var australianPhoneNumberLength = enterNumberFragment.resources.getInteger(R.integer.australian_phone_number_length)
if (phoneNumber?.startsWith("0") == true) {
australianPhoneNumberLength++
fun validatePhoneNumber(callingCode: Int, phoneNumber: String): Pair<Boolean, Int> {
val isNumberValid = when (callingCode) {
AUSTRALIA_CALLING_CODE -> {
if (phoneNumber.startsWith(AUSTRALIA_MOBILE_NUMBER_PREFIX_DIGIT)) {
phoneNumber.length == AUSTRALIA_MOBILE_NUMBER_LENGTH + 1
} else {
phoneNumber.length == AUSTRALIA_MOBILE_NUMBER_LENGTH
}
}
NORFOLK_ISLAND_CALLING_CODE -> {
if (phoneNumber.startsWith(NORFOLK_ISLAND_MOBILE_PREFIX_DIGIT)) {
phoneNumber.length == NORFOLK_ISLAND_MOBILE_NUMBER_LENGTH + 1
} else {
phoneNumber.length == NORFOLK_ISLAND_MOBILE_NUMBER_LENGTH
}
}
else -> true
}
val errorMessageResID = when (callingCode) {
AUSTRALIA_CALLING_CODE -> R.string.invalid_australian_phone_number_error_prompt
NORFOLK_ISLAND_CALLING_CODE -> R.string.invalid_norfolk_island_phone_number_error_prompt
else -> 0
}
return Pair(isNumberValid, errorMessageResID)
}
private fun adjustPrefixForAustralianAndNorfolkPhoneNumber(callingCode: Int, phoneNumber: String): String {
return when (callingCode) {
AUSTRALIA_CALLING_CODE -> phoneNumber.removePrefix(AUSTRALIA_MOBILE_NUMBER_PREFIX_DIGIT)
NORFOLK_ISLAND_CALLING_CODE -> {
if (phoneNumber.length == NORFOLK_ISLAND_MOBILE_NUMBER_LENGTH) {
"$NORFOLK_ISLAND_MOBILE_PREFIX_DIGIT$phoneNumber"
} else {
phoneNumber
}
}
else -> phoneNumber
}
return (phoneNumber?.length ?: 0) == australianPhoneNumberLength
}
}

View file

@ -24,6 +24,7 @@ class EnterPinFragment : PagerChildFragment() {
companion object {
const val ENTER_PIN_SESSION = "session"
const val ENTER_PIN_CHALLENGE_NAME = "challenge_name"
const val ENTER_PIN_CALLING_CODE = "calling_code"
const val ENTER_PIN_PHONE_NUMBER = "phone_number"
const val ENTER_PIN_DESTINATION_ID = "destination_id"
const val ENTER_PIN_PROGRESS = "progress"
@ -48,13 +49,18 @@ class EnterPinFragment : PagerChildFragment() {
arguments?.let {
val session = it.getString(ENTER_PIN_SESSION)
val challengeName = it.getString(ENTER_PIN_CHALLENGE_NAME)
val callingCode = it.getInt(ENTER_PIN_CALLING_CODE)
val phoneNumber = it.getString(ENTER_PIN_PHONE_NUMBER)
destinationId = it.getInt(ENTER_PIN_DESTINATION_ID)
stepProgress = if (it.containsKey(ENTER_PIN_PROGRESS)) it.getInt(ENTER_PIN_PROGRESS) else null
enter_pin_headline.text = resources.getString(R.string.enter_pin_headline, resources.getString(R.string.enter_number_prefix), phoneNumber)
enter_pin_headline.text = resources.getString(R.string.enter_pin_headline, "+$callingCode", phoneNumber)
presenter = EnterPinPresenter(this@EnterPinFragment,
session,
challengeName,
callingCode,
phoneNumber)
}

View file

@ -19,6 +19,7 @@ import retrofit2.Response
class EnterPinPresenter(private val enterPinFragment: EnterPinFragment,
private var session: String?,
private var challengeName: String?,
private val callingCode: Int,
private val phoneNumber: String?) : LifecycleObserver {
private val TAG = this.javaClass.simpleName
@ -45,11 +46,17 @@ class EnterPinPresenter(private val enterPinFragment: EnterPinFragment,
enterPinFragment.showGenericError()
}
else -> {
getOtp.invoke(GetOtpParams(phoneNumber,
Preference.getDeviceID(enterPinFragment.requireContext()),
Preference.getPostCode(enterPinFragment.requireContext()),
Preference.getAge(enterPinFragment.requireContext()),
Preference.getName(enterPinFragment.requireContext())),
val context = enterPinFragment.requireContext()
getOtp.invoke(
GetOtpParams(
countryCode = "+$callingCode",
phoneNumber = phoneNumber,
deviceId = Preference.getDeviceID(context),
postCode = Preference.getPostCode(context),
age = Preference.getAge(context),
name = Preference.getName(context)
),
onSuccess = {
session = it.session
challengeName = it.challengeName

View file

@ -3,20 +3,27 @@ package au.gov.health.covidsafe.ui.onboarding.fragment.personal
import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
import android.view.KeyEvent
import android.view.LayoutInflater
import android.view.View
import android.view.View.GONE
import android.view.View.VISIBLE
import android.view.ViewGroup
import android.view.accessibility.AccessibilityEvent
import android.view.inputmethod.EditorInfo
import android.widget.NumberPicker
import androidx.appcompat.app.AlertDialog
import androidx.core.os.bundleOf
import androidx.core.widget.addTextChangedListener
import au.gov.health.covidsafe.Preference
import au.gov.health.covidsafe.R
import au.gov.health.covidsafe.ui.PagerChildFragment
import au.gov.health.covidsafe.ui.UploadButtonLayout
import au.gov.health.covidsafe.ui.onboarding.fragment.enternumber.EnterNumberFragment
import kotlinx.android.synthetic.main.fragment_enter_number.*
import kotlinx.android.synthetic.main.fragment_personal_details.*
import java.util.regex.Pattern
private val POST_CODE_REGEX = Pattern.compile("^(?:(?:[2-8]\\d|9[0-7]|0?[28]|0?9(?=09))(?:\\d{2}))$")
class PersonalDetailsFragment : PagerChildFragment() {
@ -25,122 +32,39 @@ class PersonalDetailsFragment : PagerChildFragment() {
private var alertDialog: AlertDialog? = null
override var stepProgress: Int? = 1
override val navigationIcon: Int = R.drawable.ic_up
private var ageSelected: Pair<String, String>? = null
private val presenter = PersonalDetailsPresenter(this)
private var ageSelected: Pair<Int, String> = Pair(-1, "")
private val nameTextWatcher: TextWatcher = object : TextWatcher {
override fun afterTextChanged(s: Editable?) {
hideNameError()
updateButtonState()
private lateinit var name: String
private lateinit var postcode: String
private var age: Int = -1
private fun updatePersonalDetailsDataField() {
name = personal_details_name.text.toString()
postcode = personal_details_post_code.text.toString()
age = ageSelected.first
}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
}
}
private val postCodeTextWatcher: TextWatcher = object : TextWatcher {
override fun afterTextChanged(s: Editable?) {
presenter.validateInlinePostCode(s.toString())
updateButtonState()
}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
}
}
private fun isFullName() = name.trim().length > 1
private fun isValidAge() = age >= 0
private fun isValidPostcode() = postcode.length == 4 && POST_CODE_REGEX.matcher(postcode).matches()
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?)
: View? = inflater.inflate(R.layout.fragment_personal_details, container, false)
override fun onResume() {
super.onResume()
personal_details_name.addTextChangedListener(nameTextWatcher)
personal_details_post_code.addTextChangedListener(postCodeTextWatcher)
personal_details_age.setOnClickListener {
showAgePicker()
}
personal_details_age.text = ageSelected?.second
// set accessibility focus to the title "Enter your details"
personal_details_headline.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED)
}
personal_details_age.setText(ageSelected.second)
override fun onPause() {
super.onPause()
personal_details_name.removeTextChangedListener(nameTextWatcher)
personal_details_post_code.removeTextChangedListener(postCodeTextWatcher)
personal_details_age.setOnClickListener(null)
alertDialog?.dismiss()
}
override fun getUploadButtonLayout(): UploadButtonLayout = UploadButtonLayout.ContinueLayout(R.string.personal_details_button) {
presenter.saveInfos(personal_details_name.text.toString(), personal_details_post_code.text.toString(), getMidAgeToSend())
}
override fun updateButtonState() {
if (presenter.validateInputsForButtonUpdate(personal_details_name.text.toString(), personal_details_post_code.text.toString(), getMidAgeToSend())) {
enableContinueButton()
} else {
disableContinueButton()
}
}
fun showGenericError() {
activity?.let { activity ->
alertDialog?.dismiss()
alertDialog = AlertDialog.Builder(activity)
.setMessage(R.string.generic_error)
.setIcon(android.R.drawable.ic_dialog_alert)
.setPositiveButton(android.R.string.yes, null).show()
}
}
fun navigateToNextPage(minor: Boolean) {
if (minor) {
navigateTo(PersonalDetailsFragmentDirections.actionPersonalDetailsToUnderSixteenFragment().actionId)
} else {
val bundle = bundleOf(
EnterNumberFragment.ENTER_NUMBER_DESTINATION_ID to R.id.action_otpFragment_to_permissionFragment,
EnterNumberFragment.ENTER_NUMBER_PROGRESS to 2)
navigateTo(PersonalDetailsFragmentDirections.actionPersonalDetailsToEnterNumberFragment().actionId, bundle)
}
}
fun showPostcodeError() {
personal_details_post_code_error.visibility = VISIBLE
}
fun hidePostcodeError() {
personal_details_post_code_error.visibility = GONE
}
fun showNameError() {
personal_details_name_error.visibility = VISIBLE
}
fun hideNameError() {
personal_details_name_error.visibility = GONE
}
fun showAgeError() {
personal_details_age_error.visibility = VISIBLE
}
fun hideAgeError() {
personal_details_age_error.visibility = GONE
}
private fun showAgePicker() {
fun showAgePicker() {
activity?.let { activity ->
val ages = resources.getStringArray(R.array.personal_details_age_array).map {
it.split(":").let { it[0] to it[1] }
it.split(":").let { split ->
(split[0]).toInt() to split[1]
}
}
var selected = ages.firstOrNull { it == ageSelected }?.let {
ages.indexOf(it)
} ?: 0
@ -159,24 +83,127 @@ class PersonalDetailsFragment : PagerChildFragment() {
.setView(picker)
.setPositiveButton(R.string.personal_details_dialog_ok) { _, _ ->
ageSelected = ages[selected]
personal_details_age.text = ages[selected].second
hideAgeError()
personal_details_age.setText(ageSelected.second)
updatePersonalDetailsDataField()
updateButtonState()
personal_details_age_error.visibility =
if (isValidAge()) {
View.GONE
} else {
View.VISIBLE
}
personal_details_post_code.requestFocus()
}
.setNegativeButton(android.R.string.no, null)
.show()
}
}
private fun getMidAgeToSend(): String? {
val ages = resources.getStringArray(R.array.personal_details_age_array).map {
it.split(":").let { it[0] to it[1] }
personal_details_name.setOnFocusChangeListener { _, hasFocus ->
updatePersonalDetailsDataField()
updateButtonState()
personal_details_name_error.visibility = if (hasFocus || isFullName()) {
View.GONE
} else {
View.VISIBLE
}
val selected = ages.firstOrNull { it == ageSelected }?.let {
ages.indexOf(it)
}
return selected?.let {
ages[selected].first
personal_details_post_code.setOnFocusChangeListener { _, hasFocus ->
if(hasFocus) {
updatePersonalDetailsDataField()
updateButtonState()
personal_details_age_error.visibility =
if (isValidAge()) {
View.GONE
} else {
View.VISIBLE
}
}
}
personal_details_post_code.setOnEditorActionListener { _, actionId, event ->
if (actionId == EditorInfo.IME_ACTION_SEARCH ||
actionId == EditorInfo.IME_ACTION_DONE ||
event != null &&
event.action == KeyEvent.ACTION_DOWN &&
event.keyCode == KeyEvent.KEYCODE_ENTER) {
if (event == null || !event.isShiftPressed) {
// user has done typing.
updatePersonalDetailsDataField()
updateButtonState()
personal_details_age_error.visibility =
if (isValidAge()) {
View.GONE
} else {
View.VISIBLE
}
personal_details_post_code_error.visibility = if (isValidPostcode()) {
View.GONE
} else {
View.VISIBLE
}
}
}
false // pass on to other listeners.
}
personal_details_age.setOnFocusChangeListener { _, hasFocus ->
if(hasFocus){
showAgePicker()
}
}
personal_details_age.setOnClickListener {
showAgePicker()
}
// set accessibility focus to the title "Enter your details"
personal_details_headline.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED)
}
override fun onPause() {
super.onPause()
alertDialog?.dismiss()
}
override fun getUploadButtonLayout(): UploadButtonLayout = UploadButtonLayout.ContinueLayout(R.string.personal_details_button) {
val context = this.requireContext()
Preference.putName(context, name)
Preference.putAge(context, "$age")
Preference.putPostCode(context, postcode)
navigateToNextPage(age < 16)
}
override fun updateButtonState() {
updatePersonalDetailsDataField()
if (isFullName() && isValidAge() && isValidPostcode()) {
enableContinueButton()
} else {
disableContinueButton()
}
}
private fun navigateToNextPage(isUnder16: Boolean) {
if (isUnder16) {
navigateTo(PersonalDetailsFragmentDirections.actionPersonalDetailsToUnderSixteenFragment().actionId)
} else {
val bundle = bundleOf(
EnterNumberFragment.ENTER_NUMBER_DESTINATION_ID to R.id.action_otpFragment_to_permissionFragment,
EnterNumberFragment.ENTER_NUMBER_PROGRESS to 2)
navigateTo(PersonalDetailsFragmentDirections.actionPersonalDetailsToEnterNumberFragment().actionId, bundle)
}
}
}

View file

@ -1,96 +0,0 @@
package au.gov.health.covidsafe.ui.onboarding.fragment.personal
import au.gov.health.covidsafe.Preference
import java.util.regex.Pattern
class PersonalDetailsPresenter(private val personalDetailsFragment: PersonalDetailsFragment) {
private val TAG = this.javaClass.simpleName
private val POST_CODE_REGEX = Pattern.compile("^(?:(?:[2-8]\\d|9[0-7]|0?[28]|0?9(?=09))(?:\\d{2}))$")
fun saveInfos(name: String?, postCode: String?, age: String?) {
personalDetailsFragment.showLoading()
personalDetailsFragment.context?.let { context ->
val ageInt = age?.toIntOrNull()
val nameValid = name.isNullOrBlank().not()
val postCodeValid = postCode.isNullOrBlank().not() && isPostCodeValid(postCode)
val ageValid = age.isNullOrBlank().not()
if (nameValid && postCodeValid && ageValid) {
val valid = (name?.let { name ->
Preference.putName(context, name)
} ?: false) &&
(age?.let { age ->
Preference.putAge(context, age)
} ?: false) &&
(postCode?.let { postCode ->
Preference.putPostCode(context, postCode)
} ?: false)
if (valid) {
personalDetailsFragment.hideLoading()
personalDetailsFragment.navigateToNextPage(ageInt?.let { it < 16 } ?: false)
} else {
personalDetailsFragment.hideLoading()
personalDetailsFragment.showGenericError()
}
} else {
showFieldsError(name, postCode, age)
personalDetailsFragment.hideLoading()
}
} ?: run {
personalDetailsFragment.hideLoading()
personalDetailsFragment.showGenericError()
}
}
private fun showFieldsError(name: String?, postCode: String?, age: String?) {
updateNameFieldError(name)
updateAgeFieldError(age)
updatePostcodeFieldError(postCode)
}
private fun updateAgeFieldError(age: String?) {
return if (age.isNullOrBlank()) {
personalDetailsFragment.showAgeError()
} else {
personalDetailsFragment.hideAgeError()
}
}
private fun updateNameFieldError(name: String?) {
return if (name.isNullOrBlank()) {
personalDetailsFragment.showNameError()
} else {
personalDetailsFragment.hideNameError()
}
}
private fun updatePostcodeFieldError(postCode: String?) {
return if (postCode.isNullOrBlank()) {
personalDetailsFragment.showPostcodeError()
} else {
personalDetailsFragment.hidePostcodeError()
}
}
fun validateInputsForButtonUpdate(name: String?, postCode: String?, age: String?): Boolean {
val nameValid = name.isNullOrBlank().not()
val postCodeValid = postCode.isNullOrBlank().not() && isPostCodeValid(postCode)
val ageValid = age.isNullOrBlank().not()
return nameValid && postCodeValid && ageValid
}
internal fun validateInlinePostCode(postCode: String?) {
if (!postCode.isNullOrEmpty() && postCode.length == 4 && !isPostCodeValid(postCode)) {
personalDetailsFragment.showPostcodeError()
} else {
personalDetailsFragment.hidePostcodeError()
}
}
private fun isPostCodeValid(postCode: String?) = POST_CODE_REGEX.matcher(postCode.toString()).matches()
}

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="14dp"
android:height="19dp"
android:viewportWidth="14"
android:viewportHeight="19">
<path
android:pathData="M12.2969,9.0156H13.9844C13.9844,10.7031 13.3906,12.1875 12.2031,13.4688C11.0469,14.7188 9.6406,15.4688 7.9844,15.7188V19H6.0156V15.7188C4.3594,15.4688 2.9375,14.7188 1.75,13.4688C0.5938,12.1875 0.0156,10.7031 0.0156,9.0156H1.7031C1.7031,10.4844 2.2188,11.7031 3.25,12.6719C4.3125,13.6094 5.5625,14.0781 7,14.0781C8.4375,14.0781 9.6719,13.6094 10.7031,12.6719C11.7656,11.7031 12.2969,10.4844 12.2969,9.0156ZM9.1094,11.125C8.5156,11.7188 7.8125,12.0156 7,12.0156C6.1875,12.0156 5.4844,11.7188 4.8906,11.125C4.2969,10.5312 4,9.8281 4,9.0156V3.0156C4,2.2031 4.2969,1.5 4.8906,0.9063C5.4844,0.3125 6.1875,0.0156 7,0.0156C7.8125,0.0156 8.5156,0.3125 9.1094,0.9063C9.7031,1.5 10,2.2031 10,3.0156V9.0156C10,9.8281 9.7031,10.5312 9.1094,11.125Z"
android:fillColor="#666666"/>
</vector>

View file

@ -0,0 +1,13 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="8dp"
android:height="14dp"
android:viewportWidth="8"
android:viewportHeight="14">
<path
android:pathData="M1,13L7,7L1,1"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#131313"
android:strokeLineCap="round"/>
</vector>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="18dp"
android:height="18dp"
android:viewportWidth="18"
android:viewportHeight="18">
<path
android:pathData="M3.3281,9.7031C4.2031,10.5781 5.2656,11.0156 6.5156,11.0156C7.7656,11.0156 8.8281,10.5781 9.7031,9.7031C10.5781,8.8281 11.0156,7.7656 11.0156,6.5156C11.0156,5.2656 10.5781,4.2031 9.7031,3.3281C8.8281,2.4531 7.7656,2.0156 6.5156,2.0156C5.2656,2.0156 4.2031,2.4531 3.3281,3.3281C2.4531,4.2031 2.0156,5.2656 2.0156,6.5156C2.0156,7.7656 2.4531,8.8281 3.3281,9.7031ZM12.5156,11.0156L17.4844,15.9844L15.9844,17.4844L11.0156,12.5156V11.7188L10.7344,11.4375C9.5469,12.4688 8.1406,12.9844 6.5156,12.9844C4.7031,12.9844 3.1563,12.3594 1.875,11.1094C0.625,9.8594 0,8.3281 0,6.5156C0,4.7031 0.625,3.1719 1.875,1.9219C3.1563,0.6406 4.7031,0 6.5156,0C8.3281,0 9.8594,0.6406 11.1094,1.9219C12.3594,3.1719 12.9844,4.7031 12.9844,6.5156C12.9844,8.1406 12.4688,9.5469 11.4375,10.7344L11.7188,11.0156H12.5156Z"
android:fillColor="#666666"/>
</vector>

View file

@ -0,0 +1,78 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.appcompat.widget.Toolbar
android:id="@+id/countrySelectionToolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navigationIcon="@drawable/ic_up"
app:navigationContentDescription="@string/navigation_back_button_content_description"/>
<TextView
android:layout_width="match_parent"
android:layout_height="@dimen/keyline_6"
android:layout_marginStart="@dimen/keyline_1"
android:text="@string/search" />
<FrameLayout
android:layout_width="match_parent"
android:layout_height="@dimen/keyline_8"
android:layout_marginStart="@dimen/keyline_1"
android:layout_marginEnd="@dimen/keyline_1"
android:paddingTop="@dimen/keyline_0"
android:paddingBottom="@dimen/keyline_0"
android:paddingStart="@dimen/keyline_1"
android:paddingEnd="@dimen/keyline_0"
android:background="@drawable/edit_text_black_background">
<ImageView
android:id="@+id/countrySearchImageView"
android:layout_width="18dp"
android:layout_height="18dp"
android:src="@drawable/ic_search"
android:focusable="false"
android:focusableInTouchMode="false"
android:clickable="false"
android:layout_gravity="center|start" />
<EditText
android:id="@+id/countryRegionNameEditText"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/transparent" />
<ImageView
android:id="@+id/microphoneImageView"
android:layout_width="@dimen/keyline_6"
android:paddingStart="@dimen/keyline_1"
android:paddingEnd="@dimen/keyline_1"
android:layout_height="match_parent"
android:layout_gravity="end"
android:src="@drawable/ic_microphone" />
</FrameLayout>
<FrameLayout
android:layout_width="match_parent"
android:layout_marginTop="@dimen/keyline_4"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/countryListRecyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/countryInitialLetterRecyclerView"
android:layout_width="@dimen/keyline_8"
android:layout_height="match_parent"
android:layout_gravity="right" />
</FrameLayout>
</LinearLayout>

View file

@ -12,41 +12,97 @@
android:layout_height="wrap_content">
<TextView
android:id="@+id/enter_number_headline"
android:id="@+id/enter_number_page_headline"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/keyline_5"
android:layout_marginEnd="@dimen/keyline_5"
android:gravity="center_vertical"
android:text="@string/enter_number_headline"
android:textAppearance="?textAppearanceHeadline2"
android:layout_marginStart="@dimen/keyline_5"
android:layout_marginEnd="@dimen/keyline_5"
android:textSize="28sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/enter_number_prefix"
android:layout_width="wrap_content"
android:id="@+id/select_country_or_region"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/enter_number_prefix"
android:textColor="@color/slack_black"
android:textSize="@dimen/text_phone_number"
android:layout_marginStart="@dimen/keyline_5"
app:layout_constraintBottom_toBottomOf="@+id/enter_number_phone_number"
android:layout_marginTop="@dimen/keyline_5"
android:layout_marginEnd="@dimen/keyline_5"
android:gravity="center_vertical"
android:text="@string/select_country_or_region"
android:textAppearance="?textAppearanceBody1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/enter_number_phone_number" />
app:layout_constraintTop_toBottomOf="@+id/enter_number_page_headline" />
<FrameLayout
android:id="@+id/country_selection_box"
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_marginStart="@dimen/keyline_5"
android:layout_marginTop="@dimen/keyline_4"
android:layout_marginEnd="@dimen/keyline_5"
android:background="@drawable/edit_text_black_background"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/select_country_or_region">
<TextView
android:id="@+id/country_name_code"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_marginStart="@dimen/keyline_2"
android:gravity="start|center_vertical"
android:textAppearance="?textAppearanceBody1" />
<ImageView
android:id="@+id/national_flag"
android:layout_width="wrap_content"
android:layout_height="36dp"
android:layout_gravity="end|center_vertical"
android:layout_marginEnd="@dimen/keyline_6"
android:src="@drawable/ic_list_country_au" />
<ImageView
android:layout_width="6dp"
android:layout_height="12dp"
android:layout_gravity="end|center_vertical"
android:layout_marginEnd="@dimen/keyline_4"
android:src="@drawable/ic_right" />
</FrameLayout>
<TextView
android:id="@+id/enter_number_headline"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/keyline_5"
android:layout_marginTop="@dimen/keyline_5"
android:layout_marginEnd="@dimen/keyline_5"
android:gravity="center_vertical"
android:text="@string/enter_number_headline"
android:textAppearance="?textAppearanceBody1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/country_selection_box" />
<EditText
android:id="@+id/enter_number_phone_number"
android:layout_width="0dp"
android:layout_height="@dimen/text_field_height"
android:layout_marginStart="@dimen/keyline_4"
android:layout_marginStart="@dimen/keyline_5"
android:layout_marginTop="@dimen/keyline_4"
android:layout_marginEnd="@dimen/keyline_5"
android:autofillHints="phoneNational"
android:background="@drawable/edittext_modified_states"
android:layout_marginEnd="@dimen/keyline_5"
android:digits="0123456789"
android:inputType="number|phone"
android:maxLength="10"
android:maxLength="20"
android:maxLines="1"
android:paddingStart="@dimen/keyline_1"
android:paddingEnd="@dimen/keyline_1"
@ -55,63 +111,40 @@
android:textColorHighlight="@color/dark_cerulean_3"
android:textCursorDrawable="@null"
android:textSize="@dimen/text_phone_number"
android:digits="0123456789"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/enter_number_prefix"
app:layout_constraintTop_toBottomOf="@+id/enter_number_headline"
tools:text="412382192" />
<androidx.constraintlayout.widget.Barrier
android:id="@+id/enter_number_barrier"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="bottom"
app:constraint_referenced_ids="enter_number_prefix,enter_number_phone_number,invalid_phone_number" />
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/enter_number_headline" />
<TextView
android:id="@+id/invalid_phone_number"
android:layout_width="0dp"
android:layout_height="24dp"
android:layout_marginTop="@dimen/keyline_1"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:layout_marginEnd="@dimen/keyline_5"
android:text="@string/invalid_phone_number"
android:textColor="@color/error"
android:textSize="16sp"
android:visibility="gone"
android:layout_marginStart="@dimen/keyline_5"
android:layout_marginEnd="@dimen/keyline_5"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="@+id/enter_number_phone_number"
app:layout_constraintTop_toBottomOf="@+id/enter_number_phone_number"
app:layout_constraintEnd_toEndOf="parent"
tools:text="@string/invalid_phone_number"
tools:visibility="visible"
/>
<TextView
android:id="@+id/use_oz_phone_number"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/keyline_4"
android:layout_marginStart="@dimen/keyline_5"
android:layout_marginEnd="@dimen/keyline_5"
android:text="@string/oz_phone_number"
android:textAppearance="?textAppearanceBody1"
android:textColorLink="?attr/colorPrimary"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/invalid_phone_number" />
tools:visibility="visible" />
<TextView
android:id="@+id/enter_number_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/keyline_4"
android:layout_marginStart="@dimen/keyline_5"
android:layout_marginTop="@dimen/keyline_4"
android:layout_marginEnd="@dimen/keyline_5"
android:text="@string/enter_number_content"
tools:text="@string/enter_number_content"
android:textAppearance="?textAppearanceBody1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/use_oz_phone_number" />
app:layout_constraintTop_toBottomOf="@+id/invalid_phone_number"
tools:text="@string/enter_number_content" />
<FrameLayout
android:id="@+id/enter_number_relativebackground"
@ -143,11 +176,11 @@
android:id="@+id/enter_number_relative"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/keyline_1"
android:layout_marginEnd="@dimen/keyline_4"
android:text="@string/enter_number_relative"
android:textAppearance="?textAppearanceBody2"
android:textColor="@color/slack_black"
android:layout_marginStart="@dimen/keyline_1"
android:layout_marginEnd="@dimen/keyline_4"
app:layout_constraintEnd_toEndOf="@+id/enter_number_relativebackground"
app:layout_constraintStart_toEndOf="@+id/enter_number_relative_icon"
app:layout_constraintTop_toBottomOf="@+id/enter_number_top_margin" />

View file

@ -11,13 +11,14 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="@dimen/keyline_5"
android:paddingEnd="@dimen/keyline_5">
android:paddingEnd="@dimen/keyline_5"
android:paddingBottom="@dimen/keyline_9">
<TextView
android:id="@+id/personal_details_headline"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/keyline_6"
android:layout_marginTop="@dimen/keyline_0"
android:text="@string/personal_details_headline"
android:contentDescription="@string/personal_details_headline_content_description"
android:textAppearance="?textAppearanceHeadline2"
@ -61,9 +62,9 @@
<TextView
android:id="@+id/personal_details_name_error"
android:layout_width="0dp"
android:layout_height="24dp"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/keyline_1"
android:text="@string/invalid_name"
android:text="@string/personal_details_name_error_prompt"
android:textColor="@color/error"
android:visibility="gone"
app:layout_constraintStart_toEndOf="parent"
@ -90,7 +91,7 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/personal_details_name_barrier" />
<TextView
<EditText
android:id="@+id/personal_details_age"
android:layout_width="0dp"
android:layout_height="@dimen/text_field_height"
@ -114,9 +115,9 @@
<TextView
android:id="@+id/personal_details_age_error"
android:layout_width="0dp"
android:layout_height="24dp"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/keyline_1"
android:text="@string/invalid_age"
android:text="@string/personal_details_age_error_prompt"
android:textColor="@color/error"
android:visibility="gone"
app:layout_constraintStart_toEndOf="parent"
@ -169,15 +170,14 @@
<TextView
android:id="@+id/personal_details_post_code_error"
android:layout_width="0dp"
android:layout_height="24dp"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/keyline_1"
android:text="@string/invalid_post_code"
android:text="@string/personal_details_post_code_error_prompt"
android:textColor="@color/error"
android:visibility="gone"
app:layout_constraintStart_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/personal_details_post_code"
tools:text="@string/invalid_post_code"
tools:visibility="visible" />
<androidx.constraintlayout.widget.Barrier

View file

@ -3,7 +3,7 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="@dimen/permission_height"
android:layout_height="wrap_content"
tools:parentTag="android.widget.FrameLayout">
<androidx.constraintlayout.widget.ConstraintLayout
@ -12,30 +12,42 @@
android:background="?android:selectableItemBackground"
android:padding="@dimen/keyline_4">
<ImageView
android:id="@+id/permission_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/icon_checkbox"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/permission_title"
style="?textAppearanceBody1"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:ellipsize="end"
android:gravity="start|center_vertical"
android:gravity="start"
android:maxLines="1"
android:singleLine="true"
android:visibility="visible"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@+id/permission_icon"
app:layout_constraintTop_toTopOf="parent"
tools:text="Bluetooth : " />
<ImageView
android:id="@+id/permission_icon"
android:layout_width="wrap_content"
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/permission_body"
style="?textAppearanceBody2"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="@drawable/icon_checkbox"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
android:gravity="start"
android:paddingTop="@dimen/keyline_0"
android:visibility="gone"
app:layout_constraintTop_toBottomOf="@id/permission_title"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@+id/permission_icon"
tools:visibility="visible"
tools:text="Allow push notification" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -0,0 +1,44 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/country_list_item"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="@dimen/keyline_4"
android:paddingTop="@dimen/keyline_1"
android:paddingBottom="@dimen/keyline_1">
<TextView
android:id="@+id/country_list_name"
android:layout_width="0dp"
android:layout_height="@dimen/keyline_5"
android:ellipsize="end"
android:lines="1"
android:gravity="start"
android:text="@string/country_au"
android:textSize="18sp"
app:layout_constraintEnd_toStartOf="@+id/country_list_flag"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/country_list_calling_code"
android:layout_width="wrap_content"
android:layout_height="21dp"
android:text="+61"
android:textSize="16sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/country_list_name" />
<ImageView
android:id="@+id/country_list_flag"
android:layout_width="26dp"
android:layout_height="19dp"
android:layout_marginTop="@dimen/keyline_1"
android:layout_marginEnd="@dimen/keyline_8"
android:src="@drawable/ic_list_country_au"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@+id/country_list_name" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/country_initial_letter"
style="@style/countryListIndex"
android:text="A" />

View file

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#E5E5E5"
android:paddingLeft="16dp">
<TextView
android:id="@+id/country_group_title"
android:layout_width="wrap_content"
android:layout_height="24dp"
android:text="@string/group_title_a"
android:textColor="#131313"
android:textSize="17sp"
android:textStyle="bold"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</FrameLayout>

View file

@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<integer name="australian_phone_number_length">9</integer>
</resources>

View file

@ -61,16 +61,16 @@
<string name="personal_details_headline_content_description">Heading, Enter your details</string>
<string name="personal_details_name_title">Full name</string>
<string name="personal_details_name_content_description">Enter full name</string>
<string name="personal_details_name_error_prompt">Please enter your full name.</string>
<string name="personal_details_age_title">Age range (select)</string>
<string name="personal_details_age_content_description">Select age range</string>
<string name="personal_details_post_code">Postcode</string>
<string name="personal_details_age_error_prompt">Please select your age range.</string>
<string name="personal_details_post_code">Postcode in Australia</string>
<string name="personal_details_post_code_hint">e.g. 2000</string>
<string name="personal_details_post_code_content_description">Enter postcode</string>
<string name="personal_details_post_code_error_prompt">Your Australian postcode number must contain 4 digits. </string>
<string name="personal_details_disclaimer"><a href="https://www.health.gov.au/using-our-websites/privacy/privacy-notice-for-covidsafe-app">privacy policy</a></string>
<string name="personal_details_post_code_dialog_title">Postcode</string>
<string name="invalid_post_code">Invalid postcode</string>
<string name="invalid_age">Age cannot be empty</string>
<string name="invalid_name">Invalid name</string>
<string name="personal_details_age_dialog_title">Select your age</string>
<string name="personal_details_dialog_ok">Select</string>
@ -98,13 +98,258 @@
<!-- OnBoarding Enter Number -->
<string name="select_country_or_region">Select country or region</string>
<string name="enter_number_headline">Enter your mobile number</string>
<string name="enter_number_for_example">For example:</string>
<string name="invalid_phone_number">Invalid phone number.</string>
<string name="enter_number_prefix">+61</string>
<string name="invalid_australian_phone_number_error_prompt">Australian mobile numbers contain a maximum of 10 digits.</string>
<string name="invalid_norfolk_island_phone_number_error_prompt">Mobile numbers in Norfolk Island contain 5 to 6 digits.</string>
<string name="invalid_phone_number_digits_error_prompt">Mobile numbers in %1s contain %2s digits.</string>
<string name="oz_phone_number"><a href="https://www.covidsafe.gov.au/help-topics.html#verify-mobile-number-pin">Use an Australian phone number.</a></string>
<string name="enter_number_content">Well send you a six-digit PIN to verify your mobile number.</string>
<string name="enter_number_relative">Trying to register on behalf of a friend or relative?\n\nThey will need to register using their own device and phone number so that COVIDSafe can work for them. </string>
<string name="enter_number_button">Get PIN</string>
<string name="search">Search</string>
<string name="options_for_australia">Options for Australia</string>
<string name="group_title_a">A</string>
<string name="group_title_b">B</string>
<string name="group_title_c">C</string>
<string name="group_title_d">D</string>
<string name="group_title_e">E</string>
<string name="group_title_f">F</string>
<string name="group_title_g">G</string>
<string name="group_title_h">H</string>
<string name="group_title_i">I</string>
<string name="group_title_j">J</string>
<string name="group_title_k">K</string>
<string name="group_title_l">L</string>
<string name="group_title_m">M</string>
<string name="group_title_n">N</string>
<string name="group_title_o">O</string>
<string name="group_title_p">P</string>
<string name="group_title_q">Q</string>
<string name="group_title_r">R</string>
<string name="group_title_s">S</string>
<string name="group_title_t">T</string>
<string name="group_title_u">U</string>
<string name="group_title_v">V</string>
<string name="group_title_w">W</string>
<string name="group_title_x">X</string>
<string name="group_title_y">Y</string>
<string name="group_title_z">Z</string>
<!-- Countries -->
<string name="country_af">Afghanistan</string>
<string name="country_al">Albania</string>
<string name="country_dz">Algeria</string>
<string name="country_ad">Andorra</string>
<string name="country_ao">Angola</string>
<string name="country_ai">Anguilla</string>
<string name="country_ag">Antigua and Barbuda</string>
<string name="country_ar">Argentina</string>
<string name="country_am">Armenia</string>
<string name="country_aw">Aruba</string>
<string name="country_au">Australia</string>
<string name="country_at">Austria</string>
<string name="country_az">Azerbaijan</string>
<string name="country_bs">Bahamas</string>
<string name="country_bh">Bahrain</string>
<string name="country_bd">Bangladesh</string>
<string name="country_bb">Barbados</string>
<string name="country_by">Belarus</string>
<string name="country_be">Belgium</string>
<string name="country_bz">Belize</string>
<string name="country_bj">Benin</string>
<string name="country_bm">Bermuda</string>
<string name="country_bt">Bhutan</string>
<string name="country_bo">Bolivia</string>
<string name="country_ba">Bosnia and Herzegovina</string>
<string name="country_bw">Botswana</string>
<string name="country_br">Brazil</string>
<string name="country_bn">Brunei</string>
<string name="country_bg">Bulgaria</string>
<string name="country_bf">Burkina Faso</string>
<string name="country_bi">Burundi</string>
<string name="country_kh">Cambodia</string>
<string name="country_cm">Cameroon</string>
<string name="country_ca">Canada</string>
<string name="country_cv">Cape Verde</string>
<string name="country_ky">Cayman Islands</string>
<string name="country_cf">Central African Republic</string>
<string name="country_td">Chad</string>
<string name="country_cl">Chile</string>
<string name="country_cn">China</string>
<string name="country_co">Colombia</string>
<string name="country_km">Comoros</string>
<string name="country_ck">Cook Islands</string>
<string name="country_cr">Costa Rica</string>
<string name="country_hr">Croatia</string>
<string name="country_cu">Cuba</string>
<string name="country_cw">Curaçao</string>
<string name="country_cy">Cyprus</string>
<string name="country_cz">Czech Republic</string>
<string name="country_cd">Democratic Republic of the Congo</string>
<string name="country_dk">Denmark</string>
<string name="country_dj">Djibouti</string>
<string name="country_dm">Dominica</string>
<string name="country_do">Dominican Republic</string>
<string name="country_ec">Ecuador</string>
<string name="country_eg">Egypt</string>
<string name="country_sv">El Salvador</string>
<string name="country_gq">Equatorial Guinea</string>
<string name="country_ee">Estonia</string>
<string name="country_et">Ethiopia</string>
<string name="country_fo">Faroe Islands</string>
<string name="country_fj">Fiji</string>
<string name="country_fi">Finland</string>
<string name="country_fr">France</string>
<string name="country_gf">French Guiana</string>
<string name="country_ga">Gabon</string>
<string name="country_gm">Gambia</string>
<string name="country_ge">Georgia</string>
<string name="country_de">Germany</string>
<string name="country_gh">Ghana</string>
<string name="country_gi">Gibraltar</string>
<string name="country_gr">Greece</string>
<string name="country_gl">Greenland</string>
<string name="country_gd">Grenada</string>
<string name="country_gp">Guadeloupe</string>
<string name="country_gu">Guam</string>
<string name="country_gt">Guatemala</string>
<string name="country_gn">Guinea</string>
<string name="country_gw">Guinea-Bissau</string>
<string name="country_gy">Guyana</string>
<string name="country_ht">Haiti</string>
<string name="country_hn">Honduras</string>
<string name="country_hk">Hong Kong</string>
<string name="country_hu">Hungary</string>
<string name="country_is">Iceland</string>
<string name="country_in">India</string>
<string name="country_id">Indonesia</string>
<string name="country_ir">Iran</string>
<string name="country_iq">Iraq</string>
<string name="country_ie">Ireland</string>
<string name="country_il">Israel</string>
<string name="country_it">Italy</string>
<string name="country_ci">Ivory Coast</string>
<string name="country_jm">Jamaica</string>
<string name="country_jp">Japan</string>
<string name="country_jo">Jordan</string>
<string name="country_kz">Kazakhstan</string>
<string name="country_ke">Kenya</string>
<string name="country_ki">Kiribati</string>
<string name="country_kw">Kuwait</string>
<string name="country_kg">Kyrgyzstan</string>
<string name="country_la">Laos</string>
<string name="country_lv">Latvia</string>
<string name="country_lb">Lebanon</string>
<string name="country_ls">Lesotho</string>
<string name="country_lr">Liberia</string>
<string name="country_ly">Libya</string>
<string name="country_li">Liechtenstein</string>
<string name="country_lt">Lithuania</string>
<string name="country_lu">Luxembourg</string>
<string name="country_mo">Macau</string>
<string name="country_mk">North Macedonia</string>
<string name="country_mg">Madagascar</string>
<string name="country_mw">Malawi</string>
<string name="country_my">Malaysia</string>
<string name="country_mv">Maldives</string>
<string name="country_ml">Mali</string>
<string name="country_mt">Malta</string>
<string name="country_mq">Martinique</string>
<string name="country_mr">Mauritania</string>
<string name="country_mu">Mauritius</string>
<string name="country_mx">Mexico</string>
<string name="country_md">Moldova</string>
<string name="country_mc">Monaco</string>
<string name="country_mn">Mongolia</string>
<string name="country_me">Montenegro</string>
<string name="country_ms">Montserrat</string>
<string name="country_ma">Morocco</string>
<string name="country_mz">Mozambique</string>
<string name="country_mm">Myanmar</string>
<string name="country_na">Namibia</string>
<string name="country_np">Nepal</string>
<string name="country_nl">Netherlands</string>
<string name="country_an">Netherlands Antilles</string>
<string name="country_nc">New Caledonia</string>
<string name="country_nz">New Zealand</string>
<string name="country_ni">Nicaragua</string>
<string name="country_ne">Niger</string>
<string name="country_nf">Norfolk Island</string>
<string name="country_ng">Nigeria</string>
<string name="country_no">Norway</string>
<string name="country_om">Oman</string>
<string name="country_pk">Pakistan</string>
<string name="country_pw">Palau</string>
<string name="country_ps">Palestinian Territories</string>
<string name="country_pa">Panama</string>
<string name="country_pg">Papua New Guinea</string>
<string name="country_py">Paraguay</string>
<string name="country_pe">Peru</string>
<string name="country_ph">Philippines</string>
<string name="country_pl">Poland</string>
<string name="country_pt">Portugal</string>
<string name="country_pr">Puerto Rico</string>
<string name="country_qa">Qatar</string>
<string name="country_cg">Republic of the Congo</string>
<string name="country_re">Reunion Island</string>
<string name="country_ro">Romania</string>
<string name="country_ru">Russia</string>
<string name="country_rw">Rwanda</string>
<string name="country_kn">Saint Kitts and Nevis</string>
<string name="country_lc">Saint Lucia</string>
<string name="country_vc">Saint Vincent and the Grenadines</string>
<string name="country_ws">Samoa</string>
<string name="country_st">Sao Tome and Principe</string>
<string name="country_sa">Saudi Arabia</string>
<string name="country_sn">Senegal</string>
<string name="country_rs">Serbia</string>
<string name="country_sc">Seychelles</string>
<string name="country_sl">Sierra Leone</string>
<string name="country_sg">Singapore</string>
<string name="country_sk">Slovakia</string>
<string name="country_si">Slovenia</string>
<string name="country_sb">Solomon Islands</string>
<string name="country_so">Somalia</string>
<string name="country_za">South Africa</string>
<string name="country_kr">South Korea</string>
<string name="country_ss">South Sudan</string>
<string name="country_es">Spain</string>
<string name="country_lk">Sri Lanka</string>
<string name="country_sd">Sudan</string>
<string name="country_sr">Suriname</string>
<string name="country_sz">Swaziland</string>
<string name="country_se">Sweden</string>
<string name="country_ch">Switzerland</string>
<string name="country_tw">Taiwan</string>
<string name="country_tj">Tajikistan</string>
<string name="country_tz">Tanzania</string>
<string name="country_th">Thailand</string>
<string name="country_tl">Timor-Leste</string>
<string name="country_tg">Togo</string>
<string name="country_to">Tonga</string>
<string name="country_tt">Trinidad and Tobago</string>
<string name="country_tn">Tunisia</string>
<string name="country_tr">Turkey</string>
<string name="country_tm">Turkmenistan</string>
<string name="country_tc">Turks and Caicos Islands</string>
<string name="country_ug">Uganda</string>
<string name="country_ua">Ukraine</string>
<string name="country_ae">United Arab Emirates</string>
<string name="country_gb">United Kingdom</string>
<string name="country_us">United States</string>
<string name="country_uy">Uruguay</string>
<string name="country_uz">Uzbekistan</string>
<string name="country_vu">Vanuatu</string>
<string name="country_ve">Venezuela</string>
<string name="country_vn">Vietnam</string>
<string name="country_vg">British Virgin Islands</string>
<string name="country_vi">Virgin Islands, US</string>
<string name="country_ye">Yemen</string>
<string name="country_zm">Zambia</string>
<string name="country_zw">Zimbabwe</string>
<!-- OnBoarding Enter PIN -->
<string name="enter_pin_headline">Enter the PIN sent to %s %s</string>
@ -116,8 +361,8 @@
<string name="enter_pin_button">Verify</string>
<!-- OnBoarding Permission -->
<string name="permission_headline">App permissions</string>
<string name="permission_content">COVIDSafe needs Bluetooth® and notifications enabled to work.\n\nSelect Proceed to:\n\n1. Enable Bluetooth®\n\n2. Allow Location permissions\n\n3. Disable Battery optimisation\n\n\nAndroid needs Location Permissions for Bluetooth® to work.\n\nCOVIDSafe does not send pairing requests.</string>
<string name="permission_headline">App settings</string>
<string name="permission_content">COVIDSafe needs Bluetooth® and notifications enabled to work.\n\nSelect Proceed to:\n\n1. Enable Bluetooth®\n\n2. Allow Location permissions\n\n3. Disable Battery optimisation\n\n\nAndroid needs Location permissions for Bluetooth® to work.\n\nCOVIDSafe does not send pairing requests.</string>
<string name="permission_button">Proceed</string>
<string name="permission_location_rationale">Android requires location access to enable Bluetooth® functions for COVIDSafe. COVIDSafe cannot work properly without it</string>
@ -143,25 +388,32 @@
<string name="permission_success_warning">Keep push notifications on for COVIDSafe so we can notify you quickly if the app isn\'t working properly.</string>
<string name="permission_success_button">Continue</string>
<!-- Notifications -->
<string name="notification_active_title">COVIDSafe is active</string>
<string name="notification_not_active_title">COVIDSafe is not active</string>
<string name="notification_active_body">Keep COVIDSafe active when you leave home or are in public places.</string>
<string name="notification_not_active_body">Make sure COVIDSafe is active before you leave home or when in public places.</string>
<!-- Home -->
<string name="home_header_active_title">COVIDSafe is active.</string>
<string name="home_header_active_no_action_required">No further action required.</string>
<string name="home_header_inactive_title">COVIDSafe is not active.</string>
<string name="home_header_inactive_check_your_permissions">Check your permissions.</string>
<string name="home_header_inactive_check_your_permissions">Check your settings.</string>
<string name="home_header_uploaded_on_date">Your information was uploaded on %s.</string>
<string name="home_header_no_pairing">COVIDSafe does not send <a href="https://www.covidsafe.gov.au/help-topics.html#bluetooth-pairing-request">pairing requests</a>.</string>
<string name="home_bluetooth_permission">Bluetooth®: %s</string>
<string name="home_non_battery_optimization_permission">Battery optimization: %s</string>
<string name="home_location_permission">Location: %s</string>
<string name="home_push_notification_permission">Push Notification: %s</string>
<string name="home_push_notification_permission">Push notification: %s</string>
<string name="home_permission_on">On</string>
<string name="home_permission_off">Off</string>
<string name="home_setup_incomplete_title">Check\npermissions</string>
<string name="home_setup_incomplete_subtitle">COVIDSafe needs permission to access these features.</string>
<string name="home_setup_help">Help</string>
<string name="home_app_permission_status_title">Check your permissions</string>
<string name="home_app_permission_status_subtitle">COVIDSafe won\'t work without these permissions. </string>
<string name="home_app_permission_status_title">Check your settings</string>
<string name="home_app_permission_status_subtitle">COVIDSafe won\'t work without the right settings. </string>
<string name="home_app_permission_push_notification_prompt">Allow COVIDSafe to push notifications.</string>
<string name="home_been_tested_title">Has a health official asked you to upload your data?</string>
<string name="home_data_uploaded">Register for self isolation</string>
<string name="home_data_uploaded_message">Help stop the spread of COVID-19 and track your symptoms.</string>
@ -197,7 +449,7 @@
<string name="upload_step_1_header_content_description">Heading, Is a health official asking you to upload your information?</string>
<string name="upload_step_1_body">Only if you test positive to COVID-19 will a state or territory health official contact you to assist with voluntary upload of your information.\n\nOnce you press Yes youll need to provide consent to upload your information. </string>
<string name="upload_step_4_header">Upload consent</string>
<string name="upload_step_4_header"> Upload consent</string>
<string name="upload_step_4_header_content_description">Heading, Upload consent</string>
<string name="upload_step_4_sub_header">Unless you consent, your close contact information will not be uploaded.\n\nIf you consent, your close contact information will be uploaded and shared with state or territory health officials for contact tracing purposes.\n\nRead the COVIDSafe <a href="https://www.health.gov.au/using-our-websites/privacy/privacy-notice-for-covidsafe-app">privacy policy</a> for further details.</string>
<string name="upload_step_verify_pin_header">Upload your information</string>
@ -224,4 +476,5 @@
<string name="activity_self_isolation_headline">Thank you! You have helped to stop the spread of COVID-19!</string>
<string name="activity_self_isolation_content">Youve kept others safe while helping to stop the spread of COVID-19 during self-isolation.</string>
<string name="activity_self_isolation_button">Continue</string>
</resources>

View file

@ -32,4 +32,12 @@
<item name="android:fontFamily">@font/font_roboto_regular</item>
</style>
<style name="countryListIndex">
<item name="android:textColor">@color/dark_green</item>
<item name="android:textSize">13sp</item>
<item name="android:layout_width">@dimen/keyline_8</item>
<item name="android:gravity">center</item>
<item name="android:layout_height">18dp</item>
</style>
</resources>

View file

@ -12,7 +12,7 @@ buildscript {
maven { url "https://jitpack.io" }
}
dependencies {
classpath 'com.android.tools.build:gradle:3.6.3'
classpath 'com.android.tools.build:gradle:4.0.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
@ -25,6 +25,7 @@ allprojects {
google()
jcenter()
maven { url 'https://jitpack.io' }
maven { url "https://dl.bintray.com/mikefot/maven/" }
}
}

View file

@ -1,17 +1,20 @@
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA256
Contact: mailto:support@covidsafe.gov.au
Contact: mailto:security@covidsafe.gov.au
Canonical: https://www.covidsafe.gov.au/.well-known/security.txt
Encryption: https://www.covidsafe.gov.au/.well-known/pgp-key.txt
-----BEGIN PGP SIGNATURE-----
iQFNBAEBCAA3FiEEbUgetBuPAas8w7zHDyQUNNekxBkFAl6xF6AZHHN1cHBvcnRA
Y292aWRzYWZlLmdvdi5hdQAKCRAPJBQ016TEGd+bCACLrYjCbKRsTsQQyZVVtGxj
wYKW2AWclnKZWX/sxnTexg6D1tlGbZbB0OJpw0gJ0NpMoOLFd0kRZXOzv8RocIdx
xd90Nwwl3NQ2ygGCDXR0Y7uRKX/P/Y1xO7XkyiYXAqVq3YWvI9M04pY/TCRvRZ/1
qBs/WDHv/6eRh2qNy/WGXD66CmTLHBcXilTeihcTZ/27Mny5SPthdfy8odQnhUja
NfFxDm+8gQuFKUUQmr9rd8FEMPSl6BWf/kQtn0YmTeZRzD01uT1ydeHkyPSgn+nq
k9us35AlkI7aZNfNkFVWJ2v5ZVAdTHDh3pgBRZETwVg1of5DEXhc5XJV6mLsu9bM
=tik2
iQHOBAEBCAA4FiEEUWgkqhSOHRHwGue2IZjqt5flXH4FAl7Hkw0aHHNlY3VyaXR5
QGNvdmlkc2FmZS5nb3YuYXUACgkQIZjqt5flXH4KfQv/ZagXzgn9HspSTupO7zQQ
t7lpZsNWKJsXddUD3+JQQ8b1uB9kmHhiydIXervEc1yGti6YYZxKybehLdvv7HjV
Cps9kn3cns9ex3s+KEMMFrU7MykvZ2x+fvCoGfgzWm1UJElxn/cSbOnt38VxmSjC
wkmh+DGa1z9OB6ZMuivO2XBx+Y0GV5tsoxWKRtXMruQ41bTHZvIJ9lJctYTv2xEK
hYlHmrzkZjLM/sTwGKWj+ARCn1IA0Zp8uxkoBi3/++NonKAlQpSZ4Rk6B780H8GM
5lCZVLIRfH1VJzPr7+eSSBF8p0coEapyO7bk/ioBIQB5v4xfvpJRBEc+eI7IZeDa
TcO/TVZw7Vut89flR+34g9PCSFDCJgv5zR8vE5mB5m/vlfJ7XI99XMwvFNWgpcyL
72tFv74uH6tUPZOlbHGZTnBsQLkuj4fwUYaUPcR9TzeMlBHoe21bQ6OeDO02rIy4
09bWSTMGe33LvTz2UMuEkZcsNT8vc7J+597GSxgDWoR6
=6MOG
-----END PGP SIGNATURE-----