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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ build/

# Local configuration file (sdk path, etc)
local.properties
**/local.properties

# Log/OS Files
*.log
Expand Down
4 changes: 1 addition & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,5 @@

## Tooling Reminder
- Use **Figma MCP** for UI work. Use **android-pilot** MCP (see `.cursor/mcp.json`) for Gradle build/lint and device checks once an app module exists.
- The Gradle project root is **`android/`**. Run builds from there (`cd android && ./gradlew assembleDebug`). Open that folder in Android Studio.
- Do not use android-pilot scaffolding tools to invent a project layout that conflicts with `docs/rules/architecture.md` or ADRs. Details: `docs/rules/tooling.md`.

## Out of Scope (until decided otherwise)
- Gradle/app scaffolding is tracked in a separate issue from agent tooling setup.
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@

Livith iOS의 안드로이드 버전 저장소다.

현재는 **기초 문서**(협업 규칙, ADR, 에이전트 안내) 단계이며, 앱/Gradle 스캐폴드는 별도 이슈에서 진행한다.

- 에이전트·기여자 진입점: [AGENTS.md](./AGENTS.md)
- 결정 기록: [docs/adr/](./docs/adr/)
- 실행 규칙: [docs/rules/](./docs/rules/)
- 에이전트 MCP: [docs/rules/tooling.md](./docs/rules/tooling.md), [`.cursor/mcp.json`](./.cursor/mcp.json)
- 앱(Gradle 루트): [android/](./android/) — Android Studio는 이 폴더를 연다.

```bash
cd android && ./gradlew assembleDebug
```
15 changes: 15 additions & 0 deletions android/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
*.iml
.gradle
/local.properties
/.idea/caches
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
.DS_Store
/build
/captures
.externalNativeBuild
.cxx
local.properties
1 change: 1 addition & 0 deletions android/app/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build
76 changes: 76 additions & 0 deletions android/app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.compose)
alias(libs.plugins.kotlin.serialization)
alias(libs.plugins.ksp)
alias(libs.plugins.hilt)
}

android {
namespace = "com.youz2me.livith"
compileSdk {
version = release(37)
}

defaultConfig {
applicationId = "com.youz2me.livith"
minSdk = 26
targetSdk = 36
versionCode = 1
versionName = "1.0"

testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}

buildTypes {
debug {
buildConfigField("String", "BASE_URL", "\"https://dev-api.livith.com/\"")
}
release {
buildConfigField("String", "BASE_URL", "\"https://api.livith.com/\"")
optimization {
enable = false
}
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
buildFeatures {
compose = true
buildConfig = true
}
}

dependencies {
implementation(platform(libs.androidx.compose.bom))
implementation(libs.androidx.activity.compose)
implementation(libs.androidx.compose.material3)
implementation(libs.androidx.compose.ui)
implementation(libs.androidx.compose.ui.graphics)
implementation(libs.androidx.compose.ui.tooling.preview)
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.lifecycle.runtime.ktx)
implementation(libs.androidx.lifecycle.runtime.compose)
implementation(libs.androidx.lifecycle.viewmodel.compose)
implementation(libs.androidx.navigation.compose)
implementation(libs.androidx.datastore.preferences)
implementation(libs.hilt.android)
implementation(libs.hilt.navigation.compose)
implementation(libs.retrofit)
implementation(libs.retrofit.kotlinx.serialization)
implementation(libs.okhttp)
implementation(libs.okhttp.logging)
implementation(libs.kotlinx.serialization.json)
implementation(libs.kotlinx.coroutines.android)
implementation(libs.kotlinx.coroutines.core)
ksp(libs.hilt.compiler)
testImplementation(libs.junit)
androidTestImplementation(platform(libs.androidx.compose.bom))
androidTestImplementation(libs.androidx.compose.ui.test.junit4)
androidTestImplementation(libs.androidx.espresso.core)
androidTestImplementation(libs.androidx.junit)
debugImplementation(libs.androidx.compose.ui.test.manifest)
debugImplementation(libs.androidx.compose.ui.tooling)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.youz2me.livith

import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4

import org.junit.Test
import org.junit.runner.RunWith

import org.junit.Assert.*

/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("com.youz2me.livith", appContext.packageName)
}
}
31 changes: 31 additions & 0 deletions android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">

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

