From 63630e99a92b321cdc42ffe6c78caa1eedccb902 Mon Sep 17 00:00:00 2001 From: Sehwan Yun <39579912+l5x5l@users.noreply.github.com> Date: Tue, 1 Oct 2024 18:23:57 +0900 Subject: [PATCH] =?UTF-8?q?[Feature]#72=20=ED=99=88=20=ED=99=94=EB=A9=B4?= =?UTF-8?q?=EC=9D=98=20=EB=B3=B5=EC=82=AC=ED=95=9C=20=EB=A7=81=ED=81=AC=20?= =?UTF-8?q?=EC=A0=80=EC=9E=A5=ED=95=98=EA=B8=B0=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84=20(#75)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [FEATURE] 링크 복사 후 홈 화면 진입시 복사한 링크 추가 화면으로 이동하는 toast 표시 기능 구현 및 PokitToast의 최대 line수를 2로 설정 * [FIX] 로그인 화면에서 자동로그인 이벤트 발생시 화면 이동 이벤트가 2번 수행되는 문제 수정 * [FEATURE] 홈 화면에서 복사된 링크 추가 토스트를 통해 링크 추가화면 진입시, 해당 링크 url로 설정하는 기능 구현 * [CHORE] ktlint 적용 * [FIX] 코드리뷰 내용 반영 - url링크 유효성 검증시 조건 단순화 - SDK 27 이하일 때 클립보드 초기화 누락 수정 --- .../main/java/pokitmons/pokit/MainActivity.kt | 26 +++++++++++++++++++ .../pokit/navigation/RootDestination.kt | 7 ++++- .../pokitmons/pokit/navigation/RootNavHost.kt | 8 +----- .../components/block/pokittoast/PokitToast.kt | 3 +++ .../strayalpaca/addlink/AddLinkViewModel.kt | 5 ++++ .../java/pokitmons/pokit/home/HomeScreen.kt | 22 +++++++++++++--- .../pokit/home/model/ClipboardLinkManager.kt | 24 +++++++++++++++++ .../pokit/home/model/LinkAddToastMessage.kt | 3 +++ .../pokit/home/pokit/PokitViewModel.kt | 18 +++++++++++++ feature/home/src/main/res/values/strings.xml | 1 + .../java/pokitmons/pokit/login/LoginScreen.kt | 25 ++++++++++-------- 11 files changed, 120 insertions(+), 22 deletions(-) create mode 100644 feature/home/src/main/java/pokitmons/pokit/home/model/ClipboardLinkManager.kt create mode 100644 feature/home/src/main/java/pokitmons/pokit/home/model/LinkAddToastMessage.kt diff --git a/app/src/main/java/pokitmons/pokit/MainActivity.kt b/app/src/main/java/pokitmons/pokit/MainActivity.kt index 8bf0ce26..3c78fe92 100644 --- a/app/src/main/java/pokitmons/pokit/MainActivity.kt +++ b/app/src/main/java/pokitmons/pokit/MainActivity.kt @@ -1,5 +1,8 @@ package pokitmons.pokit +import android.content.ClipData +import android.content.ClipboardManager +import android.os.Build import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent @@ -22,6 +25,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 @@ -55,6 +59,28 @@ 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() + } else { + val emptyClip = ClipData.newPlainText("", "") + clipboardManager.setPrimaryClip(emptyClip) + } + } + } + } } @Composable diff --git a/app/src/main/java/pokitmons/pokit/navigation/RootDestination.kt b/app/src/main/java/pokitmons/pokit/navigation/RootDestination.kt index aa7f1bb6..f2b1a432 100644 --- a/app/src/main/java/pokitmons/pokit/navigation/RootDestination.kt +++ b/app/src/main/java/pokitmons/pokit/navigation/RootDestination.kt @@ -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 } ) } diff --git a/app/src/main/java/pokitmons/pokit/navigation/RootNavHost.kt b/app/src/main/java/pokitmons/pokit/navigation/RootNavHost.kt index 238f3b8a..3ea5bfed 100644 --- a/app/src/main/java/pokitmons/pokit/navigation/RootNavHost.kt +++ b/app/src/main/java/pokitmons/pokit/navigation/RootNavHost.kt @@ -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 @@ -93,10 +91,6 @@ fun RootNavHost( ) } - composable(Home.route) { - Box(modifier = Modifier.fillMaxSize()) - } - composable( route = AddLink.routeWithArgs, arguments = AddLink.arguments @@ -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") }, diff --git a/core/ui/src/main/java/pokitmons/pokit/core/ui/components/block/pokittoast/PokitToast.kt b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/block/pokittoast/PokitToast.kt index 494115cf..550a4009 100644 --- a/core/ui/src/main/java/pokitmons/pokit/core/ui/components/block/pokittoast/PokitToast.kt +++ b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/block/pokittoast/PokitToast.kt @@ -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 @@ -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) ) diff --git a/feature/addlink/src/main/java/com/strayalpaca/addlink/AddLinkViewModel.kt b/feature/addlink/src/main/java/com/strayalpaca/addlink/AddLinkViewModel.kt index 327c3395..fc031bdb 100644 --- a/feature/addlink/src/main/java/com/strayalpaca/addlink/AddLinkViewModel.kt +++ b/feature/addlink/src/main/java/com/strayalpaca/addlink/AddLinkViewModel.kt @@ -85,6 +85,7 @@ class AddLinkViewModel @Inject constructor( val memo: StateFlow = _memo.asStateFlow() val currentLinkId: Int? = savedStateHandle.get("link_id")?.toIntOrNull() + private val copiedLinkUrl: String? = savedStateHandle.get("link_url") // 수정 이전 pokit과 수정 이후 pokit이 다른 경우를 체크하기 위해서만 사용 private var prevPokitId: Int? = null @@ -97,6 +98,10 @@ class AddLinkViewModel @Inject constructor( } else { loadUncategorizedPokit() } + + copiedLinkUrl?.let { url -> + inputLinkUrl(url) + } } private fun initPokitAddEventDetector() { diff --git a/feature/home/src/main/java/pokitmons/pokit/home/HomeScreen.kt b/feature/home/src/main/java/pokitmons/pokit/home/HomeScreen.kt index ab29eac8..bfa97b10 100644 --- a/feature/home/src/main/java/pokitmons/pokit/home/HomeScreen.kt +++ b/feature/home/src/main/java/pokitmons/pokit/home/HomeScreen.kt @@ -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, @@ -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) { @@ -102,7 +103,7 @@ fun HomeScreen( scope.launch { sheetState.hide() showBottomSheet = false - onNavigateAddLink() + onNavigateAddLink(null) } }, verticalArrangement = Arrangement.Center, @@ -171,7 +172,7 @@ fun HomeScreen( onNavigateToAlarm = onNavigateToAlarm ) Scaffold( - bottomBar = { BottomNavigationBar() } + bottomBar = { BottomNavigationBar(viewModel) } ) { padding -> Box { when (viewModel.screenType.value) { @@ -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) + } + ) + } } } } diff --git a/feature/home/src/main/java/pokitmons/pokit/home/model/ClipboardLinkManager.kt b/feature/home/src/main/java/pokitmons/pokit/home/model/ClipboardLinkManager.kt new file mode 100644 index 00000000..e53f1ff5 --- /dev/null +++ b/feature/home/src/main/java/pokitmons/pokit/home/model/ClipboardLinkManager.kt @@ -0,0 +1,24 @@ +package pokitmons.pokit.home.model + +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.SharedFlow +import kotlinx.coroutines.flow.asSharedFlow +import kotlinx.coroutines.launch + +object ClipboardLinkManager { + private val _clipboardLinkUrl: MutableSharedFlow = MutableSharedFlow() + val clipboardLinkUrl: SharedFlow = _clipboardLinkUrl.asSharedFlow() + + fun setClipboardLink(linkUrl: String) { + CoroutineScope(Dispatchers.IO).launch { + _clipboardLinkUrl.emit(linkUrl) + } + } + + fun checkUrlIsValid(url: String): Boolean { + val isValidUrl = url.startsWith("http://") || url.startsWith("https://") + return isValidUrl + } +} diff --git a/feature/home/src/main/java/pokitmons/pokit/home/model/LinkAddToastMessage.kt b/feature/home/src/main/java/pokitmons/pokit/home/model/LinkAddToastMessage.kt new file mode 100644 index 00000000..723296a9 --- /dev/null +++ b/feature/home/src/main/java/pokitmons/pokit/home/model/LinkAddToastMessage.kt @@ -0,0 +1,3 @@ +package pokitmons.pokit.home.model + +data class LinkAddToastMessage(val linkUrl: String) diff --git a/feature/home/src/main/java/pokitmons/pokit/home/pokit/PokitViewModel.kt b/feature/home/src/main/java/pokitmons/pokit/home/pokit/PokitViewModel.kt index dfe94ae6..4da93d56 100644 --- a/feature/home/src/main/java/pokitmons/pokit/home/pokit/PokitViewModel.kt +++ b/feature/home/src/main/java/pokitmons/pokit/home/pokit/PokitViewModel.kt @@ -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 @@ -54,6 +56,9 @@ class PokitViewModel @Inject constructor( private val _toastMessage = MutableStateFlow(null) val toastMessage = _toastMessage.asStateFlow() + private val _copiedLinkUrlToastMessage = MutableStateFlow(null) + val copiedLinkUrl = _copiedLinkUrlToastMessage.asStateFlow() + private fun initLinkUpdateEventDetector() { viewModelScope.launch { LinkUpdateEvent.updatedLink.collectLatest { updatedLink -> @@ -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 -> @@ -225,6 +238,7 @@ class PokitViewModel @Inject constructor( initLinkAddEventDetector() initPokitAddEventDetector() initLinkRemoveEventDetector() + initClipboardLinkUrlDetector() loadUnCategoryLinks() loadPokits() @@ -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 } diff --git a/feature/home/src/main/res/values/strings.xml b/feature/home/src/main/res/values/strings.xml index b98ddae5..0ddade75 100644 --- a/feature/home/src/main/res/values/strings.xml +++ b/feature/home/src/main/res/values/strings.xml @@ -2,4 +2,5 @@ home 최대 30개의 포킷을 생성할 수 있습니다.\n포킷을 삭제한 뒤에 추가해주세요. + 복사한 링크 저장하기\n%s \ No newline at end of file diff --git a/feature/login/src/main/java/pokitmons/pokit/login/LoginScreen.kt b/feature/login/src/main/java/pokitmons/pokit/login/LoginScreen.kt index 2edf16b7..e37585dc 100644 --- a/feature/login/src/main/java/pokitmons/pokit/login/LoginScreen.kt +++ b/feature/login/src/main/java/pokitmons/pokit/login/LoginScreen.kt @@ -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 @@ -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(