From 1985b036d953b8d11b0ed66d28f02fb6802e8529 Mon Sep 17 00:00:00 2001
From: Juraj Kusnier <jurajkusnier@users.noreply.github.com>
Date: Tue, 23 Mar 2021 14:45:54 +0100
Subject: [PATCH] Create Event/Location Flow (EXPOSUREAPP-5720) (#2664)

* Create Event/Location Flow Implementation

* Update Event/Location Flow Implementation

* Lint format

* Code format fix

* Update nav_graph

* Update DatePicker behaviour

* Update layout

* Hide keyboard when navigating back

* Update strings

* Small design fixes

* Remove bold text

* Keep form values

* Add TraceLocationCreateViewModelTest unit test

* Small changes

* Code refactoring

Co-authored-by: harambasicluka <64483219+harambasicluka@users.noreply.github.com>
Co-authored-by: Lukas Lechner <lukas.lechner@sap.com>
---
 .../res/navigation/test_nav_graph.xml         |  16 +-
 .../EventRegistrationUIModule.kt              |   9 +-
 .../category/TraceLocationCategoryFragment.kt |   8 +-
 ...=> TraceLocationCategoryFragmentModule.kt} |   2 +-
 .../adapter/category/TraceLocationCategory.kt |   5 +-
 .../create/TraceLocationCreateFragment.kt     | 189 +++++++++++++++
 .../TraceLocationCreateFragmentModule.kt      |  18 ++
 .../create/TraceLocationCreateViewModel.kt    | 121 ++++++++++
 .../layout/trace_location_create_fragment.xml | 217 ++++++++++++++++++
 ...e_location_organizer_category_fragment.xml |   4 +-
 .../trace_location_organizer_nav_graph.xml    |  23 +-
 .../values-de/event_registration_strings.xml  |  16 ++
 .../res/values/event_registration_strings.xml |  16 ++
 .../src/main/res/values/styles.xml            |  11 +
 .../TraceLocationCreateViewModelTest.kt       | 165 +++++++++++++
 15 files changed, 810 insertions(+), 10 deletions(-)
 rename Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/organizer/category/{TraceLocationCategoryModule.kt => TraceLocationCategoryFragmentModule.kt} (91%)
 create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/organizer/create/TraceLocationCreateFragment.kt
 create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/organizer/create/TraceLocationCreateFragmentModule.kt
 create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/organizer/create/TraceLocationCreateViewModel.kt
 create mode 100644 Corona-Warn-App/src/main/res/layout/trace_location_create_fragment.xml
 create mode 100644 Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/eventregistration/organizer/create/TraceLocationCreateViewModelTest.kt

diff --git a/Corona-Warn-App/src/deviceForTesters/res/navigation/test_nav_graph.xml b/Corona-Warn-App/src/deviceForTesters/res/navigation/test_nav_graph.xml
index dc81ad56f..cc69872aa 100644
--- a/Corona-Warn-App/src/deviceForTesters/res/navigation/test_nav_graph.xml
+++ b/Corona-Warn-App/src/deviceForTesters/res/navigation/test_nav_graph.xml
@@ -171,6 +171,20 @@
         android:id="@+id/traceLocationOrganizerCategoriesFragment"
         android:name="de.rki.coronawarnapp.ui.eventregistration.organizer.category.TraceLocationCategoryFragment"
         android:label="TraceLocationCategoryFragment"
-        tools:layout="@layout/trace_location_organizer_category_fragment" />
+        tools:layout="@layout/trace_location_organizer_category_fragment">
+
+        <action
+            android:id="@+id/action_traceLocationOrganizerCategoriesFragment_to_traceLocationCreateFragment"
+            app:destination="@id/traceLocationCreateFragment" />
+    </fragment>
+    <fragment
+        android:id="@+id/traceLocationCreateFragment"
+        android:name="de.rki.coronawarnapp.ui.eventregistration.organizer.create.TraceLocationCreateFragment"
+        android:label="trace_location_create_fragment"
+        tools:layout="@layout/trace_location_create_fragment">
+        <argument
+            android:name="category"
+            app:argType="de.rki.coronawarnapp.ui.eventregistration.organizer.category.adapter.category.TraceLocationCategory" />
+    </fragment>
 
 </navigation>
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/EventRegistrationUIModule.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/EventRegistrationUIModule.kt
index c45fb6984..bf5d601ee 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/EventRegistrationUIModule.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/EventRegistrationUIModule.kt
@@ -2,6 +2,7 @@ package de.rki.coronawarnapp.ui.eventregistration
 
 import dagger.Module
 import dagger.android.ContributesAndroidInjector
+import de.rki.coronawarnapp.ui.eventregistration.organizer.create.TraceLocationCreateFragment
 import de.rki.coronawarnapp.ui.eventregistration.attendee.checkins.CheckInsFragment
 import de.rki.coronawarnapp.ui.eventregistration.attendee.checkins.CheckInsModule
 import de.rki.coronawarnapp.ui.eventregistration.attendee.confirm.ConfirmCheckInFragment
@@ -9,7 +10,8 @@ import de.rki.coronawarnapp.ui.eventregistration.attendee.confirm.ConfirmCheckIn
 import de.rki.coronawarnapp.ui.eventregistration.attendee.scan.ScanCheckInQrCodeFragment
 import de.rki.coronawarnapp.ui.eventregistration.attendee.scan.ScanCheckInQrCodeModule
 import de.rki.coronawarnapp.ui.eventregistration.organizer.category.TraceLocationCategoryFragment
