COVIDSafe code from version 2.3

This commit is contained in:
COVIDSafe Support 2021-02-25 19:41:22 -08:00 committed by covidsafe-support
parent 66200cfa15
commit f582698fce
43 changed files with 1595 additions and 75 deletions

View file

@ -29,8 +29,8 @@ android {
applicationId "au.gov.health.covidsafe" applicationId "au.gov.health.covidsafe"
minSdkVersion rootProject.ext.minSdkVersion minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 109 versionCode 114
versionName "2.2" versionName "2.4"
buildConfigField "String", "GITHASH", "\"${getGitHash()}\"" buildConfigField "String", "GITHASH", "\"${getGitHash()}\""
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"

View file

@ -56,6 +56,7 @@
<activity android:name="au.gov.health.covidsafe.ui.devicename.DeviceNameChangePromptActivity" /> <activity android:name="au.gov.health.covidsafe.ui.devicename.DeviceNameChangePromptActivity" />
<activity android:name="au.gov.health.covidsafe.ui.connection.InternetConnectionIssuesActivity" /> <activity android:name="au.gov.health.covidsafe.ui.connection.InternetConnectionIssuesActivity" />
<activity android:name="au.gov.health.covidsafe.ui.settings.ChanePostCodeActivity" /> <activity android:name="au.gov.health.covidsafe.ui.settings.ChanePostCodeActivity" />
<activity android:name="au.gov.health.covidsafe.ui.restriction.RestrictionDescActivity" />
<activity <activity
android:name="au.gov.health.covidsafe.HomeActivity" android:name="au.gov.health.covidsafe.HomeActivity"

View file

@ -2,6 +2,8 @@ package au.gov.health.covidsafe
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.view.View
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import au.gov.health.covidsafe.app.TracerApp import au.gov.health.covidsafe.app.TracerApp
@ -15,13 +17,19 @@ import au.gov.health.covidsafe.sensor.SensorDelegate
import au.gov.health.covidsafe.sensor.ble.BLEDevice import au.gov.health.covidsafe.sensor.ble.BLEDevice
import au.gov.health.covidsafe.sensor.datatype.* import au.gov.health.covidsafe.sensor.datatype.*
import au.gov.health.covidsafe.ui.devicename.DeviceNameChangePromptActivity import au.gov.health.covidsafe.ui.devicename.DeviceNameChangePromptActivity
import au.gov.health.covidsafe.ui.home.HomeFragment
import au.gov.health.covidsafe.ui.restriction.RestrictionFragment
import au.gov.health.covidsafe.ui.settings.SettingsFragment
import au.gov.health.covidsafe.ui.utils.Utils import au.gov.health.covidsafe.ui.utils.Utils
import au.gov.health.covidsafe.utils.NetworkConnectionCheck import au.gov.health.covidsafe.utils.NetworkConnectionCheck
import com.google.android.gms.tasks.OnCompleteListener import com.google.android.gms.tasks.OnCompleteListener
import com.google.firebase.iid.FirebaseInstanceId import com.google.firebase.iid.FirebaseInstanceId
import kotlinx.android.synthetic.main.activity_home.*
private const val TAG = "HomeActivity" private const val TAG = "HomeActivity"
private const val UNAUTHORIZED = "Unauthorized" private const val UNAUTHORIZED = "Unauthorized"
private const val UNAUTHENTICATED = "unauthenticated"
class HomeActivity : FragmentActivity(), NetworkConnectionCheck.NetworkConnectionListener, SensorDelegate { class HomeActivity : FragmentActivity(), NetworkConnectionCheck.NetworkConnectionListener, SensorDelegate {
@ -29,6 +37,7 @@ class HomeActivity : FragmentActivity(), NetworkConnectionCheck.NetworkConnectio
var appUpdateAvailableMessageResponseLiveData = MutableLiveData<MessagesResponse>() var appUpdateAvailableMessageResponseLiveData = MutableLiveData<MessagesResponse>()
var isWindowFocusChangeLiveData = MutableLiveData<Boolean>() var isWindowFocusChangeLiveData = MutableLiveData<Boolean>()
var isJWTCorrupted = MutableLiveData<Boolean>() var isJWTCorrupted = MutableLiveData<Boolean>()
var isJWTExpired = MutableLiveData<Boolean>()
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -43,10 +52,46 @@ class HomeActivity : FragmentActivity(), NetworkConnectionCheck.NetworkConnectio
//Get Firebase Token //Get Firebase Token
getInstanceID() getInstanceID()
onClickListener()
NetworkConnectionCheck.addNetworkChangedListener(this, this) NetworkConnectionCheck.addNetworkChangedListener(this, this)
} }
private fun onClickListener() {
navigationView.setOnNavigationItemSelectedListener {
when (it.itemId) {
R.id.navigation_home -> {
val homeFragment = HomeFragment()
openFragment(homeFragment, "home")
return@setOnNavigationItemSelectedListener true
}
R.id.navigation_restriction -> {
val restrictionFragment = RestrictionFragment()
openFragment(restrictionFragment, "restriction")
return@setOnNavigationItemSelectedListener true
}
R.id.navigation_settings -> {
val settingsFragment = SettingsFragment()
openFragment(settingsFragment, "setting")
return@setOnNavigationItemSelectedListener true
}
}
false
return@setOnNavigationItemSelectedListener false
}
}
private fun openFragment(fragment: Fragment, tag: String) {
val currentFragment = supportFragmentManager.findFragmentById(R.id.home_nav_host)?.tag
if (tag != currentFragment) {
supportFragmentManager
.beginTransaction()
.addToBackStack(tag)
.replace(R.id.home_nav_host, fragment, tag)
.commit()
}
}
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
@ -65,11 +110,11 @@ class HomeActivity : FragmentActivity(), NetworkConnectionCheck.NetworkConnectio
private fun checkAndUpdateHealthStatus() { private fun checkAndUpdateHealthStatus() {
GetMessagesScheduler.scheduleGetMessagesJob { GetMessagesScheduler.scheduleGetMessagesJob {
if (it.errorBodyMessage.equals(UNAUTHORIZED) || it.errorBodyMessage.equals(UNAUTHENTICATED)) {
if (it.errorBodyMessage.equals(UNAUTHORIZED)) {
isJWTCorrupted.postValue(true) isJWTCorrupted.postValue(true)
} else{ } else {
isJWTCorrupted.postValue(false) isJWTCorrupted.postValue(false)
isJWTExpired.postValue(false)
} }
val isAppWithLatestVersion = it.messages.isNullOrEmpty() val isAppWithLatestVersion = it.messages.isNullOrEmpty()

View file

@ -0,0 +1,55 @@
package au.gov.health.covidsafe.interactor.usecase
import android.content.Context
import androidx.lifecycle.Lifecycle
import au.gov.health.covidsafe.interactor.Either
import au.gov.health.covidsafe.interactor.Failure
import au.gov.health.covidsafe.interactor.Success
import au.gov.health.covidsafe.interactor.UseCase
import au.gov.health.covidsafe.logging.CentralLog
import au.gov.health.covidsafe.networking.request.GetRestrictionRequest
import au.gov.health.covidsafe.networking.response.RestrictionResponse
import au.gov.health.covidsafe.networking.service.AwsClient
import au.gov.health.covidsafe.preference.Preference
private const val TAG = "GetCaseStatisticsUseCase"
class GetRestrictionUseCase(private val awsClient: AwsClient, lifecycle: Lifecycle, private val context: Context?, val state: String) : UseCase<RestrictionResponse, String>(lifecycle) {
override suspend fun run(params: String): Either<Exception, RestrictionResponse> {
val token = Preference.getEncrypterJWTToken(context)
return token?.let { jwtToken ->
try {
CentralLog.d(TAG, "GetCaseStatisticsUseCase run request")
val response = retryRetrofitCall {
awsClient.getRestriction("Bearer $jwtToken", state).execute()
}
when {
response?.code() == 200 -> {
response.body()?.let { body ->
Success(body)
} ?: run {
CentralLog.d(TAG, "GetCaseStatistics Invalid response")
Failure(GetRestrictionUseCaseException.GetRestrictionUseCaseServiceException(response.code()))
}
}
else -> {
CentralLog.d(TAG, "GetCaseStatistics AWSAuthServiceError")
Failure(GetRestrictionUseCaseException.GetRestrictionUseCaseServiceException(response?.code()))
}
}
} catch (e: Exception) {
Failure(e)
}
} ?: run {
return Failure(Exception())
}
}
}
sealed class GetRestrictionUseCaseException : Exception() {
class GetRestrictionUseCaseServiceException(val code: Int? = null) : GetRestrictionUseCaseException()
}

View file

@ -0,0 +1,55 @@
package au.gov.health.covidsafe.interactor.usecase
import android.content.Context
import androidx.lifecycle.Lifecycle
import au.gov.health.covidsafe.interactor.Either
import au.gov.health.covidsafe.interactor.Failure
import au.gov.health.covidsafe.interactor.Success
import au.gov.health.covidsafe.interactor.UseCase
import au.gov.health.covidsafe.logging.CentralLog
import au.gov.health.covidsafe.networking.response.IssueInitialRefreshtokenResponse
import au.gov.health.covidsafe.networking.service.AwsClient
import au.gov.health.covidsafe.preference.Preference
private const val TAG = "GetCaseStatisticsUseCase"
class IssueInitialRefreshTokenUseCase(private val awsClient: AwsClient, lifecycle: Lifecycle, private val context: Context?) : UseCase<IssueInitialRefreshtokenResponse, String>(lifecycle) {
override suspend fun run(params: String): Either<Exception, IssueInitialRefreshtokenResponse> {
val token = Preference.getEncrypterJWTToken(context)
return token?.let { jwtToken ->
try {
CentralLog.d(TAG, "GetCaseStatisticsUseCase run request")
val response = retryRetrofitCall {
awsClient.issueInitialRefreshToken("Bearer $jwtToken").execute()
}
when {
response?.code() == 200 -> {
response.body()?.let { body ->
CentralLog.d(TAG, "IssueInitialRefreshTokenUseCase Success")
Success(body)
} ?: run {
CentralLog.d(TAG, "GetCaseStatistics Invalid response")
Failure(GetInitialRefreshtokenException.GetInitialRefreshtokenServiceException(response.code()))
}
}
else -> {
CentralLog.d(TAG, "GetCaseStatistics AWSAuthServiceError")
Failure(GetInitialRefreshtokenException.GetInitialRefreshtokenServiceException(response?.code()))
}
}
} catch (e: Exception) {
Failure(e)
}
} ?: run {
return Failure(Exception())
}
}
}
sealed class GetInitialRefreshtokenException : Exception() {
class GetInitialRefreshtokenServiceException(val code: Int? = null) : GetInitialRefreshtokenException()
}

View file

@ -0,0 +1,52 @@
package au.gov.health.covidsafe.interactor.usecase
import android.content.Context
import androidx.lifecycle.Lifecycle
import au.gov.health.covidsafe.interactor.Either
import au.gov.health.covidsafe.interactor.Failure
import au.gov.health.covidsafe.interactor.Success
import au.gov.health.covidsafe.interactor.UseCase
import au.gov.health.covidsafe.logging.CentralLog
import au.gov.health.covidsafe.networking.request.ReIssueAuthRequest
import au.gov.health.covidsafe.networking.response.IssueInitialRefreshtokenResponse
import au.gov.health.covidsafe.networking.service.AwsClient
import au.gov.health.covidsafe.preference.Preference
private const val TAG = "GetCaseStatisticsUseCase"
class ReIssueAuth(private val awsClient: AwsClient, lifecycle: Lifecycle, private val context: Context?,
private val subject: String?, private val refreshToken: String?) : UseCase<IssueInitialRefreshtokenResponse, String>(lifecycle) {
override suspend fun run(params: String): Either<Exception, IssueInitialRefreshtokenResponse> {
val token = Preference.getEncrypterJWTToken(context)
return try {
val response = retryRetrofitCall {
awsClient.reIssueAuth(ReIssueAuthRequest(subject, refreshToken)).execute()
}
when {
response?.code() == 200 -> {
response.body()?.let { body ->
CentralLog.d(TAG, "ReIssueAuth Success")
Success(body)
} ?: run {
CentralLog.d(TAG, "ReIssueAuth Invalid response")
Failure(GetReIssueAuthException.GetReIssueAuthExceptionServiceException(response.code()))
}
}
else -> {
CentralLog.d(TAG, "ReIssueAuth AWSAuthServiceError")
Failure(GetReIssueAuthException.GetReIssueAuthExceptionServiceException(response?.code()))
}
}
} catch (e: Exception) {
Failure(e)
}
}
}
sealed class GetReIssueAuthException : Exception() {
class GetReIssueAuthExceptionServiceException(val code: Int? = null) : GetReIssueAuthException()
}

View file

@ -0,0 +1,6 @@
package au.gov.health.covidsafe.networking.request
import androidx.annotation.Keep
@Keep
data class GetRestrictionRequest(val state: String?)

View file

@ -0,0 +1,6 @@
package au.gov.health.covidsafe.networking.request
import androidx.annotation.Keep
@Keep
data class ReIssueAuthRequest(val subject: String?, val refresh: String?)

View file

@ -3,4 +3,4 @@ package au.gov.health.covidsafe.networking.response
import androidx.annotation.Keep import androidx.annotation.Keep
@Keep @Keep
data class AuthChallengeResponse(val token: String, val uuid: String, val token_expiry: String, val pin: String) data class AuthChallengeResponse(val token: String, val uuid: String, val token_expiry: String, val pin: String, val refreshToken: String)

View file

@ -0,0 +1,6 @@
package au.gov.health.covidsafe.networking.response
import androidx.annotation.Keep
@Keep
data class IssueInitialRefreshtokenResponse(val token: String, val refreshToken: String)

View file

@ -0,0 +1,21 @@
package au.gov.health.covidsafe.networking.response
import androidx.annotation.Keep
import au.gov.health.covidsafe.status.persistence.StatusRecord
import com.google.gson.annotations.SerializedName
@Keep
data class RestrictionResponse(@SerializedName("state") val state: String?,
@SerializedName("activities") val activities: ArrayList<Activities>?)
@Keep
data class Activities(
@SerializedName("activity") val activity: String?,
@SerializedName("activity-title") val activitiyTitle: String?,
@SerializedName("content-date-title") val contentDateTitle: String?,
@SerializedName("subheadings") val subheadings: ArrayList<Subheadings>,
@SerializedName("content") val content: String?)
@Keep
data class Subheadings(
@SerializedName("title") val title: String?,
@SerializedName("content") val content: String?)

View file

@ -1,10 +1,13 @@
package au.gov.health.covidsafe.networking.service package au.gov.health.covidsafe.networking.service
import android.service.restrictions.RestrictionsReceiver
import au.gov.health.covidsafe.BuildConfig import au.gov.health.covidsafe.BuildConfig
import au.gov.health.covidsafe.networking.response.CaseStatisticResponse import au.gov.health.covidsafe.networking.response.CaseStatisticResponse
import au.gov.health.covidsafe.networking.request.AuthChallengeRequest import au.gov.health.covidsafe.networking.request.AuthChallengeRequest
import au.gov.health.covidsafe.networking.request.ChangePostcodeRequest import au.gov.health.covidsafe.networking.request.ChangePostcodeRequest
import au.gov.health.covidsafe.networking.request.GetRestrictionRequest
import au.gov.health.covidsafe.networking.request.OTPChallengeRequest import au.gov.health.covidsafe.networking.request.OTPChallengeRequest
import au.gov.health.covidsafe.networking.request.ReIssueAuthRequest
import au.gov.health.covidsafe.networking.response.* import au.gov.health.covidsafe.networking.response.*
import retrofit2.Call import retrofit2.Call
import retrofit2.http.* import retrofit2.http.*
@ -14,7 +17,7 @@ interface AwsClient {
@POST(BuildConfig.END_POINT_PREFIX + "/initiateAuth") @POST(BuildConfig.END_POINT_PREFIX + "/initiateAuth")
fun initiateAuth(@Body body: OTPChallengeRequest): Call<OTPChallengeResponse> fun initiateAuth(@Body body: OTPChallengeRequest): Call<OTPChallengeResponse>
@POST(BuildConfig.END_POINT_PREFIX + "/respondToAuthChallenge") @POST(BuildConfig.END_POINT_PREFIX + "/v2/respondToAuthChallenge")
fun respondToAuthChallenge(@Body body: AuthChallengeRequest): Call<AuthChallengeResponse> fun respondToAuthChallenge(@Body body: AuthChallengeRequest): Call<AuthChallengeResponse>
@GET(BuildConfig.END_POINT_PREFIX + "/getTempId") @GET(BuildConfig.END_POINT_PREFIX + "/getTempId")
@ -55,4 +58,15 @@ interface AwsClient {
@POST(BuildConfig.END_POINT_PREFIX + "/device") @POST(BuildConfig.END_POINT_PREFIX + "/device")
fun changePostcode(@Header("Authorization") jwtToken: String?, fun changePostcode(@Header("Authorization") jwtToken: String?,
@Body body: ChangePostcodeRequest): Call<UploadPostcodeResponse> @Body body: ChangePostcodeRequest): Call<UploadPostcodeResponse>
@POST(BuildConfig.END_POINT_PREFIX + "/issueInitialRefreshToken")
fun issueInitialRefreshToken(@Header("Authorization") jwtToken: String?): Call<IssueInitialRefreshtokenResponse>
@POST(BuildConfig.END_POINT_PREFIX + "/reissueAuth")
fun reIssueAuth(@Body body: ReIssueAuthRequest): Call<IssueInitialRefreshtokenResponse>
@GET(BuildConfig.END_POINT_PREFIX + "/restrictions")
fun getRestriction(@Header("Authorization") jwtToken: String?,
@Query("state") os: String): Call<RestrictionResponse>
} }

View file

@ -39,7 +39,10 @@ object Preference {
private const val TURN_CASE_NUMBER = "TURN_CASE_NUMBER" private const val TURN_CASE_NUMBER = "TURN_CASE_NUMBER"
private const val IS_REREGISTER = "IS_REREGISTER" private const val IS_REREGISTER = "IS_REREGISTER"
private const val SELECTED_STATE = "SELECTED_STATE" private const val SELECTED_STATE = "SELECTED_STATE"
private const val SELECTED_RESTRICTION_STATE = "SELECTED_STATE"
private const val SENSOR_START = "SENSOR_START"
private const val ADVERTISE_STOP = "ADVERTISE_STOP" private const val ADVERTISE_STOP = "ADVERTISE_STOP"
private const val REFRESH_TOKEN = "REFRESH_TOKEN"
fun putDeviceID(context: Context, value: String) { fun putDeviceID(context: Context, value: String) {
context.getSharedPreferences(PREF_ID, Context.MODE_PRIVATE) context.getSharedPreferences(PREF_ID, Context.MODE_PRIVATE)
@ -342,4 +345,57 @@ object Preference {
return context.getSharedPreferences(PREF_ID, Context.MODE_PRIVATE) return context.getSharedPreferences(PREF_ID, Context.MODE_PRIVATE)
.getBoolean(ADVERTISE_STOP, false) .getBoolean(ADVERTISE_STOP, false)
} }
fun putEncryptRefreshToken(context: Context?, refreshToken: String?) {
context?.let {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
val masterKeyAlias = MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC)
EncryptedSharedPreferences.create(
PREF_ID,
masterKeyAlias,
context,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
).edit()?.putString(REFRESH_TOKEN, refreshToken)?.apply()
} else {
val aesEncryptedJwtToken = refreshToken?.let {
AESEncryptionForPreAndroidM.encrypt(it)
}
context.getSharedPreferences(PREF_ID, Context.MODE_PRIVATE)
.edit().putString(REFRESH_TOKEN, aesEncryptedJwtToken)?.apply()
}
}
}
fun getEncryptRefreshToken(context: Context?): String? {
return context?.let {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
val masterKeyAlias = MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC)
EncryptedSharedPreferences.create(
PREF_ID,
masterKeyAlias,
context,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
).getString(REFRESH_TOKEN, null)
} else {
context.getSharedPreferences(PREF_ID, Context.MODE_PRIVATE)
?.getString(REFRESH_TOKEN, null)?.let {
AESEncryptionForPreAndroidM.decrypt(it)
}
}
}
}
fun putSelectedRestrictionState(context: Context, selectState: String): Boolean {
return context.getSharedPreferences(PREF_ID, Context.MODE_PRIVATE)
.edit().putString(SELECTED_RESTRICTION_STATE, selectState).commit()
}
fun getSelectedRestrictionState(context: Context): String? {
return context.getSharedPreferences(PREF_ID, Context.MODE_PRIVATE)
.getString(SELECTED_RESTRICTION_STATE, null)
}
} }

View file

@ -6,6 +6,9 @@ import android.view.View
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.navigation.Navigator import androidx.navigation.Navigator
import androidx.navigation.fragment.NavHostFragment import androidx.navigation.fragment.NavHostFragment
import au.gov.health.covidsafe.R
import au.gov.health.covidsafe.ui.home.HomeFragmentDirections
import java.lang.Exception
open class BaseFragment : Fragment() { open class BaseFragment : Fragment() {
@ -22,7 +25,12 @@ open class BaseFragment : Fragment() {
if (activity is HasBlockingState) { if (activity is HasBlockingState) {
activity.isUiBlocked = true activity.isUiBlocked = true
} }
try {
NavHostFragment.findNavController(this).navigate(actionId, bundle, null, navigatorExtras) NavHostFragment.findNavController(this).navigate(actionId, bundle, null, navigatorExtras)
} catch (e: Exception) {
NavHostFragment.findNavController(this).navigateUp()
}
} }
protected fun popBackStack() { protected fun popBackStack() {

View file

@ -52,6 +52,7 @@ import kotlinx.android.synthetic.main.view_national_case_statistics.national_cas
import kotlinx.android.synthetic.main.view_state_case_statistics.* import kotlinx.android.synthetic.main.view_state_case_statistics.*
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.json.JSONObject
import pub.devrel.easypermissions.AppSettingsDialog import pub.devrel.easypermissions.AppSettingsDialog
import pub.devrel.easypermissions.EasyPermissions import pub.devrel.easypermissions.EasyPermissions
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
@ -153,6 +154,7 @@ class HomeFragment : BaseFragment(), EasyPermissions.PermissionCallbacks, Networ
private fun initializeObservers() { private fun initializeObservers() {
(activity as HomeActivity?)?.run { (activity as HomeActivity?)?.run {
isJWTCorrupted.observe(this@HomeFragment, isJwtExpired) isJWTCorrupted.observe(this@HomeFragment, isJwtExpired)
isJWTExpired.observe(this@HomeFragment, isJwtExpired)
isAppUpdateAvailableLiveData.observe(this@HomeFragment, latestAppAvailable) isAppUpdateAvailableLiveData.observe(this@HomeFragment, latestAppAvailable)
isWindowFocusChangeLiveData.observe(this@HomeFragment, refreshUiObserver) isWindowFocusChangeLiveData.observe(this@HomeFragment, refreshUiObserver)
} }
@ -190,6 +192,25 @@ class HomeFragment : BaseFragment(), EasyPermissions.PermissionCallbacks, Networ
txt_hotspot.movementMethod = LinkMovementMethod.getInstance() txt_hotspot.movementMethod = LinkMovementMethod.getInstance()
} }
}) })
homeFragmentViewModel.reIssueFail.observe(this, Observer {
it?.let {
if (it) {
permissions_card_subtitle.visibility = GONE
registration_layout.visibility = VISIBLE
}
}
})
homeFragmentViewModel.reIssueSuccess.observe(this, Observer {
it?.let {
if (it) {
jwtExpired = false
refreshSetupCompleteOrIncompleteUi()
}
}
})
} }
private val latestAppAvailable = Observer<Boolean> { private val latestAppAvailable = Observer<Boolean> {
@ -215,7 +236,7 @@ class HomeFragment : BaseFragment(), EasyPermissions.PermissionCallbacks, Networ
// disable the app update reminder for now // disable the app update reminder for now
app_update_reminder.visibility = GONE app_update_reminder.visibility = GONE
getRefreshToken()
initializePermissionViewButtonClickListeners() initializePermissionViewButtonClickListeners()
initializeUploadTestDataNavigation() initializeUploadTestDataNavigation()
@ -233,6 +254,12 @@ class HomeFragment : BaseFragment(), EasyPermissions.PermissionCallbacks, Networ
loadStateAndListener() loadStateAndListener()
} }
private fun getRefreshToken() {
if (Preference.getEncryptRefreshToken(this.requireContext()).isNullOrEmpty()) {
homeFragmentViewModel.getRefreshToken(lifecycle)
}
}
private fun loadStateAndListener() { private fun loadStateAndListener() {
stateListAdapter = StateAdapter(this.requireContext()) stateListAdapter = StateAdapter(this.requireContext())
select_state.adapter = stateListAdapter select_state.adapter = stateListAdapter
@ -460,8 +487,7 @@ class HomeFragment : BaseFragment(), EasyPermissions.PermissionCallbacks, Networ
private fun updateJwtExpiredHeader() { private fun updateJwtExpiredHeader() {
if (jwtExpired) { if (jwtExpired) {
permissions_card_subtitle.visibility = GONE homeFragmentViewModel.getReissueAuth(lifecycle)
registration_layout.visibility = VISIBLE
} else { } else {
permissions_card_subtitle.visibility = VISIBLE permissions_card_subtitle.visibility = VISIBLE
registration_layout.visibility = GONE registration_layout.visibility = GONE
@ -691,7 +717,7 @@ class HomeFragment : BaseFragment(), EasyPermissions.PermissionCallbacks, Networ
battery_card_view.isEnabled = enableParent battery_card_view.isEnabled = enableParent
} }
private fun createStateList(): ArrayList<String>{ fun createStateList(): ArrayList<String>{
val list = ArrayList<String>() val list = ArrayList<String>()
list.add("Australia") list.add("Australia")
list.add("Australian Capital Territory") list.add("Australian Capital Territory")

View file

@ -2,6 +2,7 @@ package au.gov.health.covidsafe.ui.home
import android.app.Application import android.app.Application
import android.content.Context import android.content.Context
import android.util.Log
import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
@ -11,6 +12,8 @@ import au.gov.health.covidsafe.R
import au.gov.health.covidsafe.extensions.isInternetAvailable import au.gov.health.covidsafe.extensions.isInternetAvailable
import au.gov.health.covidsafe.factory.RetrofitServiceGenerator import au.gov.health.covidsafe.factory.RetrofitServiceGenerator
import au.gov.health.covidsafe.interactor.usecase.GetCaseStatisticsUseCase import au.gov.health.covidsafe.interactor.usecase.GetCaseStatisticsUseCase
import au.gov.health.covidsafe.interactor.usecase.IssueInitialRefreshTokenUseCase
import au.gov.health.covidsafe.interactor.usecase.ReIssueAuth
import au.gov.health.covidsafe.logging.CentralLog import au.gov.health.covidsafe.logging.CentralLog
import au.gov.health.covidsafe.networking.response.CaseDetailsData import au.gov.health.covidsafe.networking.response.CaseDetailsData
import au.gov.health.covidsafe.networking.response.CaseStatisticResponse import au.gov.health.covidsafe.networking.response.CaseStatisticResponse
@ -21,6 +24,8 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.json.JSONObject
import java.util.*
private const val TAG = "HomeFragmentViewModel" private const val TAG = "HomeFragmentViewModel"
@ -49,6 +54,8 @@ class HomeFragmentViewModel(application: Application) : AndroidViewModel(applica
val aquiredOversea = MutableLiveData<String>() val aquiredOversea = MutableLiveData<String>()
val totalyDeathe = MutableLiveData<String>() val totalyDeathe = MutableLiveData<String>()
val isV2Available = MutableLiveData<Boolean>() val isV2Available = MutableLiveData<Boolean>()
val reIssueFail = MutableLiveData<Boolean>(false)
val reIssueSuccess = MutableLiveData<Boolean>(false)
private val viewModelJob = SupervisorJob() private val viewModelJob = SupervisorJob()
private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob) private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)
@ -82,6 +89,73 @@ class HomeFragmentViewModel(application: Application) : AndroidViewModel(applica
} }
} }
fun getRefreshToken(lifecycle: Lifecycle) {
context = getApplication() as Context
viewModelScope.launch(Dispatchers.IO) {
IssueInitialRefreshTokenUseCase(awsClient, lifecycle, getApplication()).invoke("",
onSuccess = {
it.refreshToken.let {
Preference.putEncryptRefreshToken(context, it)
}
it.token.let {
Preference.putEncrypterJWTToken(context, it)
}
},
onFailure = {
CentralLog.e(TAG, "On Failure: ${it.message}")
}
)
}
}
fun getReissueAuth(lifecycle: Lifecycle) {
var subject: String? = null
context = getApplication() as Context
val token = Preference.getEncrypterJWTToken(context)
val refreshToken = Preference.getEncryptRefreshToken(context)
val tokenSeparate = token?.split(".")
var subjectItem: String? = null
if (tokenSeparate?.size !=null && tokenSeparate.size >= 3) {
subjectItem = tokenSeparate.let {
it[1]
}
}
var subjectByte: ByteArray? = null
subjectItem?.let { subjectByte = android.util.Base64.decode(subjectItem, android.util.Base64.DEFAULT)}
val charset = Charsets.UTF_8
subjectByte?.let {
val jsonModel = String(it, charset)
val jsonObj = JSONObject(jsonModel)
subject = jsonObj.get("sub").toString()
}
if (subject.isNullOrEmpty()) {
reIssueFail.value = true
} else {
viewModelScope.launch(Dispatchers.IO) {
ReIssueAuth(awsClient, lifecycle, getApplication(), subject, refreshToken).invoke("",
onSuccess = {
it.refreshToken.let {
Preference.putEncryptRefreshToken(context, it)
}
it.token.let {
Preference.putEncrypterJWTToken(context, it)
}
reIssueSuccess.value = true
},
onFailure = {
reIssueFail.value = true
}
)
}
}
}
private fun updateOnSuccess(caseStatisticResponse: CaseStatisticResponse) { private fun updateOnSuccess(caseStatisticResponse: CaseStatisticResponse) {
viewModelScope.launch { viewModelScope.launch {
isRefreshing.value = false isRefreshing.value = false

View file

@ -86,9 +86,13 @@ class EnterPinPresenter(private val enterPinFragment: EnterPinFragment,
Preference.putHandShakePin(enterPinFragment.context, handShakePin) Preference.putHandShakePin(enterPinFragment.context, handShakePin)
} }
val jwtToken = authChallengeResponse?.token val jwtToken = authChallengeResponse?.token
val refreshToken = authChallengeResponse?.refreshToken
jwtToken.let { jwtToken.let {
Preference.putEncrypterJWTToken(enterPinFragment.requireContext(), jwtToken) Preference.putEncrypterJWTToken(enterPinFragment.requireContext(), jwtToken)
} }
refreshToken?.let {
Preference.putEncryptRefreshToken(enterPinFragment.requireContext(), refreshToken)
}
enterPinFragment.hideKeyboard() enterPinFragment.hideKeyboard()
enterPinFragment.navigateToNextPage() enterPinFragment.navigateToNextPage()

View file

@ -0,0 +1,54 @@
package au.gov.health.covidsafe.ui.restriction
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.recyclerview.widget.RecyclerView
import au.gov.health.covidsafe.R
import au.gov.health.covidsafe.networking.response.Subheadings
class RestrictionAdapter internal constructor(context: Context) :
RecyclerView.Adapter<RestrictionAdapter.RestrictionViewHolder>() {
private val inflater: LayoutInflater = LayoutInflater.from(context)
private var restrictionList = emptyList<Subheadings>()
private var mListener: OnStateListClickListener? = null
interface OnStateListClickListener {
fun onSectionClick(title: String?, content: String?)
}
inner class RestrictionViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val txtRestriction: TextView = itemView.findViewById(R.id.restriction_title)
val listLayout: ConstraintLayout = itemView.findViewById(R.id.restriction_layout)
}
fun setOnStateListClickListener(actionListener: OnStateListClickListener) {
mListener = actionListener
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RestrictionAdapter.RestrictionViewHolder {
val itemView = inflater.inflate(R.layout.view_list_item_restriction, parent, false)
return RestrictionViewHolder(itemView)
}
override fun onBindViewHolder(holder: RestrictionAdapter.RestrictionViewHolder, position: Int) {
holder.txtRestriction.text = restrictionList[position].title
holder.listLayout.setOnClickListener {
// setRecords(reestrictionList)
mListener?.onSectionClick(restrictionList[position].title, restrictionList[position].content)
}
}
fun setRecords(list: List<Subheadings>) {
this.restrictionList = list
notifyDataSetChanged()
}
override fun getItemCount() = restrictionList.size
}

View file

@ -0,0 +1,47 @@
package au.gov.health.covidsafe.ui.restriction
import android.os.Bundle
import android.view.View
import android.view.WindowManager
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.Toolbar
import au.gov.health.covidsafe.R
import kotlinx.android.synthetic.main.activity_restriction_desc.*
class RestrictionDescActivity : AppCompatActivity() {
var htmlText: String = ""
var title:String = ""
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// TO make sure scroll works with editTexts
window.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN)
setContentView(R.layout.activity_restriction_desc)
val toolbar = findViewById<View>(R.id.toolbar_restriction_desc) as Toolbar
setSupportActionBar(toolbar)
val actionBar = supportActionBar
actionBar?.setDisplayHomeAsUpEnabled(true)
toolbar.setNavigationOnClickListener { finish() }
val extras = intent.extras
extras?.let {
val toolbarTitle = intent.extras?.getString("toolbarTitle","")
val titleSplit = toolbarTitle?.split(" ")
if (titleSplit != null && titleSplit.size > 5) {
supportActionBar?.setTitle("").toString()
txt_toolbar_title2.text = toolbarTitle.toString()
} else {
supportActionBar?.setTitle(intent.extras?.getString("toolbarTitle","")).toString()
txt_toolbar_title2.visibility = View.GONE
}
txt_activity.text = intent.extras?.getString("ActivityTitle","").toString()
htmlText = intent.extras?.getString("htmlDesc","0").toString()
}
val summary = "<html><head><style>a{color:#00661B}</style></head>" +
"<body >$htmlText.</body></html>"
web_view.loadDataWithBaseURL(null, summary, "text/html", "utf-8", null)
}
}

View file

@ -0,0 +1,153 @@
package au.gov.health.covidsafe.ui.restriction
import android.content.Intent
import android.os.Bundle
import android.text.method.LinkMovementMethod
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.viewModels
import androidx.lifecycle.Observer
import androidx.recyclerview.widget.LinearLayoutManager
import au.gov.health.covidsafe.R
import au.gov.health.covidsafe.databinding.FragmentRestrictionBinding
import au.gov.health.covidsafe.links.LinkBuilder
import au.gov.health.covidsafe.networking.response.Subheadings
import au.gov.health.covidsafe.preference.Preference
import au.gov.health.covidsafe.ui.base.BaseFragment
import au.gov.health.covidsafe.utils.AnimationUtils.slideAnimation
import au.gov.health.covidsafe.utils.SlideDirection
import au.gov.health.covidsafe.utils.SlideType
import kotlinx.android.synthetic.main.fragment_restriction.*
import kotlinx.android.synthetic.main.fragment_restriction.select_state
class RestrictionFragment: BaseFragment() {
private val viewModelRestriction: RestrictionViewModel by viewModels()
private lateinit var stateListAdapter: StateAdapter
private lateinit var stateActivityListAdapter: StateActivityAdapter
private lateinit var restrictionListAdapter: RestrictionAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
initializeObservers()
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return FragmentRestrictionBinding.inflate(layoutInflater).apply {
lifecycleOwner = viewLifecycleOwner
viewModel = viewModelRestriction
}.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModelRestriction.setup()
loadStateAndListener()
}
override fun onResume() {
super.onResume()
setupListener()
}
private fun initializeObservers() {
viewModelRestriction.activityList.observe(this, Observer { list ->
list?.let {
if (it.size > 0) {
stateActivityListAdapter = StateActivityAdapter(this.requireContext())
select_state_activity.adapter = stateActivityListAdapter
val layoutManager = LinearLayoutManager(this.requireContext())
select_state_activity.layoutManager = layoutManager
stateActivityListAdapter.setRecords(it, 0)
stateActivityListAdapter.setOnStateListClickListener(object : StateActivityAdapter.OnStateListClickListener {
override fun onStateClick(subheading: List<Subheadings>, activity: String?, activityTitle: String?, time: String?, content: String?) {
select_state_activity_layout.slideAnimation(SlideDirection.DOWN, SlideType.HIDE, 300)
viewModelRestriction.setSelectedStateActivity(activity, activityTitle, time)
loadRestrictionSection(subheading,activity, activityTitle, time, content)
}
})
}
}
})
}
private fun loadRestrictionSection(subheading: List<Subheadings>, activity: String? , activityTitle: String?, time: String?, content: String?) {
restrictionListAdapter = RestrictionAdapter(this.requireContext())
restriction_list.adapter = restrictionListAdapter
val layoutManager = LinearLayoutManager(this.requireContext())
restriction_list.layoutManager = layoutManager
val subContent = ArrayList<Subheadings>()
if (!content.isNullOrEmpty()) {
subContent.add(Subheadings(requireContext().getString(R.string.main_restrictions), content))
}
subheading.forEach {
subContent.add(it)
}
restrictionListAdapter.setRecords(subContent)
restrictionListAdapter.setOnStateListClickListener(object : RestrictionAdapter.OnStateListClickListener{
override fun onSectionClick(title: String?, content: String?) {
val intent = Intent(requireContext(), RestrictionDescActivity::class.java)
intent.putExtra("toolbarTitle", title)
intent.putExtra("ActivityTitle", activityTitle)
intent.putExtra("htmlDesc", content)
requireContext().startActivity(intent)
}
})
}
private fun loadStateAndListener() {
stateListAdapter = StateAdapter(this.requireContext())
select_state.adapter = stateListAdapter
val layoutManager = LinearLayoutManager(this.requireContext())
select_state.layoutManager = layoutManager
stateListAdapter.setRecords(createStateList(), 0)
stateListAdapter.setOnStateListClickListener(object : StateAdapter.OnStateListClickListener{
override fun onStateClick(state: String) {
select_state_layout.slideAnimation(SlideDirection.DOWN, SlideType.HIDE, 300)
viewModelRestriction.stateListVisible.value = false
viewModelRestriction.setSelectedState(state, lifecycle)
}
})
if (Preference.getSelectedRestrictionState(requireContext())!=null && Preference.getSelectedRestrictionState(requireContext())!="") {
viewModelRestriction.loadActivity(Preference.getSelectedRestrictionState(requireContext()).toString(), lifecycle)
}
btn_dissmiss.setOnClickListener {
activity?.onBackPressed()
}
}
fun setupListener() {
layout_select_activity.setOnClickListener {
select_state_activity_layout.slideAnimation(SlideDirection.UP, SlideType.SHOW, 300)
viewModelRestriction.stateActivityListVisible.value = true
}
layout_select_state.setOnClickListener {
select_state_layout.slideAnimation(SlideDirection.UP, SlideType.SHOW, 300)
viewModelRestriction.stateListVisible.value = true
}
}
fun createStateList(): ArrayList<String> {
val list = ArrayList<String>()
list.add(requireContext().getString(R.string.australian_capital_territory))
list.add(requireContext().getString(R.string.new_south_wales))
list.add(requireContext().getString(R.string.northern_territory))
list.add(requireContext().getString(R.string.queensland))
list.add(requireContext().getString(R.string.south_australia))
list.add(requireContext().getString(R.string.tasmania))
list.add(requireContext().getString(R.string.victoria))
list.add(requireContext().getString(R.string.western_australia))
return list
}
}

View file

@ -0,0 +1,102 @@
package au.gov.health.covidsafe.ui.restriction
import android.app.Application
import android.content.Context
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import au.gov.health.covidsafe.R
import au.gov.health.covidsafe.factory.RetrofitServiceGenerator
import au.gov.health.covidsafe.interactor.usecase.GetRestrictionUseCase
import au.gov.health.covidsafe.networking.response.Activities
import au.gov.health.covidsafe.networking.service.AwsClient
import au.gov.health.covidsafe.preference.Preference
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlin.collections.ArrayList
class RestrictionViewModel(application: Application): AndroidViewModel(application) {
val stateListVisible = MutableLiveData<Boolean>()
val stateActivityListVisible = MutableLiveData<Boolean>()
lateinit var context: Context
val selectedState = MutableLiveData<String>()
val selectedStateActivity = MutableLiveData<String>()
val currentTime = MutableLiveData<String>()
val linkVisible = MutableLiveData<Boolean>()
val activityList = MutableLiveData<ArrayList<Activities>>()
val errorLayout = MutableLiveData<Boolean>()
val awsClient: AwsClient by lazy {
RetrofitServiceGenerator.createService(AwsClient::class.java)
}
fun setup() {
context = getApplication() as Context
if (Preference.getSelectedRestrictionState(context) != "" && Preference.getSelectedRestrictionState(context) != null) {
selectedState.value = Preference.getSelectedRestrictionState(context)
}
}
init {
stateListVisible.value = false
stateActivityListVisible.value = false
linkVisible.value = false
errorLayout.value = false
}
fun setSelectedState(state: String, lifecycle: Lifecycle) {
linkVisible.value = false
stateListVisible.value = false
selectedState.value = state
Preference.putSelectedRestrictionState(context, state)
selectedStateActivity.value = ""
loadActivity(state, lifecycle)
}
fun loadActivity(state: String, lifecycle: Lifecycle) {
errorLayout.value = false
val stateAbrv = getState(state)
viewModelScope.launch(Dispatchers.IO) {
GetRestrictionUseCase(awsClient, lifecycle, getApplication(), stateAbrv).invoke("",
onSuccess = {
val list = ArrayList<Activities>()
it.activities?.forEach { activity ->
list.add(activity)
}
activityList.value = list
},
onFailure = {
errorLayout.value = true
}
)
}
}
fun setSelectedStateActivity(activity: String?, activityTitle: String?, time: String?) {
stateActivityListVisible.value = false
selectedStateActivity.value = activityTitle
linkVisible.value = true
currentTime.value = time
}
fun tryAgain() {
errorLayout.value = false
}
fun getState(state: String?): String {
return when(state) {
context.getString(R.string.australian_capital_territory) -> "act"
context.getString(R.string.new_south_wales) -> "nsw"
context.getString(R.string.northern_territory) -> "nt"
context.getString(R.string.queensland) -> "qld"
context.getString(R.string.south_australia) -> "sa"
context.getString(R.string.tasmania) -> "tas"
context.getString(R.string.victoria) -> "vic"
context.getString(R.string.western_australia) -> "wa"
else -> ""
}
}
}

View file

@ -0,0 +1,69 @@
package au.gov.health.covidsafe.ui.restriction
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.constraintlayout.widget.ConstraintLayout
import androidx.recyclerview.widget.RecyclerView
import au.gov.health.covidsafe.R
import au.gov.health.covidsafe.networking.response.Activities
import au.gov.health.covidsafe.networking.response.Subheadings
class StateActivityAdapter internal constructor(context: Context) :
RecyclerView.Adapter<StateActivityAdapter.StateViewHolder>() {
private val inflater: LayoutInflater = LayoutInflater.from(context)
private var stateList = emptyList<Activities>()
private var selectedState: Int = 0
private var mListener: OnStateListClickListener? = null
interface OnStateListClickListener {
fun onStateClick(subHeading: List<Subheadings>, activity: String?, activityTitle: String?, time: String?, content: String?)
}
inner class StateViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val txtStateName: TextView = itemView.findViewById(R.id.state_name)
val imageStateSelect: ImageView = itemView.findViewById(R.id.img_state_select)
val endLine: View = itemView.findViewById(R.id.end_line)
val countryListLayout: ConstraintLayout = itemView.findViewById(R.id.country_list_item)
}
fun setOnStateListClickListener(actionListener: OnStateListClickListener) {
mListener = actionListener
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): StateActivityAdapter.StateViewHolder {
val itemView = inflater.inflate(R.layout.view_list_item_state, parent, false)
return StateViewHolder(itemView)
}
override fun onBindViewHolder(holder: StateActivityAdapter.StateViewHolder, position: Int) {
holder.txtStateName.text = stateList[position].activitiyTitle
holder.imageStateSelect.visibility = View.GONE
holder.endLine.visibility = View.VISIBLE
if (position == selectedState) {
holder.imageStateSelect.visibility = View.VISIBLE
}
if (position == (this.stateList.size - 1)) {
holder.endLine.visibility = View.GONE
}
holder.countryListLayout.setOnClickListener {
setRecords(stateList, position)
mListener?.onStateClick(stateList[position].subheadings, stateList[position].activity, stateList[position].activitiyTitle, stateList[position].contentDateTitle, stateList[position].content)
}
}
fun setRecords(stateList: List<Activities>, selectedState: Int) {
this.stateList = stateList
this.selectedState = selectedState
notifyDataSetChanged()
}
override fun getItemCount() = stateList.size
}

View file

@ -0,0 +1,67 @@
package au.gov.health.covidsafe.ui.restriction
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.constraintlayout.widget.ConstraintLayout
import androidx.recyclerview.widget.RecyclerView
import au.gov.health.covidsafe.R
class StateAdapter internal constructor(context: Context) :
RecyclerView.Adapter<StateAdapter.StateViewHolder>() {
private val inflater: LayoutInflater = LayoutInflater.from(context)
private var stateList = emptyList<String>()
private var selectedState: Int = 0
private var mListener: OnStateListClickListener? = null
interface OnStateListClickListener {
fun onStateClick(state: String)
}
inner class StateViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val txtStateName: TextView = itemView.findViewById(R.id.state_name)
val imageStateSelect: ImageView = itemView.findViewById(R.id.img_state_select)
val endLine: View = itemView.findViewById(R.id.end_line)
val countryListLayout: ConstraintLayout = itemView.findViewById(R.id.country_list_item)
}
fun setOnStateListClickListener(actionListener: OnStateListClickListener) {
mListener = actionListener
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): StateAdapter.StateViewHolder {
val itemView = inflater.inflate(R.layout.view_list_item_state, parent, false)
return StateViewHolder(itemView)
}
override fun onBindViewHolder(holder: StateAdapter.StateViewHolder, position: Int) {
holder.txtStateName.text = stateList[position]
holder.imageStateSelect.visibility = View.GONE
holder.endLine.visibility = View.VISIBLE
if (position == selectedState) {
holder.imageStateSelect.visibility = View.VISIBLE
}
if (position == (this.stateList.size - 1)) {
holder.endLine.visibility = View.GONE
}
holder.countryListLayout.setOnClickListener {
setRecords(stateList, position)
mListener?.onStateClick(stateList[position])
}
}
fun setRecords(stateList: List<String>, selectedState: Int) {
this.stateList = stateList
this.selectedState = selectedState
notifyDataSetChanged()
}
override fun getItemCount() = stateList.size
}

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="@color/dark_green" android:state_checked="true"/>
<item android:color="@color/black"/>
</selector>

