Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
13 commits
Select commit Hold shift + click to select a range
0029a46
⚑️ feature/#77 : core λͺ¨λ“ˆμ— λͺ¨λΈ 정리
KateteDeveloper Feb 4, 2026
6899aae
μ†Œμ…œ 둜그인 μ‹œ 이메일 μž…λ ₯ ν™”λ©΄
KateteDeveloper Feb 4, 2026
2c81209
πŸ› : ν™”λ©΄ μ§„μž…μ‹œ νšŒμ›κ°€μž… μ™„λ£Œλ‘œ λ³€κ²½
KateteDeveloper Feb 4, 2026
421c914
⚑️ feature/#77 μ†Œμ…œ 둜그인 λ”₯링크 νšŒμ›κ°€μž… κ΅¬ν˜„
KateteDeveloper Feb 5, 2026
e71fb39
🎨 λ§ˆμ΄νŽ˜μ΄μ§€ λ·°λͺ¨λΈ μœ μ € λ ˆν¬μ§€ν† λ¦¬λ§Œ μ˜μ‘΄ν•  수 μžˆλ„λ‘ μˆ˜μ •ν•¨.
KateteDeveloper Feb 7, 2026
886ca5e
πŸ”₯ ν•„μš”μ—†λŠ” 이미지 파일 μ‚­μ œ
KateteDeveloper Feb 8, 2026
b89fa77
✨ νλ ˆμ΄μ…˜ 메인 ui μΉ΄λ“œ 생성
KateteDeveloper Feb 10, 2026
b458144
νλ ˆμ΄μ…˜ λ·°νŽ˜μ΄μ € κ΅¬ν˜„
KateteDeveloper Feb 10, 2026
7873d51
1차적 μΈν„°λ ‰μ…˜ μΆ”κ°€?
KateteDeveloper Feb 10, 2026
ba5758a
✨ μ½”λ“œλ ˆλΉ— ν”Όλ“œλ°± 반영 : μ†Œμ…œ 둜그인 λ”₯링크 연동 및 λ‚΄λΉ„κ²Œμ΄μ…˜ ꡬ쑰 κ°œμ„ 
KateteDeveloper Feb 21, 2026
cbcfd3d
feat: 카카였 μ†Œμ…œ 둜그인 λ”₯링크 연동 μ™„λ£Œ
KateteDeveloper Feb 23, 2026
18d2ac8
더 이상 μ‚¬μš©ν•˜μ§€ μ•ŠλŠ” νλ ˆμ΄μ…˜ ui 제거
KateteDeveloper Feb 25, 2026
3295ea2
✨ νλ ˆμ΄μ…˜ μ§€λ‚œ λ‚΄μ—­ 쑰회용 μΊ˜λ¦°λ” μ»΄ν¬λ„ŒνŠΈ μΆ”κ°€
KateteDeveloper Feb 25, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,11 @@ xmlns:tools="http://schemas.android.com/tools"
android:theme="@style/Theme.LinkU_Android"
android:networkSecurityConfig="@xml/network_security_config"
tools:targetApi="31">
<!-- μΆ”κ°€ 앱이 이미 μ‹€ν–‰ 쀑일 λ•Œ, onNewIntent()λ₯Ό 받을 수 μžˆλ„λ‘ launchMode="singleTask"좔가함.
μ—¬κΈ°μ„œ MainActivity.kt - onNewIntentμ—μ„œ λ”₯링크 μˆ˜μ‹  β†’ SocialDeepLinkBus둜 전달-->
<activity
android:name=".MainActivity"
android:launchMode="singleTask"
android:exported="true"
android:label="@string/app_name"
android:theme="@style/Theme.LinkU_Android">
Expand All @@ -28,6 +31,7 @@ xmlns:tools="http://schemas.android.com/tools"

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<!-- 폴더 곡유 앱링크 -->
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
Expand All @@ -37,6 +41,17 @@ xmlns:tools="http://schemas.android.com/tools"
android:host="open" />
</intent-filter>

