Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
31 changes: 18 additions & 13 deletions .github/workflows/android.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,23 @@ jobs:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4
- name: set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
cache: gradle
- uses: actions/checkout@v4
- name: set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
cache: gradle

- name: Create local.properties
run: echo "API_KEY=ci_dummy_key" > local.properties
- name: Create local.properties
run: echo "API_KEY=ci_dummy_key" > local.properties

- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Build with Gradle
run: ./gradlew build
- name: Create secrets.properties
run: |
echo "BASE_URL=${{ secrets.BASE_URL }}" > secrets.properties
echo "KAKAO_NATIVE_APP_KEY=${{ secrets.KAKAO_NATIVE_APP_KEY }}" >> secrets.properties

- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Build with Gradle
run: ./gradlew build
43 changes: 39 additions & 4 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,14 +1,42 @@
import java.util.Properties

plugins {
alias(libs.plugins.hyunjung.cherrydan.android.application.compose)
alias(libs.plugins.hyunjung.cherrydan.jvm.ktor)
alias(libs.plugins.mapsplatform.secrets.plugin)
}

secrets {
defaultPropertiesFileName = "secrets.properties"
}

android {
namespace = "com.hyunjung.cherrydan"

defaultConfig {
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"

buildConfigField(
"String",
"BASE_URL",
"\"https://cherrydan.com\""
)

Comment on lines +19 to +24
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Hard-coded BASE_URL couples release & staging builds

A single, immutable URL baked into all variants prevents you from pointing staging / QA builds at a different backend without another PR. Consider turning it into a variant-specific value or loading it from gradle.properties / the secrets plugin.

-        buildConfigField(
-            "String",
-            "BASE_URL",
-            "\"https://cherrydan.com\""
-        )
+        // keeps release default but allows `./gradlew assembleStaging -PBASE_URL=https://staging.api`
+        val baseUrl = (project.findProperty("BASE_URL") ?: "https://cherrydan.com") as String
+        buildConfigField("String", "BASE_URL", "\"$baseUrl\"")

🏁 Script executed:

#!/bin/bash
# Locate gradle.properties to see if project-level properties are already used
find . -maxdepth 2 -type f -name gradle.properties

# Inspect the exact context of buildConfigField in the app module
grep -R "buildConfigField" -n app/build.gradle.kts || true

# Check for existing flavor or build type configurations
grep -R -E "productFlavors|flavorDimensions|buildTypes" -n app

Length of output: 249


Make BASE_URL configurable via project property
Hard-coding the same endpoint in all variants couples your builds to a single backend. Instead, read BASE_URL from a Gradle property (e.g. in gradle.properties or via -PBASE_URL) with a fallback.

Location: app/build.gradle.kts (around lines 19–24)
Steps:

  1. In your root gradle.properties, add:
    BASE_URL=https://cherrydan.com
  2. In app/build.gradle.kts, replace the hard-coded field with:
    val baseUrl: String = (project.findProperty("BASE_URL") as? String)
        ?: "https://cherrydan.com"
    buildConfigField("String", "BASE_URL", "\"$baseUrl\"")
  3. Override per build when needed:
    ./gradlew assembleRelease -PBASE_URL=https://staging.api
    ./gradlew assembleDebug   -PBASE_URL=https://local.api
    

Optional: If you require a dedicated “staging” variant, define a staging buildType under android.buildTypes and configure it (e.g., initWith(release)) so you can run ./gradlew assembleStaging directly.

🤖 Prompt for AI Agents
In app/build.gradle.kts around lines 19 to 24, the BASE_URL is hard-coded, which
limits flexibility. Modify the code to read BASE_URL from a Gradle project
property with a fallback to the default URL. First, add
BASE_URL=https://cherrydan.com in the root gradle.properties file. Then, in
app/build.gradle.kts, replace the hard-coded buildConfigField with a variable
that reads the property using project.findProperty("BASE_URL") as a String or
defaults to the original URL. Use this variable in buildConfigField to make
BASE_URL configurable per build.

val secretsFile = file("${rootProject.projectDir}/secrets.properties")
val kakaoKey = if (secretsFile.exists()) {
val properties = Properties()
properties.load(secretsFile.inputStream())
properties.getProperty("KAKAO_NATIVE_APP_KEY", "")
} else {
""
}

manifestPlaceholders["kakaoScheme"] = "kakao$kakaoKey"
manifestPlaceholders["kakaoKey"] = kakaoKey
}
Comment on lines +34 to +36
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue

Nil-safe placeholder – avoid producing an invalid scheme

If kakaoKey is empty the scheme becomes just "kakao", which will not match the redirect URI Kakao issues (kakao{APP_KEY}://oauth).
Guard against that to fail fast:

-        manifestPlaceholders["kakaoScheme"] = "kakao$kakaoKey"
-        manifestPlaceholders["kakaoKey"] = kakaoKey
+        require(kakaoKey.isNotBlank()) { "KAKAO_NATIVE_APP_KEY is missing" }
+        manifestPlaceholders["kakaoScheme"] = "kakao$kakaoKey"
+        manifestPlaceholders["kakaoKey"] = kakaoKey
📝 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
manifestPlaceholders["kakaoScheme"] = "kakao$kakaoKey"
manifestPlaceholders["kakaoKey"] = kakaoKey
}
require(kakaoKey.isNotBlank()) { "KAKAO_NATIVE_APP_KEY is missing" }
manifestPlaceholders["kakaoScheme"] = "kakao$kakaoKey"
manifestPlaceholders["kakaoKey"] = kakaoKey
}
🤖 Prompt for AI Agents
In app/build.gradle.kts around lines 34 to 36, the kakaoScheme placeholder is
set without checking if kakaoKey is empty, which can produce an invalid scheme.
Add a guard to check if kakaoKey is not empty before setting kakaoScheme; if
kakaoKey is empty, fail fast by throwing an error or logging a clear message to
prevent generating an invalid redirect URI.


