From 6ff64f54f26a3926fc482ac1560954477da81ee6 Mon Sep 17 00:00:00 2001
From: leeseokchan00 <112953135+leeseokchan00@users.noreply.github.com>
Date: Wed, 13 Nov 2024 00:56:08 +0900
Subject: [PATCH 01/30] feature: Add firebase messaging setting
---
app/build.gradle.kts | 3 +++
app/src/main/AndroidManifest.xml | 8 ++++++-
.../offroad.app/OffRoadMessagingService.kt | 17 ++++++++++++++
build.gradle.kts | 1 +
gradle/libs.versions.toml | 23 +++++++++++++++++++
5 files changed, 51 insertions(+), 1 deletion(-)
create mode 100644 app/src/main/java/com/teamoffroad/offroad.app/OffRoadMessagingService.kt
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index d01ef5cf..a48f310a 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -3,6 +3,7 @@ import com.teamoffroad.app.setNamespace
plugins {
id("offroad.android.application")
+ alias(libs.plugins.google.services)
}
android {
@@ -54,4 +55,6 @@ dependencies {
implementation(project(":feature:explore"))
implementation(project(":feature:mypage"))
implementation(libs.kakao.user)
+ implementation(libs.bundles.firebase)
+ implementation(platform(libs.firebase.bom))
}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index fd09daa4..7b2578f2 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -23,7 +23,13 @@
android:theme="@style/Theme.Offroad"
android:usesCleartextTraffic="true"
tools:targetApi="31">
-
+
+
+
+
+
diff --git a/app/src/main/java/com/teamoffroad/offroad.app/OffRoadMessagingService.kt b/app/src/main/java/com/teamoffroad/offroad.app/OffRoadMessagingService.kt
new file mode 100644
index 00000000..731624ff
--- /dev/null
+++ b/app/src/main/java/com/teamoffroad/offroad.app/OffRoadMessagingService.kt
@@ -0,0 +1,17 @@
+package com.teamoffroad.offroad.app
+
+import com.google.firebase.messaging.FirebaseMessagingService
+import dagger.hilt.android.AndroidEntryPoint
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+@AndroidEntryPoint
+class OffRoadMessagingService : FirebaseMessagingService() {
+
+ override fun onNewToken(token: String) {
+ super.onNewToken(token)
+
+ }
+}
\ No newline at end of file
diff --git a/build.gradle.kts b/build.gradle.kts
index db622bad..3ef32e69 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -6,4 +6,5 @@ plugins {
alias(libs.plugins.android.library) apply false
alias(libs.plugins.daggers.hilt) apply false
alias(libs.plugins.ksp) apply false
+ alias(libs.plugins.google.services) apply false
}
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 8909954a..97c81079 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -73,6 +73,10 @@ naverMapSdk = "3.18.0"
googlePlayService = "21.1.0"
googleAccompanistPermissions = "0.32.0"
accompanistInsets = "0.31.5-beta"
+googleServices = "4.4.2"
+
+# Firebase
+firebase_bom = "33.5.1"
# Kakao
kakao = "2.20.6"
@@ -175,6 +179,14 @@ google-accompanist-permissions = { group = "com.google.accompanist", name = "acc
google-play-services-auth = { group = "com.google.android.gms", name = "play-services-auth", version.ref = "googlePlayService" }
accompanist-insets = { module = "com.google.accompanist:accompanist-insets", version.ref = "accompanistInsets" }
+# Firebase
+firebase-bom = { group = "com.google.firebase", name = "firebase-bom", version.ref = "firebase_bom" }
+firebase-messaging = { group = "com.google.firebase", name = "firebase-messaging-ktx" }
+firebase-crashlytics = { group = "com.google.firebase", name = "firebase-crashlytics-ktx" }
+firebase-analytics = { group = "com.google.firebase", name = "firebase-analytics-ktx" }
+firebase-remoteConfig = { group = "com.google.firebase", name = "firebase-config-ktx" }
+firebase-database = { group = "com.google.firebase", name = "firebase-database-ktx" }
+
# Kakao
kakao-user = { module = "com.kakao.sdk:v2-user", version.ref = "kakao" }
@@ -210,6 +222,9 @@ android-library = { id = "com.android.library", version.ref = "androidGradlePlug
# Kotlin Symbol Processing Plugin
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
+# Google
+google-services = { id = "com.google.gms.google-services", version.ref = "googleServices" }
+
[bundles]
# Offroad Map Libraries
offroad-map = [
@@ -225,3 +240,11 @@ offroad-camera = [
"camera-lifecycle",
"camera-view",
]
+
+firebase = [
+ "firebase-analytics",
+ "firebase-database",
+ "firebase-messaging",
+ "firebase-remoteConfig"
+]
+
From 0747a3c4e984ac7e1f7632a21268153d50e9e45d Mon Sep 17 00:00:00 2001
From: leeseokchan00 <112953135+leeseokchan00@users.noreply.github.com>
Date: Wed, 13 Nov 2024 01:37:54 +0900
Subject: [PATCH 02/30] feature: Add device token local save
---
.../offroad.app/OffRoadMessagingService.kt | 6 +++-
.../AutoSignInPreferencesDataSource.kt | 1 -
...DefaultDeviceTokenPreferencesDataSource.kt | 28 +++++++++++++++++++
.../DeviceTokenPreferencesDataSource.kt | 8 ++++++
.../core/common/data/di/DataModule.kt | 7 +++++
.../core/common/data/di/DataStoreModule.kt | 7 +++++
.../core/common/data/di/RepositoryModule.kt | 8 ++++++
.../repository/AutoSignInRepositoryImpl.kt | 2 --
.../repository/DeviceTokenRepositoryImpl.kt | 17 +++++++++++
.../repository/DeviceTokenRepository.kt | 8 ++++++
10 files changed, 88 insertions(+), 4 deletions(-)
create mode 100644 core/common/src/main/java/com/teamoffroad/core/common/data/datasource/DefaultDeviceTokenPreferencesDataSource.kt
create mode 100644 core/common/src/main/java/com/teamoffroad/core/common/data/datasource/DeviceTokenPreferencesDataSource.kt
create mode 100644 core/common/src/main/java/com/teamoffroad/core/common/data/repository/DeviceTokenRepositoryImpl.kt
create mode 100644 core/common/src/main/java/com/teamoffroad/core/common/domain/repository/DeviceTokenRepository.kt
diff --git a/app/src/main/java/com/teamoffroad/offroad.app/OffRoadMessagingService.kt b/app/src/main/java/com/teamoffroad/offroad.app/OffRoadMessagingService.kt
index 731624ff..1f461afe 100644
--- a/app/src/main/java/com/teamoffroad/offroad.app/OffRoadMessagingService.kt
+++ b/app/src/main/java/com/teamoffroad/offroad.app/OffRoadMessagingService.kt
@@ -1,6 +1,7 @@
package com.teamoffroad.offroad.app
import com.google.firebase.messaging.FirebaseMessagingService
+import com.teamoffroad.core.common.domain.repository.DeviceTokenRepository
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@@ -9,9 +10,12 @@ import javax.inject.Inject
@AndroidEntryPoint
class OffRoadMessagingService : FirebaseMessagingService() {
-
+ @Inject
+ lateinit var dataStore: DeviceTokenRepository
+
override fun onNewToken(token: String) {
super.onNewToken(token)
+ CoroutineScope(Dispatchers.IO).launch { dataStore.updateDeviceTokenEnabled(token) }
}
}
\ No newline at end of file
diff --git a/core/common/src/main/java/com/teamoffroad/core/common/data/datasource/AutoSignInPreferencesDataSource.kt b/core/common/src/main/java/com/teamoffroad/core/common/data/datasource/AutoSignInPreferencesDataSource.kt
index d76280d6..a324a567 100644
--- a/core/common/src/main/java/com/teamoffroad/core/common/data/datasource/AutoSignInPreferencesDataSource.kt
+++ b/core/common/src/main/java/com/teamoffroad/core/common/data/datasource/AutoSignInPreferencesDataSource.kt
@@ -4,6 +4,5 @@ import kotlinx.coroutines.flow.Flow
interface AutoSignInPreferencesDataSource {
val autoLogin: Flow
-
suspend fun setAutoLogin(autoLogin: Boolean)
}
diff --git a/core/common/src/main/java/com/teamoffroad/core/common/data/datasource/DefaultDeviceTokenPreferencesDataSource.kt b/core/common/src/main/java/com/teamoffroad/core/common/data/datasource/DefaultDeviceTokenPreferencesDataSource.kt
new file mode 100644
index 00000000..ec7137ba
--- /dev/null
+++ b/core/common/src/main/java/com/teamoffroad/core/common/data/datasource/DefaultDeviceTokenPreferencesDataSource.kt
@@ -0,0 +1,28 @@
+package com.teamoffroad.core.common.data.datasource
+
+import androidx.datastore.core.DataStore
+import androidx.datastore.preferences.core.Preferences
+import androidx.datastore.preferences.core.edit
+import androidx.datastore.preferences.core.stringPreferencesKey
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+import javax.inject.Inject
+
+class DefaultDeviceTokenPreferencesDataSource @Inject constructor(
+ private val dataStore: DataStore,
+) : DeviceTokenPreferencesDataSource {
+
+ object PreferencesKey {
+ val DEVICE_TOKEN_KEY = stringPreferencesKey("DEVICE_TOKEN_KEY")
+ }
+
+ override val deviceToken: Flow = dataStore.data.map { preferences ->
+ preferences[PreferencesKey.DEVICE_TOKEN_KEY].orEmpty()
+ }
+
+ override suspend fun setDeviceToken(deviceToken: String) {
+ dataStore.edit { preferences ->
+ preferences[PreferencesKey.DEVICE_TOKEN_KEY] = deviceToken
+ }
+ }
+}
\ No newline at end of file
diff --git a/core/common/src/main/java/com/teamoffroad/core/common/data/datasource/DeviceTokenPreferencesDataSource.kt b/core/common/src/main/java/com/teamoffroad/core/common/data/datasource/DeviceTokenPreferencesDataSource.kt
new file mode 100644
index 00000000..0125b0ee
--- /dev/null
+++ b/core/common/src/main/java/com/teamoffroad/core/common/data/datasource/DeviceTokenPreferencesDataSource.kt
@@ -0,0 +1,8 @@
+package com.teamoffroad.core.common.data.datasource
+
+import kotlinx.coroutines.flow.Flow
+
+interface DeviceTokenPreferencesDataSource {
+ val deviceToken: Flow
+ suspend fun setDeviceToken(deviceToken: String)
+}
\ No newline at end of file
diff --git a/core/common/src/main/java/com/teamoffroad/core/common/data/di/DataModule.kt b/core/common/src/main/java/com/teamoffroad/core/common/data/di/DataModule.kt
index 39cbde71..5d7f7b9f 100644
--- a/core/common/src/main/java/com/teamoffroad/core/common/data/di/DataModule.kt
+++ b/core/common/src/main/java/com/teamoffroad/core/common/data/di/DataModule.kt
@@ -2,7 +2,9 @@ package com.teamoffroad.core.common.data.di
import com.teamoffroad.core.common.data.datasource.AutoSignInPreferencesDataSource
import com.teamoffroad.core.common.data.datasource.DefaultAutoSignInPreferencesDataSource
+import com.teamoffroad.core.common.data.datasource.DefaultDeviceTokenPreferencesDataSource
import com.teamoffroad.core.common.data.datasource.DefaultTokenPreferencesDataSource
+import com.teamoffroad.core.common.data.datasource.DeviceTokenPreferencesDataSource
import com.teamoffroad.core.common.data.datasource.TokenPreferencesDataSource
import dagger.Binds
import dagger.Module
@@ -22,4 +24,9 @@ internal abstract class DataModule {
abstract fun bindsAutoSignInLocalDataSource(
dataSource: DefaultAutoSignInPreferencesDataSource,
): AutoSignInPreferencesDataSource
+
+ @Binds
+ abstract fun bindsDeviceTokenLocalDataSource(
+ dataSource: DefaultDeviceTokenPreferencesDataSource,
+ ): DeviceTokenPreferencesDataSource
}
diff --git a/core/common/src/main/java/com/teamoffroad/core/common/data/di/DataStoreModule.kt b/core/common/src/main/java/com/teamoffroad/core/common/data/di/DataStoreModule.kt
index d66b748b..897c0e1d 100644
--- a/core/common/src/main/java/com/teamoffroad/core/common/data/di/DataStoreModule.kt
+++ b/core/common/src/main/java/com/teamoffroad/core/common/data/di/DataStoreModule.kt
@@ -44,6 +44,13 @@ object DataStoreModule {
return context.createDataStore(AUTH_PREFERENCES)
}
+ @Provides
+ @Singleton
+ fun provideDeviceTokenDataStore(@ApplicationContext context: Context): DataStore {
+ return context.createDataStore(DEVICE_TOKEN_PREFERENCES)
+ }
+
private const val TOKEN_PREFERENCES = "com.teamoffroad.token_preferences"
private const val AUTH_PREFERENCES = "com.teamoffroad.auth_preferences"
+ private const val DEVICE_TOKEN_PREFERENCES = "com.teamoffroad.device_token_preferences"
}
diff --git a/core/common/src/main/java/com/teamoffroad/core/common/data/di/RepositoryModule.kt b/core/common/src/main/java/com/teamoffroad/core/common/data/di/RepositoryModule.kt
index 92c49565..ba5a091c 100644
--- a/core/common/src/main/java/com/teamoffroad/core/common/data/di/RepositoryModule.kt
+++ b/core/common/src/main/java/com/teamoffroad/core/common/data/di/RepositoryModule.kt
@@ -1,8 +1,10 @@
package com.teamoffroad.core.common.data.di
import com.teamoffroad.core.common.data.repository.AutoSignInRepositoryImpl
+import com.teamoffroad.core.common.data.repository.DeviceTokenRepositoryImpl
import com.teamoffroad.core.common.data.repository.TokenRepositoryImpl
import com.teamoffroad.core.common.domain.repository.AutoSignInRepository
+import com.teamoffroad.core.common.domain.repository.DeviceTokenRepository
import com.teamoffroad.core.common.domain.repository.TokenRepository
import dagger.Binds
import dagger.Module
@@ -24,4 +26,10 @@ abstract class RepositoryModule {
abstract fun bindAutoSignInRepository(
authRepositoryImpl: AutoSignInRepositoryImpl,
): AutoSignInRepository
+
+ @Binds
+ @Singleton
+ abstract fun bindDeviceTokenRepository(
+ deviceTokenRepositoryImpl: DeviceTokenRepositoryImpl
+ ): DeviceTokenRepository
}
diff --git a/core/common/src/main/java/com/teamoffroad/core/common/data/repository/AutoSignInRepositoryImpl.kt b/core/common/src/main/java/com/teamoffroad/core/common/data/repository/AutoSignInRepositoryImpl.kt
index 61f5fe7c..9232e87d 100644
--- a/core/common/src/main/java/com/teamoffroad/core/common/data/repository/AutoSignInRepositoryImpl.kt
+++ b/core/common/src/main/java/com/teamoffroad/core/common/data/repository/AutoSignInRepositoryImpl.kt
@@ -14,6 +14,4 @@ class AutoSignInRepositoryImpl @Inject constructor(
override suspend fun updateAutoSignInEnabled(enabled: Boolean) {
autoSignInPreferencesDataSource.setAutoLogin(enabled)
}
-
-
}
\ No newline at end of file
diff --git a/core/common/src/main/java/com/teamoffroad/core/common/data/repository/DeviceTokenRepositoryImpl.kt b/core/common/src/main/java/com/teamoffroad/core/common/data/repository/DeviceTokenRepositoryImpl.kt
new file mode 100644
index 00000000..220dc3d7
--- /dev/null
+++ b/core/common/src/main/java/com/teamoffroad/core/common/data/repository/DeviceTokenRepositoryImpl.kt
@@ -0,0 +1,17 @@
+package com.teamoffroad.core.common.data.repository
+
+import com.teamoffroad.core.common.data.datasource.DeviceTokenPreferencesDataSource
+import com.teamoffroad.core.common.domain.repository.DeviceTokenRepository
+import kotlinx.coroutines.flow.Flow
+import javax.inject.Inject
+
+class DeviceTokenRepositoryImpl @Inject constructor(
+ private val deviceTokenPreferencesDataSource: DeviceTokenPreferencesDataSource,
+) : DeviceTokenRepository {
+
+ override val isDeviceTokenEnabled: Flow = deviceTokenPreferencesDataSource.deviceToken
+
+ override suspend fun updateDeviceTokenEnabled(deviceToken: String) {
+ deviceTokenPreferencesDataSource.setDeviceToken(deviceToken)
+ }
+}
\ No newline at end of file
diff --git a/core/common/src/main/java/com/teamoffroad/core/common/domain/repository/DeviceTokenRepository.kt b/core/common/src/main/java/com/teamoffroad/core/common/domain/repository/DeviceTokenRepository.kt
new file mode 100644
index 00000000..56fa8ef4
--- /dev/null
+++ b/core/common/src/main/java/com/teamoffroad/core/common/domain/repository/DeviceTokenRepository.kt
@@ -0,0 +1,8 @@
+package com.teamoffroad.core.common.domain.repository
+
+import kotlinx.coroutines.flow.Flow
+
+interface DeviceTokenRepository {
+ val isDeviceTokenEnabled: Flow
+ suspend fun updateDeviceTokenEnabled(deviceToken: String)
+}
\ No newline at end of file
From 80aabc115b3fff4d562f3b57cbcdd1382ef30ef0 Mon Sep 17 00:00:00 2001
From: leeseokchan00 <112953135+leeseokchan00@users.noreply.github.com>
Date: Wed, 13 Nov 2024 13:05:05 +0900
Subject: [PATCH 03/30] refactor: Edit emblem scroll
---
.../AgreeTermsAndConditionsDialog.kt | 2 +-
.../presentation/GainedEmblemsScreen.kt | 8 ++-
.../presentation/GainedEmblemsViewModel.kt | 30 ++++++++++-
.../component/GainedEmblemsItems.kt | 54 ++++++++++++++++++-
4 files changed, 90 insertions(+), 4 deletions(-)
diff --git a/feature/auth/src/main/java/com/teamoffroad/feature/auth/presentation/component/AgreeTermsAndConditionsDialog.kt b/feature/auth/src/main/java/com/teamoffroad/feature/auth/presentation/component/AgreeTermsAndConditionsDialog.kt
index a8c1c1fd..52e8a665 100644
--- a/feature/auth/src/main/java/com/teamoffroad/feature/auth/presentation/component/AgreeTermsAndConditionsDialog.kt
+++ b/feature/auth/src/main/java/com/teamoffroad/feature/auth/presentation/component/AgreeTermsAndConditionsDialog.kt
@@ -44,7 +44,7 @@ fun AgreeTermsAndConditionsDialog(
) {
Dialog(
onDismissRequest = { onClickCancel() },
- properties = DialogProperties(dismissOnClickOutside = true, dismissOnBackPress = true)
+ properties = DialogProperties(dismissOnClickOutside = false, dismissOnBackPress = true)
) {
Box(
modifier = modifier
diff --git a/feature/mypage/src/main/java/com/teamoffroad/feature/mypage/presentation/GainedEmblemsScreen.kt b/feature/mypage/src/main/java/com/teamoffroad/feature/mypage/presentation/GainedEmblemsScreen.kt
index b572729d..8c4e4844 100644
--- a/feature/mypage/src/main/java/com/teamoffroad/feature/mypage/presentation/GainedEmblemsScreen.kt
+++ b/feature/mypage/src/main/java/com/teamoffroad/feature/mypage/presentation/GainedEmblemsScreen.kt
@@ -16,6 +16,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.airbnb.lottie.compose.LottieAnimation
import com.airbnb.lottie.compose.LottieCompositionSpec
import com.airbnb.lottie.compose.rememberLottieComposition
@@ -36,6 +37,7 @@ internal fun GainedEmblemsScreen(
viewModel: GainedEmblemsViewModel = hiltViewModel(),
) {
val isEmblemState by viewModel.emblemsUiState.collectAsState()
+ val isLoadMoreEmblemsUiState by viewModel.loadMoreEmblemsUiState.collectAsStateWithLifecycle()
LaunchedEffect(Unit) {
viewModel.getEmblems()
@@ -62,7 +64,11 @@ internal fun GainedEmblemsScreen(
)
when (isEmblemState.gainedEmblemsValidateResult) {
GainedEmblemsResult.Success -> {
- GainedEmblemsItems(isEmblemState = isEmblemState)
+ GainedEmblemsItems(
+ isEmblemState = isEmblemState,
+ onLoadMore = { viewModel.loadMoreEmblems() },
+ isLoading = isLoadMoreEmblemsUiState
+ )
}
GainedEmblemsResult.Error -> {
diff --git a/feature/mypage/src/main/java/com/teamoffroad/feature/mypage/presentation/GainedEmblemsViewModel.kt b/feature/mypage/src/main/java/com/teamoffroad/feature/mypage/presentation/GainedEmblemsViewModel.kt
index 976e59bc..6a80d9c3 100644
--- a/feature/mypage/src/main/java/com/teamoffroad/feature/mypage/presentation/GainedEmblemsViewModel.kt
+++ b/feature/mypage/src/main/java/com/teamoffroad/feature/mypage/presentation/GainedEmblemsViewModel.kt
@@ -23,12 +23,19 @@ class GainedEmblemsViewModel @Inject constructor(
MutableStateFlow(GainedEmblemsUiState())
val emblemsUiState: StateFlow = _emblemsUiState.asStateFlow()
+ private val _loadMoreEmblemsUiState: MutableStateFlow =
+ MutableStateFlow(false)
+ val loadMoreEmblemsUiState: StateFlow = _loadMoreEmblemsUiState.asStateFlow()
+
+ private var currentCount = 18
+
fun getEmblems() {
viewModelScope.launch {
runCatching {
getUserEmblemListUseCase()
}.onSuccess { result ->
- val emblems = result.getOrNull()?.toImmutableList() ?: persistentListOf()
+ val emblems =
+ result.getOrNull()?.take(currentCount)?.toImmutableList() ?: persistentListOf()
_emblemsUiState.value = _emblemsUiState.value.copy(
emblemList = emblems,
gainedEmblemsValidateResult = GainedEmblemsResult.Success,
@@ -39,4 +46,25 @@ class GainedEmblemsViewModel @Inject constructor(
}
}
}
+
+ fun loadMoreEmblems() {
+ viewModelScope.launch {
+ if (_loadMoreEmblemsUiState.value) return@launch
+ _loadMoreEmblemsUiState.value = true
+ runCatching {
+ getUserEmblemListUseCase()
+ }.onSuccess { result ->
+ val allEmblems = result.getOrNull() ?: persistentListOf()
+ val newItems = allEmblems.drop(currentCount).take(10).toImmutableList()
+
+ _emblemsUiState.value = _emblemsUiState.value.copy(
+ emblemList = (_emblemsUiState.value.emblemList + newItems).toImmutableList()
+ )
+ currentCount += 10
+ }.onFailure {
+ }.also {
+ _loadMoreEmblemsUiState.value = false
+ }
+ }
+ }
}
diff --git a/feature/mypage/src/main/java/com/teamoffroad/feature/mypage/presentation/component/GainedEmblemsItems.kt b/feature/mypage/src/main/java/com/teamoffroad/feature/mypage/presentation/component/GainedEmblemsItems.kt
index 034bbcc4..77d586d1 100644
--- a/feature/mypage/src/main/java/com/teamoffroad/feature/mypage/presentation/component/GainedEmblemsItems.kt
+++ b/feature/mypage/src/main/java/com/teamoffroad/feature/mypage/presentation/component/GainedEmblemsItems.kt
@@ -2,26 +2,55 @@ package com.teamoffroad.feature.mypage.presentation.component
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
+import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.snapshotFlow
+import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
+import com.airbnb.lottie.compose.LottieAnimation
+import com.airbnb.lottie.compose.LottieCompositionSpec
+import com.airbnb.lottie.compose.rememberLottieComposition
import com.teamoffroad.core.designsystem.theme.ListBg
import com.teamoffroad.feature.mypage.presentation.model.GainedEmblemsUiState
+import kotlinx.coroutines.flow.collectLatest
@Composable
fun GainedEmblemsItems(
modifier: Modifier = Modifier,
isEmblemState: GainedEmblemsUiState,
+ onLoadMore: () -> Unit,
+ isLoading: Boolean,
) {
+ val listState = rememberLazyListState()
+
+ LaunchedEffect(listState) {
+ snapshotFlow {
+ val isAtEnd =
+ listState.layoutInfo.visibleItemsInfo.lastOrNull()?.index == listState.layoutInfo.totalItemsCount - 1
+ isAtEnd
+ }.collectLatest { isAtEnd ->
+ if (isAtEnd) {
+ onLoadMore()
+ }
+ }
+ }
+
LazyColumn(
modifier = modifier
.fillMaxSize()
.background(ListBg),
+ state = listState,
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
item {
@@ -35,8 +64,31 @@ fun GainedEmblemsItems(
isLock = it.isLock
)
}
+ if (isLoading) {
+ item {
+ OnMoreEmblemsLoading()
+ }
+ }
item {
- Spacer(modifier = Modifier.height(32.dp))
+ Spacer(modifier = Modifier.height(100.dp))
}
}
+}
+
+@Composable
+fun OnMoreEmblemsLoading() {
+ Column(
+ modifier = Modifier
+ .background(ListBg)
+ .fillMaxWidth(),
+ verticalArrangement = Arrangement.Center,
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(com.teamoffroad.offroad.core.designsystem.R.raw.loading_circle))
+ LottieAnimation(
+ modifier = Modifier
+ .size(50.dp),
+ composition = composition,
+ )
+ }
}
\ No newline at end of file
From 47842530436d9f02804e10b8fa5f97f5ed82dfe8 Mon Sep 17 00:00:00 2001
From: leeseokchan00 <112953135+leeseokchan00@users.noreply.github.com>
Date: Thu, 14 Nov 2024 01:06:09 +0900
Subject: [PATCH 04/30] feature: Add fcm
---
.../common/data/local/AuthAuthenticator.kt | 40 ++++++++-----------
.../repository/DeviceTokenRepositoryImpl.kt | 2 +-
.../repository/DeviceTokenRepository.kt | 2 +-
.../feature/home/data/di/NetworkModule.kt | 7 ++++
.../feature/home/data/di/RepositoryModule.kt | 8 ++++
.../feature/home/data/di/UseCaseModule.kt | 10 +++++
.../data/remote/request/FcmTokenRequestDto.kt | 10 +++++
.../feature/home/data/remote/request/gitkeep | 0
.../data/remote/service/FcmTokenService.kt | 14 +++++++
.../data/repository/FcmTokenRepositoryImpl.kt | 14 +++++++
.../domain/repository/FcmTokenRepository.kt | 5 +++
.../domain/usecase/PostFcmTokenUseCase.kt | 11 +++++
.../feature/home/presentation/HomeScreen.kt | 1 +
.../home/presentation/HomeViewModel.kt | 17 +++++++-
14 files changed, 114 insertions(+), 27 deletions(-)
create mode 100644 feature/home/src/main/java/com/teamoffroad/feature/home/data/remote/request/FcmTokenRequestDto.kt
delete mode 100644 feature/home/src/main/java/com/teamoffroad/feature/home/data/remote/request/gitkeep
create mode 100644 feature/home/src/main/java/com/teamoffroad/feature/home/data/remote/service/FcmTokenService.kt
create mode 100644 feature/home/src/main/java/com/teamoffroad/feature/home/data/repository/FcmTokenRepositoryImpl.kt
create mode 100644 feature/home/src/main/java/com/teamoffroad/feature/home/domain/repository/FcmTokenRepository.kt
create mode 100644 feature/home/src/main/java/com/teamoffroad/feature/home/domain/usecase/PostFcmTokenUseCase.kt
diff --git a/core/common/src/main/java/com/teamoffroad/core/common/data/local/AuthAuthenticator.kt b/core/common/src/main/java/com/teamoffroad/core/common/data/local/AuthAuthenticator.kt
index 9042c196..ec9cfbf5 100644
--- a/core/common/src/main/java/com/teamoffroad/core/common/data/local/AuthAuthenticator.kt
+++ b/core/common/src/main/java/com/teamoffroad/core/common/data/local/AuthAuthenticator.kt
@@ -1,7 +1,6 @@
package com.teamoffroad.core.common.data.local
import android.content.Context
-import android.util.Log
import com.jakewharton.processphoenix.ProcessPhoenix
import com.teamoffroad.core.common.data.datasource.TokenPreferencesDataSource
import com.teamoffroad.core.common.data.remote.service.TokenService
@@ -26,33 +25,26 @@ class AuthAuthenticator @Inject constructor(
override fun authenticate(route: Route?, response: Response): Request? {
val tokenResponse = runCatching {
- runBlocking {
- refreshTokenUseCase.refreshAccessToken("Bearer ${tokenPreferencesDataSource.refreshToken.first()}")
+ runBlocking {
+ refreshTokenUseCase.refreshAccessToken("Bearer ${tokenPreferencesDataSource.refreshToken.first()}")
+ }
+ }.onSuccess {
+ runBlocking {
+ tokenPreferencesDataSource.apply {
+ setAccessToken(it.data?.accessToken ?: return@runBlocking)
+ setRefreshToken(it.data.refreshToken ?: return@runBlocking)
}
- }.onSuccess {
-
- Log.d("asdasd", "재발급성공")
- runBlocking {
- tokenPreferencesDataSource.apply {
- if (it != null) {
- setAccessToken(it.data?.accessToken ?: return@runBlocking)
- setRefreshToken(it.data?.refreshToken ?: return@runBlocking)
- }
- }
- }
- }.onFailure {
-
- Log.d("asdasd", "재발급실패")
- runBlocking {
- setAutoSignInUseCase.invoke(false)
- }
- ProcessPhoenix.triggerRebirth(context, intentProvider.getIntent())
- }.getOrThrow()
+ }
+ }.onFailure {
+ runBlocking {
+ setAutoSignInUseCase.invoke(false)
+ }
+ ProcessPhoenix.triggerRebirth(context, intentProvider.getIntent())
+ }.getOrThrow()
return response.request.newBuilder()
- .header(AUTHORIZATION, "Bearer ${tokenResponse?.data?.accessToken}")
+ .header(AUTHORIZATION, "Bearer ${tokenResponse.data?.accessToken}")
.build()
- Log.d("asdasd", response.message)
}
companion object {
diff --git a/core/common/src/main/java/com/teamoffroad/core/common/data/repository/DeviceTokenRepositoryImpl.kt b/core/common/src/main/java/com/teamoffroad/core/common/data/repository/DeviceTokenRepositoryImpl.kt
index 220dc3d7..61b6c7f4 100644
--- a/core/common/src/main/java/com/teamoffroad/core/common/data/repository/DeviceTokenRepositoryImpl.kt
+++ b/core/common/src/main/java/com/teamoffroad/core/common/data/repository/DeviceTokenRepositoryImpl.kt
@@ -9,7 +9,7 @@ class DeviceTokenRepositoryImpl @Inject constructor(
private val deviceTokenPreferencesDataSource: DeviceTokenPreferencesDataSource,
) : DeviceTokenRepository {
- override val isDeviceTokenEnabled: Flow = deviceTokenPreferencesDataSource.deviceToken
+ override val deviceToken: Flow = deviceTokenPreferencesDataSource.deviceToken
override suspend fun updateDeviceTokenEnabled(deviceToken: String) {
deviceTokenPreferencesDataSource.setDeviceToken(deviceToken)
diff --git a/core/common/src/main/java/com/teamoffroad/core/common/domain/repository/DeviceTokenRepository.kt b/core/common/src/main/java/com/teamoffroad/core/common/domain/repository/DeviceTokenRepository.kt
index 56fa8ef4..f52e8959 100644
--- a/core/common/src/main/java/com/teamoffroad/core/common/domain/repository/DeviceTokenRepository.kt
+++ b/core/common/src/main/java/com/teamoffroad/core/common/domain/repository/DeviceTokenRepository.kt
@@ -3,6 +3,6 @@ package com.teamoffroad.core.common.domain.repository
import kotlinx.coroutines.flow.Flow
interface DeviceTokenRepository {
- val isDeviceTokenEnabled: Flow
+ val deviceToken: Flow
suspend fun updateDeviceTokenEnabled(deviceToken: String)
}
\ No newline at end of file
diff --git a/feature/home/src/main/java/com/teamoffroad/feature/home/data/di/NetworkModule.kt b/feature/home/src/main/java/com/teamoffroad/feature/home/data/di/NetworkModule.kt
index 39ca121e..3b7ddd77 100644
--- a/feature/home/src/main/java/com/teamoffroad/feature/home/data/di/NetworkModule.kt
+++ b/feature/home/src/main/java/com/teamoffroad/feature/home/data/di/NetworkModule.kt
@@ -2,6 +2,7 @@ package com.teamoffroad.feature.home.data.di
import com.teamoffroad.core.common.data.di.qualifier.Auth
import com.teamoffroad.feature.home.data.remote.service.DummyUserService
+import com.teamoffroad.feature.home.data.remote.service.FcmTokenService
import com.teamoffroad.feature.home.data.remote.service.UserService
import dagger.Module
import dagger.Provides
@@ -25,4 +26,10 @@ object NetworkModule {
fun provideEmblemService(@Auth retrofit: Retrofit): UserService {
return retrofit.create(UserService::class.java)
}
+
+ @Provides
+ @Singleton
+ fun provideFcmTokenService(@Auth retrofit: Retrofit): FcmTokenService {
+ return retrofit.create(FcmTokenService::class.java)
+ }
}
diff --git a/feature/home/src/main/java/com/teamoffroad/feature/home/data/di/RepositoryModule.kt b/feature/home/src/main/java/com/teamoffroad/feature/home/data/di/RepositoryModule.kt
index 745fe7c8..fc8b1121 100644
--- a/feature/home/src/main/java/com/teamoffroad/feature/home/data/di/RepositoryModule.kt
+++ b/feature/home/src/main/java/com/teamoffroad/feature/home/data/di/RepositoryModule.kt
@@ -1,8 +1,10 @@
package com.teamoffroad.feature.home.data.di
import com.teamoffroad.feature.home.data.repository.DummyDummyUserRepositoryImpl
+import com.teamoffroad.feature.home.data.repository.FcmTokenRepositoryImpl
import com.teamoffroad.feature.home.data.repository.UserRepositoryImpl
import com.teamoffroad.feature.home.domain.repository.DummyUserRepository
+import com.teamoffroad.feature.home.domain.repository.FcmTokenRepository
import com.teamoffroad.feature.home.domain.repository.UserRepository
import dagger.Binds
import dagger.Module
@@ -25,4 +27,10 @@ abstract class RepositoryModule {
abstract fun bindUserRepository(
userRepositoryImpl: UserRepositoryImpl
): UserRepository
+
+ @Binds
+ @Singleton
+ abstract fun bindFcmRepository(
+ fcmTokenRepositoryImpl: FcmTokenRepositoryImpl
+ ): FcmTokenRepository
}
diff --git a/feature/home/src/main/java/com/teamoffroad/feature/home/data/di/UseCaseModule.kt b/feature/home/src/main/java/com/teamoffroad/feature/home/data/di/UseCaseModule.kt
index 23264d6e..2b410087 100644
--- a/feature/home/src/main/java/com/teamoffroad/feature/home/data/di/UseCaseModule.kt
+++ b/feature/home/src/main/java/com/teamoffroad/feature/home/data/di/UseCaseModule.kt
@@ -1,8 +1,10 @@
package com.teamoffroad.feature.home.data.di
import com.teamoffroad.feature.home.domain.repository.DummyUserRepository
+import com.teamoffroad.feature.home.domain.repository.FcmTokenRepository
import com.teamoffroad.feature.home.domain.repository.UserRepository
import com.teamoffroad.feature.home.domain.usecase.GetDummyUserListUseCase
+import com.teamoffroad.feature.home.domain.usecase.PostFcmTokenUseCase
import com.teamoffroad.feature.home.domain.usecase.UserUseCase
import dagger.Module
import dagger.Provides
@@ -29,4 +31,12 @@ class UseCaseModule {
): UserUseCase {
return UserUseCase(userRepository)
}
+
+ @Provides
+ @Singleton
+ fun providePostFcmTokenUseCase(
+ fcmTokenRepository: FcmTokenRepository,
+ ): PostFcmTokenUseCase {
+ return PostFcmTokenUseCase(fcmTokenRepository)
+ }
}
diff --git a/feature/home/src/main/java/com/teamoffroad/feature/home/data/remote/request/FcmTokenRequestDto.kt b/feature/home/src/main/java/com/teamoffroad/feature/home/data/remote/request/FcmTokenRequestDto.kt
new file mode 100644
index 00000000..7c7bfbcb
--- /dev/null
+++ b/feature/home/src/main/java/com/teamoffroad/feature/home/data/remote/request/FcmTokenRequestDto.kt
@@ -0,0 +1,10 @@
+package com.teamoffroad.feature.home.data.remote.request
+
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class FcmTokenRequestDto(
+ @SerialName("token")
+ val token: String,
+)
diff --git a/feature/home/src/main/java/com/teamoffroad/feature/home/data/remote/request/gitkeep b/feature/home/src/main/java/com/teamoffroad/feature/home/data/remote/request/gitkeep
deleted file mode 100644
index e69de29b..00000000
diff --git a/feature/home/src/main/java/com/teamoffroad/feature/home/data/remote/service/FcmTokenService.kt b/feature/home/src/main/java/com/teamoffroad/feature/home/data/remote/service/FcmTokenService.kt
new file mode 100644
index 00000000..82159d8a
--- /dev/null
+++ b/feature/home/src/main/java/com/teamoffroad/feature/home/data/remote/service/FcmTokenService.kt
@@ -0,0 +1,14 @@
+package com.teamoffroad.feature.home.data.remote.service
+
+import com.teamoffroad.core.common.data.remote.response.BaseResponse
+import com.teamoffroad.feature.home.data.remote.request.FcmTokenRequestDto
+import retrofit2.http.Body
+import retrofit2.http.POST
+
+interface FcmTokenService {
+
+ @POST("fcm/token")
+ suspend fun postFcmToken(
+ @Body request: FcmTokenRequestDto,
+ ): BaseResponse
+}
\ No newline at end of file
diff --git a/feature/home/src/main/java/com/teamoffroad/feature/home/data/repository/FcmTokenRepositoryImpl.kt b/feature/home/src/main/java/com/teamoffroad/feature/home/data/repository/FcmTokenRepositoryImpl.kt
new file mode 100644
index 00000000..b4efc98d
--- /dev/null
+++ b/feature/home/src/main/java/com/teamoffroad/feature/home/data/repository/FcmTokenRepositoryImpl.kt
@@ -0,0 +1,14 @@
+package com.teamoffroad.feature.home.data.repository
+
+import com.teamoffroad.feature.home.data.remote.request.FcmTokenRequestDto
+import com.teamoffroad.feature.home.data.remote.service.FcmTokenService
+import com.teamoffroad.feature.home.domain.repository.FcmTokenRepository
+import javax.inject.Inject
+
+class FcmTokenRepositoryImpl @Inject constructor(
+ private val fcmTokenService: FcmTokenService
+) : FcmTokenRepository {
+ override suspend fun postFcmToken(fcmToken: String) {
+ fcmTokenService.postFcmToken(FcmTokenRequestDto(fcmToken))
+ }
+}
\ No newline at end of file
diff --git a/feature/home/src/main/java/com/teamoffroad/feature/home/domain/repository/FcmTokenRepository.kt b/feature/home/src/main/java/com/teamoffroad/feature/home/domain/repository/FcmTokenRepository.kt
new file mode 100644
index 00000000..7298ea70
--- /dev/null
+++ b/feature/home/src/main/java/com/teamoffroad/feature/home/domain/repository/FcmTokenRepository.kt
@@ -0,0 +1,5 @@
+package com.teamoffroad.feature.home.domain.repository
+
+interface FcmTokenRepository {
+ suspend fun postFcmToken(fcmToken: String)
+}
\ No newline at end of file
diff --git a/feature/home/src/main/java/com/teamoffroad/feature/home/domain/usecase/PostFcmTokenUseCase.kt b/feature/home/src/main/java/com/teamoffroad/feature/home/domain/usecase/PostFcmTokenUseCase.kt
new file mode 100644
index 00000000..661c8378
--- /dev/null
+++ b/feature/home/src/main/java/com/teamoffroad/feature/home/domain/usecase/PostFcmTokenUseCase.kt
@@ -0,0 +1,11 @@
+package com.teamoffroad.feature.home.domain.usecase
+
+import com.teamoffroad.feature.home.domain.repository.FcmTokenRepository
+
+class PostFcmTokenUseCase(
+ private val fcmTokenRepository: FcmTokenRepository
+) {
+ suspend operator fun invoke(fcmToken: String) {
+ return fcmTokenRepository.postFcmToken(fcmToken)
+ }
+}
\ No newline at end of file
diff --git a/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeScreen.kt b/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeScreen.kt
index b22cda22..41357691 100644
--- a/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeScreen.kt
+++ b/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeScreen.kt
@@ -67,6 +67,7 @@ fun HomeScreen(
LaunchedEffect(Unit) {
viewModel.updateAutoSignIn()
+ viewModel.updateFcmToken()
viewModel.updateCategory(if (category.isNullOrEmpty()) "NONE" else category)
viewModel.getUsersAdventuresInformation(viewModel.category.value)
viewModel.getUserQuests()
diff --git a/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeViewModel.kt b/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeViewModel.kt
index e38fb4c8..38fd6f99 100644
--- a/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeViewModel.kt
+++ b/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeViewModel.kt
@@ -2,16 +2,19 @@ package com.teamoffroad.feature.home.presentation
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
+import com.teamoffroad.core.common.domain.repository.DeviceTokenRepository
import com.teamoffroad.core.common.domain.usecase.SetAutoSignInUseCase
import com.teamoffroad.feature.home.domain.model.Emblem
import com.teamoffroad.feature.home.domain.model.UserQuests
import com.teamoffroad.feature.home.domain.model.UsersAdventuresInformation
import com.teamoffroad.feature.home.domain.repository.UserRepository
+import com.teamoffroad.feature.home.domain.usecase.PostFcmTokenUseCase
import com.teamoffroad.feature.home.presentation.component.UiState
import com.teamoffroad.feature.home.presentation.component.getErrorMessage
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import javax.inject.Inject
@@ -19,6 +22,8 @@ import javax.inject.Inject
class HomeViewModel @Inject constructor(
private val userRepository: UserRepository,
private val setAutoSignInUseCase: SetAutoSignInUseCase,
+ private val deviceTokenRepository: DeviceTokenRepository,
+ private val fcmTokenUseCase: PostFcmTokenUseCase,
) : ViewModel() {
private val _getUsersAdventuresInformationState =
@@ -134,10 +139,20 @@ class HomeViewModel @Inject constructor(
}
}
- fun updateAutoSignIn(){
+ fun updateAutoSignIn() {
viewModelScope.launch {
setAutoSignInUseCase.invoke(true)
}
}
+ fun updateFcmToken() {
+ viewModelScope.launch {
+ val deviceToken = deviceTokenRepository.deviceToken.first()
+ if (deviceToken.isBlank()) return@launch
+ runCatching {
+ fcmTokenUseCase.invoke(deviceToken)
+ }.onSuccess { }
+ .onFailure {}
+ }
+ }
}
\ No newline at end of file
From 0278b98f1bd9e17f7abb9841d5b2b570ce0109e1 Mon Sep 17 00:00:00 2001
From: leeseokchan00 <112953135+leeseokchan00@users.noreply.github.com>
Date: Fri, 15 Nov 2024 22:29:49 +0900
Subject: [PATCH 05/30] feature: Add fcm to main
---
app/build.gradle.kts | 1 +
app/src/main/AndroidManifest.xml | 1 +
.../offroad.app/OffRoadMessagingService.kt | 144 ++++++++++++++++++
.../offroad.app/OffroadApplication.kt | 2 +
.../common/util/ActivityLifecycleHandler.kt | 34 +++++
.../teamoffroad/feature/main/MainActivity.kt | 8 +
6 files changed, 190 insertions(+)
create mode 100644 core/common/src/main/java/com/teamoffroad/core/common/util/ActivityLifecycleHandler.kt
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 9ad1db64..f3c1785f 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -56,6 +56,7 @@ dependencies {
implementation(project(":feature:mypage"))
implementation(project(":feature:characterchat"))
implementation(libs.kakao.user)
+ implementation(libs.coil)
implementation(libs.bundles.firebase)
implementation(platform(libs.firebase.bom))
}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 7b2578f2..06b5af1e 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -9,6 +9,7 @@
+
Unit
+ ): NotificationCompat.Builder {
+
+ val notificationBuilder = NotificationCompat.Builder(this, "channelId")
+ .setSmallIcon(R.mipmap.ic_launcher)
+ .setContentTitle(remoteMessage.data[KEY_TITLE])
+ .setContentText(remoteMessage.data[KEY_BODY])
+ .setAutoCancel(true)
+ .setContentIntent(pendingIntent)
+ .setPriority(NotificationCompat.PRIORITY_MAX)
+
+ val imageUrl = remoteMessage.data[KEY_IMAGE]
+ imageUrl?.let {
+ val request = ImageRequest.Builder(this)
+ .data(it)
+ .target { drawable ->
+ val bitmap = (drawable as BitmapDrawable).bitmap
+ notificationBuilder.setLargeIcon(bitmap)
+ onLargeIconReady(notificationBuilder)
+ }
+ .build()
+
+ Coil.imageLoader(this).enqueue(request)
+ } ?: run {
+ onLargeIconReady(notificationBuilder)
+ }
+ return notificationBuilder
+ }
+
+ private fun sendNotification(remoteMessage: RemoteMessage, isForeGround: Boolean) {
+ val uniqueIdentifier = generateUniqueIdentifier()
+ val intent = createNotificationIntent(remoteMessage)
+ val pendingIntent = createPendingIntent(intent, uniqueIdentifier)
+ createNotificationBuilder(remoteMessage, pendingIntent) { notificationBuilder ->
+ showNotification(notificationBuilder, uniqueIdentifier)
+ }
+ }
+
+ private fun showNotification(
+ notificationBuilder: NotificationCompat.Builder,
+ uniqueIdentifier: Int
+ ) {
+ val notificationManager =
+ getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ val channel =
+ NotificationChannel("channelId", "Notice", NotificationManager.IMPORTANCE_HIGH)
+ notificationManager.createNotificationChannel(channel)
+ }
+
+ notificationManager.notify(uniqueIdentifier, notificationBuilder.build())
+ }
+
+ companion object {
+ //TODO. 나중에 정리 해야됨
+ private const val KEY_TITLE = "title"
+ private const val KEY_BODY = "body"
+ private const val KEY_TYPE = "type"
+ private const val KEY_IMAGE = "image"
+ private const val KEY_ID = "additionalProp1"
+ private const val TYPE_CHARACTER_CHAT = "CHARACTER_CHAT"
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/teamoffroad/offroad.app/OffroadApplication.kt b/app/src/main/java/com/teamoffroad/offroad.app/OffroadApplication.kt
index 2f1cea24..913debef 100644
--- a/app/src/main/java/com/teamoffroad/offroad.app/OffroadApplication.kt
+++ b/app/src/main/java/com/teamoffroad/offroad.app/OffroadApplication.kt
@@ -2,6 +2,7 @@ package com.teamoffroad.offroad.app
import android.app.Application
import com.kakao.sdk.common.KakaoSdk
+import com.teamoffroad.core.common.util.ActivityLifecycleHandler
import dagger.hilt.android.HiltAndroidApp
@HiltAndroidApp
@@ -9,6 +10,7 @@ class OffroadApplication : Application(){
override fun onCreate() {
super.onCreate()
setKakaoSdk()
+ registerActivityLifecycleCallbacks(ActivityLifecycleHandler())
}
private fun setKakaoSdk() {
diff --git a/core/common/src/main/java/com/teamoffroad/core/common/util/ActivityLifecycleHandler.kt b/core/common/src/main/java/com/teamoffroad/core/common/util/ActivityLifecycleHandler.kt
new file mode 100644
index 00000000..69d07070
--- /dev/null
+++ b/core/common/src/main/java/com/teamoffroad/core/common/util/ActivityLifecycleHandler.kt
@@ -0,0 +1,34 @@
+package com.teamoffroad.core.common.util
+
+import android.app.Activity
+import android.app.Application
+import android.os.Bundle
+
+class ActivityLifecycleHandler : Application.ActivityLifecycleCallbacks {
+ override fun onActivityCreated(p0: Activity, p1: Bundle?) {
+ }
+
+ override fun onActivityStarted(p0: Activity) {
+ }
+
+ override fun onActivityResumed(p0: Activity) {
+ isAppInForeground = true
+ }
+
+ override fun onActivityPaused(p0: Activity) {
+ isAppInForeground = false
+ }
+
+ override fun onActivityStopped(p0: Activity) {
+ }
+
+ override fun onActivitySaveInstanceState(p0: Activity, p1: Bundle) {
+ }
+
+ override fun onActivityDestroyed(p0: Activity) {
+ }
+
+ companion object {
+ var isAppInForeground = false
+ }
+}
\ No newline at end of file
diff --git a/feature/main/src/main/java/com/teamoffroad/feature/main/MainActivity.kt b/feature/main/src/main/java/com/teamoffroad/feature/main/MainActivity.kt
index d0e92c88..34965a8e 100644
--- a/feature/main/src/main/java/com/teamoffroad/feature/main/MainActivity.kt
+++ b/feature/main/src/main/java/com/teamoffroad/feature/main/MainActivity.kt
@@ -4,6 +4,7 @@ import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.Bundle
+import android.util.Log
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.annotation.RequiresApi
@@ -20,6 +21,13 @@ class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
+ val a = intent.getStringExtra("type")
+ if (a != "CHARACTER_CHAT") {
+ val b = intent.getStringExtra("additionalProp1")
+ Log.d("fcmCHARACTER_CHAT", a.toString())
+ Log.d("fcmadditionalProp1", b.toString())
+ }
+
setContent {
val navigator: MainNavigator = rememberMainNavigator()
MainTransparentActionBar(window)
From 02a385d88b9910e116dfd20cfbef4e4e89ca59c5 Mon Sep 17 00:00:00 2001
From: leeseokchan00 <112953135+leeseokchan00@users.noreply.github.com>
Date: Sat, 16 Nov 2024 14:53:37 +0900
Subject: [PATCH 06/30] feature: Add fcm background click
---
.../offroad.app/OffRoadMessagingService.kt | 57 ++++++++++++++++---
.../teamoffroad/core/navigation/RouteModel.kt | 7 +--
.../feature/auth/presentation/AuthScreen.kt | 4 ++
.../auth/presentation/AuthViewModel.kt | 12 ++++
.../auth/presentation/SetGenderScreen.kt | 18 +++++-
.../auth/presentation/model/AuthUiState.kt | 1 +
.../teamoffroad/feature/main/MainActivity.kt | 36 +++++++-----
.../teamoffroad/feature/main/MainNavigator.kt | 8 +--
.../teamoffroad/feature/main/MainScreen.kt | 12 ++++
.../feature/main/{splash => }/SplashScreen.kt | 23 +-------
.../feature/main/component/MainNavHost.kt | 9 +--
.../feature/main/splash/SplashUiState.kt | 6 --
.../feature/main/splash/SplashViewModel.kt | 39 -------------
.../splash/navigation/SplashNavigation.kt | 23 --------
.../mypage/navigation/MyPageNavigation.kt | 16 ++++--
.../mypage/presentation/AnnouncementScreen.kt | 1 +
16 files changed, 138 insertions(+), 134 deletions(-)
rename feature/main/src/main/java/com/teamoffroad/feature/main/{splash => }/SplashScreen.kt (79%)
delete mode 100644 feature/main/src/main/java/com/teamoffroad/feature/main/splash/SplashUiState.kt
delete mode 100644 feature/main/src/main/java/com/teamoffroad/feature/main/splash/SplashViewModel.kt
delete mode 100644 feature/main/src/main/java/com/teamoffroad/feature/main/splash/navigation/SplashNavigation.kt
diff --git a/app/src/main/java/com/teamoffroad/offroad.app/OffRoadMessagingService.kt b/app/src/main/java/com/teamoffroad/offroad.app/OffRoadMessagingService.kt
index 119c136a..51e8c4d7 100644
--- a/app/src/main/java/com/teamoffroad/offroad.app/OffRoadMessagingService.kt
+++ b/app/src/main/java/com/teamoffroad/offroad.app/OffRoadMessagingService.kt
@@ -7,6 +7,7 @@ import android.content.Context
import android.content.Intent
import android.graphics.drawable.BitmapDrawable
import android.os.Build
+import android.util.Log
import androidx.core.app.NotificationCompat
import coil.Coil
import coil.request.ImageRequest
@@ -39,9 +40,9 @@ class OffRoadMessagingService : FirebaseMessagingService() {
if (ActivityLifecycleHandler.isAppInForeground) {
//TODO. 키 밸류값 나오면 바꾸기
if (remoteMessage.data[KEY_TYPE] != TYPE_CHARACTER_CHAT)
- sendNotification(remoteMessage, true)
+ sendForeGroundNotification(remoteMessage, true)
} else {
- sendNotification(remoteMessage, false)
+ sendBackGroundNotification(remoteMessage, false)
}
}
}
@@ -84,8 +85,7 @@ class OffRoadMessagingService : FirebaseMessagingService() {
private fun createNotificationIntent(remoteMessage: RemoteMessage): Intent {
return Intent(this, MainActivity::class.java).apply {
if (ActivityLifecycleHandler.isAppInForeground) {
- action = Intent.ACTION_MAIN
- addCategory(Intent.CATEGORY_LAUNCHER)
+ Log.d("asdasd", "forground")
} else {
flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK
}
@@ -96,7 +96,37 @@ class OffRoadMessagingService : FirebaseMessagingService() {
}
}
- private fun createNotificationBuilder(
+ private fun createForeGroundNotificationBuilder(
+ remoteMessage: RemoteMessage,
+ onLargeIconReady: (NotificationCompat.Builder) -> Unit
+ ): NotificationCompat.Builder {
+
+ val notificationBuilder = NotificationCompat.Builder(this, "channelId")
+ .setSmallIcon(R.mipmap.ic_launcher)
+ .setContentTitle(remoteMessage.data[KEY_TITLE])
+ .setContentText(remoteMessage.data[KEY_BODY])
+ .setAutoCancel(true)
+ .setPriority(NotificationCompat.PRIORITY_MAX)
+
+ val imageUrl = remoteMessage.data[KEY_IMAGE]
+ imageUrl?.let {
+ val request = ImageRequest.Builder(this)
+ .data(it)
+ .target { drawable ->
+ val bitmap = (drawable as BitmapDrawable).bitmap
+ notificationBuilder.setLargeIcon(bitmap)
+ onLargeIconReady(notificationBuilder)
+ }
+ .build()
+
+ Coil.imageLoader(this).enqueue(request)
+ } ?: run {
+ onLargeIconReady(notificationBuilder)
+ }
+ return notificationBuilder
+ }
+
+ private fun createBackGroundNotificationBuilder(
remoteMessage: RemoteMessage,
pendingIntent: PendingIntent,
onLargeIconReady: (NotificationCompat.Builder) -> Unit
@@ -128,11 +158,22 @@ class OffRoadMessagingService : FirebaseMessagingService() {
return notificationBuilder
}
- private fun sendNotification(remoteMessage: RemoteMessage, isForeGround: Boolean) {
+ private fun sendForeGroundNotification(remoteMessage: RemoteMessage, isForeGround: Boolean) {
+ val uniqueIdentifier = generateUniqueIdentifier()
+ createForeGroundNotificationBuilder(remoteMessage) { notificationBuilder ->
+ showNotification(notificationBuilder, uniqueIdentifier)
+
+ if(isForeGround){
+
+ }
+ }
+ }
+
+ private fun sendBackGroundNotification(remoteMessage: RemoteMessage, isForeGround: Boolean) {
val uniqueIdentifier = generateUniqueIdentifier()
val intent = createNotificationIntent(remoteMessage)
val pendingIntent = createPendingIntent(intent, uniqueIdentifier)
- createNotificationBuilder(remoteMessage, pendingIntent) { notificationBuilder ->
+ createBackGroundNotificationBuilder(remoteMessage, pendingIntent) { notificationBuilder ->
showNotification(notificationBuilder, uniqueIdentifier)
}
}
@@ -161,5 +202,7 @@ class OffRoadMessagingService : FirebaseMessagingService() {
private const val KEY_IMAGE = "image"
private const val KEY_ID = "additionalProp1"
private const val TYPE_CHARACTER_CHAT = "CHARACTER_CHAT"
+ private const val TYPE_ANNOUNCEMENT = "ANNOUNCEMENT_REDIRECT"
+
}
}
\ No newline at end of file
diff --git a/core/navigation/src/main/java/com/teamoffroad/core/navigation/RouteModel.kt b/core/navigation/src/main/java/com/teamoffroad/core/navigation/RouteModel.kt
index 182aa803..82ad7132 100644
--- a/core/navigation/src/main/java/com/teamoffroad/core/navigation/RouteModel.kt
+++ b/core/navigation/src/main/java/com/teamoffroad/core/navigation/RouteModel.kt
@@ -3,9 +3,6 @@ package com.teamoffroad.core.navigation
import kotlinx.serialization.Serializable
sealed interface Route {
- @Serializable
- data object Splash : Route
-
@Serializable
data object Auth : Route
}
@@ -76,7 +73,9 @@ sealed interface MyPageRoute : Route {
data object Setting : MyPageRoute
@Serializable
- data object Announcement : MyPageRoute
+ data class Announcement(
+ val announcementId: String?,
+ ) : MyPageRoute
@Serializable
data class AnnouncementDetail(
diff --git a/feature/auth/src/main/java/com/teamoffroad/feature/auth/presentation/AuthScreen.kt b/feature/auth/src/main/java/com/teamoffroad/feature/auth/presentation/AuthScreen.kt
index 83eec220..7ff6c750 100644
--- a/feature/auth/src/main/java/com/teamoffroad/feature/auth/presentation/AuthScreen.kt
+++ b/feature/auth/src/main/java/com/teamoffroad/feature/auth/presentation/AuthScreen.kt
@@ -63,8 +63,12 @@ internal fun AuthScreen(
EntryPointAccessors.fromActivity(context)
val oAuthInteractor = entryPoint.getOAuthInteractor()
+ LaunchedEffect(Unit) {
+ viewModel.checkAutoSignIn()
+ }
LaunchedEffect(isAuthUiState) {
when {
+ isAuthUiState.isAutoSignIn -> navigateToHome()
isAuthUiState.signInSuccess && !isAuthUiState.alreadyExist -> navigateToAgreeTermsAndConditions()
isAuthUiState.signInSuccess && isAuthUiState.alreadyExist -> navigateToHome()
isAuthUiState.kakaoSignIn -> {
diff --git a/feature/auth/src/main/java/com/teamoffroad/feature/auth/presentation/AuthViewModel.kt b/feature/auth/src/main/java/com/teamoffroad/feature/auth/presentation/AuthViewModel.kt
index 13f63d50..15c413a1 100644
--- a/feature/auth/src/main/java/com/teamoffroad/feature/auth/presentation/AuthViewModel.kt
+++ b/feature/auth/src/main/java/com/teamoffroad/feature/auth/presentation/AuthViewModel.kt
@@ -6,6 +6,7 @@ import com.google.android.gms.auth.api.signin.GoogleSignInAccount
import com.google.android.gms.auth.api.signin.GoogleSignInClient
import com.google.android.gms.common.api.ApiException
import com.google.android.gms.tasks.Task
+import com.teamoffroad.core.common.domain.usecase.GetAutoSignInUseCase
import com.teamoffroad.core.common.domain.usecase.SaveAccessTokenUseCase
import com.teamoffroad.core.common.domain.usecase.SaveRefreshTokenUseCase
import com.teamoffroad.feature.auth.domain.model.SocialSignInPlatform
@@ -24,6 +25,7 @@ class AuthViewModel @Inject constructor(
private val authUseCase: AuthUseCase,
private val saveAccessTokenUseCase: SaveAccessTokenUseCase,
private val saveRefreshTokenUseCase: SaveRefreshTokenUseCase,
+ private val getAutoSignInUseCase: GetAutoSignInUseCase,
) : ViewModel() {
private val _authUiState: MutableStateFlow =
MutableStateFlow(AuthUiState(empty = true))
@@ -77,4 +79,14 @@ class AuthViewModel @Inject constructor(
}
}
}
+
+ fun checkAutoSignIn() {
+ viewModelScope.launch {
+ getAutoSignInUseCase().collect { isAutoSignIn ->
+ _authUiState.value = _authUiState.value.copy(
+ isAutoSignIn = isAutoSignIn
+ )
+ }
+ }
+ }
}
diff --git a/feature/auth/src/main/java/com/teamoffroad/feature/auth/presentation/SetGenderScreen.kt b/feature/auth/src/main/java/com/teamoffroad/feature/auth/presentation/SetGenderScreen.kt
index 2ea82851..58fc341a 100644
--- a/feature/auth/src/main/java/com/teamoffroad/feature/auth/presentation/SetGenderScreen.kt
+++ b/feature/auth/src/main/java/com/teamoffroad/feature/auth/presentation/SetGenderScreen.kt
@@ -141,20 +141,32 @@ fun SetGenderButton(
GenderHintButton(
modifier = Modifier
.padding(bottom = 12.dp)
- .clickableWithoutRipple(interactionSource = interactionSource) { viewModel.updateCheckedGender("MALE") },
+ .clickableWithoutRipple(interactionSource = interactionSource) {
+ viewModel.updateCheckedGender(
+ "MALE"
+ )
+ },
value = stringResource(R.string.auth_set_gender_male),
isActive = male
)
GenderHintButton(
modifier = Modifier
.padding(bottom = 12.dp)
- .clickableWithoutRipple(interactionSource =interactionSource) { viewModel.updateCheckedGender("FEMALE") },
+ .clickableWithoutRipple(interactionSource = interactionSource) {
+ viewModel.updateCheckedGender(
+ "FEMALE"
+ )
+ },
value = stringResource(R.string.auth_set_gender_female),
isActive = female
)
GenderHintButton(
modifier = Modifier
- .clickableWithoutRipple(interactionSource =interactionSource) { viewModel.updateCheckedGender("OTHER") },
+ .clickableWithoutRipple(interactionSource = interactionSource) {
+ viewModel.updateCheckedGender(
+ "OTHER"
+ )
+ },
value = stringResource(R.string.auth_set_gender_other),
isActive = other
)
diff --git a/feature/auth/src/main/java/com/teamoffroad/feature/auth/presentation/model/AuthUiState.kt b/feature/auth/src/main/java/com/teamoffroad/feature/auth/presentation/model/AuthUiState.kt
index 73af2c56..2fc198f9 100644
--- a/feature/auth/src/main/java/com/teamoffroad/feature/auth/presentation/model/AuthUiState.kt
+++ b/feature/auth/src/main/java/com/teamoffroad/feature/auth/presentation/model/AuthUiState.kt
@@ -6,5 +6,6 @@ data class AuthUiState(
val signInSuccess: Boolean = false,
val alreadyExist: Boolean = false,
val kakaoSignIn: Boolean = false,
+ val isAutoSignIn: Boolean = false,
)
diff --git a/feature/main/src/main/java/com/teamoffroad/feature/main/MainActivity.kt b/feature/main/src/main/java/com/teamoffroad/feature/main/MainActivity.kt
index 34965a8e..f1f395a7 100644
--- a/feature/main/src/main/java/com/teamoffroad/feature/main/MainActivity.kt
+++ b/feature/main/src/main/java/com/teamoffroad/feature/main/MainActivity.kt
@@ -4,38 +4,46 @@ import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.Bundle
-import android.util.Log
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.annotation.RequiresApi
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import com.teamoffroad.core.designsystem.theme.OffroadTheme
import com.teamoffroad.feature.main.component.MainTransparentActionBar
import dagger.hilt.android.AndroidEntryPoint
+import kotlinx.coroutines.delay
@AndroidEntryPoint
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
-
- val a = intent.getStringExtra("type")
- if (a != "CHARACTER_CHAT") {
- val b = intent.getStringExtra("additionalProp1")
- Log.d("fcmCHARACTER_CHAT", a.toString())
- Log.d("fcmadditionalProp1", b.toString())
- }
-
+ val notificationType = intent.getStringExtra("type")
+ val notificationId = intent.getStringExtra("additionalProp1")
setContent {
val navigator: MainNavigator = rememberMainNavigator()
+ val showSplash = remember { mutableStateOf(true) }
+
+ LaunchedEffect(Unit) {
+ delay(1550)
+ showSplash.value = false
+ }
MainTransparentActionBar(window)
OffroadTheme {
- MainScreen(
- navigator = navigator,
- modifier = Modifier
- )
+ when (showSplash.value) {
+ true -> SplashScreen()
+ false -> MainScreen(
+ navigator = navigator,
+ modifier = Modifier,
+ notificationType = if (!notificationType.isNullOrBlank()) notificationType else null,
+ notificationId = if (!notificationId.isNullOrBlank()) notificationId else null,
+ )
+ }
}
}
}
@@ -53,7 +61,7 @@ class MainActivity : ComponentActivity() {
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
fun GreetingPreview() {
OffroadTheme {
- MainScreen()
+ MainScreen(notificationType = "", notificationId = "")
}
}
diff --git a/feature/main/src/main/java/com/teamoffroad/feature/main/MainNavigator.kt b/feature/main/src/main/java/com/teamoffroad/feature/main/MainNavigator.kt
index b1b3a732..219f9e22 100644
--- a/feature/main/src/main/java/com/teamoffroad/feature/main/MainNavigator.kt
+++ b/feature/main/src/main/java/com/teamoffroad/feature/main/MainNavigator.kt
@@ -21,9 +21,9 @@ import com.teamoffroad.feature.explore.navigation.navigateToExplore
import com.teamoffroad.feature.explore.navigation.navigateToPlace
import com.teamoffroad.feature.explore.navigation.navigateToQuest
import com.teamoffroad.feature.home.navigation.navigateToHome
-import com.teamoffroad.feature.main.splash.navigation.navigateToAuth
import com.teamoffroad.feature.mypage.navigation.navigateToAnnouncement
import com.teamoffroad.feature.mypage.navigation.navigateToAnnouncementDetail
+import com.teamoffroad.feature.mypage.navigation.navigateToAuth
import com.teamoffroad.feature.mypage.navigation.navigateToAvailableCouponDetail
import com.teamoffroad.feature.mypage.navigation.navigateToCharacterDetail
import com.teamoffroad.feature.mypage.navigation.navigateToGainedCharacter
@@ -39,7 +39,7 @@ internal class MainNavigator(
@Composable get() = navController
.currentBackStackEntryAsState().value?.destination
- val startDestination = Route.Splash
+ val startDestination = Route.Auth
val currentTab: MainNavTab?
@Composable get() = MainNavTab.find { tab ->
@@ -169,8 +169,8 @@ internal class MainNavigator(
navController.navigateToSetting()
}
- fun navigateToAnnouncement() {
- navController.navigateToAnnouncement()
+ fun navigateToAnnouncement(announcementId: String?) {
+ navController.navigateToAnnouncement(announcementId)
}
fun navigateToAnnouncementDetail(
diff --git a/feature/main/src/main/java/com/teamoffroad/feature/main/MainScreen.kt b/feature/main/src/main/java/com/teamoffroad/feature/main/MainScreen.kt
index 81bf51f9..9be38aaf 100644
--- a/feature/main/src/main/java/com/teamoffroad/feature/main/MainScreen.kt
+++ b/feature/main/src/main/java/com/teamoffroad/feature/main/MainScreen.kt
@@ -5,6 +5,7 @@ import androidx.annotation.RequiresApi
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Modifier
import com.teamoffroad.core.common.util.OnBackButtonListener
import com.teamoffroad.core.designsystem.component.navigationPadding
@@ -17,7 +18,18 @@ import kotlinx.collections.immutable.toPersistentList
internal fun MainScreen(
modifier: Modifier = Modifier,
navigator: MainNavigator = rememberMainNavigator(),
+ notificationType: String?,
+ notificationId: String?,
) {
+ LaunchedEffect(Unit) {
+ if (!notificationType.isNullOrBlank()) {
+ if (notificationType == "ANNOUNCEMENT_REDIRECT")
+ notificationId?.let { navigator.navigateToAnnouncement(it) }
+ else if (notificationType == "CHARACTER_CHAT") {
+ navigator.navigateToHome()
+ }
+ }
+ }
MainScreenContent(
navigator = navigator,
modifier = modifier,
diff --git a/feature/main/src/main/java/com/teamoffroad/feature/main/splash/SplashScreen.kt b/feature/main/src/main/java/com/teamoffroad/feature/main/SplashScreen.kt
similarity index 79%
rename from feature/main/src/main/java/com/teamoffroad/feature/main/splash/SplashScreen.kt
rename to feature/main/src/main/java/com/teamoffroad/feature/main/SplashScreen.kt
index 961ae735..9c1d344b 100644
--- a/feature/main/src/main/java/com/teamoffroad/feature/main/splash/SplashScreen.kt
+++ b/feature/main/src/main/java/com/teamoffroad/feature/main/SplashScreen.kt
@@ -1,4 +1,4 @@
-package com.teamoffroad.feature.main.splash
+package com.teamoffroad.feature.main
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.EnterTransition
@@ -23,9 +23,6 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.scale
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.res.painterResource
-import androidx.hilt.navigation.compose.hiltViewModel
-import androidx.lifecycle.compose.LocalLifecycleOwner
-import androidx.lifecycle.flowWithLifecycle
import com.teamoffroad.core.designsystem.component.ChangeBottomBarColor
import com.teamoffroad.core.designsystem.theme.Main2
import kotlinx.coroutines.delay
@@ -33,25 +30,7 @@ import kotlinx.coroutines.launch
@Composable
fun SplashScreen(
- navigateToAuth: () -> Unit,
- navigateToHome: () -> Unit,
- viewModel: SplashViewModel = hiltViewModel(),
) {
- val lifecycleOwner = LocalLifecycleOwner.current
-
- LaunchedEffect(Unit) {
- viewModel.showSplash()
- }
- LaunchedEffect(viewModel.splashUiState, lifecycleOwner) {
- viewModel.splashUiState.flowWithLifecycle(lifecycle = lifecycleOwner.lifecycle)
- .collect { splashUiState ->
- when (splashUiState) {
- is SplashUiState.NavigateHome -> navigateToHome()
- is SplashUiState.NavigateLogin -> navigateToAuth()
- }
- }
- }
-
ChangeBottomBarColor(Main2)
var backgroundVisibility by remember { mutableStateOf(true) }
val scale = remember { Animatable(1f) }
diff --git a/feature/main/src/main/java/com/teamoffroad/feature/main/component/MainNavHost.kt b/feature/main/src/main/java/com/teamoffroad/feature/main/component/MainNavHost.kt
index a1454bf8..7935d496 100644
--- a/feature/main/src/main/java/com/teamoffroad/feature/main/component/MainNavHost.kt
+++ b/feature/main/src/main/java/com/teamoffroad/feature/main/component/MainNavHost.kt
@@ -16,7 +16,6 @@ import com.teamoffroad.feature.auth.navigation.authNavGraph
import com.teamoffroad.feature.explore.navigation.exploreNavGraph
import com.teamoffroad.feature.home.navigation.homeNavGraph
import com.teamoffroad.feature.main.MainNavigator
-import com.teamoffroad.feature.main.splash.navigation.splashNavGraph
import com.teamoffroad.feature.mypage.navigation.myPageNavGraph
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
@@ -39,10 +38,6 @@ internal fun MainNavHost(
exitTransition = { ExitTransition.None },
popExitTransition = { ExitTransition.None },
) {
- splashNavGraph(
- navigateToAuth = { navigator.navigateToAuth() },
- navigateToHome = { navigator.navigateToHome() }
- )
homeNavGraph(
navigateToBack = navigator::popBackStackIfNotMainTabRoute,
navigateToGainedCharacter = {
@@ -81,7 +76,9 @@ internal fun MainNavHost(
},
navigateToGainedEmblems = navigator::navigateToGainedEmblems,
navigateToSetting = navigator::navigateToSetting,
- navigateToAnnouncement = navigator::navigateToAnnouncement,
+ navigateToAnnouncement = { announcementId ->
+ navigator.navigateToAnnouncement(announcementId)
+ },
navigateToAnnouncementDetail = navigator::navigateToAnnouncementDetail,
navigateToSignIn = navigator::navigateToAuth,
navigateToCharacterDetail = navigator::navigateToCharacterDetail,
diff --git a/feature/main/src/main/java/com/teamoffroad/feature/main/splash/SplashUiState.kt b/feature/main/src/main/java/com/teamoffroad/feature/main/splash/SplashUiState.kt
deleted file mode 100644
index a300ec35..00000000
--- a/feature/main/src/main/java/com/teamoffroad/feature/main/splash/SplashUiState.kt
+++ /dev/null
@@ -1,6 +0,0 @@
-package com.teamoffroad.feature.main.splash
-
-sealed class SplashUiState {
- data object NavigateHome: SplashUiState()
- data object NavigateLogin: SplashUiState()
-}
diff --git a/feature/main/src/main/java/com/teamoffroad/feature/main/splash/SplashViewModel.kt b/feature/main/src/main/java/com/teamoffroad/feature/main/splash/SplashViewModel.kt
deleted file mode 100644
index 6c02d952..00000000
--- a/feature/main/src/main/java/com/teamoffroad/feature/main/splash/SplashViewModel.kt
+++ /dev/null
@@ -1,39 +0,0 @@
-package com.teamoffroad.feature.main.splash
-
-import androidx.lifecycle.ViewModel
-import androidx.lifecycle.viewModelScope
-import com.teamoffroad.core.common.domain.usecase.GetAutoSignInUseCase
-import dagger.hilt.android.lifecycle.HiltViewModel
-import kotlinx.coroutines.delay
-import kotlinx.coroutines.flow.MutableSharedFlow
-import kotlinx.coroutines.flow.SharedFlow
-import kotlinx.coroutines.flow.asSharedFlow
-import kotlinx.coroutines.flow.first
-import kotlinx.coroutines.launch
-import javax.inject.Inject
-
-@HiltViewModel
-class SplashViewModel @Inject constructor(
- private val getAutoSignInUseCase: GetAutoSignInUseCase,
-) : ViewModel() {
- private val _splashUiState = MutableSharedFlow()
- val splashUiState: SharedFlow get() = _splashUiState.asSharedFlow()
-
- fun showSplash() {
- viewModelScope.launch {
- delay(1550L)
- checkAutoSignIn()
- }
- }
-
- private fun checkAutoSignIn() {
- viewModelScope.launch {
- val isAuthSignIn = getAutoSignInUseCase()
- if (isAuthSignIn.first()) {
- _splashUiState.emit(SplashUiState.NavigateHome)
- } else {
- _splashUiState.emit(SplashUiState.NavigateLogin)
- }
- }
- }
-}
\ No newline at end of file
diff --git a/feature/main/src/main/java/com/teamoffroad/feature/main/splash/navigation/SplashNavigation.kt b/feature/main/src/main/java/com/teamoffroad/feature/main/splash/navigation/SplashNavigation.kt
deleted file mode 100644
index fff773be..00000000
--- a/feature/main/src/main/java/com/teamoffroad/feature/main/splash/navigation/SplashNavigation.kt
+++ /dev/null
@@ -1,23 +0,0 @@
-package com.teamoffroad.feature.main.splash.navigation
-
-import androidx.navigation.NavController
-import androidx.navigation.NavGraphBuilder
-import androidx.navigation.compose.composable
-import com.teamoffroad.core.navigation.Route
-import com.teamoffroad.feature.main.splash.SplashScreen
-
-fun NavController.navigateToAuth() {
- navigate(Route.Auth)
-}
-
-fun NavGraphBuilder.splashNavGraph(
- navigateToAuth: () -> Unit,
- navigateToHome: () -> Unit,
-) {
- composable {
- SplashScreen(
- navigateToAuth,
- navigateToHome,
- )
- }
-}
\ No newline at end of file
diff --git a/feature/mypage/src/main/java/com/teamoffroad/feature/mypage/navigation/MyPageNavigation.kt b/feature/mypage/src/main/java/com/teamoffroad/feature/mypage/navigation/MyPageNavigation.kt
index 994b3c16..51ff17df 100644
--- a/feature/mypage/src/main/java/com/teamoffroad/feature/mypage/navigation/MyPageNavigation.kt
+++ b/feature/mypage/src/main/java/com/teamoffroad/feature/mypage/navigation/MyPageNavigation.kt
@@ -48,8 +48,10 @@ fun NavController.navigateToSetting() {
navigate(MyPageRoute.Setting)
}
-fun NavController.navigateToAnnouncement() {
- navigate(MyPageRoute.Announcement)
+fun NavController.navigateToAnnouncement(announcementId: String?) {
+ navigate(
+ MyPageRoute.Announcement(announcementId)
+ )
}
fun NavController.navigateToAnnouncementDetail(
@@ -74,7 +76,7 @@ fun NavController.navigateToAnnouncementDetail(
)
}
-fun NavController.navigateToSignIn() {
+fun NavController.navigateToAuth() {
navigate(Route.Auth)
}
@@ -88,7 +90,7 @@ fun NavGraphBuilder.myPageNavGraph(
navigateToAvailableCouponDetail: (Int, String, String, String, Int) -> Unit,
navigateToGainedEmblems: () -> Unit,
navigateToSetting: () -> Unit,
- navigateToAnnouncement: () -> Unit,
+ navigateToAnnouncement: (String?) -> Unit,
navigateToAnnouncementDetail: (String, String, Boolean, String, Boolean, List, List) -> Unit,
navigateToSignIn: () -> Unit,
navigateToCharacterDetail: (Int, Boolean) -> Unit,
@@ -127,14 +129,16 @@ fun NavGraphBuilder.myPageNavGraph(
composable {
SettingScreen(
- navigateToAnnouncement = navigateToAnnouncement,
+ navigateToAnnouncement = { navigateToAnnouncement(null) },
navigateToSignIn = navigateToSignIn,
navigateToBack = navigateToBack
)
}
- composable {
+ composable { backStackEntry ->
+ val announcementId = backStackEntry.toRoute().announcementId
AnnouncementScreen(
+ announcementId,
navigateToAnnouncementDetail,
navigateToBack
)
diff --git a/feature/mypage/src/main/java/com/teamoffroad/feature/mypage/presentation/AnnouncementScreen.kt b/feature/mypage/src/main/java/com/teamoffroad/feature/mypage/presentation/AnnouncementScreen.kt
index 380e639b..5c2d8bb7 100644
--- a/feature/mypage/src/main/java/com/teamoffroad/feature/mypage/presentation/AnnouncementScreen.kt
+++ b/feature/mypage/src/main/java/com/teamoffroad/feature/mypage/presentation/AnnouncementScreen.kt
@@ -34,6 +34,7 @@ import com.teamoffroad.offroad.feature.mypage.R
@Composable
internal fun AnnouncementScreen(
+ announcementId: String?,
navigateToAnnouncementDetail: (String, String, Boolean, String, Boolean, List, List) -> Unit,
navigateToBack: () -> Unit,
viewModel: AnnouncementViewModel = hiltViewModel()
From 7a398ea9ba2df3cd8caf7aa2b7e45aedcff7c63c Mon Sep 17 00:00:00 2001
From: YuJeongHyun <0703olivia@naver.com>
Date: Sat, 16 Nov 2024 20:31:18 +0900
Subject: [PATCH 07/30] feature: Add chat exist
---
.../home/presentation/component/HomeIcons.kt | 86 +++++++++++--------
1 file changed, 52 insertions(+), 34 deletions(-)
diff --git a/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/component/HomeIcons.kt b/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/component/HomeIcons.kt
index cc6d37cb..b42abc81 100644
--- a/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/component/HomeIcons.kt
+++ b/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/component/HomeIcons.kt
@@ -7,20 +7,31 @@ import android.widget.Toast
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.RequiresApi
+import androidx.compose.foundation.Canvas
import androidx.compose.foundation.Image
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.aspectRatio
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.focus.FocusRequester
+import androidx.compose.ui.graphics.drawscope.Fill
+import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.unit.dp
import com.teamoffroad.core.designsystem.component.clickableWithoutRipple
+import com.teamoffroad.core.designsystem.theme.ErrorNew
import com.teamoffroad.offroad.feature.home.R
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
@@ -30,43 +41,50 @@ fun HomeIcons(
imageUrl: String,
navigateToGainedCharacter: () -> Unit,
) {
- val scope = rememberCoroutineScope()
- val permission = Manifest.permission.WRITE_EXTERNAL_STORAGE
-
- val launcher = rememberLauncherForActivityResult(
- contract = ActivityResultContracts.RequestPermission()
- ) { isGranted ->
- if (isGranted) {
- Toast.makeText(context, "권한이 허용되었습니다.", Toast.LENGTH_SHORT).show()
- } else {
- Toast.makeText(context, "권한이 허용되지 않았습니다.", Toast.LENGTH_SHORT).show()
- }
- }
+ val showTextField = remember { mutableStateOf(false) }
+ val textState = remember { mutableStateOf(TextFieldValue()) }
+ val focusRequester = remember { FocusRequester() }
+ val focusManager = LocalFocusManager.current
Box(
- contentAlignment = Alignment.TopEnd,
- modifier = Modifier.aspectRatio(48f / 144f).padding(top = 80.dp, end = 20.dp)
+ modifier = Modifier
+ .fillMaxSize()
+ .clickableWithoutRipple {
+ focusManager.clearFocus()
+ showTextField.value = false
+ }
) {
- Column {
- val downloadInteractionSource = remember { MutableInteractionSource() }
- Image(
- painter = painterResource(id = R.drawable.ic_home_chat),
- contentDescription = "chat",
- modifier = Modifier
-// modifier = Modifier
-// .clickableWithoutRipple(downloadInteractionSource) {
-// if (ContextCompat.checkSelfPermission(
-// context,
-// permission
-// ) == PackageManager.PERMISSION_GRANTED
-// ) {
-// downloadImage(context, imageUrl, scope)
-// Toast.makeText(context, "이미지 다운 완료", Toast.LENGTH_SHORT).show()
-// } else {
-// launcher.launch(permission)
-// }
-// }
- )
+ Column(
+ modifier = Modifier
+ .align(Alignment.TopEnd)
+ .padding(top = 80.dp, end = 20.dp)
+ .width(48.dp)
+ ) {
+ Box {
+ Image(
+ painter = painterResource(id = R.drawable.ic_home_chat),
+ contentDescription = "chat",
+ modifier = Modifier.clickableWithoutRipple {
+ showTextField.value = true
+ }
+ )
+ Box(
+ modifier = Modifier.fillMaxWidth(),
+ contentAlignment = Alignment.TopEnd
+ ) {
+ Canvas(
+ modifier = Modifier
+ .padding(top = 6.dp, end = 6.dp)
+ .size(8.dp)
+ ) {
+ drawCircle(
+ color = ErrorNew,
+ style = Fill
+ )
+ }
+ }
+ }
+
Image(
painter = painterResource(id = R.drawable.ic_home_upload),
From d90884d35d3af2e7bdf4cd9ee6a7afcf80caec0e Mon Sep 17 00:00:00 2001
From: YuJeongHyun <0703olivia@naver.com>
Date: Sat, 16 Nov 2024 21:31:57 +0900
Subject: [PATCH 08/30] feature: Add ChatTextField
---
.../home/presentation/ChatTextField.kt | 150 ++++++++++++++++++
.../feature/home/presentation/HomeScreen.kt | 53 ++++---
.../res/drawable/ic_character_chat_send.xml | 12 ++
3 files changed, 193 insertions(+), 22 deletions(-)
create mode 100644 feature/home/src/main/java/com/teamoffroad/feature/home/presentation/ChatTextField.kt
create mode 100644 feature/home/src/main/res/drawable/ic_character_chat_send.xml
diff --git a/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/ChatTextField.kt b/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/ChatTextField.kt
new file mode 100644
index 00000000..8c1435e6
--- /dev/null
+++ b/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/ChatTextField.kt
@@ -0,0 +1,150 @@
+package com.teamoffroad.feature.home.presentation
+
+import android.graphics.Rect
+import android.view.ViewTreeObserver
+import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.wrapContentHeight
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.TextField
+import androidx.compose.material3.TextFieldDefaults
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableIntStateOf
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.focus.FocusRequester
+import androidx.compose.ui.focus.focusRequester
+import androidx.compose.ui.focus.onFocusChanged
+import androidx.compose.ui.layout.onGloballyPositioned
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.platform.LocalFocusManager
+import androidx.compose.ui.platform.LocalView
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.unit.dp
+import com.teamoffroad.core.designsystem.component.clickableWithoutRipple
+import com.teamoffroad.core.designsystem.theme.BtnInactive
+import com.teamoffroad.core.designsystem.theme.Main2
+import com.teamoffroad.core.designsystem.theme.OffroadTheme
+import com.teamoffroad.core.designsystem.theme.Transparent
+import com.teamoffroad.core.designsystem.theme.White
+import com.teamoffroad.offroad.feature.home.R
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun ChatTextField(
+ modifier: Modifier = Modifier,
+ text: String = "",
+ isChatting: Boolean = false,
+ onValueChange: (String) -> Unit = {},
+ onFocusChange: (Boolean) -> Unit = {},
+ onSendClick: () -> Unit = {},
+) {
+ val scrollState = rememberScrollState()
+ val focusRequester = remember { FocusRequester() }
+ val focusManager = LocalFocusManager.current
+ val contextView = LocalView.current
+
+ var keyboardVisible by remember { mutableStateOf(false) }
+
+ LaunchedEffect(isChatting) {
+ if (isChatting) {
+ focusRequester.requestFocus()
+ }
+ }
+
+ LaunchedEffect(keyboardVisible) {
+ if (!keyboardVisible) {
+ focusManager.clearFocus()
+ }
+ }
+
+ DisposableEffect(contextView) {
+ val rect = Rect()
+ val listener = ViewTreeObserver.OnGlobalLayoutListener {
+ contextView.getWindowVisibleDisplayFrame(rect)
+ val screenHeight = contextView.rootView.height
+ val keypadHeight = screenHeight - rect.bottom
+ keyboardVisible = keypadHeight > screenHeight * 0.15
+ }
+ contextView.viewTreeObserver.addOnGlobalLayoutListener(listener)
+ onDispose {
+ contextView.viewTreeObserver.removeOnGlobalLayoutListener(listener)
+ }
+ }
+
+ AnimatedVisibility(
+ visible = isChatting,
+ ) {
+ Box(
+ modifier = modifier
+ .fillMaxWidth()
+ .wrapContentHeight()
+ .background(color = White, shape = RoundedCornerShape(topStart = 18.dp, topEnd = 18.dp))
+ .padding(horizontal = 22.dp, vertical = 4.dp),
+ ) {
+ val textFieldHeight = remember { mutableIntStateOf(0) }
+
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(with(LocalDensity.current) { textFieldHeight.intValue.toDp() })
+ .align(Alignment.Center)
+ .padding(vertical = 10.dp)
+ .padding(end = 44.dp)
+ .background(
+ color = BtnInactive,
+ shape = RoundedCornerShape(10.dp),
+ ),
+ )
+ TextField(
+ value = text,
+ onValueChange = { onValueChange(it) },
+ textStyle = OffroadTheme.typography.textRegular,
+ modifier = Modifier
+ .verticalScroll(scrollState)
+ .padding(end = 44.dp)
+ .padding(horizontal = 2.dp)
+ .fillMaxWidth()
+ .focusRequester(focusRequester)
+ .onGloballyPositioned { layoutCoordinates ->
+ textFieldHeight.intValue = layoutCoordinates.size.height
+ }
+ .onFocusChanged { focusState ->
+ onFocusChange(focusState.isFocused)
+ },
+ maxLines = 2,
+ colors = TextFieldDefaults.textFieldColors(
+ containerColor = Transparent,
+ focusedIndicatorColor = Transparent,
+ unfocusedIndicatorColor = Transparent,
+ focusedTextColor = Main2,
+ ),
+ shape = RoundedCornerShape(12.dp),
+ )
+ Image(
+ painter = painterResource(id = R.drawable.ic_character_chat_send),
+ contentDescription = null,
+ modifier = Modifier
+ .padding(end = 2.dp)
+ .size(36.dp)
+ .align(Alignment.CenterEnd)
+ .clickableWithoutRipple { onSendClick() },
+ )
+ }
+ }
+}
diff --git a/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeScreen.kt b/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeScreen.kt
index b22cda22..df15802b 100644
--- a/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeScreen.kt
+++ b/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeScreen.kt
@@ -10,10 +10,11 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.padding
-import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
@@ -22,7 +23,6 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Brush
-import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
@@ -73,28 +73,37 @@ fun HomeScreen(
if (completeQuests.isNotEmpty()) isCompleteQuestDialogShown.value = true
}
- StaticAnimationWrapper {
- Surface(
+ Box(
+ modifier = Modifier
+ .background(homeGradientBackground)
+ .fillMaxSize()
+ .padding(bottom = 140.dp)
+ //.navigationBarsPadding(),
+ ) {
+ Column(modifier = Modifier.fillMaxSize()) {
+ UsersAdventuresInformation(
+ context = context,
+ modifier = Modifier
+ .weight(1f)
+ .actionBarPadding(),
+ viewModel = viewModel,
+ navigateToGainedCharacter = navigateToGainedCharacter,
+ )
+ Spacer(modifier = Modifier.padding(top = 12.dp))
+ UsersQuestInformation(context, viewModel)
+ }
+
+ Box(
modifier = Modifier
- .background(homeGradientBackground)
- .padding(bottom = 140.dp)
- .navigationBarsPadding(),
- color = Color.Transparent
+ .fillMaxSize(),
+ contentAlignment = Alignment.BottomCenter
) {
- StaticAnimationWrapper {
- Column(modifier = Modifier.fillMaxWidth()) {
- UsersAdventuresInformation(
- context = context,
- modifier = Modifier
- .weight(1f)
- .actionBarPadding(),
- viewModel = viewModel,
- navigateToGainedCharacter = navigateToGainedCharacter,
- )
- Spacer(modifier = Modifier.padding(top = 12.dp))
- UsersQuestInformation(context, viewModel)
- }
- }
+ ChatTextField(
+ modifier = Modifier
+ //.imePadding()
+ .align(Alignment.BottomCenter),
+ isChatting = true
+ )
}
}
diff --git a/feature/home/src/main/res/drawable/ic_character_chat_send.xml b/feature/home/src/main/res/drawable/ic_character_chat_send.xml
new file mode 100644
index 00000000..d3108306
--- /dev/null
+++ b/feature/home/src/main/res/drawable/ic_character_chat_send.xml
@@ -0,0 +1,12 @@
+
+
+
+
From ecbd62f76f8c4fd53f2c77b19d3c2d727b763a0f Mon Sep 17 00:00:00 2001
From: leeseokchan00 <112953135+leeseokchan00@users.noreply.github.com>
Date: Sat, 16 Nov 2024 23:17:47 +0900
Subject: [PATCH 09/30] feature: Add fcm navigate to mypage
---
.../offroad.app/OffRoadMessagingService.kt | 46 +++++++------------
.../common/domain/model/FcmNotificationKey.kt | 13 ++++++
feature/main/build.gradle.kts | 1 +
.../teamoffroad/feature/main/MainActivity.kt | 6 ++-
.../teamoffroad/feature/main/MainScreen.kt | 11 +++--
5 files changed, 42 insertions(+), 35 deletions(-)
create mode 100644 core/common/src/main/java/com/teamoffroad/core/common/domain/model/FcmNotificationKey.kt
diff --git a/app/src/main/java/com/teamoffroad/offroad.app/OffRoadMessagingService.kt b/app/src/main/java/com/teamoffroad/offroad.app/OffRoadMessagingService.kt
index 51e8c4d7..7fd03c61 100644
--- a/app/src/main/java/com/teamoffroad/offroad.app/OffRoadMessagingService.kt
+++ b/app/src/main/java/com/teamoffroad/offroad.app/OffRoadMessagingService.kt
@@ -7,13 +7,20 @@ import android.content.Context
import android.content.Intent
import android.graphics.drawable.BitmapDrawable
import android.os.Build
-import android.util.Log
import androidx.core.app.NotificationCompat
import coil.Coil
import coil.request.ImageRequest
import com.google.firebase.messaging.Constants.MessageNotificationKeys
import com.google.firebase.messaging.FirebaseMessagingService
import com.google.firebase.messaging.RemoteMessage
+import com.teamoffroad.core.common.domain.model.FcmNotificationKey.CHANNEL_ID
+import com.teamoffroad.core.common.domain.model.FcmNotificationKey.KEY_BODY
+import com.teamoffroad.core.common.domain.model.FcmNotificationKey.KEY_ID
+import com.teamoffroad.core.common.domain.model.FcmNotificationKey.KEY_IMAGE
+import com.teamoffroad.core.common.domain.model.FcmNotificationKey.KEY_TITLE
+import com.teamoffroad.core.common.domain.model.FcmNotificationKey.KEY_TYPE
+import com.teamoffroad.core.common.domain.model.FcmNotificationKey.NOTICE
+import com.teamoffroad.core.common.domain.model.FcmNotificationKey.TYPE_CHARACTER_CHAT
import com.teamoffroad.core.common.domain.repository.DeviceTokenRepository
import com.teamoffroad.core.common.util.ActivityLifecycleHandler
import com.teamoffroad.feature.main.MainActivity
@@ -38,11 +45,10 @@ class OffRoadMessagingService : FirebaseMessagingService() {
super.onMessageReceived(remoteMessage)
if (remoteMessage.data.isNotEmpty()) {
if (ActivityLifecycleHandler.isAppInForeground) {
- //TODO. 키 밸류값 나오면 바꾸기
if (remoteMessage.data[KEY_TYPE] != TYPE_CHARACTER_CHAT)
- sendForeGroundNotification(remoteMessage, true)
+ sendForeGroundNotification(remoteMessage)
} else {
- sendBackGroundNotification(remoteMessage, false)
+ sendBackGroundNotification(remoteMessage)
}
}
}
@@ -84,11 +90,7 @@ class OffRoadMessagingService : FirebaseMessagingService() {
private fun createNotificationIntent(remoteMessage: RemoteMessage): Intent {
return Intent(this, MainActivity::class.java).apply {
- if (ActivityLifecycleHandler.isAppInForeground) {
- Log.d("asdasd", "forground")
- } else {
- flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK
- }
+ flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK
putExtra(KEY_TYPE, remoteMessage.data[KEY_TYPE])
if (remoteMessage.data[KEY_TYPE] != TYPE_CHARACTER_CHAT) {
putExtra(KEY_ID, remoteMessage.data[KEY_ID])
@@ -101,7 +103,7 @@ class OffRoadMessagingService : FirebaseMessagingService() {
onLargeIconReady: (NotificationCompat.Builder) -> Unit
): NotificationCompat.Builder {
- val notificationBuilder = NotificationCompat.Builder(this, "channelId")
+ val notificationBuilder = NotificationCompat.Builder(this, CHANNEL_ID)
.setSmallIcon(R.mipmap.ic_launcher)
.setContentTitle(remoteMessage.data[KEY_TITLE])
.setContentText(remoteMessage.data[KEY_BODY])
@@ -132,7 +134,7 @@ class OffRoadMessagingService : FirebaseMessagingService() {
onLargeIconReady: (NotificationCompat.Builder) -> Unit
): NotificationCompat.Builder {
- val notificationBuilder = NotificationCompat.Builder(this, "channelId")
+ val notificationBuilder = NotificationCompat.Builder(this, CHANNEL_ID)
.setSmallIcon(R.mipmap.ic_launcher)
.setContentTitle(remoteMessage.data[KEY_TITLE])
.setContentText(remoteMessage.data[KEY_BODY])
@@ -158,18 +160,14 @@ class OffRoadMessagingService : FirebaseMessagingService() {
return notificationBuilder
}
- private fun sendForeGroundNotification(remoteMessage: RemoteMessage, isForeGround: Boolean) {
+ private fun sendForeGroundNotification(remoteMessage: RemoteMessage) {
val uniqueIdentifier = generateUniqueIdentifier()
createForeGroundNotificationBuilder(remoteMessage) { notificationBuilder ->
showNotification(notificationBuilder, uniqueIdentifier)
-
- if(isForeGround){
-
- }
}
}
- private fun sendBackGroundNotification(remoteMessage: RemoteMessage, isForeGround: Boolean) {
+ private fun sendBackGroundNotification(remoteMessage: RemoteMessage) {
val uniqueIdentifier = generateUniqueIdentifier()
val intent = createNotificationIntent(remoteMessage)
val pendingIntent = createPendingIntent(intent, uniqueIdentifier)
@@ -187,22 +185,10 @@ class OffRoadMessagingService : FirebaseMessagingService() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel =
- NotificationChannel("channelId", "Notice", NotificationManager.IMPORTANCE_HIGH)
+ NotificationChannel(CHANNEL_ID, NOTICE, NotificationManager.IMPORTANCE_HIGH)
notificationManager.createNotificationChannel(channel)
}
notificationManager.notify(uniqueIdentifier, notificationBuilder.build())
}
-
- companion object {
- //TODO. 나중에 정리 해야됨
- private const val KEY_TITLE = "title"
- private const val KEY_BODY = "body"
- private const val KEY_TYPE = "type"
- private const val KEY_IMAGE = "image"
- private const val KEY_ID = "additionalProp1"
- private const val TYPE_CHARACTER_CHAT = "CHARACTER_CHAT"
- private const val TYPE_ANNOUNCEMENT = "ANNOUNCEMENT_REDIRECT"
-
- }
}
\ No newline at end of file
diff --git a/core/common/src/main/java/com/teamoffroad/core/common/domain/model/FcmNotificationKey.kt b/core/common/src/main/java/com/teamoffroad/core/common/domain/model/FcmNotificationKey.kt
new file mode 100644
index 00000000..19031878
--- /dev/null
+++ b/core/common/src/main/java/com/teamoffroad/core/common/domain/model/FcmNotificationKey.kt
@@ -0,0 +1,13 @@
+package com.teamoffroad.core.common.domain.model
+
+object FcmNotificationKey {
+ const val CHANNEL_ID = "channelId"
+ const val NOTICE = "Notice"
+ const val KEY_TITLE = "title"
+ const val KEY_BODY = "body"
+ const val KEY_TYPE = "type"
+ const val KEY_IMAGE = "image"
+ const val KEY_ID = "additionalProp1"
+ const val TYPE_CHARACTER_CHAT = "CHARACTER_CHAT"
+ const val TYPE_ANNOUNCEMENT = "ANNOUNCEMENT_REDIRECT"
+}
\ No newline at end of file
diff --git a/feature/main/build.gradle.kts b/feature/main/build.gradle.kts
index bf84b91f..a25fadcc 100644
--- a/feature/main/build.gradle.kts
+++ b/feature/main/build.gradle.kts
@@ -13,6 +13,7 @@ android {
}
dependencies {
+ implementation(project(":core:common"))
implementation(project(":feature:auth"))
implementation(project(":feature:home"))
implementation(project(":feature:explore"))
diff --git a/feature/main/src/main/java/com/teamoffroad/feature/main/MainActivity.kt b/feature/main/src/main/java/com/teamoffroad/feature/main/MainActivity.kt
index f1f395a7..0bc6f00f 100644
--- a/feature/main/src/main/java/com/teamoffroad/feature/main/MainActivity.kt
+++ b/feature/main/src/main/java/com/teamoffroad/feature/main/MainActivity.kt
@@ -13,6 +13,8 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
+import com.teamoffroad.core.common.domain.model.FcmNotificationKey.KEY_ID
+import com.teamoffroad.core.common.domain.model.FcmNotificationKey.KEY_TYPE
import com.teamoffroad.core.designsystem.theme.OffroadTheme
import com.teamoffroad.feature.main.component.MainTransparentActionBar
import dagger.hilt.android.AndroidEntryPoint
@@ -23,8 +25,8 @@ import kotlinx.coroutines.delay
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- val notificationType = intent.getStringExtra("type")
- val notificationId = intent.getStringExtra("additionalProp1")
+ val notificationType = intent.getStringExtra(KEY_TYPE)
+ val notificationId = intent.getStringExtra(KEY_ID)
setContent {
val navigator: MainNavigator = rememberMainNavigator()
val showSplash = remember { mutableStateOf(true) }
diff --git a/feature/main/src/main/java/com/teamoffroad/feature/main/MainScreen.kt b/feature/main/src/main/java/com/teamoffroad/feature/main/MainScreen.kt
index 9be38aaf..817a6f81 100644
--- a/feature/main/src/main/java/com/teamoffroad/feature/main/MainScreen.kt
+++ b/feature/main/src/main/java/com/teamoffroad/feature/main/MainScreen.kt
@@ -7,6 +7,7 @@ import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Modifier
+import com.teamoffroad.core.common.domain.model.FcmNotificationKey.TYPE_ANNOUNCEMENT
import com.teamoffroad.core.common.util.OnBackButtonListener
import com.teamoffroad.core.designsystem.component.navigationPadding
import com.teamoffroad.feature.main.component.MainBottomBar
@@ -23,9 +24,13 @@ internal fun MainScreen(
) {
LaunchedEffect(Unit) {
if (!notificationType.isNullOrBlank()) {
- if (notificationType == "ANNOUNCEMENT_REDIRECT")
- notificationId?.let { navigator.navigateToAnnouncement(it) }
- else if (notificationType == "CHARACTER_CHAT") {
+ if (notificationType == TYPE_ANNOUNCEMENT)
+ notificationId?.let {
+ navigator.navigateToMyPage()
+ navigator.navigateToSetting()
+ navigator.navigateToAnnouncement(it)
+ }
+ else {
navigator.navigateToHome()
}
}
From afb049afda91f6b39058350d58d395e0cfcd5210 Mon Sep 17 00:00:00 2001
From: YuJeongHyun <0703olivia@naver.com>
Date: Sat, 16 Nov 2024 23:40:21 +0900
Subject: [PATCH 10/30] feature: Edit ChatTextField
---
.../home/presentation/ChatTextField.kt | 4 +-
.../feature/home/presentation/HomeScreen.kt | 38 ++++++++++++-------
.../home/presentation/component/HomeIcons.kt | 15 ++++----
3 files changed, 35 insertions(+), 22 deletions(-)
diff --git a/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/ChatTextField.kt b/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/ChatTextField.kt
index 8c1435e6..376f1105 100644
--- a/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/ChatTextField.kt
+++ b/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/ChatTextField.kt
@@ -20,6 +20,7 @@ import androidx.compose.material3.TextFieldDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.MutableState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
@@ -50,6 +51,7 @@ fun ChatTextField(
modifier: Modifier = Modifier,
text: String = "",
isChatting: Boolean = false,
+ keyboard: Boolean,
onValueChange: (String) -> Unit = {},
onFocusChange: (Boolean) -> Unit = {},
onSendClick: () -> Unit = {},
@@ -59,7 +61,7 @@ fun ChatTextField(
val focusManager = LocalFocusManager.current
val contextView = LocalView.current
- var keyboardVisible by remember { mutableStateOf(false) }
+ var keyboardVisible by remember { mutableStateOf(keyboard) }
LaunchedEffect(isChatting) {
if (isChatting) {
diff --git a/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeScreen.kt b/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeScreen.kt
index df15802b..7268cc6f 100644
--- a/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeScreen.kt
+++ b/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeScreen.kt
@@ -8,15 +8,18 @@ import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.ime
import androidx.compose.foundation.layout.imePadding
-import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.MutableState
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
@@ -24,12 +27,12 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
-import com.teamoffroad.core.designsystem.component.StaticAnimationWrapper
import com.teamoffroad.core.designsystem.component.actionBarPadding
import com.teamoffroad.core.designsystem.theme.HomeGradi1
import com.teamoffroad.core.designsystem.theme.HomeGradi2
@@ -53,6 +56,7 @@ val homeGradientBackground = Brush.verticalGradient(
colors = listOf(HomeGradi1, HomeGradi2, HomeGradi3, HomeGradi4, HomeGradi5, HomeGradi6)
)
+@OptIn(ExperimentalLayoutApi::class)
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
@Composable
fun HomeScreen(
@@ -63,7 +67,8 @@ fun HomeScreen(
val context = LocalContext.current
val viewModel: HomeViewModel = hiltViewModel()
val isCompleteQuestDialogShown = remember { mutableStateOf(false) }
-
+ val imeHeight = WindowInsets.ime.getBottom(LocalDensity.current)
+ val isChatting = remember { mutableStateOf(false) }
LaunchedEffect(Unit) {
viewModel.updateAutoSignIn()
@@ -78,10 +83,10 @@ fun HomeScreen(
.background(homeGradientBackground)
.fillMaxSize()
.padding(bottom = 140.dp)
- //.navigationBarsPadding(),
) {
Column(modifier = Modifier.fillMaxSize()) {
UsersAdventuresInformation(
+ isChatting = isChatting,
context = context,
modifier = Modifier
.weight(1f)
@@ -93,17 +98,20 @@ fun HomeScreen(
UsersQuestInformation(context, viewModel)
}
- Box(
- modifier = Modifier
- .fillMaxSize(),
- contentAlignment = Alignment.BottomCenter
- ) {
- ChatTextField(
+ if (isChatting.value) {
+ Box(
modifier = Modifier
- //.imePadding()
- .align(Alignment.BottomCenter),
- isChatting = true
- )
+ .fillMaxWidth()
+ .align(Alignment.BottomCenter)
+ .padding(bottom = 230.dp)
+ ) {
+ ChatTextField(
+ modifier = Modifier
+ .imePadding(),
+ isChatting = isChatting.value,
+ keyboard = true
+ )
+ }
}
}
@@ -121,6 +129,7 @@ fun HomeScreen(
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
@Composable
private fun UsersAdventuresInformation(
+ isChatting: MutableState,
context: Context,
modifier: Modifier = Modifier,
viewModel: HomeViewModel,
@@ -150,6 +159,7 @@ private fun UsersAdventuresInformation(
contentAlignment = Alignment.TopEnd
) {
HomeIcons(
+ isChatting = isChatting,
context = context,
imageUrl = imageUrl,
navigateToGainedCharacter = navigateToGainedCharacter,
diff --git a/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/component/HomeIcons.kt b/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/component/HomeIcons.kt
index b42abc81..3ca76717 100644
--- a/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/component/HomeIcons.kt
+++ b/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/component/HomeIcons.kt
@@ -19,6 +19,7 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
@@ -37,21 +38,21 @@ import com.teamoffroad.offroad.feature.home.R
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
@Composable
fun HomeIcons(
+ isChatting: MutableState,
context: Context,
imageUrl: String,
navigateToGainedCharacter: () -> Unit,
) {
- val showTextField = remember { mutableStateOf(false) }
- val textState = remember { mutableStateOf(TextFieldValue()) }
- val focusRequester = remember { FocusRequester() }
- val focusManager = LocalFocusManager.current
+// val showTextField = remember { mutableStateOf(false) }
+// val textState = remember { mutableStateOf(TextFieldValue()) }
+// val focusRequester = remember { FocusRequester() }
+// val focusManager = LocalFocusManager.current
Box(
modifier = Modifier
.fillMaxSize()
.clickableWithoutRipple {
- focusManager.clearFocus()
- showTextField.value = false
+ isChatting.value = false
}
) {
Column(
@@ -65,7 +66,7 @@ fun HomeIcons(
painter = painterResource(id = R.drawable.ic_home_chat),
contentDescription = "chat",
modifier = Modifier.clickableWithoutRipple {
- showTextField.value = true
+ isChatting.value = true
}
)
Box(
From 160232ef8300e8ac9ae538641cd3c09e30dd0847 Mon Sep 17 00:00:00 2001
From: YuJeongHyun <0703olivia@naver.com>
Date: Sat, 16 Nov 2024 23:46:21 +0900
Subject: [PATCH 11/30] refactor: Edit home ui
---
.../presentation/{ChatTextField.kt => HomeChatTextField.kt} | 3 +--
.../com/teamoffroad/feature/home/presentation/HomeScreen.kt | 6 +++---
2 files changed, 4 insertions(+), 5 deletions(-)
rename feature/home/src/main/java/com/teamoffroad/feature/home/presentation/{ChatTextField.kt => HomeChatTextField.kt} (98%)
diff --git a/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/ChatTextField.kt b/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeChatTextField.kt
similarity index 98%
rename from feature/home/src/main/java/com/teamoffroad/feature/home/presentation/ChatTextField.kt
rename to feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeChatTextField.kt
index 376f1105..d47ca2f7 100644
--- a/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/ChatTextField.kt
+++ b/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeChatTextField.kt
@@ -20,7 +20,6 @@ import androidx.compose.material3.TextFieldDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.MutableState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
@@ -47,7 +46,7 @@ import com.teamoffroad.offroad.feature.home.R
@OptIn(ExperimentalMaterial3Api::class)
@Composable
-fun ChatTextField(
+fun HomeChatTextField(
modifier: Modifier = Modifier,
text: String = "",
isChatting: Boolean = false,
diff --git a/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeScreen.kt b/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeScreen.kt
index 7268cc6f..54ecc04d 100644
--- a/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeScreen.kt
+++ b/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeScreen.kt
@@ -82,7 +82,7 @@ fun HomeScreen(
modifier = Modifier
.background(homeGradientBackground)
.fillMaxSize()
- .padding(bottom = 140.dp)
+ .padding(bottom = 180.dp)
) {
Column(modifier = Modifier.fillMaxSize()) {
UsersAdventuresInformation(
@@ -103,9 +103,9 @@ fun HomeScreen(
modifier = Modifier
.fillMaxWidth()
.align(Alignment.BottomCenter)
- .padding(bottom = 230.dp)
+ //.padding(bottom = 196.dp)
) {
- ChatTextField(
+ HomeChatTextField(
modifier = Modifier
.imePadding(),
isChatting = isChatting.value,
From 45f50af073e45d5c39d7aa62a41de077c9783fb5 Mon Sep 17 00:00:00 2001
From: YuJeongHyun <0703olivia@naver.com>
Date: Sun, 17 Nov 2024 00:35:40 +0900
Subject: [PATCH 12/30] refactor: Edit keyboard
---
.../home/presentation/HomeChatTextField.kt | 34 ++++++++++---------
.../feature/home/presentation/HomeScreen.kt | 10 +++---
.../home/presentation/component/HomeIcons.kt | 12 -------
3 files changed, 23 insertions(+), 33 deletions(-)
diff --git a/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeChatTextField.kt b/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeChatTextField.kt
index d47ca2f7..0373dbc7 100644
--- a/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeChatTextField.kt
+++ b/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeChatTextField.kt
@@ -20,6 +20,7 @@ import androidx.compose.material3.TextFieldDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.MutableState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
@@ -49,7 +50,7 @@ import com.teamoffroad.offroad.feature.home.R
fun HomeChatTextField(
modifier: Modifier = Modifier,
text: String = "",
- isChatting: Boolean = false,
+ isChatting: MutableState,
keyboard: Boolean,
onValueChange: (String) -> Unit = {},
onFocusChange: (Boolean) -> Unit = {},
@@ -63,7 +64,7 @@ fun HomeChatTextField(
var keyboardVisible by remember { mutableStateOf(keyboard) }
LaunchedEffect(isChatting) {
- if (isChatting) {
+ if (isChatting.value) {
focusRequester.requestFocus()
}
}
@@ -71,25 +72,26 @@ fun HomeChatTextField(
LaunchedEffect(keyboardVisible) {
if (!keyboardVisible) {
focusManager.clearFocus()
+ isChatting.value = false
}
}
- DisposableEffect(contextView) {
- val rect = Rect()
- val listener = ViewTreeObserver.OnGlobalLayoutListener {
- contextView.getWindowVisibleDisplayFrame(rect)
- val screenHeight = contextView.rootView.height
- val keypadHeight = screenHeight - rect.bottom
- keyboardVisible = keypadHeight > screenHeight * 0.15
- }
- contextView.viewTreeObserver.addOnGlobalLayoutListener(listener)
- onDispose {
- contextView.viewTreeObserver.removeOnGlobalLayoutListener(listener)
- }
- }
+// DisposableEffect(contextView) {
+// val rect = Rect()
+// val listener = ViewTreeObserver.OnGlobalLayoutListener {
+// contextView.getWindowVisibleDisplayFrame(rect)
+// val screenHeight = contextView.rootView.height
+// val keypadHeight = screenHeight - rect.bottom
+// keyboardVisible = keypadHeight > screenHeight * 0.15
+// }
+// contextView.viewTreeObserver.addOnGlobalLayoutListener(listener)
+// onDispose {
+// contextView.viewTreeObserver.removeOnGlobalLayoutListener(listener)
+// }
+// }
AnimatedVisibility(
- visible = isChatting,
+ visible = isChatting.value,
) {
Box(
modifier = modifier
diff --git a/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeScreen.kt b/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeScreen.kt
index 54ecc04d..1e485028 100644
--- a/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeScreen.kt
+++ b/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeScreen.kt
@@ -56,7 +56,6 @@ val homeGradientBackground = Brush.verticalGradient(
colors = listOf(HomeGradi1, HomeGradi2, HomeGradi3, HomeGradi4, HomeGradi5, HomeGradi6)
)
-@OptIn(ExperimentalLayoutApi::class)
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
@Composable
fun HomeScreen(
@@ -96,6 +95,7 @@ fun HomeScreen(
)
Spacer(modifier = Modifier.padding(top = 12.dp))
UsersQuestInformation(context, viewModel)
+
}
if (isChatting.value) {
@@ -103,12 +103,12 @@ fun HomeScreen(
modifier = Modifier
.fillMaxWidth()
.align(Alignment.BottomCenter)
- //.padding(bottom = 196.dp)
+ .imePadding()
+ //.padding(bottom = with(LocalDensity.current) { imeHeight.toDp() })
+ .padding(bottom = 196.dp)
) {
HomeChatTextField(
- modifier = Modifier
- .imePadding(),
- isChatting = isChatting.value,
+ isChatting = isChatting,
keyboard = true
)
}
diff --git a/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/component/HomeIcons.kt b/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/component/HomeIcons.kt
index 3ca76717..fbe7aaef 100644
--- a/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/component/HomeIcons.kt
+++ b/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/component/HomeIcons.kt
@@ -1,18 +1,12 @@
package com.teamoffroad.feature.home.presentation.component
-import android.Manifest
import android.content.Context
import android.os.Build
-import android.widget.Toast
-import androidx.activity.compose.rememberLauncherForActivityResult
-import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.RequiresApi
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.Image
-import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
@@ -20,16 +14,10 @@ import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
-import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.graphics.drawscope.Fill
-import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.res.painterResource
-import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.unit.dp
import com.teamoffroad.core.designsystem.component.clickableWithoutRipple
import com.teamoffroad.core.designsystem.theme.ErrorNew
From e3ca4d70e3620ab236db3ef68fe25d4699fcb8cf Mon Sep 17 00:00:00 2001
From: YuJeongHyun <0703olivia@naver.com>
Date: Sun, 17 Nov 2024 01:36:00 +0900
Subject: [PATCH 13/30] feature: Add finish chatting button
---
.../home/presentation/HomeChatTextField.kt | 149 +++++++++++-------
.../feature/home/presentation/HomeScreen.kt | 62 ++++++--
.../home/presentation/component/HomeIcons.kt | 11 +-
feature/home/src/main/res/values/strings.xml | 2 +
4 files changed, 147 insertions(+), 77 deletions(-)
diff --git a/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeChatTextField.kt b/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeChatTextField.kt
index 0373dbc7..0983db1d 100644
--- a/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeChatTextField.kt
+++ b/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeChatTextField.kt
@@ -6,6 +6,8 @@ import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
@@ -15,6 +17,7 @@ import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.material3.TextFieldDefaults
import androidx.compose.runtime.Composable
@@ -36,6 +39,7 @@ import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.teamoffroad.core.designsystem.component.clickableWithoutRipple
import com.teamoffroad.core.designsystem.theme.BtnInactive
@@ -76,19 +80,19 @@ fun HomeChatTextField(
}
}
-// DisposableEffect(contextView) {
-// val rect = Rect()
-// val listener = ViewTreeObserver.OnGlobalLayoutListener {
-// contextView.getWindowVisibleDisplayFrame(rect)
-// val screenHeight = contextView.rootView.height
-// val keypadHeight = screenHeight - rect.bottom
-// keyboardVisible = keypadHeight > screenHeight * 0.15
-// }
-// contextView.viewTreeObserver.addOnGlobalLayoutListener(listener)
-// onDispose {
-// contextView.viewTreeObserver.removeOnGlobalLayoutListener(listener)
-// }
-// }
+ DisposableEffect(contextView) {
+ val rect = Rect()
+ val listener = ViewTreeObserver.OnGlobalLayoutListener {
+ contextView.getWindowVisibleDisplayFrame(rect)
+ val screenHeight = contextView.rootView.height
+ val keypadHeight = screenHeight - rect.bottom
+ keyboardVisible = keypadHeight > screenHeight * 0.15
+ }
+ contextView.viewTreeObserver.addOnGlobalLayoutListener(listener)
+ onDispose {
+ contextView.viewTreeObserver.removeOnGlobalLayoutListener(listener)
+ }
+ }
AnimatedVisibility(
visible = isChatting.value,
@@ -97,57 +101,82 @@ fun HomeChatTextField(
modifier = modifier
.fillMaxWidth()
.wrapContentHeight()
- .background(color = White, shape = RoundedCornerShape(topStart = 18.dp, topEnd = 18.dp))
+ .background(
+ color = White,
+ shape = RoundedCornerShape(topStart = 18.dp, topEnd = 18.dp)
+ )
.padding(horizontal = 22.dp, vertical = 4.dp),
) {
val textFieldHeight = remember { mutableIntStateOf(0) }
- Box(
- modifier = Modifier
- .fillMaxWidth()
- .height(with(LocalDensity.current) { textFieldHeight.intValue.toDp() })
- .align(Alignment.Center)
- .padding(vertical = 10.dp)
- .padding(end = 44.dp)
- .background(
- color = BtnInactive,
- shape = RoundedCornerShape(10.dp),
- ),
- )
- TextField(
- value = text,
- onValueChange = { onValueChange(it) },
- textStyle = OffroadTheme.typography.textRegular,
- modifier = Modifier
- .verticalScroll(scrollState)
- .padding(end = 44.dp)
- .padding(horizontal = 2.dp)
- .fillMaxWidth()
- .focusRequester(focusRequester)
- .onGloballyPositioned { layoutCoordinates ->
- textFieldHeight.intValue = layoutCoordinates.size.height
- }
- .onFocusChanged { focusState ->
- onFocusChange(focusState.isFocused)
- },
- maxLines = 2,
- colors = TextFieldDefaults.textFieldColors(
- containerColor = Transparent,
- focusedIndicatorColor = Transparent,
- unfocusedIndicatorColor = Transparent,
- focusedTextColor = Main2,
- ),
- shape = RoundedCornerShape(12.dp),
- )
- Image(
- painter = painterResource(id = R.drawable.ic_character_chat_send),
- contentDescription = null,
- modifier = Modifier
- .padding(end = 2.dp)
- .size(36.dp)
- .align(Alignment.CenterEnd)
- .clickableWithoutRipple { onSendClick() },
- )
+ Column {
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(top = 18.dp, bottom = 4.dp)
+ ) {
+ Text(
+ text = stringResource(id = R.string.home_chat_me),
+ color = Main2,
+ style = OffroadTheme.typography.textBold
+ )
+ Text(
+ text = "text",
+ modifier = Modifier.weight(1f),
+ style = OffroadTheme.typography.textRegular
+ )
+ }
+
+ Box {
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(with(LocalDensity.current) { textFieldHeight.intValue.toDp() })
+ .align(Alignment.Center)
+ .padding(vertical = 10.dp)
+ .padding(end = 44.dp)
+ .background(
+ color = BtnInactive,
+ shape = RoundedCornerShape(10.dp),
+ ),
+ )
+ TextField(
+ value = text,
+ onValueChange = { onValueChange(it) },
+ textStyle = OffroadTheme.typography.textRegular,
+ modifier = Modifier
+ .verticalScroll(scrollState)
+ .padding(end = 44.dp)
+ .padding(horizontal = 2.dp)
+ .fillMaxWidth()
+ .focusRequester(focusRequester)
+ .onGloballyPositioned { layoutCoordinates ->
+ textFieldHeight.intValue = layoutCoordinates.size.height
+ }
+ .onFocusChanged { focusState ->
+ onFocusChange(focusState.isFocused)
+ },
+ maxLines = 2,
+ colors = TextFieldDefaults.textFieldColors(
+ containerColor = Transparent,
+ focusedIndicatorColor = Transparent,
+ unfocusedIndicatorColor = Transparent,
+ focusedTextColor = Main2,
+ ),
+ shape = RoundedCornerShape(12.dp),
+ )
+ Image(
+ painter = painterResource(id = R.drawable.ic_character_chat_send),
+ contentDescription = null,
+ modifier = Modifier
+ .padding(end = 2.dp)
+ .size(36.dp)
+ .align(Alignment.CenterEnd)
+ .clickableWithoutRipple { onSendClick() },
+ )
+ }
+ }
+
}
}
}
diff --git a/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeScreen.kt b/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeScreen.kt
index 1e485028..db31ebc3 100644
--- a/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeScreen.kt
+++ b/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeScreen.kt
@@ -6,6 +6,7 @@ import android.widget.Toast
import androidx.annotation.RequiresApi
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
+import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ExperimentalLayoutApi
@@ -17,6 +18,8 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.ime
import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState
@@ -26,6 +29,7 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Brush
+import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.painterResource
@@ -34,6 +38,8 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import com.teamoffroad.core.designsystem.component.actionBarPadding
+import com.teamoffroad.core.designsystem.component.clickableWithoutRipple
+import com.teamoffroad.core.designsystem.theme.ErrorNew
import com.teamoffroad.core.designsystem.theme.HomeGradi1
import com.teamoffroad.core.designsystem.theme.HomeGradi2
import com.teamoffroad.core.designsystem.theme.HomeGradi3
@@ -41,6 +47,9 @@ import com.teamoffroad.core.designsystem.theme.HomeGradi4
import com.teamoffroad.core.designsystem.theme.HomeGradi5
import com.teamoffroad.core.designsystem.theme.HomeGradi6
import com.teamoffroad.core.designsystem.theme.OffroadTheme
+import com.teamoffroad.core.designsystem.theme.Sub
+import com.teamoffroad.core.designsystem.theme.Sub55
+import com.teamoffroad.core.designsystem.theme.White
import com.teamoffroad.feature.home.domain.model.UserQuests
import com.teamoffroad.feature.home.presentation.component.CompleteQuestDialog
import com.teamoffroad.feature.home.presentation.component.HomeIcons
@@ -107,10 +116,25 @@ fun HomeScreen(
//.padding(bottom = with(LocalDensity.current) { imeHeight.toDp() })
.padding(bottom = 196.dp)
) {
- HomeChatTextField(
- isChatting = isChatting,
- keyboard = true
- )
+ Column {
+ Box(
+ modifier = Modifier.fillMaxWidth()
+ ) {
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(end = 20.dp),
+ contentAlignment = Alignment.CenterEnd
+ ) {
+ FinishChatting(isChatting)
+ }
+ }
+
+ HomeChatTextField(
+ isChatting = isChatting,
+ keyboard = true
+ )
+ }
}
}
}
@@ -184,11 +208,31 @@ private fun UsersAdventuresInformation(
}
@Composable
-private fun HomeBackground() {
- Image(
- painter = painterResource(id = R.drawable.img_home_stamp),
- contentDescription = "stamp",
- modifier = Modifier.padding(top = 16.dp)
+fun FinishChatting(
+ isChatting: MutableState,
+ backgroundColor: Color = Sub55,
+ borderColor: Color = Sub
+) {
+ Text(
+ style = OffroadTheme.typography.subtitle2Semibold,
+ text = stringResource(id = R.string.home_chat_finish),
+ modifier = Modifier
+ .padding(bottom = 8.dp)
+ .background(
+ color = backgroundColor,
+ shape = RoundedCornerShape(20.dp)
+ )
+ .border(
+ width = 1.dp,
+ shape = RoundedCornerShape(20.dp),
+ color = borderColor
+ )
+ .padding(horizontal = 16.dp)
+ .padding(vertical = 8.dp)
+ .clickableWithoutRipple {
+ isChatting.value = false
+ },
+ color = White
)
}
diff --git a/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/component/HomeIcons.kt b/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/component/HomeIcons.kt
index fbe7aaef..aaa9067d 100644
--- a/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/component/HomeIcons.kt
+++ b/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/component/HomeIcons.kt
@@ -31,17 +31,12 @@ fun HomeIcons(
imageUrl: String,
navigateToGainedCharacter: () -> Unit,
) {
-// val showTextField = remember { mutableStateOf(false) }
-// val textState = remember { mutableStateOf(TextFieldValue()) }
-// val focusRequester = remember { FocusRequester() }
-// val focusManager = LocalFocusManager.current
-
Box(
modifier = Modifier
.fillMaxSize()
- .clickableWithoutRipple {
- isChatting.value = false
- }
+// .clickableWithoutRipple {
+// isChatting.value = false
+// }
) {
Column(
modifier = Modifier
diff --git a/feature/home/src/main/res/values/strings.xml b/feature/home/src/main/res/values/strings.xml
index b966f03a..24c07e7c 100644
--- a/feature/home/src/main/res/values/strings.xml
+++ b/feature/home/src/main/res/values/strings.xml
@@ -12,4 +12,6 @@
퀘스트 \'%s\' 외 %d개를\n클리어했어요! 마이페이지에서\n보상을 확인해보세요.
확인
-
+ 나 :
+ 채팅 종료
\ No newline at end of file
From b156f075959b27c22213cfb969cd54ccfabcea59 Mon Sep 17 00:00:00 2001
From: YuJeongHyun <0703olivia@naver.com>
Date: Sun, 17 Nov 2024 01:57:57 +0900
Subject: [PATCH 14/30] feature: Add textfield onValueChange
---
.../feature/home/presentation/HomeScreen.kt | 11 ++++++++++-
.../feature/home/presentation/HomeViewModel.kt | 16 ++++++++++++++++
2 files changed, 26 insertions(+), 1 deletion(-)
diff --git a/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeScreen.kt b/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeScreen.kt
index db31ebc3..83293678 100644
--- a/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeScreen.kt
+++ b/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeScreen.kt
@@ -37,6 +37,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.teamoffroad.core.designsystem.component.actionBarPadding
import com.teamoffroad.core.designsystem.component.clickableWithoutRipple
import com.teamoffroad.core.designsystem.theme.ErrorNew
@@ -77,6 +78,7 @@ fun HomeScreen(
val isCompleteQuestDialogShown = remember { mutableStateOf(false) }
val imeHeight = WindowInsets.ime.getBottom(LocalDensity.current)
val isChatting = remember { mutableStateOf(false) }
+ val chattingText = viewModel.chattingText.collectAsStateWithLifecycle()
LaunchedEffect(Unit) {
viewModel.updateAutoSignIn()
@@ -131,8 +133,15 @@ fun HomeScreen(
}
HomeChatTextField(
+ text = chattingText.value,
isChatting = isChatting,
- keyboard = true
+ keyboard = true,
+ onValueChange = { text ->
+ viewModel.updateChattingText(text)
+ },
+// onFocusChange = { isFocused ->
+// viewModel.updateIsChatting(isFocused)
+// }
)
}
}
diff --git a/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeViewModel.kt b/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeViewModel.kt
index e38fb4c8..85142822 100644
--- a/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeViewModel.kt
+++ b/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeViewModel.kt
@@ -11,6 +11,7 @@ import com.teamoffroad.feature.home.presentation.component.UiState
import com.teamoffroad.feature.home.presentation.component.getErrorMessage
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import javax.inject.Inject
@@ -54,6 +55,12 @@ class HomeViewModel @Inject constructor(
private val _linearProgressBar = MutableStateFlow(0f)
val linearProgressBar = _linearProgressBar.asStateFlow()
+ private val _isChatting: MutableStateFlow = MutableStateFlow(false)
+ val isChatting: StateFlow = _isChatting.asStateFlow()
+
+ private val _chattingText: MutableStateFlow = MutableStateFlow("")
+ val chattingText: StateFlow = _chattingText.asStateFlow()
+
fun getUsersAdventuresInformation(category: String) {
viewModelScope.launch {
runCatching {
@@ -140,4 +147,13 @@ class HomeViewModel @Inject constructor(
}
}
+ fun updateChattingText(text: String) {
+ _chattingText.value = text
+ }
+
+ fun updateIsChatting(boolean: Boolean) {
+ _isChatting.value = boolean
+ _chattingText.value = ""
+ }
+
}
\ No newline at end of file
From ee666beb5bbe9efb7e07415aec7a73d8db2ae12f Mon Sep 17 00:00:00 2001
From: leeseokchan00 <112953135+leeseokchan00@users.noreply.github.com>
Date: Sun, 17 Nov 2024 06:02:56 +0900
Subject: [PATCH 15/30] feature: Add character chat broadcast receiver
---
app/src/main/AndroidManifest.xml | 5 ++
.../offroad.app/OffRoadMessagingService.kt | 88 +++++++++----------
.../common/domain/model/NotificationEvent.kt | 7 ++
feature/home/build.gradle.kts | 1 +
.../home/presentation/HomeViewModel.kt | 26 ++++++
feature/main/build.gradle.kts | 2 +-
.../main/CharacterChatBroadcastReceiver.kt | 26 ++++++
.../teamoffroad/feature/main/MainActivity.kt | 46 +++++++++-
.../teamoffroad/feature/main/MainScreen.kt | 2 +-
.../mypage/presentation/AnnouncementScreen.kt | 24 +++++
gradle/libs.versions.toml | 7 ++
11 files changed, 182 insertions(+), 52 deletions(-)
create mode 100644 core/common/src/main/java/com/teamoffroad/core/common/domain/model/NotificationEvent.kt
create mode 100644 feature/main/src/main/java/com/teamoffroad/feature/main/CharacterChatBroadcastReceiver.kt
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 06b5af1e..b3029ac6 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -50,5 +50,10 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/com/teamoffroad/offroad.app/OffRoadMessagingService.kt b/app/src/main/java/com/teamoffroad/offroad.app/OffRoadMessagingService.kt
index 7fd03c61..2b52b8a6 100644
--- a/app/src/main/java/com/teamoffroad/offroad.app/OffRoadMessagingService.kt
+++ b/app/src/main/java/com/teamoffroad/offroad.app/OffRoadMessagingService.kt
@@ -41,14 +41,20 @@ class OffRoadMessagingService : FirebaseMessagingService() {
CoroutineScope(Dispatchers.IO).launch { dataStore.updateDeviceTokenEnabled(token) }
}
+ //FCM 메세지를 받을 때 호출됨
override fun onMessageReceived(remoteMessage: RemoteMessage) {
super.onMessageReceived(remoteMessage)
if (remoteMessage.data.isNotEmpty()) {
if (ActivityLifecycleHandler.isAppInForeground) {
if (remoteMessage.data[KEY_TYPE] != TYPE_CHARACTER_CHAT)
- sendForeGroundNotification(remoteMessage)
+ sendNotification(remoteMessage, true)
+ else {
+ // 앱이 포그라운드에 있고, 알림타임이 캐릭터채팅인 경우
+ // 정현이 봐야할곳은 여기!! 요쪽 따라가십쇼
+ sendCharacterNotificationInForeground(remoteMessage, true)
+ }
} else {
- sendBackGroundNotification(remoteMessage)
+ sendNotification(remoteMessage, false)
}
}
}
@@ -88,47 +94,30 @@ class OffRoadMessagingService : FirebaseMessagingService() {
)
}
- private fun createNotificationIntent(remoteMessage: RemoteMessage): Intent {
- return Intent(this, MainActivity::class.java).apply {
- flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK
- putExtra(KEY_TYPE, remoteMessage.data[KEY_TYPE])
- if (remoteMessage.data[KEY_TYPE] != TYPE_CHARACTER_CHAT) {
- putExtra(KEY_ID, remoteMessage.data[KEY_ID])
- }
- }
- }
-
- private fun createForeGroundNotificationBuilder(
+ private fun createNotificationIntent(
remoteMessage: RemoteMessage,
- onLargeIconReady: (NotificationCompat.Builder) -> Unit
- ): NotificationCompat.Builder {
-
- val notificationBuilder = NotificationCompat.Builder(this, CHANNEL_ID)
- .setSmallIcon(R.mipmap.ic_launcher)
- .setContentTitle(remoteMessage.data[KEY_TITLE])
- .setContentText(remoteMessage.data[KEY_BODY])
- .setAutoCancel(true)
- .setPriority(NotificationCompat.PRIORITY_MAX)
-
- val imageUrl = remoteMessage.data[KEY_IMAGE]
- imageUrl?.let {
- val request = ImageRequest.Builder(this)
- .data(it)
- .target { drawable ->
- val bitmap = (drawable as BitmapDrawable).bitmap
- notificationBuilder.setLargeIcon(bitmap)
- onLargeIconReady(notificationBuilder)
+ isForeGround: Boolean
+ ): Intent {
+ if (isForeGround) {
+ return Intent(this, MainActivity::class.java).apply {
+ flags = Intent.FLAG_ACTIVITY_SINGLE_TOP
+ putExtra(KEY_TYPE, remoteMessage.data[KEY_TYPE])
+ if (remoteMessage.data[KEY_TYPE] != TYPE_CHARACTER_CHAT) {
+ putExtra(KEY_ID, remoteMessage.data[KEY_ID])
}
- .build()
-
- Coil.imageLoader(this).enqueue(request)
- } ?: run {
- onLargeIconReady(notificationBuilder)
+ }
+ } else {
+ return Intent(this, MainActivity::class.java).apply {
+ flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK
+ putExtra(KEY_TYPE, remoteMessage.data[KEY_TYPE])
+ if (remoteMessage.data[KEY_TYPE] != TYPE_CHARACTER_CHAT) {
+ putExtra(KEY_ID, remoteMessage.data[KEY_ID])
+ }
+ }
}
- return notificationBuilder
}
- private fun createBackGroundNotificationBuilder(
+ private fun createNotificationBuilder(
remoteMessage: RemoteMessage,
pendingIntent: PendingIntent,
onLargeIconReady: (NotificationCompat.Builder) -> Unit
@@ -160,20 +149,27 @@ class OffRoadMessagingService : FirebaseMessagingService() {
return notificationBuilder
}
- private fun sendForeGroundNotification(remoteMessage: RemoteMessage) {
+ private fun sendNotification(remoteMessage: RemoteMessage, isForeGround: Boolean) {
val uniqueIdentifier = generateUniqueIdentifier()
- createForeGroundNotificationBuilder(remoteMessage) { notificationBuilder ->
+ val intent = createNotificationIntent(remoteMessage, isForeGround)
+ val pendingIntent = createPendingIntent(intent, uniqueIdentifier)
+ createNotificationBuilder(remoteMessage, pendingIntent) { notificationBuilder ->
showNotification(notificationBuilder, uniqueIdentifier)
}
}
- private fun sendBackGroundNotification(remoteMessage: RemoteMessage) {
- val uniqueIdentifier = generateUniqueIdentifier()
- val intent = createNotificationIntent(remoteMessage)
- val pendingIntent = createPendingIntent(intent, uniqueIdentifier)
- createBackGroundNotificationBuilder(remoteMessage, pendingIntent) { notificationBuilder ->
- showNotification(notificationBuilder, uniqueIdentifier)
+ //브로드캐스트리시버에 필요한 데이터(캐릭터이름, 대화내용, 알림타입) 저장하고 브로드캐스트 발신
+ //feature main의 CharacterChatBroadcastReceiver로 가면 됩니다.
+ private fun sendCharacterNotificationInForeground(
+ remoteMessage: RemoteMessage,
+ isForeGround: Boolean
+ ) {
+ val intent = Intent("com.teamoffroad.offroad.app.ACTION_RECEIVE_NOTIFICATION").apply {
+ putExtra(KEY_TITLE, remoteMessage.data[KEY_TITLE])
+ putExtra(KEY_BODY, remoteMessage.data[KEY_BODY])
+ putExtra(KEY_TYPE, remoteMessage.data[KEY_TYPE])
}
+ sendBroadcast(intent)
}
private fun showNotification(
diff --git a/core/common/src/main/java/com/teamoffroad/core/common/domain/model/NotificationEvent.kt b/core/common/src/main/java/com/teamoffroad/core/common/domain/model/NotificationEvent.kt
new file mode 100644
index 00000000..69b592a0
--- /dev/null
+++ b/core/common/src/main/java/com/teamoffroad/core/common/domain/model/NotificationEvent.kt
@@ -0,0 +1,7 @@
+package com.teamoffroad.core.common.domain.model
+
+data class NotificationEvent(
+ val characterName: String?,
+ val characterContent: String?,
+ val type: String?,
+)
diff --git a/feature/home/build.gradle.kts b/feature/home/build.gradle.kts
index 864f7460..d10f39b4 100644
--- a/feature/home/build.gradle.kts
+++ b/feature/home/build.gradle.kts
@@ -17,4 +17,5 @@ dependencies {
implementation(libs.gson)
implementation(libs.lottie.compose)
implementation(libs.coil.svg)
+ implementation(libs.eventbus)
}
diff --git a/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeViewModel.kt b/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeViewModel.kt
index 38fd6f99..c5693758 100644
--- a/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeViewModel.kt
+++ b/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeViewModel.kt
@@ -1,7 +1,9 @@
package com.teamoffroad.feature.home.presentation
+import android.util.Log
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
+import com.teamoffroad.core.common.domain.model.NotificationEvent
import com.teamoffroad.core.common.domain.repository.DeviceTokenRepository
import com.teamoffroad.core.common.domain.usecase.SetAutoSignInUseCase
import com.teamoffroad.feature.home.domain.model.Emblem
@@ -16,6 +18,9 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
+import org.greenrobot.eventbus.EventBus
+import org.greenrobot.eventbus.Subscribe
+import org.greenrobot.eventbus.ThreadMode
import javax.inject.Inject
@HiltViewModel
@@ -59,6 +64,27 @@ class HomeViewModel @Inject constructor(
private val _linearProgressBar = MutableStateFlow(0f)
val linearProgressBar = _linearProgressBar.asStateFlow()
+ init {
+ //아까 CharacterChatBroadcastReceiver에서 게시한 브로드캐스트리시버를 여기서 받습니다.
+ EventBus.getDefault().register(this)
+ }
+
+ //뷰모델이 삭제될때 이벤트버스도 해제시켜줍니다.
+ override fun onCleared() {
+ super.onCleared()
+ EventBus.getDefault().unregister(this)
+ }
+
+ //브로드캐스트리시버가 작동할때마다 동작하는 함수(fcm발송 > 앱이 포그라운드에 있고, 타입이 캐릭터채팅이라면 작동)
+ //그런데 홈화면이 아니고 다른화면에서 이 함수가 호출되면 ui가 활성되있지 않기 때문에 ui작업을 할 수 없습니다.(함수 실행될때 로그는 찍힘)
+ //그래서 데이터스토어 같은 로컬저장소에 데이터와 캐릭터 채팅확인 여부를 저장해두었다가
+ //홈화면에 들어와서 채팅확인 여부가 x라면 알림을 보여주고, 알림을 봤다면 다시 채팅확인 여부가 o로 만드는식으로 하면 될거같습니다.
+ //그래서 포스트맨으로 fcm쏴보면서 요함수에서 하면 될 것 같습니다.
+ @Subscribe(threadMode = ThreadMode.MAIN)
+ fun onNotificationEvent(event: NotificationEvent) {
+ Log.d("characterChat data", event.toString())
+ }
+
fun getUsersAdventuresInformation(category: String) {
viewModelScope.launch {
runCatching {
diff --git a/feature/main/build.gradle.kts b/feature/main/build.gradle.kts
index a25fadcc..a70c2e55 100644
--- a/feature/main/build.gradle.kts
+++ b/feature/main/build.gradle.kts
@@ -26,7 +26,7 @@ dependencies {
implementation(libs.androidx.lifecycle.runtime.compose)
implementation(libs.androidx.lifecycle.viewmodel.compose)
implementation(libs.kotlinx.immutable)
-
+ implementation(libs.eventbus)
implementation(libs.androidx.constraintlayout.compose)
implementation(libs.accompanist.insets)
}
diff --git a/feature/main/src/main/java/com/teamoffroad/feature/main/CharacterChatBroadcastReceiver.kt b/feature/main/src/main/java/com/teamoffroad/feature/main/CharacterChatBroadcastReceiver.kt
new file mode 100644
index 00000000..45811044
--- /dev/null
+++ b/feature/main/src/main/java/com/teamoffroad/feature/main/CharacterChatBroadcastReceiver.kt
@@ -0,0 +1,26 @@
+package com.teamoffroad.feature.main
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.os.Build
+import androidx.annotation.RequiresApi
+import com.teamoffroad.core.common.domain.model.FcmNotificationKey.KEY_BODY
+import com.teamoffroad.core.common.domain.model.FcmNotificationKey.KEY_TITLE
+import com.teamoffroad.core.common.domain.model.FcmNotificationKey.KEY_TYPE
+import com.teamoffroad.core.common.domain.model.NotificationEvent
+import org.greenrobot.eventbus.EventBus
+
+class CharacterChatBroadcastReceiver : BroadcastReceiver() {
+ @RequiresApi(Build.VERSION_CODES.TIRAMISU)
+ override fun onReceive(context: Context, intent: Intent) {
+ //아까 브로드캐스트한 값들을 이벤트버스에 담아서 게시합니다. 홈화면에서 실시간으로 받을 수 있게하기 위해서!
+ //다음 메인액티비티로 가면 됩니다.
+ val notificationTitle = intent.getStringExtra(KEY_TITLE)
+ val notificationBody = intent.getStringExtra(KEY_BODY)
+ val notificationType = intent.getStringExtra(KEY_TYPE)
+
+ EventBus.getDefault()
+ .post(NotificationEvent(notificationTitle, notificationBody, notificationType))
+ }
+}
\ No newline at end of file
diff --git a/feature/main/src/main/java/com/teamoffroad/feature/main/MainActivity.kt b/feature/main/src/main/java/com/teamoffroad/feature/main/MainActivity.kt
index 0bc6f00f..4f658f93 100644
--- a/feature/main/src/main/java/com/teamoffroad/feature/main/MainActivity.kt
+++ b/feature/main/src/main/java/com/teamoffroad/feature/main/MainActivity.kt
@@ -2,8 +2,10 @@ package com.teamoffroad.feature.main
import android.content.Context
import android.content.Intent
+import android.content.IntentFilter
import android.os.Build
import android.os.Bundle
+import android.util.Log
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.annotation.RequiresApi
@@ -13,20 +15,33 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
+import androidx.lifecycle.lifecycleScope
import com.teamoffroad.core.common.domain.model.FcmNotificationKey.KEY_ID
import com.teamoffroad.core.common.domain.model.FcmNotificationKey.KEY_TYPE
import com.teamoffroad.core.designsystem.theme.OffroadTheme
import com.teamoffroad.feature.main.component.MainTransparentActionBar
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
@AndroidEntryPoint
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
class MainActivity : ComponentActivity() {
+ private val notificationTypeState = mutableStateOf(null)
+ private val notificationIdState = mutableStateOf(null)
+ private val myBroadcastReceiver = CharacterChatBroadcastReceiver()
+
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- val notificationType = intent.getStringExtra(KEY_TYPE)
- val notificationId = intent.getStringExtra(KEY_ID)
+ notificationTypeState.value = intent.getStringExtra(KEY_TYPE)
+ notificationIdState.value = intent.getStringExtra(KEY_ID)
+
+ //액티비티 생명주기에 따라 브로드캐스터리시버 만들어주기
+ //밑에 onDestroy에서 브로드캐스트리시버 해제도 해줍니다.
+ //이제 홈 뷰모델로 가면됩니다.
+ val filter = IntentFilter("com.teamoffroad.offroad.app.ACTION_RECEIVE_NOTIFICATION")
+ registerReceiver(myBroadcastReceiver, filter, RECEIVER_NOT_EXPORTED)
+
setContent {
val navigator: MainNavigator = rememberMainNavigator()
val showSplash = remember { mutableStateOf(true) }
@@ -42,14 +57,37 @@ class MainActivity : ComponentActivity() {
false -> MainScreen(
navigator = navigator,
modifier = Modifier,
- notificationType = if (!notificationType.isNullOrBlank()) notificationType else null,
- notificationId = if (!notificationId.isNullOrBlank()) notificationId else null,
+ notificationType = notificationTypeState.value,
+ notificationId = notificationIdState.value,
)
}
}
}
}
+ override fun onDestroy() {
+ super.onDestroy()
+ unregisterReceiver(myBroadcastReceiver)
+ }
+
+ override fun onNewIntent(intent: Intent) {
+ super.onNewIntent(intent)
+ updateNotificationData(intent)
+
+ Log.d("asdasd", "forground $notificationTypeState")
+ Log.d("asdasd", "forground $notificationIdState")
+ }
+
+ private fun updateNotificationData(intent: Intent) {
+ notificationTypeState.value = intent.getStringExtra(KEY_TYPE)
+ notificationIdState.value = intent.getStringExtra(KEY_ID)
+ lifecycleScope.launch {
+ delay(1500L)
+ notificationTypeState.value = null
+ notificationIdState.value = null
+ }
+ }
+
companion object {
@JvmStatic
fun newInstance(context: Context) = Intent(context, MainActivity::class.java).apply {
diff --git a/feature/main/src/main/java/com/teamoffroad/feature/main/MainScreen.kt b/feature/main/src/main/java/com/teamoffroad/feature/main/MainScreen.kt
index 4e0c63be..a5662a2e 100644
--- a/feature/main/src/main/java/com/teamoffroad/feature/main/MainScreen.kt
+++ b/feature/main/src/main/java/com/teamoffroad/feature/main/MainScreen.kt
@@ -23,7 +23,7 @@ internal fun MainScreen(
notificationType: String?,
notificationId: String?,
) {
- LaunchedEffect(Unit) {
+ LaunchedEffect(notificationType, notificationId) {
if (!notificationType.isNullOrBlank()) {
if (notificationType == TYPE_ANNOUNCEMENT)
notificationId?.let {
diff --git a/feature/mypage/src/main/java/com/teamoffroad/feature/mypage/presentation/AnnouncementScreen.kt b/feature/mypage/src/main/java/com/teamoffroad/feature/mypage/presentation/AnnouncementScreen.kt
index 5c2d8bb7..4e28f9c4 100644
--- a/feature/mypage/src/main/java/com/teamoffroad/feature/mypage/presentation/AnnouncementScreen.kt
+++ b/feature/mypage/src/main/java/com/teamoffroad/feature/mypage/presentation/AnnouncementScreen.kt
@@ -1,5 +1,6 @@
package com.teamoffroad.feature.mypage.presentation
+import android.util.Log
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
@@ -40,9 +41,32 @@ internal fun AnnouncementScreen(
viewModel: AnnouncementViewModel = hiltViewModel()
) {
val isAnnouncementState by viewModel.announcementUiState.collectAsState()
+
+ //TODO. id 어떻게 초기화?
+ Log.d("asdsad", announcementId.toString())
+
LaunchedEffect(Unit) {
viewModel.updateAnnouncement()
}
+// LaunchedEffect(isAnnouncementState) {
+// if (announcementId != null) {
+// isAnnouncementState.announcementList.forEach {
+// if (it.title.trim().equals(announcementId.trim(), ignoreCase = true)) {
+// Log.d("asdsad", "Navigating to detail for $announcementId")
+// navigateToAnnouncementDetail(
+// it.title,
+// it.content,
+// it.isImportant,
+// it.updateAt,
+// it.hasExternalLinks,
+// it.externalLinks,
+// it.externalLinksTitles,
+// )
+// }
+// }
+// }
+// }
+
Column(
modifier = Modifier
.navigationPadding()
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 97c81079..ef65b808 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -90,6 +90,10 @@ barcodeScanning = "17.1.0"
# Process phoenix
processPhoenix = "3.0.0"
+# Event Bus
+eventbus = "3.2.0"
+
+
[libraries]
# Core Libraries
androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" }
@@ -201,6 +205,9 @@ barcode-scanning = { group = "com.google.mlkit", name = "barcode-scanning", vers
# Process Phoenix
process-phoenix = { group = "com.jakewharton", name = "process-phoenix", version.ref = "processPhoenix" }
+# Event Bus
+eventbus = { group = "org.greenrobot", name = "eventbus", version.ref = "eventbus" }
+
[plugins]
# Android Application Plugin
android-application = { id = "com.android.application", version.ref = "androidGradlePlugin" }
From 669dba792e03c4ba9ae8653c066bb11d589ad06b Mon Sep 17 00:00:00 2001
From: leeseokchan00 <112953135+leeseokchan00@users.noreply.github.com>
Date: Sun, 17 Nov 2024 12:21:27 +0900
Subject: [PATCH 16/30] refactor: Edit broadcast receiver
---
.../offroad.app/OffRoadMessagingService.kt | 59 +++++++++++--------
.../feature/home/presentation/HomeScreen.kt | 1 -
.../home/presentation/HomeViewModel.kt | 1 +
.../main/CharacterChatBroadcastReceiver.kt | 54 ++++++++++++++++-
.../teamoffroad/feature/main/MainActivity.kt | 50 ++++------------
.../teamoffroad/feature/main/MainScreen.kt | 12 ++++
.../teamoffroad/feature/main/MainUiState.kt | 7 +++
.../teamoffroad/feature/main/MainViewModel.kt | 34 +++++++++++
.../mypage/presentation/AnnouncementScreen.kt | 36 +++++------
9 files changed, 168 insertions(+), 86 deletions(-)
create mode 100644 feature/main/src/main/java/com/teamoffroad/feature/main/MainUiState.kt
create mode 100644 feature/main/src/main/java/com/teamoffroad/feature/main/MainViewModel.kt
diff --git a/app/src/main/java/com/teamoffroad/offroad.app/OffRoadMessagingService.kt b/app/src/main/java/com/teamoffroad/offroad.app/OffRoadMessagingService.kt
index 2b52b8a6..b509e89c 100644
--- a/app/src/main/java/com/teamoffroad/offroad.app/OffRoadMessagingService.kt
+++ b/app/src/main/java/com/teamoffroad/offroad.app/OffRoadMessagingService.kt
@@ -51,7 +51,7 @@ class OffRoadMessagingService : FirebaseMessagingService() {
else {
// 앱이 포그라운드에 있고, 알림타임이 캐릭터채팅인 경우
// 정현이 봐야할곳은 여기!! 요쪽 따라가십쇼
- sendCharacterNotificationInForeground(remoteMessage, true)
+ sendCharacterChatNotificationInForeground(remoteMessage)
}
} else {
sendNotification(remoteMessage, false)
@@ -98,21 +98,12 @@ class OffRoadMessagingService : FirebaseMessagingService() {
remoteMessage: RemoteMessage,
isForeGround: Boolean
): Intent {
- if (isForeGround) {
- return Intent(this, MainActivity::class.java).apply {
- flags = Intent.FLAG_ACTIVITY_SINGLE_TOP
- putExtra(KEY_TYPE, remoteMessage.data[KEY_TYPE])
- if (remoteMessage.data[KEY_TYPE] != TYPE_CHARACTER_CHAT) {
- putExtra(KEY_ID, remoteMessage.data[KEY_ID])
- }
- }
- } else {
- return Intent(this, MainActivity::class.java).apply {
+ return Intent(this, MainActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK
putExtra(KEY_TYPE, remoteMessage.data[KEY_TYPE])
if (remoteMessage.data[KEY_TYPE] != TYPE_CHARACTER_CHAT) {
putExtra(KEY_ID, remoteMessage.data[KEY_ID])
- }
+
}
}
}
@@ -150,26 +141,44 @@ class OffRoadMessagingService : FirebaseMessagingService() {
}
private fun sendNotification(remoteMessage: RemoteMessage, isForeGround: Boolean) {
- val uniqueIdentifier = generateUniqueIdentifier()
- val intent = createNotificationIntent(remoteMessage, isForeGround)
- val pendingIntent = createPendingIntent(intent, uniqueIdentifier)
- createNotificationBuilder(remoteMessage, pendingIntent) { notificationBuilder ->
- showNotification(notificationBuilder, uniqueIdentifier)
+ if (!isForeGround) {
+ val uniqueIdentifier = generateUniqueIdentifier()
+ val intent = createNotificationIntent(remoteMessage, isForeGround)
+ val pendingIntent = createPendingIntent(intent, uniqueIdentifier)
+ createNotificationBuilder(remoteMessage, pendingIntent) { notificationBuilder ->
+ showNotification(notificationBuilder, uniqueIdentifier)
+ }
+ } else {
+ val uniqueIdentifier = generateUniqueIdentifier()
+ val broadCastIntent =
+ Intent("com.teamoffroad.offroad.app.ANNOUNCEMENT_FOREGROUND").apply {
+ putExtra(KEY_TITLE, remoteMessage.data[KEY_TITLE])
+ putExtra(KEY_ID, remoteMessage.data[KEY_ID])
+ }
+ val pendingIntent = PendingIntent.getBroadcast(
+ this,
+ 0,
+ broadCastIntent,
+ PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE // FLAG_IMMUTABLE 추가
+ )
+ createNotificationBuilder(remoteMessage, pendingIntent) { notificationBuilder ->
+ showNotification(notificationBuilder, uniqueIdentifier)
+ }
}
}
//브로드캐스트리시버에 필요한 데이터(캐릭터이름, 대화내용, 알림타입) 저장하고 브로드캐스트 발신
//feature main의 CharacterChatBroadcastReceiver로 가면 됩니다.
- private fun sendCharacterNotificationInForeground(
+ private fun sendCharacterChatNotificationInForeground(
remoteMessage: RemoteMessage,
- isForeGround: Boolean
) {
- val intent = Intent("com.teamoffroad.offroad.app.ACTION_RECEIVE_NOTIFICATION").apply {
- putExtra(KEY_TITLE, remoteMessage.data[KEY_TITLE])
- putExtra(KEY_BODY, remoteMessage.data[KEY_BODY])
- putExtra(KEY_TYPE, remoteMessage.data[KEY_TYPE])
- }
- sendBroadcast(intent)
+ val broadCastIntent =
+ Intent("com.teamoffroad.offroad.app.CHARACTER_CHAT_FOREGROUND").apply {
+ putExtra(KEY_TITLE, remoteMessage.data[KEY_TITLE])
+ putExtra(KEY_BODY, remoteMessage.data[KEY_BODY])
+ putExtra(KEY_TYPE, remoteMessage.data[KEY_TYPE])
+ }
+ sendBroadcast(broadCastIntent)
}
private fun showNotification(
diff --git a/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeScreen.kt b/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeScreen.kt
index 41357691..0ac1a4ae 100644
--- a/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeScreen.kt
+++ b/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeScreen.kt
@@ -64,7 +64,6 @@ fun HomeScreen(
val viewModel: HomeViewModel = hiltViewModel()
val isCompleteQuestDialogShown = remember { mutableStateOf(false) }
-
LaunchedEffect(Unit) {
viewModel.updateAutoSignIn()
viewModel.updateFcmToken()
diff --git a/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeViewModel.kt b/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeViewModel.kt
index c5693758..b12129b7 100644
--- a/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeViewModel.kt
+++ b/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeViewModel.kt
@@ -64,6 +64,7 @@ class HomeViewModel @Inject constructor(
private val _linearProgressBar = MutableStateFlow(0f)
val linearProgressBar = _linearProgressBar.asStateFlow()
+ var asd = MutableStateFlow("")
init {
//아까 CharacterChatBroadcastReceiver에서 게시한 브로드캐스트리시버를 여기서 받습니다.
EventBus.getDefault().register(this)
diff --git a/feature/main/src/main/java/com/teamoffroad/feature/main/CharacterChatBroadcastReceiver.kt b/feature/main/src/main/java/com/teamoffroad/feature/main/CharacterChatBroadcastReceiver.kt
index 45811044..c8eac734 100644
--- a/feature/main/src/main/java/com/teamoffroad/feature/main/CharacterChatBroadcastReceiver.kt
+++ b/feature/main/src/main/java/com/teamoffroad/feature/main/CharacterChatBroadcastReceiver.kt
@@ -2,18 +2,43 @@ package com.teamoffroad.feature.main
import android.content.BroadcastReceiver
import android.content.Context
+import android.content.Context.RECEIVER_EXPORTED
import android.content.Intent
+import android.content.IntentFilter
import android.os.Build
-import androidx.annotation.RequiresApi
+import android.util.Log
import com.teamoffroad.core.common.domain.model.FcmNotificationKey.KEY_BODY
+import com.teamoffroad.core.common.domain.model.FcmNotificationKey.KEY_ID
import com.teamoffroad.core.common.domain.model.FcmNotificationKey.KEY_TITLE
import com.teamoffroad.core.common.domain.model.FcmNotificationKey.KEY_TYPE
import com.teamoffroad.core.common.domain.model.NotificationEvent
import org.greenrobot.eventbus.EventBus
-class CharacterChatBroadcastReceiver : BroadcastReceiver() {
- @RequiresApi(Build.VERSION_CODES.TIRAMISU)
+class CharacterChatBroadcastReceiver(
+ private val navigateToAnnouncement: (id: String) -> Unit,
+ private val navigateToHome: (characterName: String, characterChatting: String) -> Unit,
+) : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
+
+ when (intent.action) {
+ ACTION_ANNOUNCEMENT_FOREGROUND -> {
+ val announcementID = intent.getStringExtra(KEY_ID)
+ if (announcementID != null) {
+ Log.d("asdasd", announcementID)
+ navigateToAnnouncement(announcementID)
+ }
+ }
+
+ ACTION_CHARACTER_CHAT_FOREGROUND -> {
+ val characterName = intent.getStringExtra(KEY_TITLE)
+ val characterChatting = intent.getStringExtra(KEY_BODY)
+ if (characterChatting != null && characterName != null) {
+ Log.d("asdasd", characterName)
+ navigateToHome(characterName, characterChatting)
+ }
+ }
+ }
+
//아까 브로드캐스트한 값들을 이벤트버스에 담아서 게시합니다. 홈화면에서 실시간으로 받을 수 있게하기 위해서!
//다음 메인액티비티로 가면 됩니다.
val notificationTitle = intent.getStringExtra(KEY_TITLE)
@@ -23,4 +48,27 @@ class CharacterChatBroadcastReceiver : BroadcastReceiver() {
EventBus.getDefault()
.post(NotificationEvent(notificationTitle, notificationBody, notificationType))
}
+
+ companion object {
+ const val ACTION_CHARACTER_CHAT_FOREGROUND =
+ "com.teamoffroad.offroad.app.CHARACTER_CHAT_FOREGROUND"
+ const val ACTION_ANNOUNCEMENT_FOREGROUND =
+ "com.teamoffroad.offroad.app.ANNOUNCEMENT_FOREGROUND"
+
+ fun register(context: Context, receiver: CharacterChatBroadcastReceiver) {
+ val intentFilter = IntentFilter().apply {
+ addAction(ACTION_CHARACTER_CHAT_FOREGROUND)
+ addAction(ACTION_ANNOUNCEMENT_FOREGROUND)
+ }
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ context.registerReceiver(receiver, intentFilter, RECEIVER_EXPORTED)
+ } else {
+ context.registerReceiver(receiver, intentFilter)
+ }
+ }
+
+ fun unregister(context: Context, receiver: CharacterChatBroadcastReceiver) {
+ context.unregisterReceiver(receiver)
+ }
+ }
}
\ No newline at end of file
diff --git a/feature/main/src/main/java/com/teamoffroad/feature/main/MainActivity.kt b/feature/main/src/main/java/com/teamoffroad/feature/main/MainActivity.kt
index 4f658f93..db53a4fa 100644
--- a/feature/main/src/main/java/com/teamoffroad/feature/main/MainActivity.kt
+++ b/feature/main/src/main/java/com/teamoffroad/feature/main/MainActivity.kt
@@ -2,34 +2,30 @@ package com.teamoffroad.feature.main
import android.content.Context
import android.content.Intent
-import android.content.IntentFilter
import android.os.Build
import android.os.Bundle
-import android.util.Log
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
+import androidx.activity.viewModels
import androidx.annotation.RequiresApi
-import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
-import androidx.compose.ui.tooling.preview.Preview
-import androidx.lifecycle.lifecycleScope
import com.teamoffroad.core.common.domain.model.FcmNotificationKey.KEY_ID
import com.teamoffroad.core.common.domain.model.FcmNotificationKey.KEY_TYPE
import com.teamoffroad.core.designsystem.theme.OffroadTheme
import com.teamoffroad.feature.main.component.MainTransparentActionBar
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.delay
-import kotlinx.coroutines.launch
@AndroidEntryPoint
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
class MainActivity : ComponentActivity() {
private val notificationTypeState = mutableStateOf(null)
private val notificationIdState = mutableStateOf(null)
- private val myBroadcastReceiver = CharacterChatBroadcastReceiver()
+ private lateinit var characterBroadcastReceiver: CharacterChatBroadcastReceiver
+ private val viewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -39,8 +35,11 @@ class MainActivity : ComponentActivity() {
//액티비티 생명주기에 따라 브로드캐스터리시버 만들어주기
//밑에 onDestroy에서 브로드캐스트리시버 해제도 해줍니다.
//이제 홈 뷰모델로 가면됩니다.
- val filter = IntentFilter("com.teamoffroad.offroad.app.ACTION_RECEIVE_NOTIFICATION")
- registerReceiver(myBroadcastReceiver, filter, RECEIVER_NOT_EXPORTED)
+ characterBroadcastReceiver = CharacterChatBroadcastReceiver(
+ navigateToAnnouncement = viewModel::navigateToAnnouncement,
+ navigateToHome = viewModel::navigateToHome,
+ )
+ CharacterChatBroadcastReceiver.register(this, characterBroadcastReceiver)
setContent {
val navigator: MainNavigator = rememberMainNavigator()
@@ -59,6 +58,7 @@ class MainActivity : ComponentActivity() {
modifier = Modifier,
notificationType = notificationTypeState.value,
notificationId = notificationIdState.value,
+ viewModel = viewModel
)
}
}
@@ -67,25 +67,7 @@ class MainActivity : ComponentActivity() {
override fun onDestroy() {
super.onDestroy()
- unregisterReceiver(myBroadcastReceiver)
- }
-
- override fun onNewIntent(intent: Intent) {
- super.onNewIntent(intent)
- updateNotificationData(intent)
-
- Log.d("asdasd", "forground $notificationTypeState")
- Log.d("asdasd", "forground $notificationIdState")
- }
-
- private fun updateNotificationData(intent: Intent) {
- notificationTypeState.value = intent.getStringExtra(KEY_TYPE)
- notificationIdState.value = intent.getStringExtra(KEY_ID)
- lifecycleScope.launch {
- delay(1500L)
- notificationTypeState.value = null
- notificationIdState.value = null
- }
+ CharacterChatBroadcastReceiver.unregister(this, characterBroadcastReceiver)
}
companion object {
@@ -94,14 +76,4 @@ class MainActivity : ComponentActivity() {
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
}
}
-}
-
-@Preview(showBackground = true)
-@Composable
-@RequiresApi(Build.VERSION_CODES.TIRAMISU)
-fun GreetingPreview() {
- OffroadTheme {
- MainScreen(notificationType = "", notificationId = "")
- }
-}
-
+}
\ No newline at end of file
diff --git a/feature/main/src/main/java/com/teamoffroad/feature/main/MainScreen.kt b/feature/main/src/main/java/com/teamoffroad/feature/main/MainScreen.kt
index a5662a2e..d3885a62 100644
--- a/feature/main/src/main/java/com/teamoffroad/feature/main/MainScreen.kt
+++ b/feature/main/src/main/java/com/teamoffroad/feature/main/MainScreen.kt
@@ -8,6 +8,8 @@ import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import com.teamoffroad.core.common.domain.model.FcmNotificationKey.TYPE_ANNOUNCEMENT
import com.teamoffroad.core.common.util.OnBackButtonListener
@@ -22,7 +24,17 @@ internal fun MainScreen(
navigator: MainNavigator = rememberMainNavigator(),
notificationType: String?,
notificationId: String?,
+ viewModel: MainViewModel,
) {
+ val isMainUiState by viewModel.mainUiState.collectAsState()
+ LaunchedEffect(isMainUiState) {
+ if (!isMainUiState.characterName.isNullOrBlank() && !isMainUiState.characterChatting.isNullOrBlank()) {
+ //
+ } else if (!isMainUiState.announcementId.isNullOrBlank()) {
+ navigator.navigateToAnnouncement(isMainUiState.announcementId)
+ }
+ viewModel.initState()
+ }
LaunchedEffect(notificationType, notificationId) {
if (!notificationType.isNullOrBlank()) {
if (notificationType == TYPE_ANNOUNCEMENT)
diff --git a/feature/main/src/main/java/com/teamoffroad/feature/main/MainUiState.kt b/feature/main/src/main/java/com/teamoffroad/feature/main/MainUiState.kt
new file mode 100644
index 00000000..921092ab
--- /dev/null
+++ b/feature/main/src/main/java/com/teamoffroad/feature/main/MainUiState.kt
@@ -0,0 +1,7 @@
+package com.teamoffroad.feature.main
+
+data class MainUiState(
+ val announcementId: String? = null,
+ val characterName: String? = null,
+ val characterChatting: String? = null,
+)
diff --git a/feature/main/src/main/java/com/teamoffroad/feature/main/MainViewModel.kt b/feature/main/src/main/java/com/teamoffroad/feature/main/MainViewModel.kt
new file mode 100644
index 00000000..09fb73b0
--- /dev/null
+++ b/feature/main/src/main/java/com/teamoffroad/feature/main/MainViewModel.kt
@@ -0,0 +1,34 @@
+package com.teamoffroad.feature.main
+
+import androidx.lifecycle.ViewModel
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import javax.inject.Inject
+
+@HiltViewModel
+class MainViewModel @Inject constructor() : ViewModel() {
+ private val _mainUiState = MutableStateFlow(MainUiState())
+ val mainUiState = _mainUiState.asStateFlow()
+
+ fun navigateToAnnouncement(announcementId: String) {
+ _mainUiState.value = _mainUiState.value.copy(
+ announcementId = announcementId
+ )
+ }
+
+ fun navigateToHome(characterName: String, characterChatting: String) {
+ _mainUiState.value = _mainUiState.value.copy(
+ characterName = characterName,
+ characterChatting = characterChatting,
+ )
+ }
+
+ fun initState() {
+ _mainUiState.value = _mainUiState.value.copy(
+ announcementId = null,
+ characterName = null,
+ characterChatting = null,
+ )
+ }
+}
\ No newline at end of file
diff --git a/feature/mypage/src/main/java/com/teamoffroad/feature/mypage/presentation/AnnouncementScreen.kt b/feature/mypage/src/main/java/com/teamoffroad/feature/mypage/presentation/AnnouncementScreen.kt
index 4e28f9c4..6c337916 100644
--- a/feature/mypage/src/main/java/com/teamoffroad/feature/mypage/presentation/AnnouncementScreen.kt
+++ b/feature/mypage/src/main/java/com/teamoffroad/feature/mypage/presentation/AnnouncementScreen.kt
@@ -48,24 +48,24 @@ internal fun AnnouncementScreen(
LaunchedEffect(Unit) {
viewModel.updateAnnouncement()
}
-// LaunchedEffect(isAnnouncementState) {
-// if (announcementId != null) {
-// isAnnouncementState.announcementList.forEach {
-// if (it.title.trim().equals(announcementId.trim(), ignoreCase = true)) {
-// Log.d("asdsad", "Navigating to detail for $announcementId")
-// navigateToAnnouncementDetail(
-// it.title,
-// it.content,
-// it.isImportant,
-// it.updateAt,
-// it.hasExternalLinks,
-// it.externalLinks,
-// it.externalLinksTitles,
-// )
-// }
-// }
-// }
-// }
+ LaunchedEffect(isAnnouncementState) {
+ if (announcementId != null) {
+ isAnnouncementState.announcementList.forEach {
+ if (it.title.trim().equals(announcementId.trim(), ignoreCase = true)) {
+ Log.d("asdsad", "Navigating to detail for $announcementId")
+ navigateToAnnouncementDetail(
+ it.title,
+ it.content,
+ it.isImportant,
+ it.updateAt,
+ it.hasExternalLinks,
+ it.externalLinks,
+ it.externalLinksTitles,
+ )
+ }
+ }
+ }
+ }
Column(
modifier = Modifier
From bb5bcf8bc6bb2b0029b2545b53da5efcf57dfafe Mon Sep 17 00:00:00 2001
From: YuJeongHyun <0703olivia@naver.com>
Date: Sun, 17 Nov 2024 16:18:31 +0900
Subject: [PATCH 17/30] feature: Add character chat api
---
feature/home/build.gradle.kts | 1 +
.../home/presentation/HomeChatTextField.kt | 9 ++---
.../feature/home/presentation/HomeScreen.kt | 12 +++++--
.../home/presentation/HomeViewModel.kt | 36 ++++++++++++++++++-
.../home/presentation/component/HomeIcons.kt | 6 +---
5 files changed, 51 insertions(+), 13 deletions(-)
diff --git a/feature/home/build.gradle.kts b/feature/home/build.gradle.kts
index 864f7460..37920d0e 100644
--- a/feature/home/build.gradle.kts
+++ b/feature/home/build.gradle.kts
@@ -11,6 +11,7 @@ android {
dependencies {
implementation(project(":feature:auth"))
+ implementation(project(":feature:characterchat"))
implementation(libs.retrofit.kotlinx.serialization)
implementation(libs.androidx.appcompat)
implementation(libs.google.accompanist.permissions)
diff --git a/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeChatTextField.kt b/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeChatTextField.kt
index 0983db1d..55ce7ee9 100644
--- a/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeChatTextField.kt
+++ b/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeChatTextField.kt
@@ -54,6 +54,7 @@ import com.teamoffroad.offroad.feature.home.R
fun HomeChatTextField(
modifier: Modifier = Modifier,
text: String = "",
+ sentMessage: String,
isChatting: MutableState,
keyboard: Boolean,
onValueChange: (String) -> Unit = {},
@@ -121,9 +122,9 @@ fun HomeChatTextField(
style = OffroadTheme.typography.textBold
)
Text(
- text = "text",
+ text = sentMessage,
modifier = Modifier.weight(1f),
- style = OffroadTheme.typography.textRegular
+ style = OffroadTheme.typography.textRegular,
)
}
@@ -167,12 +168,12 @@ fun HomeChatTextField(
)
Image(
painter = painterResource(id = R.drawable.ic_character_chat_send),
- contentDescription = null,
+ contentDescription = "send",
modifier = Modifier
.padding(end = 2.dp)
.size(36.dp)
.align(Alignment.CenterEnd)
- .clickableWithoutRipple { onSendClick() },
+ .clickableWithoutRipple { if (text.isNotBlank()) onSendClick() },
)
}
}
diff --git a/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeScreen.kt b/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeScreen.kt
index 83293678..648a8b26 100644
--- a/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeScreen.kt
+++ b/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeScreen.kt
@@ -76,9 +76,9 @@ fun HomeScreen(
val context = LocalContext.current
val viewModel: HomeViewModel = hiltViewModel()
val isCompleteQuestDialogShown = remember { mutableStateOf(false) }
- val imeHeight = WindowInsets.ime.getBottom(LocalDensity.current)
val isChatting = remember { mutableStateOf(false) }
val chattingText = viewModel.chattingText.collectAsStateWithLifecycle()
+ val sentMessage = remember { mutableStateOf("") }
LaunchedEffect(Unit) {
viewModel.updateAutoSignIn()
@@ -114,8 +114,6 @@ fun HomeScreen(
modifier = Modifier
.fillMaxWidth()
.align(Alignment.BottomCenter)
- .imePadding()
- //.padding(bottom = with(LocalDensity.current) { imeHeight.toDp() })
.padding(bottom = 196.dp)
) {
Column {
@@ -134,6 +132,7 @@ fun HomeScreen(
HomeChatTextField(
text = chattingText.value,
+ sentMessage = sentMessage.value,
isChatting = isChatting,
keyboard = true,
onValueChange = { text ->
@@ -142,6 +141,13 @@ fun HomeScreen(
// onFocusChange = { isFocused ->
// viewModel.updateIsChatting(isFocused)
// }
+ onSendClick = {
+ // 내가 보낸 메시지 표시
+ sentMessage.value = chattingText.value
+ viewModel.updateChattingText("")
+ // 서버에 보내기
+ viewModel.sendChat()
+ }
)
}
}
diff --git a/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeViewModel.kt b/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeViewModel.kt
index 85142822..c101d113 100644
--- a/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeViewModel.kt
+++ b/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeViewModel.kt
@@ -2,6 +2,11 @@ package com.teamoffroad.feature.home.presentation
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
+import com.teamoffroad.characterchat.domain.model.Chat
+import com.teamoffroad.characterchat.domain.repository.CharacterChatRepository
+import com.teamoffroad.characterchat.presentation.model.ChatModel
+import com.teamoffroad.characterchat.presentation.model.ChatType
+import com.teamoffroad.characterchat.presentation.model.TimeType
import com.teamoffroad.core.common.domain.usecase.SetAutoSignInUseCase
import com.teamoffroad.feature.home.domain.model.Emblem
import com.teamoffroad.feature.home.domain.model.UserQuests
@@ -14,11 +19,13 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
+import java.time.LocalDateTime
import javax.inject.Inject
@HiltViewModel
class HomeViewModel @Inject constructor(
private val userRepository: UserRepository,
+ private val characterChatRepository: CharacterChatRepository,
private val setAutoSignInUseCase: SetAutoSignInUseCase,
) : ViewModel() {
@@ -49,6 +56,9 @@ class HomeViewModel @Inject constructor(
private val _getUserQuestsState = MutableStateFlow>(UiState.Loading)
val getUserQuestsState = _getUserQuestsState.asStateFlow()
+ private val _sendChatState = MutableStateFlow>(UiState.Loading)
+ val sendChatState = _sendChatState.asStateFlow()
+
private val _circleProgressBar = MutableStateFlow(0f)
val circleProgressBar = _circleProgressBar.asStateFlow()
@@ -141,7 +151,7 @@ class HomeViewModel @Inject constructor(
}
}
- fun updateAutoSignIn(){
+ fun updateAutoSignIn() {
viewModelScope.launch {
setAutoSignInUseCase.invoke(true)
}
@@ -156,4 +166,28 @@ class HomeViewModel @Inject constructor(
_chattingText.value = ""
}
+ fun sendChat() {
+ val chattingText = chattingText.value
+
+ viewModelScope.launch {
+ runCatching {
+ val now = LocalDateTime.now()
+ val userChat = ChatModel(
+ chatType = ChatType.USER,
+ text = chattingText,
+ date = now.toLocalDate(),
+ time = Triple(TimeType.toTimeType(now.hour), now.hour, now.minute),
+ )
+ characterChatRepository.saveChat(1, chattingText)
+ }.onSuccess { chat ->
+ // _patchEmblemState.emit(UiState.Success(state))
+ // 보낸 채팅 내용 홈에 보여주어야 함
+ _sendChatState.emit(UiState.Success(chat))
+ }.onFailure { t ->
+ val errorMessage = getErrorMessage(t)
+ _sendChatState.emit(UiState.Failure(errorMessage))
+ }
+ }
+ }
+
}
\ No newline at end of file
diff --git a/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/component/HomeIcons.kt b/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/component/HomeIcons.kt
index aaa9067d..81e69a1d 100644
--- a/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/component/HomeIcons.kt
+++ b/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/component/HomeIcons.kt
@@ -32,11 +32,7 @@ fun HomeIcons(
navigateToGainedCharacter: () -> Unit,
) {
Box(
- modifier = Modifier
- .fillMaxSize()
-// .clickableWithoutRipple {
-// isChatting.value = false
-// }
+ modifier = Modifier.fillMaxSize()
) {
Column(
modifier = Modifier
From c5404a2bdb1aa187932dea9f47cad4555ab74211 Mon Sep 17 00:00:00 2001
From: YuJeongHyun <0703olivia@naver.com>
Date: Sun, 17 Nov 2024 16:47:14 +0900
Subject: [PATCH 18/30] refactor: Edit my chatting text
---
.../feature/home/presentation/HomeChatTextField.kt | 7 ++++++-
.../teamoffroad/feature/home/presentation/HomeScreen.kt | 2 --
2 files changed, 6 insertions(+), 3 deletions(-)
diff --git a/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeChatTextField.kt b/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeChatTextField.kt
index 55ce7ee9..f4fd9a87 100644
--- a/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeChatTextField.kt
+++ b/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeChatTextField.kt
@@ -123,8 +123,13 @@ fun HomeChatTextField(
)
Text(
text = sentMessage,
- modifier = Modifier.weight(1f),
+ modifier = Modifier
+ .fillMaxWidth()
+ .onGloballyPositioned { layoutCoordinates ->
+ textFieldHeight.intValue = layoutCoordinates.size.height
+ },
style = OffroadTheme.typography.textRegular,
+ maxLines = 2,
)
}
diff --git a/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeScreen.kt b/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeScreen.kt
index 648a8b26..5e28a1b4 100644
--- a/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeScreen.kt
+++ b/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeScreen.kt
@@ -142,10 +142,8 @@ fun HomeScreen(
// viewModel.updateIsChatting(isFocused)
// }
onSendClick = {
- // 내가 보낸 메시지 표시
sentMessage.value = chattingText.value
viewModel.updateChattingText("")
- // 서버에 보내기
viewModel.sendChat()
}
)
From bb206bec6117a1cd123815537a4dfe74f9c59d53 Mon Sep 17 00:00:00 2001
From: YuJeongHyun <0703olivia@naver.com>
Date: Sun, 17 Nov 2024 16:56:40 +0900
Subject: [PATCH 19/30] refactor: Delete imports
---
.../teamoffroad/feature/home/presentation/HomeScreen.kt | 8 --------
1 file changed, 8 deletions(-)
diff --git a/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeScreen.kt b/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeScreen.kt
index 5e28a1b4..c9d5b279 100644
--- a/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeScreen.kt
+++ b/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeScreen.kt
@@ -4,19 +4,14 @@ import android.content.Context
import android.os.Build
import android.widget.Toast
import androidx.annotation.RequiresApi
-import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.ime
-import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Text
@@ -31,8 +26,6 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.platform.LocalDensity
-import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
@@ -40,7 +33,6 @@ import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.teamoffroad.core.designsystem.component.actionBarPadding
import com.teamoffroad.core.designsystem.component.clickableWithoutRipple
-import com.teamoffroad.core.designsystem.theme.ErrorNew
import com.teamoffroad.core.designsystem.theme.HomeGradi1
import com.teamoffroad.core.designsystem.theme.HomeGradi2
import com.teamoffroad.core.designsystem.theme.HomeGradi3
From e6b9c9325287ba5e187960ba61e7abd2c90eb2bc Mon Sep 17 00:00:00 2001
From: YuJeongHyun <0703olivia@naver.com>
Date: Sun, 17 Nov 2024 18:14:16 +0900
Subject: [PATCH 20/30] feature: Add home character chat
---
.../feature/home/presentation/HomeScreen.kt | 82 +++++++++++++++++++
1 file changed, 82 insertions(+)
diff --git a/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeScreen.kt b/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeScreen.kt
index c67f6e16..42a0d4aa 100644
--- a/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeScreen.kt
+++ b/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeScreen.kt
@@ -27,20 +27,25 @@ import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.teamoffroad.core.designsystem.component.actionBarPadding
import com.teamoffroad.core.designsystem.component.clickableWithoutRipple
+import com.teamoffroad.core.designsystem.theme.BtnInactive
import com.teamoffroad.core.designsystem.theme.HomeGradi1
import com.teamoffroad.core.designsystem.theme.HomeGradi2
import com.teamoffroad.core.designsystem.theme.HomeGradi3
import com.teamoffroad.core.designsystem.theme.HomeGradi4
import com.teamoffroad.core.designsystem.theme.HomeGradi5
import com.teamoffroad.core.designsystem.theme.HomeGradi6
+import com.teamoffroad.core.designsystem.theme.Main2
+import com.teamoffroad.core.designsystem.theme.Main3
import com.teamoffroad.core.designsystem.theme.OffroadTheme
import com.teamoffroad.core.designsystem.theme.Sub
+import com.teamoffroad.core.designsystem.theme.Sub4
import com.teamoffroad.core.designsystem.theme.Sub55
import com.teamoffroad.core.designsystem.theme.White
import com.teamoffroad.feature.home.domain.model.UserQuests
@@ -143,6 +148,17 @@ fun HomeScreen(
}
}
}
+
+
+ Box(
+ contentAlignment = Alignment.TopCenter,
+ modifier = Modifier
+ .padding(start = 24.dp, top = 70.dp, end = 24.dp)
+ ) {
+ CharacterChat(
+ text = "맛있었겠다"
+ )
+ }
}
if (isCompleteQuestDialogShown.value) {
@@ -156,6 +172,47 @@ fun HomeScreen(
}
}
+@Composable
+fun CharacterChat(
+ text: String,
+ characterTextColor: Color = Sub4,
+ characterTextStyle: TextStyle = OffroadTheme.typography.textBold,
+ messageTextColor: Color = Main2,
+ messageTextStyle: TextStyle = OffroadTheme.typography.textRegular,
+ backgroundColor: Color = Main3,
+ borderColor: Color = BtnInactive
+) {
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .background(
+ color = backgroundColor,
+ shape = RoundedCornerShape(12.dp)
+ )
+ .padding(vertical = 14.dp, horizontal = 18.dp)
+ ) {
+ Column {
+ Row {
+ Text(
+ text = "아루 : ",
+ modifier = Modifier,
+ color = characterTextColor,
+ style = characterTextStyle
+ )
+ Text(
+ text = "맛있었겠다!!",
+ modifier = Modifier.fillMaxWidth(),
+ color = messageTextColor,
+ style = messageTextStyle,
+ maxLines = 2
+ )
+ }
+ AnswerCharacterChat()
+ }
+
+ }
+}
+
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
@Composable
private fun UsersAdventuresInformation(
@@ -242,6 +299,31 @@ fun FinishChatting(
)
}
+@Composable
+fun AnswerCharacterChat(
+ backgroundColor: Color = Main2,
+ textColor: Color = Main3,
+ textStyle: TextStyle = OffroadTheme.typography.textContents
+) {
+ Box(
+ modifier = Modifier.fillMaxWidth()
+ ) {
+ Box(contentAlignment = Alignment.CenterEnd, modifier = Modifier.fillMaxWidth()) {
+ Text(
+ text = "답장하기",
+ modifier = Modifier
+ .background(
+ color = backgroundColor,
+ shape = RoundedCornerShape(8.dp)
+ )
+ .padding(horizontal = 14.dp, vertical = 6.dp),
+ color = textColor,
+ style = textStyle
+ )
+ }
+ }
+}
+
@Composable
private fun UsersQuestInformation(
context: Context,
From 5e8bcb880550288e33a9f514a57a95adc33f9c88 Mon Sep 17 00:00:00 2001
From: YuJeongHyun <0703olivia@naver.com>
Date: Sun, 17 Nov 2024 22:40:37 +0900
Subject: [PATCH 21/30] feature: Add Character Chat
---
.../home/presentation/HomeChatTextField.kt | 59 +++++++++++++++----
.../feature/home/presentation/HomeScreen.kt | 56 ++++++++++++------
.../home/presentation/HomeViewModel.kt | 45 +++++++++++---
.../presentation/model/CharacterChatModel.kt | 6 ++
4 files changed, 127 insertions(+), 39 deletions(-)
create mode 100644 feature/home/src/main/java/com/teamoffroad/feature/home/presentation/model/CharacterChatModel.kt
diff --git a/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeChatTextField.kt b/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeChatTextField.kt
index f4fd9a87..52fe7132 100644
--- a/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeChatTextField.kt
+++ b/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeChatTextField.kt
@@ -8,6 +8,9 @@ import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.aspectRatio
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
@@ -40,9 +43,17 @@ import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
+import com.airbnb.lottie.compose.LottieAnimation
+import com.airbnb.lottie.compose.LottieCompositionSpec
+import com.airbnb.lottie.compose.LottieConstants
+import com.airbnb.lottie.compose.animateLottieCompositionAsState
+import com.airbnb.lottie.compose.rememberLottieComposition
import com.teamoffroad.core.designsystem.component.clickableWithoutRipple
import com.teamoffroad.core.designsystem.theme.BtnInactive
+import com.teamoffroad.core.designsystem.theme.ErrorNew
+import com.teamoffroad.core.designsystem.theme.Kakao
import com.teamoffroad.core.designsystem.theme.Main2
import com.teamoffroad.core.designsystem.theme.OffroadTheme
import com.teamoffroad.core.designsystem.theme.Transparent
@@ -65,6 +76,7 @@ fun HomeChatTextField(
val focusRequester = remember { FocusRequester() }
val focusManager = LocalFocusManager.current
val contextView = LocalView.current
+ var showLottieLoading by remember { mutableStateOf(false) }
var keyboardVisible by remember { mutableStateOf(keyboard) }
@@ -110,27 +122,48 @@ fun HomeChatTextField(
) {
val textFieldHeight = remember { mutableIntStateOf(0) }
- Column {
+ Column{
Row(
modifier = Modifier
.fillMaxWidth()
- .padding(top = 18.dp, bottom = 4.dp)
+ .padding(top = 6.dp, bottom = 4.dp)
) {
Text(
text = stringResource(id = R.string.home_chat_me),
color = Main2,
- style = OffroadTheme.typography.textBold
- )
- Text(
- text = sentMessage,
- modifier = Modifier
- .fillMaxWidth()
- .onGloballyPositioned { layoutCoordinates ->
- textFieldHeight.intValue = layoutCoordinates.size.height
- },
- style = OffroadTheme.typography.textRegular,
- maxLines = 2,
+ style = OffroadTheme.typography.textBold,
+ textAlign = TextAlign.Center,
+ modifier = Modifier.padding(vertical = 6.dp)
)
+ Box{
+ if (text.isNotBlank()) {
+ Box(
+ modifier = Modifier
+ .size(width = 54.dp, height = 27.dp)
+ ) {
+ val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(com.teamoffroad.offroad.core.designsystem.R.raw.loading_linear))
+ val animationState = animateLottieCompositionAsState(composition, iterations = LottieConstants.IterateForever)
+
+ if (animationState.isAtEnd && animationState.isPlaying) {
+ LaunchedEffect(Unit) { }
+ }
+
+ LottieAnimation(composition, animationState.progress)
+ }
+ } else {
+ Text(
+ text = sentMessage,
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(vertical = 6.dp)
+ .onGloballyPositioned { layoutCoordinates ->
+ textFieldHeight.intValue = layoutCoordinates.size.height
+ },
+ style = OffroadTheme.typography.textRegular,
+ maxLines = 2,
+ )
+ }
+ }
}
Box {
diff --git a/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeScreen.kt b/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeScreen.kt
index 42a0d4aa..bd83f878 100644
--- a/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeScreen.kt
+++ b/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeScreen.kt
@@ -75,7 +75,10 @@ fun HomeScreen(
val isCompleteQuestDialogShown = remember { mutableStateOf(false) }
val isChatting = remember { mutableStateOf(false) }
val chattingText = viewModel.chattingText.collectAsStateWithLifecycle()
- val sentMessage = remember { mutableStateOf("") }
+ val sendMessage = remember { mutableStateOf("") }
+ val characterChat = viewModel.getCharacterChat.collectAsStateWithLifecycle()
+ val isCharacterChatting = viewModel.isCharacterChatting.collectAsStateWithLifecycle()
+ val answerCharacterChat = remember{ mutableStateOf(false) }
LaunchedEffect(Unit) {
viewModel.updateAutoSignIn()
@@ -130,34 +133,40 @@ fun HomeScreen(
HomeChatTextField(
text = chattingText.value,
- sentMessage = sentMessage.value,
+ sentMessage = sendMessage.value,
isChatting = isChatting,
keyboard = true,
onValueChange = { text ->
+ // TODO: 입력 중일 때 로딩 로티 띄우기
viewModel.updateChattingText(text)
},
// onFocusChange = { isFocused ->
// viewModel.updateIsChatting(isFocused)
// }
onSendClick = {
- sentMessage.value = chattingText.value
- viewModel.updateChattingText("")
+ answerCharacterChat.value = true
+ sendMessage.value = chattingText.value
viewModel.sendChat()
+ viewModel.updateChattingText("")
}
)
}
}
}
-
- Box(
- contentAlignment = Alignment.TopCenter,
- modifier = Modifier
- .padding(start = 24.dp, top = 70.dp, end = 24.dp)
- ) {
- CharacterChat(
- text = "맛있었겠다"
- )
+ if (isCharacterChatting.value) {
+ Box(
+ contentAlignment = Alignment.TopCenter,
+ modifier = Modifier
+ .padding(start = 24.dp, top = 70.dp, end = 24.dp)
+ ) {
+ CharacterChat(
+ isChatting = isChatting,
+ answerCharacterChat = answerCharacterChat,
+ characterName = characterChat.value.characterName,
+ characterContent = characterChat.value.characterContent
+ )
+ }
}
}
@@ -174,7 +183,10 @@ fun HomeScreen(
@Composable
fun CharacterChat(
- text: String,
+ isChatting: MutableState,
+ answerCharacterChat: MutableState,
+ characterName: String,
+ characterContent: String,
characterTextColor: Color = Sub4,
characterTextStyle: TextStyle = OffroadTheme.typography.textBold,
messageTextColor: Color = Main2,
@@ -194,20 +206,24 @@ fun CharacterChat(
Column {
Row {
Text(
- text = "아루 : ",
+ text = "${characterName} : ",
modifier = Modifier,
color = characterTextColor,
style = characterTextStyle
)
Text(
- text = "맛있었겠다!!",
+ text = characterContent,
modifier = Modifier.fillMaxWidth(),
color = messageTextColor,
style = messageTextStyle,
maxLines = 2
)
}
- AnswerCharacterChat()
+ if (!answerCharacterChat.value) {
+ // 사용자가 답변을 보냈을 때
+ AnswerCharacterChat(isChatting = isChatting)
+ // TODO: 알림에 로딩 로티 띄우기
+ }
}
}
@@ -301,6 +317,7 @@ fun FinishChatting(
@Composable
fun AnswerCharacterChat(
+ isChatting: MutableState,
backgroundColor: Color = Main2,
textColor: Color = Main3,
textStyle: TextStyle = OffroadTheme.typography.textContents
@@ -316,7 +333,10 @@ fun AnswerCharacterChat(
color = backgroundColor,
shape = RoundedCornerShape(8.dp)
)
- .padding(horizontal = 14.dp, vertical = 6.dp),
+ .padding(horizontal = 14.dp, vertical = 6.dp)
+ .clickableWithoutRipple {
+ isChatting.value = true
+ },
color = textColor,
style = textStyle
)
diff --git a/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeViewModel.kt b/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeViewModel.kt
index bcf3c6d1..3b08ae1c 100644
--- a/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeViewModel.kt
+++ b/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeViewModel.kt
@@ -18,6 +18,7 @@ import com.teamoffroad.feature.home.domain.repository.UserRepository
import com.teamoffroad.feature.home.domain.usecase.PostFcmTokenUseCase
import com.teamoffroad.feature.home.presentation.component.UiState
import com.teamoffroad.feature.home.presentation.component.getErrorMessage
+import com.teamoffroad.feature.home.presentation.model.CharacterChatModel
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@@ -38,6 +39,8 @@ class HomeViewModel @Inject constructor(
private val deviceTokenRepository: DeviceTokenRepository,
private val fcmTokenUseCase: PostFcmTokenUseCase,
) : ViewModel() {
+ private val _getCharacterChat = MutableStateFlow(CharacterChatModel("", ""))
+ val getCharacterChat = _getCharacterChat.asStateFlow()
private val _getUsersAdventuresInformationState =
MutableStateFlow>(
@@ -75,12 +78,16 @@ class HomeViewModel @Inject constructor(
private val _linearProgressBar = MutableStateFlow(0f)
val linearProgressBar = _linearProgressBar.asStateFlow()
- private val _isChatting: MutableStateFlow = MutableStateFlow(false)
- val isChatting: StateFlow = _isChatting.asStateFlow()
+ private val _isCharacterChatting: MutableStateFlow = MutableStateFlow(false)
+ val isCharacterChatting: StateFlow = _isCharacterChatting.asStateFlow()
private val _chattingText: MutableStateFlow = MutableStateFlow("")
val chattingText: StateFlow = _chattingText.asStateFlow()
+ private val _characterName = MutableStateFlow("")
+ val characterName = _characterName.asStateFlow()
+
+
var asd = MutableStateFlow("")
init {
//아까 CharacterChatBroadcastReceiver에서 게시한 브로드캐스트리시버를 여기서 받습니다.
@@ -101,6 +108,19 @@ class HomeViewModel @Inject constructor(
@Subscribe(threadMode = ThreadMode.MAIN)
fun onNotificationEvent(event: NotificationEvent) {
Log.d("characterChat data", event.toString())
+
+ // 1. 홈 화면에서 캐릭터한테 메시지가 왔을 때
+ val characterName = event.characterName
+ val characterContent = event.characterContent
+ if(characterName != null && characterContent != null) {
+ _characterName.value = characterName
+ _getCharacterChat.value = CharacterChatModel(characterName, characterContent)
+ _isCharacterChatting.value = true
+ }
+
+
+ // 2. 로컬에 내용 저장 characterName,characterContent, confirm (이건 우선 나중에)
+
}
fun getUsersAdventuresInformation(category: String) {
@@ -109,6 +129,7 @@ class HomeViewModel @Inject constructor(
userRepository.getUsersAdventuresInformation(category)
}.onSuccess { state ->
_getUsersAdventuresInformationState.emit(UiState.Success(state))
+ _characterName.value = state.characterName
updateSelectedEmblem(state.emblemName)
updateCharacterImage(state.baseImageUrl)
updateMotionImageUrl(state.motionImageUrl)
@@ -193,11 +214,6 @@ class HomeViewModel @Inject constructor(
_chattingText.value = text
}
- fun updateIsChatting(boolean: Boolean) {
- _isChatting.value = boolean
- _chattingText.value = ""
- }
-
fun sendChat() {
val chattingText = chattingText.value
@@ -212,9 +228,22 @@ class HomeViewModel @Inject constructor(
)
characterChatRepository.saveChat(1, chattingText)
}.onSuccess { chat ->
- // _patchEmblemState.emit(UiState.Success(state))
// 보낸 채팅 내용 홈에 보여주어야 함
_sendChatState.emit(UiState.Success(chat))
+ /*
+ val characterName = event.characterName
+ val characterContent = event.characterContent
+ if(characterName != null && characterContent != null) {
+ _getCharacterChat.value = CharacterChatModel(characterName, characterContent)
+ _isCharacterChatting.value = true
+ }
+ */
+ val characterContent = chat.content
+ if (characterContent != null) {
+ _getCharacterChat.value = CharacterChatModel(_characterName.value, characterContent)
+ _isCharacterChatting.value = true
+ }
+
}.onFailure { t ->
val errorMessage = getErrorMessage(t)
_sendChatState.emit(UiState.Failure(errorMessage))
diff --git a/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/model/CharacterChatModel.kt b/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/model/CharacterChatModel.kt
new file mode 100644
index 00000000..8fda6f2d
--- /dev/null
+++ b/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/model/CharacterChatModel.kt
@@ -0,0 +1,6 @@
+package com.teamoffroad.feature.home.presentation.model
+
+data class CharacterChatModel(
+ val characterName: String,
+ val characterContent: String,
+)
\ No newline at end of file
From 54a33c2f96ff5ed65c7b1d04275d5b65179a6d22 Mon Sep 17 00:00:00 2001
From: YuJeongHyun <0703olivia@naver.com>
Date: Sun, 17 Nov 2024 23:25:59 +0900
Subject: [PATCH 22/30] feature: Add isCharacterChattingLoading
---
.../feature/home/presentation/HomeScreen.kt | 63 +++++++++++++------
.../home/presentation/HomeViewModel.kt | 18 +++---
2 files changed, 50 insertions(+), 31 deletions(-)
diff --git a/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeScreen.kt b/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeScreen.kt
index bd83f878..64b8ae36 100644
--- a/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeScreen.kt
+++ b/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeScreen.kt
@@ -13,12 +13,15 @@ import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.State
import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
@@ -32,6 +35,11 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.airbnb.lottie.compose.LottieAnimation
+import com.airbnb.lottie.compose.LottieCompositionSpec
+import com.airbnb.lottie.compose.LottieConstants
+import com.airbnb.lottie.compose.animateLottieCompositionAsState
+import com.airbnb.lottie.compose.rememberLottieComposition
import com.teamoffroad.core.designsystem.component.actionBarPadding
import com.teamoffroad.core.designsystem.component.clickableWithoutRipple
import com.teamoffroad.core.designsystem.theme.BtnInactive
@@ -78,7 +86,8 @@ fun HomeScreen(
val sendMessage = remember { mutableStateOf("") }
val characterChat = viewModel.getCharacterChat.collectAsStateWithLifecycle()
val isCharacterChatting = viewModel.isCharacterChatting.collectAsStateWithLifecycle()
- val answerCharacterChat = remember{ mutableStateOf(false) }
+ val isCharacterChattingLoading = viewModel.isCharacterChattingLoading.collectAsStateWithLifecycle()
+ val userSendChat = remember{ mutableStateOf(false) }
LaunchedEffect(Unit) {
viewModel.updateAutoSignIn()
@@ -137,17 +146,13 @@ fun HomeScreen(
isChatting = isChatting,
keyboard = true,
onValueChange = { text ->
- // TODO: 입력 중일 때 로딩 로티 띄우기
viewModel.updateChattingText(text)
},
-// onFocusChange = { isFocused ->
-// viewModel.updateIsChatting(isFocused)
-// }
onSendClick = {
- answerCharacterChat.value = true
- sendMessage.value = chattingText.value
- viewModel.sendChat()
- viewModel.updateChattingText("")
+ userSendChat.value = true // 사용자가 채팅 보냄
+ sendMessage.value = chattingText.value // 보낼 메시지
+ viewModel.sendChat() // 서버에 보내기
+ viewModel.updateChattingText("") // 초기화
}
)
}
@@ -162,7 +167,8 @@ fun HomeScreen(
) {
CharacterChat(
isChatting = isChatting,
- answerCharacterChat = answerCharacterChat,
+ isCharacterChattingLoading = isCharacterChattingLoading,
+ answerCharacterChat = userSendChat,
characterName = characterChat.value.characterName,
characterContent = characterChat.value.characterContent
)
@@ -184,6 +190,7 @@ fun HomeScreen(
@Composable
fun CharacterChat(
isChatting: MutableState,
+ isCharacterChattingLoading: State,
answerCharacterChat: MutableState,
characterName: String,
characterContent: String,
@@ -206,23 +213,39 @@ fun CharacterChat(
Column {
Row {
Text(
- text = "${characterName} : ",
+ text = "$characterName : ",
modifier = Modifier,
color = characterTextColor,
style = characterTextStyle
)
- Text(
- text = characterContent,
- modifier = Modifier.fillMaxWidth(),
- color = messageTextColor,
- style = messageTextStyle,
- maxLines = 2
- )
+
+ // 메세지를 보냈을 때 로티 띄우기
+ if (isCharacterChattingLoading.value) {
+ Box(
+ modifier = Modifier
+ .size(width = 54.dp, height = 27.dp)
+ ) {
+ val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(com.teamoffroad.offroad.core.designsystem.R.raw.loading_linear))
+ val animationState = animateLottieCompositionAsState(composition, iterations = LottieConstants.IterateForever)
+
+ if (animationState.isAtEnd && animationState.isPlaying) {
+ LaunchedEffect(Unit) { }
+ }
+
+ LottieAnimation(composition, animationState.progress)
+ }
+ } else {
+ Text(
+ text = characterContent,
+ modifier = Modifier.fillMaxWidth(),
+ color = messageTextColor,
+ style = messageTextStyle,
+ maxLines = 2
+ )
+ }
}
if (!answerCharacterChat.value) {
- // 사용자가 답변을 보냈을 때
AnswerCharacterChat(isChatting = isChatting)
- // TODO: 알림에 로딩 로티 띄우기
}
}
diff --git a/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeViewModel.kt b/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeViewModel.kt
index 3b08ae1c..6130ee13 100644
--- a/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeViewModel.kt
+++ b/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeViewModel.kt
@@ -81,13 +81,15 @@ class HomeViewModel @Inject constructor(
private val _isCharacterChatting: MutableStateFlow = MutableStateFlow(false)
val isCharacterChatting: StateFlow = _isCharacterChatting.asStateFlow()
+ private val _isCharacterChattingLoading = MutableStateFlow(false)
+ val isCharacterChattingLoading = _isCharacterChattingLoading.asStateFlow()
+
private val _chattingText: MutableStateFlow = MutableStateFlow("")
val chattingText: StateFlow = _chattingText.asStateFlow()
private val _characterName = MutableStateFlow("")
val characterName = _characterName.asStateFlow()
-
var asd = MutableStateFlow("")
init {
//아까 CharacterChatBroadcastReceiver에서 게시한 브로드캐스트리시버를 여기서 받습니다.
@@ -113,8 +115,7 @@ class HomeViewModel @Inject constructor(
val characterName = event.characterName
val characterContent = event.characterContent
if(characterName != null && characterContent != null) {
- _characterName.value = characterName
- _getCharacterChat.value = CharacterChatModel(characterName, characterContent)
+ _getCharacterChat.value = CharacterChatModel(_characterName.value, characterContent)
_isCharacterChatting.value = true
}
@@ -216,6 +217,7 @@ class HomeViewModel @Inject constructor(
fun sendChat() {
val chattingText = chattingText.value
+ _isCharacterChattingLoading.value = true
viewModelScope.launch {
runCatching {
@@ -230,14 +232,8 @@ class HomeViewModel @Inject constructor(
}.onSuccess { chat ->
// 보낸 채팅 내용 홈에 보여주어야 함
_sendChatState.emit(UiState.Success(chat))
- /*
- val characterName = event.characterName
- val characterContent = event.characterContent
- if(characterName != null && characterContent != null) {
- _getCharacterChat.value = CharacterChatModel(characterName, characterContent)
- _isCharacterChatting.value = true
- }
- */
+ _isCharacterChattingLoading.value = false
+
val characterContent = chat.content
if (characterContent != null) {
_getCharacterChat.value = CharacterChatModel(_characterName.value, characterContent)
From 9423e7ed9222d079b61e936d35ee84f5a7057b35 Mon Sep 17 00:00:00 2001
From: YuJeongHyun <0703olivia@naver.com>
Date: Sun, 17 Nov 2024 23:33:55 +0900
Subject: [PATCH 23/30] refactor: Edit home character image url
---
.../component/character/CharacterItem.kt | 31 +++++++++----------
1 file changed, 15 insertions(+), 16 deletions(-)
diff --git a/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/component/character/CharacterItem.kt b/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/component/character/CharacterItem.kt
index 3c776500..184271f0 100644
--- a/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/component/character/CharacterItem.kt
+++ b/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/component/character/CharacterItem.kt
@@ -73,23 +73,22 @@ class CharacterItem {
.fillMaxHeight()
.align(Alignment.BottomCenter)
) {
- Image(
- painter = painterResource(id = R.drawable.img_home_character),
- contentDescription = "character",
- modifier = Modifier.fillMaxSize()
- )
-
-// AsyncImage(
-// model = ImageRequest.Builder(context)
-// .data(baseCharacterImage)
-// .decoderFactory(SvgDecoder.Factory())
-// .build(),
-// contentDescription = "explorer",
-// modifier = Modifier
-// .fillMaxSize()
-// .align(Alignment.BottomCenter),
-// // TODO: placeholder, error일 때
+// Image(
+// painter = painterResource(id = R.drawable.img_home_character),
+// contentDescription = "character",
+// modifier = Modifier.fillMaxSize()
// )
+
+ AsyncImage(
+ model = ImageRequest.Builder(context)
+ .data(baseCharacterImage)
+ .decoderFactory(SvgDecoder.Factory())
+ .build(),
+ contentDescription = "explorer",
+ modifier = Modifier
+ .fillMaxSize()
+ .align(Alignment.BottomCenter),
+ )
}
} else {
val composition by rememberLottieComposition(
From 827637431c11ed0c73a67bff923c4dc4c6dab9db Mon Sep 17 00:00:00 2001
From: YuJeongHyun <0703olivia@naver.com>
Date: Sun, 17 Nov 2024 23:41:17 +0900
Subject: [PATCH 24/30] feature: Add update isCharacterChatting
---
.../feature/home/presentation/HomeChatTextField.kt | 3 +++
.../feature/home/presentation/HomeScreen.kt | 1 +
.../feature/home/presentation/HomeViewModel.kt | 10 ++++++++--
.../feature/home/presentation/component/HomeIcons.kt | 1 +
4 files changed, 13 insertions(+), 2 deletions(-)
diff --git a/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeChatTextField.kt b/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeChatTextField.kt
index 52fe7132..28ca8e83 100644
--- a/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeChatTextField.kt
+++ b/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeChatTextField.kt
@@ -27,6 +27,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.State
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
@@ -68,6 +69,7 @@ fun HomeChatTextField(
sentMessage: String,
isChatting: MutableState,
keyboard: Boolean,
+ isCharacterChatting: (Boolean) -> Unit,
onValueChange: (String) -> Unit = {},
onFocusChange: (Boolean) -> Unit = {},
onSendClick: () -> Unit = {},
@@ -90,6 +92,7 @@ fun HomeChatTextField(
if (!keyboardVisible) {
focusManager.clearFocus()
isChatting.value = false
+ isCharacterChatting(false)
}
}
diff --git a/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeScreen.kt b/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeScreen.kt
index 64b8ae36..0f5dcaa8 100644
--- a/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeScreen.kt
+++ b/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeScreen.kt
@@ -145,6 +145,7 @@ fun HomeScreen(
sentMessage = sendMessage.value,
isChatting = isChatting,
keyboard = true,
+ isCharacterChatting = viewModel::updateCharacterChatting,
onValueChange = { text ->
viewModel.updateChattingText(text)
},
diff --git a/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeViewModel.kt b/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeViewModel.kt
index 6130ee13..96ced1d3 100644
--- a/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeViewModel.kt
+++ b/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeViewModel.kt
@@ -116,7 +116,8 @@ class HomeViewModel @Inject constructor(
val characterContent = event.characterContent
if(characterName != null && characterContent != null) {
_getCharacterChat.value = CharacterChatModel(_characterName.value, characterContent)
- _isCharacterChatting.value = true
+ //_isCharacterChatting.value = true
+ updateCharacterChatting(true)
}
@@ -124,6 +125,10 @@ class HomeViewModel @Inject constructor(
}
+ fun updateCharacterChatting(state: Boolean) {
+ _isCharacterChatting.value = state
+ }
+
fun getUsersAdventuresInformation(category: String) {
viewModelScope.launch {
runCatching {
@@ -237,7 +242,8 @@ class HomeViewModel @Inject constructor(
val characterContent = chat.content
if (characterContent != null) {
_getCharacterChat.value = CharacterChatModel(_characterName.value, characterContent)
- _isCharacterChatting.value = true
+ //_isCharacterChatting.value = true
+ updateCharacterChatting(true)
}
}.onFailure { t ->
diff --git a/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/component/HomeIcons.kt b/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/component/HomeIcons.kt
index 81e69a1d..80e01acd 100644
--- a/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/component/HomeIcons.kt
+++ b/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/component/HomeIcons.kt
@@ -44,6 +44,7 @@ fun HomeIcons(
Image(
painter = painterResource(id = R.drawable.ic_home_chat),
contentDescription = "chat",
+ //없앨 부분
modifier = Modifier.clickableWithoutRipple {
isChatting.value = true
}
From b673359c6b0327a8c860afe847df46a668c9bf26 Mon Sep 17 00:00:00 2001
From: YuJeongHyun <0703olivia@naver.com>
Date: Mon, 18 Nov 2024 23:23:45 +0900
Subject: [PATCH 25/30] feature: Add ic_home_accordion
---
.../feature/home/presentation/HomeScreen.kt | 19 +++++++++++++++----
.../home/presentation/HomeViewModel.kt | 5 -----
.../main/res/drawable/ic_home_accordion.xml | 17 +++++++++++++++++
3 files changed, 32 insertions(+), 9 deletions(-)
create mode 100644 feature/home/src/main/res/drawable/ic_home_accordion.xml
diff --git a/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeScreen.kt b/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeScreen.kt
index 0f5dcaa8..54f323ad 100644
--- a/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeScreen.kt
+++ b/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeScreen.kt
@@ -4,6 +4,7 @@ import android.content.Context
import android.os.Build
import android.widget.Toast
import androidx.annotation.RequiresApi
+import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Box
@@ -29,8 +30,10 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
@@ -202,6 +205,8 @@ fun CharacterChat(
backgroundColor: Color = Main3,
borderColor: Color = BtnInactive
) {
+ val checkCharacterChattingLines = remember { mutableStateOf(false) }
+
Box(
modifier = Modifier
.fillMaxWidth()
@@ -220,7 +225,6 @@ fun CharacterChat(
style = characterTextStyle
)
- // 메세지를 보냈을 때 로티 띄우기
if (isCharacterChattingLoading.value) {
Box(
modifier = Modifier
@@ -238,13 +242,20 @@ fun CharacterChat(
} else {
Text(
text = characterContent,
- modifier = Modifier.fillMaxWidth(),
+ modifier = Modifier.weight(1f),
color = messageTextColor,
style = messageTextStyle,
- maxLines = 2
+ onTextLayout = { textLayoutResult ->
+ checkCharacterChattingLines.value = textLayoutResult.lineCount >= 3
+ },
+ maxLines = 2,
+ overflow = TextOverflow.Ellipsis,
)
}
+
+ Image(painter = painterResource(id = R.drawable.ic_home_accordion), contentDescription = "accordion down")
}
+
if (!answerCharacterChat.value) {
AnswerCharacterChat(isChatting = isChatting)
}
@@ -347,7 +358,7 @@ fun AnswerCharacterChat(
textStyle: TextStyle = OffroadTheme.typography.textContents
) {
Box(
- modifier = Modifier.fillMaxWidth()
+ modifier = Modifier.fillMaxWidth().padding(top = 10.dp)
) {
Box(contentAlignment = Alignment.CenterEnd, modifier = Modifier.fillMaxWidth()) {
Text(
diff --git a/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeViewModel.kt b/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeViewModel.kt
index 96ced1d3..541118b2 100644
--- a/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeViewModel.kt
+++ b/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeViewModel.kt
@@ -116,13 +116,9 @@ class HomeViewModel @Inject constructor(
val characterContent = event.characterContent
if(characterName != null && characterContent != null) {
_getCharacterChat.value = CharacterChatModel(_characterName.value, characterContent)
- //_isCharacterChatting.value = true
updateCharacterChatting(true)
}
-
- // 2. 로컬에 내용 저장 characterName,characterContent, confirm (이건 우선 나중에)
-
}
fun updateCharacterChatting(state: Boolean) {
@@ -242,7 +238,6 @@ class HomeViewModel @Inject constructor(
val characterContent = chat.content
if (characterContent != null) {
_getCharacterChat.value = CharacterChatModel(_characterName.value, characterContent)
- //_isCharacterChatting.value = true
updateCharacterChatting(true)
}
diff --git a/feature/home/src/main/res/drawable/ic_home_accordion.xml b/feature/home/src/main/res/drawable/ic_home_accordion.xml
new file mode 100644
index 00000000..c218271b
--- /dev/null
+++ b/feature/home/src/main/res/drawable/ic_home_accordion.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
From af89b847e8ef1caf41d0270d059fd2c0fc918783 Mon Sep 17 00:00:00 2001
From: YuJeongHyun <0703olivia@naver.com>
Date: Mon, 18 Nov 2024 23:39:33 +0900
Subject: [PATCH 26/30] feature: Add accordion rotationX
---
.../feature/home/presentation/HomeScreen.kt | 51 ++++++++++++++++---
1 file changed, 43 insertions(+), 8 deletions(-)
diff --git a/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeScreen.kt b/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeScreen.kt
index 54f323ad..36f6c576 100644
--- a/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeScreen.kt
+++ b/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeScreen.kt
@@ -2,8 +2,11 @@ package com.teamoffroad.feature.home.presentation
import android.content.Context
import android.os.Build
+import android.util.Log
import android.widget.Toast
import androidx.annotation.RequiresApi
+import androidx.compose.animation.core.animateFloatAsState
+import androidx.compose.animation.core.tween
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.border
@@ -29,6 +32,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
@@ -89,8 +93,9 @@ fun HomeScreen(
val sendMessage = remember { mutableStateOf("") }
val characterChat = viewModel.getCharacterChat.collectAsStateWithLifecycle()
val isCharacterChatting = viewModel.isCharacterChatting.collectAsStateWithLifecycle()
- val isCharacterChattingLoading = viewModel.isCharacterChattingLoading.collectAsStateWithLifecycle()
- val userSendChat = remember{ mutableStateOf(false) }
+ val isCharacterChattingLoading =
+ viewModel.isCharacterChattingLoading.collectAsStateWithLifecycle()
+ val userSendChat = remember { mutableStateOf(false) }
LaunchedEffect(Unit) {
viewModel.updateAutoSignIn()
@@ -206,6 +211,13 @@ fun CharacterChat(
borderColor: Color = BtnInactive
) {
val checkCharacterChattingLines = remember { mutableStateOf(false) }
+ val isExpanded = remember { mutableStateOf(false) }
+
+ val rotationAngle by animateFloatAsState(
+ targetValue = if (isExpanded.value) 180f else 0f,
+ animationSpec = tween(durationMillis = 300), label = ""
+ )
+
Box(
modifier = Modifier
@@ -214,6 +226,11 @@ fun CharacterChat(
color = backgroundColor,
shape = RoundedCornerShape(12.dp)
)
+ .border(
+ width = 1.dp,
+ shape = RoundedCornerShape(12.dp),
+ color = borderColor
+ )
.padding(vertical = 14.dp, horizontal = 18.dp)
) {
Column {
@@ -230,11 +247,18 @@ fun CharacterChat(
modifier = Modifier
.size(width = 54.dp, height = 27.dp)
) {
- val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(com.teamoffroad.offroad.core.designsystem.R.raw.loading_linear))
- val animationState = animateLottieCompositionAsState(composition, iterations = LottieConstants.IterateForever)
+ val composition by rememberLottieComposition(
+ LottieCompositionSpec.RawRes(
+ com.teamoffroad.offroad.core.designsystem.R.raw.loading_linear
+ )
+ )
+ val animationState = animateLottieCompositionAsState(
+ composition,
+ iterations = LottieConstants.IterateForever
+ )
if (animationState.isAtEnd && animationState.isPlaying) {
- LaunchedEffect(Unit) { }
+ LaunchedEffect(Unit) { }
}
LottieAnimation(composition, animationState.progress)
@@ -248,12 +272,21 @@ fun CharacterChat(
onTextLayout = { textLayoutResult ->
checkCharacterChattingLines.value = textLayoutResult.lineCount >= 3
},
- maxLines = 2,
+ maxLines = if (isExpanded.value) Int.MAX_VALUE else 2,
overflow = TextOverflow.Ellipsis,
)
+
+ Image(
+ painter = painterResource(id = R.drawable.ic_home_accordion),
+ contentDescription = "accordion down",
+ modifier = Modifier
+ .graphicsLayer(rotationX = rotationAngle)
+ .clickableWithoutRipple {
+ isExpanded.value = !isExpanded.value
+ }
+ )
}
- Image(painter = painterResource(id = R.drawable.ic_home_accordion), contentDescription = "accordion down")
}
if (!answerCharacterChat.value) {
@@ -358,7 +391,9 @@ fun AnswerCharacterChat(
textStyle: TextStyle = OffroadTheme.typography.textContents
) {
Box(
- modifier = Modifier.fillMaxWidth().padding(top = 10.dp)
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(top = 10.dp)
) {
Box(contentAlignment = Alignment.CenterEnd, modifier = Modifier.fillMaxWidth()) {
Text(
From ddf036157b4e51e520a39c2c1615b3ed2b6f39ed Mon Sep 17 00:00:00 2001
From: YuJeongHyun <0703olivia@naver.com>
Date: Mon, 18 Nov 2024 23:56:13 +0900
Subject: [PATCH 27/30] feature: Connect home and characterchat
---
.../feature/home/navigation/HomeNavigation.kt | 2 ++
.../feature/home/presentation/HomeScreen.kt | 24 +++++++++++++++----
.../home/presentation/component/HomeIcons.kt | 10 ++++----
.../feature/main/component/MainNavHost.kt | 3 +++
4 files changed, 30 insertions(+), 9 deletions(-)
diff --git a/feature/home/src/main/java/com/teamoffroad/feature/home/navigation/HomeNavigation.kt b/feature/home/src/main/java/com/teamoffroad/feature/home/navigation/HomeNavigation.kt
index 4aaedea0..a3233928 100644
--- a/feature/home/src/main/java/com/teamoffroad/feature/home/navigation/HomeNavigation.kt
+++ b/feature/home/src/main/java/com/teamoffroad/feature/home/navigation/HomeNavigation.kt
@@ -21,6 +21,7 @@ fun NavController.navigateToHome(
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
fun NavGraphBuilder.homeNavGraph(
navigateToBack: () -> Unit,
+ navigateToCharacterChatScreen: (Int, String) -> Unit,
navigateToGainedCharacter: () -> Unit,
) {
composable { backStackEntry ->
@@ -29,6 +30,7 @@ fun NavGraphBuilder.homeNavGraph(
HomeScreen(
category = category,
completeQuests = completeQuests,
+ navigateToCharacterChatScreen = navigateToCharacterChatScreen,
navigateToGainedCharacter = navigateToGainedCharacter,
)
}
diff --git a/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeScreen.kt b/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeScreen.kt
index 36f6c576..3fc8040f 100644
--- a/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeScreen.kt
+++ b/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeScreen.kt
@@ -84,6 +84,7 @@ fun HomeScreen(
category: String?,
completeQuests: List = emptyList(),
navigateToGainedCharacter: () -> Unit = {},
+ navigateToCharacterChatScreen: (Int, String) -> Unit
) {
val context = LocalContext.current
val viewModel: HomeViewModel = hiltViewModel()
@@ -96,6 +97,7 @@ fun HomeScreen(
val isCharacterChattingLoading =
viewModel.isCharacterChattingLoading.collectAsStateWithLifecycle()
val userSendChat = remember { mutableStateOf(false) }
+ val characterName = viewModel.characterName.collectAsStateWithLifecycle()
LaunchedEffect(Unit) {
viewModel.updateAutoSignIn()
@@ -116,11 +118,13 @@ fun HomeScreen(
UsersAdventuresInformation(
isChatting = isChatting,
context = context,
+ characterName = characterName.value,
modifier = Modifier
.weight(1f)
.actionBarPadding(),
viewModel = viewModel,
navigateToGainedCharacter = navigateToGainedCharacter,
+ navigateToCharacterChatScreen = navigateToCharacterChatScreen
)
Spacer(modifier = Modifier.padding(top = 12.dp))
UsersQuestInformation(context, viewModel)
@@ -178,8 +182,9 @@ fun HomeScreen(
isChatting = isChatting,
isCharacterChattingLoading = isCharacterChattingLoading,
answerCharacterChat = userSendChat,
- characterName = characterChat.value.characterName,
- characterContent = characterChat.value.characterContent
+ characterName = characterName.value,
+ characterContent = characterChat.value.characterContent,
+ navigateToCharacterChatScreen = navigateToCharacterChatScreen
)
}
}
@@ -208,7 +213,8 @@ fun CharacterChat(
messageTextColor: Color = Main2,
messageTextStyle: TextStyle = OffroadTheme.typography.textRegular,
backgroundColor: Color = Main3,
- borderColor: Color = BtnInactive
+ borderColor: Color = BtnInactive,
+ navigateToCharacterChatScreen: (Int, String) -> Unit
) {
val checkCharacterChattingLines = remember { mutableStateOf(false) }
val isExpanded = remember { mutableStateOf(false) }
@@ -218,7 +224,6 @@ fun CharacterChat(
animationSpec = tween(durationMillis = 300), label = ""
)
-
Box(
modifier = Modifier
.fillMaxWidth()
@@ -232,6 +237,10 @@ fun CharacterChat(
color = borderColor
)
.padding(vertical = 14.dp, horizontal = 18.dp)
+ .clickableWithoutRipple {
+ // 채팅 로그로 이동
+ navigateToCharacterChatScreen(1, characterName)
+ }
) {
Column {
Row {
@@ -302,9 +311,11 @@ fun CharacterChat(
private fun UsersAdventuresInformation(
isChatting: MutableState,
context: Context,
+ characterName: String,
modifier: Modifier = Modifier,
viewModel: HomeViewModel,
navigateToGainedCharacter: () -> Unit,
+ navigateToCharacterChatScreen: (Int, String) -> Unit
) {
val adventuresInformationState =
viewModel.getUsersAdventuresInformationState.collectAsState(initial = UiState.Loading).value
@@ -333,7 +344,9 @@ private fun UsersAdventuresInformation(
isChatting = isChatting,
context = context,
imageUrl = imageUrl,
+ characterName = characterName,
navigateToGainedCharacter = navigateToGainedCharacter,
+ navigateToCharacterChatScreen = navigateToCharacterChatScreen
)
}
@@ -474,7 +487,8 @@ fun HomeScreenPreview() {
OffroadTheme {
HomeScreen(
//padding = PaddingValues(),
- category = "NONE"
+ category = "NONE",
+ navigateToCharacterChatScreen = { _, _ -> }
)
}
}
diff --git a/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/component/HomeIcons.kt b/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/component/HomeIcons.kt
index 80e01acd..40dbc91e 100644
--- a/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/component/HomeIcons.kt
+++ b/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/component/HomeIcons.kt
@@ -29,7 +29,9 @@ fun HomeIcons(
isChatting: MutableState,
context: Context,
imageUrl: String,
+ characterName: String,
navigateToGainedCharacter: () -> Unit,
+ navigateToCharacterChatScreen: (Int, String) -> Unit
) {
Box(
modifier = Modifier.fillMaxSize()
@@ -44,10 +46,10 @@ fun HomeIcons(
Image(
painter = painterResource(id = R.drawable.ic_home_chat),
contentDescription = "chat",
- //없앨 부분
- modifier = Modifier.clickableWithoutRipple {
- isChatting.value = true
- }
+ modifier = Modifier
+ .clickableWithoutRipple {
+ navigateToCharacterChatScreen(-1, characterName)
+ }
)
Box(
modifier = Modifier.fillMaxWidth(),
diff --git a/feature/main/src/main/java/com/teamoffroad/feature/main/component/MainNavHost.kt b/feature/main/src/main/java/com/teamoffroad/feature/main/component/MainNavHost.kt
index f594d67a..15966659 100644
--- a/feature/main/src/main/java/com/teamoffroad/feature/main/component/MainNavHost.kt
+++ b/feature/main/src/main/java/com/teamoffroad/feature/main/component/MainNavHost.kt
@@ -41,6 +41,9 @@ internal fun MainNavHost(
) {
homeNavGraph(
navigateToBack = navigator::popBackStackIfNotMainTabRoute,
+ navigateToCharacterChatScreen = { id, characterName ->
+ navigator.navigateToCharacterChat(id, characterName)
+ },
navigateToGainedCharacter = {
navigator.navigateToMyPage().also {
navigator.navigateToGainedCharacter()
From 062d3c191e667ae19492afa518f33d359529bf96 Mon Sep 17 00:00:00 2001
From: YuJeongHyun <0703olivia@naver.com>
Date: Tue, 19 Nov 2024 00:06:15 +0900
Subject: [PATCH 28/30] feature: Add animation to character chat
---
.../feature/home/presentation/HomeScreen.kt | 17 +++++++++++++++++
1 file changed, 17 insertions(+)
diff --git a/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeScreen.kt b/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeScreen.kt
index 3fc8040f..716ae4b9 100644
--- a/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeScreen.kt
+++ b/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeScreen.kt
@@ -5,6 +5,7 @@ import android.os.Build
import android.util.Log
import android.widget.Toast
import androidx.annotation.RequiresApi
+import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.tween
import androidx.compose.foundation.Image
@@ -16,6 +17,7 @@ import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
@@ -28,6 +30,7 @@ import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Brush
@@ -73,6 +76,7 @@ import com.teamoffroad.feature.home.presentation.component.quest.progressbar.Rec
import com.teamoffroad.feature.home.presentation.component.user.NicknameText
import com.teamoffroad.feature.home.presentation.model.HomeProgressBarModel
import com.teamoffroad.offroad.feature.home.R
+import kotlinx.coroutines.launch
val homeGradientBackground = Brush.verticalGradient(
colors = listOf(HomeGradi1, HomeGradi2, HomeGradi3, HomeGradi4, HomeGradi5, HomeGradi6)
@@ -173,9 +177,22 @@ fun HomeScreen(
}
if (isCharacterChatting.value) {
+ val offsetY = remember { Animatable(-10.dp.value) }
+ val coroutineScope = rememberCoroutineScope()
+
+ LaunchedEffect(isCharacterChatting) {
+ coroutineScope.launch {
+ offsetY.animateTo(
+ targetValue = 0.dp.value,
+ animationSpec = tween(durationMillis = 500)
+ )
+ }
+ }
+
Box(
contentAlignment = Alignment.TopCenter,
modifier = Modifier
+ .offset(y = offsetY.value.dp)
.padding(start = 24.dp, top = 70.dp, end = 24.dp)
) {
CharacterChat(
From c457f2bf51d36b868591221bc44a051d267f706e Mon Sep 17 00:00:00 2001
From: YuJeongHyun <0703olivia@naver.com>
Date: Tue, 19 Nov 2024 16:54:44 +0900
Subject: [PATCH 29/30] refactor: Edit HomeIcons
---
.../home/presentation/component/HomeIcons.kt | 30 +++++++------------
1 file changed, 11 insertions(+), 19 deletions(-)
diff --git a/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/component/HomeIcons.kt b/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/component/HomeIcons.kt
index acc2322f..916cb5ea 100644
--- a/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/component/HomeIcons.kt
+++ b/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/component/HomeIcons.kt
@@ -1,13 +1,19 @@
package com.teamoffroad.feature.home.presentation.component
+import android.Manifest
import android.content.Context
import android.content.pm.PackageManager
import android.os.Build
+import android.widget.Toast
+import androidx.activity.compose.rememberLauncherForActivityResult
+import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.RequiresApi
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.Image
+import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
@@ -15,6 +21,8 @@ import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.drawscope.Fill
@@ -62,22 +70,7 @@ fun HomeIcons(
}
}
- Box(
- contentAlignment = Alignment.TopEnd,
- modifier = Modifier
- .aspectRatio(48f / 144f)
- .padding(top = 80.dp, end = 20.dp)
- ) {
- Column {
- val characterChatInteractionSource = remember { MutableInteractionSource() }
- Image(
- painter = painterResource(id = R.drawable.ic_home_chat),
- contentDescription = "chat",
- modifier = Modifier
- .clickableWithoutRipple(interactionSource = characterChatInteractionSource) {
-
- }
- )
+ Box(modifier = Modifier.fillMaxSize()) {
Column(
modifier = Modifier
.align(Alignment.TopEnd)
@@ -110,7 +103,6 @@ fun HomeIcons(
}
}
-
val uploadInteractionSource = remember { MutableInteractionSource() }
Image(
painter = painterResource(id = R.drawable.ic_home_upload),
@@ -142,8 +134,8 @@ fun HomeIcons(
}
}
-private suspend fun showToast(context: Context, message: String) {
+suspend fun showToast(context: Context, message: String) {
withContext(Dispatchers.Main) {
Toast.makeText(context, message, Toast.LENGTH_SHORT).show()
}
-}
+}
\ No newline at end of file
From 5b422197a00207c713c7c0153814c826be3455c4 Mon Sep 17 00:00:00 2001
From: YuJeongHyun <0703olivia@naver.com>
Date: Tue, 19 Nov 2024 17:13:41 +0900
Subject: [PATCH 30/30] refactor: HomeScreen
---
.../feature/home/presentation/HomeScreen.kt | 260 ++----------------
...tTextField.kt => HomeUserChatTextField.kt} | 8 +-
.../component/character/CharacterChat.kt | 177 ++++++++++++
.../character/CharacterChatAnimation.kt | 56 ++++
.../presentation/component/user/UserChat.kt | 97 +++++++
5 files changed, 359 insertions(+), 239 deletions(-)
rename feature/home/src/main/java/com/teamoffroad/feature/home/presentation/{HomeChatTextField.kt => HomeUserChatTextField.kt} (96%)
create mode 100644 feature/home/src/main/java/com/teamoffroad/feature/home/presentation/component/character/CharacterChat.kt
create mode 100644 feature/home/src/main/java/com/teamoffroad/feature/home/presentation/component/character/CharacterChatAnimation.kt
create mode 100644 feature/home/src/main/java/com/teamoffroad/feature/home/presentation/component/user/UserChat.kt
diff --git a/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeScreen.kt b/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeScreen.kt
index 716ae4b9..b08a6175 100644
--- a/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeScreen.kt
+++ b/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeScreen.kt
@@ -2,7 +2,6 @@ package com.teamoffroad.feature.home.presentation
import android.content.Context
import android.os.Build
-import android.util.Log
import android.widget.Toast
import androidx.annotation.RequiresApi
import androidx.compose.animation.core.Animatable
@@ -70,10 +69,13 @@ import com.teamoffroad.feature.home.domain.model.UserQuests
import com.teamoffroad.feature.home.presentation.component.CompleteQuestDialog
import com.teamoffroad.feature.home.presentation.component.HomeIcons
import com.teamoffroad.feature.home.presentation.component.UiState
+import com.teamoffroad.feature.home.presentation.component.character.CharacterChat
+import com.teamoffroad.feature.home.presentation.component.character.CharacterChatAnimation
import com.teamoffroad.feature.home.presentation.component.character.CharacterItem
import com.teamoffroad.feature.home.presentation.component.quest.progressbar.CloseCompleteRequest
import com.teamoffroad.feature.home.presentation.component.quest.progressbar.RecentQuest
import com.teamoffroad.feature.home.presentation.component.user.NicknameText
+import com.teamoffroad.feature.home.presentation.component.user.UserChat
import com.teamoffroad.feature.home.presentation.model.HomeProgressBarModel
import com.teamoffroad.offroad.feature.home.R
import kotlinx.coroutines.launch
@@ -93,9 +95,9 @@ fun HomeScreen(
val context = LocalContext.current
val viewModel: HomeViewModel = hiltViewModel()
val isCompleteQuestDialogShown = remember { mutableStateOf(false) }
- val isChatting = remember { mutableStateOf(false) }
- val chattingText = viewModel.chattingText.collectAsStateWithLifecycle()
- val sendMessage = remember { mutableStateOf("") }
+ val isUserChatting = remember { mutableStateOf(false) }
+ val userChattingText = viewModel.chattingText.collectAsStateWithLifecycle()
+ val userSendMessage = remember { mutableStateOf("") }
val characterChat = viewModel.getCharacterChat.collectAsStateWithLifecycle()
val isCharacterChatting = viewModel.isCharacterChatting.collectAsStateWithLifecycle()
val isCharacterChattingLoading =
@@ -120,7 +122,7 @@ fun HomeScreen(
) {
Column(modifier = Modifier.fillMaxSize()) {
UsersAdventuresInformation(
- isChatting = isChatting,
+ isChatting = isUserChatting,
context = context,
characterName = characterName.value,
modifier = Modifier
@@ -135,75 +137,36 @@ fun HomeScreen(
}
- if (isChatting.value) {
+ if (isUserChatting.value) {
Box(
modifier = Modifier
.fillMaxWidth()
.align(Alignment.BottomCenter)
.padding(bottom = 196.dp)
) {
- Column {
- Box(
- modifier = Modifier.fillMaxWidth()
- ) {
- Box(
- modifier = Modifier
- .fillMaxWidth()
- .padding(end = 20.dp),
- contentAlignment = Alignment.CenterEnd
- ) {
- FinishChatting(isChatting)
- }
- }
-
- HomeChatTextField(
- text = chattingText.value,
- sentMessage = sendMessage.value,
- isChatting = isChatting,
- keyboard = true,
- isCharacterChatting = viewModel::updateCharacterChatting,
- onValueChange = { text ->
- viewModel.updateChattingText(text)
- },
- onSendClick = {
- userSendChat.value = true // 사용자가 채팅 보냄
- sendMessage.value = chattingText.value // 보낼 메시지
- viewModel.sendChat() // 서버에 보내기
- viewModel.updateChattingText("") // 초기화
- }
- )
- }
+ UserChat(
+ isChatting = isUserChatting,
+ chattingText = userChattingText,
+ sendMessage = userSendMessage,
+ userSendChat = userSendChat,
+ updateCharacterChatting = viewModel::updateCharacterChatting,
+ updateChattingText = viewModel::updateChattingText,
+ sendChat = viewModel::sendChat
+ )
}
}
- if (isCharacterChatting.value) {
- val offsetY = remember { Animatable(-10.dp.value) }
- val coroutineScope = rememberCoroutineScope()
- LaunchedEffect(isCharacterChatting) {
- coroutineScope.launch {
- offsetY.animateTo(
- targetValue = 0.dp.value,
- animationSpec = tween(durationMillis = 500)
- )
- }
- }
-
- Box(
- contentAlignment = Alignment.TopCenter,
- modifier = Modifier
- .offset(y = offsetY.value.dp)
- .padding(start = 24.dp, top = 70.dp, end = 24.dp)
- ) {
- CharacterChat(
- isChatting = isChatting,
- isCharacterChattingLoading = isCharacterChattingLoading,
- answerCharacterChat = userSendChat,
- characterName = characterName.value,
- characterContent = characterChat.value.characterContent,
- navigateToCharacterChatScreen = navigateToCharacterChatScreen
- )
- }
+ if (isCharacterChatting.value) {
+ CharacterChatAnimation(
+ isCharacterChatting = isCharacterChatting,
+ isChatting = isUserChatting,
+ isCharacterChattingLoading = isCharacterChattingLoading,
+ answerCharacterChat = userSendChat,
+ characterName = characterName.value,
+ characterContent = characterChat.value.characterContent,
+ navigateToCharacterChatScreen = navigateToCharacterChatScreen
+ )
}
}
@@ -211,118 +174,11 @@ fun HomeScreen(
CompleteQuestDialog(
isCompleteQuestDialogShown = isCompleteQuestDialogShown,
completeQuests = completeQuests,
- onClickCancel = {
- isCompleteQuestDialogShown.value = false
- },
+ onClickCancel = { isCompleteQuestDialogShown.value = false },
)
}
}
-@Composable
-fun CharacterChat(
- isChatting: MutableState,
- isCharacterChattingLoading: State,
- answerCharacterChat: MutableState,
- characterName: String,
- characterContent: String,
- characterTextColor: Color = Sub4,
- characterTextStyle: TextStyle = OffroadTheme.typography.textBold,
- messageTextColor: Color = Main2,
- messageTextStyle: TextStyle = OffroadTheme.typography.textRegular,
- backgroundColor: Color = Main3,
- borderColor: Color = BtnInactive,
- navigateToCharacterChatScreen: (Int, String) -> Unit
-) {
- val checkCharacterChattingLines = remember { mutableStateOf(false) }
- val isExpanded = remember { mutableStateOf(false) }
-
- val rotationAngle by animateFloatAsState(
- targetValue = if (isExpanded.value) 180f else 0f,
- animationSpec = tween(durationMillis = 300), label = ""
- )
-
- Box(
- modifier = Modifier
- .fillMaxWidth()
- .background(
- color = backgroundColor,
- shape = RoundedCornerShape(12.dp)
- )
- .border(
- width = 1.dp,
- shape = RoundedCornerShape(12.dp),
- color = borderColor
- )
- .padding(vertical = 14.dp, horizontal = 18.dp)
- .clickableWithoutRipple {
- // 채팅 로그로 이동
- navigateToCharacterChatScreen(1, characterName)
- }
- ) {
- Column {
- Row {
- Text(
- text = "$characterName : ",
- modifier = Modifier,
- color = characterTextColor,
- style = characterTextStyle
- )
-
- if (isCharacterChattingLoading.value) {
- Box(
- modifier = Modifier
- .size(width = 54.dp, height = 27.dp)
- ) {
- val composition by rememberLottieComposition(
- LottieCompositionSpec.RawRes(
- com.teamoffroad.offroad.core.designsystem.R.raw.loading_linear
- )
- )
- val animationState = animateLottieCompositionAsState(
- composition,
- iterations = LottieConstants.IterateForever
- )
-
- if (animationState.isAtEnd && animationState.isPlaying) {
- LaunchedEffect(Unit) { }
- }
-
- LottieAnimation(composition, animationState.progress)
- }
- } else {
- Text(
- text = characterContent,
- modifier = Modifier.weight(1f),
- color = messageTextColor,
- style = messageTextStyle,
- onTextLayout = { textLayoutResult ->
- checkCharacterChattingLines.value = textLayoutResult.lineCount >= 3
- },
- maxLines = if (isExpanded.value) Int.MAX_VALUE else 2,
- overflow = TextOverflow.Ellipsis,
- )
-
- Image(
- painter = painterResource(id = R.drawable.ic_home_accordion),
- contentDescription = "accordion down",
- modifier = Modifier
- .graphicsLayer(rotationX = rotationAngle)
- .clickableWithoutRipple {
- isExpanded.value = !isExpanded.value
- }
- )
- }
-
- }
-
- if (!answerCharacterChat.value) {
- AnswerCharacterChat(isChatting = isChatting)
- }
- }
-
- }
-}
-
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
@Composable
private fun UsersAdventuresInformation(
@@ -384,66 +240,6 @@ private fun UsersAdventuresInformation(
CharacterItem().EmblemNameText(context, Modifier)
}
-@Composable
-fun FinishChatting(
- isChatting: MutableState,
- backgroundColor: Color = Sub55,
- borderColor: Color = Sub
-) {
- Text(
- style = OffroadTheme.typography.subtitle2Semibold,
- text = stringResource(id = R.string.home_chat_finish),
- modifier = Modifier
- .padding(bottom = 8.dp)
- .background(
- color = backgroundColor,
- shape = RoundedCornerShape(20.dp)
- )
- .border(
- width = 1.dp,
- shape = RoundedCornerShape(20.dp),
- color = borderColor
- )
- .padding(horizontal = 16.dp)
- .padding(vertical = 8.dp)
- .clickableWithoutRipple {
- isChatting.value = false
- },
- color = White
- )
-}
-
-@Composable
-fun AnswerCharacterChat(
- isChatting: MutableState,
- backgroundColor: Color = Main2,
- textColor: Color = Main3,
- textStyle: TextStyle = OffroadTheme.typography.textContents
-) {
- Box(
- modifier = Modifier
- .fillMaxWidth()
- .padding(top = 10.dp)
- ) {
- Box(contentAlignment = Alignment.CenterEnd, modifier = Modifier.fillMaxWidth()) {
- Text(
- text = "답장하기",
- modifier = Modifier
- .background(
- color = backgroundColor,
- shape = RoundedCornerShape(8.dp)
- )
- .padding(horizontal = 14.dp, vertical = 6.dp)
- .clickableWithoutRipple {
- isChatting.value = true
- },
- color = textColor,
- style = textStyle
- )
- }
- }
-}
-
@Composable
private fun UsersQuestInformation(
context: Context,
diff --git a/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeChatTextField.kt b/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeUserChatTextField.kt
similarity index 96%
rename from feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeChatTextField.kt
rename to feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeUserChatTextField.kt
index 28ca8e83..89885837 100644
--- a/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeChatTextField.kt
+++ b/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/HomeUserChatTextField.kt
@@ -8,9 +8,6 @@ import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.aspectRatio
-import androidx.compose.foundation.layout.fillMaxHeight
-import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
@@ -27,7 +24,6 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState
-import androidx.compose.runtime.State
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
@@ -53,8 +49,6 @@ import com.airbnb.lottie.compose.animateLottieCompositionAsState
import com.airbnb.lottie.compose.rememberLottieComposition
import com.teamoffroad.core.designsystem.component.clickableWithoutRipple
import com.teamoffroad.core.designsystem.theme.BtnInactive
-import com.teamoffroad.core.designsystem.theme.ErrorNew
-import com.teamoffroad.core.designsystem.theme.Kakao
import com.teamoffroad.core.designsystem.theme.Main2
import com.teamoffroad.core.designsystem.theme.OffroadTheme
import com.teamoffroad.core.designsystem.theme.Transparent
@@ -63,7 +57,7 @@ import com.teamoffroad.offroad.feature.home.R
@OptIn(ExperimentalMaterial3Api::class)
@Composable
-fun HomeChatTextField(
+fun HomeUserChatTextField(
modifier: Modifier = Modifier,
text: String = "",
sentMessage: String,
diff --git a/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/component/character/CharacterChat.kt b/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/component/character/CharacterChat.kt
new file mode 100644
index 00000000..19e83981
--- /dev/null
+++ b/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/component/character/CharacterChat.kt
@@ -0,0 +1,177 @@
+package com.teamoffroad.feature.home.presentation.component.character
+
+import androidx.compose.animation.core.animateFloatAsState
+import androidx.compose.animation.core.tween
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.State
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.unit.dp
+import com.airbnb.lottie.compose.LottieAnimation
+import com.airbnb.lottie.compose.LottieCompositionSpec
+import com.airbnb.lottie.compose.LottieConstants
+import com.airbnb.lottie.compose.animateLottieCompositionAsState
+import com.airbnb.lottie.compose.rememberLottieComposition
+import com.teamoffroad.core.designsystem.component.clickableWithoutRipple
+import com.teamoffroad.core.designsystem.theme.BtnInactive
+import com.teamoffroad.core.designsystem.theme.Main2
+import com.teamoffroad.core.designsystem.theme.Main3
+import com.teamoffroad.core.designsystem.theme.OffroadTheme
+import com.teamoffroad.core.designsystem.theme.Sub4
+import com.teamoffroad.offroad.feature.home.R
+
+@Composable
+fun CharacterChat(
+ isChatting: MutableState,
+ isCharacterChattingLoading: State,
+ answerCharacterChat: MutableState,
+ characterName: String,
+ characterContent: String,
+ characterTextColor: Color = Sub4,
+ characterTextStyle: TextStyle = OffroadTheme.typography.textBold,
+ messageTextColor: Color = Main2,
+ messageTextStyle: TextStyle = OffroadTheme.typography.textRegular,
+ backgroundColor: Color = Main3,
+ borderColor: Color = BtnInactive,
+ navigateToCharacterChatScreen: (Int, String) -> Unit
+) {
+ val checkCharacterChattingLines = remember { mutableStateOf(false) }
+ val isExpanded = remember { mutableStateOf(false) }
+
+ val rotationAngle by animateFloatAsState(
+ targetValue = if (isExpanded.value) 180f else 0f,
+ animationSpec = tween(durationMillis = 300), label = ""
+ )
+
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .background(
+ color = backgroundColor,
+ shape = RoundedCornerShape(12.dp)
+ )
+ .border(
+ width = 1.dp,
+ shape = RoundedCornerShape(12.dp),
+ color = borderColor
+ )
+ .padding(vertical = 14.dp, horizontal = 18.dp)
+ .clickableWithoutRipple {
+ navigateToCharacterChatScreen(-1, characterName)
+ }
+ ) {
+ Column {
+ Row {
+ Text(
+ text = "$characterName : ",
+ modifier = Modifier,
+ color = characterTextColor,
+ style = characterTextStyle
+ )
+
+ if (isCharacterChattingLoading.value) {
+ Box(
+ modifier = Modifier
+ .size(width = 54.dp, height = 27.dp)
+ ) {
+ val composition by rememberLottieComposition(
+ LottieCompositionSpec.RawRes(
+ com.teamoffroad.offroad.core.designsystem.R.raw.loading_linear
+ )
+ )
+ val animationState = animateLottieCompositionAsState(
+ composition,
+ iterations = LottieConstants.IterateForever
+ )
+
+ if (animationState.isAtEnd && animationState.isPlaying) {
+ LaunchedEffect(Unit) { }
+ }
+
+ LottieAnimation(composition, animationState.progress)
+ }
+ } else {
+ Text(
+ text = characterContent,
+ modifier = Modifier.weight(1f),
+ color = messageTextColor,
+ style = messageTextStyle,
+ onTextLayout = { textLayoutResult ->
+ checkCharacterChattingLines.value = textLayoutResult.lineCount >= 3
+ },
+ maxLines = if (isExpanded.value) Int.MAX_VALUE else 2,
+ overflow = TextOverflow.Ellipsis,
+ )
+
+ Image(
+ painter = painterResource(id = R.drawable.ic_home_accordion),
+ contentDescription = "accordion down",
+ modifier = Modifier
+ .graphicsLayer(rotationX = rotationAngle)
+ .clickableWithoutRipple {
+ isExpanded.value = !isExpanded.value
+ }
+ )
+ }
+
+ }
+
+ if (!answerCharacterChat.value) {
+ AnswerCharacterChat(isChatting = isChatting)
+ }
+ }
+
+ }
+}
+
+@Composable
+fun AnswerCharacterChat(
+ isChatting: MutableState,
+ backgroundColor: Color = Main2,
+ textColor: Color = Main3,
+ textStyle: TextStyle = OffroadTheme.typography.textContents
+) {
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(top = 10.dp)
+ ) {
+ Box(contentAlignment = Alignment.CenterEnd, modifier = Modifier.fillMaxWidth()) {
+ Text(
+ text = "답장하기",
+ modifier = Modifier
+ .background(
+ color = backgroundColor,
+ shape = RoundedCornerShape(8.dp)
+ )
+ .padding(horizontal = 14.dp, vertical = 6.dp)
+ .clickableWithoutRipple {
+ isChatting.value = true
+ },
+ color = textColor,
+ style = textStyle
+ )
+ }
+ }
+}
\ No newline at end of file
diff --git a/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/component/character/CharacterChatAnimation.kt b/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/component/character/CharacterChatAnimation.kt
new file mode 100644
index 00000000..f335a71a
--- /dev/null
+++ b/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/component/character/CharacterChatAnimation.kt
@@ -0,0 +1,56 @@
+package com.teamoffroad.feature.home.presentation.component.character
+
+import androidx.compose.animation.core.Animatable
+import androidx.compose.animation.core.tween
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.offset
+import androidx.compose.foundation.layout.padding
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.State
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import kotlinx.coroutines.launch
+
+@Composable
+fun CharacterChatAnimation(
+ isCharacterChatting: State,
+ isChatting: MutableState,
+ isCharacterChattingLoading: State,
+ answerCharacterChat: MutableState,
+ characterName: String,
+ characterContent: String,
+ navigateToCharacterChatScreen: (Int, String) -> Unit
+) {
+ val offsetY = remember { Animatable(-10.dp.value) }
+ val coroutineScope = rememberCoroutineScope()
+
+ LaunchedEffect(isCharacterChatting) {
+ coroutineScope.launch {
+ offsetY.animateTo(
+ targetValue = 0.dp.value,
+ animationSpec = tween(durationMillis = 500)
+ )
+ }
+ }
+
+ Box(
+ contentAlignment = Alignment.TopCenter,
+ modifier = Modifier
+ .offset(y = offsetY.value.dp)
+ .padding(start = 24.dp, top = 70.dp, end = 24.dp)
+ ) {
+ CharacterChat(
+ isChatting = isChatting,
+ isCharacterChattingLoading = isCharacterChattingLoading,
+ answerCharacterChat = answerCharacterChat,
+ characterName = characterName,
+ characterContent = characterContent,
+ navigateToCharacterChatScreen = navigateToCharacterChatScreen
+ )
+ }
+}
\ No newline at end of file
diff --git a/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/component/user/UserChat.kt b/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/component/user/UserChat.kt
new file mode 100644
index 00000000..50a257a3
--- /dev/null
+++ b/feature/home/src/main/java/com/teamoffroad/feature/home/presentation/component/user/UserChat.kt
@@ -0,0 +1,97 @@
+package com.teamoffroad.feature.home.presentation.component.user
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.State
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.unit.dp
+import com.teamoffroad.core.designsystem.component.clickableWithoutRipple
+import com.teamoffroad.core.designsystem.theme.OffroadTheme
+import com.teamoffroad.core.designsystem.theme.Sub
+import com.teamoffroad.core.designsystem.theme.Sub55
+import com.teamoffroad.core.designsystem.theme.White
+import com.teamoffroad.feature.home.presentation.HomeUserChatTextField
+import com.teamoffroad.offroad.feature.home.R
+
+@Composable
+fun UserChat(
+ isChatting: MutableState,
+ chattingText: State,
+ sendMessage: MutableState,
+ userSendChat: MutableState,
+ updateCharacterChatting: (Boolean) -> Unit,
+ updateChattingText: (String) -> Unit,
+ sendChat: () -> Unit,
+) {
+ Column {
+ Box(
+ modifier = Modifier.fillMaxWidth()
+ ) {
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(end = 20.dp),
+ contentAlignment = Alignment.CenterEnd
+ ) {
+ FinishChatting(isChatting)
+ }
+ }
+
+ HomeUserChatTextField(
+ text = chattingText.value,
+ sentMessage = sendMessage.value,
+ isChatting = isChatting,
+ keyboard = true,
+ isCharacterChatting = updateCharacterChatting,
+ onValueChange = { text ->
+ updateChattingText(text)
+ },
+ onSendClick = {
+ userSendChat.value = true // 사용자가 채팅 보냄
+ sendMessage.value = chattingText.value // 보낼 메시지
+ sendChat() // 서버에 보내기
+ updateChattingText("") // 초기화
+ }
+ )
+ }
+}
+
+@Composable
+fun FinishChatting(
+ isChatting: MutableState,
+ backgroundColor: Color = Sub55,
+ borderColor: Color = Sub
+) {
+ Text(
+ style = OffroadTheme.typography.subtitle2Semibold,
+ text = stringResource(id = R.string.home_chat_finish),
+ modifier = Modifier
+ .padding(bottom = 8.dp)
+ .background(
+ color = backgroundColor,
+ shape = RoundedCornerShape(20.dp)
+ )
+ .border(
+ width = 1.dp,
+ shape = RoundedCornerShape(20.dp),
+ color = borderColor
+ )
+ .padding(horizontal = 16.dp)
+ .padding(vertical = 8.dp)
+ .clickableWithoutRipple {
+ isChatting.value = false
+ },
+ color = White
+ )
+}
\ No newline at end of file