Add disclaimer fragment
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
package gq.kirmanak.mealient.data.disclaimer
|
||||
|
||||
interface DisclaimerStorage {
|
||||
suspend fun isDisclaimerAccepted(): Boolean
|
||||
|
||||
fun acceptDisclaimer()
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package gq.kirmanak.mealient.data.disclaimer
|
||||
|
||||
import android.content.SharedPreferences
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
private const val IS_DISCLAIMER_ACCEPTED_KEY = "IS_DISCLAIMER_ACCEPTED"
|
||||
|
||||
class DisclaimerStorageImpl @Inject constructor(
|
||||
private val sharedPreferences: SharedPreferences
|
||||
) : DisclaimerStorage {
|
||||
|
||||
override suspend fun isDisclaimerAccepted(): Boolean {
|
||||
Timber.v("isDisclaimerAccepted() called")
|
||||
val isAccepted = withContext(Dispatchers.IO) {
|
||||
sharedPreferences.getBoolean(IS_DISCLAIMER_ACCEPTED_KEY, false)
|
||||
}
|
||||
Timber.v("isDisclaimerAccepted() returned: $isAccepted")
|
||||
return isAccepted
|
||||
}
|
||||
|
||||
override fun acceptDisclaimer() {
|
||||
Timber.v("acceptDisclaimer() called")
|
||||
sharedPreferences.edit()
|
||||
.putBoolean(IS_DISCLAIMER_ACCEPTED_KEY, true)
|
||||
.apply()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package gq.kirmanak.mealient.ui.disclaimer
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import gq.kirmanak.mealient.R
|
||||
import gq.kirmanak.mealient.databinding.FragmentDisclaimerBinding
|
||||
import timber.log.Timber
|
||||
|
||||
@AndroidEntryPoint
|
||||
class DisclaimerFragment : Fragment() {
|
||||
private var _binding: FragmentDisclaimerBinding? = null
|
||||
private val binding: FragmentDisclaimerBinding
|
||||
get() = checkNotNull(_binding) { "Binding requested when fragment is off screen" }
|
||||
private val viewModel by viewModels<DisclaimerViewModel>()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
Timber.v("onCreate() called with: savedInstanceState = $savedInstanceState")
|
||||
listenToAcceptStatus()
|
||||
}
|
||||
|
||||
private fun listenToAcceptStatus() {
|
||||
Timber.v("listenToAcceptStatus() called")
|
||||
viewModel.isAccepted.observe(this) {
|
||||
Timber.d("listenToAcceptStatus: new status = $it")
|
||||
if (it) navigateToAuth()
|
||||
}
|
||||
viewModel.checkIsAccepted()
|
||||
}
|
||||
|
||||
private fun navigateToAuth() {
|
||||
Timber.v("navigateToAuth() called")
|
||||
findNavController().navigate(DisclaimerFragmentDirections.actionDisclaimerFragmentToAuthenticationFragment())
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
Timber.v("onCreateView() called with: inflater = $inflater, container = $container, savedInstanceState = $savedInstanceState")
|
||||
_binding = FragmentDisclaimerBinding.inflate(inflater, container, false)
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
Timber.v("onViewCreated() called with: view = $view, savedInstanceState = $savedInstanceState")
|
||||
binding.okay.setOnClickListener {
|
||||
Timber.v("onViewCreated: okay clicked")
|
||||
viewModel.acceptDisclaimer()
|
||||
}
|
||||
viewModel.okayCountDown.observe(viewLifecycleOwner) {
|
||||
Timber.d("onViewCreated: new count $it")
|
||||
binding.okay.text = if (it > 0) {
|
||||
getString(R.string.fragment_disclaimer_button_okay_timer, it)
|
||||
} else {
|
||||
getString(R.string.fragment_disclaimer_button_okay)
|
||||
}
|
||||
binding.okay.isClickable = it == 0
|
||||
}
|
||||
viewModel.startCountDown()
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
Timber.v("onDestroyView() called")
|
||||
_binding = null
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package gq.kirmanak.mealient.ui.disclaimer
|
||||
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import gq.kirmanak.mealient.data.disclaimer.DisclaimerStorage
|
||||
import gq.kirmanak.mealient.data.disclaimer.DisclaimerStorageImpl
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
interface DisclaimerModule {
|
||||
@Binds
|
||||
fun provideDisclaimerStorage(disclaimerStorageImpl: DisclaimerStorageImpl): DisclaimerStorage
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package gq.kirmanak.mealient.ui.disclaimer
|
||||
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import gq.kirmanak.mealient.data.disclaimer.DisclaimerStorage
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.take
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import java.util.concurrent.TimeUnit
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class DisclaimerViewModel @Inject constructor(
|
||||
private val disclaimerStorage: DisclaimerStorage
|
||||
) : ViewModel() {
|
||||
private val _isAccepted = MutableLiveData(false)
|
||||
val isAccepted: LiveData<Boolean> = _isAccepted
|
||||
|
||||
private val _okayCountDown = MutableLiveData(FULL_COUNT_DOWN_SEC)
|
||||
val okayCountDown: LiveData<Int> = _okayCountDown
|
||||
|
||||
fun checkIsAccepted() {
|
||||
Timber.v("checkIsAccepted() called")
|
||||
viewModelScope.launch {
|
||||
_isAccepted.value = disclaimerStorage.isDisclaimerAccepted()
|
||||
}
|
||||
}
|
||||
|
||||
fun acceptDisclaimer() {
|
||||
Timber.v("acceptDisclaimer() called")
|
||||
disclaimerStorage.acceptDisclaimer()
|
||||
_isAccepted.value = true
|
||||
}
|
||||
|
||||
fun startCountDown() {
|
||||
Timber.v("startCountDown() called")
|
||||
tickerFlow(COUNT_DOWN_TICK_PERIOD_SEC.toLong(), TimeUnit.SECONDS)
|
||||
.take(FULL_COUNT_DOWN_SEC - COUNT_DOWN_TICK_PERIOD_SEC + 1)
|
||||
.onEach { _okayCountDown.value = FULL_COUNT_DOWN_SEC - it }
|
||||
.launchIn(viewModelScope)
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends an event every [period] of [timeUnit]. For example, if period = 3 and timeUnit = SECOND
|
||||
* then this will send an event every 3 seconds. Additionally to the event, it sends counter
|
||||
* of how many ticks there were. It doesn't depend on period or timeUnit parameters, it just
|
||||
* counts how many events it sent starting from 1.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
fun tickerFlow(period: Long, timeUnit: TimeUnit) = flow {
|
||||
Timber.v("tickerFlow() called with: period = $period, timeUnit = $timeUnit")
|
||||
val periodMillis = timeUnit.toMillis(period)
|
||||
var counter = 0
|
||||
while (true) {
|
||||
delay(periodMillis)
|
||||
counter++
|
||||
emit(counter)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val FULL_COUNT_DOWN_SEC = 5
|
||||
private const val COUNT_DOWN_TICK_PERIOD_SEC = 1
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user