<!-- μ†Œμ…œ 둜그인 λ”₯링크 μΆ”κ°€ μ„œμ›μ΄κ°€ app.deeplink.base-url을 linku://둜 λ°”κΎΈλ©΄ μ†Œμ…œ 성곡 ν›„ λ¦¬λ‹€μ΄λ ‰νŠΈ
μ•±μ—μ„œ λ°›μœΌλ €λ©΄scheme=linku, host=auth 등둝함. -->
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="linku"
android:host="auth" />
</intent-filter>
<!-- TODO: μ„œμ›μ΄ linku:// 배포 μ™„λ£Œ ν›„ μ•„λž˜ 두 intent-filter μ‚­μ œ -->
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
Expand All @@ -47,6 +62,17 @@ xmlns:tools="http://schemas.android.com/tools"
android:path="/open"/>
</intent-filter>

<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="https"
android:host="linkuserver.store"
android:path="/auth" />
</intent-filter>
<!-- TODO 끝 -->

</activity>
</application>

Expand Down
82 changes: 74 additions & 8 deletions app/src/main/java/com/example/linku_android/MainActivity.kt
Original file line number Diff line number Diff line change
@@ -1,32 +1,98 @@
package com.example.linku_android

import android.content.Intent
import android.os.Bundle
import android.util.Log
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat
import androidx.hilt.navigation.compose.hiltViewModel
import com.example.core.model.SystemBarMode
import com.example.core.system.SystemBarController
import com.example.linku_android.deeplink.extractSocialDeepLinkData
import dagger.hilt.android.AndroidEntryPoint

import com.example.linku_android.deeplink.SocialDeepLinkBus
@AndroidEntryPoint
class MainActivity : ComponentActivity() {
class MainActivity : ComponentActivity(), SystemBarController {
private var currentSystemBarMode: SystemBarMode? = null

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)



intent?.data?.let { Log.d("DEEPLINK", "onCreate uri = $it") }

WindowCompat.setDecorFitsSystemWindows(window, false)
// 앱이 κΊΌμ§„ μƒνƒœμ—μ„œ λ”₯링크둜 μ‹€ν–‰λœ 경우
intent?.let { handleDeepLinkIntent(it) }
//WindowCompat.setDecorFitsSystemWindows(window, false)
//enableEdgeToEdge()
// 졜초 μ‹€ν–‰ λ”₯링크
setContent {
MainApp(
viewModel = hiltViewModel()
viewModel = hiltViewModel(),


)
}
}

override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
setIntent(intent)

// μ•± μ‹€ν–‰ 쀑 λ”₯링크 λ“€μ–΄μ˜¨ 경우
handleDeepLinkIntent(intent)
}

