Skip to content
Snippets Groups Projects
Unverified Commit e9391c01 authored by Matthias Urhahn's avatar Matthias Urhahn Committed by GitHub
Browse files

PPA UserInput Selection Screens (EXPOSUREAPP-4752) (#2270)

* PPA User Input Strings

* Data Donation: PPA User Input.
First draft.

TODO:
Districts
Tests

* Fix interaction with radio button..
Fix state matching for NRW
Add district mapping and display.
Fix "unspecified" element position.

* Test skeletons.

* Finish unit tests.

* Wish the LINTer a nice evening.

* Move extension functions into their own file and make them public to allow label-related code to be reused on the settings screen.
Additional unit tests that check the federal state short code mapping.

* XML formatting.

* Address additional PR comments.

* Adjust radiobutton accent color.
parent 2514fbe9
No related branches found
No related tags found
No related merge requests found
Showing
with 3927 additions and 3 deletions
......@@ -9,6 +9,7 @@ import de.rki.coronawarnapp.test.contactdiary.ui.ContactDiaryTestFragment
import de.rki.coronawarnapp.test.crash.ui.SettingsCrashReportFragment
import de.rki.coronawarnapp.test.debugoptions.ui.DebugOptionsFragment
import de.rki.coronawarnapp.test.keydownload.ui.KeyDownloadTestFragment
import de.rki.coronawarnapp.test.playground.ui.PlaygroundFragment
import de.rki.coronawarnapp.test.risklevel.ui.TestRiskLevelCalculationFragment
import de.rki.coronawarnapp.test.submission.ui.SubmissionTestFragment
import de.rki.coronawarnapp.test.tasks.ui.TestTaskControllerFragment
......@@ -28,7 +29,8 @@ class TestMenuFragmentViewModel @AssistedInject constructor() : CWAViewModel() {
SubmissionTestFragment.MENU_ITEM,
SettingsCrashReportFragment.MENU_ITEM,
MiscInfoFragment.MENU_ITEM,
ContactDiaryTestFragment.MENU_ITEM
ContactDiaryTestFragment.MENU_ITEM,
PlaygroundFragment.MENU_ITEM
).let { MutableLiveData(it) }
}
val showTestScreenEvent = SingleLiveEvent<TestMenuItem>()
......
package de.rki.coronawarnapp.test.playground.ui
import android.annotation.SuppressLint
import android.os.Bundle
import android.view.View
import androidx.fragment.app.Fragment
import androidx.navigation.fragment.findNavController
import de.rki.coronawarnapp.R
import de.rki.coronawarnapp.databinding.FragmentTestPlaygroundBinding
import de.rki.coronawarnapp.datadonation.analytics.ui.input.AnalyticsUserInputFragment
import de.rki.coronawarnapp.datadonation.analytics.ui.input.AnalyticsUserInputFragmentArgs
import de.rki.coronawarnapp.test.menu.ui.TestMenuItem
import de.rki.coronawarnapp.util.di.AutoInject
import de.rki.coronawarnapp.util.ui.viewBindingLazy
import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactoryProvider
import de.rki.coronawarnapp.util.viewmodel.cwaViewModels
import javax.inject.Inject
@SuppressLint("SetTextI18n")
class PlaygroundFragment : Fragment(R.layout.fragment_test_playground), AutoInject {
@Inject lateinit var viewModelFactory: CWAViewModelFactoryProvider.Factory
private val vm: PlaygroundViewModel by cwaViewModels { viewModelFactory }
private val binding: FragmentTestPlaygroundBinding by viewBindingLazy()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.apply {
dataDonationUserinfoAgegroup.setOnClickListener {
findNavController().navigate(
R.id.analyticsUserInputFragment,
AnalyticsUserInputFragmentArgs(AnalyticsUserInputFragment.InputType.AGE_GROUP).toBundle()
)
}
dataDonationUserinfoFederalstate.setOnClickListener {
findNavController().navigate(
R.id.analyticsUserInputFragment,
AnalyticsUserInputFragmentArgs(AnalyticsUserInputFragment.InputType.FEDERAL_STATE).toBundle()
)
}
dataDonationUserinfoDistrict.setOnClickListener {
findNavController().navigate(
R.id.analyticsUserInputFragment,
AnalyticsUserInputFragmentArgs(AnalyticsUserInputFragment.InputType.DISTRICT).toBundle()
)
}
}
}
companion object {
val TAG: String = PlaygroundFragment::class.simpleName!!
val MENU_ITEM = TestMenuItem(
title = "Playground",
description = "Random options for not integrated features",
targetId = R.id.playgroundFragment
)
}
}
package de.rki.coronawarnapp.test.playground.ui
import dagger.Binds
import dagger.Module
import dagger.multibindings.IntoMap
import de.rki.coronawarnapp.util.viewmodel.CWAViewModel
import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactory
import de.rki.coronawarnapp.util.viewmodel.CWAViewModelKey
@Module
abstract class PlaygroundModule {
@Binds
@IntoMap
@CWAViewModelKey(PlaygroundViewModel::class)
abstract fun playground(factory: PlaygroundViewModel.Factory): CWAViewModelFactory<out CWAViewModel>
}
package de.rki.coronawarnapp.test.playground.ui
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import de.rki.coronawarnapp.util.viewmodel.CWAViewModel
import de.rki.coronawarnapp.util.viewmodel.SimpleCWAViewModelFactory
class PlaygroundViewModel @AssistedInject constructor() : CWAViewModel() {
@AssistedFactory
interface Factory : SimpleCWAViewModelFactory<PlaygroundViewModel>
}
......@@ -14,6 +14,8 @@ import de.rki.coronawarnapp.test.keydownload.ui.KeyDownloadTestFragment
import de.rki.coronawarnapp.test.keydownload.ui.KeyDownloadTestFragmentModule
import de.rki.coronawarnapp.test.menu.ui.TestMenuFragment
import de.rki.coronawarnapp.test.menu.ui.TestMenuFragmentModule
import de.rki.coronawarnapp.test.playground.ui.PlaygroundFragment
import de.rki.coronawarnapp.test.playground.ui.PlaygroundModule
import de.rki.coronawarnapp.test.risklevel.ui.TestRiskLevelCalculationFragment
import de.rki.coronawarnapp.test.risklevel.ui.TestRiskLevelCalculationFragmentModule
import de.rki.coronawarnapp.test.submission.ui.SubmissionTestFragment
......@@ -50,4 +52,7 @@ abstract class MainActivityTestModule {
@ContributesAndroidInjector(modules = [ContactDiaryTestFragmentModule::class])
abstract fun contactDiaryTest(): ContactDiaryTestFragment
@ContributesAndroidInjector(modules = [PlaygroundModule::class])
abstract fun playground(): PlaygroundFragment
}
<?xml version="1.0" encoding="utf-8"?>
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:ignore="HardcodedText">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="8dp"
android:orientation="vertical">
<Button
android:id="@+id/data_donation_userinfo_agegroup"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Data Donation: Age Group" />
<Button
android:id="@+id/data_donation_userinfo_federalstate"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Data Donation: Federal State" />
<Button
android:id="@+id/data_donation_userinfo_district"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Data Donation: District" />
</LinearLayout>
</androidx.core.widget.NestedScrollView>
......@@ -37,6 +37,9 @@
<action
android:id="@+id/action_test_menu_fragment_to_contactDiaryTestFragment"
app:destination="@id/test_contact_diary_fragment" />
<action
android:id="@+id/action_test_menu_fragment_to_playgroundFragment"
app:destination="@id/playgroundFragment" />
</fragment>
<fragment
......@@ -100,5 +103,10 @@
android:name="de.rki.coronawarnapp.test.contactdiary.ui.ContactDiaryTestFragment"
android:label="ContactDiaryTestFragment"
tools:layout="@layout/fragment_test_contact_diary" />
<fragment
android:id="@+id/playgroundFragment"
android:name="de.rki.coronawarnapp.test.playground.ui.PlaygroundFragment"
tools:layout="@layout/fragment_test_playground"
android:label="PlaygroundFragment" />
</navigation>
This diff is collapsed.
package de.rki.coronawarnapp.datadonation.analytics
import android.content.Context
import de.rki.coronawarnapp.server.protocols.internal.ppdd.PpaData
import de.rki.coronawarnapp.util.di.AppContext
import de.rki.coronawarnapp.util.preferences.createFlowPreference
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class AnalyticsSettings @Inject constructor()
class AnalyticsSettings @Inject constructor(
@AppContext private val context: Context
) {
private val prefs by lazy {
context.getSharedPreferences("analytics_localdata", Context.MODE_PRIVATE)
}
val userInfoAgeGroup = prefs.createFlowPreference(
key = PKEY_USERINFO_AGEGROUP,
reader = { key ->
PpaData.PPAAgeGroup.forNumber(getInt(key, 0)) ?: PpaData.PPAAgeGroup.AGE_GROUP_UNSPECIFIED
},
writer = { key, value ->
val numberToWrite = when (value) {
PpaData.PPAAgeGroup.UNRECOGNIZED -> PpaData.PPAAgeGroup.AGE_GROUP_UNSPECIFIED.number
else -> value.number
}
putInt(key, numberToWrite)
}
)
val userInfoFederalState = prefs.createFlowPreference(
key = PKEY_USERINFO_FEDERALSTATE,
reader = { key ->
PpaData.PPAFederalState.forNumber(getInt(key, -1)) ?: PpaData.PPAFederalState.FEDERAL_STATE_UNSPECIFIED
},
writer = { key, value ->
val numberToWrite = when (value) {
PpaData.PPAFederalState.UNRECOGNIZED -> PpaData.PPAFederalState.FEDERAL_STATE_UNSPECIFIED.number
else -> value.number
}
putInt(key, numberToWrite)
}
)
val userInfoDistrict = prefs.createFlowPreference(
key = PKEY_USERINFO_DISTRICT,
defaultValue = 0
)
companion object {
private const val PKEY_USERINFO_AGEGROUP = "userinfo.agegroup"
private const val PKEY_USERINFO_FEDERALSTATE = "userinfo.federalstate"
private const val PKEY_USERINFO_DISTRICT = "userinfo.district"
}
}
package de.rki.coronawarnapp.datadonation.analytics.common
import android.content.Context
import com.google.gson.Gson
import com.google.gson.annotations.SerializedName
import dagger.Reusable
import de.rki.coronawarnapp.util.di.AppContext
import de.rki.coronawarnapp.util.serialization.BaseGson
import de.rki.coronawarnapp.util.serialization.fromJson
import timber.log.Timber
import javax.inject.Inject
@Reusable
class Districts @Inject constructor(
@AppContext private val context: Context,
@BaseGson private val gson: Gson
) {
suspend fun loadDistricts(): List<District> {
return try {
val rawDistricts = context.assets.open(ASSET_NAME).bufferedReader().use { it.readText() }
gson.fromJson(rawDistricts)
} catch (e: Exception) {
Timber.tag(TAG).e(e, "Failed to parse districts.")
emptyList()
}
}
data class District(
@SerializedName("districtName") val districtName: String = "",
@SerializedName("districtShortName") val districtShortName: String = "",
@SerializedName("districtId") val districtId: Int = 0,
@SerializedName("federalStateName") val federalStateName: String = "",
@SerializedName("federalStateShortName") val federalStateShortName: String = "",
@SerializedName("federalStateId") val federalStateId: Int = 0
)
companion object {
private const val ASSET_NAME = "ppdd-ppa-administrative-unit-set.json"
private const val TAG = "Districts"
}
}
package de.rki.coronawarnapp.datadonation.analytics.common
import androidx.annotation.StringRes
import de.rki.coronawarnapp.R
import de.rki.coronawarnapp.server.protocols.internal.ppdd.PpaData
val PpaData.PPAAgeGroup.labelStringRes: Int
@StringRes
get() = when (this) {
PpaData.PPAAgeGroup.AGE_GROUP_UNSPECIFIED -> R.string.analytics_userinput_agegroup_unspecified
PpaData.PPAAgeGroup.AGE_GROUP_0_TO_29 -> R.string.analytics_userinput_agegroup_0_to_29
PpaData.PPAAgeGroup.AGE_GROUP_30_TO_59 -> R.string.analytics_userinput_agegroup_30_to_59
PpaData.PPAAgeGroup.AGE_GROUP_FROM_60 -> R.string.analytics_userinput_agegroup_from_60
PpaData.PPAAgeGroup.UNRECOGNIZED -> throw UnsupportedOperationException(
"PpaData.PPAAgeGroup.UNRECOGNIZED has no label."
)
}
val PpaData.PPAFederalState.labelStringRes: Int
@StringRes
get() = when (this) {
PpaData.PPAFederalState.FEDERAL_STATE_UNSPECIFIED -> R.string.analytics_userinput_federalstate_unspecified
PpaData.PPAFederalState.FEDERAL_STATE_BW -> R.string.analytics_userinput_federalstate_bw
PpaData.PPAFederalState.FEDERAL_STATE_BY -> R.string.analytics_userinput_federalstate_by
PpaData.PPAFederalState.FEDERAL_STATE_BE -> R.string.analytics_userinput_federalstate_be
PpaData.PPAFederalState.FEDERAL_STATE_BB -> R.string.analytics_userinput_federalstate_bb
PpaData.PPAFederalState.FEDERAL_STATE_HB -> R.string.analytics_userinput_federalstate_hb
PpaData.PPAFederalState.FEDERAL_STATE_HH -> R.string.analytics_userinput_federalstate_hh
PpaData.PPAFederalState.FEDERAL_STATE_HE -> R.string.analytics_userinput_federalstate_he
PpaData.PPAFederalState.FEDERAL_STATE_MV -> R.string.analytics_userinput_federalstate_mv
PpaData.PPAFederalState.FEDERAL_STATE_NI -> R.string.analytics_userinput_federalstate_ni
PpaData.PPAFederalState.FEDERAL_STATE_NRW -> R.string.analytics_userinput_federalstate_nrw
PpaData.PPAFederalState.FEDERAL_STATE_RP -> R.string.analytics_userinput_federalstate_rp
PpaData.PPAFederalState.FEDERAL_STATE_SL -> R.string.analytics_userinput_federalstate_sl
PpaData.PPAFederalState.FEDERAL_STATE_SN -> R.string.analytics_userinput_federalstate_sn
PpaData.PPAFederalState.FEDERAL_STATE_ST -> R.string.analytics_userinput_federalstate_st
PpaData.PPAFederalState.FEDERAL_STATE_SH -> R.string.analytics_userinput_federalstate_sh
PpaData.PPAFederalState.FEDERAL_STATE_TH -> R.string.analytics_userinput_federalstate_th
PpaData.PPAFederalState.UNRECOGNIZED -> throw UnsupportedOperationException(
"PpaData.PPAFederalState.UNRECOGNIZED has no label"
)
}
val PpaData.PPAFederalState.federalStateShortName: String
get() = when (this) {
PpaData.PPAFederalState.FEDERAL_STATE_BW -> "BW"
PpaData.PPAFederalState.FEDERAL_STATE_BY -> "BY"
PpaData.PPAFederalState.FEDERAL_STATE_BE -> "BE"
PpaData.PPAFederalState.FEDERAL_STATE_BB -> "BB"
PpaData.PPAFederalState.FEDERAL_STATE_HB -> "HB"
PpaData.PPAFederalState.FEDERAL_STATE_HH -> "HH"
PpaData.PPAFederalState.FEDERAL_STATE_HE -> "HE"
PpaData.PPAFederalState.FEDERAL_STATE_MV -> "MV"
PpaData.PPAFederalState.FEDERAL_STATE_NI -> "NI"
PpaData.PPAFederalState.FEDERAL_STATE_NRW -> "NW"
PpaData.PPAFederalState.FEDERAL_STATE_RP -> "RP"
PpaData.PPAFederalState.FEDERAL_STATE_SL -> "SL"
PpaData.PPAFederalState.FEDERAL_STATE_SN -> "SN"
PpaData.PPAFederalState.FEDERAL_STATE_ST -> "ST"
PpaData.PPAFederalState.FEDERAL_STATE_SH -> "SH"
PpaData.PPAFederalState.FEDERAL_STATE_TH -> "TH"
PpaData.PPAFederalState.FEDERAL_STATE_UNSPECIFIED -> ""
PpaData.PPAFederalState.UNRECOGNIZED -> throw UnsupportedOperationException(
"PpaData.PPAFederalState.UNRECOGNIZED has no short name"
)
}
package de.rki.coronawarnapp.datadonation.analytics.ui
import dagger.Binds
import dagger.Module
import dagger.android.ContributesAndroidInjector
import dagger.multibindings.IntoMap
import de.rki.coronawarnapp.datadonation.analytics.ui.input.AnalyticsUserInputFragment
import de.rki.coronawarnapp.datadonation.analytics.ui.input.AnalyticsUserInputViewModel
import de.rki.coronawarnapp.util.viewmodel.CWAViewModel
import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactory
import de.rki.coronawarnapp.util.viewmodel.CWAViewModelKey
@Module
abstract class AnalyticsUIModule {
@ContributesAndroidInjector
abstract fun userInput(): AnalyticsUserInputFragment
@Binds
@IntoMap
@CWAViewModelKey(AnalyticsUserInputViewModel::class)
abstract fun ppaUserInfoSelection(
factory: AnalyticsUserInputViewModel.Factory
): CWAViewModelFactory<out CWAViewModel>
}
package de.rki.coronawarnapp.datadonation.analytics.ui.input
import android.os.Bundle
import android.view.View
import androidx.fragment.app.Fragment
import androidx.navigation.fragment.navArgs
import androidx.recyclerview.widget.LinearLayoutManager
import de.rki.coronawarnapp.R
import de.rki.coronawarnapp.databinding.AnalyticsPpaUserinfoInputFragmentBinding
import de.rki.coronawarnapp.util.di.AutoInject
import de.rki.coronawarnapp.util.ui.observe2
import de.rki.coronawarnapp.util.ui.popBackStack
import de.rki.coronawarnapp.util.ui.viewBindingLazy
import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactoryProvider
import de.rki.coronawarnapp.util.viewmodel.cwaViewModelsAssisted
import javax.inject.Inject
class AnalyticsUserInputFragment : Fragment(R.layout.analytics_ppa_userinfo_input_fragment), AutoInject {
val navArgs by navArgs<AnalyticsUserInputFragmentArgs>()
@Inject lateinit var viewModelFactory: CWAViewModelFactoryProvider.Factory
private val vm: AnalyticsUserInputViewModel by cwaViewModelsAssisted(
factoryProducer = { viewModelFactory },
constructorCall = { factory, _ ->
factory as AnalyticsUserInputViewModel.Factory
factory.create(navArgs.type)
}
)
private val binding: AnalyticsPpaUserinfoInputFragmentBinding by viewBindingLazy()
@Inject lateinit var itemAdapter: UserInfoItemAdapter
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val toolbarLabel = when (navArgs.type) {
InputType.AGE_GROUP -> R.string.analytics_userinput_agegroup_title
InputType.FEDERAL_STATE -> R.string.analytics_userinput_federalstate_title
InputType.DISTRICT -> R.string.analytics_userinput_district_title
}
binding.toolbar.apply {
setTitle(toolbarLabel)
setNavigationOnClickListener { popBackStack() }
}
binding.inputList.apply {
layoutManager = LinearLayoutManager(requireContext())
adapter = itemAdapter
}
vm.userInfoItems.observe2(this) {
itemAdapter.data = it
}
itemAdapter.onItemClickListener = { vm.selectUserInfoItem(it) }
vm.finishEvent.observe2(this) { popBackStack() }
}
enum class InputType {
AGE_GROUP,
FEDERAL_STATE,
DISTRICT
}
}
package de.rki.coronawarnapp.datadonation.analytics.ui.input
import android.content.Context
import androidx.lifecycle.LiveData
import androidx.lifecycle.asLiveData
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import de.rki.coronawarnapp.R
import de.rki.coronawarnapp.contactdiary.ui.day.ContactDiaryDayViewModel
import de.rki.coronawarnapp.datadonation.analytics.AnalyticsSettings
import de.rki.coronawarnapp.datadonation.analytics.common.Districts
import de.rki.coronawarnapp.datadonation.analytics.common.federalStateShortName
import de.rki.coronawarnapp.datadonation.analytics.common.labelStringRes
import de.rki.coronawarnapp.server.protocols.internal.ppdd.PpaData
import de.rki.coronawarnapp.util.coroutine.DispatcherProvider
import de.rki.coronawarnapp.util.di.AppContext
import de.rki.coronawarnapp.util.ui.SingleLiveEvent
import de.rki.coronawarnapp.util.ui.toLazyString
import de.rki.coronawarnapp.util.ui.toResolvingString
import de.rki.coronawarnapp.util.viewmodel.CWAViewModel
import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactory
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import timber.log.Timber
import java.util.Locale
class AnalyticsUserInputViewModel @AssistedInject constructor(
@Assisted val type: AnalyticsUserInputFragment.InputType,
private val settings: AnalyticsSettings,
@AppContext private val context: Context,
private val districtsSource: Districts,
dispatcherProvider: DispatcherProvider
) : CWAViewModel() {
private val ageGroupSource: Flow<List<UserInfoItem>> = flowOf(PpaData.PPAAgeGroup.values())
.map { ages ->
val selected = settings.userInfoAgeGroup.value
ages.mapNotNull { age ->
if (age == PpaData.PPAAgeGroup.UNRECOGNIZED) return@mapNotNull null
UserInfoItem(
data = age,
isSelected = age == selected,
label = age.labelStringRes.toResolvingString()
)
}
}
private val federalStateSource: Flow<List<UserInfoItem>> = flowOf(PpaData.PPAFederalState.values())
.map { states ->
val selected = settings.userInfoFederalState.value
val items = states
.mapNotNull { state ->
if (state == PpaData.PPAFederalState.UNRECOGNIZED) return@mapNotNull null
if (state == PpaData.PPAFederalState.FEDERAL_STATE_UNSPECIFIED) return@mapNotNull null
UserInfoItem(
data = state,
isSelected = state == selected,
label = state.labelStringRes.toResolvingString()
)
}
.sortedBy { it.label.get(context).toLowerCase(Locale.ROOT) }
val unspecified = UserInfoItem(
data = PpaData.PPAFederalState.FEDERAL_STATE_UNSPECIFIED,
isSelected = selected == PpaData.PPAFederalState.FEDERAL_STATE_UNSPECIFIED,
label = PpaData.PPAFederalState.FEDERAL_STATE_UNSPECIFIED.labelStringRes.toResolvingString()
)
listOf(unspecified) + items
}
private val districtSource: Flow<List<UserInfoItem>> = flow { emit(districtsSource.loadDistricts()) }
.map { allDistricts ->
val ourStateCode = settings.userInfoFederalState.value.federalStateShortName
allDistricts.filter { it.federalStateShortName == ourStateCode }
}
.map { districts ->
val selected = settings.userInfoDistrict.value
val items = districts
.map { district ->
UserInfoItem(
data = district,
isSelected = district.districtId == selected,
label = district.districtName.toLazyString()
)
}
.sortedBy { it.label.get(context).toLowerCase(Locale.ROOT) }
val unspecified = UserInfoItem(
data = Districts.District(),
isSelected = 0 == selected,
label = R.string.analytics_userinput_district_unspecified.toResolvingString()
)
listOf(unspecified) + items
}
val userInfoItems: LiveData<List<UserInfoItem>> = when (type) {
AnalyticsUserInputFragment.InputType.AGE_GROUP -> ageGroupSource
AnalyticsUserInputFragment.InputType.FEDERAL_STATE -> federalStateSource
AnalyticsUserInputFragment.InputType.DISTRICT -> districtSource
}
.catch { Timber.e(it, "Error sourcing list items.") }
.asLiveData(context = dispatcherProvider.Default)
val finishEvent = SingleLiveEvent<Unit>()
fun selectUserInfoItem(item: UserInfoItem) {
when (item.data) {
is PpaData.PPAAgeGroup -> {
settings.userInfoAgeGroup.update { item.data }
}
is PpaData.PPAFederalState -> {
settings.userInfoFederalState.update { item.data }
settings.userInfoDistrict.update { 0 }
}
is Districts.District -> {
settings.userInfoDistrict.update { item.data.districtId }
}
else -> throw IllegalArgumentException()
}
finishEvent.postValue(Unit)
}
@AssistedFactory
interface Factory : CWAViewModelFactory<ContactDiaryDayViewModel> {
fun create(type: AnalyticsUserInputFragment.InputType): AnalyticsUserInputViewModel
}
}
package de.rki.coronawarnapp.datadonation.analytics.ui.input
import de.rki.coronawarnapp.util.ui.LazyString
data class UserInfoItem(
val label: LazyString,
val isSelected: Boolean,
val data: Any
)
package de.rki.coronawarnapp.datadonation.analytics.ui.input
import android.view.ViewGroup
import de.rki.coronawarnapp.R
import de.rki.coronawarnapp.databinding.AnalyticsPpaUserinfoInputAdapterItemBinding
import de.rki.coronawarnapp.ui.lists.BaseAdapter
import javax.inject.Inject
class UserInfoItemAdapter @Inject constructor() : BaseAdapter<UserInfoItemAdapter.VH>() {
private val internalData = mutableListOf<UserInfoItem>()
var data: List<UserInfoItem>
get() = internalData.toList()
set(value) {
internalData.clear()
internalData.addAll(value)
notifyDataSetChanged()
}
var onItemClickListener: (UserInfoItem) -> Unit = {}
override fun getItemCount(): Int = internalData.size
override fun onCreateBaseVH(parent: ViewGroup, viewType: Int): VH = VH(parent)
override fun onBindBaseVH(holder: VH, position: Int, payloads: MutableList<Any>) {
holder.apply {
val item = internalData[position]
bind(item)
itemView.setOnClickListener { onItemClickListener(item) }
}
}
class VH(parent: ViewGroup) : BaseAdapter.VH(R.layout.analytics_ppa_userinfo_input_adapter_item, parent) {
private val viewBinding: AnalyticsPpaUserinfoInputAdapterItemBinding =
AnalyticsPpaUserinfoInputAdapterItemBinding.bind(itemView)
fun bind(item: UserInfoItem) = viewBinding.apply {
label.text = item.label.get(context)
radiobutton.isChecked = item.isSelected
}
}
}
......@@ -4,6 +4,7 @@ import dagger.Binds
import dagger.Module
import dagger.android.ContributesAndroidInjector
import dagger.multibindings.IntoMap
import de.rki.coronawarnapp.datadonation.analytics.ui.AnalyticsUIModule
import de.rki.coronawarnapp.tracing.ui.details.TracingDetailsFragmentModule
import de.rki.coronawarnapp.ui.information.InformationFragmentModule
import de.rki.coronawarnapp.ui.interoperability.InteroperabilityConfigurationFragment
......@@ -28,7 +29,8 @@ import de.rki.coronawarnapp.util.viewmodel.CWAViewModelKey
SettingFragmentsModule::class,
SubmissionFragmentModule::class,
InformationFragmentModule::class,
NewReleaseInfoFragmentModule::class
NewReleaseInfoFragmentModule::class,
AnalyticsUIModule::class
]
)
abstract class MainActivityModule {
......
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="@color/colorAccent" android:state_checked="true" />
<item android:color="@color/colorStableHairlineLight" />
</selector>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?selectableItemBackground">
<RadioButton
android:id="@+id/radiobutton"
style="@style/Widget.AppCompat.CompoundButton.RadioButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:buttonTint="@color/radiobutton_accented"
android:buttonTintMode="src_atop"
android:clickable="false"
android:focusable="false" />
<TextView
android:id="@+id/label"
style="@style/body1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="64dp"
android:layout_marginEnd="16dp"
tools:text="Keine Angabe" />
</FrameLayout>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
style="@style/CWAToolbar.Close"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/colorBackground"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:title="Ihr Alter/Bundesland/Kreis " />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/input_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:listitem="@layout/analytics_ppa_userinfo_input_adapter_item" />
</LinearLayout>
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment