diff --git a/data/src/main/java/com/lighthouse/database/entity/UsageHistoryEntity.kt b/data/src/main/java/com/lighthouse/database/entity/UsageHistoryEntity.kt index 294a0d228..6a31dac21 100644 --- a/data/src/main/java/com/lighthouse/database/entity/UsageHistoryEntity.kt +++ b/data/src/main/java/com/lighthouse/database/entity/UsageHistoryEntity.kt @@ -21,7 +21,8 @@ import java.util.Date data class UsageHistoryEntity( @ColumnInfo(name = "gifticon_id") val gifticonId: String, @ColumnInfo(name = "date") val date: Date, - @ColumnInfo(name = "address") val address: String, + @ColumnInfo(name = "longitude") val longitude: Double, + @ColumnInfo(name = "latitude") val latitude: Double, @ColumnInfo(name = "amount") val amount: Int ) { companion object { diff --git a/data/src/main/java/com/lighthouse/database/mapper/UsageHistoryMapper.kt b/data/src/main/java/com/lighthouse/database/mapper/UsageHistoryMapper.kt index 3c5c4df61..2a9a33b5b 100644 --- a/data/src/main/java/com/lighthouse/database/mapper/UsageHistoryMapper.kt +++ b/data/src/main/java/com/lighthouse/database/mapper/UsageHistoryMapper.kt @@ -1,12 +1,13 @@ package com.lighthouse.database.mapper import com.lighthouse.database.entity.UsageHistoryEntity +import com.lighthouse.domain.VertexLocation import com.lighthouse.domain.model.UsageHistory fun UsageHistoryEntity.toUsageHistory(): UsageHistory { return UsageHistory( date = date, - address = address, + location = VertexLocation(longitude, latitude), amount = amount ) } @@ -15,7 +16,8 @@ fun UsageHistory.toUsageHistoryEntity(gifticonId: String): UsageHistoryEntity { return UsageHistoryEntity( gifticonId = gifticonId, date = date, - address = address, + longitude = location?.longitude ?: 0f.toDouble(), + latitude = location?.latitude ?: 0f.toDouble(), amount = amount ) } diff --git a/domain/src/main/java/com/lighthouse/domain/model/UsageHistory.kt b/domain/src/main/java/com/lighthouse/domain/model/UsageHistory.kt index cd0916fb1..b02df2236 100644 --- a/domain/src/main/java/com/lighthouse/domain/model/UsageHistory.kt +++ b/domain/src/main/java/com/lighthouse/domain/model/UsageHistory.kt @@ -1,9 +1,10 @@ package com.lighthouse.domain.model +import com.lighthouse.domain.VertexLocation import java.util.Date data class UsageHistory( val date: Date, - val address: String, + val location: VertexLocation?, val amount: Int ) diff --git a/domain/src/main/java/com/lighthouse/domain/usecase/UseCashCardGifticonUseCase.kt b/domain/src/main/java/com/lighthouse/domain/usecase/UseCashCardGifticonUseCase.kt index 3a6d2eb20..a492b584e 100644 --- a/domain/src/main/java/com/lighthouse/domain/usecase/UseCashCardGifticonUseCase.kt +++ b/domain/src/main/java/com/lighthouse/domain/usecase/UseCashCardGifticonUseCase.kt @@ -3,14 +3,18 @@ package com.lighthouse.domain.usecase import com.lighthouse.domain.model.UsageHistory import com.lighthouse.domain.repository.GifticonRepository import com.lighthouse.domain.util.currentTime +import kotlinx.coroutines.flow.first import javax.inject.Inject class UseCashCardGifticonUseCase @Inject constructor( - private val gifticonRepository: GifticonRepository + private val gifticonRepository: GifticonRepository, + private val getUserLocationUseCase: GetUserLocationUseCase ) { - suspend operator fun invoke(gifticonId: String, amount: Int) { - val usageHistory = UsageHistory(currentTime, "광주 광산구 일곡동", amount) // TODO 위치 얻어오기 + suspend operator fun invoke(gifticonId: String, amount: Int, hasLocationPermission: Boolean) { + val userLocation = if (hasLocationPermission) getUserLocationUseCase().first() else null + val usageHistory = UsageHistory(currentTime, userLocation, amount) + gifticonRepository.useCashCardGifticon(gifticonId, amount, usageHistory) } } diff --git a/domain/src/main/java/com/lighthouse/domain/usecase/UseGifticonUseCase.kt b/domain/src/main/java/com/lighthouse/domain/usecase/UseGifticonUseCase.kt index 47116706b..2857d2d9e 100644 --- a/domain/src/main/java/com/lighthouse/domain/usecase/UseGifticonUseCase.kt +++ b/domain/src/main/java/com/lighthouse/domain/usecase/UseGifticonUseCase.kt @@ -3,13 +3,16 @@ package com.lighthouse.domain.usecase import com.lighthouse.domain.model.UsageHistory import com.lighthouse.domain.repository.GifticonRepository import com.lighthouse.domain.util.currentTime +import kotlinx.coroutines.flow.first import javax.inject.Inject class UseGifticonUseCase @Inject constructor( - private val gifticonRepository: GifticonRepository + private val gifticonRepository: GifticonRepository, + private val getUserLocationUseCase: GetUserLocationUseCase ) { - suspend operator fun invoke(gifticonId: String) { - val usageHistory = UsageHistory(currentTime, "광주 광산구 일곡동", 0) // TODO 위치 얻어오기 + suspend operator fun invoke(gifticonId: String, hasLocationPermission: Boolean) { + val userLocation = if (hasLocationPermission) getUserLocationUseCase().first() else null + val usageHistory = UsageHistory(currentTime, userLocation, 0) gifticonRepository.useGifticon(gifticonId, usageHistory) } diff --git a/presentation/src/main/java/com/lighthouse/presentation/ui/detailgifticon/CashCardGifticonInfoFragment.kt b/presentation/src/main/java/com/lighthouse/presentation/ui/detailgifticon/CashCardGifticonInfoFragment.kt index 1a6b03a50..1a9748f46 100644 --- a/presentation/src/main/java/com/lighthouse/presentation/ui/detailgifticon/CashCardGifticonInfoFragment.kt +++ b/presentation/src/main/java/com/lighthouse/presentation/ui/detailgifticon/CashCardGifticonInfoFragment.kt @@ -7,6 +7,7 @@ import androidx.fragment.app.activityViewModels import com.lighthouse.presentation.R import com.lighthouse.presentation.databinding.FragmentCashCardGifticonInfoBinding import com.lighthouse.presentation.ui.common.viewBindings +import com.lighthouse.presentation.util.Geography class CashCardGifticonInfoFragment : Fragment(R.layout.fragment_cash_card_gifticon_info) { val binding: FragmentCashCardGifticonInfoBinding by viewBindings() @@ -16,6 +17,7 @@ class CashCardGifticonInfoFragment : Fragment(R.layout.fragment_cash_card_giftic super.onViewCreated(view, savedInstanceState) binding.vm = viewModel + binding.geo = Geography(requireContext()) binding.lifecycleOwner = viewLifecycleOwner binding.ctfBalance.addOnValueListener { viewModel.editBalance(it) diff --git a/presentation/src/main/java/com/lighthouse/presentation/ui/detailgifticon/GifticonDetailActivity.kt b/presentation/src/main/java/com/lighthouse/presentation/ui/detailgifticon/GifticonDetailActivity.kt index c683ddce7..9ec1e74b5 100644 --- a/presentation/src/main/java/com/lighthouse/presentation/ui/detailgifticon/GifticonDetailActivity.kt +++ b/presentation/src/main/java/com/lighthouse/presentation/ui/detailgifticon/GifticonDetailActivity.kt @@ -32,9 +32,12 @@ import com.lighthouse.presentation.ui.detailgifticon.dialog.UseGifticonDialog import com.lighthouse.presentation.ui.edit.modifygifticon.ModifyGifticonActivity import com.lighthouse.presentation.ui.security.AuthCallback import com.lighthouse.presentation.ui.security.AuthManager +import com.lighthouse.presentation.util.permission.LocationPermissionManager +import com.lighthouse.presentation.util.permission.core.permissions import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.cancel import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch import javax.inject.Inject @@ -63,6 +66,8 @@ class GifticonDetailActivity : AppCompatActivity() { private val chip by lazy { binding.chipScrollDownForUseButton } private val spinnerDatePicker = SpinnerDatePicker() + private val locationPermission: LocationPermissionManager by permissions() + @Inject lateinit var authManager: AuthManager private val biometricLauncher: ActivityResultLauncher = @@ -104,6 +109,11 @@ class GifticonDetailActivity : AppCompatActivity() { chip.isVisible = btnMaster.isOnScreen().not() } } + repeatOnStarted { + locationPermission.permissionFlow.collectLatest { + viewModel.updateLocationPermission(it) + } + } repeatOnStarted { viewModel.event.collect { event -> handleEvent(event) diff --git a/presentation/src/main/java/com/lighthouse/presentation/ui/detailgifticon/GifticonDetailViewModel.kt b/presentation/src/main/java/com/lighthouse/presentation/ui/detailgifticon/GifticonDetailViewModel.kt index 281cc66d3..e7f77c013 100644 --- a/presentation/src/main/java/com/lighthouse/presentation/ui/detailgifticon/GifticonDetailViewModel.kt +++ b/presentation/src/main/java/com/lighthouse/presentation/ui/detailgifticon/GifticonDetailViewModel.kt @@ -106,6 +106,9 @@ class GifticonDetailViewModel @Inject constructor( private val _masterButtonLabel = MutableStateFlow(UIText.Empty) val masterButtonLabel = _masterButtonLabel.asStateFlow() + var hasLocationPermission = MutableStateFlow(false) + private set + fun scrollDownForUseButtonClicked() { event(GifticonDetailEvent.ScrollDownForUseButtonClicked) } @@ -150,11 +153,11 @@ class GifticonDetailViewModel @Inject constructor( viewModelScope.launch { if (gifticon.value?.isCashCard == true) { assert((gifticon.value?.balance ?: 0) >= amountToBeUsed.value) - useCashCardGifticonUseCase(gifticonId, amountToBeUsed.value) + useCashCardGifticonUseCase(gifticonId, amountToBeUsed.value, hasLocationPermission.value) amountToBeUsed.value = 0 event(GifticonDetailEvent.UseGifticonComplete) } else { - useGifticonUseCase(gifticonId) + useGifticonUseCase(gifticonId, hasLocationPermission.value) event(GifticonDetailEvent.UseGifticonComplete) } } @@ -190,6 +193,10 @@ class GifticonDetailViewModel @Inject constructor( } } + fun updateLocationPermission(isLocationPermission: Boolean) { + hasLocationPermission.value = isLocationPermission + } + private fun switchMode(mode: GifticonDetailMode) { _mode.value = mode _scrollDownChipLabel.value = when (_mode.value) { @@ -199,8 +206,8 @@ class GifticonDetailViewModel @Inject constructor( } _masterButtonLabel.value = when (_mode.value) { GifticonDetailMode.UNUSED -> UIText.StringResource(R.string.gifticon_detail_unused_mode_button_text) - GifticonDetailMode.EDIT -> UIText.StringResource(R.string.gifticon_detail_used_mode_button_text) - GifticonDetailMode.USED -> UIText.StringResource(R.string.gifticon_detail_edit_mode_button_text) + GifticonDetailMode.EDIT -> UIText.StringResource(R.string.gifticon_detail_edit_mode_button_text) + GifticonDetailMode.USED -> UIText.StringResource(R.string.gifticon_detail_used_mode_button_text) } } diff --git a/presentation/src/main/java/com/lighthouse/presentation/ui/detailgifticon/StandardGifticonInfoFragment.kt b/presentation/src/main/java/com/lighthouse/presentation/ui/detailgifticon/StandardGifticonInfoFragment.kt index 2fb6639df..35c66aa57 100644 --- a/presentation/src/main/java/com/lighthouse/presentation/ui/detailgifticon/StandardGifticonInfoFragment.kt +++ b/presentation/src/main/java/com/lighthouse/presentation/ui/detailgifticon/StandardGifticonInfoFragment.kt @@ -7,6 +7,7 @@ import androidx.fragment.app.activityViewModels import com.lighthouse.presentation.R import com.lighthouse.presentation.databinding.FragmentStandardGifticonInfoBinding import com.lighthouse.presentation.ui.common.viewBindings +import com.lighthouse.presentation.util.Geography class StandardGifticonInfoFragment : Fragment(R.layout.fragment_standard_gifticon_info) { private val binding: FragmentStandardGifticonInfoBinding by viewBindings() @@ -15,6 +16,7 @@ class StandardGifticonInfoFragment : Fragment(R.layout.fragment_standard_giftico override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) binding.vm = viewModel + binding.geo = Geography(requireContext()) binding.lifecycleOwner = viewLifecycleOwner } } diff --git a/presentation/src/main/java/com/lighthouse/presentation/ui/detailgifticon/dialog/UsageHistoryAdapter.kt b/presentation/src/main/java/com/lighthouse/presentation/ui/detailgifticon/dialog/UsageHistoryAdapter.kt index 7c5779586..575f5d4b2 100644 --- a/presentation/src/main/java/com/lighthouse/presentation/ui/detailgifticon/dialog/UsageHistoryAdapter.kt +++ b/presentation/src/main/java/com/lighthouse/presentation/ui/detailgifticon/dialog/UsageHistoryAdapter.kt @@ -9,12 +9,14 @@ import com.lighthouse.domain.model.UsageHistory import com.lighthouse.presentation.R import com.lighthouse.presentation.adapter.BindableListAdapter import com.lighthouse.presentation.databinding.ItemUsageHistoryBinding +import com.lighthouse.presentation.util.Geography class UsageHistoryAdapter : BindableListAdapter(diffUtil) { class UsageHistoryViewHolder(private val binding: ItemUsageHistoryBinding) : RecyclerView.ViewHolder(binding.root) { fun bind(usageHistory: UsageHistory) { + binding.geo = Geography(binding.root.context) binding.usage = usageHistory } } diff --git a/presentation/src/main/java/com/lighthouse/presentation/ui/gifticonlist/GifticonListViewModel.kt b/presentation/src/main/java/com/lighthouse/presentation/ui/gifticonlist/GifticonListViewModel.kt index 6ba70d7ae..e3f7cae23 100644 --- a/presentation/src/main/java/com/lighthouse/presentation/ui/gifticonlist/GifticonListViewModel.kt +++ b/presentation/src/main/java/com/lighthouse/presentation/ui/gifticonlist/GifticonListViewModel.kt @@ -143,7 +143,7 @@ class GifticonListViewModel @Inject constructor( fun completeUsage(gifticon: GifticonUIModel) { viewModelScope.launch { - useGifticonUseCase(gifticon.id) + useGifticonUseCase(gifticon.id, false) } } diff --git a/presentation/src/main/java/com/lighthouse/presentation/util/Geography.kt b/presentation/src/main/java/com/lighthouse/presentation/util/Geography.kt new file mode 100644 index 000000000..6dfe73656 --- /dev/null +++ b/presentation/src/main/java/com/lighthouse/presentation/util/Geography.kt @@ -0,0 +1,44 @@ +package com.lighthouse.presentation.util + +import android.content.Context +import android.location.Address +import android.location.Geocoder +import android.os.Build +import com.lighthouse.domain.VertexLocation +import dagger.hilt.android.qualifiers.ApplicationContext +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class Geography @Inject constructor(@ApplicationContext private val context: Context) { + + // 위도 경도로 주소 구하는 Reverse-GeoCoding + fun getAddress(position: VertexLocation?): String { + position ?: return "" + + val geoCoder = Geocoder(context) + var addr = "-" + + // GRPC 오류 대응 + try { + var location: Address? = null + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + geoCoder.getFromLocation(position.latitude, position.longitude, 1) { + location = it.first() + } + } else { + location = geoCoder.getFromLocation(position.latitude, position.longitude, 1)?.first() + } + addr = listOfNotNull( + location?.adminArea, + location?.subAdminArea, + location?.locality, + location?.subLocality, + location?.thoroughfare + ).joinToString(" ") + } catch (e: Exception) { + e.printStackTrace() + } + return addr + } +} diff --git a/presentation/src/main/res/layout/fragment_cash_card_gifticon_info.xml b/presentation/src/main/res/layout/fragment_cash_card_gifticon_info.xml index cc5cf48b3..564d74e46 100644 --- a/presentation/src/main/res/layout/fragment_cash_card_gifticon_info.xml +++ b/presentation/src/main/res/layout/fragment_cash_card_gifticon_info.xml @@ -9,6 +9,10 @@ name="vm" type="com.lighthouse.presentation.ui.detailgifticon.GifticonDetailViewModel" /> + + @@ -169,7 +173,7 @@ android:layout_marginTop="4dp" android:ellipsize="end" android:maxLines="1" - android:text="@{vm.latestUsageHistory.address}" + android:text="@{geo.getAddress(vm.latestUsageHistory.location)}" app:layout_constraintEnd_toStartOf="@id/iv_show_all_used_info_button" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/tv_used_address_label" diff --git a/presentation/src/main/res/layout/fragment_standard_gifticon_info.xml b/presentation/src/main/res/layout/fragment_standard_gifticon_info.xml index 582265bb7..f2579e068 100644 --- a/presentation/src/main/res/layout/fragment_standard_gifticon_info.xml +++ b/presentation/src/main/res/layout/fragment_standard_gifticon_info.xml @@ -9,6 +9,10 @@ name="vm" type="com.lighthouse.presentation.ui.detailgifticon.GifticonDetailViewModel" /> + + @@ -41,8 +45,8 @@ android:inputType="textAutoComplete" android:maxLines="1" android:minWidth="100dp" - android:paddingEnd="16dp" android:paddingStart="0dp" + android:paddingEnd="16dp" android:text="@{vm.tempGifticon.name ?? vm.gifticon.name}" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/tv_product_name_label" @@ -72,8 +76,8 @@ android:inputType="textAutoComplete" android:maxLines="1" android:minWidth="100dp" - android:paddingEnd="16dp" android:paddingStart="0dp" + android:paddingEnd="16dp" android:text="@{vm.tempGifticon.brand ?? vm.gifticon.brand}" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/tv_brand_label" @@ -105,8 +109,8 @@ android:enabled="@{vm.mode==mode.EDIT}" android:minWidth="100dp" android:onClick="@{() -> vm.expireDateClicked()}" - android:paddingEnd="16dp" android:paddingStart="0dp" + android:paddingEnd="16dp" app:dateFormat="@{vm.tempGifticon.expireAt ?? vm.gifticon.expireAt}" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/tv_expire_at_label" @@ -138,7 +142,7 @@ android:layout_marginTop="4dp" android:ellipsize="end" android:maxLines="1" - android:text="@{vm.latestUsageHistory.address}" + android:text="@{geo.getAddress(vm.latestUsageHistory.location)}" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/tv_used_address_label" diff --git a/presentation/src/main/res/layout/item_usage_history.xml b/presentation/src/main/res/layout/item_usage_history.xml index 23d354b9f..cc98a9a0e 100644 --- a/presentation/src/main/res/layout/item_usage_history.xml +++ b/presentation/src/main/res/layout/item_usage_history.xml @@ -7,6 +7,10 @@ + +