View file

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_checked="false">
<layer-list>
<item android:bottom="-8dp" android:left="-8dp" android:right="-8dp">
<shape>
<solid android:color="@color/lighter_green"/>
<stroke android:color="@color/dark_green" android:width="1dp"/>
</shape>
</item>
</layer-list>
</item>
<item android:state_checked="true">
<layer-list>
<item android:bottom="-8dp" android:left="-8dp" android:right="-8dp">
<shape>
<solid android:color="@color/lighter_green"/>
<stroke android:color="@color/dark_green" android:width="4dp"/>
</shape>
</item>
</layer-list>
</item>
</selector>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="@color/lighter_green"/>
<item android:color="@color/dark_green" android:state_checked="true"/>
<item android:state_pressed="true" android:state_enabled="true" android:color="@color/black" />
</selector>

View file

@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="22dp"
android:viewportWidth="24"
android:viewportHeight="22">
<path
android:pathData="M23.846,10.3569C24.0094,10.6444 24.0176,10.9958 23.8705,11.2913L20.9534,17.2807C20.7818,17.64 20.4059,17.8716 20.0055,17.8716H17.2927C16.9985,17.8716 16.7207,17.7518 16.5246,17.5442L13.9098,14.805L21.9257,6.8991L23.846,10.3569ZM6.1308,11.6906C5.4689,10.9958 5.5098,9.9097 6.2207,9.2629C6.9316,8.616 8.0429,8.6559 8.7047,9.3427L11.5074,12.2815L20.1689,3.7367L18.2405,0.2708C18.0117,-0.1445 17.3825,-0.0646 17.2681,0.3906L16.4919,3.4492C16.4183,3.7287 16.1405,3.8964 15.8545,3.8245L13.1417,3.1697C12.8557,3.0978 12.6841,2.8263 12.7576,2.5468L13.0191,1.5086C13.1008,1.1812 12.8475,0.8697 12.5043,0.8697H9.8242C9.7016,0.8697 9.579,0.9177 9.481,0.9975L0.3701,8.632C0.0187,8.9275 -0.0957,9.4066 0.0841,9.8139L2.4701,15.3082C2.846,16.1786 3.9246,16.514 4.7499,16.0189L8.2553,13.9186L6.1308,11.6906ZM19.3845,20.6826L19.981,19.6684C20.2833,19.1493 19.9074,18.5105 19.2946,18.5105H18.1016C17.4969,18.5105 17.1129,19.1573 17.4152,19.6684L18.0117,20.6826C18.3222,21.2017 19.0822,21.2017 19.3845,20.6826Z"
android:fillColor="#131313"/>
<path
android:pathData="M11.4664,15.8354H11.4501C11.1477,15.8274 10.8699,15.7076 10.6656,15.492L6.6127,11.2515C6.196,10.8203 6.2205,10.1415 6.6699,9.7342C7.1112,9.3269 7.8057,9.3509 8.2225,9.7901L11.4991,13.216L22.1217,2.7386C22.5466,2.3154 23.2411,2.3154 23.6742,2.7306C24.1073,3.1459 24.1073,3.8247 23.6824,4.2479L12.2509,15.5239C12.0384,15.7236 11.7606,15.8354 11.4664,15.8354Z"
android:fillColor="#131313"/>
</vector>

