Skip to content

Commit

Permalink
[Feature] #76 암시적 인텐트를 통한 링크 저장 기능 구현 (#77)
Browse files Browse the repository at this point in the history
* [FEATURE] 공유하기 암시적 인텐트 수신 기능 구현

* [FIX] 설정 화면에서 로그아웃 후 로그인 화면에서 뒤로가기 클릭시 로그인 화면이 종료되지 않는 문제 수정

* [CHORE] ktlint 적용
  • Loading branch information
l5x5l authored Oct 8, 2024
1 parent 63630e9 commit ae8e398
Show file tree
Hide file tree
Showing 9 changed files with 132 additions and 11 deletions.
10 changes: 8 additions & 2 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,20 @@
android:screenOrientation="portrait"
android:name=".MainActivity"
android:exported="true"
android:label="@string/app_name"
android:windowSoftInputMode="adjustResize"
android:theme="@style/Theme.Pokit">
android:theme="@style/Theme.Pokit"
android:launchMode="singleInstance"
tools:ignore="DiscouragedApi,LockedOrientationActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:mimeType="text/plain"/>
</intent-filter>
</activity>
</application>

Expand Down
40 changes: 32 additions & 8 deletions app/src/main/java/pokitmons/pokit/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ package pokitmons.pokit

import android.content.ClipData
import android.content.ClipboardManager
import android.content.Intent
import android.os.Build
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.viewModels
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
Expand All @@ -24,14 +26,18 @@ import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.delay
import pokitmons.pokit.core.feature.flow.collectAsEffect
import pokitmons.pokit.core.ui.theme.PokitTheme
import pokitmons.pokit.home.model.ClipboardLinkManager
import pokitmons.pokit.navigation.AddLink
import pokitmons.pokit.navigation.RootNavHost

@AndroidEntryPoint
class MainActivity : ComponentActivity() {
private val viewModel: MainViewModel by viewModels()

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
handleSharedLinkIntent(intent)
setContent {
var showSplash by remember { mutableStateOf(true) }

Expand All @@ -44,6 +50,12 @@ class MainActivity : ComponentActivity() {
val navBackStackEntry by navHostController.currentBackStackEntryAsState()
val currentDestination by remember(navBackStackEntry) { derivedStateOf { navBackStackEntry?.destination } }

viewModel.navigationEvent.collectAsEffect { navigationEvent ->
if (navigationEvent is NavigationEvent.AddLink) {
navHostController.navigate("${AddLink.route}?${AddLink.linkUrl}=${navigationEvent.url}")
}
}

PokitTheme {
if (showSplash) {
SplashScreen()
Expand All @@ -53,25 +65,26 @@ class MainActivity : ComponentActivity() {

LaunchedEffect(currentDestination) {
currentDestination?.route?.let { route ->
// 믹스패널/파베 애널리틱스 화면 이동 로깅용
viewModel.setCurrentRoute(route)
}
}
}
}
}

override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
handleSharedLinkIntent(intent)
}

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)
val setClipDataSuccess = viewModel.setClipData(clipData)
if (!setClipDataSuccess) return@let
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
clipboardManager.clearPrimaryClip()
} else {
Expand All @@ -81,6 +94,17 @@ class MainActivity : ComponentActivity() {
}
}
}

private fun handleSharedLinkIntent(intent: Intent) {
val action = intent.action ?: return

val isSharedLinkData = (action == Intent.ACTION_SEND && intent.type == "text/plain")
if (isSharedLinkData) {
intent.getStringExtra(Intent.EXTRA_TEXT)?.let { url ->
viewModel.setSharedLinkUrl(url)
}
}
}
}

@Composable
Expand Down
56 changes: 56 additions & 0 deletions app/src/main/java/pokitmons/pokit/MainViewModel.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package pokitmons.pokit

import android.content.ClipData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
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 pokitmons.pokit.home.model.ClipboardLinkManager
import pokitmons.pokit.home.model.PendingSharedLinkManager
import pokitmons.pokit.navigation.Login
import pokitmons.pokit.navigation.ROUTE_WITHOUT_LOGIN
import javax.inject.Inject

@HiltViewModel
class MainViewModel @Inject constructor() : ViewModel() {
private val _currentRoute: MutableStateFlow<String> = MutableStateFlow(Login.route)
val currentRoute: StateFlow<String> = _currentRoute.asStateFlow()

private val _navigationEvent: MutableEventFlow<NavigationEvent> = MutableEventFlow()
val navigationEvent: EventFlow<NavigationEvent> = _navigationEvent.asEventFlow()

fun setCurrentRoute(route: String) {
_currentRoute.update { route }
}

fun setClipData(clipData: ClipData): Boolean {
if (clipData.itemCount == 0) return false
val clipboardTextData = clipData.getItemAt(0).text.toString()

if (!ClipboardLinkManager.checkUrlIsValid(clipboardTextData)) return false

ClipboardLinkManager.setClipboardLink(clipboardTextData)
return true
}

fun setSharedLinkUrl(url: String) {
if (currentRoute.value in ROUTE_WITHOUT_LOGIN) {
PendingSharedLinkManager.setSharedLink(url)
} else {
viewModelScope.launch {
_navigationEvent.emit(NavigationEvent.AddLink(url))
}
}
}
}

sealed class NavigationEvent {
data class AddLink(val url: String) : NavigationEvent()
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package pokitmons.pokit.navigation
import androidx.navigation.NavType
import androidx.navigation.navArgument

val ROUTE_WITHOUT_LOGIN = listOf(Login.route, TermOfService.route, InputNickname.route, SelectKeyword.route, SignUpSuccess.route)

object Login {
val route: String = "login"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ fun RootNavHost(
onNavigateToEditNickname = { navHostController.navigate(EditNickname.route) },
onNavigateToLogin = {
navHostController.navigate(Login.route) {
popUpTo(navHostController.graph.startDestinationId) {
popUpTo(navHostController.graph.id) {
inclusive = true
}
}
Expand Down
3 changes: 3 additions & 0 deletions feature/home/src/main/java/pokitmons/pokit/home/HomeScreen.kt
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@ fun HomeScreen(
HomeSideEffect.NavigateToAddPokit -> {
onNavigateAddPokit()
}
is HomeSideEffect.NavigateToAddLink -> {
onNavigateAddLink(homeSideEffect.url)
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ package pokitmons.pokit.home.model

sealed class HomeSideEffect {
data object NavigateToAddPokit : HomeSideEffect()
data class NavigateToAddLink(val url: String) : HomeSideEffect()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
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

object PendingSharedLinkManager {
private val _sharedLinkUrl: MutableEventFlow<String> = MutableEventFlow()
val sharedLinkUrl: EventFlow<String> = _sharedLinkUrl.asEventFlow()

fun setSharedLink(linkUrl: String) {
CoroutineScope(Dispatchers.IO).launch {
_sharedLinkUrl.emit(linkUrl)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ 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 pokitmons.pokit.home.model.PendingSharedLinkManager
import javax.inject.Inject
import kotlin.math.max
import com.strayalpaca.pokitdetail.model.Link as DetailLink
Expand Down Expand Up @@ -107,6 +108,14 @@ class PokitViewModel @Inject constructor(
}
}

private fun initPendingSharedLinkUrlDetector() {
viewModelScope.launch {
PendingSharedLinkManager.sharedLinkUrl.collectLatest { linkUrl ->
_sideEffect.emit(HomeSideEffect.NavigateToAddLink(linkUrl))
}
}
}

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

loadUnCategoryLinks()
loadPokits()
Expand Down

0 comments on commit ae8e398

Please sign in to comment.