Skip to content
This repository has been archived by the owner on Jun 20, 2023. It is now read-only.

Profile screen (EXPOSUREAPP-6029) #2964

Merged
merged 69 commits into from
Apr 29, 2021
Merged
Show file tree
Hide file tree
Changes from 64 commits
Commits
Show all changes
69 commits
Select commit Hold shift + click to select a range
88e3511
Create rat profile nav graph skeleton
mtwalli Apr 15, 2021
a715ac8
Rename ids
mtwalli Apr 15, 2021
b0a92d1
Wire card logic
mtwalli Apr 15, 2021
d88d65f
Adjust strings
mtwalli Apr 15, 2021
35ce7bd
Open profile card
mtwalli Apr 15, 2021
d99b3fa
Create Profile settings
mtwalli Apr 15, 2021
c2261e8
Navigation skeleton
mtwalli Apr 15, 2021
249fbbb
Wire navigation
mtwalli Apr 15, 2021
1ae3f30
Adjust navigation
mtwalli Apr 15, 2021
341460e
Fix test
mtwalli Apr 15, 2021
11e0ff8
Create Profile screen
mtwalli Apr 15, 2021
3e13c66
do navigate
mtwalli Apr 15, 2021
9287707
Set dummy profile
mtwalli Apr 15, 2021
9f72215
Profile validation
mtwalli Apr 15, 2021
926e5c6
Update SubmissionDispatcherViewModel.kt
mtwalli Apr 15, 2021
64020fa
Extract strings
mtwalli Apr 16, 2021
a338ee0
Navigation
mtwalli Apr 16, 2021
5606f3d
Extra lines
mtwalli Apr 16, 2021
a384f01
Adjust navigation
mtwalli Apr 16, 2021
45684ba
lint
mtwalli Apr 16, 2021
3940cc0
Merge branch 'release/2.2.x' into feature/6025-rat-create-profile-card
mtwalli Apr 19, 2021
921ea9e
Merge branch 'release/2.2.x' into feature/6025-rat-create-profile-card
mtwalli Apr 20, 2021
4ca1464
Merge branch 'release/2.2.x' into feature/6025-rat-create-profile-card
mtwalli Apr 20, 2021
0ba04f9
Add new fields
mtwalli Apr 20, 2021
e0d85a4
rename to zip code
mtwalli Apr 20, 2021
b1ed6a5
Update strings.xml
mtwalli Apr 20, 2021
d8a5aa7
fix layout
mtwalli Apr 20, 2021
11008a2
Bind new fields
mtwalli Apr 20, 2021
5a08522
Show date picker
mtwalli Apr 20, 2021
a240a6b
Create RATProfileCreateFragmentViewModelTest.kt
mtwalli Apr 20, 2021
ea21c46
Move strings to antigen strings
mtwalli Apr 20, 2021
63a59f9
Merge branch 'release/2.2.x' into feature/6025-rat-create-profile-card
mtwalli Apr 26, 2021
05beb84
Refactoring
mtwalli Apr 26, 2021
798c03e
Align naming
mtwalli Apr 26, 2021
13610d5
Different prefs model and some tests
mtwalli Apr 26, 2021
5528aed
UI
mtwalli Apr 26, 2021
f9587e2
Bind profile data
mtwalli Apr 26, 2021
6da5e2e
lint
mtwalli Apr 26, 2021
417ea71
Merge branch 'release/2.2.x' into feature/6029-profile-screen
mtwalli Apr 26, 2021
a5cf331
Merge branch 'release/2.2.x' into feature/6025-rat-create-profile-card
mtwalli Apr 26, 2021
a545cca
Merge branch 'release/2.2.x' into feature/6025-rat-create-profile-card
mtwalli Apr 27, 2021
3a45e8e
Use LocalDate directly
mtwalli Apr 27, 2021
e1d942e
Update tests
mtwalli Apr 27, 2021
23d0c42
Merge branch 'feature/6025-rat-create-profile-card' into feature/6029…
mtwalli Apr 27, 2021
f1fdf14
Refactoring
mtwalli Apr 27, 2021
289d9d7
Update styles.xml
mtwalli Apr 27, 2021
c5f07a8
Update rat_profile_onboarding_fragment.xml
mtwalli Apr 27, 2021
40ddda5
Adjust Onboarding navigation
mtwalli Apr 27, 2021
0b3aafe
lint
mtwalli Apr 27, 2021
6d76676
Show deletion dialog - continue flow
mtwalli Apr 27, 2021
414fd0a
V-Card
mtwalli Apr 27, 2021
791392d
Add gone margin
mtwalli Apr 28, 2021
b4cf6e3
Merge branch 'release/2.2.x' into feature/6029-profile-screen
mtwalli Apr 28, 2021
db35a8a
Merge branch 'release/2.2.x' into feature/6029-profile-screen
mtwalli Apr 28, 2021
32f9db7
remove un-required ()
mtwalli Apr 28, 2021
b180c0c
Reset date once date is cleared
mtwalli Apr 28, 2021
97b3441
Link
mtwalli Apr 28, 2021
96567a2
Add FN
mtwalli Apr 28, 2021
7295784
Remove close button
mtwalli Apr 28, 2021
2de4401
Display all info
mtwalli Apr 28, 2021
c9bfc72
lint
mtwalli Apr 28, 2021
edb2f02
Suppress lint
mtwalli Apr 28, 2021
88ff8d7
Add dialog text
mtwalli Apr 28, 2021
52f84d9
Merge branch 'release/2.2.x' into feature/6029-profile-screen
mtwalli Apr 28, 2021
596c6bb
Merge branch 'release/2.2.x' into feature/6029-profile-screen
harambasicluka Apr 28, 2021
5131965
Extra line spacing
mtwalli Apr 29, 2021
8cb494a
Merge branch 'release/2.2.x' into feature/6029-profile-screen
mtwalli Apr 29, 2021
4bf7fa6
Merge branch 'release/2.2.x' into feature/6029-profile-screen
harambasicluka Apr 29, 2021
d53aeeb
Merge branch 'release/2.2.x' into feature/6029-profile-screen
mtwalli Apr 29, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package de.rki.coronawarnapp.coronatest.antigen.profile