View file

@ -0,0 +1,20 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M19,11H5C3.8954,11 3,11.8954 3,13V20C3,21.1046 3.8954,22 5,22H19C20.1046,22 21,21.1046 21,20V13C21,11.8954 20.1046,11 19,11Z"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#000000"
android:strokeLineCap="round"/>
<path
android:pathData="M7,11V7C6.9988,5.7601 7.4583,4.5639 8.2894,3.6437C9.1205,2.7235 10.2638,2.1449 11.4975,2.0203C12.7312,1.8957 13.9671,2.2339 14.9655,2.9693C15.9638,3.7048 16.6533,4.7849 16.9,6"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#000000"
android:strokeLineCap="round"/>
</vector>

View file

@ -0,0 +1,20 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M12,15C13.6569,15 15,13.6569 15,12C15,10.3431 13.6569,9 12,9C10.3431,9 9,10.3431 9,12C9,13.6569 10.3431,15 12,15Z"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#000000"
android:strokeLineCap="round"/>
<path
android:pathData="M19.4,15C19.2669,15.3016 19.2272,15.6362 19.286,15.9606C19.3448,16.285 19.4995,16.5843 19.73,16.82L19.79,16.88C19.976,17.0657 20.1235,17.2863 20.2241,17.5291C20.3248,17.7719 20.3766,18.0322 20.3766,18.295C20.3766,18.5578 20.3248,18.8181 20.2241,19.0609C20.1235,19.3037 19.976,19.5243 19.79,19.71C19.6043,19.896 19.3837,20.0435 19.1409,20.1441C18.8981,20.2448 18.6378,20.2966 18.375,20.2966C18.1122,20.2966 17.8519,20.2448 17.6091,20.1441C17.3663,20.0435 17.1457,19.896 16.96,19.71L16.9,19.65C16.6643,19.4195 16.365,19.2648 16.0406,19.206C15.7162,19.1472 15.3816,19.1869 15.08,19.32C14.7842,19.4468 14.532,19.6572 14.3543,19.9255C14.1766,20.1938 14.0813,20.5082 14.08,20.83V21C14.08,21.5304 13.8693,22.0391 13.4942,22.4142C13.1191,22.7893 12.6104,23 12.08,23C11.5496,23 11.0409,22.7893 10.6658,22.4142C10.2907,22.0391 10.08,21.5304 10.08,21V20.91C10.0723,20.579 9.9651,20.258 9.7725,19.9887C9.5799,19.7194 9.3107,19.5143 9,19.4C8.6984,19.2669 8.3638,19.2272 8.0394,19.286C7.715,19.3448 7.4157,19.4995 7.18,19.73L7.12,19.79C6.9342,19.976 6.7137,20.1235 6.4709,20.2241C6.2281,20.3248 5.9678,20.3766 5.705,20.3766C5.4422,20.3766 5.1819,20.3248 4.9391,20.2241C4.6963,20.1235 4.4757,19.976 4.29,19.79C4.1041,19.6043 3.9565,19.3837 3.8559,19.1409C3.7552,18.8981 3.7034,18.6378 3.7034,18.375C3.7034,18.1122 3.7552,17.8519 3.8559,17.6091C3.9565,17.3663 4.1041,17.1457 4.29,16.96L4.35,16.9C4.5805,16.6643 4.7352,16.365 4.794,16.0406C4.8528,15.7162 4.8131,15.3816 4.68,15.08C4.5532,14.7842 4.3428,14.532 4.0745,14.3543C3.8062,14.1766 3.4918,14.0813 3.17,14.08H3C2.4696,14.08 1.9609,13.8693 1.5858,13.4942C1.2107,13.1191 1,12.6104 1,12.08C1,11.5496 1.2107,11.0409 1.5858,10.6658C1.9609,10.2907 2.4696,10.08 3,10.08H3.09C3.421,10.0723 3.742,9.9651 4.0113,9.7725C4.2806,9.5799 4.4857,9.3107 4.6,9C4.7331,8.6984 4.7728,8.3638 4.714,8.0394C4.6552,7.715 4.5005,7.4157 4.27,7.18L4.21,7.12C4.0241,6.9342 3.8765,6.7137 3.7759,6.4709C3.6752,6.2281 3.6234,5.9678 3.6234,5.705C3.6234,5.4422 3.6752,5.1819 3.7759,4.9391C3.8765,4.6963 4.0241,4.4757 4.21,4.29C4.3958,4.1041 4.6163,3.9565 4.8591,3.8559C5.1019,3.7552 5.3622,3.7034 5.625,3.7034C5.8878,3.7034 6.1481,3.7552 6.3909,3.8559C6.6337,3.9565 6.8542,4.1041 7.04,4.29L7.1,4.35C7.3357,4.5805 7.635,4.7352 7.9594,4.794C8.2838,4.8528 8.6184,4.8131 8.92,4.68H9C9.2958,4.5532 9.548,4.3428 9.7257,4.0745C9.9034,3.8062 9.9987,3.4918 10,3.17V3C10,2.4696 10.2107,1.9609 10.5858,1.5858C10.9609,1.2107 11.4696,1 12,1C12.5304,1 13.0391,1.2107 13.4142,1.5858C13.7893,1.9609 14,2.4696 14,3V3.09C14.0013,3.4118 14.0966,3.7262 14.2743,3.9945C14.452,4.2628 14.7042,4.4732 15,4.6C15.3016,4.7331 15.6362,4.7728 15.9606,4.714C16.285,4.6552 16.5843,4.5005 16.82,4.27L16.88,4.21C17.0657,4.0241 17.2863,3.8765 17.5291,3.7759C17.7719,3.6752 18.0322,3.6234 18.295,3.6234C18.5578,3.6234 18.8181,3.6752 19.0609,3.7759C19.3037,3.8765 19.5243,4.0241 19.71,4.21C19.896,4.3958 20.0435,4.6163 20.1441,4.8591C20.2448,5.1019 20.2966,5.3622 20.2966,5.625C20.2966,5.8878 20.2448,6.1481 20.1441,6.3909C20.0435,6.6337 19.896,6.8542 19.71,7.04L19.65,7.1C19.4195,7.3357 19.2648,7.635 19.206,7.9594C19.1472,8.2838 19.1869,8.6184 19.32,8.92V9C19.4468,9.2958 19.6572,9.548 19.9255,9.7257C20.1938,9.9034 20.5082,9.9987 20.83,10H21C21.5304,10 22.0391,10.2107 22.4142,10.5858C22.7893,10.9609 23,11.4696 23,12C23,12.5304 22.7893,13.0391 22.4142,13.4142C22.0391,13.7893 21.5304,14 21,14H20.91C20.5882,14.0013 20.2738,14.0966 20.0055,14.2743C19.7372,14.452 19.5268,14.7042 19.4,15V15Z"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#000000"
android:strokeLineCap="round"/>
</vector>

