Skip to content
Snippets Groups Projects
Unverified Commit e9c75459 authored by Juraj Kusnier's avatar Juraj Kusnier Committed by GitHub
Browse files

Implement Self-CheckIn (EXPOSUREAPP-5747) (#2757)


* Create self-checkin event

* navigation to check-in

* Prevent multiple self-check-ins

* Fix merge

* Update layout

* Allow check-in multiple times

* fix lint

* Update test menu

* Force onboarding before check-in by deeplink

* fix typos

Co-authored-by: default avatarLukas Lechner <lukas.lechner@sap.com>
Co-authored-by: default avatarharambasicluka <64483219+harambasicluka@users.noreply.github.com>
Co-authored-by: default avatarI502720 <axel.herbstreith@sap.com>
parent a4ccf034
No related branches found
Tags v2.1.0-RC5
No related merge requests found
Showing
with 93 additions and 7 deletions
...@@ -6,6 +6,7 @@ import dagger.assisted.AssistedFactory ...@@ -6,6 +6,7 @@ import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject import dagger.assisted.AssistedInject
import de.rki.coronawarnapp.contactdiary.ui.ContactDiarySettings import de.rki.coronawarnapp.contactdiary.ui.ContactDiarySettings
import de.rki.coronawarnapp.environment.BuildConfigWrap import de.rki.coronawarnapp.environment.BuildConfigWrap
import de.rki.coronawarnapp.eventregistration.TraceLocationSettings
import de.rki.coronawarnapp.main.CWASettings import de.rki.coronawarnapp.main.CWASettings
import de.rki.coronawarnapp.util.coroutine.DispatcherProvider import de.rki.coronawarnapp.util.coroutine.DispatcherProvider
import de.rki.coronawarnapp.util.viewmodel.CWAViewModel import de.rki.coronawarnapp.util.viewmodel.CWAViewModel
...@@ -13,6 +14,7 @@ import de.rki.coronawarnapp.util.viewmodel.SimpleCWAViewModelFactory ...@@ -13,6 +14,7 @@ import de.rki.coronawarnapp.util.viewmodel.SimpleCWAViewModelFactory
class DeltaOnboardingFragmentViewModel @AssistedInject constructor( class DeltaOnboardingFragmentViewModel @AssistedInject constructor(
private val settings: CWASettings, private val settings: CWASettings,
private val traceLocationSettings: TraceLocationSettings,
private val contactDiarySettings: ContactDiarySettings, private val contactDiarySettings: ContactDiarySettings,
dispatcherProvider: DispatcherProvider dispatcherProvider: DispatcherProvider
) : CWAViewModel(dispatcherProvider = dispatcherProvider) { ) : CWAViewModel(dispatcherProvider = dispatcherProvider) {
...@@ -43,10 +45,21 @@ class DeltaOnboardingFragmentViewModel @AssistedInject constructor( ...@@ -43,10 +45,21 @@ class DeltaOnboardingFragmentViewModel @AssistedInject constructor(
fun isDeltaOnboardingDone() = settings.wasInteroperabilityShownAtLeastOnce fun isDeltaOnboardingDone() = settings.wasInteroperabilityShownAtLeastOnce
fun setDeltaOboardinDone(value: Boolean) { fun setDeltaOnboardingDone(value: Boolean) {
settings.wasInteroperabilityShownAtLeastOnce = value settings.wasInteroperabilityShownAtLeastOnce = value
} }
fun isAttendeeOnboardingDone() =
traceLocationSettings.onboardingStatus == TraceLocationSettings.OnboardingStatus.ONBOARDED_2_0
fun setAttendeeOnboardingDone(value: Boolean) {
traceLocationSettings.onboardingStatus =
if (value)
TraceLocationSettings.OnboardingStatus.ONBOARDED_2_0
else
TraceLocationSettings.OnboardingStatus.NOT_ONBOARDED
}
@AssistedFactory @AssistedFactory
interface Factory : SimpleCWAViewModelFactory<DeltaOnboardingFragmentViewModel> interface Factory : SimpleCWAViewModelFactory<DeltaOnboardingFragmentViewModel>
} }
...@@ -26,6 +26,7 @@ class DeltaonboardingFragment : Fragment(R.layout.fragment_test_deltaonboarding) ...@@ -26,6 +26,7 @@ class DeltaonboardingFragment : Fragment(R.layout.fragment_test_deltaonboarding)
binding.switchContactJournalOnboarding.isChecked = viewModel.isContactJournalOnboardingDone() binding.switchContactJournalOnboarding.isChecked = viewModel.isContactJournalOnboardingDone()
binding.switchDeltaOnboarding.isChecked = viewModel.isDeltaOnboardingDone() binding.switchDeltaOnboarding.isChecked = viewModel.isDeltaOnboardingDone()
binding.switchAttendeeOnboarding.isChecked = viewModel.isAttendeeOnboardingDone()
viewModel.changelogVersion.observe(viewLifecycleOwner) { viewModel.changelogVersion.observe(viewLifecycleOwner) {
binding.lastChangelogEdittext.setText(it.toString()) binding.lastChangelogEdittext.setText(it.toString())
} }
...@@ -48,7 +49,11 @@ class DeltaonboardingFragment : Fragment(R.layout.fragment_test_deltaonboarding) ...@@ -48,7 +49,11 @@ class DeltaonboardingFragment : Fragment(R.layout.fragment_test_deltaonboarding)
} }
binding.switchDeltaOnboarding.setOnCheckedChangeListener { _, value -> binding.switchDeltaOnboarding.setOnCheckedChangeListener { _, value ->
viewModel.setDeltaOboardinDone(value) viewModel.setDeltaOnboardingDone(value)
}
binding.switchAttendeeOnboarding.setOnCheckedChangeListener { _, value ->
viewModel.setAttendeeOnboardingDone(value)
} }
} }
......
...@@ -146,5 +146,32 @@ ...@@ -146,5 +146,32 @@
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/debug_container4"
style="@style/Card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="@dimen/spacing_tiny">
<TextView
android:id="@+id/event_creation_title"
style="@style/headline6"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Event Creation Onboarding"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.switchmaterial.SwitchMaterial
android:id="@+id/switch_attendee_onboarding"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/spacing_small"
android:text="Attendee onboarding finished"
app:layout_constraintTop_toBottomOf="@id/event_creation_title" />
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout> </LinearLayout>
</androidx.core.widget.NestedScrollView> </androidx.core.widget.NestedScrollView>
...@@ -26,6 +26,10 @@ class CheckInOnboardingFragment : Fragment(R.layout.fragment_trace_location_onbo ...@@ -26,6 +26,10 @@ class CheckInOnboardingFragment : Fragment(R.layout.fragment_trace_location_onbo
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
if (viewModel.isOnboardingComplete && args.uri != null) {
doNavigate(CheckInOnboardingFragmentDirections.actionCheckInOnboardingFragmentToCheckInsFragment(args.uri))
}
with(binding) { with(binding) {
checkInOnboardingAcknowledge.setOnClickListener { viewModel.onAcknowledged() } checkInOnboardingAcknowledge.setOnClickListener { viewModel.onAcknowledged() }
// TODO if consent is already given: should the text be changed? // TODO if consent is already given: should the text be changed?
...@@ -45,7 +49,7 @@ class CheckInOnboardingFragment : Fragment(R.layout.fragment_trace_location_onbo ...@@ -45,7 +49,7 @@ class CheckInOnboardingFragment : Fragment(R.layout.fragment_trace_location_onbo
doNavigate( doNavigate(
when (navEvent) { when (navEvent) {
CheckInOnboardingNavigation.AcknowledgedNavigation -> CheckInOnboardingNavigation.AcknowledgedNavigation ->
CheckInOnboardingFragmentDirections.actionCheckInOnboardingFragmentToCheckInsFragment() CheckInOnboardingFragmentDirections.actionCheckInOnboardingFragmentToCheckInsFragment(args.uri)
CheckInOnboardingNavigation.DataProtectionNavigation -> CheckInOnboardingNavigation.DataProtectionNavigation ->
CheckInOnboardingFragmentDirections.actionCheckInOnboardingFragmentToPrivacyFragment() CheckInOnboardingFragmentDirections.actionCheckInOnboardingFragmentToPrivacyFragment()
} }
......
...@@ -6,6 +6,8 @@ sealed class TraceLocationEvent { ...@@ -6,6 +6,8 @@ sealed class TraceLocationEvent {
data class DuplicateItem(val traceLocation: TraceLocation) : TraceLocationEvent() data class DuplicateItem(val traceLocation: TraceLocation) : TraceLocationEvent()
data class SelfCheckIn(val traceLocation: TraceLocation) : TraceLocationEvent()
data class ConfirmDeleteItem(val traceLocation: TraceLocation) : TraceLocationEvent() data class ConfirmDeleteItem(val traceLocation: TraceLocation) : TraceLocationEvent()
data class ConfirmSwipeItem(val traceLocation: TraceLocation, val position: Int) : TraceLocationEvent() data class ConfirmSwipeItem(val traceLocation: TraceLocation, val position: Int) : TraceLocationEvent()
......
...@@ -7,12 +7,14 @@ import androidx.appcompat.app.AlertDialog ...@@ -7,12 +7,14 @@ import androidx.appcompat.app.AlertDialog
import androidx.appcompat.widget.Toolbar import androidx.appcompat.widget.Toolbar
import androidx.core.view.isGone import androidx.core.view.isGone
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.navigation.NavOptions
import androidx.navigation.fragment.FragmentNavigatorExtras import androidx.navigation.fragment.FragmentNavigatorExtras
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import com.google.android.material.transition.Hold import com.google.android.material.transition.Hold
import de.rki.coronawarnapp.R import de.rki.coronawarnapp.R
import de.rki.coronawarnapp.databinding.TraceLocationOrganizerTraceLocationsListFragmentBinding import de.rki.coronawarnapp.databinding.TraceLocationOrganizerTraceLocationsListFragmentBinding
import de.rki.coronawarnapp.eventregistration.checkins.qrcode.TraceLocation import de.rki.coronawarnapp.eventregistration.checkins.qrcode.TraceLocation
import de.rki.coronawarnapp.ui.eventregistration.attendee.checkins.CheckInsFragment
import de.rki.coronawarnapp.ui.eventregistration.organizer.category.adapter.category.traceLocationCategories import de.rki.coronawarnapp.ui.eventregistration.organizer.category.adapter.category.traceLocationCategories
import de.rki.coronawarnapp.ui.eventregistration.organizer.details.QrCodeDetailFragmentArgs import de.rki.coronawarnapp.ui.eventregistration.organizer.details.QrCodeDetailFragmentArgs
import de.rki.coronawarnapp.util.DialogHelper import de.rki.coronawarnapp.util.DialogHelper
...@@ -105,6 +107,14 @@ class TraceLocationsFragment : Fragment(R.layout.trace_location_organizer_trace_ ...@@ -105,6 +107,14 @@ class TraceLocationsFragment : Fragment(R.layout.trace_location_organizer_trace_
it.traceLocation.id it.traceLocation.id
) )
) )
is TraceLocationEvent.SelfCheckIn -> {
findNavController().navigate(
CheckInsFragment.createCheckInUri(it.traceLocation.locationUrl),
NavOptions.Builder()
.setPopUpTo(R.id.checkInsFragment, true)
.build()
)
}
} }
} }
......
...@@ -4,6 +4,7 @@ import androidx.lifecycle.LiveData ...@@ -4,6 +4,7 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.asLiveData import androidx.lifecycle.asLiveData
import dagger.assisted.AssistedFactory import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject import dagger.assisted.AssistedInject
import de.rki.coronawarnapp.eventregistration.checkins.CheckInRepository
import de.rki.coronawarnapp.eventregistration.checkins.qrcode.TraceLocation import de.rki.coronawarnapp.eventregistration.checkins.qrcode.TraceLocation
import de.rki.coronawarnapp.eventregistration.storage.repo.TraceLocationRepository import de.rki.coronawarnapp.eventregistration.storage.repo.TraceLocationRepository
import de.rki.coronawarnapp.ui.eventregistration.organizer.list.items.TraceLocationItem import de.rki.coronawarnapp.ui.eventregistration.organizer.list.items.TraceLocationItem
...@@ -13,10 +14,12 @@ import de.rki.coronawarnapp.util.coroutine.DispatcherProvider ...@@ -13,10 +14,12 @@ import de.rki.coronawarnapp.util.coroutine.DispatcherProvider
import de.rki.coronawarnapp.util.ui.SingleLiveEvent import de.rki.coronawarnapp.util.ui.SingleLiveEvent
import de.rki.coronawarnapp.util.viewmodel.CWAViewModel import de.rki.coronawarnapp.util.viewmodel.CWAViewModel
import de.rki.coronawarnapp.util.viewmodel.SimpleCWAViewModelFactory import de.rki.coronawarnapp.util.viewmodel.SimpleCWAViewModelFactory
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
class TraceLocationsViewModel @AssistedInject constructor( class TraceLocationsViewModel @AssistedInject constructor(
dispatcherProvider: DispatcherProvider, dispatcherProvider: DispatcherProvider,
checkInsRepository: CheckInRepository,
private val traceLocationRepository: TraceLocationRepository, private val traceLocationRepository: TraceLocationRepository,
private val timeStamper: TimeStamper private val timeStamper: TimeStamper
) : CWAViewModel(dispatcherProvider = dispatcherProvider) { ) : CWAViewModel(dispatcherProvider = dispatcherProvider) {
...@@ -29,11 +32,20 @@ class TraceLocationsViewModel @AssistedInject constructor( ...@@ -29,11 +32,20 @@ class TraceLocationsViewModel @AssistedInject constructor(
.filter { it.endDate == null || it.endDate.isAfter(timeStamper.nowUTC.toDateTime().minusDays(15)) } .filter { it.endDate == null || it.endDate.isAfter(timeStamper.nowUTC.toDateTime().minusDays(15)) }
.sortedBy { it.description } .sortedBy { it.description }
} }
.map { traceLocations -> .combine(checkInsRepository.allCheckIns) { traceLocations, checkIns ->
traceLocations.map { traceLocation -> traceLocations.map { traceLocation ->
Pair(
traceLocation,
checkIns.firstOrNull { traceLocation.locationId == it.traceLocationId && !it.completed } == null
)
}
}
.map { traceLocations ->
traceLocations.map { item ->
TraceLocationVH.Item( TraceLocationVH.Item(
traceLocation = traceLocation, traceLocation = item.first,
onCheckIn = { /* TODO */ }, canCheckIn = item.second,
onCheckIn = { events.postValue(TraceLocationEvent.SelfCheckIn(it)) },
onDuplicate = { events.postValue(TraceLocationEvent.DuplicateItem(it)) }, onDuplicate = { events.postValue(TraceLocationEvent.DuplicateItem(it)) },
onShowPrint = { events.postValue(TraceLocationEvent.StartQrCodePosterFragment(it)) }, onShowPrint = { events.postValue(TraceLocationEvent.StartQrCodePosterFragment(it)) },
onDeleteItem = { events.postValue(TraceLocationEvent.ConfirmDeleteItem(it)) }, onDeleteItem = { events.postValue(TraceLocationEvent.ConfirmDeleteItem(it)) },
......
...@@ -2,6 +2,7 @@ package de.rki.coronawarnapp.ui.eventregistration.organizer.list.items ...@@ -2,6 +2,7 @@ package de.rki.coronawarnapp.ui.eventregistration.organizer.list.items
import android.view.ViewGroup import android.view.ViewGroup
import androidx.core.view.isGone import androidx.core.view.isGone
import androidx.core.view.isVisible
import de.rki.coronawarnapp.R import de.rki.coronawarnapp.R
import de.rki.coronawarnapp.databinding.TraceLocationOrganizerTraceLocationsItemBinding import de.rki.coronawarnapp.databinding.TraceLocationOrganizerTraceLocationsItemBinding
import de.rki.coronawarnapp.eventregistration.checkins.qrcode.TraceLocation import de.rki.coronawarnapp.eventregistration.checkins.qrcode.TraceLocation
...@@ -62,12 +63,14 @@ class TraceLocationVH(parent: ViewGroup) : ...@@ -62,12 +63,14 @@ class TraceLocationVH(parent: ViewGroup) :
} }
} }
checkinAction.isVisible = item.canCheckIn
checkinAction.setOnClickListener { item.onCheckIn(item.traceLocation) } checkinAction.setOnClickListener { item.onCheckIn(item.traceLocation) }
itemView.setOnClickListener { item.onCardClicked(item.traceLocation, adapterPosition) } itemView.setOnClickListener { item.onCardClicked(item.traceLocation, adapterPosition) }
} }
data class Item( data class Item(
val traceLocation: TraceLocation, val traceLocation: TraceLocation,
val canCheckIn: Boolean,
val onCheckIn: (TraceLocation) -> Unit, val onCheckIn: (TraceLocation) -> Unit,
val onDuplicate: (TraceLocation) -> Unit, val onDuplicate: (TraceLocation) -> Unit,
val onShowPrint: (TraceLocation) -> Unit, val onShowPrint: (TraceLocation) -> Unit,
......
...@@ -85,6 +85,11 @@ ...@@ -85,6 +85,11 @@
app:barrierDirection="bottom" app:barrierDirection="bottom"
app:constraint_referenced_ids="address,duration,icon" /> app:constraint_referenced_ids="address,duration,icon" />
<Space
android:layout_width="match_parent"
android:layout_height="16dp"
app:layout_constraintTop_toBottomOf="@id/button_barrier" />
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/checkin_action" android:id="@+id/checkin_action"
style="@style/Widget.MaterialComponents.Button.OutlinedButton" style="@style/Widget.MaterialComponents.Button.OutlinedButton"
......
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
android:name="de.rki.coronawarnapp.ui.eventregistration.attendee.onboarding.CheckInOnboardingFragment" android:name="de.rki.coronawarnapp.ui.eventregistration.attendee.onboarding.CheckInOnboardingFragment"
android:label="CheckInOnboardingFragment" android:label="CheckInOnboardingFragment"
tools:layout="@layout/fragment_trace_location_onboarding"> tools:layout="@layout/fragment_trace_location_onboarding">
<deepLink app:uri="coronawarnapp://check-ins/{uri}" />
<action <action
android:id="@+id/action_checkInOnboardingFragment_to_checkInsFragment" android:id="@+id/action_checkInOnboardingFragment_to_checkInsFragment"
app:destination="@id/checkInsFragment" app:destination="@id/checkInsFragment"
...@@ -21,6 +22,11 @@ ...@@ -21,6 +22,11 @@
android:name="showBottomNav" android:name="showBottomNav"
android:defaultValue="true" android:defaultValue="true"
app:argType="boolean" /> app:argType="boolean" />
<argument
android:name="uri"
android:defaultValue="@null"
app:argType="string"
app:nullable="true" />
</fragment> </fragment>
<fragment <fragment
android:id="@+id/checkInPrivacyFragment" android:id="@+id/checkInPrivacyFragment"
...@@ -56,7 +62,6 @@ ...@@ -56,7 +62,6 @@
android:name="de.rki.coronawarnapp.ui.eventregistration.attendee.checkins.CheckInsFragment" android:name="de.rki.coronawarnapp.ui.eventregistration.attendee.checkins.CheckInsFragment"
android:label="CheckInsFragment" android:label="CheckInsFragment"
tools:layout="@layout/trace_location_attendee_checkins_fragment"> tools:layout="@layout/trace_location_attendee_checkins_fragment">
<deepLink app:uri="coronawarnapp://check-ins/{uri}" />
<action <action
android:id="@+id/action_checkInsFragment_to_scanCheckInQrCodeFragment" android:id="@+id/action_checkInsFragment_to_scanCheckInQrCodeFragment"
app:destination="@id/scanCheckInQrCodeFragment" /> app:destination="@id/scanCheckInQrCodeFragment" />
......
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