buildFeatures {
buildConfig = true
}
}

Expand Down Expand Up @@ -57,14 +85,21 @@ dependencies {
api(libs.play.feature.delivery)
api(libs.play.review)

implementation(projects.feature.auth)
implementation(projects.feature.home)
implementation(projects.feature.notification)
implementation(projects.feature.search)

implementation(projects.core.presentation.designsystem)
implementation(projects.core.presentation.ui)
implementation(projects.core.domain)
implementation(projects.core.data)
implementation(projects.core.database)
implementation(projects.core.network)
implementation(projects.core.common)

implementation(projects.feature.auth)
implementation(projects.feature.home)
implementation(projects.feature.notification)
implementation(projects.feature.search)
// Kakao SDK
implementation(libs.kakao.auth)
implementation(libs.kakao.common)
implementation(libs.kakao.user)
}
18 changes: 18 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

<application
android:name=".CherrydanApplication"
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
Expand All @@ -23,6 +27,20 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

<!-- Kakao SDK -->
<activity
android:name="com.kakao.sdk.auth.AuthCodeHandlerActivity"
android:exported="true">
<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="${kakaoScheme}" />
</intent-filter>
</activity>
</application>

</manifest>
44 changes: 44 additions & 0 deletions app/src/main/java/com/hyunjung/cherrydan/CherrydanApplication.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package com.hyunjung.cherrydan

import android.app.Application
import com.hyunjung.core.data.di.dataModule
import com.hyunjung.core.network.di.networkModule
import com.hyunjung.feature.auth.di.authViewModelModule
import com.kakao.sdk.common.KakaoSdk
import org.koin.android.ext.koin.androidContext
import org.koin.core.context.startKoin
import timber.log.Timber