import android.content.Context
import androidx.core.content.edit
import com.google.gson.Gson
import dagger.Reusable
import de.rki.coronawarnapp.util.di.AppContext
import de.rki.coronawarnapp.util.preferences.clearAndNotify
import de.rki.coronawarnapp.util.preferences.createFlowPreference
import de.rki.coronawarnapp.util.serialization.BaseGson
import de.rki.coronawarnapp.util.serialization.fromJson
Expand Down Expand Up @@ -35,9 +35,17 @@ class RATProfileSettings @Inject constructor(
}
)

fun clear() = prefs.clearAndNotify()
val onboarded = prefs.createFlowPreference(
key = PREFS_KEY_ONBOARDED,
defaultValue = false
)

fun deleteProfile() = prefs.edit(commit = true) {
remove(PREFS_KEY_PROFILE)
}

companion object {
private const val PREFS_KEY_PROFILE = "ratprofile.settings.profile"
private const val PREFS_KEY_ONBOARDED = "ratprofile.settings.onboarded"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package de.rki.coronawarnapp.coronatest.antigen.profile

import dagger.Reusable
import de.rki.coronawarnapp.util.TimeStamper
import org.joda.time.format.ISODateTimeFormat
import javax.inject.Inject

@Reusable
class VCard @Inject constructor(
private val timeStamper: TimeStamper
) {

/**
* Return V-Card format for [RATProfile]
* @return [String]
*/
fun create(ratProfile: RATProfile): String = ratProfile.run {
val lastName = lastName.escapeAll()
val firstName = firstName.escapeAll()
val fullName = buildString {
append(firstName)
if (lastName.isNotBlank()) {
append(" $lastName")
}
}
val city = city.escapeAll()
val street = street.escapeAll()
val zipCode = zipCode.escapeAll()
val phone = phone.escapeAll()
val email = email.escapeAll()
val birthDate = birthDate?.toString(ISODateTimeFormat.basicDate()).orEmpty()
val rev = timeStamper.nowUTC.toString(ISODateTimeFormat.basicDateTimeNoMillis()) // Time the vCard was updated
"""
BEGIN:VCARD
VERSION:4.0
N:$lastName;$firstName;;;
FN:$fullName
BDAY:$birthDate
EMAIL;TYPE=home:$email
TEL;TYPE="cell,home":$phone
ADR;TYPE=home:;;$street;$city;;$zipCode
REV:$rev
END:VCARD
""".trimIndent()
}

private fun String.escapeAll(): String = replace("\n", "")
.replace("\\", "\\\\")
.replace(",", "\\,")
.replace(";", "\\;")
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,20 @@ import dagger.Module
import dagger.android.ContributesAndroidInjector
import de.rki.coronawarnapp.ui.coronatest.rat.profile.create.RATProfileCreateFragment
import de.rki.coronawarnapp.ui.coronatest.rat.profile.create.RATProfileCreateFragmentModule
import de.rki.coronawarnapp.ui.coronatest.rat.profile.onboarding.RATProfileOnboardingFragment
import de.rki.coronawarnapp.ui.coronatest.rat.profile.onboarding.RATProfileOnboardingFragmentModule
import de.rki.coronawarnapp.ui.coronatest.rat.profile.qrcode.RATProfileQrCodeFragment
import de.rki.coronawarnapp.ui.coronatest.rat.profile.qrcode.RATProfileQrCodeFragmentModule

@Module
internal abstract class RATProfileUIModule {

@ContributesAndroidInjector(modules = [RATProfileCreateFragmentModule::class])
abstract fun ratProfileCreateFragment(): RATProfileCreateFragment

@ContributesAndroidInjector(modules = [RATProfileQrCodeFragmentModule::class])
abstract fun ratProfileQrCodeFragment(): RATProfileQrCodeFragment

@ContributesAndroidInjector(modules = [RATProfileOnboardingFragmentModule::class])
abstract fun ratProfileOnboardingFragment(): RATProfileOnboardingFragment
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ class RATProfileCreateFragment : Fragment(R.layout.rat_profile_create_fragment),

// Birth date
birthDateInputEdit.setOnClickListener { openDatePicker() }
birthDateInputEdit.doAfterTextChanged {
if (it.toString().isBlank()) {
viewModel.birthDateChanged(null)
}
}

// Address
streetInputEdit.doAfterTextChanged { viewModel.streetChanged(it.toString()) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ class RATProfileCreateFragmentViewModel @AssistedInject constructor(
}
}

fun birthDateChanged(birthDate: LocalDate) {
fun birthDateChanged(birthDate: LocalDate?) {
profileData.apply {
value = value?.copy(birthDate = birthDate)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,28 @@ import android.view.View
import androidx.fragment.app.Fragment
import de.rki.coronawarnapp.R
import de.rki.coronawarnapp.databinding.RatProfileOnboardingFragmentBinding
import de.rki.coronawarnapp.util.di.AutoInject
import de.rki.coronawarnapp.util.ui.doNavigate
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.cwaViewModels
import javax.inject.Inject

class RATProfileOnboardingFragment : Fragment(R.layout.rat_profile_onboarding_fragment) {
class RATProfileOnboardingFragment : Fragment(R.layout.rat_profile_onboarding_fragment), AutoInject {

private val binding: RatProfileOnboardingFragmentBinding by viewBindingLazy()

@Inject lateinit var viewModelFactory: CWAViewModelFactoryProvider.Factory

private val viewModel: RATProfileOnboardingFragmentViewModel by cwaViewModels { viewModelFactory }

override fun onViewCreated(view: View, savedInstanceState: Bundle?) =
with(binding) {
toolbar.setNavigationOnClickListener { popBackStack() }

nextButton.setOnClickListener {
viewModel.onNext()
doNavigate(
RATProfileOnboardingFragmentDirections
.actionRatProfileOnboardingFragmentToRatProfileCreateFragment()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package de.rki.coronawarnapp.ui.coronatest.rat.profile.onboarding

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 RATProfileOnboardingFragmentModule {
@Binds
@IntoMap
@CWAViewModelKey(RATProfileOnboardingFragmentViewModel::class)
abstract fun ratProfileOnboardingFragment(
factory: RATProfileOnboardingFragmentViewModel.Factory
): CWAViewModelFactory<out CWAViewModel>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package de.rki.coronawarnapp.ui.coronatest.rat.profile.onboarding

import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import de.rki.coronawarnapp.coronatest.antigen.profile.RATProfileSettings
import de.rki.coronawarnapp.util.viewmodel.CWAViewModel
import de.rki.coronawarnapp.util.viewmodel.SimpleCWAViewModelFactory

class RATProfileOnboardingFragmentViewModel @AssistedInject constructor(
private val ratProfileSettings: RATProfileSettings,
) : CWAViewModel() {

fun onNext() {
ratProfileSettings.onboarded.update { true }
}

@AssistedFactory
interface Factory : SimpleCWAViewModelFactory<RATProfileOnboardingFragmentViewModel>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package de.rki.coronawarnapp.ui.coronatest.rat.profile.qrcode

sealed class ProfileQrCodeNavigation {
object Back : ProfileQrCodeNavigation()
object SubmissionConsent : ProfileQrCodeNavigation()
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,106 @@ package de.rki.coronawarnapp.ui.coronatest.rat.profile.qrcode

import android.os.Bundle
import android.view.View
import android.widget.LinearLayout
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.text.bold
import androidx.core.text.buildSpannedString
import androidx.fragment.app.Fragment
import androidx.navigation.fragment.findNavController
import com.google.android.material.appbar.AppBarLayout
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import de.rki.coronawarnapp.R
import de.rki.coronawarnapp.coronatest.antigen.profile.RATProfile
import de.rki.coronawarnapp.databinding.RatProfileQrCodeFragmentBinding
import de.rki.coronawarnapp.util.di.AutoInject
import de.rki.coronawarnapp.util.joinToSpannable
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.cwaViewModels
import javax.inject.Inject
import kotlin.math.abs

class RATProfileQrCodeFragment : Fragment(R.layout.rat_profile_qr_code_fragment) {
class RATProfileQrCodeFragment : Fragment(R.layout.rat_profile_qr_code_fragment), AutoInject {

@Inject lateinit var viewModelFactory: CWAViewModelFactoryProvider.Factory

private val viewModel: RATProfileQrCodeFragmentViewModel by cwaViewModels { viewModelFactory }
private val binding: RatProfileQrCodeFragmentBinding by viewBindingLazy()

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
setToolbarOverlay()
binding.apply {
appBarLayout.addOnOffsetChangedListener(
AppBarLayout.OnOffsetChangedListener { appBarLayout, verticalOffset ->
val alpha = 1.0f - abs(verticalOffset / (appBarLayout.totalScrollRange.toFloat() * 0.5f))
title.alpha = alpha
}
)

nextButton.setOnClickListener { viewModel.onNext() }
toolbar.setNavigationOnClickListener { viewModel.onClose() }
toolbar.setOnMenuItemClickListener {
confirmDeletionDialog()
true
}
}
viewModel.profile.observe(viewLifecycleOwner) { personProfile ->
with(binding) {
progressBar.hide()
personProfile.profile?.let { bindPersonInfo(it) }
qrCodeImage.setImageBitmap(personProfile.bitmap)
}
}

viewModel.events.observe(viewLifecycleOwner) {
when (it) {
ProfileQrCodeNavigation.Back -> popBackStack()
ProfileQrCodeNavigation.SubmissionConsent ->
findNavController().navigate(R.id.submissionConsentFragment)
}
}
}

private fun confirmDeletionDialog() {
MaterialAlertDialogBuilder(requireContext())
.setTitle(getString(R.string.rat_qr_code_profile_dialog_title))
.setMessage(getString(R.string.rat_qr_code_profile_dialog_message))
.setPositiveButton(getString(R.string.rat_qr_code_profile_dialog_positive_button)) { _, _ ->
viewModel.deleteProfile()
}
.setNegativeButton(getString(R.string.rat_qr_code_profile_dialog_negative_button)) { _, _ ->
// No-Op
}
.show()
}

private fun bindPersonInfo(ratProfile: RATProfile) = with(ratProfile) {
val name = buildSpannedString { bold { append("$firstName $lastName") } }
val birthDate = birthDate?.let {
getString(
R.string.rat_qr_code_profile_birth_date,
birthDate.toString("dd.MM.yyyy").orEmpty()
)
}.orEmpty()

val address = "$zipCode $city"
binding.profileInfo.text = arrayOf(name, birthDate, street, address, phone, email)
.filter { it.isNotBlank() }
.joinToSpannable("\n")
}

private fun setToolbarOverlay() {
val width = requireContext().resources.displayMetrics.widthPixels

val params: CoordinatorLayout.LayoutParams = binding.nestedScrollView.layoutParams
as (CoordinatorLayout.LayoutParams)

val textParams = binding.title.layoutParams as (LinearLayout.LayoutParams)
textParams.bottomMargin = (width / 2) - 24 /* 24 is space between screen border and QrCode */
binding.title.requestLayout() /* 24 is space between screen border and QrCode */

binding.closeButton.setOnClickListener { popBackStack() }
val behavior: AppBarLayout.ScrollingViewBehavior = params.behavior as (AppBarLayout.ScrollingViewBehavior)
behavior.overlayTop = (width / 2) - 24
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package de.rki.coronawarnapp.ui.coronatest.rat.profile.qrcode

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 RATProfileQrCodeFragmentModule {
@Binds
@IntoMap
@CWAViewModelKey(RATProfileQrCodeFragmentViewModel::class)
abstract fun ratProfileQrCodeFragmentViewModel(
factory: RATProfileQrCodeFragmentViewModel.Factory
): CWAViewModelFactory<out CWAViewModel>
}
Loading