-import de.rki.coronawarnapp.ui.eventregistration.organizer.category.TraceLocationCategoryModule
+import de.rki.coronawarnapp.ui.eventregistration.organizer.category.TraceLocationCategoryFragmentModule
+import de.rki.coronawarnapp.ui.eventregistration.organizer.create.TraceLocationCreateFragmentModule
 
 @Module
 internal abstract class EventRegistrationUIModule {
@@ -23,6 +25,9 @@ internal abstract class EventRegistrationUIModule {
     @ContributesAndroidInjector(modules = [CheckInsModule::class])
     abstract fun checkInsFragment(): CheckInsFragment
 
-    @ContributesAndroidInjector(modules = [TraceLocationCategoryModule::class])
+    @ContributesAndroidInjector(modules = [TraceLocationCategoryFragmentModule::class])
     abstract fun traceLocationCategoryFragment(): TraceLocationCategoryFragment
+
+    @ContributesAndroidInjector(modules = [TraceLocationCreateFragmentModule::class])
+    abstract fun traceLocationCreateFragment(): TraceLocationCreateFragment
 }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/organizer/category/TraceLocationCategoryFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/organizer/category/TraceLocationCategoryFragment.kt
index 251a4a1a8..6c42c65b0 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/organizer/category/TraceLocationCategoryFragment.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/organizer/category/TraceLocationCategoryFragment.kt
@@ -8,12 +8,12 @@ import de.rki.coronawarnapp.R
 import de.rki.coronawarnapp.databinding.TraceLocationOrganizerCategoryFragmentBinding
 import de.rki.coronawarnapp.ui.eventregistration.organizer.category.adapter.TraceLocationCategoryAdapter
 import de.rki.coronawarnapp.util.di.AutoInject
+import de.rki.coronawarnapp.util.ui.doNavigate
 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.cwaViewModels
-import timber.log.Timber
 import javax.inject.Inject
 
 class TraceLocationCategoryFragment : Fragment(R.layout.trace_location_organizer_category_fragment), AutoInject {
@@ -30,8 +30,10 @@ class TraceLocationCategoryFragment : Fragment(R.layout.trace_location_organizer
 
         vm.categoryItems.observe2(this) { categoryItems ->
             val adapter = TraceLocationCategoryAdapter(categoryItems) {
-                // TODO: Set click-listener - Continue with event creation flow in next PR
-                Timber.d("Clicked on TraceLocationCategory: $it")
+                doNavigate(
+                    TraceLocationCategoryFragmentDirections
+                        .actionTraceLocationOrganizerCategoriesFragmentToTraceLocationCreateFragment(it)
+                )
             }
             binding.recyclerViewCategories.adapter = adapter
         }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/organizer/category/TraceLocationCategoryModule.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/organizer/category/TraceLocationCategoryFragmentModule.kt
similarity index 91%
rename from Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/organizer/category/TraceLocationCategoryModule.kt
rename to Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/organizer/category/TraceLocationCategoryFragmentModule.kt
index c410c0643..e2151c927 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/organizer/category/TraceLocationCategoryModule.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/organizer/category/TraceLocationCategoryFragmentModule.kt
@@ -8,7 +8,7 @@ import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactory
 import de.rki.coronawarnapp.util.viewmodel.CWAViewModelKey
 
 @Module
-abstract class TraceLocationCategoryModule {
+abstract class TraceLocationCategoryFragmentModule {
 
     @Binds
     @IntoMap
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/organizer/category/adapter/category/TraceLocationCategory.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/organizer/category/adapter/category/TraceLocationCategory.kt
index 39472f4af..9d9bc2ac3 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/organizer/category/adapter/category/TraceLocationCategory.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/organizer/category/adapter/category/TraceLocationCategory.kt
@@ -1,5 +1,6 @@
 package de.rki.coronawarnapp.ui.eventregistration.organizer.category.adapter.category
 
+import android.os.Parcelable
 import androidx.annotation.StringRes
 import de.rki.coronawarnapp.R
 import de.rki.coronawarnapp.server.protocols.internal.pt.TraceLocationOuterClass
@@ -18,13 +19,15 @@ import de.rki.coronawarnapp.server.protocols.internal.pt.TraceLocationOuterClass
 import de.rki.coronawarnapp.ui.eventregistration.organizer.category.adapter.CategoryItem
 import de.rki.coronawarnapp.ui.eventregistration.organizer.category.adapter.category.TraceLocationUIType.EVENT
 import de.rki.coronawarnapp.ui.eventregistration.organizer.category.adapter.category.TraceLocationUIType.LOCATION
+import kotlinx.parcelize.Parcelize
 
+@Parcelize
 data class TraceLocationCategory(
     val type: TraceLocationOuterClass.TraceLocationType,
     val uiType: TraceLocationUIType,
     @StringRes val title: Int,
     @StringRes val subtitle: Int? = null
-) : CategoryItem {
+) : CategoryItem, Parcelable {
     override val stableId = hashCode().toLong()
 }
 
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/organizer/create/TraceLocationCreateFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/organizer/create/TraceLocationCreateFragment.kt
new file mode 100644
index 000000000..d3fc7232a
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/organizer/create/TraceLocationCreateFragment.kt
@@ -0,0 +1,189 @@
+package de.rki.coronawarnapp.ui.eventregistration.organizer.create
+
+import android.os.Bundle
+import android.text.format.DateFormat.is24HourFormat
+import android.view.View
+import androidx.core.view.isVisible
+import androidx.core.widget.doOnTextChanged
+import androidx.fragment.app.Fragment
+import androidx.navigation.fragment.navArgs
+import com.google.android.material.datepicker.CalendarConstraints
+import com.google.android.material.datepicker.DateValidatorPointForward
+import com.google.android.material.datepicker.MaterialDatePicker
+import com.google.android.material.timepicker.MaterialTimePicker
+import com.google.android.material.timepicker.TimeFormat
+import de.rki.coronawarnapp.R
+import de.rki.coronawarnapp.contactdiary.util.getLocale
+import de.rki.coronawarnapp.contactdiary.util.hideKeyboard
+import de.rki.coronawarnapp.databinding.TraceLocationCreateFragmentBinding
+import de.rki.coronawarnapp.ui.durationpicker.DurationPicker
+import de.rki.coronawarnapp.ui.durationpicker.toContactDiaryFormat
+import de.rki.coronawarnapp.util.di.AutoInject
+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 org.joda.time.DateTimeZone
+import org.joda.time.Duration
+import org.joda.time.LocalDate
+import org.joda.time.LocalDateTime
+import org.joda.time.LocalTime
+import javax.inject.Inject
+
+class TraceLocationCreateFragment : Fragment(R.layout.trace_location_create_fragment), AutoInject {
+
+    private val binding: TraceLocationCreateFragmentBinding by viewBindingLazy()
+    private val navArgs by navArgs<TraceLocationCreateFragmentArgs>()
+
+    @Inject lateinit var viewModelFactory: CWAViewModelFactoryProvider.Factory
+    private val viewModel: TraceLocationCreateViewModel by cwaViewModelsAssisted(
+        keyProducer = { navArgs.category.type.toString() },
+        factoryProducer = { viewModelFactory },
+        constructorCall = { factory, _ ->
+            factory as TraceLocationCreateViewModel.Factory
+            factory.create(navArgs.category)
+        }
+    )
+
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        super.onViewCreated(view, savedInstanceState)
+
+        binding.toolbar.setNavigationOnClickListener {
+            it.hideKeyboard()
+            popBackStack()
+        }
+
+        viewModel.uiState.observe(viewLifecycleOwner) { state ->
+            binding.apply {
+                toolbar.setSubtitle(state.title)
+                valueStart.text = state.getBegin(requireContext().getLocale())
+                valueEnd.text = state.getEnd(requireContext().getLocale())
+                layoutBegin.isVisible = state.isDateVisible
+                layoutEnd.isVisible = state.isDateVisible
+                valueLengthOfStay.text = state.getCheckInLength(resources)
+                buttonSubmit.isEnabled = state.isSendEnable
+            }
+        }
+
+        binding.descriptionInputEdit.doOnTextChanged { text, _, _, _ ->
+            viewModel.description = text?.toString()
+        }
+
+        binding.placeInputEdit.doOnTextChanged { text, _, _, _ ->
+            viewModel.address = text?.toString()
+        }
+
+        binding.layoutBegin.setOnClickListener {
+            it.hideKeyboard()
+            showDatePicker(viewModel.begin) { value ->
+                viewModel.begin = value
+            }
+        }
+
+        binding.layoutEnd.setOnClickListener {
+            it.hideKeyboard()
+            showDatePicker(viewModel.end, viewModel.begin) { value ->
+                viewModel.end = value
+            }
+        }
+
+        binding.layoutLengthOfStay.setOnClickListener {
+            it.hideKeyboard()
+            showDurationPicker()
+        }
+
+        binding.buttonSubmit.setOnClickListener {
+            viewModel.send()
+        }
+    }
+
+    private fun showDatePicker(
+        defaultValue: LocalDateTime?,
+        minConstraint: LocalDateTime? = null,
+        callback: (LocalDateTime) -> Unit
+    ) {
+        MaterialDatePicker
+            .Builder
+            .datePicker()
+            .setSelection(defaultValue?.toDateTime(DateTimeZone.UTC)?.millis)
+            .apply {
+                if (minConstraint != null) {
+                    setCalendarConstraints(
+                        CalendarConstraints.Builder()
+                            .setValidator(
+                                DateValidatorPointForward
+                                    .from(minConstraint.withMillisOfDay(0).toDateTime(DateTimeZone.UTC).millis)
+                            )
+                            .build()
+                    )
+                }
+            }
+            .build()
+            .apply {
+                addOnPositiveButtonClickListener {
+                    showTimePicker(LocalDate(it), defaultValue?.hourOfDay, defaultValue?.minuteOfHour, callback)
+                }
+            }
+            .show(childFragmentManager, DATE_PICKER_TAG)
+    }
+
+    private fun showTimePicker(date: LocalDate, hours: Int?, minutes: Int?, callback: (LocalDateTime) -> Unit) {
+        MaterialTimePicker
+            .Builder()
+            .setTimeFormat(if (is24HourFormat(requireContext())) TimeFormat.CLOCK_24H else TimeFormat.CLOCK_12H)
+            .apply {
+                if (hours != null && minutes != null) {
+                    setHour(hours)
+                    setMinute(minutes)
+                }
+            }
+            .build()
+            .apply {
+                addOnPositiveButtonClickListener {
+                    callback(date.toLocalDateTime(LocalTime(this.hour, this.minute)))
+                }
+            }
+            .show(childFragmentManager, TIME_PICKER_TAG)
+    }
+
+    private fun showDurationPicker() {
+        DurationPicker.Builder()
+            .duration(viewModel.checkInLength?.toContactDiaryFormat() ?: "")
+            .title(getString(R.string.tracelocation_organizer_add_event_length_of_stay))
+            .build()
+            .apply {
+                setDurationChangeListener {
+                    viewModel.checkInLength = it
+                }
+            }
+            .show(parentFragmentManager, DURATION_PICKER_TAG)
+    }
+
+    override fun onSaveInstanceState(outState: Bundle) {
+        super.onSaveInstanceState(
+            outState.apply {
+                putLong(LENGTH_OF_STAY, viewModel.checkInLength?.standardMinutes ?: 0L)
+                putSerializable(BEGIN, viewModel.begin)
+                putSerializable(END, viewModel.end)
+            }
+        )
+    }
+
+    override fun onViewStateRestored(savedInstanceState: Bundle?) {
+        super.onViewStateRestored(savedInstanceState)
+        savedInstanceState?.getLong(LENGTH_OF_STAY)?.let {
+            viewModel.checkInLength = Duration.standardMinutes(it)
+        }
+        viewModel.begin = savedInstanceState?.getSerializable(BEGIN) as LocalDateTime?
+        viewModel.end = savedInstanceState?.getSerializable(END) as LocalDateTime?
+    }
+
+    companion object {
+        private const val DURATION_PICKER_TAG = "duration_picker"
+        private const val DATE_PICKER_TAG = "date_picker"
+        private const val TIME_PICKER_TAG = "time_picker"
+        private const val LENGTH_OF_STAY = "length_of_stay"
+        private const val BEGIN = "begin"
+        private const val END = "end"
+    }
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/organizer/create/TraceLocationCreateFragmentModule.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/organizer/create/TraceLocationCreateFragmentModule.kt
new file mode 100644
index 000000000..b32ebf2fb
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/organizer/create/TraceLocationCreateFragmentModule.kt
@@ -0,0 +1,18 @@
+package de.rki.coronawarnapp.ui.eventregistration.organizer.create
+
+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 TraceLocationCreateFragmentModule {
+
+    @Binds
+    @IntoMap
+    @CWAViewModelKey(TraceLocationCreateViewModel::class)
+    abstract fun traceLocationCreateViewModel(factory: TraceLocationCreateViewModel.Factory):
+        CWAViewModelFactory<out CWAViewModel>
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/organizer/create/TraceLocationCreateViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/organizer/create/TraceLocationCreateViewModel.kt
new file mode 100644
index 000000000..38ec57f89
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/organizer/create/TraceLocationCreateViewModel.kt
@@ -0,0 +1,121 @@
+package de.rki.coronawarnapp.ui.eventregistration.organizer.create
+
+import android.content.res.Resources
+import androidx.annotation.StringRes
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import de.rki.coronawarnapp.R
+import de.rki.coronawarnapp.contactdiary.util.CWADateTimeFormatPatternFactory.shortDatePattern
+import de.rki.coronawarnapp.ui.durationpicker.toReadableDuration
+import de.rki.coronawarnapp.ui.eventregistration.organizer.category.adapter.category.TraceLocationCategory
+import de.rki.coronawarnapp.ui.eventregistration.organizer.category.adapter.category.TraceLocationUIType
+import de.rki.coronawarnapp.util.viewmodel.CWAViewModel
+import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactory
+import org.joda.time.Duration
+import org.joda.time.LocalDateTime
+import java.util.Locale
+import kotlin.properties.ReadWriteProperty
+import kotlin.reflect.KProperty
+
+class TraceLocationCreateViewModel @AssistedInject constructor(
+    @Assisted private val category: TraceLocationCategory
+) : CWAViewModel() {
+
+    private val mutableUiState = MutableLiveData<UIState>()
+    val uiState: LiveData<UIState>
+        get() = mutableUiState
+
+    var description: String? by UpdateDelegate()
+    var address: String? by UpdateDelegate()
+    var checkInLength: Duration? by UpdateDelegate()
+    var begin: LocalDateTime? by UpdateDelegate()
+    var end: LocalDateTime? by UpdateDelegate()
+
+    init {
+        checkInLength = when (category.uiType) {
+            TraceLocationUIType.LOCATION -> {
+                Duration.standardHours(2)
+            }
+            TraceLocationUIType.EVENT -> {
+                Duration.ZERO
+            }
+        }
+    }
+
+    fun send() {
+        // TODO: This will be implemented in another PR
+    }
+
+    private fun updateState() {
+        mutableUiState.value = UIState(
+            begin = begin,
+            end = end,
+            checkInLength = checkInLength,
+            title = category.title,
+            isDateVisible = category.uiType == TraceLocationUIType.EVENT,
+            isSendEnable = when (category.uiType) {
+                TraceLocationUIType.LOCATION -> {
+                    description?.trim()?.length in 1..100 &&
+                        address?.trim()?.length in 0..100 &&
+                        (checkInLength ?: Duration.ZERO) > Duration.ZERO
+                }
+                TraceLocationUIType.EVENT -> {
+                    description?.trim()?.length in 1..100 &&
+                        address?.trim()?.length in 0..100 &&
+                        begin != null &&
+                        end != null &&
+                        end?.isAfter(begin) == true
+                }
+            }
+        )
+    }
+
+    data class UIState(
+        private val begin: LocalDateTime? = null,
+        private val end: LocalDateTime? = null,
+        private val checkInLength: Duration? = null,
+        @StringRes val title: Int,
+        val isDateVisible: Boolean,
+        val isSendEnable: Boolean
+    ) {
+        fun getBegin(locale: Locale) = getFormattedTime(begin, locale)
+
+        fun getEnd(locale: Locale) = getFormattedTime(end, locale)
+
+        fun getCheckInLength(resources: Resources): String? {
+            return checkInLength?.toReadableDuration(
+                suffix = resources.getString(R.string.tracelocation_organizer_duration_suffix)
+            )
+        }
+
+        private fun getFormattedTime(value: LocalDateTime?, locale: Locale) =
+            value?.toString("E, ${locale.shortDatePattern()}   HH:mm", locale)
+    }
+
+    private class UpdateDelegate<T> : ReadWriteProperty<TraceLocationCreateViewModel?, T?> {
+        var value: T? = null
+
+        override fun setValue(
+            thisRef: TraceLocationCreateViewModel?,
+            property: KProperty<*>,
+            value: T?
+        ) {
+            if (value != null) {
+                this.value = value
+            }
+            thisRef?.updateState()
+        }
+
+        override fun getValue(thisRef: TraceLocationCreateViewModel?, property: KProperty<*>): T? {
+            return this.value
+        }
+    }
+
+    @AssistedFactory
+    interface Factory : CWAViewModelFactory<TraceLocationCreateViewModel> {
+        fun create(category: TraceLocationCategory): TraceLocationCreateViewModel
+    }
+}
diff --git a/Corona-Warn-App/src/main/res/layout/trace_location_create_fragment.xml b/Corona-Warn-App/src/main/res/layout/trace_location_create_fragment.xml
new file mode 100644
index 000000000..37ac4ec19
--- /dev/null
+++ b/Corona-Warn-App/src/main/res/layout/trace_location_create_fragment.xml
@@ -0,0 +1,217 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:contentDescription="@string/tracelocation_organizer_category_title"
+    tools:context=".ui.eventregistration.organizer.create.TraceLocationCreateFragment">
+
+    <com.google.android.material.appbar.MaterialToolbar
+        android:id="@+id/toolbar"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        app:navigationIcon="@drawable/ic_back"
+        app:title="@string/tracelocation_organizer_category_title"
+        tools:subtitle="@string/tracelocation_organizer_category_craft_title" />
+
+    <View
+        android:id="@+id/toolbar_divider"
+        android:layout_width="0dp"
+        android:layout_height="@dimen/card_divider"
+        android:background="@color/colorHairline"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/toolbar" />
+
+    <ScrollView
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        android:layout_marginBottom="@dimen/spacing_small"
+        android:fillViewport="true"
+        app:layout_constraintBottom_toTopOf="@id/button_submit"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/toolbar_divider">
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="vertical"
+            android:paddingBottom="@dimen/spacing_normal">
+
+            <com.google.android.material.textfield.TextInputLayout
+                android:id="@+id/description_input_layout"
+                style="@style/TextInputLayoutTheme"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginHorizontal="@dimen/spacing_normal"
+                android:layout_marginTop="@dimen/spacing_small"
+                android:hint="@string/tracelocation_organizer_add_event_description"
+                app:endIconMode="clear_text">
+
+                <com.google.android.material.textfield.TextInputEditText
+                    android:id="@+id/description_input_edit"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:imeOptions="actionNext"
+                    android:inputType="textCapWords"
+                    android:maxLength="100"
+                    android:textStyle="bold" />
+
+            </com.google.android.material.textfield.TextInputLayout>
+
+            <com.google.android.material.textfield.TextInputLayout
+                android:id="@+id/place_input_layout"
+                style="@style/TextInputLayoutTheme"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginHorizontal="@dimen/spacing_normal"
+                android:layout_marginTop="@dimen/spacing_tiny"
+                android:layout_marginBottom="@dimen/spacing_normal"
+                android:hint="@string/tracelocation_organizer_add_event_location"
+                app:endIconMode="clear_text">
+
+                <com.google.android.material.textfield.TextInputEditText
+                    android:id="@+id/place_input_edit"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:imeOptions="actionNext"
+                    android:inputType="textCapWords"
+                    android:maxLength="100" />
+
+            </com.google.android.material.textfield.TextInputLayout>
+
+            <androidx.constraintlayout.widget.ConstraintLayout
+                android:id="@+id/layout_begin"
+                style="@style/ContactDiaryExpandableListItem"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginHorizontal="@dimen/spacing_normal"
+                android:layout_marginBottom="@dimen/spacing_tiny"
+                android:backgroundTint="@color/colorContactDiaryListItem"
+                android:clickable="true"
+                android:focusable="true"
+                android:padding="@dimen/spacing_small">
+
+                <TextView
+                    android:id="@+id/label_start"
+                    style="@style/subtitle"
+                    android:layout_width="0dp"
+                    android:layout_height="wrap_content"
+                    android:text="@string/tracelocation_organizer_add_event_begin"
+                    app:layout_constraintBottom_toBottomOf="parent"
+                    app:layout_constraintEnd_toStartOf="@id/value_start"
+                    app:layout_constraintStart_toStartOf="parent"
+                    app:layout_constraintTop_toTopOf="parent" />
+
+                <TextView
+                    android:id="@+id/value_start"
+                    style="@style/subtitleMedium"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    app:layout_constraintBottom_toBottomOf="parent"
+                    app:layout_constraintEnd_toEndOf="parent"
+                    app:layout_constraintTop_toTopOf="parent"
+                    tools:text="Do., 11.02.21    10:00" />
+
+            </androidx.constraintlayout.widget.ConstraintLayout>
+
+            <androidx.constraintlayout.widget.ConstraintLayout
+                android:id="@+id/layout_end"
+                style="@style/ContactDiaryExpandableListItem"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginHorizontal="@dimen/spacing_normal"
+                android:layout_marginBottom="@dimen/spacing_tiny"
+                android:backgroundTint="@color/colorContactDiaryListItem"
+                android:clickable="true"
+                android:focusable="true"
+                android:padding="@dimen/spacing_small">
+
+                <TextView
+                    android:id="@+id/label_end"
+                    style="@style/subtitle"
+                    android:layout_width="0dp"
+                    android:layout_height="wrap_content"
+                    android:text="@string/tracelocation_organizer_add_event_end"
+                    app:layout_constraintBottom_toBottomOf="parent"
+                    app:layout_constraintEnd_toStartOf="@id/value_end"
+                    app:layout_constraintStart_toStartOf="parent"
+                    app:layout_constraintTop_toTopOf="parent" />
+
+                <TextView
+                    android:id="@+id/value_end"
+                    style="@style/subtitleMedium"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    app:layout_constraintBottom_toBottomOf="parent"
+                    app:layout_constraintEnd_toEndOf="parent"
+                    app:layout_constraintTop_toTopOf="parent"
+                    tools:text="Do., 11.02.21    10:00" />
+
+            </androidx.constraintlayout.widget.ConstraintLayout>
+
+            <androidx.constraintlayout.widget.ConstraintLayout
+                android:id="@+id/layout_length_of_stay"
+                style="@style/ContactDiaryExpandableListItem"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginHorizontal="@dimen/spacing_normal"
+                android:backgroundTint="@color/colorContactDiaryListItem"
+                android:clickable="true"
+                android:focusable="true"
+                android:padding="@dimen/spacing_small">
+
+                <TextView
+                    android:id="@+id/label_length_of_stay"
+                    style="@style/subtitle"
+                    android:layout_width="0dp"
+                    android:layout_height="wrap_content"
+                    android:text="@string/tracelocation_organizer_add_event_length_of_stay"
+                    app:layout_constraintEnd_toStartOf="@id/value_length_of_stay"
+                    app:layout_constraintStart_toStartOf="parent"
+                    app:layout_constraintTop_toTopOf="parent" />
+
+                <TextView
+                    android:id="@+id/value_length_of_stay"
+                    style="@style/subtitleMedium"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    app:layout_constraintEnd_toEndOf="parent"
+                    app:layout_constraintTop_toTopOf="parent"
+                    tools:text="1:00 Std." />
+
+                <TextView
+                    android:id="@+id/description_length_of_stay"
+                    style="@style/subtitleMedium"
+                    android:layout_width="0dp"
+                    android:layout_height="wrap_content"
+                    android:paddingTop="@dimen/spacing_small"
+                    android:text="@string/tracelocation_organizer_add_event_length_of_stay_description"
+                    app:layout_constraintBottom_toBottomOf="parent"
+                    app:layout_constraintEnd_toEndOf="parent"
+                    app:layout_constraintStart_toStartOf="parent"
+                    app:layout_constraintTop_toBottomOf="@id/value_length_of_stay" />
+
+            </androidx.constraintlayout.widget.ConstraintLayout>
+
+        </LinearLayout>
+    </ScrollView>
+
+    <Button
+        android:id="@+id/button_submit"
+        style="@style/buttonPrimary"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginHorizontal="@dimen/spacing_normal"
+        android:layout_marginBottom="@dimen/spacing_small"
+        android:text="@string/tracelocation_organizer_save"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/Corona-Warn-App/src/main/res/layout/trace_location_organizer_category_fragment.xml b/Corona-Warn-App/src/main/res/layout/trace_location_organizer_category_fragment.xml
index 0aa9f86df..26baca4ab 100644
--- a/Corona-Warn-App/src/main/res/layout/trace_location_organizer_category_fragment.xml
+++ b/Corona-Warn-App/src/main/res/layout/trace_location_organizer_category_fragment.xml
@@ -1,6 +1,7 @@
 <?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:id="@+id/category_root"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
@@ -19,6 +20,7 @@
         android:layout_width="match_parent"
         android:layout_height="0dp"
         android:layout_weight="1"
-        app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
+        app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
+        tools:listitem="@layout/trace_location_organizer_category_item" />
 
 </LinearLayout>
\ No newline at end of file
diff --git a/Corona-Warn-App/src/main/res/navigation/trace_location_organizer_nav_graph.xml b/Corona-Warn-App/src/main/res/navigation/trace_location_organizer_nav_graph.xml
index 35744089a..cb5019612 100644
--- a/Corona-Warn-App/src/main/res/navigation/trace_location_organizer_nav_graph.xml
+++ b/Corona-Warn-App/src/main/res/navigation/trace_location_organizer_nav_graph.xml
@@ -1,6 +1,27 @@
 <?xml version="1.0" encoding="utf-8"?>
 <navigation 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:id="@+id/event_organizer_nav_graph">
-    <!-- TODO add organiser screens -->
+
+    <fragment
+        android:id="@+id/traceLocationOrganizerCategoriesFragment"
+        android:name="de.rki.coronawarnapp.ui.eventregistration.organizer.category.TraceLocationCategoryFragment"
+        android:label="TraceLocationCategoryFragment"
+        tools:layout="@layout/trace_location_organizer_category_fragment">
+
+        <action
+            android:id="@+id/action_traceLocationOrganizerCategoriesFragment_to_traceLocationCreateFragment"
+            app:destination="@id/traceLocationCreateFragment" />
+    </fragment>
+    <fragment
+        android:id="@+id/traceLocationCreateFragment"
+        android:name="de.rki.coronawarnapp.ui.eventregistration.organizer.create.TraceLocationCreateFragment"
+        android:label="trace_location_create_fragment"
+        tools:layout="@layout/trace_location_create_fragment">
+        <argument
+            android:name="category"
+            app:argType="de.rki.coronawarnapp.ui.eventregistration.organizer.category.adapter.category.TraceLocationCategory" />
+    </fragment>
+
 </navigation>
\ No newline at end of file
diff --git a/Corona-Warn-App/src/main/res/values-de/event_registration_strings.xml b/Corona-Warn-App/src/main/res/values-de/event_registration_strings.xml
index 171b63159..026406e04 100644
--- a/Corona-Warn-App/src/main/res/values-de/event_registration_strings.xml
+++ b/Corona-Warn-App/src/main/res/values-de/event_registration_strings.xml
@@ -107,6 +107,22 @@
     <!-- XTXT: Title for other event  -->
     <string name="tracelocation_organizer_category_other_event_title">anderes Event</string>
 
+    <!-- XTXT: Title for event description  -->
+    <string name="tracelocation_organizer_add_event_description">"Bezeichnung"</string>
+    <!-- XTXT: Title for event location  -->
+    <string name="tracelocation_organizer_add_event_location">"Ort"</string>
+    <!-- XTXT: Title for event length of stay  -->
+    <string name="tracelocation_organizer_add_event_length_of_stay">"Typische Aufenthaltsdauer"</string>
+    <!-- XTXT: Description for event length of stay  -->
+    <string name="tracelocation_organizer_add_event_length_of_stay_description">"Geben Sie an wie lange sich Gäste üblicherweise bei Ihnen aufhalten."</string>
+    <!-- XTXT: Title for event start time  -->
+    <string name="tracelocation_organizer_add_event_begin">"Beginn"</string>
+    <!-- XTXT: Title for event end time  -->
+    <string name="tracelocation_organizer_add_event_end">"Ende"</string>
+    <!-- XTXT: Duration suffix -->
+    <string name="tracelocation_organizer_duration_suffix">"Std."</string>
+    <!-- XTXT: Save button text -->
+    <string name="tracelocation_organizer_save">"Speichern"</string>
 
     <!--    Trace Location Homescreen Card-->
     <!-- XHED: Headline for trace location creation card -->
diff --git a/Corona-Warn-App/src/main/res/values/event_registration_strings.xml b/Corona-Warn-App/src/main/res/values/event_registration_strings.xml
index 020131edc..8616928eb 100644
--- a/Corona-Warn-App/src/main/res/values/event_registration_strings.xml
+++ b/Corona-Warn-App/src/main/res/values/event_registration_strings.xml
@@ -108,6 +108,22 @@
     <!-- XTXT: Title for other event  -->
     <string name="tracelocation_organizer_category_other_event_title">anderes Event</string>
 
+    <!-- XTXT: Title for event description  -->
+    <string name="tracelocation_organizer_add_event_description">"Bezeichnung"</string>
+    <!-- XTXT: Title for event location  -->
+    <string name="tracelocation_organizer_add_event_location">"Ort"</string>
+    <!-- XTXT: Title for event length of stay  -->
+    <string name="tracelocation_organizer_add_event_length_of_stay">"Typische Aufenthaltsdauer"</string>
+    <!-- XTXT: Description for event length of stay  -->
+    <string name="tracelocation_organizer_add_event_length_of_stay_description">"Geben Sie an wie lange sich Gäste üblicherweise bei Ihnen aufhalten."</string>
+    <!-- XTXT: Title for event start time  -->
+    <string name="tracelocation_organizer_add_event_begin">"Beginn"</string>
+    <!-- XTXT: Title for event end time  -->
+    <string name="tracelocation_organizer_add_event_end">"Ende"</string>
+    <!-- XTXT: Duration suffix -->
+    <string name="tracelocation_organizer_duration_suffix">"Std."</string>
+    <!-- XTXT: Save button text -->
+    <string name="tracelocation_organizer_save">"Speichern"</string>
 
     <!--    Trace Location Homescreen Card-->
     <!-- XHED: Headline for trace location creation card -->
diff --git a/Corona-Warn-App/src/main/res/values/styles.xml b/Corona-Warn-App/src/main/res/values/styles.xml
index 91e297f4f..7d60a1a66 100644
--- a/Corona-Warn-App/src/main/res/values/styles.xml
+++ b/Corona-Warn-App/src/main/res/values/styles.xml
@@ -6,6 +6,17 @@
         <item name="android:windowBackground">@color/colorBackground</item>
         <item name="alertDialogTheme">@style/DialogAlertTheme</item>
         <item name="android:actionOverflowButtonStyle">@style/CWAToolbar.Overflow</item>
+
+        <item name="materialCalendarTheme">@style/ThemeOverlay.App.DatePicker</item>
+        <item name="materialTimePickerTheme">@style/ThemeOverlay.App.TimePicker</item>
+    </style>
+
+    <style name="ThemeOverlay.App.DatePicker" parent="ThemeOverlay.MaterialComponents.MaterialCalendar">
+        <item name="colorPrimary">@color/colorAccent</item>
+    </style>
+
+    <style name="ThemeOverlay.App.TimePicker" parent="ThemeOverlay.MaterialComponents.TimePicker">
+        <item name="colorPrimary">@color/colorAccent</item>
     </style>
 
     <style name="AppTheme.NoActionBar" parent="AppTheme">
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/eventregistration/organizer/create/TraceLocationCreateViewModelTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/eventregistration/organizer/create/TraceLocationCreateViewModelTest.kt
new file mode 100644
index 000000000..11bd81019
--- /dev/null
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/eventregistration/organizer/create/TraceLocationCreateViewModelTest.kt
@@ -0,0 +1,165 @@
+package de.rki.coronawarnapp.ui.eventregistration.organizer.create
+
+import de.rki.coronawarnapp.R
+import de.rki.coronawarnapp.server.protocols.internal.pt.TraceLocationOuterClass
+import de.rki.coronawarnapp.ui.eventregistration.organizer.category.adapter.category.TraceLocationCategory
+import de.rki.coronawarnapp.ui.eventregistration.organizer.category.adapter.category.TraceLocationUIType
+import io.kotest.matchers.shouldBe
+import org.joda.time.Duration
+import org.joda.time.LocalDateTime
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.extension.ExtendWith
+import org.junit.jupiter.params.ParameterizedTest
+import org.junit.jupiter.params.provider.MethodSource
+import testhelpers.BaseTest
+import testhelpers.extensions.InstantExecutorExtension
+import testhelpers.extensions.observeForTesting
+
+@ExtendWith(InstantExecutorExtension::class)
+internal class TraceLocationCreateViewModelTest : BaseTest() {
+
+    @ParameterizedTest
+    @MethodSource("provideArguments")
+    fun `send should not be enabled for empty form`(category: TraceLocationCategory) {
+        val viewModel = TraceLocationCreateViewModel(category = category)
+        viewModel.uiState.observeForTesting {
+            viewModel.uiState.value?.isSendEnable shouldBe false
+        }
+    }
+
+    @ParameterizedTest
+    @MethodSource("provideArguments")
+    fun `title should be set according to the category item`(category: TraceLocationCategory) {
+        val viewModel = TraceLocationCreateViewModel(category = category)
+        viewModel.uiState.observeForTesting {
+            viewModel.uiState.value?.title shouldBe category.title
+        }
+    }
+
+    @ParameterizedTest
+    @MethodSource("provideArguments")
+    fun `send should be enabled when all data are entered`(category: TraceLocationCategory) {
+        val viewModel = TraceLocationCreateViewModel(category = category)
+
+        viewModel.address = "Address"
+        viewModel.description = "Description"
+        viewModel.begin = LocalDateTime()
+        viewModel.end = LocalDateTime().plusHours(1)
+        viewModel.checkInLength = Duration.standardMinutes(1)
+
+        viewModel.uiState.observeForTesting {
+            viewModel.uiState.value?.isSendEnable shouldBe true
+        }
+    }
+
+    @ParameterizedTest
+    @MethodSource("provideArguments")
+    fun `send should not not be enabled when description it too long`(category: TraceLocationCategory) {
+        val viewModel = TraceLocationCreateViewModel(category = category)
+
+        viewModel.address = "Address"
+        viewModel.description = "A".repeat(101)
+        viewModel.begin = LocalDateTime()
+        viewModel.end = LocalDateTime().plusHours(1)
+        viewModel.checkInLength = Duration.standardMinutes(1)
+
+        viewModel.uiState.observeForTesting {
+            viewModel.uiState.value?.isSendEnable shouldBe false
+        }
+    }
+
+    @ParameterizedTest
+    @MethodSource("provideArguments")
+    fun `send should not not be enabled when address it too long`(category: TraceLocationCategory) {
+        val viewModel = TraceLocationCreateViewModel(category = category)
+
+        viewModel.address = "A".repeat(101)
+        viewModel.description = "Description"
+        viewModel.begin = LocalDateTime()
+        viewModel.end = LocalDateTime().plusHours(1)
+        viewModel.checkInLength = Duration.standardMinutes(1)
+
+        viewModel.uiState.observeForTesting {
+            viewModel.uiState.value?.isSendEnable shouldBe false
+        }
+    }
+
+    @Test
+    fun `begin and end should be visible for EVENT`() {
+        val viewModel = TraceLocationCreateViewModel(category = categoryEvent)
+        viewModel.uiState.observeForTesting {
+            viewModel.uiState.value?.isDateVisible shouldBe true
+        }
+    }
+
+    @Test
+    fun `begin and end should not be visible for LOCATION`() {
+        val viewModel = TraceLocationCreateViewModel(category = categoryLocation)
+        viewModel.uiState.observeForTesting {
+            viewModel.uiState.value?.isDateVisible shouldBe false
+        }
+    }
+
+    @Test
+    fun `send should not be enabled when length of stay is ZERO and category is LOCATION`() {
+        val viewModel = TraceLocationCreateViewModel(category = categoryLocation)
+
+        viewModel.address = "Address"
+        viewModel.description = "Description"
+        viewModel.checkInLength = Duration.ZERO
+
+        viewModel.uiState.observeForTesting {
+            viewModel.uiState.value?.isSendEnable shouldBe false
+        }
+    }
+
+    @Test
+    fun `send should be enabled when length of stay is ZERO and category is EVENT`() {
+        val viewModel = TraceLocationCreateViewModel(category = categoryEvent)
+
+        viewModel.address = "Address"
+        viewModel.description = "Description"
+        viewModel.begin = LocalDateTime()
+        viewModel.end = LocalDateTime().plusHours(1)
+        viewModel.checkInLength = Duration.ZERO
+
+        viewModel.uiState.observeForTesting {
+            viewModel.uiState.value?.isSendEnable shouldBe true
+        }
+    }
+
+    @Test
+    fun `send should not be enabled when end is before begin and category is EVENT`() {
+        val viewModel = TraceLocationCreateViewModel(category = categoryEvent)
+
+        viewModel.address = "Address"
+        viewModel.description = "Description"
+        viewModel.begin = LocalDateTime().plusHours(1)
+        viewModel.end = LocalDateTime()
+        viewModel.checkInLength = Duration.ZERO
+
+        viewModel.uiState.observeForTesting {
+            viewModel.uiState.value?.isSendEnable shouldBe false
+        }
+    }
+
+    companion object {
+        private val categoryLocation = TraceLocationCategory(
+            TraceLocationOuterClass.TraceLocationType.LOCATION_TYPE_PERMANENT_RETAIL,
+            TraceLocationUIType.LOCATION,
+            R.string.tracelocation_organizer_category_retail_title,
+            R.string.tracelocation_organizer_category_retail_subtitle
+        )
+
+        private val categoryEvent = TraceLocationCategory(
+            TraceLocationOuterClass.TraceLocationType.LOCATION_TYPE_TEMPORARY_CULTURAL_EVENT,
+            TraceLocationUIType.EVENT,
+            R.string.tracelocation_organizer_category_cultural_event_title,
+            R.string.tracelocation_organizer_category_cultural_event_subtitle
+        )
+
+        @Suppress("unused")
+        @JvmStatic
+        fun provideArguments() = listOf(categoryEvent, categoryLocation)
+    }
+}
-- 
GitLab