class CherrydanApplication : Application() {

override fun onCreate() {
super.onCreate()

// Timber 초기화 (가장 먼저)
if (BuildConfig.DEBUG) {
Timber.plant(Timber.DebugTree())
}

// 카카오 앱키 확인
val kakaoAppKey = BuildConfig.KAKAO_NATIVE_APP_KEY
Timber.d("Kakao App Key: $kakaoAppKey")

if (kakaoAppKey.isEmpty()) {
Timber.e("Kakao App Key가 설정되지 않았습니다!")
}

// Kakao SDK 초기화
KakaoSdk.init(this, BuildConfig.KAKAO_NATIVE_APP_KEY)

// Koin 초기화
startKoin {
androidContext(this@CherrydanApplication)
modules(
authViewModelModule,
dataModule,
networkModule
// 다른 모듈들도 여기에 추가
)
}
}
}
58 changes: 56 additions & 2 deletions app/src/main/java/com/hyunjung/cherrydan/MainActivity.kt
Original file line number Diff line number Diff line change
@@ -1,30 +1,84 @@
package com.hyunjung.cherrydan

import android.content.pm.PackageManager
import android.os.Bundle
import android.util.Base64
import android.util.Log
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.tooling.preview.Preview
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import com.hyunjung.cherrydan.navigation.AppNavigation
import com.hyunjung.core.presentation.designsystem.CherrydanTheme
import com.hyunjung.feature.auth.login.LogInScreenRoot
import com.hyunjung.feature.auth.splash.SplashScreen
import com.hyunjung.feature.home.navigation.HomeNavigation
import timber.log.Timber
import java.security.MessageDigest

class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
CherrydanTheme {
HomeNavigation()
var isLoggedIn by remember { mutableStateOf(false) }

if (isLoggedIn) {
HomeNavigation()
} else {
val navController = rememberNavController()

NavHost(
navController = navController,
startDestination = "splash"
) {
composable("splash") {
SplashScreen(
onSplashFinished = {
// TODO: 실제로는 토큰 확인 후 결정
// if (hasValidToken) { isLoggedIn = true } else { navigate to login }
navController.navigate("login") {
popUpTo("splash") { inclusive = true }
}
}
)
}
composable("login") {
LogInScreenRoot(
onKakaoLogInClick = { },
onNaverLogInClick = { },
onGoogleLogInClick = { },
onLoginSuccess = { isLoggedIn = true }
)
}
}
}
}
}
}

override fun onResume() {
super.onResume()
Timber.d("MainActivity onResume called")
}
}

@Preview
@Composable
private fun MainActivityPreview() {
CherrydanTheme {
HomeNavigation()
AppNavigation(
navController = rememberNavController()
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package com.hyunjung.cherrydan.navigation

import androidx.compose.runtime.Composable
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import com.hyunjung.feature.auth.login.LogInScreenRoot
import com.hyunjung.feature.auth.splash.SplashScreen
import com.hyunjung.feature.home.navigation.HomeNavigation

@Composable
fun AppNavigation(
navController: NavHostController = rememberNavController()
) {
NavHost(
navController = navController,
startDestination = "splash"
) {
composable("splash") {
SplashScreen(
onSplashFinished = {
navController.navigate("login") {
popUpTo("splash") { inclusive = true }
}
}
)
}
composable("login") {
LogInScreenRoot(
onKakaoLogInClick = {
navController.navigate("home") {
popUpTo("login") { inclusive = true }
}
},
onNaverLogInClick = {
navController.navigate("home") {
popUpTo("login") { inclusive = true }
}
},
onGoogleLogInClick = {
navController.navigate("home") {
popUpTo("login") { inclusive = true }
}
},
onLoginSuccess = {
navController.navigate("home") {
popUpTo("login") { inclusive = true }
}
}
)
}
composable("home") {
HomeNavigation()
}
// authGraph(navController)
//
// mainGraph(navController)
//
// profileGraph(navController)
}
}
Loading