diff --git a/README.md b/README.md
index 8973bd0b..aed4ffeb 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,11 @@
# 청년을 위한 플랫폼 '청하'
-청하(청춘하랑)는 ‘청춘’과 “함께 높이 날다”라는 의미를 가진 순우리말 ‘하랑’을 합쳐,
-우리 청년들이 이루고자 하는 목표를 위해 함께 날아가고자 만들어진 서비스예요.
-청하에서 여러분이 이루고자 하는 목표를 위한 첫걸음을 시작해 보세요.
+청하(청년하랑) 앱은 청년들을 위한 정책 정보를 제공하고, 사용자들 간의 소통을 도와주는 커뮤니티 플랫폼입니다.
+
+최신 청년 정책 정보 제공: 청년을 위한 다양한 정책과 그 세부 정보를 확인할 수 있습니다.
+커뮤니티 기능: 같은 관심사를 가진 사용자들과 소통하고 네트워크를 형성할 수 있는 커뮤니티 기능을 갖추고 있습니다.
+
+청하 앱을 통해 청년 정책에 대한 정보를 쉽게 얻고, 다른 청년들과 소통하며 유익한 커뮤니티를 형성하세요!
## Environment
- Android Studio Jellyfish
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index f5925ce8..bd86b5d5 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -10,8 +10,8 @@ android {
defaultConfig {
applicationId = "com.withpeace.withpeace"
targetSdk = 34
- versionCode = 6
- versionName = "1.0.1"
+ versionCode = 7
+ versionName = "1.0.2"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
@@ -33,6 +33,11 @@ android {
"proguard-rules.pro",
)
}
+ create("benchmark") {
+ initWith(buildTypes.getByName("release"))
+ matchingFallbacks += listOf("release")
+ isDebuggable = false
+ }
}
}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 4f2039ec..2dbd14cd 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -3,12 +3,10 @@
xmlns:tools="http://schemas.android.com/tools"
package="com.withpeace.withpeace">
-
-
-
-
-
-
+
+
+
+
+ android:theme="@style/Theme.Withpeace.Starting"
+ android:windowSoftInputMode="adjustResize">
@@ -35,4 +37,5 @@
-
+
+
\ No newline at end of file
diff --git a/app/src/main/java/com/withpeace/withpeace/MainActivity.kt b/app/src/main/java/com/withpeace/withpeace/MainActivity.kt
index 8b31f4a8..da55c3d3 100644
--- a/app/src/main/java/com/withpeace/withpeace/MainActivity.kt
+++ b/app/src/main/java/com/withpeace/withpeace/MainActivity.kt
@@ -53,7 +53,6 @@ class MainActivity : ComponentActivity() {
super.onCreate(savedInstanceState)
// System Bar에 가려지는 뷰 영역을 개발자가 제어하겠다.
WindowCompat.setDecorFitsSystemWindows(window, false)
-
lifecycleScope.launch {
splashScreen = installSplashScreen()
splashScreen.setKeepOnScreenCondition { true }
@@ -63,9 +62,7 @@ class MainActivity : ComponentActivity() {
when (uiState) {
MainUiState.Home -> composeStart(HOME_ROUTE)
MainUiState.Login -> composeStart(LOGIN_ROUTE)
- MainUiState.Update -> {
- compulsionUpdate()
- }
+ MainUiState.Update -> {}
MainUiState.Error -> finish()
MainUiState.Loading -> {}
diff --git a/app/src/main/java/com/withpeace/withpeace/MainViewModel.kt b/app/src/main/java/com/withpeace/withpeace/MainViewModel.kt
index 680d78f1..c2e06ba1 100644
--- a/app/src/main/java/com/withpeace/withpeace/MainViewModel.kt
+++ b/app/src/main/java/com/withpeace/withpeace/MainViewModel.kt
@@ -6,7 +6,11 @@ import com.withpeace.withpeace.core.domain.usecase.CheckAppUpdateUseCase
import com.withpeace.withpeace.core.domain.usecase.IsLoginUseCase
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.channels.Channel
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.receiveAsFlow
+import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import javax.inject.Inject
@@ -15,8 +19,8 @@ class MainViewModel @Inject constructor(
private val isLoginUseCase: IsLoginUseCase,
private val checkAppUpdateUseCase: CheckAppUpdateUseCase,
) : ViewModel() {
- private val _uiState: Channel = Channel()
- val uiState = _uiState.receiveAsFlow()
+ private val _uiState: MutableStateFlow = MutableStateFlow(MainUiState.Loading)
+ val uiState = _uiState.asStateFlow()
init {
checkUpdate()
@@ -27,18 +31,18 @@ class MainViewModel @Inject constructor(
checkAppUpdateUseCase(
currentVersion = BuildConfig.VERSION_CODE,
onError = {
- _uiState.send(MainUiState.Error)
+ _uiState.update { MainUiState.Error }
},
).collect { shouldUpdate ->
if (shouldUpdate) {
- _uiState.send(MainUiState.Update)
+ _uiState.update {MainUiState.Update }
return@collect
}
val isLogin = isLoginUseCase()
if (isLogin) {
- _uiState.send(MainUiState.Home)
+ _uiState.update { MainUiState.Home }
} else {
- _uiState.send(MainUiState.Login)
+ _uiState.update { MainUiState.Login }
}
}
}
diff --git a/app/src/main/java/com/withpeace/withpeace/WithpeaceApp.kt b/app/src/main/java/com/withpeace/withpeace/WithpeaceApp.kt
index 2e9423a7..d70e8cf3 100644
--- a/app/src/main/java/com/withpeace/withpeace/WithpeaceApp.kt
+++ b/app/src/main/java/com/withpeace/withpeace/WithpeaceApp.kt
@@ -11,7 +11,10 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
+import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.semantics.testTagsAsResourceId
import androidx.navigation.NavHostController
import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController
@@ -19,6 +22,7 @@ import com.withpeace.withpeace.core.designsystem.theme.WithpeaceTheme
import com.withpeace.withpeace.navigation.WithpeaceNavHost
import kotlinx.coroutines.launch
+@OptIn(ExperimentalComposeUiApi::class)
@Composable
fun WithpeaceApp(
startDestination: String,
@@ -47,7 +51,9 @@ fun WithpeaceApp(
)
}
},
- modifier = Modifier.fillMaxSize(),
+ modifier = Modifier.fillMaxSize().semantics {
+ testTagsAsResourceId = true
+ },
snackbarHost = { SnackbarHost(snackBarHostState) },
containerColor = WithpeaceTheme.colors.SystemWhite,
) { innerPadding ->
diff --git a/benchmark/.gitignore b/benchmark/.gitignore
new file mode 100644
index 00000000..42afabfd
--- /dev/null
+++ b/benchmark/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/benchmark/build.gradle.kts b/benchmark/build.gradle.kts
new file mode 100644
index 00000000..2b9312fb
--- /dev/null
+++ b/benchmark/build.gradle.kts
@@ -0,0 +1,53 @@
+plugins {
+ alias(libs.plugins.android.test)
+ alias(libs.plugins.kotlin.android)
+}
+
+android {
+ namespace = "com.withpeace.withpeace.benchmark"
+ compileSdk = 34
+
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_1_8
+ targetCompatibility = JavaVersion.VERSION_1_8
+ }
+
+ kotlinOptions {
+ jvmTarget = "1.8"
+ }
+
+ defaultConfig {
+ minSdk = 26
+ targetSdk = 34
+
+ testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+ testInstrumentationRunnerArguments["androidx.benchmark.suppressErrors"] = "EMULATOR"
+ }
+
+ buildTypes {
+ // This benchmark buildType is used for benchmarking, and should function like your
+ // release build (for example, with minification on). It"s signed with a debug key
+ // for easy local/CI testing.
+ create("benchmark") {
+ isDebuggable = true
+ signingConfig = getByName("debug").signingConfig
+ matchingFallbacks += listOf("release")
+ }
+ }
+
+ targetProjectPath = ":app"
+ experimentalProperties["android.experimental.self-instrumenting"] = true
+}
+
+dependencies {
+ implementation(libs.junit)
+ implementation(libs.androidx.test.espresso.core)
+ implementation(libs.androidx.uiautomator)
+ implementation(libs.androidx.benchmark.macro.junit4)
+}
+
+androidComponents {
+ beforeVariants(selector().all()) {
+ it.enable = it.buildType == "benchmark"
+ }
+}
\ No newline at end of file
diff --git a/benchmark/src/main/AndroidManifest.xml b/benchmark/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..227314ee
--- /dev/null
+++ b/benchmark/src/main/AndroidManifest.xml
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/benchmark/src/main/java/com/withpeace/withpeace/benchmark/ExampleStartupBenchmark.kt b/benchmark/src/main/java/com/withpeace/withpeace/benchmark/ExampleStartupBenchmark.kt
new file mode 100644
index 00000000..bf3d23e1
--- /dev/null
+++ b/benchmark/src/main/java/com/withpeace/withpeace/benchmark/ExampleStartupBenchmark.kt
@@ -0,0 +1,39 @@
+package com.withpeace.withpeace.benchmark
+
+import androidx.benchmark.macro.CompilationMode
+import androidx.benchmark.macro.StartupMode
+import androidx.benchmark.macro.StartupTimingMetric
+import androidx.benchmark.macro.junit4.MacrobenchmarkRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * This is an example startup benchmark.
+ *
+ * It navigates to the device's home screen, and launches the default activity.
+ *
+ * Before running this benchmark:
+ * 1) switch your app's active build variant in the Studio (affects Studio runs only)
+ * 2) add `` to your app's manifest, within the `` tag
+ *
+ * Run this benchmark from Studio to see startup measurements, and captured system traces
+ * for investigating your app's performance.
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleStartupBenchmark {
+ @get:Rule
+ val benchmarkRule = MacrobenchmarkRule()
+
+ @Test
+ fun startup() = benchmarkRule.measureRepeated(
+ packageName = "com.withpeace.withpeace",
+ metrics = listOf(StartupTimingMetric()),
+ iterations = 5,
+ startupMode = StartupMode.COLD
+ ) {
+ pressHome()
+ startActivityAndWait()
+ }
+}
\ No newline at end of file
diff --git a/benchmark/src/main/java/com/withpeace/withpeace/benchmark/home/HomeActions.kt b/benchmark/src/main/java/com/withpeace/withpeace/benchmark/home/HomeActions.kt
new file mode 100644
index 00000000..f0bdafa5
--- /dev/null
+++ b/benchmark/src/main/java/com/withpeace/withpeace/benchmark/home/HomeActions.kt
@@ -0,0 +1,23 @@
+package com.withpeace.withpeace.benchmark.home
+
+import androidx.benchmark.macro.MacrobenchmarkScope
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.BySelector
+import androidx.test.uiautomator.UiDevice
+import androidx.test.uiautomator.UiObject2
+import androidx.test.uiautomator.Until
+
+fun MacrobenchmarkScope.policyContent() {
+ device.wait(Until.gone(By.res("circular_progress_bar")), 5_000)
+
+ val obj = device.waitAndFindObject(By.res("forYou:topicSelection"), 10_000)
+
+}
+
+fun UiDevice.waitAndFindObject(selector: BySelector, timeout: Long): UiObject2 {
+ if (!wait(Until.hasObject(selector), timeout)) {
+ throw AssertionError("Element not found on screen in ${timeout}ms (selector=$selector)")
+ }
+
+ return findObject(selector)
+}
\ No newline at end of file
diff --git a/benchmark/src/main/java/com/withpeace/withpeace/benchmark/home/HomeBenchMark.kt b/benchmark/src/main/java/com/withpeace/withpeace/benchmark/home/HomeBenchMark.kt
new file mode 100644
index 00000000..4d9860ea
--- /dev/null
+++ b/benchmark/src/main/java/com/withpeace/withpeace/benchmark/home/HomeBenchMark.kt
@@ -0,0 +1,36 @@
+package com.withpeace.withpeace.benchmark.home
+
+import androidx.benchmark.macro.FrameTimingMetric
+import androidx.benchmark.macro.StartupMode
+import androidx.benchmark.macro.junit4.MacrobenchmarkRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.Direction
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class HomeBenchMark {
+ @get:Rule
+ val benchmarkRule = MacrobenchmarkRule()
+
+ @Test
+ fun scrollPolicy() = benchmarkRule.measureRepeated(
+ packageName = "com.withpeace.withpeace",
+ metrics = listOf(FrameTimingMetric()),
+ iterations = 10,
+ startupMode = StartupMode.HOT,
+ setupBlock = {
+ pressHome()
+ startActivityAndWait()
+ }
+ ) {
+ val contentList = device.waitAndFindObject(By.res("home:policies"), 10000)
+ contentList.setGestureMargin(device.displayWidth / 5)
+
+ contentList.fling(Direction.DOWN)
+
+ device.waitForIdle()
+ }
+}
\ No newline at end of file
diff --git a/build.gradle.kts b/build.gradle.kts
index f4b90c26..516d96bf 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -10,6 +10,7 @@ plugins {
alias(libs.plugins.kotlin.serialization) apply false
alias(libs.plugins.firebase.services) apply false
alias(libs.plugins.firebase.crashlytics) apply false
+ alias(libs.plugins.android.test) apply false
}
apply {
diff --git a/feature/home/src/main/java/com/withpeace/withpeace/feature/home/HomeScreen.kt b/feature/home/src/main/java/com/withpeace/withpeace/feature/home/HomeScreen.kt
index 1f5562a1..637bb229 100644
--- a/feature/home/src/main/java/com/withpeace/withpeace/feature/home/HomeScreen.kt
+++ b/feature/home/src/main/java/com/withpeace/withpeace/feature/home/HomeScreen.kt
@@ -35,6 +35,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
@@ -178,7 +179,7 @@ private fun PolicyItems(
) {
Spacer(modifier = modifier.height(8.dp))
LazyColumn(
- modifier = modifier.fillMaxSize(),
+ modifier = modifier.fillMaxSize().testTag("home:policies"),
contentPadding = PaddingValues(bottom = 16.dp),
) {
items(
diff --git a/feature/policyconsent/src/main/java/com/withpeace/withpeace/feature/policyconsent/PolicyConsentScreen.kt b/feature/policyconsent/src/main/java/com/withpeace/withpeace/feature/policyconsent/PolicyConsentScreen.kt
index 2c72b9f5..db0d7194 100644
--- a/feature/policyconsent/src/main/java/com/withpeace/withpeace/feature/policyconsent/PolicyConsentScreen.kt
+++ b/feature/policyconsent/src/main/java/com/withpeace/withpeace/feature/policyconsent/PolicyConsentScreen.kt
@@ -110,7 +110,7 @@ fun PolicyConsentScreen(
Text(
modifier = modifier.align(Alignment.CenterHorizontally),
textAlign = TextAlign.Center,
- text = "청하(청춘하랑)에 어서오세요!\n" +
+ text = "청하(청년하랑)에 어서오세요!\n" +
"약관에 동의하시면 청하와의 여정을\n" +
"시작할 수 있어요!",
style = WithpeaceTheme.typography.title2.merge(
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index dc383a05..0ed740bb 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -1,17 +1,16 @@
[versions]
androidGradlePlugin = "8.2.2"
-androidDesugarJdkLibs = "1.2.2"
androidxCore = "1.12.0"
androidxAppCompat = "1.6.1"
androidxLifecycle = "2.7.0"
-androidxComposeBom = "2023.10.01"
+androidxComposeBom = "2024.06.00"
androidxComposeCompiler = "1.5.1"
-androidxComposeNavigation = "2.7.6"
+androidxComposeNavigation = "2.7.7"
composeIconExtended = "1.5.4"
androidxActivity = "1.8.2"
coreSplashscreen = "1.0.1"
-hilt = "2.48"
-hiltNavigationCompose = "1.1.0"
+hilt = "2.51.1"
+hiltNavigationCompose = "1.2.0"
androidxConstraintlayout = "1.0.1"
lifecycleRuntimeKtx = "2.7.0"
@@ -54,15 +53,15 @@ androidxGlance = "1.0.0-beta01"
glanceExperimentalTools = "0.2.2"
junit = "1.1.5"
material = "1.12.0"
-material3Android = "1.3.0-beta03"
-material3 = "1.3.0-beta03"
+material3Android = "1.3.0-beta04"
+material3 = "1.3.0-beta04"
multidex = "2.0.1"
google-login = "1.1.0"
credential = "1.2.0"
-paging = "3.3.0-alpha02"
+paging = "3.3.0"
coreTesting = "2.2.0"
dependencyGraph = "0.8.0"
@@ -72,10 +71,11 @@ firebaseBom = "32.7.4"
firebaseCrashlytics = "2.9.9"
textflow = "1.1.2"
+uiautomator = "2.2.0"
+benchmarkMacroJunit4 = "1.2.0-beta01"
[libraries]
android-gradlePlugin = { group = "com.android.tools.build", name = "gradle", version.ref = "androidGradlePlugin" }
-android-desugarJdkLibs = { group = "com.android.tools", name = "desugar_jdk_libs", version.ref = "androidDesugarJdkLibs" }
kotlin-gradlePlugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
hilt-gradlePlugin = { module = "com.google.dagger:hilt-android-gradle-plugin", version.ref = "hilt" }
@@ -185,6 +185,8 @@ androidx-material3-android = { group = "androidx.compose.material3", name = "mat
firebase-bom = { group = "com.google.firebase", name = "firebase-bom", version.ref = "firebaseBom" }
firebase-analytics = { group = "com.google.firebase", name = "firebase-analytics" }
firebase-crashlytics = { group = "com.google.firebase", name = "firebase-crashlytics" }
+androidx-uiautomator = { group = "androidx.test.uiautomator", name = "uiautomator", version.ref = "uiautomator" }
+androidx-benchmark-macro-junit4 = { group = "androidx.benchmark", name = "benchmark-macro-junit4", version.ref = "benchmarkMacroJunit4" }
[bundles]
@@ -200,5 +202,6 @@ verify-detekt = { id = "io.gitlab.arturbosch.detekt", version.ref = "detekt" }
ktlint = { id = "org.jlleitschuh.gradle.ktlint", version.ref = "ktlint" }
firebase-services = { id = "com.google.gms.google-services", version.ref = "firebasePlugin" }
firebase-crashlytics = { id = "com.google.firebase.crashlytics", version.ref = "firebaseCrashlytics" }
+android-test = { id = "com.android.test", version.ref = "androidGradlePlugin" }
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 8ea150ea..6e8ded44 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -41,3 +41,4 @@ include(":feature:policydetail")
include(":feature:policyconsent")
include(":feature:privacypolicy")
include(":feature:termsofservice")
+include(":benchmark")