diff --git a/.github/workflows/develop_PR_builder.yml b/.github/workflows/develop_PR_builder.yml
index 33d22b83..2f72d32c 100644
--- a/.github/workflows/develop_PR_builder.yml
+++ b/.github/workflows/develop_PR_builder.yml
@@ -36,6 +36,12 @@ jobs:
- name: Change gradlew permissions
run: chmod +x ./gradlew
+ - name: Add Local Properties
+ env:
+ KAKAO_API_KEY: ${{ secrets.KAKAO_API_KEY }}
+ run: |
+ echo kakaoApiKey=$KAKAO_API_KEY >> ./local.properties
+
- name: Access Firebase Service
run: echo '${{ secrets.GOOGLE_SERVICES_JSON }}' > ./app/google-services.json
diff --git a/.gitignore b/.gitignore
index 2a37cc12..ae099a52 100644
--- a/.gitignore
+++ b/.gitignore
@@ -28,7 +28,6 @@ render.experimental.xml
# Keystore files
*.jks
-*.keystore
# Google Services (e.g. APIs or Firebase)
google-services.json
diff --git a/README.md b/README.md
index 5c762140..0023faf4 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,111 @@
-# HMH Android
+# 하면함 Android
+
+## 스마트폰 중독 탈출, 너도 하면함!
+
+
+
+
+
+
+
+
+
+
+
+Tech Stack
+
+- [Android App Architecture](https://developer.android.com/topic/architecture)
+- [Dagger-Hilt](https://developer.android.com/training/dependency-injection/hilt-android)
+- [Kotlin Coroutines](https://kotlinlang.org/docs/coroutines-overview.html), [Flow](https://kotlinlang.org/docs/flow.html)
+- [Jetpack Compose](https://developer.android.com/jetpack/compose)
+- [Material 3](https://m3.material.io/)
+- [Gradle Version Catalog](https://docs.gradle.org/current/userguide/platforms.html)
+ and [Custom Convention Plugins](https://docs.gradle.org/current/samples/sample_convention_plugins.html)
+
+Activity Graph
+
+![Alt](https://repobeats.axiom.co/api/embed/d2c401ae723c367a03ed9fb81ea6e6e7cfbee2ea.svg "Repobeats analytics image")
+
+## Contributors ✨
+
+하면한다는 Android 개발자
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+## Folder Tree 📁
+
+- 📁 app
+ - 📁 build
+ - 📄 build.gradle.kts
+ - 📄 google-services.json
+ - 📁 libs
+ - 📄 proguard-rules.pro
+ - 📁 src
+- 📁 build-logic
+ - 📁 convention
+ - 📁 gradle
+ - 📄 gradle.properties
+ - 📄 settings.gradle.kts
+- 📄 build.gradle.kts
+- 📁 buildSrc
+ - 📁 build
+ - 📄 build.gradle.kts
+ - 📄 gradle.properties
+ - 📄 settings.gradle.kts
+ - 📁 src
+- 📁 core
+ - 📁 common
+ - 📁 database
+- 📁 data
+ - 📁 usagestats
+- 📁 domain
+ - 📁 usagestats
+- 📁 feature
+ - 📁 login
+ - 📁 main
+ - 📁 onboarding
+ - 📁 statistics
+- 📁 gradle
+ - 📄 libs.versions.toml
+ - 📁 wrapper
+- 📄 gradle.properties
+- 📄 gradlew
+- 📄 gradlew.bat
+- 📄 local.properties
+- 📄 settings.gradle.kts
+
+## Progress Board 📋
+- [Team-HMH-Android](https://github.com/orgs/Team-HMH/projects/1)
+
+
+## Code Convention 💻
+- [Kotlin Code Convention](https://www.notion.so/msmmx/Kotlin-Convention-5ab4410e68c949c287804d2380c51af4)
+- [XML Convention](https://www.notion.so/msmmx/XML-Convention-b11209ba2b404df08f251383c7d3c316)
+- [name convention](https://www.notion.so/msmmx/name-convention-ca8bab7da7314b30b2a962755854b11e)
+- [GitHub Convention](https://www.notion.so/msmmx/b75d3559813e478f9f6d73e9c818834b?pvs=4#bc0bf41e7df9421391dc1000d25a17c6)
+
+
+This project follows the all-contributors specification. Contributions of any kind welcome!
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 411b5ed6..05ed02c8 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -16,9 +16,23 @@ android {
versionName = libs.versions.appVersion.get()
}
+ signingConfigs {
+ getByName("debug") {
+ keyAlias = "android_debug_key"
+ keyPassword = "android"
+ storeFile = File("${project.rootDir.absolutePath}/keystore/debug.keystore")
+ storePassword = "android"
+ }
+ }
+
buildTypes {
+ debug {
+ isDebuggable = true
+ signingConfig = signingConfigs.getByName("debug")
+ }
release {
isMinifyEnabled = false
+ isDebuggable = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro",
@@ -28,16 +42,6 @@ android {
}
dependencies {
- // Feature
- implementation(projects.feature.onboarding)
-
- // Core
- implementation(projects.core.common)
- implementation(projects.core.database)
-
- // Feature
- implementation(projects.feature.onboarding)
- implementation(projects.feature.main)
// Feature
implementation(projects.feature.statistics)
@@ -57,4 +61,12 @@ dependencies {
// Splash
implementation(libs.splash.screen)
+
+ // Features
+ implementation(projects.feature.login)
+ implementation(projects.feature.onboarding)
+ implementation(projects.feature.main)
+
+ // kakao
+ implementation(libs.kakao.login)
}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 7faba2b9..dff28e6b 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -2,12 +2,16 @@
+
+
-
+
-
-
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/java/com/hmh/hamyeonham/SampleActivity.kt b/app/src/main/java/com/hmh/hamyeonham/SampleActivity.kt
index f3a7b90d..62aa7129 100644
--- a/app/src/main/java/com/hmh/hamyeonham/SampleActivity.kt
+++ b/app/src/main/java/com/hmh/hamyeonham/SampleActivity.kt
@@ -1,27 +1,24 @@
package com.hmh.hamyeonham
-import android.content.Intent
import android.os.Bundle
import android.view.animation.Animation
import android.view.animation.AnimationUtils
import androidx.appcompat.app.AppCompatActivity
import androidx.core.splashscreen.SplashScreen
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
+import com.hmh.hamyeonham.common.view.viewBinding
import com.hmh.hamyeonham.databinding.ActivitySampleBinding
-import com.hmh.hamyeonham.statistics.StaticsActivity
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
class SampleActivity : AppCompatActivity() {
- private lateinit var binding: ActivitySampleBinding
+ private val binding by viewBinding(ActivitySampleBinding::inflate)
override fun onCreate(savedInstanceState: Bundle?) {
- binding = ActivitySampleBinding.inflate(layoutInflater)
super.onCreate(savedInstanceState)
val splashScreen = installSplashScreen()
initSplashAnimation(splashScreen)
setContentView(binding.root)
- Intent(this, StaticsActivity::class.java).let(::startActivity)
}
private fun initSplashAnimation(splashScreen: SplashScreen) {
@@ -29,17 +26,14 @@ class SampleActivity : AppCompatActivity() {
val splashScreenView = splashScreenViewProvider.view
val fadeOut = AnimationUtils.loadAnimation(this, R.anim.fade_out)
- fadeOut.setAnimationListener(
- object : Animation.AnimationListener {
- override fun onAnimationStart(animation: Animation) {}
+ fadeOut.setAnimationListener(object : Animation.AnimationListener {
+ override fun onAnimationStart(animation: Animation) {}
+ override fun onAnimationEnd(animation: Animation) {
+ splashScreenViewProvider.remove()
+ }
- override fun onAnimationEnd(animation: Animation) {
- splashScreenViewProvider.remove()
- }
-
- override fun onAnimationRepeat(animation: Animation) {}
- },
- )
+ override fun onAnimationRepeat(animation: Animation) {}
+ })
splashScreenView.startAnimation(fadeOut)
}
}
diff --git a/app/src/main/res/layout/activity_sample.xml b/app/src/main/res/layout/activity_sample.xml
index d36955d3..e7c78653 100644
--- a/app/src/main/res/layout/activity_sample.xml
+++ b/app/src/main/res/layout/activity_sample.xml
@@ -2,6 +2,6 @@
+ android:background="@color/white">
diff --git a/build-logic/convention/src/main/kotlin/com/hmh/hamyeonham/plugin/CommonConfigs.kt b/build-logic/convention/src/main/kotlin/com/hmh/hamyeonham/plugin/CommonConfigs.kt
index 11c11dd6..cc1306ad 100644
--- a/build-logic/convention/src/main/kotlin/com/hmh/hamyeonham/plugin/CommonConfigs.kt
+++ b/build-logic/convention/src/main/kotlin/com/hmh/hamyeonham/plugin/CommonConfigs.kt
@@ -10,6 +10,10 @@ import java.util.Properties
internal fun Project.configureAndroidCommonPlugin() {
+ val properties = Properties().apply {
+ load(rootProject.file("local.properties").inputStream())
+ }
+
apply()
apply()
with(plugins) {
@@ -18,7 +22,13 @@ internal fun Project.configureAndroidCommonPlugin() {
apply()
extensions.getByType().apply {
- defaultConfig {}
+ defaultConfig {
+ val kakaoApiKey = properties["kakaoApiKey"] as? String ?: ""
+
+ manifestPlaceholders["kakaoApiKey"] = properties["kakaoApiKey"] as String
+
+ buildConfigField("String", "KAKAO_API_KEY", "\"${kakaoApiKey}\"")
+ }
buildFeatures.apply {
viewBinding = true
buildConfig = true
diff --git a/feature/login/.gitignore b/feature/login/.gitignore
new file mode 100644
index 00000000..42afabfd
--- /dev/null
+++ b/feature/login/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/feature/login/build.gradle.kts b/feature/login/build.gradle.kts
new file mode 100644
index 00000000..da2007b1
--- /dev/null
+++ b/feature/login/build.gradle.kts
@@ -0,0 +1,22 @@
+@Suppress("DSL_SCOPE_VIOLATION") // TODO: Remove once KTIJ-19369 is fixed
+plugins {
+ hmh("feature")
+ alias(libs.plugins.kotlin.android)
+}
+
+android {
+ namespace = "com.hmh.hamyeonham.feature.login"
+}
+
+dependencies {
+ implementation(projects.core.common)
+ implementation(libs.appcompat)
+ implementation(libs.material)
+ implementation(libs.constraintlayout)
+
+ // kakao
+ implementation(libs.kakao.login)
+
+ implementation(libs.dot.indicator)
+ implementation(libs.coil.core)
+}
diff --git a/feature/login/consumer-rules.pro b/feature/login/consumer-rules.pro
new file mode 100644
index 00000000..e69de29b
diff --git a/feature/login/proguard-rules.pro b/feature/login/proguard-rules.pro
new file mode 100644
index 00000000..481bb434
--- /dev/null
+++ b/feature/login/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/feature/login/src/main/AndroidManifest.xml b/feature/login/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..ec037070
--- /dev/null
+++ b/feature/login/src/main/AndroidManifest.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/feature/login/src/main/java/com/hmh/hamyeonham/feature/login/LoginActivity.kt b/feature/login/src/main/java/com/hmh/hamyeonham/feature/login/LoginActivity.kt
new file mode 100644
index 00000000..c5fddea4
--- /dev/null
+++ b/feature/login/src/main/java/com/hmh/hamyeonham/feature/login/LoginActivity.kt
@@ -0,0 +1,92 @@
+package com.hmh.hamyeonham.feature.login
+
+import android.content.Intent
+import android.os.Bundle
+import androidx.activity.viewModels
+import androidx.appcompat.app.AppCompatActivity
+import com.hmh.hamyeonham.common.context.toast
+import com.hmh.hamyeonham.feature.login.data.DummyImage
+import com.hmh.hamyeonham.feature.login.databinding.ActivityLoginBinding
+import com.kakao.sdk.auth.model.OAuthToken
+import com.kakao.sdk.common.model.ClientError
+import com.kakao.sdk.common.model.ClientErrorCause
+import com.kakao.sdk.user.UserApiClient
+
+class LoginActivity : AppCompatActivity() {
+ private lateinit var binding: ActivityLoginBinding
+
+ private val loginViewModel: LoginViewModel by viewModels()
+ private lateinit var loginViewPagerAdapter: LoginViewPagerAdapter
+
+ // 삭제 예정
+ private val dummyImageList = listOf(
+ DummyImage(
+ Image = R.drawable.login_sample_rectagle_viewpager,
+ ),
+ DummyImage(
+ Image = R.drawable.login_sample_rectagle_viewpager,
+ ),
+ DummyImage(
+ Image = R.drawable.login_sample_rectagle_viewpager,
+ ),
+ )
+
+ private val callback: (OAuthToken?, Throwable?) -> Unit = { token, error ->
+ when {
+ error != null -> {
+ }
+
+ token != null -> {
+ moveToUserInfoActivity()
+ }
+ }
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ binding = ActivityLoginBinding.inflate(layoutInflater)
+ super.onCreate(savedInstanceState)
+ setContentView(binding.root)
+
+ binding.btnLogin.setOnClickListener {
+ loginWithKakaoApp()
+ }
+ setLoginViewPager()
+ }
+
+ private fun setLoginViewPager() {
+ loginViewPagerAdapter = LoginViewPagerAdapter(dummyImageList)
+ binding.run {
+ vpLogin.adapter = loginViewPagerAdapter
+ indicatorLoginDots.attachTo(binding.vpLogin)
+ }
+ }
+
+ private fun loginWithKakaoApp() {
+ if (UserApiClient.instance.isKakaoTalkLoginAvailable(this)) {
+ UserApiClient.instance.loginWithKakaoTalk(this) { token, error ->
+ if (error != null) {
+ toast("카카오 로그인 실패")
+ if (error is ClientError && error.reason == ClientErrorCause.Cancelled) {
+ toast("다시 로그인 해주세요.")
+ return@loginWithKakaoTalk
+ }
+ loginWithKakaoAccount()
+ } else if (token != null) {
+ toast("카카오 로그인 성공")
+ moveToUserInfoActivity()
+ }
+ }
+ } else {
+ loginWithKakaoAccount()
+ }
+ }
+
+ private fun loginWithKakaoAccount() {
+ UserApiClient.instance.loginWithKakaoAccount(this, callback = callback)
+ }
+
+ private fun moveToUserInfoActivity() {
+ startActivity(Intent(this, UserInfoActivity::class.java))
+ finish()
+ }
+}
diff --git a/feature/login/src/main/java/com/hmh/hamyeonham/feature/login/LoginViewModel.kt b/feature/login/src/main/java/com/hmh/hamyeonham/feature/login/LoginViewModel.kt
new file mode 100644
index 00000000..0d5cdf4c
--- /dev/null
+++ b/feature/login/src/main/java/com/hmh/hamyeonham/feature/login/LoginViewModel.kt
@@ -0,0 +1,12 @@
+package com.hmh.hamyeonham.feature.login
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+
+class LoginViewModel : ViewModel(){
+
+ private val _kakaoLoginResult = MutableLiveData()
+ val kakaoLoginResult: LiveData get() = _kakaoLoginResult
+
+}
diff --git a/feature/login/src/main/java/com/hmh/hamyeonham/feature/login/LoginViewPagerAdapter.kt b/feature/login/src/main/java/com/hmh/hamyeonham/feature/login/LoginViewPagerAdapter.kt
new file mode 100644
index 00000000..328a8d7b
--- /dev/null
+++ b/feature/login/src/main/java/com/hmh/hamyeonham/feature/login/LoginViewPagerAdapter.kt
@@ -0,0 +1,38 @@
+package com.hmh.hamyeonham.feature.login
+
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import androidx.recyclerview.widget.RecyclerView
+import coil.load
+import com.hmh.hamyeonham.feature.login.data.DummyImage
+import com.hmh.hamyeonham.feature.login.databinding.ItemLoginViewPagerBinding
+
+class LoginViewPagerAdapter(private val imageList: List) :
+ RecyclerView.Adapter() {
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PagerViewHolder {
+ val view = LayoutInflater.from(parent.context)
+ .inflate(R.layout.item_login_view_pager, parent, false)
+ return PagerViewHolder(ItemLoginViewPagerBinding.bind(view))
+ }
+
+ class PagerViewHolder(private val binding: ItemLoginViewPagerBinding) :
+ RecyclerView.ViewHolder(binding.root) {
+
+ fun onBindView(imageInfo: DummyImage) {
+ binding.run {
+ ivLoginViewPagerItem.load(imageInfo.Image) {
+ placeholder(R.drawable.login_sample_rectagle_viewpager)
+ error(R.drawable.login_sample_rectagle_viewpager)
+ }
+ }
+ }
+ }
+
+ override fun getItemCount(): Int = imageList.size
+
+ override fun onBindViewHolder(holder: PagerViewHolder, position: Int) {
+ val exploreImage = imageList[position]
+ holder.onBindView(exploreImage)
+ }
+}
diff --git a/feature/login/src/main/java/com/hmh/hamyeonham/feature/login/UserInfoActivity.kt b/feature/login/src/main/java/com/hmh/hamyeonham/feature/login/UserInfoActivity.kt
new file mode 100644
index 00000000..fc020b48
--- /dev/null
+++ b/feature/login/src/main/java/com/hmh/hamyeonham/feature/login/UserInfoActivity.kt
@@ -0,0 +1,53 @@
+package com.hmh.hamyeonham.feature.login
+
+import android.content.Intent
+import android.os.Bundle
+import androidx.activity.viewModels
+import androidx.appcompat.app.AppCompatActivity
+import com.hmh.hamyeonham.common.context.toast
+import com.hmh.hamyeonham.feature.login.databinding.ActivityUserInfoBinding
+import com.kakao.sdk.user.UserApiClient
+
+class UserInfoActivity : AppCompatActivity() {
+ private lateinit var binding: ActivityUserInfoBinding
+
+ private val userInfoViewModel: UserInfoViewModel by viewModels()
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ binding = ActivityUserInfoBinding.inflate(layoutInflater)
+ super.onCreate(savedInstanceState)
+ setContentView(binding.root)
+
+ getKakaoUserNickname()
+ binding.btnLoginLogout.setOnClickListener {
+ logoutFromKakao()
+ }
+ }
+
+ private fun getKakaoUserNickname() {
+ UserApiClient.instance.me { user, error ->
+ if (error != null) {
+ moveToLogin()
+ } else if (user != null) {
+ val kakaoNickname = user.kakaoAccount?.profile?.nickname
+ binding.tvLoginNickname.text = kakaoNickname
+ }
+ }
+ }
+
+ private fun logoutFromKakao() {
+ UserApiClient.instance.logout { error ->
+ if (error != null) {
+ toast("다시 로그아웃 해주세요.")
+ } else {
+ toast("로그아웃 되었습니다.")
+ moveToLogin()
+ }
+ }
+ }
+
+ private fun moveToLogin() {
+ startActivity(Intent(this, LoginActivity::class.java))
+ finish()
+ }
+}
diff --git a/feature/login/src/main/java/com/hmh/hamyeonham/feature/login/UserInfoViewModel.kt b/feature/login/src/main/java/com/hmh/hamyeonham/feature/login/UserInfoViewModel.kt
new file mode 100644
index 00000000..69e9365a
--- /dev/null
+++ b/feature/login/src/main/java/com/hmh/hamyeonham/feature/login/UserInfoViewModel.kt
@@ -0,0 +1,14 @@
+package com.hmh.hamyeonham.feature.login
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+
+class UserInfoViewModel : ViewModel() {
+
+ private val _kakaoLogoutResult = MutableLiveData()
+ val kakaoLogoutResult: LiveData get() = _kakaoLogoutResult
+
+ private val _kakaoUserNickname = MutableLiveData()
+ val kakaoUserNickname: LiveData get() = _kakaoUserNickname
+}
diff --git a/feature/login/src/main/java/com/hmh/hamyeonham/feature/login/data/DummyImage.kt b/feature/login/src/main/java/com/hmh/hamyeonham/feature/login/data/DummyImage.kt
new file mode 100644
index 00000000..807c56a3
--- /dev/null
+++ b/feature/login/src/main/java/com/hmh/hamyeonham/feature/login/data/DummyImage.kt
@@ -0,0 +1,5 @@
+package com.hmh.hamyeonham.feature.login.data
+
+data class DummyImage(
+ val Image: Int,
+)
diff --git a/feature/login/src/main/res/drawable/login_kakao_large_wide.png b/feature/login/src/main/res/drawable/login_kakao_large_wide.png
new file mode 100644
index 00000000..c0c18561
Binary files /dev/null and b/feature/login/src/main/res/drawable/login_kakao_large_wide.png differ
diff --git a/feature/login/src/main/res/drawable/login_sample_rectagle_viewpager.xml b/feature/login/src/main/res/drawable/login_sample_rectagle_viewpager.xml
new file mode 100644
index 00000000..5932a8db
--- /dev/null
+++ b/feature/login/src/main/res/drawable/login_sample_rectagle_viewpager.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/feature/login/src/main/res/layout/activity_login.xml b/feature/login/src/main/res/layout/activity_login.xml
new file mode 100644
index 00000000..b0f8532b
--- /dev/null
+++ b/feature/login/src/main/res/layout/activity_login.xml
@@ -0,0 +1,52 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/feature/login/src/main/res/layout/activity_user_info.xml b/feature/login/src/main/res/layout/activity_user_info.xml
new file mode 100644
index 00000000..8f23b60a
--- /dev/null
+++ b/feature/login/src/main/res/layout/activity_user_info.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/feature/login/src/main/res/layout/item_login_view_pager.xml b/feature/login/src/main/res/layout/item_login_view_pager.xml
new file mode 100644
index 00000000..ec932b78
--- /dev/null
+++ b/feature/login/src/main/res/layout/item_login_view_pager.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/feature/login/src/main/res/values/strings.xml b/feature/login/src/main/res/values/strings.xml
new file mode 100644
index 00000000..e04d8a19
--- /dev/null
+++ b/feature/login/src/main/res/values/strings.xml
@@ -0,0 +1,5 @@
+
+
+ Hello blank fragment
+ Service detailed description
+
\ No newline at end of file
diff --git a/feature/onboarding/src/androidTest/java/com/hmh/hamyeonham/feature/onboarding/ExampleInstrumentedTest.kt b/feature/onboarding/src/androidTest/java/com/hmh/hamyeonham/feature/onboarding/ExampleInstrumentedTest.kt
deleted file mode 100644
index 152c7bd4..00000000
--- a/feature/onboarding/src/androidTest/java/com/hmh/hamyeonham/feature/onboarding/ExampleInstrumentedTest.kt
+++ /dev/null
@@ -1,24 +0,0 @@
-package com.hmh.hamyeonham.feature.onboarding
-
-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.hmh.hamyeonham.feature.onboarding.test", appContext.packageName)
- }
-}
\ No newline at end of file
diff --git a/feature/onboarding/src/main/AndroidManifest.xml b/feature/onboarding/src/main/AndroidManifest.xml
index 0a100119..a2fcd80d 100644
--- a/feature/onboarding/src/main/AndroidManifest.xml
+++ b/feature/onboarding/src/main/AndroidManifest.xml
@@ -1,10 +1,29 @@
-
+
+
+
+
+
-
+
+
+
+
+
+
+
+
diff --git a/feature/onboarding/src/main/java/com/hmh/hamyeonham/feature/onboarding/OnBoardingAccessibilityService.kt b/feature/onboarding/src/main/java/com/hmh/hamyeonham/feature/onboarding/OnBoardingAccessibilityService.kt
new file mode 100644
index 00000000..774a15ed
--- /dev/null
+++ b/feature/onboarding/src/main/java/com/hmh/hamyeonham/feature/onboarding/OnBoardingAccessibilityService.kt
@@ -0,0 +1,19 @@
+package com.hmh.hamyeonham.feature.onboarding
+
+import android.accessibilityservice.AccessibilityService
+import android.util.Log
+import android.view.accessibility.AccessibilityEvent
+import com.hmh.hamyeonham.common.context.toast
+
+class OnBoardingAccessibilityService : AccessibilityService() {
+ override fun onAccessibilityEvent(event: AccessibilityEvent) {
+ if (event.eventType == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
+ val packageName = event.packageName ?: return
+ Log.d("AccessibilityService", "현재 실행 중인 앱 패키지: $packageName")
+ }
+ }
+
+ override fun onInterrupt() {
+
+ }
+}
diff --git a/feature/onboarding/src/main/java/com/hmh/hamyeonham/feature/onboarding/OnBoardingActivity.kt b/feature/onboarding/src/main/java/com/hmh/hamyeonham/feature/onboarding/OnBoardingActivity.kt
index 00e84cf4..3d88903b 100644
--- a/feature/onboarding/src/main/java/com/hmh/hamyeonham/feature/onboarding/OnBoardingActivity.kt
+++ b/feature/onboarding/src/main/java/com/hmh/hamyeonham/feature/onboarding/OnBoardingActivity.kt
@@ -1,21 +1,106 @@
package com.hmh.hamyeonham.feature.onboarding
+import android.app.usage.UsageStatsManager
+import android.content.Context
+import android.content.Intent
+import android.net.Uri
import android.os.Bundle
+import android.provider.Settings
+import androidx.activity.result.ActivityResultLauncher
+import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
+import com.hmh.hamyeonham.common.context.toast
import com.hmh.hamyeonham.feature.onboarding.databinding.ActivityOnBoardingBinding
class OnBoardingActivity : AppCompatActivity() {
+
private lateinit var binding: ActivityOnBoardingBinding
+
+ private val accessibilitySettingsLauncher: ActivityResultLauncher =
+ registerForActivityResult(
+ ActivityResultContracts.StartActivityForResult(),
+ ) {
+ if (isAccessibilityServiceEnabled()) {
+ toast("접근성 서비스가 활성화되었습니다.")
+ } else {
+ toast("접근성 서비스가 활성화되지 않았습니다.")
+ }
+ }
+
override fun onCreate(savedInstanceState: Bundle?) {
binding = ActivityOnBoardingBinding.inflate(layoutInflater)
super.onCreate(savedInstanceState)
setContentView(binding.root)
+ clickRequireAccessibilityBtn()
+ }
- binding.run {
- numberPicker.npCustomHours.minValue = 1
- numberPicker.npCustomHours.maxValue = 6
- numberPicker.npCustomMinutes.minValue = 1
- numberPicker.npCustomMinutes.maxValue = 59
+ private fun clickRequireAccessibilityBtn() {
+ binding.btnAccessibility.setOnClickListener {
+ openAccessibilitySettingsIfNeeded()
+ }
+ binding.btnUsage.setOnClickListener {
+ requestUsageAccessPermission()
}
+ binding.btnDrawOnOthers.setOnClickListener {
+ if (!hasOverlayPermission()) {
+ requestOverlayPermission()
+ } else {
+ toast("다른 앱 위에 그리기 권한이 이미 허용되어 있습니다.")
+ }
+ }
+ }
+
+ private fun requestUsageAccessPermission() {
+ if (!hasUsageStatsPermission()) {
+ try {
+ val packageUri = Uri.parse("package:$packageName")
+ val intent = Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS, packageUri)
+ startActivity(intent)
+ } catch (e: Exception) {
+ val intent = Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS)
+ startActivity(intent)
+ }
+ } else {
+ toast("사용 정보 접근 권한이 이미 허용되어 있습니다.")
+ }
+ }
+
+ private fun requestOverlayPermission() {
+ val packageUri = Uri.parse("package:$packageName")
+ val intent = Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, packageUri)
+ startActivity(intent)
+ }
+
+ private fun hasOverlayPermission(): Boolean {
+ return Settings.canDrawOverlays(this)
+ }
+
+ private fun isAccessibilityServiceEnabled(): Boolean {
+ val service = packageName + "/" + OnBoardingAccessibilityService::class.java.canonicalName
+ val enabledServicesSetting = Settings.Secure.getString(
+ contentResolver,
+ Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
+ )
+ return enabledServicesSetting?.contains(service) == true
+ }
+
+ private fun openAccessibilitySettingsIfNeeded() {
+ if (!isAccessibilityServiceEnabled()) {
+ val intent = Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS)
+ accessibilitySettingsLauncher.launch(intent)
+ } else {
+ toast("접근성 권한이 이미 허용되어 있습니다.")
+ }
+ }
+
+ private fun hasUsageStatsPermission(): Boolean {
+ val usageStatsManager = getSystemService(Context.USAGE_STATS_SERVICE) as UsageStatsManager
+ val time = System.currentTimeMillis()
+ val stats = usageStatsManager.queryUsageStats(
+ UsageStatsManager.INTERVAL_DAILY,
+ time - 1000 * 60,
+ time,
+ )
+ return stats != null && stats.isNotEmpty()
}
}
diff --git a/feature/onboarding/src/main/res/layout/activity_on_boarding.xml b/feature/onboarding/src/main/res/layout/activity_on_boarding.xml
index 4339e9b8..0be76867 100644
--- a/feature/onboarding/src/main/res/layout/activity_on_boarding.xml
+++ b/feature/onboarding/src/main/res/layout/activity_on_boarding.xml
@@ -1,16 +1,38 @@
-
+ android:layout_marginBottom="30dp"/>
+
+
+
\ No newline at end of file
diff --git a/feature/onboarding/src/main/res/layout/dialog_numberpicker_custom.xml b/feature/onboarding/src/main/res/layout/dialog_numberpicker_custom.xml
index 4d6a17b7..fa243454 100644
--- a/feature/onboarding/src/main/res/layout/dialog_numberpicker_custom.xml
+++ b/feature/onboarding/src/main/res/layout/dialog_numberpicker_custom.xml
@@ -12,7 +12,6 @@
android:id="@+id/np_custom_hours"
android:layout_width="50dp"
android:layout_height="100dp"
- android:theme="@style/AppTheme.NumberPicker"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
diff --git a/feature/onboarding/src/main/res/values/strings.xml b/feature/onboarding/src/main/res/values/strings.xml
index 73862c41..ab795438 100644
--- a/feature/onboarding/src/main/res/values/strings.xml
+++ b/feature/onboarding/src/main/res/values/strings.xml
@@ -1 +1,3 @@
-
\ No newline at end of file
+
+ 화면 동작 감지
+
diff --git a/feature/onboarding/src/main/res/xml/accessibility_service_config.xml b/feature/onboarding/src/main/res/xml/accessibility_service_config.xml
new file mode 100644
index 00000000..c150fa1c
--- /dev/null
+++ b/feature/onboarding/src/main/res/xml/accessibility_service_config.xml
@@ -0,0 +1,7 @@
+
+
diff --git a/feature/onboarding/src/test/java/com/hmh/hamyeonham/feature/onboarding/ExampleUnitTest.kt b/feature/onboarding/src/test/java/com/hmh/hamyeonham/feature/onboarding/ExampleUnitTest.kt
deleted file mode 100644
index f726f340..00000000
--- a/feature/onboarding/src/test/java/com/hmh/hamyeonham/feature/onboarding/ExampleUnitTest.kt
+++ /dev/null
@@ -1,17 +0,0 @@
-package com.hmh.hamyeonham.feature.onboarding
-
-import org.junit.Test
-
-import org.junit.Assert.*
-
-/**
- * Example local unit test, which will execute on the development machine (host).
- *
- * See [testing documentation](http://d.android.com/tools/testing).
- */
-class ExampleUnitTest {
- @Test
- fun addition_isCorrect() {
- assertEquals(4, 2 + 2)
- }
-}
\ No newline at end of file
diff --git a/keystore/debug.keystore b/keystore/debug.keystore
new file mode 100644
index 00000000..fad34262
Binary files /dev/null and b/keystore/debug.keystore differ
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 4999d997..9b0c9668 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -13,6 +13,7 @@ dependencyResolutionManagement {
repositories {
google()
mavenCentral()
+ maven { setUrl("https://devrepo.kakao.com/nexus/content/groups/public/") }
}
}
@@ -20,8 +21,11 @@ rootProject.name = "HMH-Android"
include(":app")
include(":core:common")
include(":core:database")
+
include(":feature:onboarding")
include(":feature:main")
+include(":feature:statistics")
+include(":feature:login")
+
include(":data:usagestats")
include(":domain:usagestats")
-include(":feature:statistics")