View file

@ -1,9 +1,37 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<fragment xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" 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:layout_gravity="center_vertical"
android:background="@color/splash_background"
android:backgroundTint="@color/splash_frame_background"
tools:context=".HomeActivity"
>
<fragment
android:id="@+id/home_nav_host" android:id="@+id/home_nav_host"
android:name="androidx.navigation.fragment.NavHostFragment" android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
app:defaultNavHost="true" app:defaultNavHost="true"
app:navGraph="@navigation/nav_home" /> app:navGraph="@navigation/nav_home"
app:layout_constraintBottom_toTopOf="@+id/navigationView"/>
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/navigationView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="0dp"
android:layout_marginStart="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:itemBackground="@drawable/btm_navigation_background"
app:itemIconTint="@drawable/btm_navigation"
app:itemTextColor="@drawable/btm_navigation"
app:menu="@menu/navigation"
android:elevation="@dimen/space_24">
</com.google.android.material.bottomnavigation.BottomNavigationView>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -0,0 +1,68 @@
<?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"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:layout_marginBottom="@dimen/space_24">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/toolbar_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar_restriction_desc"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="@color/lighter_green"
app:title="@string/main_restrictions"
app:popupTheme="@style/MobileKit.FeedbackTheme.PopupOverlay" />
<TextView
android:id="@+id/txt_toolbar_title2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="@dimen/space_16"
android:paddingBottom="@dimen/space_16"
android:ellipsize="end"
style="?textAppearanceBody2"
android:maxLines="2"
android:textColor="@color/black"
android:textStyle="bold"
android:background="@color/lighter_green"/>
<TextView
android:id="@+id/txt_activity"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="@dimen/space_16"
android:paddingBottom="@dimen/space_16"
style="?textAppearanceBody2"
android:textColor="@color/dark_green"
android:background="@color/lighter_green"/>
</com.google.android.material.appbar.AppBarLayout>
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginStart="@dimen/space_16"
android:layout_marginEnd="@dimen/space_16"
android:orientation="vertical">
<WebView
android:id="@+id/web_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/space_16"
android:layout_marginBottom="@dimen/space_16"/>
</LinearLayout>
</ScrollView>
</LinearLayout>

