diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index a18c819af..ce9c833c1 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -87,7 +87,7 @@ android:exported="false" android:screenOrientation="portrait" android:windowSoftInputMode="adjustResize" /> - + = source.deleteChatSetting() - override suspend fun saveWoofSetting(isSet: Boolean): Result = source.saveWoofSetting(isSet) + override suspend fun savePlaygroundSetting(isSet: Boolean): Result = source.savePlaygroundSetting(isSet) - override suspend fun getWoofSetting(): Result = source.getWoofSetting() + override suspend fun getPlaygroundSetting(): Result = source.getPlaygroundSetting() - override suspend fun deleteWoofSetting(): Result = source.deleteWoofSetting() + override suspend fun deletePlaygroundSetting(): Result = source.deletePlaygroundSetting() } diff --git a/android/app/src/main/java/com/happy/friendogly/data/repository/PlaygroundRepositoryImpl.kt b/android/app/src/main/java/com/happy/friendogly/data/repository/PlaygroundRepositoryImpl.kt index 717b232e4..4ee6dbd58 100644 --- a/android/app/src/main/java/com/happy/friendogly/data/repository/PlaygroundRepositoryImpl.kt +++ b/android/app/src/main/java/com/happy/friendogly/data/repository/PlaygroundRepositoryImpl.kt @@ -74,14 +74,14 @@ class PlaygroundRepositoryImpl ) } - override suspend fun getFootprintMarkBtnInfo(): Result = - source.getFootprintMarkBtnInfo().mapCatching { dto -> + override suspend fun getPetExistence(): Result = + source.getPetExistence().mapCatching { dto -> dto.toDomain() } override suspend fun getPlaygrounds(): Result> = source - .getNearFootprints() + .getNearPlaygrounds() .mapCatching { dto -> dto.toDomain() } diff --git a/android/app/src/main/java/com/happy/friendogly/data/repository/RecentPetsRepositoryImpl.kt b/android/app/src/main/java/com/happy/friendogly/data/repository/RecentPetsRepositoryImpl.kt index 5bdac883d..d2dd7e84e 100644 --- a/android/app/src/main/java/com/happy/friendogly/data/repository/RecentPetsRepositoryImpl.kt +++ b/android/app/src/main/java/com/happy/friendogly/data/repository/RecentPetsRepositoryImpl.kt @@ -38,7 +38,8 @@ class RecentPetsRepositoryImpl ) override suspend fun insertRecentPet( - id: Long, + memberId: Long, + petId: Long, name: String, imgUrl: String, birthday: LocalDate, @@ -46,7 +47,8 @@ class RecentPetsRepositoryImpl sizeType: SizeType, ): DomainResult { return dataSource.insertRecentPet( - id = id, + memberId = memberId, + petId = petId, name = name, imgUrl = imgUrl, birthday = birthday, diff --git a/android/app/src/main/java/com/happy/friendogly/data/source/AlarmSettingDataSource.kt b/android/app/src/main/java/com/happy/friendogly/data/source/AlarmSettingDataSource.kt index 396e2a6b2..9a82d7e90 100644 --- a/android/app/src/main/java/com/happy/friendogly/data/source/AlarmSettingDataSource.kt +++ b/android/app/src/main/java/com/happy/friendogly/data/source/AlarmSettingDataSource.kt @@ -7,9 +7,9 @@ interface AlarmSettingDataSource { suspend fun deleteChatSetting(): Result - suspend fun saveWoofSetting(isSet: Boolean): Result + suspend fun savePlaygroundSetting(isSet: Boolean): Result - suspend fun getWoofSetting(): Result + suspend fun getPlaygroundSetting(): Result - suspend fun deleteWoofSetting(): Result + suspend fun deletePlaygroundSetting(): Result } diff --git a/android/app/src/main/java/com/happy/friendogly/data/source/PlaygroundDataSource.kt b/android/app/src/main/java/com/happy/friendogly/data/source/PlaygroundDataSource.kt index a26315379..02b7cccb6 100644 --- a/android/app/src/main/java/com/happy/friendogly/data/source/PlaygroundDataSource.kt +++ b/android/app/src/main/java/com/happy/friendogly/data/source/PlaygroundDataSource.kt @@ -17,9 +17,9 @@ interface PlaygroundDataSource { suspend fun patchPlaygroundArrival(request: PatchPlaygroundArrivalRequest): Result - suspend fun getFootprintMarkBtnInfo(): Result + suspend fun getPetExistence(): Result - suspend fun getNearFootprints(): Result> + suspend fun getNearPlaygrounds(): Result> suspend fun getPlaygroundInfo(id: Long): Result diff --git a/android/app/src/main/java/com/happy/friendogly/data/source/RecentPetsDataSource.kt b/android/app/src/main/java/com/happy/friendogly/data/source/RecentPetsDataSource.kt index 1293a6d3b..2faa707bb 100644 --- a/android/app/src/main/java/com/happy/friendogly/data/source/RecentPetsDataSource.kt +++ b/android/app/src/main/java/com/happy/friendogly/data/source/RecentPetsDataSource.kt @@ -11,7 +11,8 @@ interface RecentPetsDataSource { suspend fun getAllRecentPet(): Result> suspend fun insertRecentPet( - id: Long, + memberId: Long, + petId: Long, name: String, imgUrl: String, birthday: LocalDate, diff --git a/android/app/src/main/java/com/happy/friendogly/domain/repository/AlarmSettingRepository.kt b/android/app/src/main/java/com/happy/friendogly/domain/repository/AlarmSettingRepository.kt index 60d9e63b8..8540362f1 100644 --- a/android/app/src/main/java/com/happy/friendogly/domain/repository/AlarmSettingRepository.kt +++ b/android/app/src/main/java/com/happy/friendogly/domain/repository/AlarmSettingRepository.kt @@ -7,9 +7,9 @@ interface AlarmSettingRepository { suspend fun deleteChatSetting(): Result - suspend fun saveWoofSetting(isSet: Boolean): Result + suspend fun savePlaygroundSetting(isSet: Boolean): Result - suspend fun getWoofSetting(): Result + suspend fun getPlaygroundSetting(): Result - suspend fun deleteWoofSetting(): Result + suspend fun deletePlaygroundSetting(): Result } diff --git a/android/app/src/main/java/com/happy/friendogly/domain/repository/PlaygroundRepository.kt b/android/app/src/main/java/com/happy/friendogly/domain/repository/PlaygroundRepository.kt index de4d0769b..63fa70479 100644 --- a/android/app/src/main/java/com/happy/friendogly/domain/repository/PlaygroundRepository.kt +++ b/android/app/src/main/java/com/happy/friendogly/domain/repository/PlaygroundRepository.kt @@ -22,7 +22,7 @@ interface PlaygroundRepository { longitude: Double, ): DomainResult - suspend fun getFootprintMarkBtnInfo(): Result + suspend fun getPetExistence(): Result suspend fun getPlaygrounds(): Result> diff --git a/android/app/src/main/java/com/happy/friendogly/domain/repository/RecentPetsRepository.kt b/android/app/src/main/java/com/happy/friendogly/domain/repository/RecentPetsRepository.kt index 31d4c820b..19e08f739 100644 --- a/android/app/src/main/java/com/happy/friendogly/domain/repository/RecentPetsRepository.kt +++ b/android/app/src/main/java/com/happy/friendogly/domain/repository/RecentPetsRepository.kt @@ -13,7 +13,8 @@ interface RecentPetsRepository { suspend fun getAllRecentPet(): DomainResult, DataError.Local> suspend fun insertRecentPet( - id: Long, + memberId: Long, + petId: Long, name: String, imgUrl: String, birthday: LocalDate, diff --git a/android/app/src/main/java/com/happy/friendogly/domain/usecase/DeleteWoofAlarmUseCase.kt b/android/app/src/main/java/com/happy/friendogly/domain/usecase/DeletePlaygroundAlarmUseCase.kt similarity index 83% rename from android/app/src/main/java/com/happy/friendogly/domain/usecase/DeleteWoofAlarmUseCase.kt rename to android/app/src/main/java/com/happy/friendogly/domain/usecase/DeletePlaygroundAlarmUseCase.kt index 92a6936b6..7fadd6f93 100644 --- a/android/app/src/main/java/com/happy/friendogly/domain/usecase/DeleteWoofAlarmUseCase.kt +++ b/android/app/src/main/java/com/happy/friendogly/domain/usecase/DeletePlaygroundAlarmUseCase.kt @@ -3,10 +3,10 @@ package com.happy.friendogly.domain.usecase import com.happy.friendogly.domain.repository.AlarmSettingRepository import javax.inject.Inject -class DeleteWoofAlarmUseCase +class DeletePlaygroundAlarmUseCase @Inject constructor( private val repository: AlarmSettingRepository, ) { - suspend operator fun invoke(): Result = repository.deleteWoofSetting() + suspend operator fun invoke(): Result = repository.deletePlaygroundSetting() } diff --git a/android/app/src/main/java/com/happy/friendogly/domain/usecase/GetPetExistenceUseCase.kt b/android/app/src/main/java/com/happy/friendogly/domain/usecase/GetPetExistenceUseCase.kt index 4a914dc02..75d001a93 100644 --- a/android/app/src/main/java/com/happy/friendogly/domain/usecase/GetPetExistenceUseCase.kt +++ b/android/app/src/main/java/com/happy/friendogly/domain/usecase/GetPetExistenceUseCase.kt @@ -7,5 +7,5 @@ import javax.inject.Inject class GetPetExistenceUseCase @Inject constructor(private val repository: PlaygroundRepository) { - suspend operator fun invoke(): Result = repository.getFootprintMarkBtnInfo() + suspend operator fun invoke(): Result = repository.getPetExistence() } diff --git a/android/app/src/main/java/com/happy/friendogly/domain/usecase/GetWoofAlarmUseCase.kt b/android/app/src/main/java/com/happy/friendogly/domain/usecase/GetPlaygroundAlarmUseCase.kt similarity index 83% rename from android/app/src/main/java/com/happy/friendogly/domain/usecase/GetWoofAlarmUseCase.kt rename to android/app/src/main/java/com/happy/friendogly/domain/usecase/GetPlaygroundAlarmUseCase.kt index c15255f46..cdf6ce052 100644 --- a/android/app/src/main/java/com/happy/friendogly/domain/usecase/GetWoofAlarmUseCase.kt +++ b/android/app/src/main/java/com/happy/friendogly/domain/usecase/GetPlaygroundAlarmUseCase.kt @@ -3,10 +3,10 @@ package com.happy.friendogly.domain.usecase import com.happy.friendogly.domain.repository.AlarmSettingRepository import javax.inject.Inject -class GetWoofAlarmUseCase +class GetPlaygroundAlarmUseCase @Inject constructor( private val repository: AlarmSettingRepository, ) { - suspend operator fun invoke(): Result = repository.getWoofSetting() + suspend operator fun invoke(): Result = repository.getPlaygroundSetting() } diff --git a/android/app/src/main/java/com/happy/friendogly/domain/usecase/InsertRecentPetUseCase.kt b/android/app/src/main/java/com/happy/friendogly/domain/usecase/InsertRecentPetUseCase.kt index f852b5641..2c1d978ed 100644 --- a/android/app/src/main/java/com/happy/friendogly/domain/usecase/InsertRecentPetUseCase.kt +++ b/android/app/src/main/java/com/happy/friendogly/domain/usecase/InsertRecentPetUseCase.kt @@ -14,7 +14,8 @@ class InsertRecentPetUseCase val repository: RecentPetsRepository, ) { suspend operator fun invoke( - id: Long, + memberId: Long, + petId: Long, name: String, imgUrl: String, birthday: LocalDate, @@ -22,9 +23,10 @@ class InsertRecentPetUseCase sizeType: SizeType, ): DomainResult = repository.insertRecentPet( + memberId = memberId, + petId = petId, imgUrl = imgUrl, name = name, - id = id, birthday = birthday, gender = gender, sizeType = sizeType, diff --git a/android/app/src/main/java/com/happy/friendogly/domain/usecase/SaveWoofAlarmUseCase.kt b/android/app/src/main/java/com/happy/friendogly/domain/usecase/SavePlaygroundAlarmUseCase.kt similarity index 79% rename from android/app/src/main/java/com/happy/friendogly/domain/usecase/SaveWoofAlarmUseCase.kt rename to android/app/src/main/java/com/happy/friendogly/domain/usecase/SavePlaygroundAlarmUseCase.kt index d267e1fff..8941b906e 100644 --- a/android/app/src/main/java/com/happy/friendogly/domain/usecase/SaveWoofAlarmUseCase.kt +++ b/android/app/src/main/java/com/happy/friendogly/domain/usecase/SavePlaygroundAlarmUseCase.kt @@ -3,10 +3,10 @@ package com.happy.friendogly.domain.usecase import com.happy.friendogly.domain.repository.AlarmSettingRepository import javax.inject.Inject -class SaveWoofAlarmUseCase +class SavePlaygroundAlarmUseCase @Inject constructor( private val repository: AlarmSettingRepository, ) { - suspend operator fun invoke(isSet: Boolean): Result = repository.saveWoofSetting(isSet) + suspend operator fun invoke(isSet: Boolean): Result = repository.savePlaygroundSetting(isSet) } diff --git a/android/app/src/main/java/com/happy/friendogly/firebase/analytics/Types.kt b/android/app/src/main/java/com/happy/friendogly/firebase/analytics/Types.kt index eb1b09342..b487aa666 100644 --- a/android/app/src/main/java/com/happy/friendogly/firebase/analytics/Types.kt +++ b/android/app/src/main/java/com/happy/friendogly/firebase/analytics/Types.kt @@ -30,7 +30,7 @@ object Types { const val KAKAO_LOGIN_CLICKED = "kakao_login_clicked" const val GOOGLE_LOGIN_CLICKED = "google_login_clicked" const val GROUP_LIST_FRAGMENT = "group_list_fragment" - const val WOOF_FRAGMENT = "woof_fragment" + const val PLAYGROUND_FRAGMENT = "playground_fragment" const val CHAT_LIST_FRAGMENT = "chat_list_fragment" const val MY_PAGE_FRAGMENT = "my_page_fragment" const val CLUB_LIST_FRAGMENT_SWITCHED = "group_list_fragment_switched" @@ -41,12 +41,17 @@ object Types { const val REGISTER_MARKER_BTN_CLICKED = "register_marker_btn_clicked" const val LOCATION_BTN_CLICKED = "location_btn_clicked" const val MY_PLAYGROUND_BTN_CLICKED = "my_playground_btn_clicked" - const val REFRESH_BTN_CLICKED = "refresh_btn_clicked" + const val PLAYGROUND_REFRESH_BTN_CLICKED = "playground_refresh_btn_clicked" + const val PLAYGROUND_INFO_REFRESH_BTN_CLICKED = "playground_info_refresh_btn_clicked" const val BACK_BTN_CLICKED = "back_btn_clicked" const val CLOSE_BTN_CLICKED = "close_btn_clicked" const val PLAYGROUND_PET_DETAIL_CLICKED = "playground_pet_detail_clicked" const val HELP_BTN_CLICKED = "help_btn_clicked" const val PET_IMAGE_CLICKED = "pet_image_clicked" + const val STATE_MESSAGE_CLICKED = "state_message_clicked" + const val JOIN_PLAYGROUND_CLICKED = "join_playground_clicked" + const val LEAVE_PLAYGROUND_CLICKED = "leave_playground_clicked" + const val CHECK_PET_EXISTENCE_CLICKED = "check_pet_existence_clicked" const val CLUB_SELECT_PARTICIPATION_FILTER = "club_select_participation_filter" const val CLUB_SELECT_CLUB_FILTER = "club_select_club_filter" const val CLUB_ADD_UN_SELECT_FILTER = "club_add_un_select_filter" diff --git a/android/app/src/main/java/com/happy/friendogly/local/dao/RecentPetsDao.kt b/android/app/src/main/java/com/happy/friendogly/local/dao/RecentPetsDao.kt index 9b7381a2a..1b45ffe25 100644 --- a/android/app/src/main/java/com/happy/friendogly/local/dao/RecentPetsDao.kt +++ b/android/app/src/main/java/com/happy/friendogly/local/dao/RecentPetsDao.kt @@ -3,16 +3,30 @@ package com.happy.friendogly.local.dao import androidx.room.Dao import androidx.room.Insert import androidx.room.Query +import androidx.room.Transaction import com.happy.friendogly.local.model.RecentPetEntity +import java.time.LocalDateTime @Dao interface RecentPetsDao { @Query("SELECT * FROM recent_pet WHERE memberId = :id") suspend fun getRecentPetById(id: Long): RecentPetEntity? - @Query("SELECT * FROM recent_pet") + @Query("SELECT * FROM recent_pet ORDER BY created_at DESC") suspend fun getAllRecentPet(): List + @Query("DELETE FROM recent_pet WHERE petId = :petId AND DATE(created_at) = DATE(:createdAt)") + suspend fun deleteByPetIdAndCreatedAt( + petId: Long, + createdAt: LocalDateTime, + ) + @Insert suspend fun insertRecentPet(recentPetEntity: RecentPetEntity) + + @Transaction + suspend fun insertOrUpdateRecentPet(recentPetEntity: RecentPetEntity) { + deleteByPetIdAndCreatedAt(recentPetEntity.petId, recentPetEntity.createdAt) + insertRecentPet(recentPetEntity) + } } diff --git a/android/app/src/main/java/com/happy/friendogly/local/di/WoofAlarmModule.kt b/android/app/src/main/java/com/happy/friendogly/local/di/PlaygroundAlarmModule.kt similarity index 87% rename from android/app/src/main/java/com/happy/friendogly/local/di/WoofAlarmModule.kt rename to android/app/src/main/java/com/happy/friendogly/local/di/PlaygroundAlarmModule.kt index 466311479..45c64b6e5 100644 --- a/android/app/src/main/java/com/happy/friendogly/local/di/WoofAlarmModule.kt +++ b/android/app/src/main/java/com/happy/friendogly/local/di/PlaygroundAlarmModule.kt @@ -12,7 +12,7 @@ import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.map import java.io.IOException -class WoofAlarmModule(val context: Context) { +class PlaygroundAlarmModule(val context: Context) { private val Context.dataStore: DataStore by preferencesDataStore(name = DATA_STORE_NAME) private val key = booleanPreferencesKey(ALARM_SETTING) @@ -41,7 +41,7 @@ class WoofAlarmModule(val context: Context) { } companion object { - private const val ALARM_SETTING = "WOOF_ALARM_SETTING" - private const val DATA_STORE_NAME = "woofAlarmDataStore" + private const val ALARM_SETTING = "PLAYGROUND_ALARM_SETTING" + private const val DATA_STORE_NAME = "playgroundAlarmDataStore" } } diff --git a/android/app/src/main/java/com/happy/friendogly/local/mapper/RecentPetEntityMapper.kt b/android/app/src/main/java/com/happy/friendogly/local/mapper/RecentPetEntityMapper.kt index 8afb96676..6f3cffdc2 100644 --- a/android/app/src/main/java/com/happy/friendogly/local/mapper/RecentPetEntityMapper.kt +++ b/android/app/src/main/java/com/happy/friendogly/local/mapper/RecentPetEntityMapper.kt @@ -5,7 +5,8 @@ import com.happy.friendogly.local.model.RecentPetEntity fun RecentPetEntity.toData(): RecentPetDto = RecentPetDto( - memberId = id, + memberId = memberId, + petId = petId, name = name, imgUrl = imgUrl, birthday = birthday, diff --git a/android/app/src/main/java/com/happy/friendogly/local/model/RecentPetEntity.kt b/android/app/src/main/java/com/happy/friendogly/local/model/RecentPetEntity.kt index 277370fae..930e979de 100644 --- a/android/app/src/main/java/com/happy/friendogly/local/model/RecentPetEntity.kt +++ b/android/app/src/main/java/com/happy/friendogly/local/model/RecentPetEntity.kt @@ -14,6 +14,8 @@ import java.time.LocalDateTime data class RecentPetEntity( @ColumnInfo(name = "memberId") val memberId: Long, + @ColumnInfo(name = "petId") + val petId: Long, @ColumnInfo(name = "name") val name: String, @ColumnInfo(name = "imgUrl") diff --git a/android/app/src/main/java/com/happy/friendogly/local/source/AlarmSettingDataSourceImpl.kt b/android/app/src/main/java/com/happy/friendogly/local/source/AlarmSettingDataSourceImpl.kt index a68874721..a3dc6c3e3 100644 --- a/android/app/src/main/java/com/happy/friendogly/local/source/AlarmSettingDataSourceImpl.kt +++ b/android/app/src/main/java/com/happy/friendogly/local/source/AlarmSettingDataSourceImpl.kt @@ -2,7 +2,7 @@ package com.happy.friendogly.local.source import com.happy.friendogly.data.source.AlarmSettingDataSource import com.happy.friendogly.local.di.ChatAlarmModule -import com.happy.friendogly.local.di.WoofAlarmModule +import com.happy.friendogly.local.di.PlaygroundAlarmModule import kotlinx.coroutines.flow.first import javax.inject.Inject @@ -10,7 +10,7 @@ class AlarmSettingDataSourceImpl @Inject constructor( private val chatAlarmModule: ChatAlarmModule, - private val woofAlarmModule: WoofAlarmModule, + private val playgroundAlarmModule: PlaygroundAlarmModule, ) : AlarmSettingDataSource { override suspend fun saveChatSetting(isSet: Boolean): Result = runCatching { @@ -27,18 +27,18 @@ class AlarmSettingDataSourceImpl chatAlarmModule.deleteSetting() } - override suspend fun saveWoofSetting(isSet: Boolean): Result = + override suspend fun savePlaygroundSetting(isSet: Boolean): Result = runCatching { - woofAlarmModule.saveSetting(isSet) + playgroundAlarmModule.saveSetting(isSet) } - override suspend fun getWoofSetting(): Result = + override suspend fun getPlaygroundSetting(): Result = runCatching { - woofAlarmModule.isSet.first() + playgroundAlarmModule.isSet.first() } - override suspend fun deleteWoofSetting(): Result = + override suspend fun deletePlaygroundSetting(): Result = runCatching { - woofAlarmModule.deleteSetting() + playgroundAlarmModule.deleteSetting() } } diff --git a/android/app/src/main/java/com/happy/friendogly/local/source/RecentPetsDataSourceImpl.kt b/android/app/src/main/java/com/happy/friendogly/local/source/RecentPetsDataSourceImpl.kt index ac7dd36fd..f0cd23442 100644 --- a/android/app/src/main/java/com/happy/friendogly/local/source/RecentPetsDataSourceImpl.kt +++ b/android/app/src/main/java/com/happy/friendogly/local/source/RecentPetsDataSourceImpl.kt @@ -28,7 +28,8 @@ class RecentPetsDataSourceImpl } override suspend fun insertRecentPet( - id: Long, + memberId: Long, + petId: Long, name: String, imgUrl: String, birthday: LocalDate, @@ -38,7 +39,8 @@ class RecentPetsDataSourceImpl runCatching { val recentPetDto = RecentPetDto( - memberId = id, + memberId = memberId, + petId = petId, imgUrl = imgUrl, name = name, birthday = birthday, @@ -46,6 +48,6 @@ class RecentPetsDataSourceImpl sizeType = sizeType, createAt = LocalDateTime.now(), ) - dao.insertRecentPet(recentPetEntity = recentPetDto.toLocal()) + dao.insertOrUpdateRecentPet(recentPetEntity = recentPetDto.toLocal()) } } diff --git a/android/app/src/main/java/com/happy/friendogly/presentation/alarm/AlarmReceiver.kt b/android/app/src/main/java/com/happy/friendogly/presentation/alarm/AlarmReceiver.kt index c8dfb120a..c99a2a32f 100644 --- a/android/app/src/main/java/com/happy/friendogly/presentation/alarm/AlarmReceiver.kt +++ b/android/app/src/main/java/com/happy/friendogly/presentation/alarm/AlarmReceiver.kt @@ -21,7 +21,7 @@ import com.google.firebase.messaging.FirebaseMessagingService import com.google.firebase.messaging.RemoteMessage import com.happy.friendogly.R import com.happy.friendogly.domain.usecase.GetChatAlarmUseCase -import com.happy.friendogly.domain.usecase.GetWoofAlarmUseCase +import com.happy.friendogly.domain.usecase.GetPlaygroundAlarmUseCase import com.happy.friendogly.presentation.ui.MainActivity import com.happy.friendogly.presentation.ui.MainActivity.Companion.EXTRA_FRAGMENT import com.happy.friendogly.presentation.ui.chatlist.chat.ChatActivity @@ -39,24 +39,24 @@ class AlarmReceiver : FirebaseMessagingService() { private lateinit var notificationManager: NotificationManager @Inject - lateinit var getWoofAlarmUseCase: GetWoofAlarmUseCase + lateinit var getPlaygroundAlarmUseCase: GetPlaygroundAlarmUseCase @Inject lateinit var getChatAlarmUseCase: GetChatAlarmUseCase - @RequiresApi(Build.VERSION_CODES.Q) + @RequiresApi(Build.VERSION_CODES.R) override fun onMessageReceived(message: RemoteMessage) { super.onMessageReceived(message) if (message.data[ALARM_TYPE] == "CHAT") { showChatAlarm( message, ) - } else if (message.data[ALARM_TYPE] == "FOOTPRINT") { - showWoofAlarm(message.data[ALARM_TITLE], message.data[ALARM_BODY]) + } else if (message.data[ALARM_TYPE] == "PLAYGROUND") { + showPlaygroundAlarm(message.data[ALARM_TITLE], message.data[ALARM_BODY]) } } - @RequiresApi(Build.VERSION_CODES.Q) + @RequiresApi(Build.VERSION_CODES.R) private fun showChatAlarm(message: RemoteMessage) = CoroutineScope(Dispatchers.IO).launch { notificationManager = @@ -80,15 +80,15 @@ class AlarmReceiver : FirebaseMessagingService() { private fun Map.getValue(key: String): String = this[key] ?: throw IllegalArgumentException("채팅 알림 데이터에 $key 에 관한 값이 없습니다.") - private fun showWoofAlarm( + private fun showPlaygroundAlarm( title: String?, body: String?, ) = CoroutineScope(Dispatchers.IO).launch { notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager - if (getWoofAlarmUseCase.invoke().getOrDefault(true)) { - createNotificationChannel(AlarmType.WOOF) - deliverWoofNotification(title, body) + if (getPlaygroundAlarmUseCase().getOrDefault(true)) { + createNotificationChannel(AlarmType.PLAYGROUND) + deliverPlaygroundNotification(title, body) } } @@ -273,7 +273,7 @@ class AlarmReceiver : FirebaseMessagingService() { return round.toBitmap() } - private fun deliverWoofNotification( + private fun deliverPlaygroundNotification( title: String?, body: String?, ) { @@ -291,7 +291,7 @@ class AlarmReceiver : FirebaseMessagingService() { ) val builder = - NotificationCompat.Builder(this, WOOF_CHANNEL_ID) + NotificationCompat.Builder(this, PLAYGROUND_CHANNEL_ID) .setSmallIcon(R.mipmap.ic_launcher) .setContentTitle(title) .setContentText(body) @@ -305,27 +305,27 @@ class AlarmReceiver : FirebaseMessagingService() { enum class AlarmType { CHAT, - WOOF, + PLAYGROUND, ; fun channelId(): String = when (this) { CHAT -> CHAT_CHANNEL_ID - WOOF -> WOOF_CHANNEL_ID + PLAYGROUND -> PLAYGROUND_CHANNEL_ID } fun channelName(): String = when (this) { CHAT -> CHAT_CHANNEL_NAME - WOOF -> WOOF_CHANNEL_NAME + PLAYGROUND -> PLAYGROUND_CHANNEL_NAME } } companion object { private const val CHAT_CHANNEL_ID = "chat_channel" private const val CHAT_CHANNEL_NAME = "채팅" - private const val WOOF_CHANNEL_ID = "woof_channel" - private const val WOOF_CHANNEL_NAME = "친구 찾기" + private const val PLAYGROUND_CHANNEL_ID = "playground_channel" + private const val PLAYGROUND_CHANNEL_NAME = "놀이터" private const val ALARM_TITLE = "title" private const val ALARM_BODY = "body" private const val ALARM_TYPE = "type" diff --git a/android/app/src/main/java/com/happy/friendogly/presentation/ui/club/list/adapter/club/ClubViewHolder.kt b/android/app/src/main/java/com/happy/friendogly/presentation/ui/club/list/adapter/club/ClubViewHolder.kt index 2b8f0318a..4d4e92b8f 100644 --- a/android/app/src/main/java/com/happy/friendogly/presentation/ui/club/list/adapter/club/ClubViewHolder.kt +++ b/android/app/src/main/java/com/happy/friendogly/presentation/ui/club/list/adapter/club/ClubViewHolder.kt @@ -9,10 +9,10 @@ import com.happy.friendogly.presentation.ui.club.list.adapter.pet.ClubPetAdapter class ClubViewHolder( private val binding: ItemClubBinding, ) : RecyclerView.ViewHolder(binding.root) { - private val woofAdapter = ClubPetAdapter() + private val clubPetAdapter = ClubPetAdapter() init { - binding.rcvClubListDogList.adapter = woofAdapter + binding.rcvClubListDogList.adapter = clubPetAdapter } fun bind( @@ -21,6 +21,6 @@ class ClubViewHolder( ) { binding.club = clubItemUiModel binding.actionHandler = actionHandler - woofAdapter.submitList(clubItemUiModel.clubPets) + clubPetAdapter.submitList(clubItemUiModel.clubPets) } } diff --git a/android/app/src/main/java/com/happy/friendogly/presentation/ui/message/action/MessageActionHandler.kt b/android/app/src/main/java/com/happy/friendogly/presentation/ui/message/action/MessageActionHandler.kt deleted file mode 100644 index 96b9b9a99..000000000 --- a/android/app/src/main/java/com/happy/friendogly/presentation/ui/message/action/MessageActionHandler.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.happy.friendogly.presentation.ui.message.action - -interface MessageActionHandler { - fun clickCancelBtn() - - fun clickConfirmBtn() - - fun clearMessageBtn() -} diff --git a/android/app/src/main/java/com/happy/friendogly/presentation/ui/message/action/MessageAlertAction.kt b/android/app/src/main/java/com/happy/friendogly/presentation/ui/message/action/MessageAlertAction.kt deleted file mode 100644 index 78bda66ed..000000000 --- a/android/app/src/main/java/com/happy/friendogly/presentation/ui/message/action/MessageAlertAction.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.happy.friendogly.presentation.ui.message.action - -interface MessageAlertAction { - data object AlertFailToPatchPlaygroundMessage : MessageAlertAction -} diff --git a/android/app/src/main/java/com/happy/friendogly/presentation/ui/message/action/MessageNavigateAction.kt b/android/app/src/main/java/com/happy/friendogly/presentation/ui/message/action/MessageNavigateAction.kt deleted file mode 100644 index 4c702e9b0..000000000 --- a/android/app/src/main/java/com/happy/friendogly/presentation/ui/message/action/MessageNavigateAction.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.happy.friendogly.presentation.ui.message.action - -interface MessageNavigateAction { - data class FinishMessageActivity(val messageUpdated: Boolean) : MessageNavigateAction -} diff --git a/android/app/src/main/java/com/happy/friendogly/presentation/ui/playground/PlaygroundFragment.kt b/android/app/src/main/java/com/happy/friendogly/presentation/ui/playground/PlaygroundFragment.kt index 99ab79e8b..2f31fd299 100644 --- a/android/app/src/main/java/com/happy/friendogly/presentation/ui/playground/PlaygroundFragment.kt +++ b/android/app/src/main/java/com/happy/friendogly/presentation/ui/playground/PlaygroundFragment.kt @@ -2,7 +2,7 @@ package com.happy.friendogly.presentation.ui.playground import android.app.Activity import android.content.Context -import android.content.Context.RECEIVER_NOT_EXPORTED +import android.content.Context.RECEIVER_EXPORTED import android.content.Intent import android.content.IntentFilter import android.graphics.Rect @@ -31,34 +31,59 @@ import com.happy.friendogly.R import com.happy.friendogly.databinding.FragmentPlaygroundBinding import com.happy.friendogly.firebase.analytics.AnalyticsHelper import com.happy.friendogly.presentation.base.observeEvent +import com.happy.friendogly.presentation.dialog.AlertDialogModel +import com.happy.friendogly.presentation.dialog.DefaultCoralAlertDialog import com.happy.friendogly.presentation.dialog.PetAddAlertDialog import com.happy.friendogly.presentation.ui.MainActivity.Companion.LOCATION_PERMISSION_REQUEST_CODE import com.happy.friendogly.presentation.ui.MainActivityActionHandler -import com.happy.friendogly.presentation.ui.message.MessageActivity import com.happy.friendogly.presentation.ui.otherprofile.OtherProfileActivity import com.happy.friendogly.presentation.ui.permission.LocationPermission import com.happy.friendogly.presentation.ui.petimage.PetImageActivity import com.happy.friendogly.presentation.ui.playground.action.PlaygroundAlertAction -import com.happy.friendogly.presentation.ui.playground.action.PlaygroundAlertAction.AlertEndWalkSnackbar +import com.happy.friendogly.presentation.ui.playground.action.PlaygroundAlertAction.AlertAddressOutOfKoreaSnackbar +import com.happy.friendogly.presentation.ui.playground.action.PlaygroundAlertAction.AlertAutoLeavePlaygroundSnackbar +import com.happy.friendogly.presentation.ui.playground.action.PlaygroundAlertAction.AlertFailToCheckPetExistence +import com.happy.friendogly.presentation.ui.playground.action.PlaygroundAlertAction.AlertFailToJoinPlaygroundSnackbar +import com.happy.friendogly.presentation.ui.playground.action.PlaygroundAlertAction.AlertFailToLeavePlaygroundSnackbar +import com.happy.friendogly.presentation.ui.playground.action.PlaygroundAlertAction.AlertFailToLoadPlaygroundInfoSnackbar +import com.happy.friendogly.presentation.ui.playground.action.PlaygroundAlertAction.AlertFailToLoadPlaygroundSummarySnackbar +import com.happy.friendogly.presentation.ui.playground.action.PlaygroundAlertAction.AlertFailToLoadPlaygroundsSnackbar +import com.happy.friendogly.presentation.ui.playground.action.PlaygroundAlertAction.AlertFailToRegisterPlaygroundSnackbar +import com.happy.friendogly.presentation.ui.playground.action.PlaygroundAlertAction.AlertFailToUpdatePlaygroundArrival +import com.happy.friendogly.presentation.ui.playground.action.PlaygroundAlertAction.AlertHasNotLocationPermissionDialog import com.happy.friendogly.presentation.ui.playground.action.PlaygroundAlertAction.AlertHasNotPetDialog -import com.happy.friendogly.presentation.ui.playground.action.PlaygroundAlertAction.AlertMarkerRegisteredSnackbar -import com.happy.friendogly.presentation.ui.playground.action.PlaygroundMapAction -import com.happy.friendogly.presentation.ui.playground.action.PlaygroundMapAction.MakeMyPlaygroundMarker -import com.happy.friendogly.presentation.ui.playground.action.PlaygroundMapAction.MakeNearPlaygroundMarkers -import com.happy.friendogly.presentation.ui.playground.action.PlaygroundNavigateAction -import com.happy.friendogly.presentation.ui.playground.action.PlaygroundTrackingModeAction.FaceTrackingMode -import com.happy.friendogly.presentation.ui.playground.action.PlaygroundTrackingModeAction.FollowTrackingMode -import com.happy.friendogly.presentation.ui.playground.action.PlaygroundTrackingModeAction.NoFollowTrackingMode +import com.happy.friendogly.presentation.ui.playground.action.PlaygroundAlertAction.AlertHelpBalloon +import com.happy.friendogly.presentation.ui.playground.action.PlaygroundAlertAction.AlertJoinPlaygroundSnackbar +import com.happy.friendogly.presentation.ui.playground.action.PlaygroundAlertAction.AlertLeaveAndJoinPlaygroundDialog +import com.happy.friendogly.presentation.ui.playground.action.PlaygroundAlertAction.AlertLeaveAndRegisterPlaygroundDialog +import com.happy.friendogly.presentation.ui.playground.action.PlaygroundAlertAction.AlertLeaveMyPlaygroundSnackbar +import com.happy.friendogly.presentation.ui.playground.action.PlaygroundAlertAction.AlertNotExistMyPlaygroundSnackbar +import com.happy.friendogly.presentation.ui.playground.action.PlaygroundAlertAction.AlertOverlapPlaygroundCreationSnackbar +import com.happy.friendogly.presentation.ui.playground.action.PlaygroundAlertAction.AlertPlaygroundRegisteredSnackbar +import com.happy.friendogly.presentation.ui.playground.action.PlaygroundMapAction.ChangeBottomSheetBehavior +import com.happy.friendogly.presentation.ui.playground.action.PlaygroundMapAction.ChangeTrackingMode +import com.happy.friendogly.presentation.ui.playground.action.PlaygroundMapAction.HideRegisteringPlaygroundScreen +import com.happy.friendogly.presentation.ui.playground.action.PlaygroundMapAction.MakePlaygrounds +import com.happy.friendogly.presentation.ui.playground.action.PlaygroundMapAction.MoveCameraCenterPosition +import com.happy.friendogly.presentation.ui.playground.action.PlaygroundMapAction.RegisterMyPlayground +import com.happy.friendogly.presentation.ui.playground.action.PlaygroundMapAction.ShowRegisteringPlaygroundScreen +import com.happy.friendogly.presentation.ui.playground.action.PlaygroundMapAction.StartLocationService +import com.happy.friendogly.presentation.ui.playground.action.PlaygroundNavigateAction.NavigateToOtherProfile +import com.happy.friendogly.presentation.ui.playground.action.PlaygroundNavigateAction.NavigateToPetImage +import com.happy.friendogly.presentation.ui.playground.action.PlaygroundNavigateAction.NavigateToStateMessage import com.happy.friendogly.presentation.ui.playground.adapter.PetDetailAdapter import com.happy.friendogly.presentation.ui.playground.adapter.PetSummaryAdapter -import com.happy.friendogly.presentation.ui.playground.model.PlayStatus import com.happy.friendogly.presentation.ui.playground.model.Playground import com.happy.friendogly.presentation.ui.playground.service.PlaygroundLocationReceiver import com.happy.friendogly.presentation.ui.playground.service.PlaygroundLocationService import com.happy.friendogly.presentation.ui.playground.service.PlaygroundLocationService.Companion.ACTION_START import com.happy.friendogly.presentation.ui.playground.state.PlaygroundUiState -import com.happy.friendogly.presentation.ui.playground.uimodel.PlaygroundMarkerUiModel +import com.happy.friendogly.presentation.ui.playground.uimodel.PlaygroundUiModel +import com.happy.friendogly.presentation.ui.playground.util.ANIMATE_DURATION_MILLIS +import com.happy.friendogly.presentation.ui.playground.util.hideViewAnimation +import com.happy.friendogly.presentation.ui.playground.util.showViewAnimation import com.happy.friendogly.presentation.ui.playground.viewmodel.PlaygroundViewModel +import com.happy.friendogly.presentation.ui.statemessage.StateMessageActivity import com.happy.friendogly.presentation.utils.isSystemInDarkMode import com.naver.maps.geometry.LatLng import com.naver.maps.geometry.LatLngBounds @@ -86,9 +111,10 @@ import kotlin.math.floor import kotlin.math.sin @AndroidEntryPoint -class PlaygroundFragment : - Fragment(), - OnMapReadyCallback { +class PlaygroundFragment : Fragment(), OnMapReadyCallback { + @Inject + lateinit var analyticsHelper: AnalyticsHelper + private var _binding: FragmentPlaygroundBinding? = null private val binding get() = _binding!! private var snackbar: Snackbar? = null @@ -102,8 +128,6 @@ class PlaygroundFragment : private lateinit var activityResultLauncher: ActivityResultLauncher private val mapView: MapView by lazy { binding.mapView } - private val registeringCircleOverlay: CircleOverlay by lazy { CircleOverlay() } - private val pathOverlay: PathOverlay by lazy { PathOverlay() } private val locationSource: FusedLocationSource by lazy { FusedLocationSource( this, @@ -121,18 +145,22 @@ class PlaygroundFragment : private val viewModel by viewModels() - @Inject - lateinit var analyticsHelper: AnalyticsHelper - override fun onAttach(context: Context) { super.onAttach(context) onBackPressedCallback = object : OnBackPressedCallback(true) { override fun handleOnBackPressed() { - if (viewModel.uiState.value is PlaygroundUiState.FindingPlayground) { - requireActivity().finish() + val currentState = viewModel.uiState.value + if (currentState is PlaygroundUiState.RegisteringPlayground) { + balloon?.dismiss() + currentState.circleOverlay.map = null + binding.layoutPlaygroundRegister.hideViewAnimation() + } + + if (currentState !is PlaygroundUiState.FindingPlayground) { + viewModel.updateUiState(PlaygroundUiState.FindingPlayground()) } else { - viewModel.updateUiState(PlaygroundUiState.FindingPlayground) + requireActivity().finish() } } } @@ -161,6 +189,7 @@ class PlaygroundFragment : setupRecyclerView() setupBottomSheetBehavior() setupActivityResultLauncher() + setupObserving() } override fun onStart() { @@ -172,6 +201,7 @@ class PlaygroundFragment : super.onResume() mapView.onResume() if (locationPermission.hasPermissions() && viewModel.uiState.value is PlaygroundUiState.LocationPermissionsNotGranted) { + viewModel.updateUiState(PlaygroundUiState.Loading) activateMap() } @@ -219,13 +249,13 @@ class PlaygroundFragment : } override fun onMapReady(naverMap: NaverMap) { - initMap(naverMap) + setUpMap(naverMap) if (locationPermission.hasPermissions()) { activateMap() } } - private fun initMap(naverMap: NaverMap) { + private fun setUpMap(naverMap: NaverMap) { map = naverMap map.mapType = NaverMap.MapType.Navi map.isNightModeEnabled = isSystemInDarkMode() @@ -244,62 +274,43 @@ class PlaygroundFragment : isLocationButtonEnabled = false isZoomControlEnabled = false logoGravity = Gravity.TOP or Gravity.START - setLogoMargin(MAP_LOGO_MARGIN, MAP_LOGO_MARGIN, 0, 0) + setLogoMargin( + MAP_LOGO_MARGIN, + MAP_LOGO_MARGIN, + MAP_LOGO_DEFAULT_MARGIN, + MAP_LOGO_DEFAULT_MARGIN, + ) } - binding.lbvWoofLocation.map = map - binding.lbvWoofRegisterLocation.map = map + binding.lbvPlaygroundLocation.map = map + binding.lbvPlaygroundRegisterLocation.map = map map.onMapClickListener = NaverMap.OnMapClickListener { _, _ -> - if (viewModel.uiState.value is PlaygroundUiState.ViewingPlaygroundInfo || - viewModel.uiState.value is PlaygroundUiState.ViewingPlaygroundSummary - ) { - viewModel.updateUiState(PlaygroundUiState.FindingPlayground) - } + reduceMarkerSize() + viewModel.updateUiStateIfViewingPlayground() } map.addOnLocationChangeListener { location -> latLng = LatLng(location.latitude, location.longitude) - - if (viewModel.myPlayground.value != null && viewModel.uiState.value !is PlaygroundUiState.RegisteringPlayground) { - pathOverlay.coords = - listOf( - latLng, - viewModel.myPlayground.value - ?.marker - ?.position, - ) - } + viewModel.updatePathOverlayByLocationChange(latLng) } map.addOnCameraChangeListener { reason, _ -> + val currentState = viewModel.uiState.value if (reason == REASON_GESTURE) { - viewModel.changeTrackingModeToNoFollow() - - if (viewModel.uiState.value is PlaygroundUiState.FindingPlayground && - viewModel.refreshBtnVisible.value == false - ) { - viewModel.updateRefreshBtnVisibility(visible = true) - } - - if (viewModel.uiState.value is PlaygroundUiState.ViewingPlaygroundInfo || - viewModel.uiState.value is PlaygroundUiState.ViewingPlaygroundSummary - ) { - viewModel.updateUiState(PlaygroundUiState.FindingPlayground) - } + reduceMarkerSize() + viewModel.handleUiStateByCameraChange() } - if (viewModel.uiState.value is PlaygroundUiState.RegisteringPlayground) { - registeringCircleOverlay.center = map.cameraPosition.target - viewModel.updateRegisterPlaygroundBtnCameraIdle(cameraIdle = false) - } else { - registeringCircleOverlay.map = null + if (currentState is PlaygroundUiState.RegisteringPlayground) { + currentState.circleOverlay.center = map.cameraPosition.target } } map.addOnCameraIdleListener { - if (viewModel.uiState.value is PlaygroundUiState.RegisteringPlayground) { - viewModel.updateRegisterPlaygroundBtnCameraIdle(cameraIdle = true) + val currentState = viewModel.uiState.value + if (currentState is PlaygroundUiState.RegisteringPlayground) { + viewModel.updateCameraIdle(cameraIdle = true) getAddress(map.cameraPosition.target) } } @@ -314,250 +325,117 @@ class PlaygroundFragment : viewModel.uiState.observe(viewLifecycleOwner) { uiState -> when (uiState) { is PlaygroundUiState.LocationPermissionsNotGranted -> - locationPermission - .createAlarmDialog() + locationPermission.createAlarmDialog() .show(parentFragmentManager, tag) - is PlaygroundUiState.FindingPlayground -> hideRegisterPlaygroundScreen() - is PlaygroundUiState.RegisteringPlayground -> showRegisterPlaygroundScreen() + is PlaygroundUiState.FindingPlayground -> { + viewModel.showMyPlayground(map) + bottomSheetBehavior.state = BottomSheetBehavior.STATE_COLLAPSED + } - else -> return@observe - } - } + is PlaygroundUiState.RegisteringPlayground -> { + setUpRegisteringCircleOverlay(uiState, map.cameraPosition.target) + } - viewModel.myPlayStatus.observe(viewLifecycleOwner) { myPlayStatus -> - if (myPlayStatus == PlayStatus.NO_PLAYGROUND) { - clearMyPlayground() - viewModel.updatePlaygrounds() + else -> return@observe } } viewModel.playgroundInfo.observe(viewLifecycleOwner) { playgroundInfo -> if (playgroundInfo != null) { petDetailAdapter.submitList(playgroundInfo.petDetails) - - Handler(Looper.getMainLooper()).postDelayed( - { - if (playgroundInfo.petDetails.size <= EXPANDED_PET_SIZE) { - bottomSheetBehavior.state = BottomSheetBehavior.STATE_EXPANDED - } else { - bottomSheetBehavior.state = BottomSheetBehavior.STATE_HALF_EXPANDED - } - }, - DELAY_MILLIS, - ) - } else { - petDetailAdapter.submitList(emptyList()) +// changeBottomSheetBehavior() } } viewModel.playgroundSummary.observe(viewLifecycleOwner) { playgroundSummary -> - petSummaryAdapter.submitList(playgroundSummary.petImageUrls) + if (playgroundSummary != null) { + petSummaryAdapter.submitList(playgroundSummary.petImageUrls) + } } - viewModel.myPlayground.observe(viewLifecycleOwner) { myPlaygroundMarker -> + viewModel.myPlayground.observe(viewLifecycleOwner) { myPlayground -> stopLocationService() - if (myPlaygroundMarker != null) { - myPlaygroundMarker.marker.map = map - setUpPathOverlay() - bottomSheetBehavior.isHideable = false + if (myPlayground != null) { viewModel.updatePlaygroundArrival(latLng) - } else { - bottomSheetBehavior.isHideable = true - bottomSheetBehavior.state = BottomSheetBehavior.STATE_HIDDEN } } - viewModel.nearPlaygrounds.observe(viewLifecycleOwner) { - clearNearPlaygroundMarkers() - markNearPlaygroundMarkers() - } - - viewModel.mapAction.observeEvent(viewLifecycleOwner) { event -> + viewModel.mapAction.observe(viewLifecycleOwner) { event -> when (event) { - is MakeMyPlaygroundMarker -> { - val previousMyFootprintMarker = viewModel.myPlayground.value - if (previousMyFootprintMarker != null) { - previousMyFootprintMarker.marker.map = null - } - - val marker = createMarker(playground = event.myPlayground) - val circleOverlay = createCircleOverlay(position = marker.position) - viewModel.loadMyPlayground(marker, circleOverlay) + is MakePlaygrounds -> { + makeMyPlayground(event.myPlayground) + makeNearPlaygrounds(event.nearPlaygrounds) } - is MakeNearPlaygroundMarkers -> { - val nearFootprintMarkers = - event.nearPlaygrounds.map { playground -> - val marker = createMarker(playground = playground) - val circleOverlay = - createCircleOverlay( - position = - LatLng( - playground.latitude, - playground.longitude, - ), - ) - PlaygroundMarkerUiModel( - id = playground.id, - marker = marker, - circleOverlay = circleOverlay, - ) - } - clearNearPlaygroundMarkers() - viewModel.loadNearPlaygrounds(nearFootprintMarkers) - } + is RegisterMyPlayground -> viewModel.registerMyPlayground(map.cameraPosition.target) - is PlaygroundMapAction.RegisterMyPlayground -> viewModel.registerMyPlayground(map.cameraPosition.target) + is ShowRegisteringPlaygroundScreen -> { + getAddress(map.cameraPosition.target) - is PlaygroundMapAction.MoveCameraCenterPosition -> moveCameraCenterPosition(event.position) + binding.layoutPlaygroundRegister.showViewAnimation() + Handler(Looper.getMainLooper()).postDelayed( + { + showHelpBalloon(textRestId = R.string.playground_register_help) + }, + ANIMATE_DURATION_MILLIS, + ) + } - is PlaygroundMapAction.ScanNearPlaygrounds -> viewModel.scanNearPlaygrounds() + is HideRegisteringPlaygroundScreen -> { + binding.layoutPlaygroundRegister.hideViewAnimation() + } - is PlaygroundMapAction.StartLocationService -> startLocationService() - } - } + is MoveCameraCenterPosition -> { + moveCameraCenterPosition(event.position) + } - viewModel.changeTrackingModeAction.observeEvent(viewLifecycleOwner) { event -> - when (event) { - is NoFollowTrackingMode -> map.locationTrackingMode = LocationTrackingMode.NoFollow + is ChangeBottomSheetBehavior -> changeBottomSheetBehavior() - is FollowTrackingMode -> map.locationTrackingMode = LocationTrackingMode.Follow + is ChangeTrackingMode -> { + if (map.locationTrackingMode == LocationTrackingMode.Follow) { + map.locationTrackingMode = LocationTrackingMode.Face + } else { + map.locationTrackingMode = LocationTrackingMode.Follow + } + } - is FaceTrackingMode -> map.locationTrackingMode = LocationTrackingMode.Face + is StartLocationService -> startLocationService() } } viewModel.alertAction.observeEvent(viewLifecycleOwner) { event -> when (event) { - is PlaygroundAlertAction.AlertHasNotLocationPermissionDialog -> - locationPermission - .createAlarmDialog() - .show(parentFragmentManager, tag) - - is AlertHasNotPetDialog -> showRegisterPetDialog() - is PlaygroundAlertAction.AlertAddressOutOfKoreaSnackbar -> - showSnackbar( - resources.getString( - R.string.woof_address_out_of_korea, - ), - ) - - is AlertMarkerRegisteredSnackbar -> showSnackbar(resources.getString(R.string.woof_marker_registered)) - is PlaygroundAlertAction.AlertNotExistMyPlaygroundSnackbar -> - showSnackbar( - resources.getString( - R.string.woof_not_exist_my_playground, - ), - ) - - is PlaygroundAlertAction.AlertLeaveMyPlaygroundSnackbar -> - showSnackbar( - resources.getString( - R.string.playground_leave_my_playground, - ), - ) - - is PlaygroundAlertAction.AlertAutoLeavePlaygroundSnackbar -> - showSnackbar( - resources.getString( - R.string.playground_leave_my_playground, - ), - ) - - is PlaygroundAlertAction.AlertOverlapPlaygroundCreationSnackbar -> { - showSnackbar( - resources.getString( - R.string.playground_overlap_playground_creation, - ), - ) - } - - is PlaygroundAlertAction.AlertAlreadyParticipatePlaygroundSnackbar -> { - showSnackbar( - resources.getString( - R.string.playground_already_participate_playground, - ), - ) + is AlertHasNotLocationPermissionDialog -> { + locationPermission.createAlarmDialog().show(parentFragmentManager, tag) } - is AlertEndWalkSnackbar -> showSnackbar(resources.getString(R.string.woof_stop_walk)) - is PlaygroundAlertAction.AlertFailToCheckPetExistence -> - showSnackbar( - resources.getString( - R.string.woof_fail_to_load_pet_existence_btn, - ), - ) - - is PlaygroundAlertAction.AlertFailToLoadPlaygroundsSnackbar -> - showSnackbar( - resources.getString( - R.string.woof_fail_to_load_near_footprints, - ), - ) - - is PlaygroundAlertAction.AlertFailToRegisterPlaygroundSnackbar -> - showSnackbar( - resources.getString( - R.string.woof_fail_to_register_playground, - ), - ) - - is PlaygroundAlertAction.AlertFailToUpdatePlaygroundArrival -> - showSnackbar( - resources.getString(R.string.woof_fail_to_update_playground_arrival), - ) - - is PlaygroundAlertAction.AlertFailToEndWalkSnackbar -> - showSnackbar( - resources.getString( - R.string.woof_fail_to_end_walk, - ), - ) - - is PlaygroundAlertAction.AlertFailToDeleteMyFootprintSnackbar -> - showSnackbar( - resources.getString( - R.string.woof_fail_to_delete_my_footprint, - ), - ) + is AlertHasNotPetDialog -> showRegisterPetDialog() - is PlaygroundAlertAction.AlertFailToLoadPlaygroundInfoSnackbar -> - showSnackbar( - resources.getString( - R.string.fail_to_load_playground_info, - ), - ) + is AlertLeaveAndRegisterPlaygroundDialog -> showSwitchPlaygroundDialog { viewModel.leaveAndRegisterPlayground() } - is PlaygroundAlertAction.AlertFailToLoadPlaygroundSummarySnackbar -> - showSnackbar( - resources.getString(R.string.fail_to_load_playground_summary), - ) + is AlertLeaveAndJoinPlaygroundDialog -> showSwitchPlaygroundDialog { viewModel.leaveAndJoinPlayground() } - is PlaygroundAlertAction.AlertFailToJoinPlaygroundSnackbar -> { - showSnackbar( - resources.getString(R.string.fail_to_join_playground), - ) - } + is AlertHelpBalloon -> showHelpBalloon(event.textResId) - is PlaygroundAlertAction.AlertHelpBalloon -> showHelpBalloon(event.textResId) + else -> showSnackbarForEvent(event) } } viewModel.navigateAction.observeEvent(viewLifecycleOwner) { event -> when (event) { - is PlaygroundNavigateAction.NavigateToOtherProfile -> { + is NavigateToOtherProfile -> { val intent = OtherProfileActivity.getIntent(requireContext(), event.memberId) startActivity(intent) } - is PlaygroundNavigateAction.NavigateToPetImage -> { + is NavigateToPetImage -> { val intent = PetImageActivity.getIntent(requireContext(), event.petImageUrl) startActivity(intent) } - is PlaygroundNavigateAction.NavigateToPlaygroundMessage -> { - val intent = MessageActivity.getIntent(requireContext(), event.message) + is NavigateToStateMessage -> { + val intent = StateMessageActivity.getIntent(requireContext(), event.message) activityResultLauncher.launch(intent) } } @@ -576,70 +454,67 @@ class PlaygroundFragment : } private fun setupBroadCastReceiver() { - playgroundReceiver = - PlaygroundLocationReceiver(::updateLocation, ::leavePlayground) + playgroundReceiver = PlaygroundLocationReceiver(::updateLocation, ::leavePlayground) val intentFilter = IntentFilter().apply { addAction(PlaygroundLocationReceiver.ACTION_UPDATE_LOCATION) addAction(PlaygroundLocationReceiver.ACTION_LEAVE_PLAYGROUND) } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { requireContext().registerReceiver( playgroundReceiver, intentFilter, - RECEIVER_NOT_EXPORTED, + RECEIVER_EXPORTED, ) } else { - requireContext().registerReceiver( - playgroundReceiver, - intentFilter, - RECEIVER_NOT_EXPORTED, - ) + requireContext().registerReceiver(playgroundReceiver, intentFilter) } } private fun activateMap() { - viewModel.updateUiState(PlaygroundUiState.Loading) locationSource.activate { location -> val lastLocation = location ?: return@activate latLng = LatLng(lastLocation.latitude, lastLocation.longitude) - setupObserving() moveCameraCenterPosition(latLng) Handler(Looper.getMainLooper()).postDelayed( { map.locationTrackingMode = LocationTrackingMode.Follow }, - DELAY_MILLIS, + ANIMATE_DURATION_MILLIS, ) + viewModel.loadPlaygrounds() } } - private fun setupRegisteringCircleOverlay(position: LatLng) { - registeringCircleOverlay.apply { + private fun setUpRegisteringCircleOverlay( + uiState: PlaygroundUiState.RegisteringPlayground, + position: LatLng, + ) { + uiState.circleOverlay.apply { center = position radius = PLAYGROUND_RADIUS color = resources.getColor(R.color.map_circle, null) } - registeringCircleOverlay.map = map + uiState.circleOverlay.map = map } - private fun setUpPathOverlay() { + private fun createPathOverlay(position: LatLng): PathOverlay { + val pathOverlay = PathOverlay() pathOverlay.apply { coords = listOf( latLng, - viewModel.myPlayground.value - ?.marker - ?.position, + position, ) - width = 30 - outlineWidth = 0 + width = PATH_OVERLAY_WIDTH + outlineWidth = PATH_OVERLAY_OUTLINE_WIDTH patternImage = OverlayImage.fromResource(R.drawable.ic_footprint) - patternInterval = 60 + patternInterval = PATH_OVERLAY_PATTERN_INTERVAL color = resources.getColor(R.color.blue, null) - map = map } + pathOverlay.map = map + + return pathOverlay } private fun moveCameraCenterPosition(position: LatLng) { @@ -647,6 +522,39 @@ class PlaygroundFragment : map.moveCamera(cameraUpdate) } + private fun makeMyPlayground(myPlayground: Playground?) { + if (myPlayground != null) { + val marker = createMarker(playground = myPlayground) + val circleOverlay = createCircleOverlay(position = marker.position) + val pathOverlay = createPathOverlay(marker.position) + viewModel.loadMyPlayground( + playgroundId = myPlayground.id, + marker = marker, + circleOverlay = circleOverlay, + pathOverlay = pathOverlay, + ) + } + } + + private fun makeNearPlaygrounds(nearPlaygrounds: List) { + val playgrounds = + nearPlaygrounds.map { playground -> + PlaygroundUiModel( + id = playground.id, + marker = createMarker(playground = playground), + circleOverlay = + createCircleOverlay( + position = + LatLng( + playground.latitude, + playground.longitude, + ), + ), + ) + } + viewModel.loadNearPlaygrounds(playgrounds) + } + private fun createMarker(playground: Playground): Marker { val marker = Marker() marker.apply { @@ -677,78 +585,50 @@ class PlaygroundFragment : marker: Marker, ) { marker.setOnClickListener { - changeClickedMarkerSize(marker) + reduceMarkerSize() + enlargeMarkerSize(marker) viewModel.loadRecentlyClickedPlayground(marker) val position = adjustPosition(marker) moveCameraCenterPosition(position) - - if (id == viewModel.myPlayground.value?.id) { - viewModel.loadPlaygroundInfo(id = id) - viewModel.updateUiState(PlaygroundUiState.ViewingPlaygroundInfo) - } else { - viewModel.loadPlaygroundSummary(id = id) - viewModel.updateUiState(PlaygroundUiState.ViewingPlaygroundSummary) - } + viewModel.handlePlaygroundInfo(id) true } } - private fun markerIcon(playground: Playground): Int = - if (playground.isParticipating) R.drawable.ic_my_playground else R.drawable.ic_near_playground - - private fun showRegisterPlaygroundScreen() { + private fun changeBottomSheetBehavior() { val myPlayground = viewModel.myPlayground.value + val playgroundInfo = viewModel.playgroundInfo.value ?: return if (myPlayground != null) { - myPlayground.marker.map = null - myPlayground.circleOverlay.map = null - pathOverlay.map = null + bottomSheetBehavior.isHideable = false + if (playgroundInfo.petDetails.size <= EXPANDED_PET_SIZE) { + bottomSheetBehavior.state = BottomSheetBehavior.STATE_EXPANDED + } else { + bottomSheetBehavior.state = BottomSheetBehavior.STATE_HALF_EXPANDED + } + } else { + bottomSheetBehavior.isHideable = true + bottomSheetBehavior.state = BottomSheetBehavior.STATE_HIDDEN } - setupRegisteringCircleOverlay(map.cameraPosition.target) - getAddress(map.cameraPosition.target) - - Handler(Looper.getMainLooper()).postDelayed( - { - showHelpBalloon(textRestId = R.string.playground_register_help) - }, - DELAY_MILLIS, - ) } - private fun hideRegisterPlaygroundScreen() { - val myPlayground = viewModel.myPlayground.value - if (myPlayground != null) { - myPlayground.marker.map = map - myPlayground.circleOverlay.center = myPlayground.marker.position - pathOverlay.map = map - } - registeringCircleOverlay.map = null - changeRecentlyClickedMarkerSize() - bottomSheetBehavior.state = BottomSheetBehavior.STATE_COLLAPSED - } + private fun markerIcon(playground: Playground): Int = + if (playground.isParticipating) R.drawable.ic_my_playground else R.drawable.ic_near_playground - private fun changeRecentlyClickedMarkerSize() { + private fun reduceMarkerSize() { val recentlyClickedMarker = viewModel.recentlyClickedPlayground.value ?: return recentlyClickedMarker.width = MARKER_DEFAULT_WIDTH recentlyClickedMarker.height = MARKER_DEFAULT_HEIGHT } - private fun changeClickedMarkerSize(marker: Marker) { + private fun enlargeMarkerSize(marker: Marker) { marker.width = MARKER_CLICKED_WIDTH marker.height = MARKER_CLICKED_HEIGHT } - private fun clearNearPlaygroundMarkers() { - val nearPlaygroundMarkers = viewModel.nearPlaygrounds.value ?: return - nearPlaygroundMarkers.forEach { playgroundMarker -> - playgroundMarker.marker.map = null - playgroundMarker.circleOverlay.map = null - } - } - private fun adjustPosition(marker: Marker): LatLng { val bearingRadians = Math.toRadians(map.cameraPosition.bearing) val offsetDistance = - (map.contentBounds.northLatitude - map.contentBounds.southLatitude) / 8.0 + (map.contentBounds.northLatitude - map.contentBounds.southLatitude) / OFFSET_DIVISOR val latitude = marker.position.latitude - offsetDistance * cos(bearingRadians) val longitude = marker.position.longitude - offsetDistance * sin(bearingRadians) @@ -756,12 +636,6 @@ class PlaygroundFragment : return LatLng(latitude, longitude) } - private fun clearMyPlayground() { - val myPlaygroundMarker = viewModel.myPlayground.value ?: return - myPlaygroundMarker.circleOverlay.map = null - pathOverlay.map = null - } - private fun getAddress(position: LatLng) { val geocoder = Geocoder(requireContext(), Locale.KOREA) val addressLatLng = convertLtnLng(position) @@ -769,34 +643,34 @@ class PlaygroundFragment : geocoder.getFromLocation( addressLatLng.latitude, addressLatLng.longitude, - 1, + ADDRESS_MAX_RESULT, ) { addresses -> - showAddress(addresses) + updateAddress(addresses) } } else { val addresses = geocoder.getFromLocation(addressLatLng.latitude, addressLatLng.longitude, 1) ?: return - showAddress(addresses) + updateAddress(addresses) } } - private fun showAddress(addresses: List
) { + private fun updateAddress(addresses: List
) { if (addresses.isEmpty()) return - val address = addresses[0] - val addressLine = - address - .getAddressLine(0) - .replace(resources.getString(R.string.woof_address_korea), "") - .trimStart() - viewModel.updateAddressLine(addressLine) - - val countryName = address.countryName - val inKorea = countryName == resources.getString(R.string.woof_address_korea) - viewModel.updateRegisterPlaygroundBtnInKorea(inKorea = inKorea) - } - - private fun convertLtnLng(latLng: LatLng): LatLng = LatLng(floor(latLng.latitude * 100) / 100, floor(latLng.longitude * 100) / 100) + val firstAddress = addresses[0] + val address = + firstAddress.getAddressLine(0) + .replace(resources.getString(R.string.playground_address_korea), "").trimStart() + val countryName = firstAddress.countryName + val inKorea = countryName == resources.getString(R.string.playground_address_korea) + viewModel.updateAddressAndInKorea(address = address, inKorea = inKorea) + } + + private fun convertLtnLng(latLng: LatLng): LatLng = + LatLng( + floor(latLng.latitude * LAT_LNG_ROUNDING_FACTOR) / LAT_LNG_ROUNDING_FACTOR, + floor(latLng.longitude * LAT_LNG_ROUNDING_FACTOR) / LAT_LNG_ROUNDING_FACTOR, + ) private fun startLocationService() { val myPlayStatus = viewModel.myPlayStatus.value ?: return @@ -809,42 +683,11 @@ class PlaygroundFragment : private fun updateLocation(location: Location) { latLng = LatLng(location.latitude, location.longitude) - monitorDistanceAndManagePlayStatus() + viewModel.monitorDistanceAndManagePlayStatus(latLng) } private fun leavePlayground() { - clearMyPlayground() viewModel.leavePlayground() - viewModel.updatePlaygrounds() - } - - private fun monitorDistanceAndManagePlayStatus() { - val distanceResults = FloatArray(1) - val myFootprintMarker = viewModel.myPlayground.value ?: return - val position = myFootprintMarker.marker.position - Location.distanceBetween( - latLng.latitude, - latLng.longitude, - position.latitude, - position.longitude, - distanceResults, - ) - val distance = distanceResults[0] - if (withinPlaygroundRange(distance) || outOfPlaygroundRange(distance)) { - viewModel.updatePlaygroundArrival(latLng) - val myPlayground = viewModel.myPlayground.value ?: return - viewModel.loadPlaygroundInfo(myPlayground.id) - } - } - - private fun withinPlaygroundRange(distance: Float): Boolean { - val myPlayStatus = viewModel.myPlayStatus.value ?: return false - return myPlayStatus == PlayStatus.AWAY && distance <= PLAYGROUND_RADIUS - } - - private fun outOfPlaygroundRange(distance: Float): Boolean { - val myPlayStatus = viewModel.myPlayStatus.value ?: return false - return myPlayStatus == PlayStatus.PLAYING && distance > PLAYGROUND_RADIUS } private fun stopLocationService() { @@ -856,14 +699,6 @@ class PlaygroundFragment : ) } - private fun markNearPlaygroundMarkers() { - val nearPlaygroundMarkers = viewModel.nearPlaygrounds.value ?: return - nearPlaygroundMarkers.forEach { playgroundMarker -> - playgroundMarker.marker.map = map - playgroundMarker.circleOverlay.map = map - } - } - private fun setupRecyclerView() { binding.rcvPlaygroundPet.itemAnimator = null binding.rcvPlaygroundPet.adapter = petDetailAdapter @@ -883,7 +718,7 @@ class PlaygroundFragment : val position = parent.getChildAdapterPosition(view) if (position != 0) { val overlapOffset = - (10f * requireContext().resources.displayMetrics.density).toInt() + (ITEM_OFFSET_DP * requireContext().resources.displayMetrics.density).toInt() outRect.left = overlapOffset * -1 } } @@ -900,9 +735,7 @@ class PlaygroundFragment : ?: return@registerForActivityResult if (messageUpdated) { - val myPlayground = - viewModel.myPlayground.value ?: return@registerForActivityResult - viewModel.loadPlaygroundInfo(myPlayground.id) + viewModel.playgroundMessageUpdated() } } } @@ -912,8 +745,8 @@ class PlaygroundFragment : bottomSheetBehavior.isDraggable = true bottomSheetBehavior.isHideable = false bottomSheetBehavior.isFitToContents = false - bottomSheetBehavior.expandedOffset = 50 - bottomSheetBehavior.peekHeight = 250 + bottomSheetBehavior.expandedOffset = BEHAVIOR_EXPANDED_OFFSET + bottomSheetBehavior.peekHeight = BEHAVIOR_PEEK_HEIGHT bottomSheetBehavior.addBottomSheetCallback( object : BottomSheetBehavior.BottomSheetCallback() { @@ -921,28 +754,56 @@ class PlaygroundFragment : bottomSheet: View, slideOffset: Float, ) { - if (viewModel.uiState.value == PlaygroundUiState.FindingPlayground && slideOffset >= 0.4f) { - viewModel.updateUiState(PlaygroundUiState.ViewingPlaygroundInfo) - } } override fun onStateChanged( bottomSheet: View, newState: Int, ) { - if (viewModel.uiState.value == PlaygroundUiState.ViewingPlaygroundInfo && + if (viewModel.uiState.value is PlaygroundUiState.ViewingPlaygroundInfo && newState == BottomSheetBehavior.STATE_COLLAPSED ) { - changeRecentlyClickedMarkerSize() + reduceMarkerSize() binding.rcvPlaygroundPet.smoothScrollToPosition(0) - } else { - viewModel.updateRefreshBtnVisibility(false) + viewModel.updateUiState(PlaygroundUiState.FindingPlayground()) + } + + if (viewModel.uiState.value is PlaygroundUiState.FindingPlayground && + ( + newState == BottomSheetBehavior.STATE_HALF_EXPANDED || + newState == BottomSheetBehavior.STATE_EXPANDED + ) + ) { + viewModel.updateUiState(PlaygroundUiState.ViewingPlaygroundInfo) } } }, ) } + private fun showSnackbarForEvent(event: PlaygroundAlertAction) { + val messageResId = + when (event) { + is AlertAddressOutOfKoreaSnackbar -> R.string.playground_address_out_of_korea + is AlertPlaygroundRegisteredSnackbar -> R.string.playground_marker_registered + is AlertNotExistMyPlaygroundSnackbar -> R.string.playground_not_exist_my_playground + is AlertJoinPlaygroundSnackbar -> R.string.playground_join_complete + is AlertLeaveMyPlaygroundSnackbar -> R.string.playground_leave_my_playground + is AlertAutoLeavePlaygroundSnackbar -> R.string.playground_leave_my_playground + is AlertOverlapPlaygroundCreationSnackbar -> R.string.playground_overlap_playground_creation + is AlertFailToCheckPetExistence -> R.string.playground_fail_to_load_pet_existence_btn + is AlertFailToLoadPlaygroundsSnackbar -> R.string.playground_fail_to_load_near_playgrounds + is AlertFailToRegisterPlaygroundSnackbar -> R.string.playground_fail_to_register_playground + is AlertFailToUpdatePlaygroundArrival -> R.string.playground_fail_to_update_playground_arrival + is AlertFailToLeavePlaygroundSnackbar -> R.string.playground_fail_to_leave + is AlertFailToLoadPlaygroundInfoSnackbar -> R.string.playground_fail_to_load_playground_info + is AlertFailToLoadPlaygroundSummarySnackbar -> R.string.playground_fail_to_load_playground_summary + is AlertFailToJoinPlaygroundSnackbar -> R.string.playground_fail_to_join_playground + else -> null + } + messageResId?.let { showSnackbar(resources.getString(it)) } + } + private fun showRegisterPetDialog() { PetAddAlertDialog( clickToNegative = {}, @@ -968,23 +829,15 @@ class PlaygroundFragment : private fun showBalloon(text: String) { balloon?.dismiss() balloon = - Balloon - .Builder(requireContext()) - .setWidth(BalloonSizeSpec.WRAP) - .setHeight(BalloonSizeSpec.WRAP) - .setText(text) - .setTextColorResource(R.color.white) - .setTextSize(14f) - .setMarginBottom(10) + Balloon.Builder(requireContext()).setWidth(BalloonSizeSpec.WRAP) + .setHeight(BalloonSizeSpec.WRAP).setText(text).setTextColorResource(R.color.white) + .setTextSize(BALLOON_TEXT_SIZE).setMarginBottom(BALLOON_MARGIN_BOTTOM) .setArrowPositionRules(ArrowPositionRules.ALIGN_ANCHOR) - .setArrowSize(10) - .setArrowPosition(0.5f) - .setPadding(12) - .setFocusable(false) - .setCornerRadius(8f) + .setArrowSize(BALLOON_ARROW_SIZE) + .setArrowPosition(BALLOON_ARROW_POSITION).setPadding(BALLOON_PADDING) + .setFocusable(false).setCornerRadius(BALLOON_CORNER_RADIUS) .setBackgroundColorResource(R.color.coral400) - .setBalloonAnimation(BalloonAnimation.ELASTIC) - .setLifecycleOwner(viewLifecycleOwner) + .setBalloonAnimation(BalloonAnimation.ELASTIC).setLifecycleOwner(viewLifecycleOwner) .build() balloon?.showAlignTop(binding.btnPlaygroundRegisteringHelp) @@ -995,6 +848,24 @@ class PlaygroundFragment : showBalloon(text) } + private fun showSwitchPlaygroundDialog(action: () -> Unit) { + val dialog = + DefaultCoralAlertDialog( + alertDialogModel = + AlertDialogModel( + title = requireContext().getString(R.string.playground_dialog_title), + description = requireContext().getString(R.string.playground_dialog_description), + negativeContents = requireContext().getString(R.string.dialog_negative_default), + positiveContents = requireContext().getString(R.string.dialog_positive_default), + ), + clickToNegative = { }, + clickToPositive = { + action() + }, + ) + dialog.show(parentFragmentManager, tag) + } + companion object { private const val PLAYGROUND_RADIUS = 150.0 private const val MIN_ZOOM = 7.0 @@ -1003,13 +874,28 @@ class PlaygroundFragment : private const val MARKER_DEFAULT_HEIGHT = 128 private const val MARKER_CLICKED_WIDTH = 144 private const val MARKER_CLICKED_HEIGHT = 192 - private const val DELAY_MILLIS = 300L private const val MIN_KOREA_LATITUDE = 33.0 private const val MAX_KOREA_LATITUDE = 39.0 private const val MIN_KOREA_LONGITUDE = 125.0 private const val MAX_KOREA_LONGITUDE = 132.0 - private const val MAP_LOGO_MARGIN = 20 + private const val MAP_LOGO_MARGIN = 30 + private const val MAP_LOGO_DEFAULT_MARGIN = 0 private const val EXPANDED_PET_SIZE = 2 + private const val PATH_OVERLAY_WIDTH = 30 + private const val PATH_OVERLAY_OUTLINE_WIDTH = 0 + private const val PATH_OVERLAY_PATTERN_INTERVAL = 60 + private const val ADDRESS_MAX_RESULT = 1 + private const val OFFSET_DIVISOR = 8.0 + private const val ITEM_OFFSET_DP = 10 + private const val LAT_LNG_ROUNDING_FACTOR = 100 + private const val BEHAVIOR_EXPANDED_OFFSET = 50 + private const val BEHAVIOR_PEEK_HEIGHT = 250 + private const val BALLOON_TEXT_SIZE = 14f + private const val BALLOON_MARGIN_BOTTOM = 10 + private const val BALLOON_ARROW_SIZE = 10 + private const val BALLOON_ARROW_POSITION = 0.5f + private const val BALLOON_PADDING = 12 + private const val BALLOON_CORNER_RADIUS = 8f private const val DEFAULT_MESSAGE_UPDATED = false const val EXTRA_MESSAGE_UPDATED = "messageUpdated" diff --git a/android/app/src/main/java/com/happy/friendogly/presentation/ui/playground/action/PlaygroundActionHandler.kt b/android/app/src/main/java/com/happy/friendogly/presentation/ui/playground/action/PlaygroundActionHandler.kt index 73e5a9267..4fb3ad6eb 100644 --- a/android/app/src/main/java/com/happy/friendogly/presentation/ui/playground/action/PlaygroundActionHandler.kt +++ b/android/app/src/main/java/com/happy/friendogly/presentation/ui/playground/action/PlaygroundActionHandler.kt @@ -9,7 +9,9 @@ interface PlaygroundActionHandler { fun clickMyPlaygroundBtn() - fun clickRefreshBtn() + fun clickPlaygroundRefreshBtn() + + fun clickPlaygroundInfoRefreshBtn(playgroundId: Long) fun clickBackBtn() @@ -21,9 +23,9 @@ interface PlaygroundActionHandler { fun clickPetImage(petImageUrl: String) - fun clickPlaygroundMessage(message: String) + fun clickStateMessage(stateMessage: String) - fun clickJoinPlaygroundBtn(playgroundId: Long) + fun clickJoinPlaygroundBtn() fun clickLeavePlaygroundBtn() } diff --git a/android/app/src/main/java/com/happy/friendogly/presentation/ui/playground/action/PlaygroundAlertAction.kt b/android/app/src/main/java/com/happy/friendogly/presentation/ui/playground/action/PlaygroundAlertAction.kt index 45e13a96d..e71d6e3d7 100644 --- a/android/app/src/main/java/com/happy/friendogly/presentation/ui/playground/action/PlaygroundAlertAction.kt +++ b/android/app/src/main/java/com/happy/friendogly/presentation/ui/playground/action/PlaygroundAlertAction.kt @@ -5,22 +5,24 @@ sealed interface PlaygroundAlertAction { data object AlertHasNotPetDialog : PlaygroundAlertAction + data object AlertLeaveAndRegisterPlaygroundDialog : PlaygroundAlertAction + + data object AlertLeaveAndJoinPlaygroundDialog : PlaygroundAlertAction + data object AlertAddressOutOfKoreaSnackbar : PlaygroundAlertAction data object AlertNotExistMyPlaygroundSnackbar : PlaygroundAlertAction - data object AlertMarkerRegisteredSnackbar : PlaygroundAlertAction + data object AlertPlaygroundRegisteredSnackbar : PlaygroundAlertAction data object AlertLeaveMyPlaygroundSnackbar : PlaygroundAlertAction + data object AlertJoinPlaygroundSnackbar : PlaygroundAlertAction + data object AlertAutoLeavePlaygroundSnackbar : PlaygroundAlertAction data object AlertOverlapPlaygroundCreationSnackbar : PlaygroundAlertAction - data object AlertAlreadyParticipatePlaygroundSnackbar : PlaygroundAlertAction - - data object AlertEndWalkSnackbar : PlaygroundAlertAction - data object AlertFailToCheckPetExistence : PlaygroundAlertAction data object AlertFailToLoadPlaygroundsSnackbar : PlaygroundAlertAction @@ -29,9 +31,9 @@ sealed interface PlaygroundAlertAction { data object AlertFailToUpdatePlaygroundArrival : PlaygroundAlertAction - data object AlertFailToEndWalkSnackbar : PlaygroundAlertAction + data object AlertFailToLeavePlaygroundSnackbar : PlaygroundAlertAction - data object AlertFailToDeleteMyFootprintSnackbar : PlaygroundAlertAction + data object AlertFailToSwitchPlaygroundSnackbar : PlaygroundAlertAction data object AlertFailToLoadPlaygroundInfoSnackbar : PlaygroundAlertAction diff --git a/android/app/src/main/java/com/happy/friendogly/presentation/ui/playground/action/PlaygroundMapAction.kt b/android/app/src/main/java/com/happy/friendogly/presentation/ui/playground/action/PlaygroundMapAction.kt index c06318e28..d9cfe363e 100644 --- a/android/app/src/main/java/com/happy/friendogly/presentation/ui/playground/action/PlaygroundMapAction.kt +++ b/android/app/src/main/java/com/happy/friendogly/presentation/ui/playground/action/PlaygroundMapAction.kt @@ -4,16 +4,22 @@ import com.happy.friendogly.presentation.ui.playground.model.Playground import com.naver.maps.geometry.LatLng sealed interface PlaygroundMapAction { - data class MakeMyPlaygroundMarker(val myPlayground: Playground) : PlaygroundMapAction + data class MakePlaygrounds( + val myPlayground: Playground?, + val nearPlaygrounds: List, + ) : PlaygroundMapAction - data class MakeNearPlaygroundMarkers(val nearPlaygrounds: List) : - PlaygroundMapAction + data object RegisterMyPlayground : PlaygroundMapAction + + data object ShowRegisteringPlaygroundScreen : PlaygroundMapAction + + data object HideRegisteringPlaygroundScreen : PlaygroundMapAction data class MoveCameraCenterPosition(val position: LatLng) : PlaygroundMapAction - data object RegisterMyPlayground : PlaygroundMapAction + data object ChangeBottomSheetBehavior : PlaygroundMapAction - data object ScanNearPlaygrounds : PlaygroundMapAction + data object ChangeTrackingMode : PlaygroundMapAction data object StartLocationService : PlaygroundMapAction } diff --git a/android/app/src/main/java/com/happy/friendogly/presentation/ui/playground/action/PlaygroundNavigateAction.kt b/android/app/src/main/java/com/happy/friendogly/presentation/ui/playground/action/PlaygroundNavigateAction.kt index d2290e471..20bec5058 100644 --- a/android/app/src/main/java/com/happy/friendogly/presentation/ui/playground/action/PlaygroundNavigateAction.kt +++ b/android/app/src/main/java/com/happy/friendogly/presentation/ui/playground/action/PlaygroundNavigateAction.kt @@ -5,5 +5,5 @@ interface PlaygroundNavigateAction { data class NavigateToPetImage(val petImageUrl: String) : PlaygroundNavigateAction - data class NavigateToPlaygroundMessage(val message: String) : PlaygroundNavigateAction + data class NavigateToStateMessage(val message: String) : PlaygroundNavigateAction } diff --git a/android/app/src/main/java/com/happy/friendogly/presentation/ui/playground/action/PlaygroundTrackingModeAction.kt b/android/app/src/main/java/com/happy/friendogly/presentation/ui/playground/action/PlaygroundTrackingModeAction.kt deleted file mode 100644 index d259b8d01..000000000 --- a/android/app/src/main/java/com/happy/friendogly/presentation/ui/playground/action/PlaygroundTrackingModeAction.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.happy.friendogly.presentation.ui.playground.action - -sealed interface PlaygroundTrackingModeAction { - data object NoFollowTrackingMode : PlaygroundTrackingModeAction - - data object FollowTrackingMode : PlaygroundTrackingModeAction - - data object FaceTrackingMode : PlaygroundTrackingModeAction -} diff --git a/android/app/src/main/java/com/happy/friendogly/presentation/ui/playground/service/PlaygroundLocationManager.kt b/android/app/src/main/java/com/happy/friendogly/presentation/ui/playground/service/PlaygroundLocationManager.kt index c7d9bd686..c5fca5e9f 100644 --- a/android/app/src/main/java/com/happy/friendogly/presentation/ui/playground/service/PlaygroundLocationManager.kt +++ b/android/app/src/main/java/com/happy/friendogly/presentation/ui/playground/service/PlaygroundLocationManager.kt @@ -53,6 +53,6 @@ class PlaygroundLocationManager( } companion object { - private const val INTERVAL_MILLIS: Long = 10000 + private const val INTERVAL_MILLIS: Long = 6000 } } diff --git a/android/app/src/main/java/com/happy/friendogly/presentation/ui/playground/service/PlaygroundLocationService.kt b/android/app/src/main/java/com/happy/friendogly/presentation/ui/playground/service/PlaygroundLocationService.kt index 32a0eafb1..2e4c50e19 100644 --- a/android/app/src/main/java/com/happy/friendogly/presentation/ui/playground/service/PlaygroundLocationService.kt +++ b/android/app/src/main/java/com/happy/friendogly/presentation/ui/playground/service/PlaygroundLocationService.kt @@ -7,9 +7,12 @@ import android.app.PendingIntent import android.app.Service import android.content.Context import android.content.Intent +import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION import android.location.Location +import android.os.Build import android.os.IBinder import androidx.core.app.NotificationCompat +import androidx.core.app.ServiceCompat import com.happy.friendogly.R import com.happy.friendogly.presentation.ui.MainActivity import com.happy.friendogly.presentation.ui.MainActivity.Companion.EXTRA_FRAGMENT @@ -59,15 +62,24 @@ class PlaygroundLocationService : Service() { private fun startForegroundService(playStatusTitle: String) { createNotificationChannel() - startForeground(SERVICE_ID, createNotification(playStatusTitle)) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + ServiceCompat.startForeground( + this, + SERVICE_ID, + createNotification(playStatusTitle), + FOREGROUND_SERVICE_TYPE_LOCATION, + ) + } else { + startForeground(SERVICE_ID, createNotification(playStatusTitle)) + } locationManager.startLocationUpdate() } private fun createNotificationChannel() { val notificationChannel = NotificationChannel( - WALK_SERVICE_CHANNEL_ID, - WALK_SERVICE_CHANNEL_NAME, + PLAYGROUND_SERVICE_CHANNEL_ID, + PLAYGROUND_SERVICE_CHANNEL_NAME, NotificationManager.IMPORTANCE_DEFAULT, ) val notificationManager = @@ -104,9 +116,9 @@ class PlaygroundLocationService : Service() { stopIntent, ) - return NotificationCompat.Builder(this, WALK_SERVICE_CHANNEL_ID) + return NotificationCompat.Builder(this, PLAYGROUND_SERVICE_CHANNEL_ID) .setContentTitle(playStatusTitle) - .setContentText(resources.getString(R.string.woof_location_tracking)) + .setContentText(resources.getString(R.string.playground_location_tracking)) .setSmallIcon(R.drawable.ic_footprint).setContentIntent(pendingIntent) .setPriority(NotificationCompat.PRIORITY_HIGH).addAction(stopAction) .setAutoCancel(false).setShowWhen(false).setDefaults(NotificationCompat.DEFAULT_ALL) @@ -134,8 +146,8 @@ class PlaygroundLocationService : Service() { } companion object { - private const val WALK_SERVICE_CHANNEL_ID = "walk_service_id" - private const val WALK_SERVICE_CHANNEL_NAME = "Walk Service" + private const val PLAYGROUND_SERVICE_CHANNEL_ID = "playground_service_id" + private const val PLAYGROUND_SERVICE_CHANNEL_NAME = "Playground Service" private const val EXTRA_PLAY_STATUS = "playStatus" private const val REQUEST_CODE_ID = 0 private const val SERVICE_ID = 1 diff --git a/android/app/src/main/java/com/happy/friendogly/presentation/ui/playground/state/PlaygroundUiState.kt b/android/app/src/main/java/com/happy/friendogly/presentation/ui/playground/state/PlaygroundUiState.kt index 6d9856582..8f27db575 100644 --- a/android/app/src/main/java/com/happy/friendogly/presentation/ui/playground/state/PlaygroundUiState.kt +++ b/android/app/src/main/java/com/happy/friendogly/presentation/ui/playground/state/PlaygroundUiState.kt @@ -1,13 +1,20 @@ package com.happy.friendogly.presentation.ui.playground.state +import com.happy.friendogly.presentation.ui.playground.uimodel.PlaygroundRegisterBtnClickable +import com.naver.maps.map.overlay.CircleOverlay + sealed interface PlaygroundUiState { data object Loading : PlaygroundUiState data object LocationPermissionsNotGranted : PlaygroundUiState - data object FindingPlayground : PlaygroundUiState + data class FindingPlayground(val refreshBtnVisible: Boolean = false) : PlaygroundUiState - data object RegisteringPlayground : PlaygroundUiState + data class RegisteringPlayground( + val circleOverlay: CircleOverlay = CircleOverlay(), + val address: String? = null, + val playgroundRegisterBtnClickable: PlaygroundRegisterBtnClickable = PlaygroundRegisterBtnClickable(), + ) : PlaygroundUiState data object ViewingPlaygroundSummary : PlaygroundUiState diff --git a/android/app/src/main/java/com/happy/friendogly/presentation/ui/playground/uimodel/MyPlaygroundUiModel.kt b/android/app/src/main/java/com/happy/friendogly/presentation/ui/playground/uimodel/MyPlaygroundUiModel.kt new file mode 100644 index 000000000..071c851bf --- /dev/null +++ b/android/app/src/main/java/com/happy/friendogly/presentation/ui/playground/uimodel/MyPlaygroundUiModel.kt @@ -0,0 +1,12 @@ +package com.happy.friendogly.presentation.ui.playground.uimodel + +import com.naver.maps.map.overlay.CircleOverlay +import com.naver.maps.map.overlay.Marker +import com.naver.maps.map.overlay.PathOverlay + +data class MyPlaygroundUiModel( + val id: Long, + val marker: Marker, + val circleOverlay: CircleOverlay, + val pathOverlay: PathOverlay, +) diff --git a/android/app/src/main/java/com/happy/friendogly/presentation/ui/playground/uimodel/PlaygroundRegisterBtnClickable.kt b/android/app/src/main/java/com/happy/friendogly/presentation/ui/playground/uimodel/PlaygroundRegisterBtnClickable.kt new file mode 100644 index 000000000..8bd2192f7 --- /dev/null +++ b/android/app/src/main/java/com/happy/friendogly/presentation/ui/playground/uimodel/PlaygroundRegisterBtnClickable.kt @@ -0,0 +1,6 @@ +package com.happy.friendogly.presentation.ui.playground.uimodel + +data class PlaygroundRegisterBtnClickable( + val cameraIdle: Boolean = true, + val inKorea: Boolean = true, +) diff --git a/android/app/src/main/java/com/happy/friendogly/presentation/ui/playground/uimodel/PlaygroundRegisterBtnUiModel.kt b/android/app/src/main/java/com/happy/friendogly/presentation/ui/playground/uimodel/PlaygroundRegisterBtnUiModel.kt deleted file mode 100644 index 2d6e342ee..000000000 --- a/android/app/src/main/java/com/happy/friendogly/presentation/ui/playground/uimodel/PlaygroundRegisterBtnUiModel.kt +++ /dev/null @@ -1,3 +0,0 @@ -package com.happy.friendogly.presentation.ui.playground.uimodel - -data class PlaygroundRegisterBtnUiModel(val cameraIdle: Boolean, val inKorea: Boolean? = null) diff --git a/android/app/src/main/java/com/happy/friendogly/presentation/ui/playground/uimodel/PlaygroundMarkerUiModel.kt b/android/app/src/main/java/com/happy/friendogly/presentation/ui/playground/uimodel/PlaygroundUiModel.kt similarity index 86% rename from android/app/src/main/java/com/happy/friendogly/presentation/ui/playground/uimodel/PlaygroundMarkerUiModel.kt rename to android/app/src/main/java/com/happy/friendogly/presentation/ui/playground/uimodel/PlaygroundUiModel.kt index 074763d55..d1cc1d9af 100644 --- a/android/app/src/main/java/com/happy/friendogly/presentation/ui/playground/uimodel/PlaygroundMarkerUiModel.kt +++ b/android/app/src/main/java/com/happy/friendogly/presentation/ui/playground/uimodel/PlaygroundUiModel.kt @@ -3,7 +3,7 @@ package com.happy.friendogly.presentation.ui.playground.uimodel import com.naver.maps.map.overlay.CircleOverlay import com.naver.maps.map.overlay.Marker -data class PlaygroundMarkerUiModel( +data class PlaygroundUiModel( val id: Long, val marker: Marker, val circleOverlay: CircleOverlay, diff --git a/android/app/src/main/java/com/happy/friendogly/presentation/ui/playground/util/PlaygroundAnimation.kt b/android/app/src/main/java/com/happy/friendogly/presentation/ui/playground/util/PlaygroundAnimation.kt index 85f76df10..7ea3419a9 100644 --- a/android/app/src/main/java/com/happy/friendogly/presentation/ui/playground/util/PlaygroundAnimation.kt +++ b/android/app/src/main/java/com/happy/friendogly/presentation/ui/playground/util/PlaygroundAnimation.kt @@ -28,7 +28,6 @@ private fun View.animateView() { animate().translationY(0f).setDuration(ANIMATE_DURATION_MILLIS).setListener( object : AnimatorListenerAdapter() { override fun onAnimationStart(animation: Animator) { - bringToFront() isVisible = true } }, diff --git a/android/app/src/main/java/com/happy/friendogly/presentation/ui/playground/util/PlaygroundBindingAdapter.kt b/android/app/src/main/java/com/happy/friendogly/presentation/ui/playground/util/PlaygroundBindingAdapter.kt index 8455afb14..b175f0da3 100644 --- a/android/app/src/main/java/com/happy/friendogly/presentation/ui/playground/util/PlaygroundBindingAdapter.kt +++ b/android/app/src/main/java/com/happy/friendogly/presentation/ui/playground/util/PlaygroundBindingAdapter.kt @@ -15,9 +15,9 @@ import com.happy.friendogly.R import com.happy.friendogly.domain.model.Gender import com.happy.friendogly.domain.model.SizeType import com.happy.friendogly.presentation.ui.playground.action.PlaygroundActionHandler +import com.happy.friendogly.presentation.ui.playground.model.PlayStatus import com.happy.friendogly.presentation.ui.playground.state.PlaygroundUiState -import com.happy.friendogly.presentation.ui.playground.uimodel.PlaygroundInfoUiModel -import com.happy.friendogly.presentation.ui.playground.uimodel.PlaygroundMarkerUiModel +import com.happy.friendogly.presentation.ui.playground.uimodel.MyPlaygroundUiModel import kotlinx.datetime.LocalDate import kotlinx.datetime.toJavaLocalDate import java.time.Period @@ -34,9 +34,9 @@ fun TextView.bindPetAge(petBirthDate: LocalDate?) { text = if (years < 1) { - resources.getString(R.string.woof_age_month, months) + resources.getString(R.string.playground_age_month, months) } else { - resources.getString(R.string.woof_age_year, years) + resources.getString(R.string.playground_age_year, years) } } } @@ -69,27 +69,18 @@ fun TextView.bindPetGender(petGender: Gender?) { @BindingAdapter("myPlaygroundBtnVisibility") fun TextView.bindMyPlaygroundBtnVisibility(uiState: PlaygroundUiState?) { isVisible = - (uiState != PlaygroundUiState.RegisteringPlayground && uiState != PlaygroundUiState.ViewingPlaygroundSummary) + (uiState !is PlaygroundUiState.RegisteringPlayground && uiState !is PlaygroundUiState.ViewingPlaygroundSummary) } @BindingAdapter("playgroundLocationBtnVisibility") fun View.bindPlaygroundLocationBtnVisibility(uiState: PlaygroundUiState?) { isVisible = - (uiState != PlaygroundUiState.RegisteringPlayground && uiState != PlaygroundUiState.ViewingPlaygroundSummary) + (uiState !is PlaygroundUiState.RegisteringPlayground && uiState !is PlaygroundUiState.ViewingPlaygroundSummary) } @BindingAdapter("registeringVisibility") fun View.bindRegisteringVisibility(uiState: PlaygroundUiState?) { - isVisible = (uiState is PlaygroundUiState.RegisteringPlayground) -} - -@BindingAdapter("registeringPlaygroundAnimation") -fun View.bindRegisteringAnimation(uiState: PlaygroundUiState?) { - if (uiState is PlaygroundUiState.RegisteringPlayground) { - showViewAnimation() - } else { - hideViewAnimation() - } + isVisible = uiState is PlaygroundUiState.RegisteringPlayground } @BindingAdapter("viewingPlaygroundSummaryAnimation") @@ -103,79 +94,67 @@ fun View.bindViewingPlaygroundSummaryAnimation(uiState: PlaygroundUiState?) { @BindingAdapter("loadingVisibility") fun FrameLayout.bindLoadingVisibility(uiState: PlaygroundUiState?) { - isVisible = (uiState == PlaygroundUiState.Loading) + isVisible = (uiState is PlaygroundUiState.Loading) } @BindingAdapter("loadingAnimation") fun LottieAnimationView.bindLoadingAnimation(uiState: PlaygroundUiState?) { - if (uiState == PlaygroundUiState.Loading) { + if (uiState is PlaygroundUiState.Loading) { playAnimation() } else { pauseAnimation() } } -@BindingAdapter("uiState", "playgroundInfoVisibility") +@BindingAdapter("playgroundInfoUiState", "playgroundInfoVisibility") fun View.bindPlaygroundInfoVisibility( uiState: PlaygroundUiState?, - playgroundInfo: PlaygroundInfoUiModel?, + playStatus: PlayStatus?, ) { isVisible = - if (playgroundInfo != null && ( - uiState == PlaygroundUiState.FindingPlayground || - uiState == PlaygroundUiState.ViewingPlaygroundInfo - ) - ) { - bringToFront() - true - } else { - false - } + playStatus != PlayStatus.NO_PLAYGROUND && ( + uiState is PlaygroundUiState.Loading || + uiState is PlaygroundUiState.FindingPlayground || + uiState is PlaygroundUiState.ViewingPlaygroundInfo + ) } -@BindingAdapter("uiState") +@BindingAdapter("petExistenceBtnUiState") fun View.bindPetExistenceBtnVisibility(uiState: PlaygroundUiState?) { isVisible = - (uiState != PlaygroundUiState.RegisteringPlayground && uiState != PlaygroundUiState.ViewingPlaygroundSummary) + !(uiState is PlaygroundUiState.RegisteringPlayground || uiState is PlaygroundUiState.ViewingPlaygroundSummary) } -@BindingAdapter("uiState", "refreshBtnVisibility") -fun TextView.bindRefreshBtnVisibility( - uiState: PlaygroundUiState?, - refreshBtnVisible: Boolean, -) { +@BindingAdapter("refreshBtnUiState") +fun TextView.bindRefreshBtnVisibility(uiState: PlaygroundUiState?) { isVisible = if (uiState is PlaygroundUiState.FindingPlayground) { - refreshBtnVisible + uiState.refreshBtnVisible } else { false } } -@BindingAdapter("uiState", "cameraIdle") -fun TextView.bindRegisterPlaygroundBtnClickable( - uiState: PlaygroundUiState?, - cameraIdle: Boolean, -) { +@BindingAdapter("registerPlaygroundBtnUiState") +fun TextView.bindRegisterPlaygroundBtnClickable(uiState: PlaygroundUiState?) { isClickable = if (uiState is PlaygroundUiState.RegisteringPlayground) { - cameraIdle + uiState.playgroundRegisterBtnClickable.cameraIdle } else { false } } @BindingAdapter("btnMargin") -fun View.bindBtnMargin(myPlayground: PlaygroundMarkerUiModel?) { +fun View.bindBtnMargin(playStatus: PlayStatus?) { fun Int.dp(): Int { val metrics = Resources.getSystem().displayMetrics - return TypedValue - .applyDimension(TypedValue.COMPLEX_UNIT_DIP, this.toFloat(), metrics) + return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, this.toFloat(), metrics) .toInt() } val marginBottom = - if (myPlayground != null) { + if (playStatus != PlayStatus.NO_PLAYGROUND) { MARGIN_BOTTOM_PLAYING } else { MARGIN_BOTTOM_DEFAULT @@ -189,15 +168,15 @@ fun View.bindBtnMargin(myPlayground: PlaygroundMarkerUiModel?) { this.layoutParams = layoutParams } -@BindingAdapter("registerLocationBtnVisibility") -fun View.bindRegisterLocationBtnVisibility(uiState: PlaygroundUiState?) { - isVisible = (uiState == PlaygroundUiState.RegisteringPlayground) +@BindingAdapter("registeringPlaygroundVisibility") +fun View.bindRegisteringPlaygroundVisibility(uiState: PlaygroundUiState?) { + isVisible = (uiState is PlaygroundUiState.RegisteringPlayground) } @BindingAdapter("playgroundAction", "playgroundBtn", "playgroundId") fun AppCompatButton.bindPlaygroundBtn( playgroundAction: PlaygroundActionHandler, - myPlayground: PlaygroundMarkerUiModel?, + myPlayground: MyPlaygroundUiModel?, playgroundId: Long, ) { if (myPlayground != null && myPlayground.id == playgroundId) { @@ -208,16 +187,15 @@ fun AppCompatButton.bindPlaygroundBtn( } else { text = resources.getString(R.string.playground_join) setOnClickListener { - playgroundAction.clickJoinPlaygroundBtn(playgroundId) + playgroundAction.clickJoinPlaygroundBtn() } } } @BindingAdapter("playgroundBtnVisibility") -fun AppCompatButton.bindPlaygroundBtnVisibility(playgroundInfo: PlaygroundInfoUiModel?) { - if (playgroundInfo != null) { - visibility = if (playgroundInfo.petDetails.isNotEmpty()) View.VISIBLE else View.GONE - } +fun AppCompatButton.bindPlaygroundBtnVisibility(uiState: PlaygroundUiState?) { + visibility = + if (uiState is PlaygroundUiState.ViewingPlaygroundInfo) View.VISIBLE else View.INVISIBLE } @BindingAdapter("petIsArrival") @@ -233,5 +211,10 @@ fun TextView.bindPetIsArrival(isArrival: Boolean) { @BindingAdapter("helpBtnVisibility") fun ImageButton.bindHelpBtnVisibility(uiState: PlaygroundUiState?) { - isVisible = uiState == PlaygroundUiState.RegisteringPlayground + isVisible = uiState is PlaygroundUiState.RegisteringPlayground +} + +@BindingAdapter("addressText") +fun TextView.bindAddressText(uiState: PlaygroundUiState?) { + if (uiState is PlaygroundUiState.RegisteringPlayground) text = uiState.address } diff --git a/android/app/src/main/java/com/happy/friendogly/presentation/ui/playground/viewmodel/PlaygroundViewModel.kt b/android/app/src/main/java/com/happy/friendogly/presentation/ui/playground/viewmodel/PlaygroundViewModel.kt index c41da2b12..0b5cc3dd4 100644 --- a/android/app/src/main/java/com/happy/friendogly/presentation/ui/playground/viewmodel/PlaygroundViewModel.kt +++ b/android/app/src/main/java/com/happy/friendogly/presentation/ui/playground/viewmodel/PlaygroundViewModel.kt @@ -1,12 +1,12 @@ package com.happy.friendogly.presentation.ui.playground.viewmodel +import android.location.Location import android.os.Handler import android.os.Looper import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.viewModelScope import com.happy.friendogly.R -import com.happy.friendogly.data.mapper.toPlayground import com.happy.friendogly.domain.error.DataError import com.happy.friendogly.domain.fold import com.happy.friendogly.domain.usecase.DeletePlaygroundLeaveUseCase @@ -14,6 +14,7 @@ import com.happy.friendogly.domain.usecase.GetPetExistenceUseCase import com.happy.friendogly.domain.usecase.GetPlaygroundInfoUseCase import com.happy.friendogly.domain.usecase.GetPlaygroundSummaryUseCase import com.happy.friendogly.domain.usecase.GetPlaygroundsUseCase +import com.happy.friendogly.domain.usecase.InsertRecentPetUseCase import com.happy.friendogly.domain.usecase.PatchPlaygroundArrivalUseCase import com.happy.friendogly.domain.usecase.PostPlaygroundJoinUseCase import com.happy.friendogly.domain.usecase.PostPlaygroundUseCase @@ -23,32 +24,70 @@ import com.happy.friendogly.presentation.base.Event import com.happy.friendogly.presentation.base.emit import com.happy.friendogly.presentation.ui.playground.action.PlaygroundActionHandler import com.happy.friendogly.presentation.ui.playground.action.PlaygroundAlertAction +import com.happy.friendogly.presentation.ui.playground.action.PlaygroundAlertAction.AlertAddressOutOfKoreaSnackbar +import com.happy.friendogly.presentation.ui.playground.action.PlaygroundAlertAction.AlertAutoLeavePlaygroundSnackbar +import com.happy.friendogly.presentation.ui.playground.action.PlaygroundAlertAction.AlertFailToCheckPetExistence +import com.happy.friendogly.presentation.ui.playground.action.PlaygroundAlertAction.AlertFailToJoinPlaygroundSnackbar +import com.happy.friendogly.presentation.ui.playground.action.PlaygroundAlertAction.AlertFailToLeavePlaygroundSnackbar +import com.happy.friendogly.presentation.ui.playground.action.PlaygroundAlertAction.AlertFailToLoadPlaygroundInfoSnackbar +import com.happy.friendogly.presentation.ui.playground.action.PlaygroundAlertAction.AlertFailToLoadPlaygroundSummarySnackbar +import com.happy.friendogly.presentation.ui.playground.action.PlaygroundAlertAction.AlertFailToLoadPlaygroundsSnackbar +import com.happy.friendogly.presentation.ui.playground.action.PlaygroundAlertAction.AlertFailToRegisterPlaygroundSnackbar +import com.happy.friendogly.presentation.ui.playground.action.PlaygroundAlertAction.AlertFailToUpdatePlaygroundArrival +import com.happy.friendogly.presentation.ui.playground.action.PlaygroundAlertAction.AlertHasNotLocationPermissionDialog +import com.happy.friendogly.presentation.ui.playground.action.PlaygroundAlertAction.AlertHasNotPetDialog +import com.happy.friendogly.presentation.ui.playground.action.PlaygroundAlertAction.AlertHelpBalloon +import com.happy.friendogly.presentation.ui.playground.action.PlaygroundAlertAction.AlertJoinPlaygroundSnackbar +import com.happy.friendogly.presentation.ui.playground.action.PlaygroundAlertAction.AlertLeaveAndJoinPlaygroundDialog +import com.happy.friendogly.presentation.ui.playground.action.PlaygroundAlertAction.AlertLeaveAndRegisterPlaygroundDialog +import com.happy.friendogly.presentation.ui.playground.action.PlaygroundAlertAction.AlertLeaveMyPlaygroundSnackbar +import com.happy.friendogly.presentation.ui.playground.action.PlaygroundAlertAction.AlertNotExistMyPlaygroundSnackbar +import com.happy.friendogly.presentation.ui.playground.action.PlaygroundAlertAction.AlertOverlapPlaygroundCreationSnackbar +import com.happy.friendogly.presentation.ui.playground.action.PlaygroundAlertAction.AlertPlaygroundRegisteredSnackbar import com.happy.friendogly.presentation.ui.playground.action.PlaygroundMapAction +import com.happy.friendogly.presentation.ui.playground.action.PlaygroundMapAction.ChangeBottomSheetBehavior +import com.happy.friendogly.presentation.ui.playground.action.PlaygroundMapAction.ChangeTrackingMode +import com.happy.friendogly.presentation.ui.playground.action.PlaygroundMapAction.HideRegisteringPlaygroundScreen +import com.happy.friendogly.presentation.ui.playground.action.PlaygroundMapAction.MakePlaygrounds +import com.happy.friendogly.presentation.ui.playground.action.PlaygroundMapAction.MoveCameraCenterPosition +import com.happy.friendogly.presentation.ui.playground.action.PlaygroundMapAction.RegisterMyPlayground +import com.happy.friendogly.presentation.ui.playground.action.PlaygroundMapAction.ShowRegisteringPlaygroundScreen +import com.happy.friendogly.presentation.ui.playground.action.PlaygroundMapAction.StartLocationService import com.happy.friendogly.presentation.ui.playground.action.PlaygroundNavigateAction -import com.happy.friendogly.presentation.ui.playground.action.PlaygroundTrackingModeAction +import com.happy.friendogly.presentation.ui.playground.action.PlaygroundNavigateAction.NavigateToOtherProfile +import com.happy.friendogly.presentation.ui.playground.action.PlaygroundNavigateAction.NavigateToPetImage +import com.happy.friendogly.presentation.ui.playground.action.PlaygroundNavigateAction.NavigateToStateMessage import com.happy.friendogly.presentation.ui.playground.mapper.toPresentation import com.happy.friendogly.presentation.ui.playground.model.PlayStatus +import com.happy.friendogly.presentation.ui.playground.model.PlaygroundPetDetail import com.happy.friendogly.presentation.ui.playground.model.PlaygroundSummary import com.happy.friendogly.presentation.ui.playground.state.PlaygroundUiState +import com.happy.friendogly.presentation.ui.playground.uimodel.MyPlaygroundUiModel import com.happy.friendogly.presentation.ui.playground.uimodel.PlaygroundInfoUiModel -import com.happy.friendogly.presentation.ui.playground.uimodel.PlaygroundMarkerUiModel -import com.happy.friendogly.presentation.ui.playground.uimodel.PlaygroundRegisterBtnUiModel +import com.happy.friendogly.presentation.ui.playground.uimodel.PlaygroundUiModel import com.happy.friendogly.presentation.ui.playground.util.ANIMATE_DURATION_MILLIS import com.happy.friendogly.presentation.utils.logBackBtnClicked +import com.happy.friendogly.presentation.utils.logCheckPetExistenceClicked import com.happy.friendogly.presentation.utils.logCloseBtnClicked import com.happy.friendogly.presentation.utils.logHelpBtnClicked +import com.happy.friendogly.presentation.utils.logJoinPlaygroundClicked +import com.happy.friendogly.presentation.utils.logLeavePlaygroundClicked import com.happy.friendogly.presentation.utils.logLocationBtnClicked import com.happy.friendogly.presentation.utils.logMyPlaygroundBtnClicked import com.happy.friendogly.presentation.utils.logPetExistence import com.happy.friendogly.presentation.utils.logPetExistenceBtnClicked import com.happy.friendogly.presentation.utils.logPetImageClicked +import com.happy.friendogly.presentation.utils.logPlaygroundInfoRefreshBtnClicked import com.happy.friendogly.presentation.utils.logPlaygroundPetDetailClicked +import com.happy.friendogly.presentation.utils.logPlaygroundRefreshBtnClicked import com.happy.friendogly.presentation.utils.logPlaygroundSize -import com.happy.friendogly.presentation.utils.logRefreshBtnClicked import com.happy.friendogly.presentation.utils.logRegisterMarkerBtnClicked +import com.happy.friendogly.presentation.utils.logStateMessageClicked import com.naver.maps.geometry.LatLng +import com.naver.maps.map.NaverMap import com.naver.maps.map.overlay.CircleOverlay import com.naver.maps.map.overlay.Marker +import com.naver.maps.map.overlay.PathOverlay import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.launch import javax.inject.Inject @@ -64,6 +103,7 @@ class PlaygroundViewModel private val getPetExistenceUseCase: GetPetExistenceUseCase, private val getPlaygroundInfoUseCase: GetPlaygroundInfoUseCase, private val getPlaygroundSummaryUseCase: GetPlaygroundSummaryUseCase, + private val insertRecentPetUseCase: InsertRecentPetUseCase, private val postPlaygroundJoinUseCase: PostPlaygroundJoinUseCase, private val deletePlaygroundLeaveUseCase: DeletePlaygroundLeaveUseCase, ) : BaseViewModel(), @@ -76,11 +116,11 @@ class PlaygroundViewModel MutableLiveData(PlayStatus.NO_PLAYGROUND) val myPlayStatus: LiveData get() = _myPlayStatus - private val _myPlayground: MutableLiveData = MutableLiveData() - val myPlayground: LiveData get() = _myPlayground + private val _myPlayground: MutableLiveData = MutableLiveData() + val myPlayground: LiveData get() = _myPlayground - private val _nearPlaygrounds: MutableLiveData> = MutableLiveData() - val nearPlaygrounds: LiveData> get() = _nearPlaygrounds + private val _nearPlaygrounds: MutableLiveData> = MutableLiveData() + val nearPlaygrounds: LiveData> get() = _nearPlaygrounds private val _recentlyClickedPlayground: MutableLiveData = MutableLiveData() val recentlyClickedPlayground: LiveData get() = _recentlyClickedPlayground @@ -91,15 +131,8 @@ class PlaygroundViewModel private val _playgroundSummary: MutableLiveData = MutableLiveData() val playgroundSummary: LiveData get() = _playgroundSummary - private val _addressLine: MutableLiveData = MutableLiveData() - val addressLine: LiveData get() = _addressLine - - private val _mapAction: MutableLiveData> = MutableLiveData() - val mapAction: LiveData> get() = _mapAction - - private val _changeTrackingModeAction: MutableLiveData> = - MutableLiveData() - val changeTrackingModeAction: LiveData> get() = _changeTrackingModeAction + private val _mapAction: MutableLiveData = MutableLiveData() + val mapAction: LiveData get() = _mapAction private val _alertAction: MutableLiveData> = MutableLiveData() val alertAction: LiveData> get() = _alertAction @@ -108,13 +141,6 @@ class PlaygroundViewModel MutableLiveData() val navigateAction: LiveData> get() = _navigateAction - private val _refreshBtnVisible: MutableLiveData = MutableLiveData(false) - val refreshBtnVisible: LiveData get() = _refreshBtnVisible - - private val _playgroundRegisterBtn: MutableLiveData = - MutableLiveData() - val playgroundRegisterBtn: LiveData get() = _playgroundRegisterBtn - override fun clickPetExistenceBtn() { analyticsHelper.logPetExistenceBtnClicked() runIfLocationPermissionGranted { @@ -125,257 +151,398 @@ class PlaygroundViewModel override fun clickRegisterMarkerBtn() { analyticsHelper.logRegisterMarkerBtnClicked() runIfLocationPermissionGranted { - _mapAction.emit(PlaygroundMapAction.RegisterMyPlayground) + _mapAction.value = RegisterMyPlayground } } override fun clickLocationBtn() { analyticsHelper.logLocationBtnClicked() runIfLocationPermissionGranted { - changeLocationTrackingMode() + _mapAction.value = ChangeTrackingMode } } override fun clickMyPlaygroundBtn() { analyticsHelper.logMyPlaygroundBtnClicked() runIfLocationPermissionGranted { - val myPlaygroundMarker = myPlayground.value - if (myPlaygroundMarker != null) { - _mapAction.emit(PlaygroundMapAction.MoveCameraCenterPosition(myPlaygroundMarker.marker.position)) - changeTrackingModeToNoFollow() + val myPlayground = myPlayground.value + if (myPlayground != null) { + _mapAction.value = MoveCameraCenterPosition(myPlayground.marker.position) } else { - _alertAction.emit(PlaygroundAlertAction.AlertNotExistMyPlaygroundSnackbar) + _alertAction.emit(AlertNotExistMyPlaygroundSnackbar) } } } - override fun clickRefreshBtn() { - analyticsHelper.logRefreshBtnClicked() - updateRefreshBtnVisibility(visible = false) + override fun clickPlaygroundRefreshBtn() { + analyticsHelper.logPlaygroundRefreshBtnClicked() runIfLocationPermissionGranted { - _mapAction.emit(PlaygroundMapAction.ScanNearPlaygrounds) + loadNearPlaygrounds() } } + override fun clickPlaygroundInfoRefreshBtn(playgroundId: Long) { + analyticsHelper.logPlaygroundInfoRefreshBtnClicked() + loadPlaygroundInfo(playgroundId) + } + override fun clickBackBtn() { analyticsHelper.logBackBtnClicked() - updateUiState(PlaygroundUiState.FindingPlayground) + clearRegisteringPlaygroundState() } override fun clickCloseBtn() { analyticsHelper.logCloseBtnClicked() - updateUiState(PlaygroundUiState.FindingPlayground) + clearRegisteringPlaygroundState() } override fun clickPlaygroundPetDetail(memberId: Long) { analyticsHelper.logPlaygroundPetDetailClicked() - _navigateAction.emit(PlaygroundNavigateAction.NavigateToOtherProfile(memberId)) + _navigateAction.emit(NavigateToOtherProfile(memberId)) } override fun clickHelpBtn() { analyticsHelper.logHelpBtnClicked() if (uiState.value is PlaygroundUiState.RegisteringPlayground) { val textResId = R.string.playground_register_help - _alertAction.emit(PlaygroundAlertAction.AlertHelpBalloon(textResId)) + _alertAction.emit(AlertHelpBalloon(textResId)) } } override fun clickPetImage(petImageUrl: String) { analyticsHelper.logPetImageClicked() - _navigateAction.emit(PlaygroundNavigateAction.NavigateToPetImage(petImageUrl)) + _navigateAction.emit(NavigateToPetImage(petImageUrl)) + } + + override fun clickStateMessage(stateMessage: String) { + analyticsHelper.logStateMessageClicked() + _navigateAction.emit(NavigateToStateMessage(stateMessage)) + } + + override fun clickJoinPlaygroundBtn() { + analyticsHelper.logJoinPlaygroundClicked() + joinPlayground() + } + + override fun clickLeavePlaygroundBtn() { + analyticsHelper.logLeavePlaygroundClicked() + leavePlayground() + } + + private fun checkPetExistence() { + analyticsHelper.logCheckPetExistenceClicked() + viewModelScope.launch { + getPetExistenceUseCase() + .onSuccess { petExistence -> + analyticsHelper.logPetExistence(petExistence.isExistPet) + if (!petExistence.isExistPet) { + _alertAction.emit(AlertHasNotPetDialog) + } else { + hideMyPlayground() + updateUiState(PlaygroundUiState.RegisteringPlayground()) + _mapAction.value = ShowRegisteringPlaygroundScreen + } + }.onFailure { + _alertAction.emit(AlertFailToCheckPetExistence) + } + } + } + + private fun runIfLocationPermissionGranted(action: () -> Unit) { + if (uiState.value !is PlaygroundUiState.LocationPermissionsNotGranted) { + action() + } else { + _alertAction.emit(AlertHasNotLocationPermissionDialog) + } + } + + private fun loadPlaygroundSummary(id: Long) { + viewModelScope.launch { + getPlaygroundSummaryUseCase(id) + .onSuccess { playgroundSummary -> + _playgroundSummary.value = playgroundSummary + updateUiState(PlaygroundUiState.ViewingPlaygroundSummary) + }.onFailure { + _alertAction.emit(AlertFailToLoadPlaygroundSummarySnackbar) + } + } + } + + private fun clearPlaygrounds() { + clearMyPlayground() + clearNearPlaygrounds() + } + + private fun clearMyPlayground() { + hideMyPlayground() + _myPlayStatus.value = PlayStatus.NO_PLAYGROUND + _myPlayground.value = null + _playgroundInfo.value = null + } + + private fun clearNearPlaygrounds() { + val nearPlaygroundMarkers = nearPlaygrounds.value ?: return + nearPlaygroundMarkers.forEach { playgroundMarker -> + playgroundMarker.marker.map = null + playgroundMarker.circleOverlay.map = null + } + } + + private fun clearRegisteringPlaygroundState() { + val currentUiState = uiState.value + if (currentUiState is PlaygroundUiState.RegisteringPlayground) { + currentUiState.circleOverlay.map = null + } + _mapAction.value = HideRegisteringPlaygroundScreen + updateUiState(PlaygroundUiState.FindingPlayground()) } - override fun clickPlaygroundMessage(message: String) { - _navigateAction.emit(PlaygroundNavigateAction.NavigateToPlaygroundMessage(message)) + private fun insertRecentPet(playgroundPetDetails: List) { + viewModelScope.launch { + val otherPetDetails = + playgroundPetDetails.filter { playgroundPetDetail -> + !playgroundPetDetail.isMine + } + + otherPetDetails.forEach { otherPetDetail -> + insertRecentPetUseCase( + memberId = otherPetDetail.memberId, + petId = otherPetDetail.petId, + name = otherPetDetail.name, + imgUrl = otherPetDetail.imageUrl, + birthday = otherPetDetail.birthDate, + gender = otherPetDetail.gender, + sizeType = otherPetDetail.sizeType, + ) + } + } } - override fun clickJoinPlaygroundBtn(playgroundId: Long) { + private fun joinPlayground() { viewModelScope.launch { - postPlaygroundJoinUseCase(playgroundId = playgroundId).fold( + val playgroundSummary = playgroundSummary.value ?: return@launch + postPlaygroundJoinUseCase(playgroundId = playgroundSummary.playgroundId).fold( onSuccess = { - updatePlaygrounds() + loadPlaygrounds() + _alertAction.emit(AlertJoinPlaygroundSnackbar) }, onError = { error -> when (error) { DataError.Network.ALREADY_PARTICIPATE_PLAYGROUND -> _alertAction.emit( - PlaygroundAlertAction.AlertAlreadyParticipatePlaygroundSnackbar, + AlertLeaveAndJoinPlaygroundDialog, ) - else -> _alertAction.emit(PlaygroundAlertAction.AlertFailToJoinPlaygroundSnackbar) + else -> _alertAction.emit(AlertFailToJoinPlaygroundSnackbar) } }, ) } } - override fun clickLeavePlaygroundBtn() { - leavePlayground() + private fun loadPlaygroundInfo(id: Long) { + viewModelScope.launch { + getPlaygroundInfoUseCase(id) + .onSuccess { playgroundInfo -> + updateUiState(PlaygroundUiState.Loading) + _playgroundInfo.value = playgroundInfo.toPresentation() + insertRecentPet(playgroundInfo.playgroundPetDetails) + Handler(Looper.getMainLooper()).postDelayed( + { + _mapAction.value = ChangeBottomSheetBehavior + updateUiState(PlaygroundUiState.ViewingPlaygroundInfo) + }, + ANIMATE_DURATION_MILLIS, + ) + }.onFailure { + _alertAction.emit(AlertFailToLoadPlaygroundInfoSnackbar) + } + } } - private fun checkPetExistence() { + private fun hideMyPlayground() { + val myPlayground = myPlayground.value ?: return + myPlayground.marker.map = null + myPlayground.circleOverlay.map = null + myPlayground.pathOverlay.map = null + } + + private fun withinPlaygroundRange(distance: Float): Boolean { + val myPlayground = myPlayground.value ?: return false + val myPlayStatus = myPlayStatus.value ?: return false + return myPlayStatus == PlayStatus.AWAY && distance <= myPlayground.circleOverlay.radius + } + + private fun outOfPlaygroundRange(distance: Float): Boolean { + val myPlayground = myPlayground.value ?: return false + val myPlayStatus = myPlayStatus.value ?: return false + return myPlayStatus == PlayStatus.PLAYING && distance > myPlayground.circleOverlay.radius + } + + private fun loadNearPlaygrounds() { viewModelScope.launch { - getPetExistenceUseCase() - .onSuccess { petExistence -> - analyticsHelper.logPetExistence(petExistence.isExistPet) - if (!petExistence.isExistPet) { - _alertAction.emit(PlaygroundAlertAction.AlertHasNotPetDialog) - } else { - _uiState.value = PlaygroundUiState.Loading - Handler(Looper.getMainLooper()).postDelayed( - { - _uiState.value = PlaygroundUiState.RegisteringPlayground - }, - ANIMATE_DURATION_MILLIS, - ) - } + getPlaygroundsUseCase() + .onSuccess { playgrounds -> + analyticsHelper.logPlaygroundSize(playgrounds.size) + updateUiState(PlaygroundUiState.Loading) + clearNearPlaygrounds() + val nearPlaygrounds = + playgrounds.filter { playground -> !playground.isParticipating } + + _mapAction.value = MakePlaygrounds(null, nearPlaygrounds) }.onFailure { - // 이미 참여한 놀이터 있을 때, 나머지 에러 처리 - _alertAction.emit(PlaygroundAlertAction.AlertAlreadyParticipatePlaygroundSnackbar) - _alertAction.emit(PlaygroundAlertAction.AlertFailToCheckPetExistence) + updateUiState(PlaygroundUiState.FindingPlayground()) + _alertAction.emit(AlertFailToLoadPlaygroundsSnackbar) } } } - private fun changeLocationTrackingMode() { - val changeTrackingModeAction = - changeTrackingModeAction.value?.value - ?: PlaygroundTrackingModeAction.FollowTrackingMode - val trackingMode = - if (changeTrackingModeAction is PlaygroundTrackingModeAction.FollowTrackingMode) { - PlaygroundTrackingModeAction.FaceTrackingMode - } else { - PlaygroundTrackingModeAction.FollowTrackingMode - } - _changeTrackingModeAction.emit(trackingMode) + fun updatePathOverlayByLocationChange(latLng: LatLng) { + if (myPlayStatus.value != PlayStatus.NO_PLAYGROUND && + uiState.value !is PlaygroundUiState.RegisteringPlayground + ) { + val myPlayground = myPlayground.value ?: return + myPlayground.pathOverlay.coords = + listOf( + latLng, + myPlayground.marker.position, + ) + } } - private fun runIfLocationPermissionGranted(action: () -> Unit) { - if (uiState.value !is PlaygroundUiState.LocationPermissionsNotGranted) { - action() - } else { - _alertAction.emit(PlaygroundAlertAction.AlertHasNotLocationPermissionDialog) + fun handleUiStateByCameraChange() { + when (val currentState = uiState.value) { + is PlaygroundUiState.FindingPlayground -> { + if (!currentState.refreshBtnVisible) { + updateUiState(PlaygroundUiState.FindingPlayground(refreshBtnVisible = true)) + } + } + + is PlaygroundUiState.RegisteringPlayground -> { + updateCameraIdle(cameraIdle = false) + } + + is PlaygroundUiState.ViewingPlaygroundSummary, + PlaygroundUiState.ViewingPlaygroundInfo, + -> { + updateUiState( + PlaygroundUiState.FindingPlayground( + refreshBtnVisible = true, + ), + ) + } + + else -> return } } fun registerMyPlayground(latLng: LatLng) { - if (playgroundRegisterBtn.value?.inKorea == true) { + val currentUiState = uiState.value + if (currentUiState is PlaygroundUiState.RegisteringPlayground && currentUiState.playgroundRegisterBtnClickable.inKorea) { viewModelScope.launch { postPlaygroundUseCase(latLng.latitude, latLng.longitude).fold( - onSuccess = { myPlayground -> - _mapAction.emit(PlaygroundMapAction.MakeMyPlaygroundMarker(myPlayground = myPlayground.toPlayground())) - _alertAction.emit(PlaygroundAlertAction.AlertMarkerRegisteredSnackbar) - _uiState.value = PlaygroundUiState.FindingPlayground - scanNearPlaygrounds() + onSuccess = { + currentUiState.circleOverlay.map = null + _mapAction.value = HideRegisteringPlaygroundScreen + loadPlaygrounds() + _alertAction.emit(AlertPlaygroundRegisteredSnackbar) }, onError = { error -> when (error) { DataError.Network.OVERLAP_PLAYGROUND_CREATION -> _alertAction.emit( - PlaygroundAlertAction.AlertOverlapPlaygroundCreationSnackbar, + AlertOverlapPlaygroundCreationSnackbar, ) DataError.Network.ALREADY_PARTICIPATE_PLAYGROUND -> _alertAction.emit( - PlaygroundAlertAction.AlertAlreadyParticipatePlaygroundSnackbar, + AlertLeaveAndRegisterPlaygroundDialog, ) - else -> _alertAction.emit(PlaygroundAlertAction.AlertFailToRegisterPlaygroundSnackbar) + else -> _alertAction.emit(AlertFailToRegisterPlaygroundSnackbar) } }, ) } } else { - _alertAction.emit(PlaygroundAlertAction.AlertAddressOutOfKoreaSnackbar) + _alertAction.emit(AlertAddressOutOfKoreaSnackbar) } } - fun updatePlaygrounds() { + fun loadPlaygrounds() { viewModelScope.launch { getPlaygroundsUseCase() .onSuccess { playgrounds -> analyticsHelper.logPlaygroundSize(playgrounds.size) + updateUiState(PlaygroundUiState.Loading) + clearPlaygrounds() + val myPlayground = + playgrounds.firstOrNull { playground -> playground.isParticipating } + val nearPlaygrounds = playgrounds.filter { playground -> !playground.isParticipating } - _mapAction.value = - Event(PlaygroundMapAction.MakeNearPlaygroundMarkers(nearPlaygrounds = nearPlaygrounds)) - val myPlayground = - playgrounds.firstOrNull { playground -> playground.isParticipating } - if (myPlayground != null) { - _mapAction.value = - Event(PlaygroundMapAction.MakeMyPlaygroundMarker(myPlayground = myPlayground)) - } + _mapAction.value = MakePlaygrounds(myPlayground, nearPlaygrounds) }.onFailure { - _alertAction.emit(PlaygroundAlertAction.AlertFailToLoadPlaygroundsSnackbar) + updateUiState(PlaygroundUiState.FindingPlayground()) + _alertAction.emit(AlertFailToLoadPlaygroundsSnackbar) } } } fun loadMyPlayground( + playgroundId: Long, marker: Marker, circleOverlay: CircleOverlay, + pathOverlay: PathOverlay, ) { - val actionsValue = mapAction.value?.value val myPlayground = - (actionsValue as? PlaygroundMapAction.MakeMyPlaygroundMarker)?.myPlayground ?: return - - _myPlayground.value = - PlaygroundMarkerUiModel( - id = myPlayground.id, + MyPlaygroundUiModel( + id = playgroundId, marker = marker, circleOverlay = circleOverlay, + pathOverlay = pathOverlay, ) + _myPlayground.value = myPlayground + _recentlyClickedPlayground.value = myPlayground.marker } - fun scanNearPlaygrounds() { - _uiState.value = PlaygroundUiState.Loading - viewModelScope.launch { - getPlaygroundsUseCase() - .onSuccess { playgrounds -> - analyticsHelper.logPlaygroundSize(playgrounds.size) - val nearPlaygrounds = - playgrounds.filter { playground -> !playground.isParticipating } - _mapAction.emit( - PlaygroundMapAction.MakeNearPlaygroundMarkers( - nearPlaygrounds = nearPlaygrounds, - ), - ) - }.onFailure { - _alertAction.emit(PlaygroundAlertAction.AlertFailToLoadPlaygroundsSnackbar) - } - } - } - - fun loadNearPlaygrounds(markers: List) { + fun loadNearPlaygrounds(markers: List) { _nearPlaygrounds.value = markers Handler(Looper.getMainLooper()).postDelayed( { - _uiState.value = PlaygroundUiState.FindingPlayground + updateUiState(PlaygroundUiState.FindingPlayground()) }, ANIMATE_DURATION_MILLIS, ) } - fun loadPlaygroundInfo(id: Long) { - viewModelScope.launch { - getPlaygroundInfoUseCase(id) - .onSuccess { playgroundInfo -> - _playgroundInfo.value = playgroundInfo.toPresentation() - }.onFailure { - _alertAction.emit(PlaygroundAlertAction.AlertFailToLoadPlaygroundInfoSnackbar) - } + fun showMyPlayground(map: NaverMap) { + val myPlayground = myPlayground.value ?: return + myPlayground.marker.map = map + myPlayground.circleOverlay.center = myPlayground.marker.position + myPlayground.circleOverlay.map = map + myPlayground.pathOverlay.map = map + } + + fun updateUiStateIfViewingPlayground() { + if (uiState.value is PlaygroundUiState.ViewingPlaygroundInfo || + uiState.value is PlaygroundUiState.ViewingPlaygroundSummary + ) { + updateUiState(PlaygroundUiState.FindingPlayground()) } } - fun loadPlaygroundSummary(id: Long) { - viewModelScope.launch { - getPlaygroundSummaryUseCase(id) - .onSuccess { playgroundSummary -> - _playgroundSummary.value = playgroundSummary - }.onFailure { - _alertAction.emit(PlaygroundAlertAction.AlertFailToLoadPlaygroundSummarySnackbar) - } + fun playgroundMessageUpdated() { + val myPlayground = myPlayground.value ?: return + loadPlaygroundInfo(myPlayground.id) + } + + fun handlePlaygroundInfo(id: Long) { + if (id == myPlayground.value?.id) { + loadPlaygroundInfo(id = id) + } else { + loadPlaygroundSummary(id = id) } } @@ -404,19 +571,17 @@ class PlaygroundViewModel } if (previousPlayStatus == PlayStatus.NO_PLAYGROUND) { - _mapAction.emit(PlaygroundMapAction.StartLocationService) + _mapAction.value = StartLocationService } }, onError = { error -> when (error) { DataError.Network.NO_PARTICIPATING_PLAYGROUND -> { - _alertAction.emit(PlaygroundAlertAction.AlertAutoLeavePlaygroundSnackbar) - _myPlayStatus.value = PlayStatus.NO_PLAYGROUND - _myPlayground.value = null - _playgroundInfo.value = null + _alertAction.emit(AlertAutoLeavePlaygroundSnackbar) + loadPlaygrounds() } - else -> _alertAction.emit(PlaygroundAlertAction.AlertFailToUpdatePlaygroundArrival) + else -> _alertAction.emit(AlertFailToUpdatePlaygroundArrival) } }, ) @@ -427,33 +592,78 @@ class PlaygroundViewModel viewModelScope.launch { deletePlaygroundLeaveUseCase() .onSuccess { - _myPlayStatus.value = PlayStatus.NO_PLAYGROUND - _myPlayground.value = null - _playgroundInfo.value = null - _alertAction.emit(PlaygroundAlertAction.AlertLeaveMyPlaygroundSnackbar) + loadPlaygrounds() + _alertAction.emit(AlertLeaveMyPlaygroundSnackbar) }.onFailure { - _alertAction.emit(PlaygroundAlertAction.AlertFailToDeleteMyFootprintSnackbar) + _alertAction.emit(AlertFailToLeavePlaygroundSnackbar) } } } - fun updateAddressLine(addressLine: String) { - _addressLine.postValue(addressLine) + fun leaveAndRegisterPlayground() { + viewModelScope.launch { + deletePlaygroundLeaveUseCase() + .onSuccess { + _mapAction.value = RegisterMyPlayground + }.onFailure { + _alertAction.emit(AlertFailToLeavePlaygroundSnackbar) + } + } } - fun updateRefreshBtnVisibility(visible: Boolean) { - _refreshBtnVisible.value = visible + fun leaveAndJoinPlayground() { + viewModelScope.launch { + deletePlaygroundLeaveUseCase() + .onSuccess { + updateUiState(PlaygroundUiState.Loading) + joinPlayground() + }.onFailure { + _alertAction.emit(AlertFailToLeavePlaygroundSnackbar) + } + } } - fun updateRegisterPlaygroundBtnCameraIdle(cameraIdle: Boolean) { - _playgroundRegisterBtn.value = PlaygroundRegisterBtnUiModel(cameraIdle = cameraIdle) + fun updateCameraIdle(cameraIdle: Boolean) { + val currentUiState = uiState.value + if (currentUiState is PlaygroundUiState.RegisteringPlayground) { + val updatedBtnClickable = + currentUiState.playgroundRegisterBtnClickable.copy(cameraIdle = cameraIdle) + updateUiState(currentUiState.copy(playgroundRegisterBtnClickable = updatedBtnClickable)) + } } - fun updateRegisterPlaygroundBtnInKorea(inKorea: Boolean) { - _playgroundRegisterBtn.postValue(playgroundRegisterBtn.value?.copy(inKorea = inKorea)) + fun updateAddressAndInKorea( + address: String, + inKorea: Boolean, + ) { + val currentUiState = uiState.value + if (currentUiState is PlaygroundUiState.RegisteringPlayground) { + val updatedBtnClickable = + currentUiState.playgroundRegisterBtnClickable.copy(inKorea = inKorea) + _uiState.postValue( + currentUiState.copy( + address = address, + playgroundRegisterBtnClickable = updatedBtnClickable, + ), + ) + } } - fun changeTrackingModeToNoFollow() { - _changeTrackingModeAction.emit(PlaygroundTrackingModeAction.NoFollowTrackingMode) + fun monitorDistanceAndManagePlayStatus(latLng: LatLng) { + val distanceResults = FloatArray(1) + val myPlayground = myPlayground.value ?: return + val position = myPlayground.marker.position + Location.distanceBetween( + latLng.latitude, + latLng.longitude, + position.latitude, + position.longitude, + distanceResults, + ) + val distance = distanceResults[0] + if (withinPlaygroundRange(distance) || outOfPlaygroundRange(distance)) { + updatePlaygroundArrival(latLng) + loadPlaygroundInfo(myPlayground.id) + } } } diff --git a/android/app/src/main/java/com/happy/friendogly/presentation/ui/recentpet/RecentPetActionHandler.kt b/android/app/src/main/java/com/happy/friendogly/presentation/ui/recentpet/RecentPetActionHandler.kt index 938a1b20b..cf648cf9d 100644 --- a/android/app/src/main/java/com/happy/friendogly/presentation/ui/recentpet/RecentPetActionHandler.kt +++ b/android/app/src/main/java/com/happy/friendogly/presentation/ui/recentpet/RecentPetActionHandler.kt @@ -1,5 +1,7 @@ package com.happy.friendogly.presentation.ui.recentpet interface RecentPetActionHandler { - fun navigateToProfile(id: Long) + fun navigateToProfile(memberId: Long) + + fun navigateToPetImage(petImageUrl: String) } diff --git a/android/app/src/main/java/com/happy/friendogly/presentation/ui/recentpet/RecentPetActivity.kt b/android/app/src/main/java/com/happy/friendogly/presentation/ui/recentpet/RecentPetActivity.kt index c4b98e672..f70636a38 100644 --- a/android/app/src/main/java/com/happy/friendogly/presentation/ui/recentpet/RecentPetActivity.kt +++ b/android/app/src/main/java/com/happy/friendogly/presentation/ui/recentpet/RecentPetActivity.kt @@ -8,6 +8,7 @@ import com.happy.friendogly.databinding.ActivityRecentPetBinding import com.happy.friendogly.presentation.base.BaseActivity import com.happy.friendogly.presentation.base.observeEvent import com.happy.friendogly.presentation.ui.otherprofile.OtherProfileActivity +import com.happy.friendogly.presentation.ui.petimage.PetImageActivity import com.happy.friendogly.presentation.ui.recentpet.adapter.RecentPetAdapter import dagger.hilt.android.AndroidEntryPoint @@ -35,11 +36,19 @@ class RecentPetActivity : BaseActivity(R.layout.activi viewModel.navigateAction.observeEvent(this@RecentPetActivity) { navigateAction -> when (navigateAction) { is RecentPetNavigationAction.NavigateToBack -> finish() - is RecentPetNavigationAction.NavigateToOtherProfile -> - OtherProfileActivity.getIntent( - this, - id = navigateAction.memberId, - ) + is RecentPetNavigationAction.NavigateToOtherProfile -> { + val intent = + OtherProfileActivity.getIntent( + this, + id = navigateAction.memberId, + ) + startActivity(intent) + } + + is RecentPetNavigationAction.NavigateToPetImage -> { + val intent = PetImageActivity.getIntent(this, navigateAction.petImageUrl) + startActivity(intent) + } } } } diff --git a/android/app/src/main/java/com/happy/friendogly/presentation/ui/recentpet/RecentPetNavigationAction.kt b/android/app/src/main/java/com/happy/friendogly/presentation/ui/recentpet/RecentPetNavigationAction.kt index 94b9d26e5..7390372c6 100644 --- a/android/app/src/main/java/com/happy/friendogly/presentation/ui/recentpet/RecentPetNavigationAction.kt +++ b/android/app/src/main/java/com/happy/friendogly/presentation/ui/recentpet/RecentPetNavigationAction.kt @@ -4,4 +4,6 @@ sealed interface RecentPetNavigationAction { data object NavigateToBack : RecentPetNavigationAction data class NavigateToOtherProfile(val memberId: Long) : RecentPetNavigationAction + + data class NavigateToPetImage(val petImageUrl: String) : RecentPetNavigationAction } diff --git a/android/app/src/main/java/com/happy/friendogly/presentation/ui/recentpet/RecentPetUiState.kt b/android/app/src/main/java/com/happy/friendogly/presentation/ui/recentpet/RecentPetUiState.kt index 1a3950685..af75fa785 100644 --- a/android/app/src/main/java/com/happy/friendogly/presentation/ui/recentpet/RecentPetUiState.kt +++ b/android/app/src/main/java/com/happy/friendogly/presentation/ui/recentpet/RecentPetUiState.kt @@ -78,13 +78,13 @@ data class RecentPetView( ): RecentPetView { return RecentPetView( index = index, + last = last, memberId = recentPet.memberId, imageUrl = recentPet.imgUrl, name = recentPet.name, birthday = recentPet.birthday, gender = recentPet.gender, sizeType = recentPet.sizeType, - last = last, ) } } diff --git a/android/app/src/main/java/com/happy/friendogly/presentation/ui/recentpet/RecentPetViewModel.kt b/android/app/src/main/java/com/happy/friendogly/presentation/ui/recentpet/RecentPetViewModel.kt index fd3de63a7..97b05aae1 100644 --- a/android/app/src/main/java/com/happy/friendogly/presentation/ui/recentpet/RecentPetViewModel.kt +++ b/android/app/src/main/java/com/happy/friendogly/presentation/ui/recentpet/RecentPetViewModel.kt @@ -42,7 +42,11 @@ class RecentPetViewModel _navigateAction.emit(RecentPetNavigationAction.NavigateToBack) } - override fun navigateToProfile(id: Long) { - _navigateAction.emit(RecentPetNavigationAction.NavigateToOtherProfile(memberId = id)) + override fun navigateToProfile(memberId: Long) { + _navigateAction.emit(RecentPetNavigationAction.NavigateToOtherProfile(memberId = memberId)) + } + + override fun navigateToPetImage(petImageUrl: String) { + _navigateAction.emit(RecentPetNavigationAction.NavigateToPetImage(petImageUrl = petImageUrl)) } } diff --git a/android/app/src/main/java/com/happy/friendogly/presentation/ui/setting/SettingActivity.kt b/android/app/src/main/java/com/happy/friendogly/presentation/ui/setting/SettingActivity.kt index dd55db417..26e51e484 100644 --- a/android/app/src/main/java/com/happy/friendogly/presentation/ui/setting/SettingActivity.kt +++ b/android/app/src/main/java/com/happy/friendogly/presentation/ui/setting/SettingActivity.kt @@ -48,7 +48,7 @@ class SettingActivity : BaseActivity(R.layout.activity_s ).show() with(binding) { alarmSettingsChattingPushSwitch.isChecked = false - alarmSettingsWoofPushSwitch.isChecked = false + alarmSettingsPlaygroundPushSwitch.isChecked = false } } } @@ -66,11 +66,11 @@ class SettingActivity : BaseActivity(R.layout.activity_s } else { uiState.chattingAlarmPushPermitted } - alarmSettingsWoofPushSwitch.isChecked = + alarmSettingsPlaygroundPushSwitch.isChecked = if (isValidSDK) { - uiState.woofAlarmPushPermitted && hasPermission + uiState.playgroundAlarmPushPermitted && hasPermission } else { - uiState.woofAlarmPushPermitted + uiState.playgroundAlarmPushPermitted } } } @@ -84,11 +84,11 @@ class SettingActivity : BaseActivity(R.layout.activity_s } viewModel.saveChattingAlarmSetting(isChecked) } - alarmSettingsWoofPushSwitch.setOnCheckedChangeListener { _, isChecked -> + alarmSettingsPlaygroundPushSwitch.setOnCheckedChangeListener { _, isChecked -> if (isChecked) { requestNotificationPermission() } - viewModel.saveWoofAlarmSetting(isChecked) + viewModel.savePlaygroundAlarmSetting(isChecked) } } } diff --git a/android/app/src/main/java/com/happy/friendogly/presentation/ui/setting/SettingUiState.kt b/android/app/src/main/java/com/happy/friendogly/presentation/ui/setting/SettingUiState.kt index dec4977d2..126524c4a 100644 --- a/android/app/src/main/java/com/happy/friendogly/presentation/ui/setting/SettingUiState.kt +++ b/android/app/src/main/java/com/happy/friendogly/presentation/ui/setting/SettingUiState.kt @@ -4,5 +4,5 @@ data class SettingUiState( val totalAlarmPushPermitted: Boolean = true, val chattingAlarmPushPermitted: Boolean = true, val clubAlarmPushPermitted: Boolean = true, - val woofAlarmPushPermitted: Boolean = true, + val playgroundAlarmPushPermitted: Boolean = true, ) diff --git a/android/app/src/main/java/com/happy/friendogly/presentation/ui/setting/SettingViewModel.kt b/android/app/src/main/java/com/happy/friendogly/presentation/ui/setting/SettingViewModel.kt index 3710c8e5d..0efca296d 100644 --- a/android/app/src/main/java/com/happy/friendogly/presentation/ui/setting/SettingViewModel.kt +++ b/android/app/src/main/java/com/happy/friendogly/presentation/ui/setting/SettingViewModel.kt @@ -7,10 +7,10 @@ import com.happy.friendogly.domain.fold import com.happy.friendogly.domain.usecase.DeleteMemberUseCase import com.happy.friendogly.domain.usecase.DeleteTokenUseCase import com.happy.friendogly.domain.usecase.GetChatAlarmUseCase -import com.happy.friendogly.domain.usecase.GetWoofAlarmUseCase +import com.happy.friendogly.domain.usecase.GetPlaygroundAlarmUseCase import com.happy.friendogly.domain.usecase.PostLogoutUseCase import com.happy.friendogly.domain.usecase.SaveChatAlarmUseCase -import com.happy.friendogly.domain.usecase.SaveWoofAlarmUseCase +import com.happy.friendogly.domain.usecase.SavePlaygroundAlarmUseCase import com.happy.friendogly.presentation.base.BaseViewModel import com.happy.friendogly.presentation.base.Event import com.happy.friendogly.presentation.base.emit @@ -23,9 +23,9 @@ class SettingViewModel constructor( private val deleteTokenUseCase: DeleteTokenUseCase, private val getChatAlarmUseCase: GetChatAlarmUseCase, - private val getWoofAlarmUseCase: GetWoofAlarmUseCase, + private val getPlaygroundAlarmUseCase: GetPlaygroundAlarmUseCase, private val saveChatAlarmUseCase: SaveChatAlarmUseCase, - private val saveWoofAlarmUseCase: SaveWoofAlarmUseCase, + private val savePlaygroundAlarmUseCase: SavePlaygroundAlarmUseCase, private val deleteMemberUseCase: DeleteMemberUseCase, private val postLogoutUseCase: PostLogoutUseCase, ) : BaseViewModel() { @@ -60,10 +60,10 @@ class SettingViewModel } launch { - getWoofAlarmUseCase().onSuccess { + getPlaygroundAlarmUseCase().onSuccess { _uiState.value = _uiState.value?.copy( - woofAlarmPushPermitted = it, + playgroundAlarmPushPermitted = it, ) }.onFailure { // TODO 에러핸들링 @@ -77,9 +77,9 @@ class SettingViewModel } } - fun saveWoofAlarmSetting(checked: Boolean) { + fun savePlaygroundAlarmSetting(checked: Boolean) { launch { - saveWoofAlarmUseCase(checked) + savePlaygroundAlarmUseCase(checked) } } diff --git a/android/app/src/main/java/com/happy/friendogly/presentation/ui/message/MessageActivity.kt b/android/app/src/main/java/com/happy/friendogly/presentation/ui/statemessage/StateMessageActivity.kt similarity index 78% rename from android/app/src/main/java/com/happy/friendogly/presentation/ui/message/MessageActivity.kt rename to android/app/src/main/java/com/happy/friendogly/presentation/ui/statemessage/StateMessageActivity.kt index 5f6abd1f7..28f8515ab 100644 --- a/android/app/src/main/java/com/happy/friendogly/presentation/ui/message/MessageActivity.kt +++ b/android/app/src/main/java/com/happy/friendogly/presentation/ui/statemessage/StateMessageActivity.kt @@ -1,4 +1,4 @@ -package com.happy.friendogly.presentation.ui.message +package com.happy.friendogly.presentation.ui.statemessage import android.app.Activity import android.content.Context @@ -8,20 +8,20 @@ import android.text.TextWatcher import android.view.inputmethod.EditorInfo import androidx.activity.viewModels import com.happy.friendogly.R -import com.happy.friendogly.databinding.ActivityMessageBinding +import com.happy.friendogly.databinding.ActivityStateMessageBinding import com.happy.friendogly.presentation.base.BaseActivity import com.happy.friendogly.presentation.base.observeEvent -import com.happy.friendogly.presentation.ui.message.action.MessageAlertAction -import com.happy.friendogly.presentation.ui.message.action.MessageNavigateAction -import com.happy.friendogly.presentation.ui.message.viewmodel.MessageViewModel import com.happy.friendogly.presentation.ui.playground.PlaygroundFragment.Companion.EXTRA_MESSAGE_UPDATED +import com.happy.friendogly.presentation.ui.statemessage.action.StateMessageAlertAction +import com.happy.friendogly.presentation.ui.statemessage.action.StateMessageNavigateAction +import com.happy.friendogly.presentation.ui.statemessage.viewmodel.StateViewModel import com.happy.friendogly.presentation.utils.showKeyboard import dagger.hilt.android.AndroidEntryPoint @AndroidEntryPoint -class MessageActivity : - BaseActivity(R.layout.activity_message) { - private val viewModel by viewModels() +class StateMessageActivity : + BaseActivity(R.layout.activity_state_message) { + private val viewModel by viewModels() override fun initCreateView() { binding.etMessage.requestFocus() @@ -45,15 +45,15 @@ class MessageActivity : private fun setupObserving() { viewModel.alertAction.observeEvent(this) { event -> when (event) { - is MessageAlertAction.AlertFailToPatchPlaygroundMessage -> { - showSnackbar(getString(R.string.fail_to_patch_message)) + is StateMessageAlertAction.AlertFailToPatchPlaygroundStateMessage -> { + showSnackbar(getString(R.string.fail_to_patch_state_message)) } } } viewModel.navigateAction.observeEvent(this) { event -> when (event) { - is MessageNavigateAction.FinishMessageActivity -> { + is StateMessageNavigateAction.FinishStateMessageActivity -> { val resultIntent = Intent().apply { putExtra(EXTRA_MESSAGE_UPDATED, event.messageUpdated) @@ -109,7 +109,7 @@ class MessageActivity : context: Context, message: String, ): Intent { - return Intent(context, MessageActivity::class.java).apply { + return Intent(context, StateMessageActivity::class.java).apply { putExtra(PUT_EXTRA_MESSAGE, message) } } diff --git a/android/app/src/main/java/com/happy/friendogly/presentation/ui/statemessage/action/StateMessageActionHandler.kt b/android/app/src/main/java/com/happy/friendogly/presentation/ui/statemessage/action/StateMessageActionHandler.kt new file mode 100644 index 000000000..20f84f91d --- /dev/null +++ b/android/app/src/main/java/com/happy/friendogly/presentation/ui/statemessage/action/StateMessageActionHandler.kt @@ -0,0 +1,9 @@ +package com.happy.friendogly.presentation.ui.statemessage.action + +interface StateMessageActionHandler { + fun clickCancelBtn() + + fun clickConfirmBtn() + + fun clearMessageBtn() +} diff --git a/android/app/src/main/java/com/happy/friendogly/presentation/ui/statemessage/action/StateMessageAlertAction.kt b/android/app/src/main/java/com/happy/friendogly/presentation/ui/statemessage/action/StateMessageAlertAction.kt new file mode 100644 index 000000000..588f97585 --- /dev/null +++ b/android/app/src/main/java/com/happy/friendogly/presentation/ui/statemessage/action/StateMessageAlertAction.kt @@ -0,0 +1,5 @@ +package com.happy.friendogly.presentation.ui.statemessage.action + +interface StateMessageAlertAction { + data object AlertFailToPatchPlaygroundStateMessage : StateMessageAlertAction +} diff --git a/android/app/src/main/java/com/happy/friendogly/presentation/ui/statemessage/action/StateMessageNavigateAction.kt b/android/app/src/main/java/com/happy/friendogly/presentation/ui/statemessage/action/StateMessageNavigateAction.kt new file mode 100644 index 000000000..a21986a0c --- /dev/null +++ b/android/app/src/main/java/com/happy/friendogly/presentation/ui/statemessage/action/StateMessageNavigateAction.kt @@ -0,0 +1,5 @@ +package com.happy.friendogly.presentation.ui.statemessage.action + +interface StateMessageNavigateAction { + data class FinishStateMessageActivity(val messageUpdated: Boolean) : StateMessageNavigateAction +} diff --git a/android/app/src/main/java/com/happy/friendogly/presentation/ui/message/util/MessageBindingAdapter.kt b/android/app/src/main/java/com/happy/friendogly/presentation/ui/statemessage/util/StateMessageBindingAdapter.kt similarity index 73% rename from android/app/src/main/java/com/happy/friendogly/presentation/ui/message/util/MessageBindingAdapter.kt rename to android/app/src/main/java/com/happy/friendogly/presentation/ui/statemessage/util/StateMessageBindingAdapter.kt index 2eedaf507..ad84adefa 100644 --- a/android/app/src/main/java/com/happy/friendogly/presentation/ui/message/util/MessageBindingAdapter.kt +++ b/android/app/src/main/java/com/happy/friendogly/presentation/ui/statemessage/util/StateMessageBindingAdapter.kt @@ -1,4 +1,4 @@ -package com.happy.friendogly.presentation.ui.message.util +package com.happy.friendogly.presentation.ui.statemessage.util import android.widget.ImageView import android.widget.TextView @@ -8,7 +8,7 @@ import com.happy.friendogly.R @BindingAdapter("messageLength") fun TextView.bindMessageLength(message: String) { - text = resources.getString(R.string.message_length, message.length) + text = resources.getString(R.string.state_message_length, message.length) } @BindingAdapter("clearBtnVisibility") diff --git a/android/app/src/main/java/com/happy/friendogly/presentation/ui/message/viewmodel/MessageViewModel.kt b/android/app/src/main/java/com/happy/friendogly/presentation/ui/statemessage/viewmodel/StateViewModel.kt similarity index 53% rename from android/app/src/main/java/com/happy/friendogly/presentation/ui/message/viewmodel/MessageViewModel.kt rename to android/app/src/main/java/com/happy/friendogly/presentation/ui/statemessage/viewmodel/StateViewModel.kt index d7b1beeb6..7fa095513 100644 --- a/android/app/src/main/java/com/happy/friendogly/presentation/ui/message/viewmodel/MessageViewModel.kt +++ b/android/app/src/main/java/com/happy/friendogly/presentation/ui/statemessage/viewmodel/StateViewModel.kt @@ -1,4 +1,4 @@ -package com.happy.friendogly.presentation.ui.message.viewmodel +package com.happy.friendogly.presentation.ui.statemessage.viewmodel import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData @@ -7,39 +7,39 @@ import androidx.lifecycle.viewModelScope import com.happy.friendogly.domain.usecase.PathPlaygroundMessageUseCase import com.happy.friendogly.presentation.base.Event import com.happy.friendogly.presentation.base.emit -import com.happy.friendogly.presentation.ui.message.action.MessageActionHandler -import com.happy.friendogly.presentation.ui.message.action.MessageAlertAction -import com.happy.friendogly.presentation.ui.message.action.MessageNavigateAction +import com.happy.friendogly.presentation.ui.statemessage.action.StateMessageActionHandler +import com.happy.friendogly.presentation.ui.statemessage.action.StateMessageAlertAction +import com.happy.friendogly.presentation.ui.statemessage.action.StateMessageNavigateAction import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.launch import javax.inject.Inject @HiltViewModel -class MessageViewModel +class StateViewModel @Inject constructor( private val patchPlaygroundMessageUseCase: PathPlaygroundMessageUseCase, - ) : ViewModel(), MessageActionHandler { + ) : ViewModel(), StateMessageActionHandler { private val _message: MutableLiveData = MutableLiveData() val message: LiveData get() = _message - private val _alertAction: MutableLiveData> = MutableLiveData() - val alertAction: LiveData> get() = _alertAction + private val _alertAction: MutableLiveData> = MutableLiveData() + val alertAction: LiveData> get() = _alertAction - private val _navigateAction: MutableLiveData> = MutableLiveData() - val navigateAction: LiveData> get() = _navigateAction + private val _navigateAction: MutableLiveData> = MutableLiveData() + val navigateAction: LiveData> get() = _navigateAction override fun clickCancelBtn() { - _navigateAction.emit(MessageNavigateAction.FinishMessageActivity(messageUpdated = false)) + _navigateAction.emit(StateMessageNavigateAction.FinishStateMessageActivity(messageUpdated = false)) } override fun clickConfirmBtn() { viewModelScope.launch { val newMessage = message.value ?: return@launch patchPlaygroundMessageUseCase(newMessage).onSuccess { - _navigateAction.emit(MessageNavigateAction.FinishMessageActivity(messageUpdated = true)) + _navigateAction.emit(StateMessageNavigateAction.FinishStateMessageActivity(messageUpdated = true)) }.onFailure { - _alertAction.emit(MessageAlertAction.AlertFailToPatchPlaygroundMessage) + _alertAction.emit(StateMessageAlertAction.AlertFailToPatchPlaygroundStateMessage) } } } diff --git a/android/app/src/main/java/com/happy/friendogly/presentation/utils/AnalyticsHelperExtensions.kt b/android/app/src/main/java/com/happy/friendogly/presentation/utils/AnalyticsHelperExtensions.kt index a40814104..c692c55f6 100644 --- a/android/app/src/main/java/com/happy/friendogly/presentation/utils/AnalyticsHelperExtensions.kt +++ b/android/app/src/main/java/com/happy/friendogly/presentation/utils/AnalyticsHelperExtensions.kt @@ -50,8 +50,12 @@ fun AnalyticsHelper.logMyPlaygroundBtnClicked() { logEvent(type = Types.MY_PLAYGROUND_BTN_CLICKED) } -fun AnalyticsHelper.logRefreshBtnClicked() { - logEvent(type = Types.REFRESH_BTN_CLICKED) +fun AnalyticsHelper.logPlaygroundRefreshBtnClicked() { + logEvent(type = Types.PLAYGROUND_REFRESH_BTN_CLICKED) +} + +fun AnalyticsHelper.logPlaygroundInfoRefreshBtnClicked() { + logEvent(type = Types.PLAYGROUND_INFO_REFRESH_BTN_CLICKED) } fun AnalyticsHelper.logBackBtnClicked() { @@ -74,13 +78,29 @@ fun AnalyticsHelper.logPetImageClicked() { logEvent(type = Types.PET_IMAGE_CLICKED) } +fun AnalyticsHelper.logStateMessageClicked() { + logEvent(type = Types.STATE_MESSAGE_CLICKED) +} + +fun AnalyticsHelper.logJoinPlaygroundClicked() { + logEvent(type = Types.JOIN_PLAYGROUND_CLICKED) +} + +fun AnalyticsHelper.logLeavePlaygroundClicked() { + logEvent(type = Types.LEAVE_PLAYGROUND_CLICKED) +} + +fun AnalyticsHelper.logCheckPetExistenceClicked() { + logEvent(type = Types.CHECK_PET_EXISTENCE_CLICKED) +} + fun AnalyticsHelper.logPlaygroundSize(size: Int) { - logEvent(type = Types.WOOF_FRAGMENT, ParamKeys.PLAYGROUND_SIZE to size) + logEvent(type = Types.PLAYGROUND_FRAGMENT, ParamKeys.PLAYGROUND_SIZE to size) } fun AnalyticsHelper.logPetExistence(isExistPet: Boolean) { logEvent( - type = Types.WOOF_FRAGMENT, + type = Types.PLAYGROUND_FRAGMENT, ParamKeys.PET_EXISTENCE to isExistPet, ) } diff --git a/android/app/src/main/java/com/happy/friendogly/remote/source/PlaygroundDataSourceImpl.kt b/android/app/src/main/java/com/happy/friendogly/remote/source/PlaygroundDataSourceImpl.kt index 56de4251b..7050b2609 100644 --- a/android/app/src/main/java/com/happy/friendogly/remote/source/PlaygroundDataSourceImpl.kt +++ b/android/app/src/main/java/com/happy/friendogly/remote/source/PlaygroundDataSourceImpl.kt @@ -40,11 +40,11 @@ class PlaygroundDataSourceImpl } } - override suspend fun getFootprintMarkBtnInfo(): Result { + override suspend fun getPetExistence(): Result { return runCatching { service.getPetExistence().data.toData() } } - override suspend fun getNearFootprints(): Result> { + override suspend fun getNearPlaygrounds(): Result> { return runCatching { service.getPlaygrounds().data.toData() } } diff --git a/android/app/src/main/res/drawable/ic_close_white.xml b/android/app/src/main/res/drawable/ic_close_white.xml index a38e43a81..14f77c59f 100644 --- a/android/app/src/main/res/drawable/ic_close_white.xml +++ b/android/app/src/main/res/drawable/ic_close_white.xml @@ -6,14 +6,14 @@ diff --git a/android/app/src/main/res/drawable/ic_refresh.xml b/android/app/src/main/res/drawable/ic_refresh_coral.xml similarity index 100% rename from android/app/src/main/res/drawable/ic_refresh.xml rename to android/app/src/main/res/drawable/ic_refresh_coral.xml diff --git a/android/app/src/main/res/drawable/ic_refresh_gray.xml b/android/app/src/main/res/drawable/ic_refresh_gray.xml new file mode 100644 index 000000000..8d897cc78 --- /dev/null +++ b/android/app/src/main/res/drawable/ic_refresh_gray.xml @@ -0,0 +1,20 @@ + + + + diff --git a/android/app/src/main/res/layout/activity_setting.xml b/android/app/src/main/res/layout/activity_setting.xml index 8fc935cb4..20680102e 100644 --- a/android/app/src/main/res/layout/activity_setting.xml +++ b/android/app/src/main/res/layout/activity_setting.xml @@ -151,29 +151,29 @@ tools:checked="true" /> + app:title="@{String.format(@string/alarm_settings_playground_push)}" /> + type="com.happy.friendogly.presentation.ui.statemessage.viewmodel.StateViewModel" /> @@ -35,7 +35,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="16dp" - android:text="@string/message_title" + android:text="@string/state_message_title" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> @@ -49,7 +49,7 @@ android:clickable="true" android:onClick="@{() -> vm.clickConfirmBtn()}" android:padding="6dp" - android:text="@string/message_confirm" + android:text="@string/state_message_confirm" app:layout_constraintBottom_toBottomOf="@id/tv_message_title" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="@id/tv_message_title" /> @@ -64,13 +64,13 @@ android:backgroundTint="@color/gray600" android:focusableInTouchMode="true" android:gravity="center" + android:imeOptions="actionDone" + android:inputType="text" android:maxLength="20" android:maxLines="1" android:paddingBottom="18dp" android:singleLine="true" - android:inputType="text" android:text="@{vm.message}" - android:imeOptions="actionDone" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" @@ -101,6 +101,5 @@ app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/et_message" /> - diff --git a/android/app/src/main/res/layout/dialog_coral_default_alert.xml b/android/app/src/main/res/layout/dialog_coral_default_alert.xml index 07e2fc891..4560c33f5 100644 --- a/android/app/src/main/res/layout/dialog_coral_default_alert.xml +++ b/android/app/src/main/res/layout/dialog_coral_default_alert.xml @@ -18,7 +18,7 @@ - - - - - - - - - - - - + android:layout_height="match_parent" /> - + tools:visibility="visible"> @@ -352,7 +294,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="4dp" - android:text="@{String.valueOf(vm.playgroundSummary.arrivedPetCount)}" + android:text="@{String.valueOf(vm.playgroundSummary.totalPetCount)}" app:layout_constraintEnd_toStartOf="@id/tv_playground_total_pet_count" app:layout_constraintStart_toStartOf="@id/tv_playground_participation_status" app:layout_constraintTop_toBottomOf="@id/tv_playground_participation_status" @@ -383,7 +325,7 @@ app:layout_constraintTop_toTopOf="@id/layout_playground_summary_pet_count" /> + + + + + + + + + + + + + + + + + - - - - - - 모든 채팅 메시지 알림 모임 알림 가입한 모임의 알림 - 산책 알림 - 다른 강아지의 산책 여부 알림 + 놀이터 알림 + 다른 강아지의 놀이터 참여 여부 알림 설정 탈퇴하기 로그아웃 @@ -244,34 +244,31 @@ 나가기 머무르기 - - %s\'s 댕댕이 - 위치 권한이 필요합니다. - 설정 - package:%s - 발자국을 등록했어요! - 강아지를 등록해야 발자국을 남길 수 있어요! - %d살 - %d개월 - 내가 참여한 놀이터가 존재하지 않아요! - MY - 놀이터 등록하기 - 발자국 등록은 한국에서만 가능합니다! + + 위치 권한이 필요합니다. + 설정 + package:%s + 놀이터를 등록했어요! + 강아지를 등록해야 놀이터에 참여할 수 있어요! + %d살 + %d개월 + 내가 참여한 놀이터가 존재하지 않아요! + MY + 놀이터 등록하기 + 놀이터 등록은 한국에서만 가능합니다! 놀이터에 다른 강아지들도 참여할 수 있어요! \n친구들을 만나 반갑게 인사해보세요~ - 이 위치에 놀이터 등록 - 실시간으로 위치를 추적 중입니다. - 정보 - 소개 - 새로고침 - 산책 종료하기 + 이 위치에 놀이터 등록 + 실시간으로 위치를 추적 중입니다. + 정보 + 소개 + 새로고침 + 놀이터에 참여했어요! 놀이터에서 나갔습니다! 오랜 시간 자리를 비워 자동으로 놀이터에서 나갔습니다! - 이미 참여중인 놀이터가 존재합니다! 놀이터 등록 범위가 다른 놀이터와 중복됩니다! - 산책을 종료했어요! - 전체 - 대한민국 + 전체 + 대한민국 놀이터 참여하기 놀이터 나가기 노는중 @@ -280,23 +277,24 @@ 참여 현황 마리 상태메시지를 입력해주세요. - - 강아지 등록 여부를 확인하지 못했습니다! - 놀이터들을 불러오지 못했습니다! - 놀이터 등록에 실패했습니다! - 놀이터 도착 여부를 업데이트하지 못했습니다! - 산책 종료에 실패했습니다! - 발자국 삭제에 실패했습니다! - 놀이터 정보를 불러오지 못했습니다! - 놀이터 요약 정보를 불러오지 못했습니다! - 놀이터 참여하기에 실패했습니다! - - 상태메시지 - 취소 - 확인 - %d / 20 - 상태메시지 설정에 실패했습니다! - + 새로운 놀이터 참여 + 현재 참여 중인 놀이터에서 나가고,\n새로운 놀이터에 참여하시겠습니까? + + 강아지 등록 여부를 확인하지 못했습니다! + 놀이터들을 불러오지 못했습니다! + 놀이터 등록에 실패했습니다! + 놀이터 도착 여부를 업데이트하지 못했습니다! + 놀이터 나가기에 실패했습니다! + 놀이터 정보를 불러오지 못했습니다! + 놀이터 요약 정보를 불러오지 못했습니다! + 놀이터 참여하기에 실패했습니다! + + + 상태메시지 + 취소 + 확인 + %d / 20 + 상태메시지 설정에 실패했습니다! @@ -309,7 +307,7 @@ 알람 허용 사용 설정 알람을 설정하지 않으면, 채팅과 모임 알람을 받을 수 없어요! - 알림을 거부하여 모임 알림을 받을 수 없습니다. + 알림을 거부하여 모임 알림을 받을 수 없습니다. 위치 허용 사용 설정 위치 설정하지 않으면, 친구를 찾을 수 없어요! 취소 diff --git a/android/app/src/test/java/com/happy/friendogly/presentation/ui/setting/SettingViewModelTest.kt b/android/app/src/test/java/com/happy/friendogly/presentation/ui/setting/SettingViewModelTest.kt index e8ffdb647..72b6bdeec 100644 --- a/android/app/src/test/java/com/happy/friendogly/presentation/ui/setting/SettingViewModelTest.kt +++ b/android/app/src/test/java/com/happy/friendogly/presentation/ui/setting/SettingViewModelTest.kt @@ -5,10 +5,10 @@ import com.happy.friendogly.domain.error.DataError import com.happy.friendogly.domain.usecase.DeleteMemberUseCase import com.happy.friendogly.domain.usecase.DeleteTokenUseCase import com.happy.friendogly.domain.usecase.GetChatAlarmUseCase -import com.happy.friendogly.domain.usecase.GetWoofAlarmUseCase +import com.happy.friendogly.domain.usecase.GetPlaygroundAlarmUseCase import com.happy.friendogly.domain.usecase.PostLogoutUseCase import com.happy.friendogly.domain.usecase.SaveChatAlarmUseCase -import com.happy.friendogly.domain.usecase.SaveWoofAlarmUseCase +import com.happy.friendogly.domain.usecase.SavePlaygroundAlarmUseCase import com.happy.friendogly.utils.CoroutinesTestExtension import com.happy.friendogly.utils.InstantTaskExecutorExtension import io.mockk.coEvery @@ -38,13 +38,13 @@ class SettingViewModelTest { private lateinit var getChatAlarmUseCase: GetChatAlarmUseCase @MockK - private lateinit var getWoofAlarmUseCase: GetWoofAlarmUseCase + private lateinit var getPlaygroundAlarmUseCase: GetPlaygroundAlarmUseCase @MockK private lateinit var saveChatAlarmUseCase: SaveChatAlarmUseCase @MockK - private lateinit var saveWoofAlarmUseCase: SaveWoofAlarmUseCase + private lateinit var savePlaygroundAlarmUseCase: SavePlaygroundAlarmUseCase @MockK private lateinit var deleteMemberUseCase: DeleteMemberUseCase @@ -55,14 +55,14 @@ class SettingViewModelTest { @BeforeEach fun setUp() { coEvery { getChatAlarmUseCase() } returns Result.success(true) - coEvery { getWoofAlarmUseCase() } returns Result.success(true) + coEvery { getPlaygroundAlarmUseCase() } returns Result.success(true) viewModel = SettingViewModel( deleteTokenUseCase, getChatAlarmUseCase, - getWoofAlarmUseCase, + getPlaygroundAlarmUseCase, saveChatAlarmUseCase, - saveWoofAlarmUseCase, + savePlaygroundAlarmUseCase, deleteMemberUseCase, postLogoutUseCase, ) @@ -74,7 +74,7 @@ class SettingViewModelTest { // then val actual = viewModel.uiState.value ?: return@runTest Assertions.assertThat(actual.chattingAlarmPushPermitted).isEqualTo(true) - Assertions.assertThat(actual.woofAlarmPushPermitted).isEqualTo(true) + Assertions.assertThat(actual.playgroundAlarmPushPermitted).isEqualTo(true) } @Test @@ -82,15 +82,15 @@ class SettingViewModelTest { runTest { // given coEvery { saveChatAlarmUseCase(true) } returns Result.success(Unit) - coEvery { saveWoofAlarmUseCase(true) } returns Result.success(Unit) + coEvery { savePlaygroundAlarmUseCase(true) } returns Result.success(Unit) // when viewModel.saveChattingAlarmSetting(true) - viewModel.saveWoofAlarmSetting(true) + viewModel.savePlaygroundAlarmSetting(true) // then coVerify { saveChatAlarmUseCase(true) } - coVerify { saveWoofAlarmUseCase(true) } + coVerify { savePlaygroundAlarmUseCase(true) } } @Test