private fun handleDeepLinkIntent(intent: Intent) {
val uri = intent.data ?: return

when (uri.host) {
"auth" -> {
val data = extractSocialDeepLinkData(intent) ?: return
Log.d("DEEPLINK", "μ†Œμ…œ 둜그인 λ”₯링크 μˆ˜μ‹ : $data")
SocialDeepLinkBus.emit(data) // ← λ‹€μŒ λ‹¨κ³„μ—μ„œ λ§Œλ“€ 파일
Comment on lines +53 to +54
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

λ‘œκ·Έμ— $data둜 토큰이 λ…ΈμΆœλ©λ‹ˆλ‹€.

SocialLoginData의 toString()에 토큰이 ν¬ν•¨λ˜λ―€λ‘œ Line 53μ—μ„œλ„ 민감 정보가 λ‘œκΉ…λ©λ‹ˆλ‹€.

πŸ€– Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/main/java/com/example/linku_android/MainActivity.kt` around lines 53
- 54, MainActivity is logging the entire SocialLoginData (including the token)
via Log.d("DEEPLINK", "… $data"); change this to avoid calling
SocialLoginData.toString() or printing the token: either log only non-sensitive
fields (e.g., userId, provider) or create a redacted accessor (e.g.,
SocialLoginData.toRedactedString() or SocialLoginData.getSafeLogFields()) and
use that in the Log.d call; additionally, update SocialLoginData.toString() to
not include the token (or ensure tokens are omitted/masked) so future accidental
logs are safe; target symbols: MainActivity Log.d call,
SocialLoginData.toString(), and SocialDeepLinkBus.emit usage.

}
}
}
Comment on lines +47 to 57
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | πŸ”΄ Critical

https://linkuserver.store/auth λ”₯링크가 μ†Œμ…œ 둜그인으둜 λΌμš°νŒ…λ˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.

handleDeepLinkIntentλŠ” uri.host == "auth"만 κ²€μ‚¬ν•©λ‹ˆλ‹€. linku://auth의 경우 hostκ°€ "auth"μ΄λ―€λ‘œ λ™μž‘ν•˜μ§€λ§Œ, https://linkuserver.store/auth의 경우 hostλŠ” "linkuserver.store"이고 pathκ°€ "/auth"μ΄λ―€λ‘œ 이 뢄기에 μ§„μž…ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.

AndroidManifest에 https://linkuserver.store/auth intent-filterκ°€ λ“±λ‘λ˜μ–΄ μžˆμœΌλ―€λ‘œ (Line 65-73), ν•΄λ‹Ή λ”₯링크도 μ²˜λ¦¬ν•΄μ•Ό ν•©λ‹ˆλ‹€.

πŸ› HTTPS λ”₯링크 λŒ€μ‘ μˆ˜μ • μ œμ•ˆ
     private fun handleDeepLinkIntent(intent: Intent) {
         val uri = intent.data ?: return
 
         when (uri.host) {
             "auth" -> {
                 val data = extractSocialDeepLinkData(intent) ?: return
-                Log.d("DEEPLINK", "μ†Œμ…œ 둜그인 λ”₯링크 μˆ˜μ‹ : $data")
+                Log.d("DEEPLINK", "μ†Œμ…œ 둜그인 λ”₯링크 μˆ˜μ‹  (custom scheme)")
+                SocialDeepLinkBus.emit(data)
+            }
+            "linkuserver.store" -> {
+                if (uri.path == "/auth") {
+                    val data = extractSocialDeepLinkData(intent) ?: return
+                    Log.d("DEEPLINK", "μ†Œμ…œ 둜그인 λ”₯링크 μˆ˜μ‹  (https)")
+                    SocialDeepLinkBus.emit(data)
+                }
+            }
+        }
+    }
πŸ€– Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/main/java/com/example/linku_android/MainActivity.kt` around lines 47
- 57, handleDeepLinkIntent currently only checks uri.host == "auth" so HTTPS
links like https://linkuserver.store/auth (where uri.host == "linkuserver.store"
and uri.path == "/auth") are ignored; update the routing to accept either host
== "auth" OR path == "/auth" (or trim leading slash) before calling
extractSocialDeepLinkData and SocialDeepLinkBus.emit; modify the when/if in
handleDeepLinkIntent to inspect uri.path (and normalize it) in addition to
uri.host so both linku://auth and https://linkuserver.store/auth are handled.

}




/**
* SystemBarController κ΅¬ν˜„
* μ•± μ „μ—­ μ‹œμŠ€ν…œ λ°” 단일 μ œμ–΄ 지점
*/
override fun setSystemBarMode(mode: SystemBarMode) {
if (currentSystemBarMode == mode) return
currentSystemBarMode = mode

WindowCompat.setDecorFitsSystemWindows(
window,
mode == SystemBarMode.VISIBLE
)

val controller = WindowInsetsControllerCompat(
window,
window.decorView
)

if (mode == SystemBarMode.VISIBLE) {
controller.show(
WindowInsetsCompat.Type.statusBars() or
WindowInsetsCompat.Type.navigationBars()
)
controller.systemBarsBehavior =
WindowInsetsControllerCompat.BEHAVIOR_DEFAULT
} else {
controller.hide(
WindowInsetsCompat.Type.statusBars() or
WindowInsetsCompat.Type.navigationBars()
)
controller.systemBarsBehavior =
WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
}

}
}

77 changes: 52 additions & 25 deletions app/src/main/java/com/example/linku_android/MainApp.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.animation.EnterTransition
import androidx.compose.animation.ExitTransition
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
Expand Down Expand Up @@ -47,28 +46,11 @@ import com.example.mypage.MyPageViewModel
//import com.example.mypage.MyPageScreen
import androidx.navigation.NavType
import androidx.navigation.navArgument
import androidx.navigation.compose.navigation


import androidx.navigation.compose.currentBackStackEntryAsState
import com.example.home.HomeApp
import com.example.curation.ui.CurationDetailScreen
import com.example.curation.ui.CurationScreen
import com.example.login.ui.animation.AnimatedLoginScreen
import com.example.login.ui.screen.EmailVerificationScreen
import com.example.login.ui.terms.ServiceTermsScreen
import com.example.login.ui.terms.PrivacyTermsScreenFixed
import com.example.login.ui.terms.MarketingTermsScreenComposable
import com.example.login.ui.screen.SignUpPasswordScreen
import com.example.login.ui.screen.EmailLoginScreen
import com.example.login.ui.screen.InterestContentScreen
import com.example.login.ui.screen.InterestPurposeScreen
import com.example.login.ui.screen.SignUpGenderScreen
import com.example.login.ui.screen.SignUpNicknameScreen
import com.example.login.ui.screen.SignUpJobScreen
import com.example.login.ui.screen.WelcomeScreen
import com.example.login.ui.screen.ResetPasswordScreen
import com.example.login.viewmodel.SignUpViewModel
import java.io.File
import java.io.FileOutputStream