View file

@ -19,14 +19,14 @@
android:clickable="true" android:clickable="true"
android:focusable="true" android:focusable="true"
app:layout_constraintTop_toTopOf="parent"> app:layout_constraintTop_toTopOf="parent">
<LinearLayout <LinearLayout
android:id="@+id/home_header_settings" android:id="@+id/home_header_settings"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_alignParentEnd="true" android:layout_alignParentEnd="true"
android:orientation="vertical" android:orientation="vertical"
android:gravity="center"> android:gravity="center"
android:visibility="gone">
<ImageView <ImageView
android:id="@+id/settings_image_view" android:id="@+id/settings_image_view"
@ -49,7 +49,6 @@
android:textAllCaps="false" android:textAllCaps="false"
android:textColor="@color/slate_black_1" /> android:textColor="@color/slate_black_1" />
</LinearLayout> </LinearLayout>
</RelativeLayout> </RelativeLayout>
<com.airbnb.lottie.LottieAnimationView <com.airbnb.lottie.LottieAnimationView

View file

@ -0,0 +1,326 @@
<?xml version="1.0" encoding="utf-8"?>
<layout 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">
<data>
<variable
name="viewModel"
type="au.gov.health.covidsafe.ui.restriction.RestrictionViewModel" />
</data>
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/home_root"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/screen_background">
<androidx.appcompat.widget.Toolbar
android:id="@+id/restriction_toolbar"
app:titleTextAppearance="@style/ToolbarStyle.TitleTextStyle"
android:layout_width="match_parent"
android:layout_height="@dimen/settings_toolbar_height"
android:background="@color/lighter_green"
android:gravity="center_vertical"
android:minHeight="?attr/actionBarSize"
app:buttonGravity="center_vertical"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navigationContentDescription="@string/navigation_back_button_content_description"
app:title="@string/restrictions_heading"
app:titleTextColor="@color/slate_black_1"
tools:layout_editor_absoluteX="0dp" />
<ScrollView
android:id="@+id/scroll_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="50dp"
android:layout_marginTop="@dimen/settings_toolbar_height">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:id="@+id/layout_select_state"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="@dimen/space_16"
android:orientation="vertical"
android:gravity="center_vertical"
app:layout_constraintTop_toTopOf="parent"
android:background="@color/white">
<TextView
android:id="@+id/txt_select_state"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAlignment="center"
android:lineSpacingExtra="5dp"
style="?textAppearanceBody1"
android:text="@string/restrictions_select_state" />
<TextView
android:id="@+id/txt_selected_state"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAlignment="center"
style="?textAppearanceBody1"
android:textColor="@color/dark_green"
android:text="@{viewModel.selectedState}"
tools:ignore="MissingConstraints"
visibility="@{viewModel.selectedState != null}"/>
</LinearLayout>
<LinearLayout
android:id="@+id/layout_select_activity"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="@dimen/space_16"
android:layout_marginTop="1dp"
android:orientation="vertical"
android:gravity="center_vertical"
app:layout_constraintTop_toBottomOf="@+id/layout_select_state"
android:background="@color/white"
visibility="@{viewModel.selectedState != null}">
<TextView
android:id="@+id/txt_select_activity"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAlignment="center"
android:lineSpacingExtra="5dp"
style="?textAppearanceBody1"
android:text="@string/restrictions_select_activity" />
<TextView
android:id="@+id/txt_selected_activity"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAlignment="center"
style="?textAppearanceBody1"
android:textColor="@color/dark_green"
android:text="@{viewModel.selectedStateActivity}"
visibility="@{viewModel.selectedStateActivity != null}"/>
</LinearLayout>
<TextView
android:id="@+id/txt_time"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="@dimen/space_32"
android:paddingStart="@dimen/space_16"
android:paddingEnd="@dimen/space_16"
android:paddingBottom="@dimen/space_16"
style="?textAppearanceBody1"
android:textColor="@color/error_red"
android:text="@{viewModel.currentTime}"
app:layout_constraintTop_toBottomOf="@+id/layout_select_activity"
visibility="@{viewModel.linkVisible}"/>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/restriction_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@+id/txt_time"
visibility="@{viewModel.linkVisible}"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="@dimen/space_16"
android:layout_marginLeft="@dimen/space_16"
android:layout_marginBottom="@dimen/settings_toolbar_height"
android:lineSpacingExtra="5dp"
style="?textAppearanceBody1"
android:layout_gravity="bottom"
android:textSize="14sp"
android:textColor="@color/grey5"
android:text="@string/restrictions_disclaimer"
visibility="@{!viewModel.linkVisible}"
/>
</FrameLayout>
<FrameLayout
android:id="@+id/select_state_layout"
android:layout_marginRight="@dimen/space_24"
android:layout_marginLeft="@dimen/space_24"
android:layout_marginTop="@dimen/space_24"
android:layout_marginBottom="@dimen/settings_toolbar_height"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingTop="@dimen/space_16"
app:layout_anchorGravity="center"
android:background="@color/white"
android:elevation="12dp"
visibility="@{viewModel.stateListVisible}"
android:visibility="gone">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:animateLayoutChanges="true">
<TextView
android:id="@+id/title_select_state"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/space_16"
android:layout_marginEnd="@dimen/space_16"
android:textSize="16sp"
android:textStyle="bold"
android:gravity="center"
android:text="@string/restrictions_select_state" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@color/grey4"
android:layout_marginTop="@dimen/space_12"
android:layout_marginBottom="@dimen/space_12"
app:layout_constraintTop_toBottomOf="@+id/layout_select_activity"/>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/select_state"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/space_16"
android:layout_marginEnd="@dimen/space_16" />
</LinearLayout>
</FrameLayout>
<FrameLayout
android:id="@+id/select_state_activity_layout"
android:layout_marginRight="@dimen/space_24"
android:layout_marginLeft="@dimen/space_24"
android:layout_marginTop="@dimen/space_24"
android:layout_marginBottom="@dimen/settings_toolbar_height"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingTop="@dimen/space_16"
app:layout_anchorGravity="center"
android:background="@color/white"
android:elevation="12dp"
visibility="@{viewModel.stateActivityListVisible}"
android:visibility="gone">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:animateLayoutChanges="true">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/space_16"
android:layout_marginEnd="@dimen/space_16"
android:textSize="18sp"
android:textStyle="bold"
android:textColor="@color/slate_black_2"
android:gravity="center"
android:text="@string/restrictions_select_activity" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@color/grey4"
android:layout_marginTop="@dimen/space_12"
android:layout_marginBottom="@dimen/space_12"
app:layout_constraintTop_toBottomOf="@+id/layout_select_activity"/>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/select_state_activity"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/space_16"
android:layout_marginEnd="@dimen/space_16" />
</LinearLayout>
</FrameLayout>
<FrameLayout
android:id="@+id/frame_layout_error"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/white"
visibility="@{viewModel.errorLayout}"
android:visibility="gone">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:layout_gravity="center"
android:gravity="top">
<com.airbnb.lottie.LottieAnimationView
android:id="@+id/case_loading_animation_view"
android:layout_width="@dimen/covidsafe_loading_animation_size"
android:layout_height="@dimen/covidsafe_loading_animation_size"
android:layout_centerHorizontal="true"
android:layout_marginTop="@dimen/space_40"
android:layout_marginBottom="@dimen/space_24"
app:lottie_autoPlay="true"
app:lottie_fileName="loading_upload.json"
app:lottie_loop="true"
app:lottie_speed="1"
android:layout_gravity="center_horizontal"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/space_16"
android:textSize="18sp"
android:textStyle="bold"
android:textColor="@color/black"
android:gravity="center"
android:text="@string/restrictions_error_heading" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/space_16"
style="?textAppearanceBody2"
android:gravity="center"
android:text="@string/restrictions_error_message" />
<Button
android:id="@+id/try_again"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="?attr/textAppearanceButton"
android:layout_marginTop="@dimen/space_40"
android:layout_marginStart="@dimen/space_32"
android:layout_marginEnd="@dimen/space_32"
android:text="@string/restrictions_error_try"
android:onClick="@{() -> viewModel.tryAgain()}"/>
<TextView
android:id="@+id/btn_dissmiss"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/space_16"
style="?textAppearanceBody2"
android:gravity="center"
android:textColor="@color/dark_green"
android:text="@string/restrictions_error_dismiss" />
</LinearLayout>
</FrameLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</layout>

