From f582698fce271c3c4b3231ffcf9290e301b1b368 Mon Sep 17 00:00:00 2001 From: COVIDSafe Support <64945427+covidsafe-support@users.noreply.github.com> Date: Thu, 25 Feb 2021 19:41:22 -0800 Subject: [PATCH] COVIDSafe code from version 2.3 --- app/build.gradle | 4 +- app/src/main/AndroidManifest.xml | 1 + .../au/gov/health/covidsafe/HomeActivity.kt | 53 ++- .../usecase/GetRestrictionUseCase.kt | 55 +++ .../IssueInitialRefreshTokenUseCase.kt | 55 +++ .../interactor/usecase/ReIssueAuth.kt | 52 +++ .../request/GetRestrictionRequest.kt | 6 + .../networking/request/ReIssueAuthRequest.kt | 6 + .../response/AuthChallengeResponse.kt | 2 +- .../IssueInitialRefreshtokenResponse.kt | 6 + .../response/RestrictionResponse.kt | 21 ++ .../covidsafe/networking/service/AwsClient.kt | 16 +- .../health/covidsafe/preference/Preference.kt | 56 +++ .../health/covidsafe/ui/base/BaseFragment.kt | 10 +- .../health/covidsafe/ui/home/HomeFragment.kt | 34 +- .../ui/home/HomeFragmentViewModel.kt | 74 ++++ .../fragment/enterpin/EnterPinPresenter.kt | 4 + .../ui/restriction/RestrictionAdapter.kt | 54 +++ .../ui/restriction/RestrictionDescActivity.kt | 47 +++ .../ui/restriction/RestrictionFragment.kt | 153 ++++++++ .../ui/restriction/RestrictionViewModel.kt | 102 ++++++ .../ui/restriction/StateActivityAdapter.kt | 69 ++++ .../covidsafe/ui/restriction/StateAdapter.kt | 67 ++++ app/src/main/res/drawable/btm_navigation.xml | 5 + .../drawable/btm_navigation_background.xml | 23 ++ app/src/main/res/drawable/home_menu.xml | 6 + app/src/main/res/drawable/ic_home.xml | 12 + app/src/main/res/drawable/ic_restriction.xml | 20 ++ app/src/main/res/drawable/ic_setting.xml | 20 ++ app/src/main/res/layout/activity_home.xml | 38 +- .../res/layout/activity_restriction_desc.xml | 68 ++++ .../main/res/layout/fragment_home_header.xml | 17 +- .../main/res/layout/fragment_restriction.xml | 326 ++++++++++++++++++ app/src/main/res/layout/fragment_settings.xml | 1 - .../res/layout/fragment_upload_master.xml | 92 ++--- .../res/layout/view_home_setup_complete.xml | 1 - .../res/layout/view_home_setup_incomplete.xml | 4 +- .../res/layout/view_list_item_restriction.xml | 40 +++ .../main/res/layout/view_list_item_state.xml | 6 +- app/src/main/res/menu/navigation.xml | 17 + app/src/main/res/values/colors.xml | 1 + app/src/main/res/values/strings.xml | 25 ++ app/src/main/res/values/type.xml | 1 - 43 files changed, 1595 insertions(+), 75 deletions(-) create mode 100644 app/src/main/java/au/gov/health/covidsafe/interactor/usecase/GetRestrictionUseCase.kt create mode 100644 app/src/main/java/au/gov/health/covidsafe/interactor/usecase/IssueInitialRefreshTokenUseCase.kt create mode 100644 app/src/main/java/au/gov/health/covidsafe/interactor/usecase/ReIssueAuth.kt create mode 100644 app/src/main/java/au/gov/health/covidsafe/networking/request/GetRestrictionRequest.kt create mode 100644 app/src/main/java/au/gov/health/covidsafe/networking/request/ReIssueAuthRequest.kt create mode 100644 app/src/main/java/au/gov/health/covidsafe/networking/response/IssueInitialRefreshtokenResponse.kt create mode 100644 app/src/main/java/au/gov/health/covidsafe/networking/response/RestrictionResponse.kt create mode 100644 app/src/main/java/au/gov/health/covidsafe/ui/restriction/RestrictionAdapter.kt create mode 100644 app/src/main/java/au/gov/health/covidsafe/ui/restriction/RestrictionDescActivity.kt create mode 100644 app/src/main/java/au/gov/health/covidsafe/ui/restriction/RestrictionFragment.kt create mode 100644 app/src/main/java/au/gov/health/covidsafe/ui/restriction/RestrictionViewModel.kt create mode 100644 app/src/main/java/au/gov/health/covidsafe/ui/restriction/StateActivityAdapter.kt create mode 100644 app/src/main/java/au/gov/health/covidsafe/ui/restriction/StateAdapter.kt create mode 100644 app/src/main/res/drawable/btm_navigation.xml create mode 100644 app/src/main/res/drawable/btm_navigation_background.xml create mode 100644 app/src/main/res/drawable/home_menu.xml create mode 100644 app/src/main/res/drawable/ic_home.xml create mode 100644 app/src/main/res/drawable/ic_restriction.xml create mode 100644 app/src/main/res/drawable/ic_setting.xml create mode 100644 app/src/main/res/layout/activity_restriction_desc.xml create mode 100644 app/src/main/res/layout/fragment_restriction.xml create mode 100644 app/src/main/res/layout/view_list_item_restriction.xml create mode 100644 app/src/main/res/menu/navigation.xml diff --git a/app/build.gradle b/app/build.gradle index 02731b3..fc2e1cc 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -29,8 +29,8 @@ android { applicationId "au.gov.health.covidsafe" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 109 - versionName "2.2" + versionCode 114 + versionName "2.4" buildConfigField "String", "GITHASH", "\"${getGitHash()}\"" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 10150c9..d9c0b10 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -56,6 +56,7 @@ + () var isWindowFocusChangeLiveData = MutableLiveData() var isJWTCorrupted = MutableLiveData() + var isJWTExpired = MutableLiveData() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -39,14 +48,50 @@ class HomeActivity : FragmentActivity(), NetworkConnectionCheck.NetworkConnectio setContentView(R.layout.activity_home) - Utils.startBluetoothMonitoringService(this) + Utils.startBluetoothMonitoringService(this) //Get Firebase Token getInstanceID() + onClickListener() 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() { super.onResume() @@ -65,11 +110,11 @@ class HomeActivity : FragmentActivity(), NetworkConnectionCheck.NetworkConnectio private fun checkAndUpdateHealthStatus() { GetMessagesScheduler.scheduleGetMessagesJob { - - if (it.errorBodyMessage.equals(UNAUTHORIZED)) { + if (it.errorBodyMessage.equals(UNAUTHORIZED) || it.errorBodyMessage.equals(UNAUTHENTICATED)) { isJWTCorrupted.postValue(true) - } else{ + } else { isJWTCorrupted.postValue(false) + isJWTExpired.postValue(false) } val isAppWithLatestVersion = it.messages.isNullOrEmpty() diff --git a/app/src/main/java/au/gov/health/covidsafe/interactor/usecase/GetRestrictionUseCase.kt b/app/src/main/java/au/gov/health/covidsafe/interactor/usecase/GetRestrictionUseCase.kt new file mode 100644 index 0000000..9439710 --- /dev/null +++ b/app/src/main/java/au/gov/health/covidsafe/interactor/usecase/GetRestrictionUseCase.kt @@ -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(lifecycle) { + + override suspend fun run(params: String): Either { + 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() +} + + diff --git a/app/src/main/java/au/gov/health/covidsafe/interactor/usecase/IssueInitialRefreshTokenUseCase.kt b/app/src/main/java/au/gov/health/covidsafe/interactor/usecase/IssueInitialRefreshTokenUseCase.kt new file mode 100644 index 0000000..4393fc9 --- /dev/null +++ b/app/src/main/java/au/gov/health/covidsafe/interactor/usecase/IssueInitialRefreshTokenUseCase.kt @@ -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(lifecycle) { + + override suspend fun run(params: String): Either { + 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() +} + + diff --git a/app/src/main/java/au/gov/health/covidsafe/interactor/usecase/ReIssueAuth.kt b/app/src/main/java/au/gov/health/covidsafe/interactor/usecase/ReIssueAuth.kt new file mode 100644 index 0000000..0d73b05 --- /dev/null +++ b/app/src/main/java/au/gov/health/covidsafe/interactor/usecase/ReIssueAuth.kt @@ -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(lifecycle) { + + override suspend fun run(params: String): Either { + 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() +} + + diff --git a/app/src/main/java/au/gov/health/covidsafe/networking/request/GetRestrictionRequest.kt b/app/src/main/java/au/gov/health/covidsafe/networking/request/GetRestrictionRequest.kt new file mode 100644 index 0000000..64aa6b4 --- /dev/null +++ b/app/src/main/java/au/gov/health/covidsafe/networking/request/GetRestrictionRequest.kt @@ -0,0 +1,6 @@ +package au.gov.health.covidsafe.networking.request + +import androidx.annotation.Keep + +@Keep +data class GetRestrictionRequest(val state: String?) \ No newline at end of file diff --git a/app/src/main/java/au/gov/health/covidsafe/networking/request/ReIssueAuthRequest.kt b/app/src/main/java/au/gov/health/covidsafe/networking/request/ReIssueAuthRequest.kt new file mode 100644 index 0000000..3e5f1ab --- /dev/null +++ b/app/src/main/java/au/gov/health/covidsafe/networking/request/ReIssueAuthRequest.kt @@ -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?) \ No newline at end of file diff --git a/app/src/main/java/au/gov/health/covidsafe/networking/response/AuthChallengeResponse.kt b/app/src/main/java/au/gov/health/covidsafe/networking/response/AuthChallengeResponse.kt index c09d56c..5967d39 100644 --- a/app/src/main/java/au/gov/health/covidsafe/networking/response/AuthChallengeResponse.kt +++ b/app/src/main/java/au/gov/health/covidsafe/networking/response/AuthChallengeResponse.kt @@ -3,4 +3,4 @@ package au.gov.health.covidsafe.networking.response import androidx.annotation.Keep @Keep -data class AuthChallengeResponse(val token: String, val uuid: String, val token_expiry: String, val pin: String) \ No newline at end of file +data class AuthChallengeResponse(val token: String, val uuid: String, val token_expiry: String, val pin: String, val refreshToken: String) \ No newline at end of file diff --git a/app/src/main/java/au/gov/health/covidsafe/networking/response/IssueInitialRefreshtokenResponse.kt b/app/src/main/java/au/gov/health/covidsafe/networking/response/IssueInitialRefreshtokenResponse.kt new file mode 100644 index 0000000..ceabee1 --- /dev/null +++ b/app/src/main/java/au/gov/health/covidsafe/networking/response/IssueInitialRefreshtokenResponse.kt @@ -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) \ No newline at end of file diff --git a/app/src/main/java/au/gov/health/covidsafe/networking/response/RestrictionResponse.kt b/app/src/main/java/au/gov/health/covidsafe/networking/response/RestrictionResponse.kt new file mode 100644 index 0000000..5f4f897 --- /dev/null +++ b/app/src/main/java/au/gov/health/covidsafe/networking/response/RestrictionResponse.kt @@ -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?) + +@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, + @SerializedName("content") val content: String?) +@Keep +data class Subheadings( + @SerializedName("title") val title: String?, + @SerializedName("content") val content: String?) \ No newline at end of file diff --git a/app/src/main/java/au/gov/health/covidsafe/networking/service/AwsClient.kt b/app/src/main/java/au/gov/health/covidsafe/networking/service/AwsClient.kt index f0cf404..539d20b 100644 --- a/app/src/main/java/au/gov/health/covidsafe/networking/service/AwsClient.kt +++ b/app/src/main/java/au/gov/health/covidsafe/networking/service/AwsClient.kt @@ -1,10 +1,13 @@ package au.gov.health.covidsafe.networking.service +import android.service.restrictions.RestrictionsReceiver import au.gov.health.covidsafe.BuildConfig import au.gov.health.covidsafe.networking.response.CaseStatisticResponse import au.gov.health.covidsafe.networking.request.AuthChallengeRequest 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.ReIssueAuthRequest import au.gov.health.covidsafe.networking.response.* import retrofit2.Call import retrofit2.http.* @@ -14,7 +17,7 @@ interface AwsClient { @POST(BuildConfig.END_POINT_PREFIX + "/initiateAuth") fun initiateAuth(@Body body: OTPChallengeRequest): Call - @POST(BuildConfig.END_POINT_PREFIX + "/respondToAuthChallenge") + @POST(BuildConfig.END_POINT_PREFIX + "/v2/respondToAuthChallenge") fun respondToAuthChallenge(@Body body: AuthChallengeRequest): Call @GET(BuildConfig.END_POINT_PREFIX + "/getTempId") @@ -55,4 +58,15 @@ interface AwsClient { @POST(BuildConfig.END_POINT_PREFIX + "/device") fun changePostcode(@Header("Authorization") jwtToken: String?, @Body body: ChangePostcodeRequest): Call + + @POST(BuildConfig.END_POINT_PREFIX + "/issueInitialRefreshToken") + fun issueInitialRefreshToken(@Header("Authorization") jwtToken: String?): Call + + @POST(BuildConfig.END_POINT_PREFIX + "/reissueAuth") + fun reIssueAuth(@Body body: ReIssueAuthRequest): Call + + @GET(BuildConfig.END_POINT_PREFIX + "/restrictions") + fun getRestriction(@Header("Authorization") jwtToken: String?, + @Query("state") os: String): Call + } \ No newline at end of file diff --git a/app/src/main/java/au/gov/health/covidsafe/preference/Preference.kt b/app/src/main/java/au/gov/health/covidsafe/preference/Preference.kt index d0b613b..9f1aa9f 100644 --- a/app/src/main/java/au/gov/health/covidsafe/preference/Preference.kt +++ b/app/src/main/java/au/gov/health/covidsafe/preference/Preference.kt @@ -39,7 +39,10 @@ object Preference { private const val TURN_CASE_NUMBER = "TURN_CASE_NUMBER" private const val IS_REREGISTER = "IS_REREGISTER" 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 REFRESH_TOKEN = "REFRESH_TOKEN" fun putDeviceID(context: Context, value: String) { context.getSharedPreferences(PREF_ID, Context.MODE_PRIVATE) @@ -342,4 +345,57 @@ object Preference { return context.getSharedPreferences(PREF_ID, Context.MODE_PRIVATE) .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) + } } diff --git a/app/src/main/java/au/gov/health/covidsafe/ui/base/BaseFragment.kt b/app/src/main/java/au/gov/health/covidsafe/ui/base/BaseFragment.kt index 97550a8..d7b9239 100644 --- a/app/src/main/java/au/gov/health/covidsafe/ui/base/BaseFragment.kt +++ b/app/src/main/java/au/gov/health/covidsafe/ui/base/BaseFragment.kt @@ -6,6 +6,9 @@ import android.view.View import androidx.fragment.app.Fragment import androidx.navigation.Navigator 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() { @@ -22,7 +25,12 @@ open class BaseFragment : Fragment() { if (activity is HasBlockingState) { activity.isUiBlocked = true } - NavHostFragment.findNavController(this).navigate(actionId, bundle, null, navigatorExtras) + try { + NavHostFragment.findNavController(this).navigate(actionId, bundle, null, navigatorExtras) + } catch (e: Exception) { + NavHostFragment.findNavController(this).navigateUp() + } + } protected fun popBackStack() { diff --git a/app/src/main/java/au/gov/health/covidsafe/ui/home/HomeFragment.kt b/app/src/main/java/au/gov/health/covidsafe/ui/home/HomeFragment.kt index 8694c4d..c0c5a45 100644 --- a/app/src/main/java/au/gov/health/covidsafe/ui/home/HomeFragment.kt +++ b/app/src/main/java/au/gov/health/covidsafe/ui/home/HomeFragment.kt @@ -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.coroutines.Dispatchers import kotlinx.coroutines.launch +import org.json.JSONObject import pub.devrel.easypermissions.AppSettingsDialog import pub.devrel.easypermissions.EasyPermissions import java.text.SimpleDateFormat @@ -153,6 +154,7 @@ class HomeFragment : BaseFragment(), EasyPermissions.PermissionCallbacks, Networ private fun initializeObservers() { (activity as HomeActivity?)?.run { isJWTCorrupted.observe(this@HomeFragment, isJwtExpired) + isJWTExpired.observe(this@HomeFragment, isJwtExpired) isAppUpdateAvailableLiveData.observe(this@HomeFragment, latestAppAvailable) isWindowFocusChangeLiveData.observe(this@HomeFragment, refreshUiObserver) } @@ -190,6 +192,25 @@ class HomeFragment : BaseFragment(), EasyPermissions.PermissionCallbacks, Networ 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 { @@ -215,7 +236,7 @@ class HomeFragment : BaseFragment(), EasyPermissions.PermissionCallbacks, Networ // disable the app update reminder for now app_update_reminder.visibility = GONE - + getRefreshToken() initializePermissionViewButtonClickListeners() initializeUploadTestDataNavigation() @@ -233,6 +254,12 @@ class HomeFragment : BaseFragment(), EasyPermissions.PermissionCallbacks, Networ loadStateAndListener() } + private fun getRefreshToken() { + if (Preference.getEncryptRefreshToken(this.requireContext()).isNullOrEmpty()) { + homeFragmentViewModel.getRefreshToken(lifecycle) + } + } + private fun loadStateAndListener() { stateListAdapter = StateAdapter(this.requireContext()) select_state.adapter = stateListAdapter @@ -460,8 +487,7 @@ class HomeFragment : BaseFragment(), EasyPermissions.PermissionCallbacks, Networ private fun updateJwtExpiredHeader() { if (jwtExpired) { - permissions_card_subtitle.visibility = GONE - registration_layout.visibility = VISIBLE + homeFragmentViewModel.getReissueAuth(lifecycle) } else { permissions_card_subtitle.visibility = VISIBLE registration_layout.visibility = GONE @@ -691,7 +717,7 @@ class HomeFragment : BaseFragment(), EasyPermissions.PermissionCallbacks, Networ battery_card_view.isEnabled = enableParent } - private fun createStateList(): ArrayList{ + fun createStateList(): ArrayList{ val list = ArrayList() list.add("Australia") list.add("Australian Capital Territory") diff --git a/app/src/main/java/au/gov/health/covidsafe/ui/home/HomeFragmentViewModel.kt b/app/src/main/java/au/gov/health/covidsafe/ui/home/HomeFragmentViewModel.kt index 1888bf7..f93eec9 100644 --- a/app/src/main/java/au/gov/health/covidsafe/ui/home/HomeFragmentViewModel.kt +++ b/app/src/main/java/au/gov/health/covidsafe/ui/home/HomeFragmentViewModel.kt @@ -2,6 +2,7 @@ package au.gov.health.covidsafe.ui.home import android.app.Application import android.content.Context +import android.util.Log import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.Lifecycle 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.factory.RetrofitServiceGenerator 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.networking.response.CaseDetailsData import au.gov.health.covidsafe.networking.response.CaseStatisticResponse @@ -21,6 +24,8 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.launch +import org.json.JSONObject +import java.util.* private const val TAG = "HomeFragmentViewModel" @@ -49,6 +54,8 @@ class HomeFragmentViewModel(application: Application) : AndroidViewModel(applica val aquiredOversea = MutableLiveData() val totalyDeathe = MutableLiveData() val isV2Available = MutableLiveData() + val reIssueFail = MutableLiveData(false) + val reIssueSuccess = MutableLiveData(false) private val viewModelJob = SupervisorJob() 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) { viewModelScope.launch { isRefreshing.value = false diff --git a/app/src/main/java/au/gov/health/covidsafe/ui/onboarding/fragment/enterpin/EnterPinPresenter.kt b/app/src/main/java/au/gov/health/covidsafe/ui/onboarding/fragment/enterpin/EnterPinPresenter.kt index 283c5c4..ef6e4da 100644 --- a/app/src/main/java/au/gov/health/covidsafe/ui/onboarding/fragment/enterpin/EnterPinPresenter.kt +++ b/app/src/main/java/au/gov/health/covidsafe/ui/onboarding/fragment/enterpin/EnterPinPresenter.kt @@ -86,9 +86,13 @@ class EnterPinPresenter(private val enterPinFragment: EnterPinFragment, Preference.putHandShakePin(enterPinFragment.context, handShakePin) } val jwtToken = authChallengeResponse?.token + val refreshToken = authChallengeResponse?.refreshToken jwtToken.let { Preference.putEncrypterJWTToken(enterPinFragment.requireContext(), jwtToken) } + refreshToken?.let { + Preference.putEncryptRefreshToken(enterPinFragment.requireContext(), refreshToken) + } enterPinFragment.hideKeyboard() enterPinFragment.navigateToNextPage() diff --git a/app/src/main/java/au/gov/health/covidsafe/ui/restriction/RestrictionAdapter.kt b/app/src/main/java/au/gov/health/covidsafe/ui/restriction/RestrictionAdapter.kt new file mode 100644 index 0000000..aabc7b4 --- /dev/null +++ b/app/src/main/java/au/gov/health/covidsafe/ui/restriction/RestrictionAdapter.kt @@ -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() { + + private val inflater: LayoutInflater = LayoutInflater.from(context) + private var restrictionList = emptyList() + 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) { + this.restrictionList = list + notifyDataSetChanged() + } + + override fun getItemCount() = restrictionList.size + +} diff --git a/app/src/main/java/au/gov/health/covidsafe/ui/restriction/RestrictionDescActivity.kt b/app/src/main/java/au/gov/health/covidsafe/ui/restriction/RestrictionDescActivity.kt new file mode 100644 index 0000000..ee9a902 --- /dev/null +++ b/app/src/main/java/au/gov/health/covidsafe/ui/restriction/RestrictionDescActivity.kt @@ -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(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 = "" + + "$htmlText." + + web_view.loadDataWithBaseURL(null, summary, "text/html", "utf-8", null) + } +} \ No newline at end of file diff --git a/app/src/main/java/au/gov/health/covidsafe/ui/restriction/RestrictionFragment.kt b/app/src/main/java/au/gov/health/covidsafe/ui/restriction/RestrictionFragment.kt new file mode 100644 index 0000000..595b962 --- /dev/null +++ b/app/src/main/java/au/gov/health/covidsafe/ui/restriction/RestrictionFragment.kt @@ -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, 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, 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() + 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 { + val list = ArrayList() + 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 + } +} \ No newline at end of file diff --git a/app/src/main/java/au/gov/health/covidsafe/ui/restriction/RestrictionViewModel.kt b/app/src/main/java/au/gov/health/covidsafe/ui/restriction/RestrictionViewModel.kt new file mode 100644 index 0000000..5c3da7b --- /dev/null +++ b/app/src/main/java/au/gov/health/covidsafe/ui/restriction/RestrictionViewModel.kt @@ -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() + val stateActivityListVisible = MutableLiveData() + lateinit var context: Context + val selectedState = MutableLiveData() + val selectedStateActivity = MutableLiveData() + val currentTime = MutableLiveData() + val linkVisible = MutableLiveData() + val activityList = MutableLiveData>() + val errorLayout = MutableLiveData() + + 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() + 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 -> "" + } + } +} \ No newline at end of file diff --git a/app/src/main/java/au/gov/health/covidsafe/ui/restriction/StateActivityAdapter.kt b/app/src/main/java/au/gov/health/covidsafe/ui/restriction/StateActivityAdapter.kt new file mode 100644 index 0000000..ed794d9 --- /dev/null +++ b/app/src/main/java/au/gov/health/covidsafe/ui/restriction/StateActivityAdapter.kt @@ -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() { + + private val inflater: LayoutInflater = LayoutInflater.from(context) + private var stateList = emptyList() + private var selectedState: Int = 0 + private var mListener: OnStateListClickListener? = null + + interface OnStateListClickListener { + fun onStateClick(subHeading: List, 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, selectedState: Int) { + this.stateList = stateList + this.selectedState = selectedState + notifyDataSetChanged() + } + + override fun getItemCount() = stateList.size + +} diff --git a/app/src/main/java/au/gov/health/covidsafe/ui/restriction/StateAdapter.kt b/app/src/main/java/au/gov/health/covidsafe/ui/restriction/StateAdapter.kt new file mode 100644 index 0000000..6ba4af9 --- /dev/null +++ b/app/src/main/java/au/gov/health/covidsafe/ui/restriction/StateAdapter.kt @@ -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() { + + private val inflater: LayoutInflater = LayoutInflater.from(context) + private var stateList = emptyList() + 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, selectedState: Int) { + this.stateList = stateList + this.selectedState = selectedState + notifyDataSetChanged() + } + + override fun getItemCount() = stateList.size + +} diff --git a/app/src/main/res/drawable/btm_navigation.xml b/app/src/main/res/drawable/btm_navigation.xml new file mode 100644 index 0000000..9b00d5c --- /dev/null +++ b/app/src/main/res/drawable/btm_navigation.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/btm_navigation_background.xml b/app/src/main/res/drawable/btm_navigation_background.xml new file mode 100644 index 0000000..8dab41f --- /dev/null +++ b/app/src/main/res/drawable/btm_navigation_background.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/home_menu.xml b/app/src/main/res/drawable/home_menu.xml new file mode 100644 index 0000000..c501085 --- /dev/null +++ b/app/src/main/res/drawable/home_menu.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_home.xml b/app/src/main/res/drawable/ic_home.xml new file mode 100644 index 0000000..76a626d --- /dev/null +++ b/app/src/main/res/drawable/ic_home.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/ic_restriction.xml b/app/src/main/res/drawable/ic_restriction.xml new file mode 100644 index 0000000..2b1de2f --- /dev/null +++ b/app/src/main/res/drawable/ic_restriction.xml @@ -0,0 +1,20 @@ + + + + diff --git a/app/src/main/res/drawable/ic_setting.xml b/app/src/main/res/drawable/ic_setting.xml new file mode 100644 index 0000000..57fc02c --- /dev/null +++ b/app/src/main/res/drawable/ic_setting.xml @@ -0,0 +1,20 @@ + + + + diff --git a/app/src/main/res/layout/activity_home.xml b/app/src/main/res/layout/activity_home.xml index 215d022..ba74df4 100644 --- a/app/src/main/res/layout/activity_home.xml +++ b/app/src/main/res/layout/activity_home.xml @@ -1,9 +1,37 @@ - \ No newline at end of file + android:layout_gravity="center_vertical" + android:background="@color/splash_background" + android:backgroundTint="@color/splash_frame_background" + tools:context=".HomeActivity" + > + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_restriction_desc.xml b/app/src/main/res/layout/activity_restriction_desc.xml new file mode 100644 index 0000000..ec16f4a --- /dev/null +++ b/app/src/main/res/layout/activity_restriction_desc.xml @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_home_header.xml b/app/src/main/res/layout/fragment_home_header.xml index 77f6b32..0fcf7a0 100644 --- a/app/src/main/res/layout/fragment_home_header.xml +++ b/app/src/main/res/layout/fragment_home_header.xml @@ -19,21 +19,21 @@ android:clickable="true" android:focusable="true" app:layout_constraintTop_toTopOf="parent"> - + android:gravity="center" + android:visibility="gone"> - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +