Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature]#72 홈 화면의 복사한 링크 저장하기 기능 구현 #75

Merged
merged 5 commits into from
Oct 1, 2024
22 changes: 22 additions & 0 deletions app/src/main/java/pokitmons/pokit/MainActivity.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package pokitmons.pokit

import android.content.ClipboardManager
import android.os.Build
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
Expand All @@ -22,6 +24,7 @@ import androidx.navigation.compose.rememberNavController
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.delay
import pokitmons.pokit.core.ui.theme.PokitTheme
import pokitmons.pokit.home.model.ClipboardLinkManager
import pokitmons.pokit.navigation.RootNavHost

@AndroidEntryPoint
Expand Down Expand Up @@ -55,6 +58,25 @@ class MainActivity : ComponentActivity() {
}
}
}

override fun onWindowFocusChanged(hasFocus: Boolean) {
super.onWindowFocusChanged(hasFocus)

if (hasFocus) {
val clipboardManager = getSystemService(CLIPBOARD_SERVICE) as ClipboardManager
clipboardManager.primaryClip?.let { clipData ->
if (clipData.itemCount == 0) return@let
val clipboardTextData = clipData.getItemAt(0).text.toString()

if (!ClipboardLinkManager.checkUrlIsValid(clipboardTextData)) return@let

ClipboardLinkManager.setClipboardLink(clipboardTextData)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
clipboardManager.clearPrimaryClip()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

여기서 현재 클립된 데이터를 지우는 이유가 있나영?.?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

클립보드 복사 -> 홈 화면 진입 -> 링크 추가 화면 진입 및 링크 추가 -> 다시 홈 화면 복귀 -> 홈 버튼 누른 후 앱 재진입
위 케이스에서 이미 저장한 클립 데이터가 남아있어서 다시 toast 메세지가 표시되는 현상이 있었어!

그래서 복사된 링크를 한번 설정하면 해당 링크로는 다시 toast 메세지가 발생하지 않게끔 하려는게 주 목적이었어

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아하 요건 그러면 파이 이상에서만 발생하는 건가?.?

Copy link
Contributor Author

@l5x5l l5x5l Sep 30, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

어 그러네 파이 미만부분이 아예 처리가 안되어있었구나
수정완!

}
}
}
}
}

@Composable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,16 @@ object Home {
object AddLink {
val route: String = "addLink"
val linkIdArg = "link_id"
val routeWithArgs = "$route?$linkIdArg={$linkIdArg}"
val linkUrl = "link_url"
val routeWithArgs = "$route?$linkIdArg={$linkIdArg}&$linkUrl={$linkUrl}"
var arguments = listOf(
navArgument(linkIdArg) {
nullable = true
type = NavType.StringType
},
navArgument(linkUrl) {
nullable = true
type = NavType.StringType
}
)
}
Expand Down
8 changes: 1 addition & 7 deletions app/src/main/java/pokitmons/pokit/navigation/RootNavHost.kt
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package pokitmons.pokit.navigation

import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
Expand Down Expand Up @@ -93,10 +91,6 @@ fun RootNavHost(
)
}

composable(Home.route) {
Box(modifier = Modifier.fillMaxSize())
}

composable(
route = AddLink.routeWithArgs,
arguments = AddLink.arguments
Expand Down Expand Up @@ -187,7 +181,7 @@ fun RootNavHost(
"${PokitDetail.route}/$pokitId?${PokitDetail.pokitCountQuery}=$linkCount"
)
},
onNavigateAddLink = { navHostController.navigate(AddLink.route) },
onNavigateAddLink = { navHostController.navigate("${AddLink.route}?${AddLink.linkUrl}=$it") },
onNavigateAddPokit = { navHostController.navigate(AddPokit.route) },
onNavigateToLinkModify = { navHostController.navigate("${AddLink.route}?${AddLink.linkIdArg}=$it") },
onNavigateToPokitModify = { navHostController.navigate("${AddPokit.route}?${AddPokit.pokitIdArg}=$it") },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import pokitmons.pokit.core.ui.R
import pokitmons.pokit.core.ui.theme.PokitTheme
Expand All @@ -41,6 +42,8 @@ fun PokitToast(
Text(
text = text,
style = PokitTheme.typography.body3Medium.copy(color = PokitTheme.colors.inverseWh),
maxLines = 2,
overflow = TextOverflow.Ellipsis,
modifier = Modifier.weight(1f)
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ class AddLinkViewModel @Inject constructor(
val memo: StateFlow<String> = _memo.asStateFlow()

val currentLinkId: Int? = savedStateHandle.get<String>("link_id")?.toIntOrNull()
private val copiedLinkUrl: String? = savedStateHandle.get<String>("link_url")

// 수정 이전 pokit과 수정 이후 pokit이 다른 경우를 체크하기 위해서만 사용
private var prevPokitId: Int? = null
Expand All @@ -97,6 +98,10 @@ class AddLinkViewModel @Inject constructor(
} else {
loadUncategorizedPokit()
}

copiedLinkUrl?.let { url ->
inputLinkUrl(url)
}
}

private fun initPokitAddEventDetector() {
Expand Down
22 changes: 19 additions & 3 deletions feature/home/src/main/java/pokitmons/pokit/home/HomeScreen.kt
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ fun HomeScreen(
onNavigateToPokitDetail: (String, Int) -> Unit,
onNavigateToSearch: () -> Unit,
onNavigateToSetting: () -> Unit,
onNavigateAddLink: () -> Unit,
onNavigateAddLink: (String?) -> Unit,
onNavigateAddPokit: () -> Unit,
onNavigateToLinkModify: (String) -> Unit,
onNavigateToPokitModify: (String) -> Unit,
Expand All @@ -63,6 +63,7 @@ fun HomeScreen(
var showBottomSheet by remember { mutableStateOf(false) }

val toastMessage by viewModel.toastMessage.collectAsState()
val copiedLinkToastMessage by viewModel.copiedLinkUrl.collectAsState()

viewModel.sideEffect.collectAsEffect { homeSideEffect: HomeSideEffect ->
when (homeSideEffect) {
Expand Down Expand Up @@ -102,7 +103,7 @@ fun HomeScreen(
scope.launch {
sheetState.hide()
showBottomSheet = false
onNavigateAddLink()
onNavigateAddLink(null)
}
},
verticalArrangement = Arrangement.Center,
Expand Down Expand Up @@ -171,7 +172,7 @@ fun HomeScreen(
onNavigateToAlarm = onNavigateToAlarm
)
Scaffold(
bottomBar = { BottomNavigationBar() }
bottomBar = { BottomNavigationBar(viewModel) }
) { padding ->
Box {
when (viewModel.screenType.value) {
Expand Down Expand Up @@ -203,6 +204,21 @@ fun HomeScreen(
onClickClose = viewModel::closeToastMessage
)
}

copiedLinkToastMessage?.linkUrl?.let { linkUrl ->
PokitToast(
modifier = Modifier
.align(Alignment.BottomCenter)
.padding(padding)
.padding(start = 12.dp, end = 12.dp, bottom = 48.dp),
text = stringResource(id = pokitmons.pokit.home.R.string.toast_add_copied_link, linkUrl),
onClickClose = viewModel::closeLinkAddToastMessage,
onClick = {
viewModel.closeLinkAddToastMessage()
onNavigateAddLink(linkUrl)
}
)
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package pokitmons.pokit.home.model

import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import pokitmons.pokit.core.feature.flow.EventFlow
import pokitmons.pokit.core.feature.flow.MutableEventFlow
import pokitmons.pokit.core.feature.flow.asEventFlow
import java.util.Locale
import java.util.regex.Pattern

object ClipboardLinkManager {
private val _clipboardLinkUrl: MutableEventFlow<String> = MutableEventFlow()
val clipboardLinkUrl: EventFlow<String> = _clipboardLinkUrl.asEventFlow()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EventFlow가 뭔가 전역적으로 처리하는 그런 기능인가여?

Copy link
Contributor Author

@l5x5l l5x5l Sep 29, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

내부적으로 SharedFlow를 가지고 있는 Flow 구현체인데,
한번 collect된 데이터를 중복해서 collect하지 않도록 살짝 수정한 클래스로 생각하면 될듯?
아래 링크에서 6번째 항목 부분 참고하면 될 것 같아
MVVM의 ViewModel에서 이벤트를 처리하는 방법 6가지

SharedFlow 대신 EventFlow를 쓴 이유는 복사한 링크 이벤트에 대해서 막연하게 중복으로 이벤트가 collect되면 안되다는 생각이었는데, 다시 살펴보니까 이 부분에서는 EventFlow 말고 SharedFlow 사용해도 문제 없을 둣!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

음 그러면 StateFlow도 사용해도 될 것 같은데, Flow마다 처리 방법을 좀 더 봐야겠담


fun setClipboardLink(linkUrl: String) {
CoroutineScope(Dispatchers.IO).launch {
_clipboardLinkUrl.emit(linkUrl)
}
}

fun checkUrlIsValid(url: String): Boolean {
val urlPattern = (
"^(http://|https://)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}" +
"(:[0-9]{1,5})?(/.*)?$"
)

val pattern = Pattern.compile(urlPattern)
val matcher = pattern.matcher(url.lowercase(Locale.getDefault()))

return matcher.matches()
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

url 확인정도면 정규식 말구 startWith("http") or startWith("https")로 대체 가능할 것 같은뎁?
의견 궁금함미다 꼭 해당 정규식을 사용해야하는 케이스가 있을까?.?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 부분은 그냥 url이니까 정규식 사용해야지~ 하고 크게 고민없이 작성했는데
생각해보니 형 말대로 더 간단하게 대체할 수 있겠다

"http_asdfjklasjdklfjsalkdf" 이런 문자 복사하고 들어오면 이것도 링크로 인식할 수는 있겠지만,
사용자가 이런 문자를 복사하고 이 앱에 진입하는 경우는 거의 없을 것 같아!

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package pokitmons.pokit.home.model

data class LinkAddToastMessage(val linkUrl: String)
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,10 @@ import pokitmons.pokit.domain.usecase.link.SetBookmarkUseCase
import pokitmons.pokit.domain.usecase.pokit.DeletePokitUseCase
import pokitmons.pokit.domain.usecase.pokit.GetPokitCountUseCase
import pokitmons.pokit.domain.usecase.pokit.GetPokitsUseCase
import pokitmons.pokit.home.model.ClipboardLinkManager
import pokitmons.pokit.home.model.HomeSideEffect
import pokitmons.pokit.home.model.HomeToastMessage
import pokitmons.pokit.home.model.LinkAddToastMessage
import javax.inject.Inject
import kotlin.math.max
import com.strayalpaca.pokitdetail.model.Link as DetailLink
Expand All @@ -54,6 +56,9 @@ class PokitViewModel @Inject constructor(
private val _toastMessage = MutableStateFlow<HomeToastMessage?>(null)
val toastMessage = _toastMessage.asStateFlow()

private val _copiedLinkUrlToastMessage = MutableStateFlow<LinkAddToastMessage?>(null)
val copiedLinkUrl = _copiedLinkUrlToastMessage.asStateFlow()

private fun initLinkUpdateEventDetector() {
viewModelScope.launch {
LinkUpdateEvent.updatedLink.collectLatest { updatedLink ->
Expand Down Expand Up @@ -94,6 +99,14 @@ class PokitViewModel @Inject constructor(
}
}

private fun initClipboardLinkUrlDetector() {
viewModelScope.launch {
ClipboardLinkManager.clipboardLinkUrl.collectLatest { linkUrl ->
_copiedLinkUrlToastMessage.update { LinkAddToastMessage(linkUrl) }
}
}
}

private fun initPokitUpdateEventDetector() {
viewModelScope.launch {
PokitUpdateEvent.updatedPokit.collectLatest { updatedPokit ->
Expand Down Expand Up @@ -225,6 +238,7 @@ class PokitViewModel @Inject constructor(
initLinkAddEventDetector()
initPokitAddEventDetector()
initLinkRemoveEventDetector()
initClipboardLinkUrlDetector()

loadUnCategoryLinks()
loadPokits()
Expand Down Expand Up @@ -309,6 +323,10 @@ class PokitViewModel @Inject constructor(
_toastMessage.update { null }
}

fun closeLinkAddToastMessage() {
_copiedLinkUrlToastMessage.update { null }
}

fun showLinkOptionBottomSheet(link: DetailLink) {
_linkOptionBottomSheetType.update { BottomSheetType.MODIFY }
_currentSelectedLink.update { link }
Expand Down
1 change: 1 addition & 0 deletions feature/home/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@
<string name="app_name">home</string>

<string name="toast_cannot_create_pokit">최대 30개의 포킷을 생성할 수 있습니다.\n포킷을 삭제한 뒤에 추가해주세요.</string>
<string name="toast_add_copied_link">복사한 링크 저장하기\n%s</string>
</resources>
25 changes: 14 additions & 11 deletions feature/login/src/main/java/pokitmons/pokit/login/LoginScreen.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.rememberCoroutineScope
Expand Down Expand Up @@ -47,18 +48,20 @@ fun LoginScreen(
val coroutineScope = rememberCoroutineScope()

// TODO 리팩토링
when (loginState) {
is LoginState.Init -> Unit
is LoginState.Login -> {
loginViewModel.changeState()
onNavigateToTermsOfServiceScreen()
LaunchedEffect(loginState) {
when (loginState) {
is LoginState.Init -> Unit
is LoginState.Login -> {
loginViewModel.changeState()
onNavigateToTermsOfServiceScreen()
}
is LoginState.Registered -> {
loginViewModel.changeState()
onNavigateToHomeScreen()
}
is LoginState.Failed -> loginViewModel.setVisible(true)
is LoginState.AutoLogin -> onNavigateToHomeScreen()
}
is LoginState.Registered -> {
loginViewModel.changeState()
onNavigateToHomeScreen()
}
is LoginState.Failed -> loginViewModel.setVisible(true)
is LoginState.AutoLogin -> onNavigateToHomeScreen()
}

Box(
Expand Down
Loading