View file

@ -18,7 +18,6 @@
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:navigationContentDescription="@string/navigation_back_button_content_description" app:navigationContentDescription="@string/navigation_back_button_content_description"
app:navigationIcon="@drawable/ic_up"
app:title="@string/settings" app:title="@string/settings"
app:titleTextColor="@color/slate_black_1" app:titleTextColor="@color/slate_black_1"
tools:layout_editor_absoluteX="0dp" /> tools:layout_editor_absoluteX="0dp" />

View file

@ -6,7 +6,8 @@
android:layout_height="match_parent" android:layout_height="match_parent"
android:id="@+id/root" android:id="@+id/root"
android:orientation="vertical" android:orientation="vertical"
tools:context=".HomeActivity"> tools:context=".HomeActivity"
android:background="@color/white">
<androidx.appcompat.widget.Toolbar <androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar" android:id="@+id/toolbar"
@ -17,6 +18,14 @@
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:navigationIcon="@drawable/ic_up" app:navigationIcon="@drawable/ic_up"
app:navigationContentDescription="@string/navigation_back_button_content_description"/> app:navigationContentDescription="@string/navigation_back_button_content_description"/>
<ScrollView
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<fragment <fragment
android:id="@+id/fragment_nav_host_upload" android:id="@+id/fragment_nav_host_upload"
@ -62,5 +71,6 @@
android:layout_weight="1" android:layout_weight="1"
android:text="@string/action_continue" /> android:text="@string/action_continue" />
</LinearLayout> </LinearLayout>
</LinearLayout>
</ScrollView>
</LinearLayout> </LinearLayout>