<application
android:name=".LivithApplication"
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.Livith">
<activity
android:name=".presentation.MainActivity"
android:exported="true"
android:label="@string/app_name"
android:theme="@style/Theme.Livith"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>

</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.youz2me.livith

import android.app.Application
import dagger.hilt.android.HiltAndroidApp

@HiltAndroidApp
class LivithApplication : Application()
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package com.youz2me.livith.core.di

import com.youz2me.livith.BuildConfig
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
import kotlinx.serialization.json.Json
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
import retrofit2.converter.kotlinx.serialization.asConverterFactory

@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {

@Provides
@Singleton
fun provideJson(): Json = Json {
ignoreUnknownKeys = true
explicitNulls = false
}

@Provides
@Singleton
fun provideOkHttpClient(): OkHttpClient {
val logging = HttpLoggingInterceptor().apply {
level = if (BuildConfig.DEBUG) {
HttpLoggingInterceptor.Level.BASIC
} else {
HttpLoggingInterceptor.Level.NONE
}
}
return OkHttpClient.Builder()
.addInterceptor(logging)
.build()
}

@Provides
@Singleton
fun provideRetrofit(
okHttpClient: OkHttpClient,
json: Json,
): Retrofit {
val contentType = "application/json".toMediaType()
return Retrofit.Builder()
.baseUrl(BuildConfig.BASE_URL)
.client(okHttpClient)
.addConverterFactory(json.asConverterFactory(contentType))
.build()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.youz2me.livith.data

/**
* Data layer root: repository implementations, data sources, DTOs, and mappers live here.
*/
object DataLayer
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.youz2me.livith.designsystem.theme

import androidx.compose.ui.graphics.Color

val Purple80 = Color(0xFFD0BCFF)
val PurpleGrey80 = Color(0xFFCCC2DC)
val Pink80 = Color(0xFFEFB8C8)

val Purple40 = Color(0xFF6650a4)
val PurpleGrey40 = Color(0xFF625b71)
val Pink40 = Color(0xFF7D5260)
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package com.youz2me.livith.designsystem.theme

import android.os.Build
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.dynamicDarkColorScheme
import androidx.compose.material3.dynamicLightColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext

private val DarkColorScheme = darkColorScheme(
primary = Purple80,
secondary = PurpleGrey80,
tertiary = Pink80
)

private val LightColorScheme = lightColorScheme(
primary = Purple40,
secondary = PurpleGrey40,
tertiary = Pink40
)

@Composable
fun LivithTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
dynamicColor: Boolean = true,
content: @Composable () -> Unit
) {
val colorScheme = when {
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
val context = LocalContext.current
if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
}

darkTheme -> DarkColorScheme
else -> LightColorScheme
}

MaterialTheme(
colorScheme = colorScheme,
typography = Typography,
content = content
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.youz2me.livith.designsystem.theme

import androidx.compose.material3.Typography
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.sp

val Typography = Typography(
bodyLarge = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 16.sp,
lineHeight = 24.sp,
letterSpacing = 0.5.sp
)
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.youz2me.livith.domain

/**
* Domain layer root: entities, repository interfaces, and use cases live here.
*/
object DomainLayer
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.youz2me.livith.presentation

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.Surface
import androidx.compose.ui.Modifier
import com.youz2me.livith.designsystem.theme.LivithTheme
import com.youz2me.livith.presentation.navigation.LivithNavHost
import dagger.hilt.android.AndroidEntryPoint

@AndroidEntryPoint
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
LivithTheme {
Surface(modifier = Modifier.fillMaxSize()) {
LivithNavHost()
}
}
}
}
}
Loading