Expand All @@ -86,19 +68,20 @@ import com.example.login.viewmodel.LoginViewModel

import dagger.hilt.android.EntryPointAccessors
import androidx.core.net.toUri
import com.example.curation.CurationDetailViewModel
import com.example.core.model.auth.LoginState
import com.example.core.model.auth.SocialLoginEvent
import com.example.linku_android.curation.curationGraph
import com.example.linku_android.deeplink.SocialDeepLinkBus
import com.example.linku_android.deeplink.appLinkRoute
import com.example.login.LoginApp
import com.example.login.ui.bottom_sheet.TermsAgreementSheet
import com.example.login.viewmodel.LoginState
import com.example.login.navigation.LoginApp



@Composable
fun MainApp(
viewModel: MainViewModel,
) {

) {

// μ•± μ‹€ν–‰ μ‹œ μ‹€ν–‰ν•˜μ—¬ 이전 계정 기둝 μ‚­μ œ
LaunchedEffect(Unit) {
Expand Down Expand Up @@ -127,9 +110,51 @@ fun MainApp(
// λ§ˆμ΄νŽ˜μ΄μ§€μ—μ„œ μ‚¬μš©ν•  λ·°λͺ¨λΈ
val mypageViewModel: MyPageViewModel = hiltViewModel()

// TEMP 토큰 μž„μ‹œ 보관
var pendingSocialToken by remember { mutableStateOf<String?>(null) }

// SocialDeepLinkBus ꡬ독 - MainActivityμ—μ„œ emitν•œ μ†Œμ…œ λ”₯링크 μˆ˜μ‹ 
LaunchedEffect(Unit) {
Log.d("SOCIAL_VM", "Bus ꡬ독 μ‹œμž‘")
SocialDeepLinkBus.flow.collect { data ->
Log.d("SOCIAL_VM", "MainApp Bus μˆ˜μ‹ : $data")
loginViewModel.handleSocialDeepLink(data)
}
}
Comment on lines +117 to +123
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Busμ—μ„œ μˆ˜μ‹ ν•œ 데이터 λ‘œκΉ… μ‹œ 토큰이 λ…ΈμΆœλ©λ‹ˆλ‹€.

Line 120의 "$data"λŠ” SocialLoginData.toString()을 톡해 accessToken, refreshToken, socialToken이 logcat에 κΈ°λ‘λ©λ‹ˆλ‹€. SocialLoginData의 toString() λ§ˆμŠ€ν‚Ήκ³Ό ν•¨κ»˜ 이 λ‘œκ·Έλ„ μ œκ±°ν•˜κ±°λ‚˜ 디버그 λΉŒλ“œλ‘œ μ œν•œν•˜μ„Έμš”.

πŸ€– Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/main/java/com/example/linku_android/MainApp.kt` around lines 117 -
123, The log call that interpolates "$data" inside the LaunchedEffect
(SocialDeepLinkBus.flow.collect) exposes sensitive tokens because
SocialLoginData.toString() prints accessToken/refreshToken/socialToken; remove
or change this log so it never prints full SocialLoginData (e.g., stop logging
data, log only a non-sensitive field or a masked representation), or wrap the
Log.d call with a build-type check (only in debug builds) and ensure
SocialLoginData.toString() is updated to mask tokens; update references in
MainApp.kt (LaunchedEffect, SocialDeepLinkBus.flow.collect,
loginViewModel.handleSocialDeepLink) and the SocialLoginData.toString()
implementation accordingly.



val socialLoginEvent by loginViewModel.socialLoginEvent.collectAsStateWithLifecycle()
4
LaunchedEffect(socialLoginEvent) {
Comment on lines +126 to +128
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | πŸ”΄ Critical

🧩 Analysis chain

🏁 Script executed:

cat -n app/src/main/java/com/example/linku_android/MainApp.kt | sed -n '122,132p'

Repository: LinkYou-2025/LinkU_Android

Length of output: 534


λΆˆν•„μš”ν•œ λ¦¬ν„°λŸ΄ 토큰 제거 ν•„μˆ˜

Line 127의 단독 숫자 4λŠ” Kotlin ꡬ문 였λ₯˜μ΄λ©° 컴파일 λΈ”λ‘œμ»€μž…λ‹ˆλ‹€. 이 라인을 μ œκ±°ν•˜μ„Έμš”.

πŸ€– Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/main/java/com/example/linku_android/MainApp.kt` around lines 126 -
128, Remove the stray literal token causing the syntax error: delete the
standalone "4" between the collectAsStateWithLifecycle() line and the
LaunchedEffect(...) invocation in MainApp.kt so that the val socialLoginEvent by
loginViewModel.socialLoginEvent.collectAsStateWithLifecycle() and the
LaunchedEffect(socialLoginEvent) block are contiguous and compile correctly.

val event = socialLoginEvent as? SocialLoginEvent.NavigateToSocialEntry ?: return@LaunchedEffect
pendingSocialToken = event.socialToken
navigator.navigate("login_root") {
popUpTo(0) { inclusive = true }
launchSingleTop = true
}
loginViewModel.consumeSocialLoginEvent()
}


var currentLinkuNavigationItem by remember { mutableStateOf<LinkuNavigationItem?>(null) }
var showNavBar by remember { mutableStateOf(false) }

val loginState by loginViewModel.loginState.collectAsStateWithLifecycle()
LaunchedEffect(loginState) {
if (loginState is LoginState.Success) {
Log.d("SOCIAL_VM", "LoginState.Success 감지 β†’ ν™ˆ 이동")
homeViewModel.refreshAfterLogin()
mypageViewModel.refreshUserInfo()
showNavBar = true
currentLinkuNavigationItem = LinkuNavigationItem.HOME
navigator.navigate(NavigationRoute.Home.route) {
popUpTo(0) { inclusive = true }
launchSingleTop = true
}
}
}
Comment on lines +142 to +155
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | πŸ”΄ Critical

LoginState.Success 감지가 μ†Œμ…œ 둜그인뿐 μ•„λ‹ˆλΌ λͺ¨λ“  둜그인 성곡에 λ°˜μ‘ν•˜μ—¬ 쀑볡 λ„€λΉ„κ²Œμ΄μ…˜μ΄ λ°œμƒν•©λ‹ˆλ‹€.

이 LaunchedEffectλŠ” loginState is LoginState.Success일 λ•Œ 무쑰건 ν™ˆμœΌλ‘œ μ΄λ™ν•©λ‹ˆλ‹€. κ·ΈλŸ¬λ‚˜ 일반 이메일/λΉ„λ°€λ²ˆν˜Έ 둜그인 μ‹œμ—λ„ login() λ©”μ„œλ“œκ°€ LoginState.Successλ₯Ό μ„€μ •ν•˜λ―€λ‘œ, LoginApp의 onLoginSuccess 콜백(Line 317-343)κ³Ό 이 LaunchedEffectκ°€ λ™μ‹œμ— ν™ˆ λ„€λΉ„κ²Œμ΄μ…˜μ„ μ‹€ν–‰ν•©λ‹ˆλ‹€.

μ†Œμ…œ 둜그인 성곡에 λŒ€ν•΄μ„œλ§Œ λ°˜μ‘ν•˜λ„λ‘ 쑰건을 μΆ”κ°€ν•˜κ±°λ‚˜, onLoginSuccess 콜백과 역할을 뢄리해야 ν•©λ‹ˆλ‹€.

πŸ› οΈ μ†Œμ…œ 둜그인 μ „μš© 쑰건 μΆ”κ°€ μ œμ•ˆ
     LaunchedEffect(loginState) {
-        if (loginState is LoginState.Success) {
+        val success = loginState as? LoginState.Success ?: return@LaunchedEffect
+        // μ†Œμ…œ 둜그인(ACTIVE λ”₯링크)μ—μ„œλ§Œ μ—¬κΈ°μ„œ ν™ˆ 이동 처리
+        if (success.result.status == "ACTIVE" && pendingSocialToken == null) {
             Log.d("SOCIAL_VM", "LoginState.Success 감지 β†’ ν™ˆ 이동")
             homeViewModel.refreshAfterLogin()
             mypageViewModel.refreshUserInfo()
             showNavBar = true
             currentLinkuNavigationItem = LinkuNavigationItem.HOME
             navigator.navigate(NavigationRoute.Home.route) {
                 popUpTo(0) { inclusive = true }
                 launchSingleTop = true
             }
         }
     }
πŸ€– Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/main/java/com/example/linku_android/MainApp.kt` around lines 142 -
155, The LaunchedEffect reacting to loginViewModel.loginState (checking
LoginState.Success) causes duplicate navigation because onLoginSuccess also
navigates; change the LaunchedEffect in MainApp.kt to only perform the home
navigation for social logins by adding a guard that checks the login source
(e.g., a new or existing flag on loginViewModel like isSocialLogin or a property
on LoginState indicating social vs email) before calling
homeViewModel.refreshAfterLogin(), mypageViewModel.refreshUserInfo(), setting
showNavBar/currentLinkuNavigationItem, and navigator.navigate; alternatively,
clear or consume the social-login-only event after handling so onLoginSuccess
and LaunchedEffect do not both navigate.



var saveLinkEntryTriggered by remember { mutableStateOf(false) }

// ν˜„μž¬ 라우트 κ΄€μ°°
Expand Down Expand Up @@ -206,7 +231,7 @@ fun MainApp(
val deps = remember {
EntryPointAccessors.fromApplication(app, SplashDeps::class.java)
}
val loginVM: LoginViewModel = hiltViewModel()



NavHost(
Expand Down Expand Up @@ -261,7 +286,7 @@ fun MainApp(
autoLoginTried = true

// refresh 있음 β†’ μžλ™λ‘œκ·ΈμΈ μ‹œλ„
loginVM.tryAutoLogin(
loginViewModel.tryAutoLogin(
onSuccess = {
navigator.navigate(NavigationRoute.Home.route) {
popUpTo(NavigationRoute.Splash.route) { inclusive = true }
Expand All @@ -288,7 +313,9 @@ fun MainApp(
//navController = navigator,
loginViewModel = loginViewModel,
showNavBar = { showNavBar = it },
initialSocialToken = pendingSocialToken,
onLoginSuccess = {
pendingSocialToken = null
// μ„Έμ„  정보가 μ €μž₯ ν›„, ν™ˆ ν™”λ©΄ 데이터 μ¦‰μ‹œ λ‘œλ“œ
homeViewModel.refreshAfterLogin()
// λ§ˆμ΄νŽ˜μ΄μ§€ 정보도 미리 둜그(μžμ—°μŠ€λŸ½κ²Œ?)
Expand Down
19 changes: 13 additions & 6 deletions app/src/main/java/com/example/linku_android/Splash.kt
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ import kotlinx.coroutines.flow.first
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalInspectionMode
import com.example.core.model.SystemBarMode
import com.example.core.session.SessionStore
import com.example.core.system.SystemBarController
import com.example.data.preference.AuthPreference
import dagger.hilt.EntryPoint
import dagger.hilt.InstallIn
Expand All @@ -45,12 +47,17 @@ interface SplashDeps {
fun Splash(onResult: (Boolean) -> Unit) {

//λ°”ν…€λ°” μˆ¨κΉ€
DesignSystemBars(
statusBarColor = Color.Transparent,
navigationBarColor = Color.Transparent,
darkIcons = false,
immersive = true
)
val systemBarController =
LocalContext.current as? SystemBarController
val isPreview = LocalInspectionMode.current
// μ‹œμŠ€ν…œ λ°” μˆ¨κΉ€ : λ””μžμ΄λ„ˆμ™€ ν˜‘μ˜ν•œ λ‚΄μ—­
if (!isPreview && systemBarController != null) {
DisposableEffect(Unit) {
systemBarController.setSystemBarMode(SystemBarMode.HIDDEN)
onDispose { }
}
}

val rotationAnim = remember { Animatable(0f) }
var isGlowPhase by remember { mutableStateOf(false) }

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.example.linku_android.deeplink

import com.example.core.model.auth.SocialLoginData
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.asSharedFlow

object SocialDeepLinkBus {
private val _flow = MutableSharedFlow<SocialLoginData>(
extraBufferCapacity = 1,
replay = 1 // 늦게 ꡬ독해도 λ§ˆμ§€λ§‰ κ°’ 받을 수 있음
)
val flow: SharedFlow<SocialLoginData> = _flow.asSharedFlow()
fun emit(data: SocialLoginData) { _flow.tryEmit(data) }
Comment on lines +8 to +14
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

replay=1둜 인해 였래된 λ”₯링크 데이터가 재처리될 수 μžˆμŠ΅λ‹ˆλ‹€.

replay=1은 λŠ¦μ€ κ΅¬λ…μžκ°€ λ§ˆμ§€λ§‰ 값을 받을 수 있게 ν•˜μ§€λ§Œ, μ†Œμ…œ 둜그인 처리 μ™„λ£Œ 후에도 ν•΄λ‹Ή 값이 μΊμ‹œμ— λ‚¨μŠ΅λ‹ˆλ‹€. MainApp의 LaunchedEffect(Unit)κ°€ μž¬μ‹€ν–‰λ˜κ±°λ‚˜ ν™”λ©΄ μž¬κ΅¬μ„± μ‹œ λ™μΌν•œ λ”₯링크 데이터가 λ‹€μ‹œ 처리될 수 μžˆμŠ΅λ‹ˆλ‹€.

처리 μ™„λ£Œ ν›„ replay μΊμ‹œλ₯Ό μ΄ˆκΈ°ν™”ν•˜λŠ” λ©”μ„œλ“œλ₯Ό μΆ”κ°€ν•˜λŠ” 것을 ꢌμž₯ν•©λ‹ˆλ‹€.

♻️ μΊμ‹œ μ΄ˆκΈ°ν™” λ©”μ„œλ“œ μΆ”κ°€ μ œμ•ˆ
 object SocialDeepLinkBus {
     private val _flow = MutableSharedFlow<SocialLoginData>(
         extraBufferCapacity = 1,
         replay = 1
     )
     val flow: SharedFlow<SocialLoginData> = _flow.asSharedFlow()
     fun emit(data: SocialLoginData) { _flow.tryEmit(data) }
+    fun clear() { _flow.resetReplayCache() }
 }
πŸ“ Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
object SocialDeepLinkBus {
private val _flow = MutableSharedFlow<SocialLoginData>(
extraBufferCapacity = 1,
replay = 1 // 늦게 ꡬ독해도 λ§ˆμ§€λ§‰ κ°’ 받을 수 있음
)
val flow: SharedFlow<SocialLoginData> = _flow.asSharedFlow()
fun emit(data: SocialLoginData) { _flow.tryEmit(data) }
object SocialDeepLinkBus {
private val _flow = MutableSharedFlow<SocialLoginData>(
extraBufferCapacity = 1,
replay = 1 // 늦게 ꡬ독해도 λ§ˆμ§€λ§‰ κ°’ 받을 수 있음
)
val flow: SharedFlow<SocialLoginData> = _flow.asSharedFlow()
fun emit(data: SocialLoginData) { _flow.tryEmit(data) }
fun clear() { _flow.resetReplayCache() }
}
πŸ€– Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/main/java/com/example/linku_android/deeplink/SocialDeepLinkBus.kt`
around lines 8 - 14, The SharedFlow currently uses replay=1 so old
SocialLoginData can be re-emitted; add a cache-clearing API on SocialDeepLinkBus
that calls _flow.resetReplayCache() to drop the stored replayed value (e.g., fun
clear() { _flow.resetReplayCache() }) and call this after processing a deep
link; reference symbols: SocialDeepLinkBus, _flow, flow, emit.

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.example.linku_android.deeplink

import android.content.Intent
import android.util.Log
import com.example.core.model.auth.SocialLoginData

fun extractSocialDeepLinkData(intent: Intent): SocialLoginData? {
val uri = intent.data ?: return null

Log.d("SOCIAL_LOGIN", "URI 전체: $uri")
Log.d("SOCIAL_LOGIN", "scheme: ${uri.scheme}")
Log.d("SOCIAL_LOGIN", "host: ${uri.host}")
Log.d("SOCIAL_LOGIN", "path: ${uri.path}")


val provider = uri.getQueryParameter("provider") ?: return null
val result = uri.getQueryParameter("result") ?: return null

return SocialLoginData(
provider = provider,
result = result,
status = uri.getQueryParameter("status"),
accessToken = uri.getQueryParameter("accessToken"),
refreshToken = uri.getQueryParameter("refreshToken"),
socialToken = uri.getQueryParameter("socialToken"),
errorCode = uri.getQueryParameter("errorCode")
)
}
Loading