View file

@ -20,7 +20,6 @@
android:layout_alignParentTop="true" android:layout_alignParentTop="true"
android:layout_marginEnd="@dimen/space_16" android:layout_marginEnd="@dimen/space_16"
android:layout_toStartOf="@+id/active_tick_image_view" android:layout_toStartOf="@+id/active_tick_image_view"
android:accessibilityTraversalBefore="@id/home_header_settings"
android:text="@string/home_header_active_title" android:text="@string/home_header_active_title"
android:textColor="@color/dark_green" android:textColor="@color/dark_green"
android:textSize="@dimen/text_size_20" /> android:textSize="@dimen/text_size_20" />

View file

@ -132,7 +132,7 @@
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@+id/permission_icon" app:layout_constraintEnd_toStartOf="@+id/permission_icon"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
android:text="@string/jwt_heading" android:text="@string/jwt_expired"
tools:text="Please register again" /> tools:text="Please register again" />
<TextView <TextView
@ -147,7 +147,7 @@
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@+id/ic_register_again" app:layout_constraintEnd_toStartOf="@+id/ic_register_again"
tools:visibility="visible" tools:visibility="visible"
android:text="@string/jwt_description" android:text="@string/jwt_expired_description"
tools:text="There is an issue with your registration details." /> tools:text="There is an issue with your registration details." />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -0,0 +1,40 @@
<?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"
android:id="@+id/restriction_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="@dimen/space_16"
android:paddingBottom="@dimen/space_16"
android:paddingStart="@dimen/space_16"
android:paddingEnd="@dimen/space_24"
android:layout_marginBottom="1dp"
android:background="@color/white">
<TextView
android:id="@+id/restriction_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/space_24"
android:paddingEnd="@dimen/space_32"
android:gravity="start"
android:text="@string/country_region_name_au"
android:textSize="18sp"
android:maxLines="4"
android:textAlignment="viewStart"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/img_state_select"
android:layout_width="20dp"
android:layout_height="match_parent"
android:layout_marginTop="@dimen/keyline_1"
android:layout_marginEnd="@dimen/space_4"
android:layout_marginStart="@dimen/space_4"
android:src="@drawable/ic_chevron_right_black"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@+id/restriction_title" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -12,13 +12,15 @@
<TextView <TextView
android:id="@+id/state_name" android:id="@+id/state_name"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="@dimen/keyline_5" android:layout_height="wrap_content"
android:paddingStart="@dimen/keyline_4" android:paddingStart="@dimen/keyline_4"
android:layout_marginEnd="@dimen/space_24" android:layout_marginEnd="@dimen/space_24"
android:lines="1" android:paddingEnd="@dimen/space_24"
android:singleLine="false"
android:gravity="start" android:gravity="start"
android:text="@string/country_region_name_au" android:text="@string/country_region_name_au"
android:textSize="16sp" android:textSize="16sp"
android:maxLines="2"
android:textAlignment="viewStart" android:textAlignment="viewStart"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />

