From 0383f95e0f743d206c926ff9c0e2cf56ad3a326d Mon Sep 17 00:00:00 2001 From: Illia Date: Thu, 30 Jan 2025 07:11:31 +0100 Subject: [PATCH 01/89] Add SMS history screen with retry option for failed SMS forwarding --- .../data/local/database/dao/HistoryDao.kt | 21 +++++ .../smsforwarder/data/mapper/DataMapper.kt | 11 +++ .../open/smsforwarder/data/mapper/Mapper.kt | 2 + .../data/repository/HistoryRepository.kt | 19 ++++ .../open/smsforwarder/domain/model/History.kt | 9 ++ .../open/smsforwarder/navigation/Screens.kt | 3 + .../processor/ForwardingProcessor.kt | 12 +-- .../ui/history/SmsHistoryEffect.kt | 6 ++ .../ui/history/SmsHistoryFragment.kt | 72 +++++++++++++++ .../ui/history/SmsHistoryState.kt | 7 ++ .../ui/history/SmsHistoryViewModel.kt | 61 +++++++++++++ .../ui/history/adapter/SmsHistoryAdapter.kt | 20 +++++ .../history/adapter/SmsHistoryDiffCallback.kt | 14 +++ .../history/adapter/SmsHistoryViewHolder.kt | 49 +++++++++++ .../open/smsforwarder/ui/home/HomeFragment.kt | 1 + .../smsforwarder/ui/home/HomeViewModel.kt | 4 + .../ui/mapper/PresentationMapper.kt | 10 +++ .../open/smsforwarder/ui/model/HistoryUI.kt | 8 ++ app/src/main/res/drawable/ic_refresh.xml | 9 ++ app/src/main/res/drawable/ic_sms_history.xml | 10 +++ app/src/main/res/layout/fragment_home.xml | 13 ++- .../main/res/layout/fragment_sms_history.xml | 71 +++++++++++++++ app/src/main/res/layout/item_history.xml | 87 +++++++++++++++++++ app/src/main/res/values/strings.xml | 7 ++ 24 files changed, 520 insertions(+), 6 deletions(-) create mode 100644 app/src/main/kotlin/org/open/smsforwarder/domain/model/History.kt create mode 100644 app/src/main/kotlin/org/open/smsforwarder/ui/history/SmsHistoryEffect.kt create mode 100644 app/src/main/kotlin/org/open/smsforwarder/ui/history/SmsHistoryFragment.kt create mode 100644 app/src/main/kotlin/org/open/smsforwarder/ui/history/SmsHistoryState.kt create mode 100644 app/src/main/kotlin/org/open/smsforwarder/ui/history/SmsHistoryViewModel.kt create mode 100644 app/src/main/kotlin/org/open/smsforwarder/ui/history/adapter/SmsHistoryAdapter.kt create mode 100644 app/src/main/kotlin/org/open/smsforwarder/ui/history/adapter/SmsHistoryDiffCallback.kt create mode 100644 app/src/main/kotlin/org/open/smsforwarder/ui/history/adapter/SmsHistoryViewHolder.kt create mode 100644 app/src/main/kotlin/org/open/smsforwarder/ui/model/HistoryUI.kt create mode 100644 app/src/main/res/drawable/ic_refresh.xml create mode 100644 app/src/main/res/drawable/ic_sms_history.xml create mode 100644 app/src/main/res/layout/fragment_sms_history.xml create mode 100644 app/src/main/res/layout/item_history.xml diff --git a/app/src/main/kotlin/org/open/smsforwarder/data/local/database/dao/HistoryDao.kt b/app/src/main/kotlin/org/open/smsforwarder/data/local/database/dao/HistoryDao.kt index 2612267..fd42f50 100644 --- a/app/src/main/kotlin/org/open/smsforwarder/data/local/database/dao/HistoryDao.kt +++ b/app/src/main/kotlin/org/open/smsforwarder/data/local/database/dao/HistoryDao.kt @@ -3,9 +3,11 @@ package org.open.smsforwarder.data.local.database.dao import androidx.room.Dao import androidx.room.Query import androidx.room.Upsert +import kotlinx.coroutines.flow.Flow import org.open.smsforwarder.data.local.database.entity.HistoryEntity import org.open.smsforwarder.data.local.database.entity.HistoryEntity.Companion.DATE import org.open.smsforwarder.data.local.database.entity.HistoryEntity.Companion.FORWARDING_HISTORY_TABLE +import org.open.smsforwarder.data.local.database.entity.HistoryEntity.Companion.ID @Dao interface HistoryDao { @@ -20,4 +22,23 @@ interface HistoryDao { """ ) suspend fun getForwardedMessagesCountLast24Hours(): Int + + @Query( + """ + SELECT * + FROM $FORWARDING_HISTORY_TABLE + """ + ) + fun getForwardedMessagesFlow(): Flow> + + + @Query( + """ + SELECT * + FROM $FORWARDING_HISTORY_TABLE + WHERE $ID = :id + """ + ) + suspend fun getForwardedMessageById(id: Long): HistoryEntity? + } diff --git a/app/src/main/kotlin/org/open/smsforwarder/data/mapper/DataMapper.kt b/app/src/main/kotlin/org/open/smsforwarder/data/mapper/DataMapper.kt index 1d0f748..8d7fa59 100644 --- a/app/src/main/kotlin/org/open/smsforwarder/data/mapper/DataMapper.kt +++ b/app/src/main/kotlin/org/open/smsforwarder/data/mapper/DataMapper.kt @@ -1,8 +1,10 @@ package org.open.smsforwarder.data.mapper import org.open.smsforwarder.data.local.database.entity.ForwardingEntity +import org.open.smsforwarder.data.local.database.entity.HistoryEntity import org.open.smsforwarder.data.local.database.entity.RuleEntity import org.open.smsforwarder.domain.model.Forwarding +import org.open.smsforwarder.domain.model.History import org.open.smsforwarder.domain.model.Rule fun ForwardingEntity.toDomain(): Forwarding = @@ -23,6 +25,15 @@ fun RuleEntity.toDomain() = textRule = rule ) +fun HistoryEntity.toDomain() = + History( + id = id, + date = date, + forwardingId = forwardingId, + message = message, + isForwardingSuccessful = isForwardingSuccessful + ) + fun Forwarding.toData(): ForwardingEntity = ForwardingEntity( id = id, diff --git a/app/src/main/kotlin/org/open/smsforwarder/data/mapper/Mapper.kt b/app/src/main/kotlin/org/open/smsforwarder/data/mapper/Mapper.kt index 15b0804..c94a129 100644 --- a/app/src/main/kotlin/org/open/smsforwarder/data/mapper/Mapper.kt +++ b/app/src/main/kotlin/org/open/smsforwarder/data/mapper/Mapper.kt @@ -6,12 +6,14 @@ import javax.inject.Inject class Mapper @Inject constructor() { fun toHistoryEntity( + id: Long, forwardingId: Long, time: Long, message: String, isForwardingSuccessful: Boolean, ): HistoryEntity = HistoryEntity( + id = id, forwardingId = forwardingId, date = time, message = message, diff --git a/app/src/main/kotlin/org/open/smsforwarder/data/repository/HistoryRepository.kt b/app/src/main/kotlin/org/open/smsforwarder/data/repository/HistoryRepository.kt index a83cf9a..1d01af8 100644 --- a/app/src/main/kotlin/org/open/smsforwarder/data/repository/HistoryRepository.kt +++ b/app/src/main/kotlin/org/open/smsforwarder/data/repository/HistoryRepository.kt @@ -1,9 +1,15 @@ package org.open.smsforwarder.data.repository import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.map import kotlinx.coroutines.withContext import org.open.smsforwarder.data.local.database.dao.HistoryDao +import org.open.smsforwarder.data.local.database.entity.HistoryEntity import org.open.smsforwarder.data.mapper.Mapper +import org.open.smsforwarder.data.mapper.toDomain +import org.open.smsforwarder.domain.model.History import javax.inject.Inject class HistoryRepository @Inject constructor( @@ -15,6 +21,7 @@ class HistoryRepository @Inject constructor( } suspend fun insertOrUpdateForwardedSms( + historyEntityId: Long?, forwardingId: Long, message: String, isForwardingSuccessful: Boolean @@ -22,6 +29,7 @@ class HistoryRepository @Inject constructor( withContext(Dispatchers.IO) { historyDao.upsertForwardedSms( mapper.toHistoryEntity( + id = historyEntityId ?: 0L, forwardingId = forwardingId, time = System.currentTimeMillis(), message = message, @@ -30,4 +38,15 @@ class HistoryRepository @Inject constructor( ) } } + + fun getForwardedMessagesFlow(): Flow> = + historyDao.getForwardedMessagesFlow() + .distinctUntilChanged() + .map { historyEntity -> historyEntity.map(HistoryEntity::toDomain) } + + + suspend fun getForwardedMessageById(id: Long): History? = + withContext(Dispatchers.IO) { + historyDao.getForwardedMessageById(id)?.toDomain() + } } diff --git a/app/src/main/kotlin/org/open/smsforwarder/domain/model/History.kt b/app/src/main/kotlin/org/open/smsforwarder/domain/model/History.kt new file mode 100644 index 0000000..f278f1a --- /dev/null +++ b/app/src/main/kotlin/org/open/smsforwarder/domain/model/History.kt @@ -0,0 +1,9 @@ +package org.open.smsforwarder.domain.model + +data class History( + val id: Long = 0, + val date: Long? = 0, + val forwardingId: Long = 0, + val message: String = "", + val isForwardingSuccessful: Boolean = false +) diff --git a/app/src/main/kotlin/org/open/smsforwarder/navigation/Screens.kt b/app/src/main/kotlin/org/open/smsforwarder/navigation/Screens.kt index dc9ae31..44a535f 100644 --- a/app/src/main/kotlin/org/open/smsforwarder/navigation/Screens.kt +++ b/app/src/main/kotlin/org/open/smsforwarder/navigation/Screens.kt @@ -1,6 +1,7 @@ package org.open.smsforwarder.navigation import com.github.terrakok.cicerone.androidx.FragmentScreen +import org.open.smsforwarder.ui.history.SmsHistoryFragment import org.open.smsforwarder.ui.home.HomeFragment import org.open.smsforwarder.ui.onboarding.OnboardingFragment import org.open.smsforwarder.ui.steps.addrecipientdetails.addemaildetails.AddEmailDetailsFragment @@ -24,4 +25,6 @@ object Screens { FragmentScreen { AddForwardingRuleFragment.newInstance(id) } fun onboardingFragment() = FragmentScreen { OnboardingFragment() } + + fun smsHistoryFragment() = FragmentScreen { SmsHistoryFragment() } } diff --git a/app/src/main/kotlin/org/open/smsforwarder/processing/processor/ForwardingProcessor.kt b/app/src/main/kotlin/org/open/smsforwarder/processing/processor/ForwardingProcessor.kt index 61e9d94..2c2430c 100644 --- a/app/src/main/kotlin/org/open/smsforwarder/processing/processor/ForwardingProcessor.kt +++ b/app/src/main/kotlin/org/open/smsforwarder/processing/processor/ForwardingProcessor.kt @@ -19,7 +19,7 @@ class ForwardingProcessor @Inject constructor( private val authRepository: AuthRepository ) { - suspend fun process(messages: Array) { + suspend fun process(messages: Array, historyEntityId: Long? = null) { val rules = rulesRepository.getRules() if (rules.isEmpty()) return @@ -38,10 +38,10 @@ class ForwardingProcessor @Inject constructor( forwarders[recipient.forwardingType] ?.execute(recipient, message) ?.onSuccess { - updateForwardingStatus(recipient, message, "") + updateForwardingStatus(recipient, message, "", historyEntityId) } ?.onFailure { error -> - updateForwardingStatus(recipient, message, error.message.orEmpty()) + updateForwardingStatus(recipient, message, error.message.orEmpty(), historyEntityId) if (error is TokenRevokedException || error is RefreshTokenException) { authRepository.signOut(recipient) } @@ -53,7 +53,8 @@ class ForwardingProcessor @Inject constructor( private suspend fun updateForwardingStatus( forwarding: Forwarding, message: String, - errorText: String + errorText: String, + historyEntityId: Long? ) { forwardingRepository.insertOrUpdateForwarding( forwarding.copy(error = errorText) @@ -61,7 +62,8 @@ class ForwardingProcessor @Inject constructor( historyRepository.insertOrUpdateForwardedSms( forwardingId = forwarding.id, message = message, - isForwardingSuccessful = errorText.isEmpty() + isForwardingSuccessful = errorText.isEmpty(), + historyEntityId = historyEntityId ) } } diff --git a/app/src/main/kotlin/org/open/smsforwarder/ui/history/SmsHistoryEffect.kt b/app/src/main/kotlin/org/open/smsforwarder/ui/history/SmsHistoryEffect.kt new file mode 100644 index 0000000..65823fe --- /dev/null +++ b/app/src/main/kotlin/org/open/smsforwarder/ui/history/SmsHistoryEffect.kt @@ -0,0 +1,6 @@ +package org.open.smsforwarder.ui.history + +sealed interface SmsHistoryEffect { + + data class RetryEffect(val id: Long) : SmsHistoryEffect +} diff --git a/app/src/main/kotlin/org/open/smsforwarder/ui/history/SmsHistoryFragment.kt b/app/src/main/kotlin/org/open/smsforwarder/ui/history/SmsHistoryFragment.kt new file mode 100644 index 0000000..c370fb2 --- /dev/null +++ b/app/src/main/kotlin/org/open/smsforwarder/ui/history/SmsHistoryFragment.kt @@ -0,0 +1,72 @@ +package org.open.smsforwarder.ui.history + +import android.os.Bundle +import android.view.View +import android.widget.Toast +import androidx.core.view.isVisible +import androidx.fragment.app.Fragment +import androidx.fragment.app.viewModels +import by.kirich1409.viewbindingdelegate.viewBinding +import dagger.hilt.android.AndroidEntryPoint +import org.open.smsforwarder.R +import org.open.smsforwarder.databinding.FragmentSmsHistoryBinding +import org.open.smsforwarder.extension.bindClicksTo +import org.open.smsforwarder.extension.observeWithLifecycle +import org.open.smsforwarder.extension.unsafeLazy +import org.open.smsforwarder.ui.history.adapter.SmsHistoryAdapter + +@AndroidEntryPoint +class SmsHistoryFragment : Fragment(R.layout.fragment_sms_history) { + + private val binding by viewBinding(FragmentSmsHistoryBinding::bind) + private val viewModel: SmsHistoryViewModel by viewModels() + private val adapter by unsafeLazy { + SmsHistoryAdapter( + onRetry = viewModel::onRetryClicked + ) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + setUpAdapter() + setUpListeners() + setObservers() + } + + private fun setUpListeners() { + with(binding) { + arrowBackIv bindClicksTo viewModel::onBackClicked + } + } + + private fun setObservers() { + viewModel.apply { + viewState.observeWithLifecycle(viewLifecycleOwner, action = ::renderState) + viewEffect.observeWithLifecycle(viewLifecycleOwner, action = ::handleEffect) + } + } + + private fun renderState(state: SmsHistoryState) { + adapter.submitList(state.historyItems) + with(binding) { + emptyStateText.isVisible = state.historyItems.isEmpty() + historyItems.isVisible = state.historyItems.isNotEmpty() + } + } + + private fun handleEffect(effect: SmsHistoryEffect) { + when (effect) { + is SmsHistoryEffect.RetryEffect -> { + Toast.makeText( + requireContext(), + getString(R.string.sms_history_retrying), + Toast.LENGTH_SHORT + ).show() + } + } + } + + private fun setUpAdapter() { + binding.historyItems.adapter = adapter + } +} diff --git a/app/src/main/kotlin/org/open/smsforwarder/ui/history/SmsHistoryState.kt b/app/src/main/kotlin/org/open/smsforwarder/ui/history/SmsHistoryState.kt new file mode 100644 index 0000000..cd589dd --- /dev/null +++ b/app/src/main/kotlin/org/open/smsforwarder/ui/history/SmsHistoryState.kt @@ -0,0 +1,7 @@ +package org.open.smsforwarder.ui.history + +import org.open.smsforwarder.ui.model.HistoryUI + +data class SmsHistoryState( + val historyItems: List = emptyList() +) diff --git a/app/src/main/kotlin/org/open/smsforwarder/ui/history/SmsHistoryViewModel.kt b/app/src/main/kotlin/org/open/smsforwarder/ui/history/SmsHistoryViewModel.kt new file mode 100644 index 0000000..067a261 --- /dev/null +++ b/app/src/main/kotlin/org/open/smsforwarder/ui/history/SmsHistoryViewModel.kt @@ -0,0 +1,61 @@ +package org.open.smsforwarder.ui.history + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.github.terrakok.cicerone.Router +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.receiveAsFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import org.open.smsforwarder.data.repository.HistoryRepository +import org.open.smsforwarder.extension.asStateFlowWithInitialAction +import org.open.smsforwarder.extension.launchAndCancelPrevious +import org.open.smsforwarder.processing.processor.ForwardingProcessor +import org.open.smsforwarder.ui.mapper.toHistoryUi +import javax.inject.Inject + +@HiltViewModel +class SmsHistoryViewModel @Inject constructor( + private val router: Router, + private val historyRepository: HistoryRepository, + private val forwardingProcessor: ForwardingProcessor +) : ViewModel() { + + private var _viewState = MutableStateFlow(SmsHistoryState()) + val viewState = _viewState.asStateFlowWithInitialAction(viewModelScope) { loadData() } + + private val _viewEffect: Channel = Channel(Channel.BUFFERED) + val viewEffect: Flow = _viewEffect.receiveAsFlow() + + fun onBackClicked() { + router.exit() + } + + fun onRetryClicked(id: Long) { + viewModelScope.launch { + withContext(Dispatchers.IO) { + historyRepository.getForwardedMessageById(id)?.let { historyItem -> + forwardingProcessor.process(arrayOf(historyItem.message), id) + } + } + } + _viewEffect.trySend(SmsHistoryEffect.RetryEffect(id)) + } + + private fun loadData() { + launchAndCancelPrevious { + historyRepository + .getForwardedMessagesFlow() + .collect { result -> + _viewState.update { + it.copy(historyItems = result.map { resultItem -> resultItem.toHistoryUi() }) + } + } + } + } +} diff --git a/app/src/main/kotlin/org/open/smsforwarder/ui/history/adapter/SmsHistoryAdapter.kt b/app/src/main/kotlin/org/open/smsforwarder/ui/history/adapter/SmsHistoryAdapter.kt new file mode 100644 index 0000000..5d4f75b --- /dev/null +++ b/app/src/main/kotlin/org/open/smsforwarder/ui/history/adapter/SmsHistoryAdapter.kt @@ -0,0 +1,20 @@ +package org.open.smsforwarder.ui.history.adapter + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.ListAdapter +import org.open.smsforwarder.databinding.ItemHistoryBinding +import org.open.smsforwarder.ui.model.HistoryUI + +class SmsHistoryAdapter( + private val onRetry: (Long) -> Unit +): ListAdapter(SmsHistoryDiffCallback()) { + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SmsHistoryViewHolder { + val binding = ItemHistoryBinding.inflate(LayoutInflater.from(parent.context), parent, false) + return SmsHistoryViewHolder(binding, onRetry) + } + + override fun onBindViewHolder(holder: SmsHistoryViewHolder, position: Int) { + holder.bind(getItem(position)) + } +} diff --git a/app/src/main/kotlin/org/open/smsforwarder/ui/history/adapter/SmsHistoryDiffCallback.kt b/app/src/main/kotlin/org/open/smsforwarder/ui/history/adapter/SmsHistoryDiffCallback.kt new file mode 100644 index 0000000..6fbe31d --- /dev/null +++ b/app/src/main/kotlin/org/open/smsforwarder/ui/history/adapter/SmsHistoryDiffCallback.kt @@ -0,0 +1,14 @@ +package org.open.smsforwarder.ui.history.adapter + +import androidx.recyclerview.widget.DiffUtil +import org.open.smsforwarder.ui.model.HistoryUI + +class SmsHistoryDiffCallback : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: HistoryUI, newItem: HistoryUI) = + oldItem.id == newItem.id + + override fun areContentsTheSame(oldItem: HistoryUI, newItem: HistoryUI) = + oldItem.isForwardingSuccessful == newItem.isForwardingSuccessful && + oldItem.date == newItem.date && + oldItem.message == newItem.message +} diff --git a/app/src/main/kotlin/org/open/smsforwarder/ui/history/adapter/SmsHistoryViewHolder.kt b/app/src/main/kotlin/org/open/smsforwarder/ui/history/adapter/SmsHistoryViewHolder.kt new file mode 100644 index 0000000..b7d0c40 --- /dev/null +++ b/app/src/main/kotlin/org/open/smsforwarder/ui/history/adapter/SmsHistoryViewHolder.kt @@ -0,0 +1,49 @@ +package org.open.smsforwarder.ui.history.adapter + +import android.view.View +import androidx.recyclerview.widget.RecyclerView.ViewHolder +import org.open.smsforwarder.R +import org.open.smsforwarder.databinding.ItemHistoryBinding +import org.open.smsforwarder.ui.model.HistoryUI +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Locale + +class SmsHistoryViewHolder( + private val binding: ItemHistoryBinding, + private val onRetry: (Long) -> Unit +): ViewHolder(binding.root) { + + fun bind(model: HistoryUI) = with(binding) { + val context = itemView.context + date.text = convertLongToTime(model.date) + message.text = model.message + + retry.setOnClickListener { + onRetry(model.id) + } + + if (model.isForwardingSuccessful == true) { + status.text = context.getString(R.string.history_item_status_success) + status.setTextColor(context.getColor(R.color.green)) + retry.visibility = View.GONE + } else { + status.text = context.getString(R.string.history_item_status_fail) + status.setTextColor(context.getColor(R.color.red)) + retry.visibility = View.VISIBLE + } + } + + private fun convertLongToTime(time: Long?): String? = + runCatching { + time?.let { timestamp -> + DATE_FORMAT.get()?.format(Date(timestamp)) + } + }.getOrDefault(null) + + private companion object { + private val DATE_FORMAT = ThreadLocal.withInitial { + SimpleDateFormat("HH:mm dd.MM.yyyy", Locale.getDefault()) + } + } +} diff --git a/app/src/main/kotlin/org/open/smsforwarder/ui/home/HomeFragment.kt b/app/src/main/kotlin/org/open/smsforwarder/ui/home/HomeFragment.kt index c91cfcb..bc70653 100644 --- a/app/src/main/kotlin/org/open/smsforwarder/ui/home/HomeFragment.kt +++ b/app/src/main/kotlin/org/open/smsforwarder/ui/home/HomeFragment.kt @@ -94,6 +94,7 @@ class HomeFragment : Fragment(R.layout.fragment_home), DeleteDialogListener { private fun setListeners() { binding.powerManagementWarning bindClicksTo viewModel::onBatteryOptimizationWarningClicked binding.startNewForwardingBtn bindClicksTo viewModel::onNewForwardingClicked + binding.smsHistory bindClicksTo viewModel::onSmsHistoryClicked } private fun setObservers() { diff --git a/app/src/main/kotlin/org/open/smsforwarder/ui/home/HomeViewModel.kt b/app/src/main/kotlin/org/open/smsforwarder/ui/home/HomeViewModel.kt index 4b91d2a..522b2d6 100644 --- a/app/src/main/kotlin/org/open/smsforwarder/ui/home/HomeViewModel.kt +++ b/app/src/main/kotlin/org/open/smsforwarder/ui/home/HomeViewModel.kt @@ -60,6 +60,10 @@ class HomeViewModel @Inject constructor( } } + fun onSmsHistoryClicked() { + router.navigateTo(Screens.smsHistoryFragment()) + } + fun onItemEditClicked(id: Long) { router.navigateTo(Screens.chooseForwardingMethodFragment(id)) } diff --git a/app/src/main/kotlin/org/open/smsforwarder/ui/mapper/PresentationMapper.kt b/app/src/main/kotlin/org/open/smsforwarder/ui/mapper/PresentationMapper.kt index 6874c97..013c8bd 100644 --- a/app/src/main/kotlin/org/open/smsforwarder/ui/mapper/PresentationMapper.kt +++ b/app/src/main/kotlin/org/open/smsforwarder/ui/mapper/PresentationMapper.kt @@ -1,9 +1,11 @@ package org.open.smsforwarder.ui.mapper import org.open.smsforwarder.domain.model.Forwarding +import org.open.smsforwarder.domain.model.History import org.open.smsforwarder.domain.model.Rule import org.open.smsforwarder.ui.home.HomeState import org.open.smsforwarder.ui.model.ForwardingUI +import org.open.smsforwarder.ui.model.HistoryUI import org.open.smsforwarder.ui.steps.addrecipientdetails.addemaildetails.AddEmailDetailsState import org.open.smsforwarder.ui.steps.addrecipientdetails.addphonedetails.AddPhoneDetailsState @@ -68,3 +70,11 @@ fun AddEmailDetailsState.toDomain() = senderEmail = senderEmail, recipientEmail = recipientEmail ) + +fun History.toHistoryUi() = + HistoryUI( + id = id, + date = date, + message = message, + isForwardingSuccessful = isForwardingSuccessful + ) diff --git a/app/src/main/kotlin/org/open/smsforwarder/ui/model/HistoryUI.kt b/app/src/main/kotlin/org/open/smsforwarder/ui/model/HistoryUI.kt new file mode 100644 index 0000000..e6b75bc --- /dev/null +++ b/app/src/main/kotlin/org/open/smsforwarder/ui/model/HistoryUI.kt @@ -0,0 +1,8 @@ +package org.open.smsforwarder.ui.model + +data class HistoryUI ( + val id: Long = 0, + val date: Long? = 0L, + val message: String? = "", + val isForwardingSuccessful: Boolean? = true +) diff --git a/app/src/main/res/drawable/ic_refresh.xml b/app/src/main/res/drawable/ic_refresh.xml new file mode 100644 index 0000000..2d9b89b --- /dev/null +++ b/app/src/main/res/drawable/ic_refresh.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_sms_history.xml b/app/src/main/res/drawable/ic_sms_history.xml new file mode 100644 index 0000000..fddda32 --- /dev/null +++ b/app/src/main/res/drawable/ic_sms_history.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/layout/fragment_home.xml b/app/src/main/res/layout/fragment_home.xml index bf4eefe..444223d 100644 --- a/app/src/main/res/layout/fragment_home.xml +++ b/app/src/main/res/layout/fragment_home.xml @@ -40,7 +40,18 @@ android:src="@drawable/ic_warning" android:visibility="visible" app:layout_constraintBottom_toBottomOf="@id/title_label" - app:layout_constraintEnd_toEndOf="@id/end_margin" + app:layout_constraintEnd_toStartOf="@id/sms_history" + tools:ignore="ContentDescription" /> + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/item_history.xml b/app/src/main/res/layout/item_history.xml new file mode 100644 index 0000000..6d0747a --- /dev/null +++ b/app/src/main/res/layout/item_history.xml @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c3c879d..e27b373 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -99,4 +99,11 @@ Forwarding is disabled.\nGeneric Failure Error Forwarded messages %1$s messages were forwarded in the last 24 hours + Date: + Status + Message: + Success + Fail + Sms history + Retrying… From bb28eee12089435f93afa4a4c9eb7a5f714ee6b2 Mon Sep 17 00:00:00 2001 From: Illia Date: Wed, 5 Feb 2025 11:55:39 +0100 Subject: [PATCH 02/89] Rename files, classes, methods; refactor code; update retry forwarding flow --- .../data/local/database/dao/HistoryDao.kt | 20 ++----- .../smsforwarder/data/mapper/DataMapper.kt | 9 +++ .../open/smsforwarder/data/mapper/Mapper.kt | 2 - .../data/repository/HistoryRepository.kt | 20 ++++--- .../open/smsforwarder/navigation/Screens.kt | 4 +- .../processor/ForwardingProcessor.kt | 59 +++++++++++++++---- .../ui/history/ForwardingHistoryEffect.kt | 6 ++ ...agment.kt => ForwardingHistoryFragment.kt} | 22 +++---- ...toryState.kt => ForwardingHistoryState.kt} | 2 +- ...Model.kt => ForwardingHistoryViewModel.kt} | 20 +++---- .../ui/history/SmsHistoryEffect.kt | 6 -- .../ui/history/adapter/SmsHistoryAdapter.kt | 1 + .../history/adapter/SmsHistoryDiffCallback.kt | 1 + .../history/adapter/SmsHistoryViewHolder.kt | 4 +- .../open/smsforwarder/ui/home/HomeFragment.kt | 2 +- .../smsforwarder/ui/home/HomeViewModel.kt | 2 +- ..._history.xml => ic_forwarding_history.xml} | 0 ...ry.xml => fragment_forwarding_history.xml} | 2 +- app/src/main/res/layout/fragment_home.xml | 8 +-- app/src/main/res/layout/item_history.xml | 8 +-- app/src/main/res/values/strings.xml | 14 ++--- 21 files changed, 123 insertions(+), 89 deletions(-) create mode 100644 app/src/main/kotlin/org/open/smsforwarder/ui/history/ForwardingHistoryEffect.kt rename app/src/main/kotlin/org/open/smsforwarder/ui/history/{SmsHistoryFragment.kt => ForwardingHistoryFragment.kt} (71%) rename app/src/main/kotlin/org/open/smsforwarder/ui/history/{SmsHistoryState.kt => ForwardingHistoryState.kt} (80%) rename app/src/main/kotlin/org/open/smsforwarder/ui/history/{SmsHistoryViewModel.kt => ForwardingHistoryViewModel.kt} (69%) delete mode 100644 app/src/main/kotlin/org/open/smsforwarder/ui/history/SmsHistoryEffect.kt rename app/src/main/res/drawable/{ic_sms_history.xml => ic_forwarding_history.xml} (100%) rename app/src/main/res/layout/{fragment_sms_history.xml => fragment_forwarding_history.xml} (98%) diff --git a/app/src/main/kotlin/org/open/smsforwarder/data/local/database/dao/HistoryDao.kt b/app/src/main/kotlin/org/open/smsforwarder/data/local/database/dao/HistoryDao.kt index fd42f50..7e39014 100644 --- a/app/src/main/kotlin/org/open/smsforwarder/data/local/database/dao/HistoryDao.kt +++ b/app/src/main/kotlin/org/open/smsforwarder/data/local/database/dao/HistoryDao.kt @@ -23,22 +23,10 @@ interface HistoryDao { ) suspend fun getForwardedMessagesCountLast24Hours(): Int - @Query( - """ - SELECT * - FROM $FORWARDING_HISTORY_TABLE - """ - ) - fun getForwardedMessagesFlow(): Flow> - + @Query("SELECT * FROM $FORWARDING_HISTORY_TABLE") + fun getForwardingHistoryFlow(): Flow> - @Query( - """ - SELECT * - FROM $FORWARDING_HISTORY_TABLE - WHERE $ID = :id - """ - ) - suspend fun getForwardedMessageById(id: Long): HistoryEntity? + @Query(" SELECT * FROM $FORWARDING_HISTORY_TABLE WHERE $ID = :id") + suspend fun getForwardingHistoryById(id: Long): HistoryEntity? } diff --git a/app/src/main/kotlin/org/open/smsforwarder/data/mapper/DataMapper.kt b/app/src/main/kotlin/org/open/smsforwarder/data/mapper/DataMapper.kt index 8d7fa59..6adc0b4 100644 --- a/app/src/main/kotlin/org/open/smsforwarder/data/mapper/DataMapper.kt +++ b/app/src/main/kotlin/org/open/smsforwarder/data/mapper/DataMapper.kt @@ -51,3 +51,12 @@ fun Rule.toData(): RuleEntity = forwardingId = forwardingId, rule = textRule ) + +fun History.toData(): HistoryEntity = + HistoryEntity( + id = id, + forwardingId = forwardingId, + date = date, + message = message, + isForwardingSuccessful = isForwardingSuccessful + ) diff --git a/app/src/main/kotlin/org/open/smsforwarder/data/mapper/Mapper.kt b/app/src/main/kotlin/org/open/smsforwarder/data/mapper/Mapper.kt index c94a129..15b0804 100644 --- a/app/src/main/kotlin/org/open/smsforwarder/data/mapper/Mapper.kt +++ b/app/src/main/kotlin/org/open/smsforwarder/data/mapper/Mapper.kt @@ -6,14 +6,12 @@ import javax.inject.Inject class Mapper @Inject constructor() { fun toHistoryEntity( - id: Long, forwardingId: Long, time: Long, message: String, isForwardingSuccessful: Boolean, ): HistoryEntity = HistoryEntity( - id = id, forwardingId = forwardingId, date = time, message = message, diff --git a/app/src/main/kotlin/org/open/smsforwarder/data/repository/HistoryRepository.kt b/app/src/main/kotlin/org/open/smsforwarder/data/repository/HistoryRepository.kt index 1d01af8..29917c9 100644 --- a/app/src/main/kotlin/org/open/smsforwarder/data/repository/HistoryRepository.kt +++ b/app/src/main/kotlin/org/open/smsforwarder/data/repository/HistoryRepository.kt @@ -8,6 +8,7 @@ import kotlinx.coroutines.withContext import org.open.smsforwarder.data.local.database.dao.HistoryDao import org.open.smsforwarder.data.local.database.entity.HistoryEntity import org.open.smsforwarder.data.mapper.Mapper +import org.open.smsforwarder.data.mapper.toData import org.open.smsforwarder.data.mapper.toDomain import org.open.smsforwarder.domain.model.History import javax.inject.Inject @@ -20,8 +21,7 @@ class HistoryRepository @Inject constructor( historyDao.getForwardedMessagesCountLast24Hours() } - suspend fun insertOrUpdateForwardedSms( - historyEntityId: Long?, + suspend fun insertForwardedSms( forwardingId: Long, message: String, isForwardingSuccessful: Boolean @@ -29,7 +29,6 @@ class HistoryRepository @Inject constructor( withContext(Dispatchers.IO) { historyDao.upsertForwardedSms( mapper.toHistoryEntity( - id = historyEntityId ?: 0L, forwardingId = forwardingId, time = System.currentTimeMillis(), message = message, @@ -39,14 +38,21 @@ class HistoryRepository @Inject constructor( } } - fun getForwardedMessagesFlow(): Flow> = - historyDao.getForwardedMessagesFlow() + suspend fun updateForwardedSms(history: History) { + withContext(Dispatchers.IO) { + historyDao.upsertForwardedSms(history.toData()) + } + } + + fun getForwardingHistoryFlow(): Flow> = + historyDao + .getForwardingHistoryFlow() .distinctUntilChanged() .map { historyEntity -> historyEntity.map(HistoryEntity::toDomain) } - suspend fun getForwardedMessageById(id: Long): History? = + suspend fun getForwardingHistoryById(id: Long): History? = withContext(Dispatchers.IO) { - historyDao.getForwardedMessageById(id)?.toDomain() + historyDao.getForwardingHistoryById(id)?.toDomain() } } diff --git a/app/src/main/kotlin/org/open/smsforwarder/navigation/Screens.kt b/app/src/main/kotlin/org/open/smsforwarder/navigation/Screens.kt index 44a535f..c127bbd 100644 --- a/app/src/main/kotlin/org/open/smsforwarder/navigation/Screens.kt +++ b/app/src/main/kotlin/org/open/smsforwarder/navigation/Screens.kt @@ -1,7 +1,7 @@ package org.open.smsforwarder.navigation import com.github.terrakok.cicerone.androidx.FragmentScreen -import org.open.smsforwarder.ui.history.SmsHistoryFragment +import org.open.smsforwarder.ui.history.ForwardingHistoryFragment import org.open.smsforwarder.ui.home.HomeFragment import org.open.smsforwarder.ui.onboarding.OnboardingFragment import org.open.smsforwarder.ui.steps.addrecipientdetails.addemaildetails.AddEmailDetailsFragment @@ -26,5 +26,5 @@ object Screens { fun onboardingFragment() = FragmentScreen { OnboardingFragment() } - fun smsHistoryFragment() = FragmentScreen { SmsHistoryFragment() } + fun forwardingHistoryFragment() = FragmentScreen { ForwardingHistoryFragment() } } diff --git a/app/src/main/kotlin/org/open/smsforwarder/processing/processor/ForwardingProcessor.kt b/app/src/main/kotlin/org/open/smsforwarder/processing/processor/ForwardingProcessor.kt index 2c2430c..06ccf3f 100644 --- a/app/src/main/kotlin/org/open/smsforwarder/processing/processor/ForwardingProcessor.kt +++ b/app/src/main/kotlin/org/open/smsforwarder/processing/processor/ForwardingProcessor.kt @@ -1,5 +1,7 @@ package org.open.smsforwarder.processing.processor +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext import org.open.smsforwarder.data.remote.interceptor.RefreshTokenException import org.open.smsforwarder.data.remote.interceptor.TokenRevokedException import org.open.smsforwarder.data.repository.AuthRepository @@ -8,6 +10,7 @@ import org.open.smsforwarder.data.repository.HistoryRepository import org.open.smsforwarder.data.repository.RulesRepository import org.open.smsforwarder.domain.model.Forwarding import org.open.smsforwarder.domain.model.ForwardingType +import org.open.smsforwarder.domain.model.History import org.open.smsforwarder.processing.forwarder.Forwarder import javax.inject.Inject @@ -19,7 +22,7 @@ class ForwardingProcessor @Inject constructor( private val authRepository: AuthRepository ) { - suspend fun process(messages: Array, historyEntityId: Long? = null) { + suspend fun process(messages: Array) { val rules = rulesRepository.getRules() if (rules.isEmpty()) return @@ -38,32 +41,68 @@ class ForwardingProcessor @Inject constructor( forwarders[recipient.forwardingType] ?.execute(recipient, message) ?.onSuccess { - updateForwardingStatus(recipient, message, "", historyEntityId) + updateForwardingAndCreateHistory(recipient, message, "") } ?.onFailure { error -> - updateForwardingStatus(recipient, message, error.message.orEmpty(), historyEntityId) - if (error is TokenRevokedException || error is RefreshTokenException) { - authRepository.signOut(recipient) - } + updateForwardingAndCreateHistory(recipient, message, error.message.orEmpty()) + handleTokenErrors(error, recipient) } } } } - private suspend fun updateForwardingStatus( + suspend fun retryForwarding(historyItem: History) { + withContext(Dispatchers.IO) { + historyItem.let { history -> + forwardingRepository.getForwardingById(history.forwardingId)?.let { recipient -> + forwarders[recipient.forwardingType] + ?.execute(recipient, history.message) + ?.onSuccess { + updateForwardingAndHistory(recipient, history, "") + } + ?.onFailure { error -> + updateForwardingAndHistory(recipient, history, error.message.orEmpty()) + handleTokenErrors(error, recipient) + } + } + } + } + } + + private suspend fun handleTokenErrors(error: Throwable, recipient: Forwarding) { + if (error is TokenRevokedException || error is RefreshTokenException) { + authRepository.signOut(recipient) + } + } + + private suspend fun updateForwardingAndHistory( + forwarding: Forwarding, + history: History, + errorText: String + ) { + forwardingRepository.insertOrUpdateForwarding( + forwarding.copy(error = errorText) + ) + historyRepository.updateForwardedSms( + history.copy( + isForwardingSuccessful = errorText.isEmpty(), + date = System.currentTimeMillis() + ) + ) + } + + private suspend fun updateForwardingAndCreateHistory( forwarding: Forwarding, message: String, errorText: String, - historyEntityId: Long? ) { forwardingRepository.insertOrUpdateForwarding( forwarding.copy(error = errorText) ) - historyRepository.insertOrUpdateForwardedSms( + historyRepository.insertForwardedSms( forwardingId = forwarding.id, message = message, isForwardingSuccessful = errorText.isEmpty(), - historyEntityId = historyEntityId ) } } diff --git a/app/src/main/kotlin/org/open/smsforwarder/ui/history/ForwardingHistoryEffect.kt b/app/src/main/kotlin/org/open/smsforwarder/ui/history/ForwardingHistoryEffect.kt new file mode 100644 index 0000000..0044c75 --- /dev/null +++ b/app/src/main/kotlin/org/open/smsforwarder/ui/history/ForwardingHistoryEffect.kt @@ -0,0 +1,6 @@ +package org.open.smsforwarder.ui.history + +sealed interface ForwardingHistoryEffect { + + data class RetryEffect(val id: Long) : ForwardingHistoryEffect +} diff --git a/app/src/main/kotlin/org/open/smsforwarder/ui/history/SmsHistoryFragment.kt b/app/src/main/kotlin/org/open/smsforwarder/ui/history/ForwardingHistoryFragment.kt similarity index 71% rename from app/src/main/kotlin/org/open/smsforwarder/ui/history/SmsHistoryFragment.kt rename to app/src/main/kotlin/org/open/smsforwarder/ui/history/ForwardingHistoryFragment.kt index c370fb2..eb43b6c 100644 --- a/app/src/main/kotlin/org/open/smsforwarder/ui/history/SmsHistoryFragment.kt +++ b/app/src/main/kotlin/org/open/smsforwarder/ui/history/ForwardingHistoryFragment.kt @@ -2,24 +2,24 @@ package org.open.smsforwarder.ui.history import android.os.Bundle import android.view.View -import android.widget.Toast import androidx.core.view.isVisible import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels import by.kirich1409.viewbindingdelegate.viewBinding import dagger.hilt.android.AndroidEntryPoint import org.open.smsforwarder.R -import org.open.smsforwarder.databinding.FragmentSmsHistoryBinding +import org.open.smsforwarder.databinding.FragmentForwardingHistoryBinding import org.open.smsforwarder.extension.bindClicksTo import org.open.smsforwarder.extension.observeWithLifecycle +import org.open.smsforwarder.extension.showToast import org.open.smsforwarder.extension.unsafeLazy import org.open.smsforwarder.ui.history.adapter.SmsHistoryAdapter @AndroidEntryPoint -class SmsHistoryFragment : Fragment(R.layout.fragment_sms_history) { +class ForwardingHistoryFragment : Fragment(R.layout.fragment_forwarding_history) { - private val binding by viewBinding(FragmentSmsHistoryBinding::bind) - private val viewModel: SmsHistoryViewModel by viewModels() + private val binding by viewBinding(FragmentForwardingHistoryBinding::bind) + private val viewModel: ForwardingHistoryViewModel by viewModels() private val adapter by unsafeLazy { SmsHistoryAdapter( onRetry = viewModel::onRetryClicked @@ -46,7 +46,7 @@ class SmsHistoryFragment : Fragment(R.layout.fragment_sms_history) { } } - private fun renderState(state: SmsHistoryState) { + private fun renderState(state: ForwardingHistoryState) { adapter.submitList(state.historyItems) with(binding) { emptyStateText.isVisible = state.historyItems.isEmpty() @@ -54,14 +54,10 @@ class SmsHistoryFragment : Fragment(R.layout.fragment_sms_history) { } } - private fun handleEffect(effect: SmsHistoryEffect) { + private fun handleEffect(effect: ForwardingHistoryEffect) { when (effect) { - is SmsHistoryEffect.RetryEffect -> { - Toast.makeText( - requireContext(), - getString(R.string.sms_history_retrying), - Toast.LENGTH_SHORT - ).show() + is ForwardingHistoryEffect.RetryEffect -> { + showToast(R.string.forwarding_history_retrying) } } } diff --git a/app/src/main/kotlin/org/open/smsforwarder/ui/history/SmsHistoryState.kt b/app/src/main/kotlin/org/open/smsforwarder/ui/history/ForwardingHistoryState.kt similarity index 80% rename from app/src/main/kotlin/org/open/smsforwarder/ui/history/SmsHistoryState.kt rename to app/src/main/kotlin/org/open/smsforwarder/ui/history/ForwardingHistoryState.kt index cd589dd..3347b5c 100644 --- a/app/src/main/kotlin/org/open/smsforwarder/ui/history/SmsHistoryState.kt +++ b/app/src/main/kotlin/org/open/smsforwarder/ui/history/ForwardingHistoryState.kt @@ -2,6 +2,6 @@ package org.open.smsforwarder.ui.history import org.open.smsforwarder.ui.model.HistoryUI -data class SmsHistoryState( +data class ForwardingHistoryState( val historyItems: List = emptyList() ) diff --git a/app/src/main/kotlin/org/open/smsforwarder/ui/history/SmsHistoryViewModel.kt b/app/src/main/kotlin/org/open/smsforwarder/ui/history/ForwardingHistoryViewModel.kt similarity index 69% rename from app/src/main/kotlin/org/open/smsforwarder/ui/history/SmsHistoryViewModel.kt rename to app/src/main/kotlin/org/open/smsforwarder/ui/history/ForwardingHistoryViewModel.kt index 067a261..11ad0f0 100644 --- a/app/src/main/kotlin/org/open/smsforwarder/ui/history/SmsHistoryViewModel.kt +++ b/app/src/main/kotlin/org/open/smsforwarder/ui/history/ForwardingHistoryViewModel.kt @@ -4,14 +4,12 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.github.terrakok.cicerone.Router import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext import org.open.smsforwarder.data.repository.HistoryRepository import org.open.smsforwarder.extension.asStateFlowWithInitialAction import org.open.smsforwarder.extension.launchAndCancelPrevious @@ -20,17 +18,17 @@ import org.open.smsforwarder.ui.mapper.toHistoryUi import javax.inject.Inject @HiltViewModel -class SmsHistoryViewModel @Inject constructor( +class ForwardingHistoryViewModel @Inject constructor( private val router: Router, private val historyRepository: HistoryRepository, private val forwardingProcessor: ForwardingProcessor ) : ViewModel() { - private var _viewState = MutableStateFlow(SmsHistoryState()) + private var _viewState = MutableStateFlow(ForwardingHistoryState()) val viewState = _viewState.asStateFlowWithInitialAction(viewModelScope) { loadData() } - private val _viewEffect: Channel = Channel(Channel.BUFFERED) - val viewEffect: Flow = _viewEffect.receiveAsFlow() + private val _viewEffect: Channel = Channel(Channel.BUFFERED) + val viewEffect: Flow = _viewEffect.receiveAsFlow() fun onBackClicked() { router.exit() @@ -38,19 +36,17 @@ class SmsHistoryViewModel @Inject constructor( fun onRetryClicked(id: Long) { viewModelScope.launch { - withContext(Dispatchers.IO) { - historyRepository.getForwardedMessageById(id)?.let { historyItem -> - forwardingProcessor.process(arrayOf(historyItem.message), id) - } + historyRepository.getForwardingHistoryById(id)?.let { historyItem -> + forwardingProcessor.retryForwarding(historyItem) } } - _viewEffect.trySend(SmsHistoryEffect.RetryEffect(id)) + _viewEffect.trySend(ForwardingHistoryEffect.RetryEffect(id)) } private fun loadData() { launchAndCancelPrevious { historyRepository - .getForwardedMessagesFlow() + .getForwardingHistoryFlow() .collect { result -> _viewState.update { it.copy(historyItems = result.map { resultItem -> resultItem.toHistoryUi() }) diff --git a/app/src/main/kotlin/org/open/smsforwarder/ui/history/SmsHistoryEffect.kt b/app/src/main/kotlin/org/open/smsforwarder/ui/history/SmsHistoryEffect.kt deleted file mode 100644 index 65823fe..0000000 --- a/app/src/main/kotlin/org/open/smsforwarder/ui/history/SmsHistoryEffect.kt +++ /dev/null @@ -1,6 +0,0 @@ -package org.open.smsforwarder.ui.history - -sealed interface SmsHistoryEffect { - - data class RetryEffect(val id: Long) : SmsHistoryEffect -} diff --git a/app/src/main/kotlin/org/open/smsforwarder/ui/history/adapter/SmsHistoryAdapter.kt b/app/src/main/kotlin/org/open/smsforwarder/ui/history/adapter/SmsHistoryAdapter.kt index 5d4f75b..89d98f8 100644 --- a/app/src/main/kotlin/org/open/smsforwarder/ui/history/adapter/SmsHistoryAdapter.kt +++ b/app/src/main/kotlin/org/open/smsforwarder/ui/history/adapter/SmsHistoryAdapter.kt @@ -9,6 +9,7 @@ import org.open.smsforwarder.ui.model.HistoryUI class SmsHistoryAdapter( private val onRetry: (Long) -> Unit ): ListAdapter(SmsHistoryDiffCallback()) { + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SmsHistoryViewHolder { val binding = ItemHistoryBinding.inflate(LayoutInflater.from(parent.context), parent, false) return SmsHistoryViewHolder(binding, onRetry) diff --git a/app/src/main/kotlin/org/open/smsforwarder/ui/history/adapter/SmsHistoryDiffCallback.kt b/app/src/main/kotlin/org/open/smsforwarder/ui/history/adapter/SmsHistoryDiffCallback.kt index 6fbe31d..1aa2578 100644 --- a/app/src/main/kotlin/org/open/smsforwarder/ui/history/adapter/SmsHistoryDiffCallback.kt +++ b/app/src/main/kotlin/org/open/smsforwarder/ui/history/adapter/SmsHistoryDiffCallback.kt @@ -4,6 +4,7 @@ import androidx.recyclerview.widget.DiffUtil import org.open.smsforwarder.ui.model.HistoryUI class SmsHistoryDiffCallback : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: HistoryUI, newItem: HistoryUI) = oldItem.id == newItem.id diff --git a/app/src/main/kotlin/org/open/smsforwarder/ui/history/adapter/SmsHistoryViewHolder.kt b/app/src/main/kotlin/org/open/smsforwarder/ui/history/adapter/SmsHistoryViewHolder.kt index b7d0c40..e3ac4fb 100644 --- a/app/src/main/kotlin/org/open/smsforwarder/ui/history/adapter/SmsHistoryViewHolder.kt +++ b/app/src/main/kotlin/org/open/smsforwarder/ui/history/adapter/SmsHistoryViewHolder.kt @@ -24,11 +24,11 @@ class SmsHistoryViewHolder( } if (model.isForwardingSuccessful == true) { - status.text = context.getString(R.string.history_item_status_success) + status.text = context.getString(R.string.forwarding_history_item_status_success) status.setTextColor(context.getColor(R.color.green)) retry.visibility = View.GONE } else { - status.text = context.getString(R.string.history_item_status_fail) + status.text = context.getString(R.string.forwarding_history_item_status_fail) status.setTextColor(context.getColor(R.color.red)) retry.visibility = View.VISIBLE } diff --git a/app/src/main/kotlin/org/open/smsforwarder/ui/home/HomeFragment.kt b/app/src/main/kotlin/org/open/smsforwarder/ui/home/HomeFragment.kt index bc70653..a1340bb 100644 --- a/app/src/main/kotlin/org/open/smsforwarder/ui/home/HomeFragment.kt +++ b/app/src/main/kotlin/org/open/smsforwarder/ui/home/HomeFragment.kt @@ -94,7 +94,7 @@ class HomeFragment : Fragment(R.layout.fragment_home), DeleteDialogListener { private fun setListeners() { binding.powerManagementWarning bindClicksTo viewModel::onBatteryOptimizationWarningClicked binding.startNewForwardingBtn bindClicksTo viewModel::onNewForwardingClicked - binding.smsHistory bindClicksTo viewModel::onSmsHistoryClicked + binding.forwardingHistory bindClicksTo viewModel::onSmsHistoryClicked } private fun setObservers() { diff --git a/app/src/main/kotlin/org/open/smsforwarder/ui/home/HomeViewModel.kt b/app/src/main/kotlin/org/open/smsforwarder/ui/home/HomeViewModel.kt index 522b2d6..69c168f 100644 --- a/app/src/main/kotlin/org/open/smsforwarder/ui/home/HomeViewModel.kt +++ b/app/src/main/kotlin/org/open/smsforwarder/ui/home/HomeViewModel.kt @@ -61,7 +61,7 @@ class HomeViewModel @Inject constructor( } fun onSmsHistoryClicked() { - router.navigateTo(Screens.smsHistoryFragment()) + router.navigateTo(Screens.forwardingHistoryFragment()) } fun onItemEditClicked(id: Long) { diff --git a/app/src/main/res/drawable/ic_sms_history.xml b/app/src/main/res/drawable/ic_forwarding_history.xml similarity index 100% rename from app/src/main/res/drawable/ic_sms_history.xml rename to app/src/main/res/drawable/ic_forwarding_history.xml diff --git a/app/src/main/res/layout/fragment_sms_history.xml b/app/src/main/res/layout/fragment_forwarding_history.xml similarity index 98% rename from app/src/main/res/layout/fragment_sms_history.xml rename to app/src/main/res/layout/fragment_forwarding_history.xml index 01437e4..5613cde 100644 --- a/app/src/main/res/layout/fragment_sms_history.xml +++ b/app/src/main/res/layout/fragment_forwarding_history.xml @@ -35,7 +35,7 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginTop="16dp" - android:text="@string/sms_history_title" + android:text="@string/forwarding_history_title" android:textAppearance="@style/SmsTextAppearance.Bold.16" android:textAlignment="center" app:layout_constraintStart_toEndOf="@id/arrow_back_iv" diff --git a/app/src/main/res/layout/fragment_home.xml b/app/src/main/res/layout/fragment_home.xml index 444223d..213adb8 100644 --- a/app/src/main/res/layout/fragment_home.xml +++ b/app/src/main/res/layout/fragment_home.xml @@ -40,18 +40,18 @@ android:src="@drawable/ic_warning" android:visibility="visible" app:layout_constraintBottom_toBottomOf="@id/title_label" - app:layout_constraintEnd_toStartOf="@id/sms_history" + app:layout_constraintEnd_toStartOf="@id/forwarding_history" tools:ignore="ContentDescription" /> @@ -35,7 +35,7 @@ android:id="@+id/status_label" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:text="@string/history_item_status" + android:text="@string/forwarding_history_item_status" android:layout_marginEnd="16dp" android:textAppearance="@style/SmsTextAppearance.Medium.12.Gray" app:layout_constraintEnd_toStartOf="@id/retry" @@ -49,7 +49,7 @@ android:textAppearance="@style/SmsTextAppearance.Medium.12" app:layout_constraintTop_toBottomOf="@id/status_label" app:layout_constraintStart_toStartOf="@id/status_label" - tools:text="@string/history_item_status_success" + tools:text="@string/forwarding_history_item_status_success" tools:textColor="@color/green" /> diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e27b373..20af243 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -99,11 +99,11 @@ Forwarding is disabled.\nGeneric Failure Error Forwarded messages %1$s messages were forwarded in the last 24 hours - Date: - Status - Message: - Success - Fail - Sms history - Retrying… + Date: + Status + Message: + Success + Fail + Sms history + Retrying… From 8551649af0cc1d4ec7ea19128d57afbbfd6cdee5 Mon Sep 17 00:00:00 2001 From: Illia Date: Wed, 5 Feb 2025 12:00:34 +0100 Subject: [PATCH 03/89] Remove redundant with() --- .../open/smsforwarder/ui/history/ForwardingHistoryFragment.kt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/src/main/kotlin/org/open/smsforwarder/ui/history/ForwardingHistoryFragment.kt b/app/src/main/kotlin/org/open/smsforwarder/ui/history/ForwardingHistoryFragment.kt index eb43b6c..02b172f 100644 --- a/app/src/main/kotlin/org/open/smsforwarder/ui/history/ForwardingHistoryFragment.kt +++ b/app/src/main/kotlin/org/open/smsforwarder/ui/history/ForwardingHistoryFragment.kt @@ -34,9 +34,7 @@ class ForwardingHistoryFragment : Fragment(R.layout.fragment_forwarding_history) } private fun setUpListeners() { - with(binding) { - arrowBackIv bindClicksTo viewModel::onBackClicked - } + binding.arrowBackIv bindClicksTo viewModel::onBackClicked } private fun setObservers() { From 25f7d6a50e0b473344c0ac794c221a6fc724495c Mon Sep 17 00:00:00 2001 From: belgoscode Date: Wed, 5 Mar 2025 15:55:48 +0300 Subject: [PATCH 04/89] [OSF-14] Refactor use case messages --- .github/workflows/build.yml | 1 + .../data/repository/FeedbackRepository.kt | 2 +- .../open/smsforwarder/domain/ValidationError.kt | 8 ++++++++ .../open/smsforwarder/domain/ValidationResult.kt | 5 +---- .../domain/usecase/ValidateBlankFieldUseCase.kt | 5 ++--- .../domain/usecase/ValidateEmailUseCase.kt | 7 +++---- .../domain/usecase/ValidatePhoneUseCase.kt | 5 ++--- .../extension/ValidationErrorMessageExt.kt | 14 ++++++++++++++ .../smsforwarder/ui/feedback/FeedbackEffect.kt | 2 +- .../smsforwarder/ui/feedback/FeedbackFragment.kt | 4 ++-- .../open/smsforwarder/ui/feedback/FeedbackState.kt | 6 +++--- .../smsforwarder/ui/feedback/FeedbackViewModel.kt | 5 +++-- .../addemaildetails/AddEmailDetailsFragment.kt | 2 +- .../addemaildetails/AddEmailDetailsState.kt | 4 ++-- .../addemaildetails/AddEmailDetailsViewModel.kt | 3 ++- .../addphonedetails/AddPhoneDetailsFragment.kt | 2 +- .../addphonedetails/AddPhoneDetailsState.kt | 4 ++-- .../addphonedetails/AddPhoneDetailsViewModel.kt | 3 ++- 18 files changed, 51 insertions(+), 31 deletions(-) create mode 100644 app/src/main/kotlin/org/open/smsforwarder/domain/ValidationError.kt create mode 100644 app/src/main/kotlin/org/open/smsforwarder/extension/ValidationErrorMessageExt.kt diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 65774eb..a51942e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -18,6 +18,7 @@ env: CLIENT_ID: ${{ secrets.CLIENT_ID }} CLIENT_SECRET: ${{ secrets.CLIENT_SECRET }} REDIRECT_URI: ${{ secrets.REDIRECT_URI }} + FEEDBACK_DB_PATH: ${{ secrets.FEEDBACK_DB_PATH }} jobs: build: diff --git a/app/src/main/kotlin/org/open/smsforwarder/data/repository/FeedbackRepository.kt b/app/src/main/kotlin/org/open/smsforwarder/data/repository/FeedbackRepository.kt index 47c753e..b5dc0b4 100644 --- a/app/src/main/kotlin/org/open/smsforwarder/data/repository/FeedbackRepository.kt +++ b/app/src/main/kotlin/org/open/smsforwarder/data/repository/FeedbackRepository.kt @@ -35,4 +35,4 @@ class FeedbackRepository @Inject constructor( const val EMAIL_KEY = "email" const val BODY_KEY = "body" } -} \ No newline at end of file +} diff --git a/app/src/main/kotlin/org/open/smsforwarder/domain/ValidationError.kt b/app/src/main/kotlin/org/open/smsforwarder/domain/ValidationError.kt new file mode 100644 index 0000000..3c0bcf9 --- /dev/null +++ b/app/src/main/kotlin/org/open/smsforwarder/domain/ValidationError.kt @@ -0,0 +1,8 @@ +package org.open.smsforwarder.domain + +enum class ValidationError { + BLANK_FIELD, + BLANK_EMAIL, + INVALID_EMAIL, + INVALID_PHONE +} diff --git a/app/src/main/kotlin/org/open/smsforwarder/domain/ValidationResult.kt b/app/src/main/kotlin/org/open/smsforwarder/domain/ValidationResult.kt index d46f075..31b3fec 100644 --- a/app/src/main/kotlin/org/open/smsforwarder/domain/ValidationResult.kt +++ b/app/src/main/kotlin/org/open/smsforwarder/domain/ValidationResult.kt @@ -1,9 +1,6 @@ package org.open.smsforwarder.domain -import org.open.smsforwarder.utils.Resources - -// TODO: Ticket 14. Refactor this entity and use cases - move errorMessage out of the domain. data class ValidationResult( val successful: Boolean, - val errorMessage: Resources.StringProvider? = null + val errorType: ValidationError? = null ) diff --git a/app/src/main/kotlin/org/open/smsforwarder/domain/usecase/ValidateBlankFieldUseCase.kt b/app/src/main/kotlin/org/open/smsforwarder/domain/usecase/ValidateBlankFieldUseCase.kt index 1403b90..f550112 100644 --- a/app/src/main/kotlin/org/open/smsforwarder/domain/usecase/ValidateBlankFieldUseCase.kt +++ b/app/src/main/kotlin/org/open/smsforwarder/domain/usecase/ValidateBlankFieldUseCase.kt @@ -1,8 +1,7 @@ package org.open.smsforwarder.domain.usecase -import org.open.smsforwarder.R +import org.open.smsforwarder.domain.ValidationError import org.open.smsforwarder.domain.ValidationResult -import org.open.smsforwarder.utils.Resources import javax.inject.Inject class ValidateBlankFieldUseCase @Inject constructor() { @@ -10,7 +9,7 @@ class ValidateBlankFieldUseCase @Inject constructor() { fun execute(field: String?): ValidationResult = if (field.isNullOrBlank()) { ValidationResult( successful = false, - errorMessage = Resources.StringResource(R.string.error_generic_is_blank) + errorType = ValidationError.BLANK_FIELD ) } else { ValidationResult(successful = true) diff --git a/app/src/main/kotlin/org/open/smsforwarder/domain/usecase/ValidateEmailUseCase.kt b/app/src/main/kotlin/org/open/smsforwarder/domain/usecase/ValidateEmailUseCase.kt index 81771f1..fb1335b 100644 --- a/app/src/main/kotlin/org/open/smsforwarder/domain/usecase/ValidateEmailUseCase.kt +++ b/app/src/main/kotlin/org/open/smsforwarder/domain/usecase/ValidateEmailUseCase.kt @@ -1,9 +1,8 @@ package org.open.smsforwarder.domain.usecase -import org.open.smsforwarder.R import org.open.smsforwarder.domain.EmailValidator +import org.open.smsforwarder.domain.ValidationError import org.open.smsforwarder.domain.ValidationResult -import org.open.smsforwarder.utils.Resources import javax.inject.Inject class ValidateEmailUseCase @Inject constructor( @@ -14,13 +13,13 @@ class ValidateEmailUseCase @Inject constructor( if (email.isBlank()) { return ValidationResult( successful = false, - errorMessage = Resources.StringResource(R.string.error_email_is_blank) + errorType = ValidationError.BLANK_EMAIL ) } return if (!emailValidator.isValid(email)) { ValidationResult( successful = false, - errorMessage = Resources.StringResource(R.string.error_email_is_not_valid) + errorType = ValidationError.INVALID_EMAIL ) } else { ValidationResult(successful = true) diff --git a/app/src/main/kotlin/org/open/smsforwarder/domain/usecase/ValidatePhoneUseCase.kt b/app/src/main/kotlin/org/open/smsforwarder/domain/usecase/ValidatePhoneUseCase.kt index 2d44e3b..5081d04 100644 --- a/app/src/main/kotlin/org/open/smsforwarder/domain/usecase/ValidatePhoneUseCase.kt +++ b/app/src/main/kotlin/org/open/smsforwarder/domain/usecase/ValidatePhoneUseCase.kt @@ -1,9 +1,8 @@ package org.open.smsforwarder.domain.usecase -import org.open.smsforwarder.R import org.open.smsforwarder.domain.PhoneValidator +import org.open.smsforwarder.domain.ValidationError import org.open.smsforwarder.domain.ValidationResult -import org.open.smsforwarder.utils.Resources import javax.inject.Inject class ValidatePhoneUseCase @Inject constructor( @@ -14,7 +13,7 @@ class ValidatePhoneUseCase @Inject constructor( if (!phoneValidator.isValid(phone)) { ValidationResult( successful = false, - errorMessage = Resources.StringResource(R.string.error_phone_number_is_not_valid) + errorType = ValidationError.INVALID_PHONE ) } else { ValidationResult( diff --git a/app/src/main/kotlin/org/open/smsforwarder/extension/ValidationErrorMessageExt.kt b/app/src/main/kotlin/org/open/smsforwarder/extension/ValidationErrorMessageExt.kt new file mode 100644 index 0000000..e3d4285 --- /dev/null +++ b/app/src/main/kotlin/org/open/smsforwarder/extension/ValidationErrorMessageExt.kt @@ -0,0 +1,14 @@ +package org.open.smsforwarder.extension + +import org.open.smsforwarder.R +import org.open.smsforwarder.domain.ValidationError +import org.open.smsforwarder.utils.Resources + +fun ValidationError.getErrorStringProvider(): Resources.StringProvider { + return when (this) { + ValidationError.BLANK_FIELD -> Resources.StringResource(R.string.error_generic_is_blank) + ValidationError.BLANK_EMAIL -> Resources.StringResource(R.string.error_email_is_blank) + ValidationError.INVALID_EMAIL -> Resources.StringResource(R.string.error_email_is_not_valid) + ValidationError.INVALID_PHONE -> Resources.StringResource(R.string.error_phone_number_is_not_valid) + } +} diff --git a/app/src/main/kotlin/org/open/smsforwarder/ui/feedback/FeedbackEffect.kt b/app/src/main/kotlin/org/open/smsforwarder/ui/feedback/FeedbackEffect.kt index f819311..c31b3d1 100644 --- a/app/src/main/kotlin/org/open/smsforwarder/ui/feedback/FeedbackEffect.kt +++ b/app/src/main/kotlin/org/open/smsforwarder/ui/feedback/FeedbackEffect.kt @@ -4,4 +4,4 @@ import androidx.annotation.StringRes sealed interface FeedbackEffect -data class SubmitResultEffect(@StringRes val messageRes: Int) : FeedbackEffect \ No newline at end of file +data class SubmitResultEffect(@StringRes val messageRes: Int) : FeedbackEffect diff --git a/app/src/main/kotlin/org/open/smsforwarder/ui/feedback/FeedbackFragment.kt b/app/src/main/kotlin/org/open/smsforwarder/ui/feedback/FeedbackFragment.kt index 5598dc1..5a6be0e 100644 --- a/app/src/main/kotlin/org/open/smsforwarder/ui/feedback/FeedbackFragment.kt +++ b/app/src/main/kotlin/org/open/smsforwarder/ui/feedback/FeedbackFragment.kt @@ -47,8 +47,8 @@ class FeedbackFragment : Fragment(R.layout.fragment_feedback) { private fun renderState(state: FeedbackState) { with(binding) { submitBtn.isEnabled = state.submitButtonEnabled - emailEtLayout.error = state.emailInputError?.asString(requireContext()) - bodyEtLayout.error = state.bodyInputError?.asString(requireContext()) + emailEtLayout.error = state.emailInputErrorProvider?.asString(requireContext()) + bodyEtLayout.error = state.bodyInputErrorProvider?.asString(requireContext()) } } diff --git a/app/src/main/kotlin/org/open/smsforwarder/ui/feedback/FeedbackState.kt b/app/src/main/kotlin/org/open/smsforwarder/ui/feedback/FeedbackState.kt index 3b74e92..09538e0 100644 --- a/app/src/main/kotlin/org/open/smsforwarder/ui/feedback/FeedbackState.kt +++ b/app/src/main/kotlin/org/open/smsforwarder/ui/feedback/FeedbackState.kt @@ -3,12 +3,12 @@ package org.open.smsforwarder.ui.feedback import org.open.smsforwarder.utils.Resources data class FeedbackState( - val emailInputError: Resources.StringProvider? = null, - val bodyInputError: Resources.StringProvider? = null, + val emailInputErrorProvider: Resources.StringProvider? = null, + val bodyInputErrorProvider: Resources.StringProvider? = null, val emailInput: String? = null, val bodyInput: String? = null ) { - private val hasNoInputErrors = (emailInputError == null) && (bodyInputError == null) + private val hasNoInputErrors = (emailInputErrorProvider == null) && (bodyInputErrorProvider == null) private val hasValues = !emailInput.isNullOrBlank() && !bodyInput.isNullOrBlank() val submitButtonEnabled = hasNoInputErrors && hasValues } diff --git a/app/src/main/kotlin/org/open/smsforwarder/ui/feedback/FeedbackViewModel.kt b/app/src/main/kotlin/org/open/smsforwarder/ui/feedback/FeedbackViewModel.kt index 81a93a2..b319fea 100644 --- a/app/src/main/kotlin/org/open/smsforwarder/ui/feedback/FeedbackViewModel.kt +++ b/app/src/main/kotlin/org/open/smsforwarder/ui/feedback/FeedbackViewModel.kt @@ -15,6 +15,7 @@ import org.open.smsforwarder.R import org.open.smsforwarder.data.repository.FeedbackRepository import org.open.smsforwarder.domain.usecase.ValidateBlankFieldUseCase import org.open.smsforwarder.domain.usecase.ValidateEmailUseCase +import org.open.smsforwarder.extension.getErrorStringProvider import javax.inject.Inject @HiltViewModel @@ -55,7 +56,7 @@ class FeedbackViewModel @Inject constructor( _viewState.update { it.copy( emailInput = email, - emailInputError = emailValidationResult.errorMessage + emailInputErrorProvider = emailValidationResult.errorType?.getErrorStringProvider() ) } } @@ -65,7 +66,7 @@ class FeedbackViewModel @Inject constructor( _viewState.update { state -> state.copy( bodyInput = body, - bodyInputError = bodyValidationResult.errorMessage + bodyInputErrorProvider = bodyValidationResult.errorType?.getErrorStringProvider() ) } } diff --git a/app/src/main/kotlin/org/open/smsforwarder/ui/steps/addrecipientdetails/addemaildetails/AddEmailDetailsFragment.kt b/app/src/main/kotlin/org/open/smsforwarder/ui/steps/addrecipientdetails/addemaildetails/AddEmailDetailsFragment.kt index 9d97aef..ce363ec 100644 --- a/app/src/main/kotlin/org/open/smsforwarder/ui/steps/addrecipientdetails/addemaildetails/AddEmailDetailsFragment.kt +++ b/app/src/main/kotlin/org/open/smsforwarder/ui/steps/addrecipientdetails/addemaildetails/AddEmailDetailsFragment.kt @@ -62,7 +62,7 @@ class AddEmailDetailsFragment : Fragment(R.layout.fragment_add_email_details) { signOutBtn.setVisibilityIfChanged(state.signOutBtnVisible) recipientEmailEt.setTextIfChangedKeepState(state.recipientEmail) nextBtn.isEnabled = state.nextButtonEnabled - recipientEmailLayout.error = state.inputError?.asString(requireContext()) + recipientEmailLayout.error = state.inputErrorProvider?.asString(requireContext()) } } diff --git a/app/src/main/kotlin/org/open/smsforwarder/ui/steps/addrecipientdetails/addemaildetails/AddEmailDetailsState.kt b/app/src/main/kotlin/org/open/smsforwarder/ui/steps/addrecipientdetails/addemaildetails/AddEmailDetailsState.kt index 977e796..ce29b57 100644 --- a/app/src/main/kotlin/org/open/smsforwarder/ui/steps/addrecipientdetails/addemaildetails/AddEmailDetailsState.kt +++ b/app/src/main/kotlin/org/open/smsforwarder/ui/steps/addrecipientdetails/addemaildetails/AddEmailDetailsState.kt @@ -13,8 +13,8 @@ data class AddEmailDetailsState( val sigInBtnVisible: Boolean = false, val signOutBtnVisible: Boolean = false, val recipientEmail: String = "", - val inputError: Resources.StringProvider? = null + val inputErrorProvider: Resources.StringProvider? = null ) { val nextButtonEnabled = - inputError == null && !senderEmail.isNullOrEmpty() && recipientEmail.isNotBlank() + (inputErrorProvider == null) && !senderEmail.isNullOrEmpty() && recipientEmail.isNotBlank() } diff --git a/app/src/main/kotlin/org/open/smsforwarder/ui/steps/addrecipientdetails/addemaildetails/AddEmailDetailsViewModel.kt b/app/src/main/kotlin/org/open/smsforwarder/ui/steps/addrecipientdetails/addemaildetails/AddEmailDetailsViewModel.kt index 8d268fa..837cf12 100644 --- a/app/src/main/kotlin/org/open/smsforwarder/ui/steps/addrecipientdetails/addemaildetails/AddEmailDetailsViewModel.kt +++ b/app/src/main/kotlin/org/open/smsforwarder/ui/steps/addrecipientdetails/addemaildetails/AddEmailDetailsViewModel.kt @@ -24,6 +24,7 @@ import org.open.smsforwarder.data.repository.AuthRepository import org.open.smsforwarder.data.repository.ForwardingRepository import org.open.smsforwarder.domain.usecase.ValidateEmailUseCase import org.open.smsforwarder.extension.asStateFlowWithInitialAction +import org.open.smsforwarder.extension.getErrorStringProvider import org.open.smsforwarder.extension.launchAndCancelPrevious import org.open.smsforwarder.helper.GoogleSignInHelper import org.open.smsforwarder.navigation.Screens @@ -87,7 +88,7 @@ class AddEmailDetailsViewModel @AssistedInject constructor( _viewState.update { it.copy( recipientEmail = email, - inputError = emailValidationResult.errorMessage + inputErrorProvider = emailValidationResult.errorType?.getErrorStringProvider() ) } } diff --git a/app/src/main/kotlin/org/open/smsforwarder/ui/steps/addrecipientdetails/addphonedetails/AddPhoneDetailsFragment.kt b/app/src/main/kotlin/org/open/smsforwarder/ui/steps/addrecipientdetails/addphonedetails/AddPhoneDetailsFragment.kt index 38f6039..d2874d7 100644 --- a/app/src/main/kotlin/org/open/smsforwarder/ui/steps/addrecipientdetails/addphonedetails/AddPhoneDetailsFragment.kt +++ b/app/src/main/kotlin/org/open/smsforwarder/ui/steps/addrecipientdetails/addphonedetails/AddPhoneDetailsFragment.kt @@ -44,7 +44,7 @@ class AddPhoneDetailsFragment : Fragment(R.layout.fragment_add_phone_details) { private fun renderState(state: AddPhoneDetailsState) { with(binding) { recipientPhoneEt.setTextIfChangedKeepState(state.recipientPhone) - recipientPhoneLayout.error = state.inputError?.asString(requireContext()) + recipientPhoneLayout.error = state.inputErrorProvider?.asString(requireContext()) nextBtn.isEnabled = state.nextButtonEnabled } } diff --git a/app/src/main/kotlin/org/open/smsforwarder/ui/steps/addrecipientdetails/addphonedetails/AddPhoneDetailsState.kt b/app/src/main/kotlin/org/open/smsforwarder/ui/steps/addrecipientdetails/addphonedetails/AddPhoneDetailsState.kt index b09bb09..da57caa 100644 --- a/app/src/main/kotlin/org/open/smsforwarder/ui/steps/addrecipientdetails/addphonedetails/AddPhoneDetailsState.kt +++ b/app/src/main/kotlin/org/open/smsforwarder/ui/steps/addrecipientdetails/addphonedetails/AddPhoneDetailsState.kt @@ -9,9 +9,9 @@ data class AddPhoneDetailsState( val title: String = "", val forwardingType: ForwardingType? = null, val recipientPhone: String = "", - val inputError: Resources.StringProvider? = null, + val inputErrorProvider: Resources.StringProvider? = null, val rules: List = emptyList() ) { - val nextButtonEnabled = inputError == null && recipientPhone.isNotBlank() + val nextButtonEnabled = (inputErrorProvider == null) && (recipientPhone.isNotBlank()) } diff --git a/app/src/main/kotlin/org/open/smsforwarder/ui/steps/addrecipientdetails/addphonedetails/AddPhoneDetailsViewModel.kt b/app/src/main/kotlin/org/open/smsforwarder/ui/steps/addrecipientdetails/addphonedetails/AddPhoneDetailsViewModel.kt index d6dda2b..b2a601a 100644 --- a/app/src/main/kotlin/org/open/smsforwarder/ui/steps/addrecipientdetails/addphonedetails/AddPhoneDetailsViewModel.kt +++ b/app/src/main/kotlin/org/open/smsforwarder/ui/steps/addrecipientdetails/addphonedetails/AddPhoneDetailsViewModel.kt @@ -17,6 +17,7 @@ import org.open.smsforwarder.analytics.AnalyticsTracker import org.open.smsforwarder.data.repository.ForwardingRepository import org.open.smsforwarder.domain.usecase.ValidatePhoneUseCase import org.open.smsforwarder.extension.asStateFlowWithInitialAction +import org.open.smsforwarder.extension.getErrorStringProvider import org.open.smsforwarder.extension.launchAndCancelPrevious import org.open.smsforwarder.navigation.Screens import org.open.smsforwarder.ui.mapper.toDomain @@ -58,7 +59,7 @@ class AddPhoneDetailsViewModel @AssistedInject constructor( _viewState.update { it.copy( recipientPhone = phoneNumber, - inputError = phoneValidationResult.errorMessage + inputErrorProvider = phoneValidationResult.errorType?.getErrorStringProvider() ) } } From 0bf125f6e7ed77cbaadcb5beeac554eb71de0187 Mon Sep 17 00:00:00 2001 From: Dmitriy Date: Mon, 10 Mar 2025 00:37:05 +0300 Subject: [PATCH 05/89] Add parallax onboardin effect --- .../ui/onboarding/OnboardingFragment.kt | 2 ++ .../adapter/OnboardingPagerTransformer.kt | 21 +++++++++++++++++++ 2 files changed, 23 insertions(+) create mode 100644 app/src/main/kotlin/org/open/smsforwarder/ui/onboarding/adapter/OnboardingPagerTransformer.kt diff --git a/app/src/main/kotlin/org/open/smsforwarder/ui/onboarding/OnboardingFragment.kt b/app/src/main/kotlin/org/open/smsforwarder/ui/onboarding/OnboardingFragment.kt index b464466..ae39551 100644 --- a/app/src/main/kotlin/org/open/smsforwarder/ui/onboarding/OnboardingFragment.kt +++ b/app/src/main/kotlin/org/open/smsforwarder/ui/onboarding/OnboardingFragment.kt @@ -16,6 +16,7 @@ import org.open.smsforwarder.extension.observeWithLifecycle import org.open.smsforwarder.extension.showOkDialog import org.open.smsforwarder.extension.unsafeLazy import org.open.smsforwarder.ui.onboarding.OnboardingState.Companion.slides +import org.open.smsforwarder.ui.onboarding.adapter.OnboardingPagerTransformer import org.open.smsforwarder.ui.onboarding.adapter.OnboardingSliderAdapter import org.open.smsforwarder.utils.ButtonFillAnimator @@ -38,6 +39,7 @@ class OnboardingFragment : Fragment(R.layout.fragment_onboarding) { private fun setupView() { with(binding) { adapter.setData(slides) + onboardingVp.setPageTransformer(OnboardingPagerTransformer()) onboardingVp.adapter = adapter TabLayoutMediator(dotsIndicator, onboardingVp) { _, _ -> }.attach() buttonFillAnimator = ButtonFillAnimator( diff --git a/app/src/main/kotlin/org/open/smsforwarder/ui/onboarding/adapter/OnboardingPagerTransformer.kt b/app/src/main/kotlin/org/open/smsforwarder/ui/onboarding/adapter/OnboardingPagerTransformer.kt new file mode 100644 index 0000000..0740c2f --- /dev/null +++ b/app/src/main/kotlin/org/open/smsforwarder/ui/onboarding/adapter/OnboardingPagerTransformer.kt @@ -0,0 +1,21 @@ +package org.open.smsforwarder.ui.onboarding.adapter + +import android.view.View +import androidx.viewpager2.widget.ViewPager2 +import com.google.android.material.textview.MaterialTextView +import org.open.smsforwarder.R + +private const val TITLE_PARALLAX_FACTOR = 0.45f +private const val SUBTITLE_PARALLAX_FACTOR = 2.5f + +class OnboardingPagerTransformer : ViewPager2.PageTransformer { + + override fun transformPage(page: View, position: Float) { + if (position >= -1 && position <= 1) { + page.findViewById(R.id.text_title).translationX = + position * page.width / TITLE_PARALLAX_FACTOR + page.findViewById(R.id.text_subtitle).translationX = + position * page.width / SUBTITLE_PARALLAX_FACTOR + } + } +} From 2072954eb97605f6af8deb37de5fdee17e19e8ae Mon Sep 17 00:00:00 2001 From: belgoscode Date: Fri, 14 Mar 2025 13:49:51 +0300 Subject: [PATCH 06/89] [OSF-3] Minor fixes #2 --- .../smsforwarder/ui/history/ForwardingHistoryFragment.kt | 4 ++-- .../open/smsforwarder/ui/history/ForwardingHistoryState.kt | 4 +++- app/src/main/res/layout/fragment_forwarding_history.xml | 4 ++-- app/src/main/res/layout/item_history.xml | 2 +- app/src/main/res/values/strings.xml | 1 + app/src/main/res/values/text_appearance.xml | 5 +++++ 6 files changed, 14 insertions(+), 6 deletions(-) diff --git a/app/src/main/kotlin/org/open/smsforwarder/ui/history/ForwardingHistoryFragment.kt b/app/src/main/kotlin/org/open/smsforwarder/ui/history/ForwardingHistoryFragment.kt index 02b172f..b5a4a44 100644 --- a/app/src/main/kotlin/org/open/smsforwarder/ui/history/ForwardingHistoryFragment.kt +++ b/app/src/main/kotlin/org/open/smsforwarder/ui/history/ForwardingHistoryFragment.kt @@ -47,8 +47,8 @@ class ForwardingHistoryFragment : Fragment(R.layout.fragment_forwarding_history) private fun renderState(state: ForwardingHistoryState) { adapter.submitList(state.historyItems) with(binding) { - emptyStateText.isVisible = state.historyItems.isEmpty() - historyItems.isVisible = state.historyItems.isNotEmpty() + emptyStateText.isVisible = state.isEmptyStateTextVisible + historyItems.isVisible = state.isHistoryItemsVisible } } diff --git a/app/src/main/kotlin/org/open/smsforwarder/ui/history/ForwardingHistoryState.kt b/app/src/main/kotlin/org/open/smsforwarder/ui/history/ForwardingHistoryState.kt index 3347b5c..488482b 100644 --- a/app/src/main/kotlin/org/open/smsforwarder/ui/history/ForwardingHistoryState.kt +++ b/app/src/main/kotlin/org/open/smsforwarder/ui/history/ForwardingHistoryState.kt @@ -3,5 +3,7 @@ package org.open.smsforwarder.ui.history import org.open.smsforwarder.ui.model.HistoryUI data class ForwardingHistoryState( - val historyItems: List = emptyList() + val historyItems: List = emptyList(), + val isHistoryItemsVisible: Boolean = historyItems.isNotEmpty(), + val isEmptyStateTextVisible: Boolean = historyItems.isEmpty(), ) diff --git a/app/src/main/res/layout/fragment_forwarding_history.xml b/app/src/main/res/layout/fragment_forwarding_history.xml index 5613cde..1fdbfe9 100644 --- a/app/src/main/res/layout/fragment_forwarding_history.xml +++ b/app/src/main/res/layout/fragment_forwarding_history.xml @@ -49,8 +49,8 @@ android:layout_marginTop="256dp" android:gravity="center_horizontal" android:padding="16dp" - android:text="@string/empty_recipients" - android:textAppearance="@style/SmsTextAppearance.SemiBold.32" + android:text="@string/forwarding_history_empty" + android:textAppearance="@style/SmsTextAppearance.Medium.16.Gray" android:visibility="gone" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" diff --git a/app/src/main/res/layout/item_history.xml b/app/src/main/res/layout/item_history.xml index 927a069..7010198 100644 --- a/app/src/main/res/layout/item_history.xml +++ b/app/src/main/res/layout/item_history.xml @@ -36,7 +36,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/forwarding_history_item_status" - android:layout_marginEnd="16dp" + android:layout_marginEnd="24dp" android:textAppearance="@style/SmsTextAppearance.Medium.12.Gray" app:layout_constraintEnd_toStartOf="@id/retry" app:layout_constraintTop_toTopOf="parent" /> diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 982d46e..00bf2f3 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -108,6 +108,7 @@ Fail Sms history Retrying… + There is no forwarded messages yet Feedback Fill out the form and we will be in touch Contact e-mail diff --git a/app/src/main/res/values/text_appearance.xml b/app/src/main/res/values/text_appearance.xml index b75f17e..ceacad1 100644 --- a/app/src/main/res/values/text_appearance.xml +++ b/app/src/main/res/values/text_appearance.xml @@ -36,6 +36,11 @@ 18sp + + From 03ee535a46229c2052d610b9a072d5f7560c1864 Mon Sep 17 00:00:00 2001 From: belgoscode Date: Mon, 17 Mar 2025 10:28:45 +0300 Subject: [PATCH 07/89] [OSF-14] Comment fixes #2 --- app/src/main/res/layout/fragment_forwarding_history.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/layout/fragment_forwarding_history.xml b/app/src/main/res/layout/fragment_forwarding_history.xml index 1fdbfe9..934e06b 100644 --- a/app/src/main/res/layout/fragment_forwarding_history.xml +++ b/app/src/main/res/layout/fragment_forwarding_history.xml @@ -46,7 +46,6 @@ android:id="@+id/empty_state_text" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginTop="256dp" android:gravity="center_horizontal" android:padding="16dp" android:text="@string/forwarding_history_empty" @@ -54,6 +53,7 @@ android:visibility="gone" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintTop_toBottomOf="@id/title_label" /> Date: Fri, 11 Apr 2025 16:33:48 +0300 Subject: [PATCH 08/89] [OSF-21] Accessibility compliance --- .../open/smsforwarder/ui/home/HomeFragment.kt | 7 ++++ .../ui/home/adapter/HomeViewHolder.kt | 6 ++++ .../ui/onboarding/OnboardingFragment.kt | 35 ++++++++++++++++--- .../AddEmailDetailsFragment.kt | 7 ++++ .../AddPhoneDetailsFragment.kt | 7 ++++ .../addrule/AddForwardingRuleFragment.kt | 8 +++++ .../steps/addrule/adapter/RuleViewHolder.kt | 8 +++++ .../ChooseForwardingMethodFragment.kt | 7 ++++ .../radio_button_text_colors.xml | 5 +++ .../res/drawable/button_add_rule_colors.xml | 2 +- app/src/main/res/drawable/default_dot.xml | 2 +- .../edit_text_layout_stroke_colors.xml | 4 +-- .../main/res/drawable/ic_add_circle_24.xml | 2 +- app/src/main/res/layout/dialog_delete.xml | 2 +- app/src/main/res/layout/dialog_edit_rule.xml | 2 +- .../res/layout/fragment_add_email_details.xml | 33 ++++++++++------- .../layout/fragment_add_forwarding_rule.xml | 26 ++++++++------ .../res/layout/fragment_add_phone_details.xml | 23 +++++++----- .../fragment_choose_forwarding_method.xml | 25 ++++++++----- app/src/main/res/layout/fragment_feedback.xml | 2 +- app/src/main/res/layout/fragment_home.xml | 9 +++-- .../main/res/layout/fragment_onboarding.xml | 13 +++++-- app/src/main/res/layout/item_recipient.xml | 14 +++++--- app/src/main/res/layout/item_rule.xml | 6 ++++ app/src/main/res/values-night/themes.xml | 3 ++ app/src/main/res/values/attrs.xml | 2 ++ app/src/main/res/values/colors.xml | 9 +++-- app/src/main/res/values/strings.xml | 25 ++++++++----- app/src/main/res/values/text_appearance.xml | 12 +++---- app/src/main/res/values/themes.xml | 11 +++--- 30 files changed, 234 insertions(+), 83 deletions(-) create mode 100644 app/src/main/res/drawable-night/radio_button_text_colors.xml diff --git a/app/src/main/kotlin/org/open/smsforwarder/ui/home/HomeFragment.kt b/app/src/main/kotlin/org/open/smsforwarder/ui/home/HomeFragment.kt index 02f89f5..c46e309 100644 --- a/app/src/main/kotlin/org/open/smsforwarder/ui/home/HomeFragment.kt +++ b/app/src/main/kotlin/org/open/smsforwarder/ui/home/HomeFragment.kt @@ -13,6 +13,7 @@ import android.text.Spanned import android.text.method.LinkMovementMethod import android.text.style.ClickableSpan import android.view.View +import android.view.accessibility.AccessibilityEvent import android.widget.TextView import androidx.activity.result.contract.ActivityResultContracts import androidx.core.view.isVisible @@ -80,6 +81,12 @@ class HomeFragment : Fragment(R.layout.fragment_home), DeleteDialogListener { requestPermissions() } + override fun onResume() { + super.onResume() + binding.titleLabel.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED) + binding.titleLabel.requestFocus() + } + override fun onDestroyView() { binding.forwardings.adapter = null super.onDestroyView() diff --git a/app/src/main/kotlin/org/open/smsforwarder/ui/home/adapter/HomeViewHolder.kt b/app/src/main/kotlin/org/open/smsforwarder/ui/home/adapter/HomeViewHolder.kt index 1e8a73c..e481836 100644 --- a/app/src/main/kotlin/org/open/smsforwarder/ui/home/adapter/HomeViewHolder.kt +++ b/app/src/main/kotlin/org/open/smsforwarder/ui/home/adapter/HomeViewHolder.kt @@ -16,6 +16,8 @@ class HomeViewHolder( val context = itemView.context title.isVisible = state.title.isNotBlank() title.text = state.title + title.contentDescription = + context.getString(R.string.forwarding) + context.getString(R.string.space) + state.title forwardingType.text = state.forwardingType?.value phoneGroup.isVisible = state.isSmsBlockCompleted() recipientPhoneEt.text = state.recipientPhone @@ -29,6 +31,10 @@ class HomeViewHolder( } buttonEditItem.setOnClickListener { onItemEdit(state.id) } + buttonEditItem.contentDescription = + context.getString(R.string.edit_forwarding) + context.getString(R.string.space) + state.title buttonRemoveItem.setOnClickListener { onItemRemove(state.id) } + buttonRemoveItem.contentDescription = + context.getString(R.string.delete_forwarding) + context.getString(R.string.space) + state.title } } diff --git a/app/src/main/kotlin/org/open/smsforwarder/ui/onboarding/OnboardingFragment.kt b/app/src/main/kotlin/org/open/smsforwarder/ui/onboarding/OnboardingFragment.kt index ae39551..91409f0 100644 --- a/app/src/main/kotlin/org/open/smsforwarder/ui/onboarding/OnboardingFragment.kt +++ b/app/src/main/kotlin/org/open/smsforwarder/ui/onboarding/OnboardingFragment.kt @@ -2,6 +2,7 @@ package org.open.smsforwarder.ui.onboarding import android.os.Bundle import android.view.View +import android.view.accessibility.AccessibilityEvent import androidx.core.view.isVisible import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels @@ -36,12 +37,25 @@ class OnboardingFragment : Fragment(R.layout.fragment_onboarding) { setupObservers() } + override fun onResume() { + super.onResume() + setLabelFocus() + } + + private fun setLabelFocus() { + binding.stepLabel.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED) + binding.stepLabel.requestFocus() + } + private fun setupView() { with(binding) { adapter.setData(slides) onboardingVp.setPageTransformer(OnboardingPagerTransformer()) onboardingVp.adapter = adapter - TabLayoutMediator(dotsIndicator, onboardingVp) { _, _ -> }.attach() + dotsIndicator.tabIconTint = null + TabLayoutMediator(dotsIndicator, onboardingVp) { tab, _ -> + tab.setIcon(R.drawable.dots_selector) + }.attach() buttonFillAnimator = ButtonFillAnimator( button = nextBtn, lifecycle = viewLifecycleOwner.lifecycle, @@ -74,6 +88,8 @@ class OnboardingFragment : Fragment(R.layout.fragment_onboarding) { with(binding) { backBtn.isVisible = onboardingState.currentStep >= 2 stepLabel.text = getString(R.string.onboarding_step_label, onboardingState.currentStep) + stepLabel.contentDescription = + getString(R.string.onboarding_title) + getString(R.string.comma) + stepLabel.text skipAllBtn.isVisible = !onboardingState.isLastSlide nextBtn.text = getString(onboardingState.nextButtonRes) acknowledgeChBx.isVisible = onboardingState.isLastSlide @@ -91,9 +107,20 @@ class OnboardingFragment : Fragment(R.layout.fragment_onboarding) { ) } - NextPageEffect -> binding.onboardingVp.currentItem++ - PreviousPageEffect -> binding.onboardingVp.currentItem-- - SkipAllEffect -> binding.onboardingVp.currentItem = adapter.itemCount - 1 + NextPageEffect -> { + binding.onboardingVp.currentItem++ + setLabelFocus() + } + + PreviousPageEffect -> { + binding.onboardingVp.currentItem-- + setLabelFocus() + } + + SkipAllEffect -> { + binding.onboardingVp.currentItem = adapter.itemCount - 1 + setLabelFocus() + } } } } diff --git a/app/src/main/kotlin/org/open/smsforwarder/ui/steps/addrecipientdetails/addemaildetails/AddEmailDetailsFragment.kt b/app/src/main/kotlin/org/open/smsforwarder/ui/steps/addrecipientdetails/addemaildetails/AddEmailDetailsFragment.kt index ce363ec..56b3ce5 100644 --- a/app/src/main/kotlin/org/open/smsforwarder/ui/steps/addrecipientdetails/addemaildetails/AddEmailDetailsFragment.kt +++ b/app/src/main/kotlin/org/open/smsforwarder/ui/steps/addrecipientdetails/addemaildetails/AddEmailDetailsFragment.kt @@ -2,6 +2,7 @@ package org.open.smsforwarder.ui.steps.addrecipientdetails.addemaildetails import android.os.Bundle import android.view.View +import android.view.accessibility.AccessibilityEvent import androidx.activity.result.contract.ActivityResultContracts import androidx.core.os.bundleOf import androidx.fragment.app.Fragment @@ -37,6 +38,12 @@ class AddEmailDetailsFragment : Fragment(R.layout.fragment_add_email_details) { setObservers() } + override fun onResume() { + super.onResume() + binding.step2.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED) + binding.step2.requestFocus() + } + private fun setListeners() { with(binding) { arrowBackIv bindClicksTo viewModel::onBackClicked diff --git a/app/src/main/kotlin/org/open/smsforwarder/ui/steps/addrecipientdetails/addphonedetails/AddPhoneDetailsFragment.kt b/app/src/main/kotlin/org/open/smsforwarder/ui/steps/addrecipientdetails/addphonedetails/AddPhoneDetailsFragment.kt index d2874d7..e47cb42 100644 --- a/app/src/main/kotlin/org/open/smsforwarder/ui/steps/addrecipientdetails/addphonedetails/AddPhoneDetailsFragment.kt +++ b/app/src/main/kotlin/org/open/smsforwarder/ui/steps/addrecipientdetails/addphonedetails/AddPhoneDetailsFragment.kt @@ -2,6 +2,7 @@ package org.open.smsforwarder.ui.steps.addrecipientdetails.addphonedetails import android.os.Bundle import android.view.View +import android.view.accessibility.AccessibilityEvent import androidx.core.os.bundleOf import androidx.fragment.app.Fragment import by.kirich1409.viewbindingdelegate.viewBinding @@ -28,6 +29,12 @@ class AddPhoneDetailsFragment : Fragment(R.layout.fragment_add_phone_details) { setObservers() } + override fun onResume() { + super.onResume() + binding.step2.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED) + binding.step2.requestFocus() + } + private fun setListeners() { with(binding) { arrowBackIv bindClicksTo viewModel::onBackClicked diff --git a/app/src/main/kotlin/org/open/smsforwarder/ui/steps/addrule/AddForwardingRuleFragment.kt b/app/src/main/kotlin/org/open/smsforwarder/ui/steps/addrule/AddForwardingRuleFragment.kt index f42a9d3..32f06fa 100644 --- a/app/src/main/kotlin/org/open/smsforwarder/ui/steps/addrule/AddForwardingRuleFragment.kt +++ b/app/src/main/kotlin/org/open/smsforwarder/ui/steps/addrule/AddForwardingRuleFragment.kt @@ -2,6 +2,7 @@ package org.open.smsforwarder.ui.steps.addrule import android.os.Bundle import android.view.View +import android.view.accessibility.AccessibilityEvent import androidx.core.os.bundleOf import androidx.core.view.isVisible import androidx.fragment.app.Fragment @@ -44,6 +45,12 @@ class AddForwardingRuleFragment : Fragment(R.layout.fragment_add_forwarding_rule setObservers() } + override fun onResume() { + super.onResume() + binding.step3.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED) + binding.step3.requestFocus() + } + private fun setAdapter() { with(binding) { rulesRv.adapter = adapter @@ -74,6 +81,7 @@ class AddForwardingRuleFragment : Fragment(R.layout.fragment_add_forwarding_rule adapter.submitList(state.rules) emptyTv.isVisible = state.rules.isEmpty() finishBtn.isEnabled = state.rules.isNotEmpty() + rulesRv.isVisible = state.rules.isNotEmpty() messagePatternLayout.error = state.errorMessage?.let { getString(it) } buttonAddRuleBtn.isEnabled = state.isAddRuleButtonEnabled } diff --git a/app/src/main/kotlin/org/open/smsforwarder/ui/steps/addrule/adapter/RuleViewHolder.kt b/app/src/main/kotlin/org/open/smsforwarder/ui/steps/addrule/adapter/RuleViewHolder.kt index 3fe575b..23671dd 100644 --- a/app/src/main/kotlin/org/open/smsforwarder/ui/steps/addrule/adapter/RuleViewHolder.kt +++ b/app/src/main/kotlin/org/open/smsforwarder/ui/steps/addrule/adapter/RuleViewHolder.kt @@ -1,6 +1,7 @@ package org.open.smsforwarder.ui.steps.addrule.adapter import androidx.recyclerview.widget.RecyclerView +import org.open.smsforwarder.R import org.open.smsforwarder.databinding.ItemRuleBinding import org.open.smsforwarder.domain.model.Rule @@ -11,8 +12,15 @@ class RuleViewHolder( ) : RecyclerView.ViewHolder(binding.root) { fun bind(rule: Rule) = with(binding) { + val context = itemView.context buttonEditItem.setOnClickListener { onItemEdit(rule) } + buttonEditItem.contentDescription = + context.getString(R.string.dialog_edit_rule_title) + context.getString(R.string.space) + rule.textRule buttonRemoveItem.setOnClickListener { onItemRemove(rule) } + buttonRemoveItem.contentDescription = + context.getString(R.string.dialog_delete_rule_title) + context.getString(R.string.space) + rule.textRule ruleTv.text = rule.textRule + ruleTv.contentDescription = + context.getString(R.string.rule) + context.getString(R.string.space) + rule.textRule } } diff --git a/app/src/main/kotlin/org/open/smsforwarder/ui/steps/choosemethod/ChooseForwardingMethodFragment.kt b/app/src/main/kotlin/org/open/smsforwarder/ui/steps/choosemethod/ChooseForwardingMethodFragment.kt index e72e546..96b49bf 100644 --- a/app/src/main/kotlin/org/open/smsforwarder/ui/steps/choosemethod/ChooseForwardingMethodFragment.kt +++ b/app/src/main/kotlin/org/open/smsforwarder/ui/steps/choosemethod/ChooseForwardingMethodFragment.kt @@ -2,6 +2,7 @@ package org.open.smsforwarder.ui.steps.choosemethod import android.os.Bundle import android.view.View +import android.view.accessibility.AccessibilityEvent import androidx.core.os.bundleOf import androidx.fragment.app.Fragment import by.kirich1409.viewbindingdelegate.viewBinding @@ -34,6 +35,12 @@ class ChooseForwardingMethodFragment : Fragment(R.layout.fragment_choose_forward setObservers() } + override fun onResume() { + super.onResume() + binding.step1.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED) + binding.step1.requestFocus() + } + private fun setListeners() { with(binding) { titleEt bindTextChangesTo viewModel::onTitleChanged diff --git a/app/src/main/res/drawable-night/radio_button_text_colors.xml b/app/src/main/res/drawable-night/radio_button_text_colors.xml new file mode 100644 index 0000000..ef76e51 --- /dev/null +++ b/app/src/main/res/drawable-night/radio_button_text_colors.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/button_add_rule_colors.xml b/app/src/main/res/drawable/button_add_rule_colors.xml index 5557eb4..17131e9 100644 --- a/app/src/main/res/drawable/button_add_rule_colors.xml +++ b/app/src/main/res/drawable/button_add_rule_colors.xml @@ -1,5 +1,5 @@ - + \ No newline at end of file diff --git a/app/src/main/res/drawable/default_dot.xml b/app/src/main/res/drawable/default_dot.xml index 0f05be0..c5b76e9 100644 --- a/app/src/main/res/drawable/default_dot.xml +++ b/app/src/main/res/drawable/default_dot.xml @@ -4,7 +4,7 @@ android:width="8dp" android:height="8dp"> - + \ No newline at end of file diff --git a/app/src/main/res/drawable/edit_text_layout_stroke_colors.xml b/app/src/main/res/drawable/edit_text_layout_stroke_colors.xml index 836dbe9..80eae7b 100644 --- a/app/src/main/res/drawable/edit_text_layout_stroke_colors.xml +++ b/app/src/main/res/drawable/edit_text_layout_stroke_colors.xml @@ -1,5 +1,5 @@ - - + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_add_circle_24.xml b/app/src/main/res/drawable/ic_add_circle_24.xml index 52322e8..213fafa 100644 --- a/app/src/main/res/drawable/ic_add_circle_24.xml +++ b/app/src/main/res/drawable/ic_add_circle_24.xml @@ -1,7 +1,7 @@ - - + + @@ -64,7 +70,7 @@ android:layout_marginTop="32dp" android:text="@string/signed_in_as" android:textAppearance="@style/SmsTextAppearance.Regular.14" - android:textColor="@color/green" + android:textColor="?attr/colorSuccess" android:visibility="gone" app:layout_constraintEnd_toEndOf="@id/end_margin" app:layout_constraintStart_toStartOf="@id/start_margin" @@ -131,9 +137,9 @@ android:layout_height="wrap_content" android:layout_marginTop="16dp" app:errorEnabled="true" - app:errorIconTint="@color/red" - app:errorTextColor="@color/red" - app:helperTextTextColor="@color/red" + app:errorIconTint="?attr/colorError" + app:errorTextColor="?attr/colorError" + app:helperTextTextColor="?attr/colorError" app:layout_constraintEnd_toEndOf="@id/end_margin" app:layout_constraintStart_toStartOf="@id/start_margin" app:layout_constraintTop_toBottomOf="@id/enter_email_tv" @@ -144,6 +150,7 @@ android:layout_width="match_parent" android:layout_height="56dp" android:hint="@string/enter_email_hint" + android:textColorHint="?attr/colorSubsidiary" android:imeOptions="actionDone" android:inputType="textEmailAddress" /> diff --git a/app/src/main/res/layout/fragment_add_forwarding_rule.xml b/app/src/main/res/layout/fragment_add_forwarding_rule.xml index 4f6aff7..ec8a1c2 100644 --- a/app/src/main/res/layout/fragment_add_forwarding_rule.xml +++ b/app/src/main/res/layout/fragment_add_forwarding_rule.xml @@ -21,15 +21,6 @@ android:orientation="vertical" app:layout_constraintGuide_end="16dp" /> - - + + @@ -81,6 +86,7 @@ android:layout_width="match_parent" android:layout_height="56dp" android:hint="@string/enter_text_rule_pattern" + android:textColorHint="?attr/colorSubsidiary" android:imeOptions="actionDone" android:inputType="text" android:textAppearance="@style/SmsTextAppearance.Regular.16" /> diff --git a/app/src/main/res/layout/fragment_add_phone_details.xml b/app/src/main/res/layout/fragment_add_phone_details.xml index 41c2c55..01e24ce 100644 --- a/app/src/main/res/layout/fragment_add_phone_details.xml +++ b/app/src/main/res/layout/fragment_add_phone_details.xml @@ -24,14 +24,6 @@ android:orientation="vertical" app:layout_constraintGuide_end="16dp" /> - - + + diff --git a/app/src/main/res/layout/fragment_choose_forwarding_method.xml b/app/src/main/res/layout/fragment_choose_forwarding_method.xml index 709f3ee..e33dbf9 100644 --- a/app/src/main/res/layout/fragment_choose_forwarding_method.xml +++ b/app/src/main/res/layout/fragment_choose_forwarding_method.xml @@ -27,20 +27,13 @@ android:orientation="vertical" app:layout_constraintGuide_end="16dp" /> - - + + @@ -123,7 +130,7 @@ android:layout_height="wrap_content" android:layout_marginTop="12dp" android:text="@string/sms_warning" - android:textAppearance="@style/SmsTextAppearance.Regular.14.OnSurfaceVariant" + android:textAppearance="@style/SmsTextAppearance.Regular.14.Subsidiary" android:visibility="gone" tools:visibility="visible" /> diff --git a/app/src/main/res/layout/fragment_feedback.xml b/app/src/main/res/layout/fragment_feedback.xml index 8583a6c..d1ed8e4 100644 --- a/app/src/main/res/layout/fragment_feedback.xml +++ b/app/src/main/res/layout/fragment_feedback.xml @@ -25,7 +25,7 @@ android:layout_height="wrap_content" android:layout_marginTop="@dimen/medium" android:text="@string/feedback_description" - android:textAppearance="@style/SmsTextAppearance.Regular.14.OnSurfaceVariant" /> + android:textAppearance="@style/SmsTextAppearance.Regular.14.Subsidiary" /> + /> @@ -42,9 +46,11 @@ android:id="@+id/skip_all_btn" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:minWidth="48dp" + android:minHeight="48dp" android:paddingVertical="8dp" android:text="@string/onboarding_skip_all" - android:textAppearance="@style/SmsTextAppearance.Regular.14.OnSurfaceVariant" + android:textAppearance="@style/SmsTextAppearance.Regular.14.Subsidiary" app:layout_constraintBottom_toBottomOf="@id/step_label" app:layout_constraintEnd_toEndOf="@id/right_guideline" app:layout_constraintTop_toTopOf="@id/step_label" /> @@ -76,13 +82,14 @@ diff --git a/app/src/main/res/layout/item_recipient.xml b/app/src/main/res/layout/item_recipient.xml index e79ef5f..981dbb1 100644 --- a/app/src/main/res/layout/item_recipient.xml +++ b/app/src/main/res/layout/item_recipient.xml @@ -29,7 +29,7 @@ android:layout_height="wrap_content" android:layout_marginTop="12dp" android:text="@string/item_recipient_fwd_type" - android:textAppearance="@style/SmsTextAppearance.Medium.12.Gray" + android:textAppearance="@style/SmsTextAppearance.Medium.12.Subsidiary" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/title" /> @@ -51,7 +51,7 @@ android:layout_height="wrap_content" android:layout_marginTop="12dp" android:text="@string/item_recipient_phone" - android:textAppearance="@style/SmsTextAppearance.Medium.12.Gray" + android:textAppearance="@style/SmsTextAppearance.Medium.12.Subsidiary" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/forwarding_type" /> @@ -73,7 +73,7 @@ android:layout_height="wrap_content" android:layout_marginTop="12dp" android:text="@string/item_recipient_email" - android:textAppearance="@style/SmsTextAppearance.Medium.12.Gray" + android:textAppearance="@style/SmsTextAppearance.Medium.12.Subsidiary" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/forwarding_type" /> @@ -101,7 +101,7 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginTop="16dp" - android:textAppearance="@style/SmsTextAppearance.Medium.12.Red" + android:textAppearance="@style/SmsTextAppearance.Medium.12.Error" app:layout_constraintEnd_toStartOf="@id/button_edit_item" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/barrier" @@ -111,9 +111,12 @@ android:id="@+id/button_edit_item" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:minWidth="48dp" + android:minHeight="48dp" android:background="@drawable/image_button_background" android:paddingHorizontal="14dp" android:paddingVertical="13dp" + android:contentDescription="@string/edit_forwarding" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent" app:srcCompat="@drawable/ic_edit" @@ -123,10 +126,13 @@ android:id="@+id/button_remove_item" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:minWidth="48dp" + android:minHeight="48dp" android:layout_marginTop="22dp" android:background="@drawable/image_button_background" android:paddingHorizontal="14dp" android:paddingVertical="13dp" + android:contentDescription="@string/delete_forwarding" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toBottomOf="@id/button_edit_item" app:srcCompat="@drawable/ic_remove" diff --git a/app/src/main/res/layout/item_rule.xml b/app/src/main/res/layout/item_rule.xml index 9ffdd91..4f76095 100644 --- a/app/src/main/res/layout/item_rule.xml +++ b/app/src/main/res/layout/item_rule.xml @@ -26,10 +26,13 @@ android:id="@+id/button_edit_item" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:minWidth="48dp" + android:minHeight="48dp" android:layout_marginEnd="16dp" android:background="@drawable/image_button_background" android:paddingHorizontal="14dp" android:paddingVertical="13dp" + android:contentDescription="@string/edit_rule_title" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toStartOf="@id/button_remove_item" app:layout_constraintTop_toTopOf="parent" @@ -40,9 +43,12 @@ android:id="@+id/button_remove_item" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:minWidth="48dp" + android:minHeight="48dp" android:background="@drawable/image_button_background" android:paddingHorizontal="14dp" android:paddingVertical="13dp" + android:contentDescription="@string/dialog_delete_rule_title" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent" diff --git a/app/src/main/res/values-night/themes.xml b/app/src/main/res/values-night/themes.xml index 2340a99..144113d 100644 --- a/app/src/main/res/values-night/themes.xml +++ b/app/src/main/res/values-night/themes.xml @@ -14,6 +14,9 @@ @color/stroke_dark @color/gray @color/black + @color/gray_light + @color/red_light + @color/green_light - - - \ No newline at end of file diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index 3894acd..401aa48 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -14,6 +14,9 @@ @color/text_disabled_light @color/platinum @color/light_blue + @color/gray + @color/red + @color/green