View file

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/navigation_home"
android:icon="@drawable/ic_home"
android:title="@string/home_bottom_nav" />
<item
android:id="@+id/navigation_restriction"
android:icon="@drawable/ic_restriction"
android:title="@string/restrictions_heading"/>
<item
android:id="@+id/navigation_settings"
android:icon="@drawable/ic_setting"
android:title="@string/settings"/>
</menu>

View file

@ -13,6 +13,7 @@
<color name="grey">#E2E2E2</color> <color name="grey">#E2E2E2</color>
<color name="grey4">#E1E5E5</color> <color name="grey4">#E1E5E5</color>
<color name="lighter_green">#C8FFB9</color> <color name="lighter_green">#C8FFB9</color>
<color name="grey5">#959595</color>
<!-- Splash Screen --> <!-- Splash Screen -->
<color name="splash_background">#E5E5E5</color> <color name="splash_background">#E5E5E5</color>

View file

@ -304,6 +304,7 @@
<string name="home_app_permission_status_title">Check your settings</string> <string name="home_app_permission_status_title">Check your settings</string>
<string name="home_been_tested_title">Has a health official asked you to upload your data?</string> <string name="home_been_tested_title">Has a health official asked you to upload your data?</string>
<string name="home_bluetooth_permission">Bluetooth®: %s</string> <string name="home_bluetooth_permission">Bluetooth®: %s</string>
<string name="home_bottom_nav">Home</string>
<string name="home_data_has_been_uploaded">Your data has been uploaded</string> <string name="home_data_has_been_uploaded">Your data has been uploaded</string>
<string name="home_data_has_been_uploaded_message">You\re helping stop the spread of COVID-19 by uploading your data daily while in self-isolation.</string> <string name="home_data_has_been_uploaded_message">You\re helping stop the spread of COVID-19 by uploading your data daily while in self-isolation.</string>
<string name="home_data_uploaded">Register for self isolation</string> <string name="home_data_uploaded">Register for self isolation</string>
@ -396,6 +397,7 @@
<string name="learn_more">Learn more</string> <string name="learn_more">Learn more</string>
<string name="loading_numbers">Loading latest numbers</string> <string name="loading_numbers">Loading latest numbers</string>
<string name="locally_acquired">%@ locally acquired</string> <string name="locally_acquired">%@ locally acquired</string>
<string name="main_restrictions">Main restrictions</string>
<!-- Splash Screen --> <!-- Splash Screen -->
<string name="migration_in_progress"> COVIDSafe update in progress. \n\n Please make sure you phone is not switched off until the update is complete.</string> <string name="migration_in_progress"> COVIDSafe update in progress. \n\n Please make sure you phone is not switched off until the update is complete.</string>
<string name="minute">Minute</string> <string name="minute">Minute</string>
@ -482,6 +484,27 @@
<!-- Onboarding Registration Consent --> <!-- Onboarding Registration Consent -->
<string name="registration_consent_headline">Registration consent</string> <string name="registration_consent_headline">Registration consent</string>
<string name="registration_consent_second_paragraph">information about my contact with other COVIDSafe users, if another user I have come into contact with tests positive for COVID-19 and uploads their contact data</string> <string name="registration_consent_second_paragraph">information about my contact with other COVIDSafe users, if another user I have come into contact with tests positive for COVID-19 and uploads their contact data</string>
<string name="restrictions_accommodation">Accommodation</string>
<string name="restrictions_activity">Activity</string>
<string name="restrictions_cafes">Cafes and Restaurants</string>
<string name="restrictions_disclaimer">Your selection will only be used to show restrictions in your area. COVIDSafe does not store or use location data.</string>
<string name="restrictions_domestic_travel">Domestic Travel</string>
<string name="restrictions_education">Education and Childcare</string>
<string name="restrictions_entertainment">Entertainment Venues</string>
<string name="restrictions_error_dismiss">Dismiss</string>
<string name="restrictions_error_heading">Restrictions are not available</string>
<string name="restrictions_error_message">Check your internet connection or try again later.</string>
<string name="restrictions_error_try">Try again</string>
<string name="restrictions_gatherings_work">Gatherings and Work</string>
<string name="restrictions_hair_beauty">Hair and Beauty Services</string>
<string name="restrictions_heading">Restrictions</string>
<string name="restrictions_hotspots">Hotspots and Case Locations</string>
<string name="restrictions_retail">Retail and Sales</string>
<string name="restrictions_select_activity">Select activity</string>
<string name="restrictions_select_state">Select state or territory</string>
<string name="restrictions_sports">Sports and Recreation</string>
<string name="restrictions_state">State or territory</string>
<string name="restrictions_wedding">Wedding, Funeral and Religion</string>
<string name="save">Save</string> <string name="save">Save</string>
<string name="scheduled" formatted="false">You scheduled COVIDSafe to snooze from %@ to %@.</string> <string name="scheduled" formatted="false">You scheduled COVIDSafe to snooze from %@ to %@.</string>
<string name="search">Search</string> <string name="search">Search</string>
@ -511,6 +534,8 @@
<string name="snooze_ends">Snooze ends in</string> <string name="snooze_ends">Snooze ends in</string>
<string name="snooze_from">Snooze from</string> <string name="snooze_from">Snooze from</string>
<string name="snooze_heading">Snooze COVIDSafe</string> <string name="snooze_heading">Snooze COVIDSafe</string>
<string name="snooze_interference_popup1">The next scheduled snooze starts at %@</string>
<string name="snooze_interference_popup2">Set a snooze that ends before this time.</string>
<string name="snooze_on">Snooze on %@</string> <string name="snooze_on">Snooze on %@</string>
<string name="snooze_timer_description">Set a timer to snooze COVIDSafe.</string> <string name="snooze_timer_description">Set a timer to snooze COVIDSafe.</string>
<string name="snooze_to">Snooze to</string> <string name="snooze_to">Snooze to</string>

View file

@ -149,5 +149,4 @@
<item name="android:fontFamily">@font/font_roboto_regular</item> <item name="android:fontFamily">@font/font_roboto_regular</item>
</style> </style>
</resources> </resources>