diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
index 7865aae6..cb15ee1f 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.md
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -7,35 +7,25 @@ assignees: ''
---
-### 발견한 문제
+# ⚠️ Bug Report
----
+## 발견한 문제
-
-### 스크린샷
----
+## 스크린샷
-
-### 플랫폼(Android, iOS, Web)
----
-- 플랫폼:
-- 디바이스:
-- OS:
-- 브라우저:
+## 플랫폼(Android)
-### 기타 정보
+- 디바이스:
----
+## 기타 정보
-- 앱 버전:
+- 앱 버전:
diff --git a/.github/workflows/qa_apk.yml b/.github/workflows/qa_apk.yml
new file mode 100644
index 00000000..d0319d9f
--- /dev/null
+++ b/.github/workflows/qa_apk.yml
@@ -0,0 +1,88 @@
+name: Android APK Build and Slack Upload
+
+on:
+ workflow_dispatch: # 버튼을 누를 시 실행되도록 설정
+ inputs:
+ environment:
+ description: 'Select environment'
+ required: true
+ default: 'qa'
+ type: choice
+ options:
+ - qa
+ - production
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v3
+
+ - name: Cache Gradle packages
+ uses: actions/cache@v2
+ with:
+ path: |
+ ~/.gradle/caches
+ ~/.gradle/wrapper
+ key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties', '**/buildSrc/**/*.kt') }}
+ restore-keys: |
+ ${{ runner.os }}-gradle-
+
+ - name: Set up JDK 17
+ uses: actions/setup-java@v3
+ with:
+ java-version: '17'
+ distribution: 'temurin'
+ cache: gradle
+
+ - name: Create Local Properties
+ run: touch local.properties
+
+ - name: Access Local Properties
+ env:
+ DEV_BASE_URL: ${{ secrets.DEV_BASE_URL }}
+ PROD_BASE_URL: ${{ secrets.PROD_BASE_URL }}
+ KAKAO_APP_KEY: ${{ secrets.KAKAO_NATIVE_APP_KEY }}
+ run: |
+ echo DEV_BASE_URL=\"$DEV_BASE_URL\" >> local.properties
+ echo PROD_BASE_URL=\"$PROD_BASE_URL\" >> local.properties
+ echo KAKAO_NATIVE_APP_KEY=$KAKAO_APP_KEY >> local.properties
+
+ - name: Generate google-services.json
+ run: |
+ echo "$GOOGLE_SERVICE" > app/google-services.json.b64
+ base64 -d -i app/google-services.json.b64 > app/google-services.json
+ env:
+ GOOGLE_SERVICE: ${{ secrets.GOOGLE_SERVICE }}
+
+ - name: Grant execute permission for gradlew
+ run: chmod +x gradlew
+
+ - name: Build Debug APK
+ run: ./gradlew assembleDebug --stacktrace
+
+ - name: Upload APK
+ uses: actions/upload-artifact@v3
+ with:
+ name: app
+ path: app/build/outputs/apk/debug/app-debug.apk
+
+ - name: Slack - Send Msg
+ uses: 8398a7/action-slack@v3
+ with:
+ status: ${{ job.status }}
+ fields: workflow,commit,repo,author,job,ref,took
+ env:
+ SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
+
+ - name: Slack - Upload APK
+ if: github.event_name == 'workflow_dispatch'
+ uses: MeilCli/slack-upload-file@v4
+ with:
+ slack_token: ${{ secrets.SLACK_TOKEN }}
+ channel_id: ${{ secrets.SLACK_CHANNEL }}
+ initial_comment: 'APK 빌드가 완료되었습니다.'
+ file_type: 'apk'
+ file_name: 'app-debug.apk'
+ file_path: './app/build/outputs/apk/debug/app-debug.apk'
diff --git a/.github/workflows/release_tag.yml b/.github/workflows/release_tag.yml
index c841112d..bc7f0c4e 100644
--- a/.github/workflows/release_tag.yml
+++ b/.github/workflows/release_tag.yml
@@ -1,9 +1,12 @@
name: Release Tag
-on:
- push:
- branches: [ "production" ]
-
+#트리거 요소
+on:
+ pull_request:
+ branches:
+ - production
+ types:
+ - closed
jobs:
build:
diff --git a/README.md b/README.md
index 729eb24b..74182d9c 100644
--- a/README.md
+++ b/README.md
@@ -4,21 +4,30 @@
## 📌 Project Init
- 숭실대 학식 리뷰 앱
-- 기간: 2023.03 ~
-- PlayStore : [EAT-SSU](https://play.google.com/store/apps/details?id=com.eassu.android)
+- 기간: 2023.03 ~
+- [PlayStore 바로가기](https://play.google.com/store/apps/details?id=com.eassu.android)
+
+![그래픽이미지](https://github.com/user-attachments/assets/e89f46bb-dece-45a9-a453-a00bf9d463cd)
+
+
## 🛠 Tech Stack
-- `Kotlin`
-- `MVVM` + `Clean Architecture`
-- `Coroutine`
-- `Flow`
-- `UiState`
-- `Hilt`
+- Kotlin
+- MVVM
+- Clean Architecture
+- Coroutine + Flow
+- UiState
+- Hilt
+- xml + viewBinding (+dataBinding)
+- Retrofit2 + Okhttp3
+- Gilde
+- KaKao OAuth SDK
+- Firebase RemoteConfig, Crashlytics
## 🤔 Not Yet..
-- `Modularization`
-- `Jetpack Compose`
-- `DataSource` + `Repository Pattern`
+- Modularization
+- Jetpack Compose
+- DataSource + Repository Pattern
## 📄 Package
```
@@ -44,7 +53,7 @@ com.eatssu.android
## 🤖 Android
-- Android Studio : Android Studio Hedgehog | 2023.1.1 Patch 2
+- Android Studio : Android Studio Koala | 2024.1.1
- JDK : 17
- minSDK : 23
- targetSDK : 34
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 5562fa2e..1e39c1e5 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -15,12 +15,15 @@ android {
namespace = "com.eatssu.android"
compileSdk = 34
+ // S8: API 28
+ // S21: API 33
defaultConfig {
applicationId = "com.eatssu.android"
minSdk = 23
targetSdk = 34
- versionCode = 19
- versionName = "2.0.0"
+ versionCode = 1
+ versionName = "2.1.0"
+
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
@@ -138,14 +141,14 @@ dependencies {
implementation(libs.lifecycle.viewmodel)
implementation(libs.lifecycle.livedata)
- // Firebase
implementation(libs.play.services.base)
- implementation(libs.firebase.config)
+
+ // Firebase
implementation(platform(libs.firebase.bom))
+ implementation(libs.firebase.config)
implementation(libs.firebase.analytics)
implementation(libs.firebase.crashlytics)
-
// Timber for logging
implementation(libs.timber)
}
diff --git a/app/release/output-metadata.json b/app/release/output-metadata.json
deleted file mode 100644
index e9c9601b..00000000
--- a/app/release/output-metadata.json
+++ /dev/null
@@ -1,20 +0,0 @@
-{
- "version": 3,
- "artifactType": {
- "type": "APK",
- "kind": "Directory"
- },
- "applicationId": "com.eatssu.android",
- "variantName": "release",
- "elements": [
- {
- "type": "SINGLE",
- "filters": [],
- "attributes": [],
- "versionCode": 13,
- "versionName": "1.1.11",
- "outputFile": "app-release.apk"
- }
- ],
- "elementType": "File"
-}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index ffd974c0..50bbb7fb 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -2,6 +2,7 @@
+
@@ -17,11 +18,15 @@
android:maxSdkVersion="32" />
-
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/java/com/eatssu/android/App.kt b/app/src/main/java/com/eatssu/android/App.kt
index f46558ab..af142e29 100644
--- a/app/src/main/java/com/eatssu/android/App.kt
+++ b/app/src/main/java/com/eatssu/android/App.kt
@@ -10,7 +10,7 @@ import timber.log.Timber
@HiltAndroidApp
class App: Application() {
companion object{
- lateinit var appContext : Context
+ lateinit var appContext: Context //todo 이거 빼기
}
override fun onCreate() {
diff --git a/app/src/main/java/com/eatssu/android/data/repository/PreferencesRepository.kt b/app/src/main/java/com/eatssu/android/data/repository/PreferencesRepository.kt
new file mode 100644
index 00000000..de5b771b
--- /dev/null
+++ b/app/src/main/java/com/eatssu/android/data/repository/PreferencesRepository.kt
@@ -0,0 +1,31 @@
+// PreferencesRepository.kt
+package com.eatssu.android.data.repository
+
+import android.content.Context
+import androidx.datastore.core.DataStore
+import androidx.datastore.preferences.core.Preferences
+import androidx.datastore.preferences.core.booleanPreferencesKey
+import androidx.datastore.preferences.core.edit
+import androidx.datastore.preferences.preferencesDataStore
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+
+class PreferencesRepository(private val context: Context) {
+
+ private val Context.dataStore: DataStore by preferencesDataStore(name = "settings")
+
+ companion object {
+ private val DAILY_NOTIFICATION_KEY = booleanPreferencesKey("daily_notification")
+ }
+
+ val dailyNotificationStatus: Flow = context.dataStore.data
+ .map { preferences ->
+ preferences[DAILY_NOTIFICATION_KEY] ?: false // Default value is false
+ }
+
+ suspend fun setDailyNotificationStatus(status: Boolean) {
+ context.dataStore.edit { preferences ->
+ preferences[DAILY_NOTIFICATION_KEY] = status
+ }
+ }
+}
diff --git a/app/src/main/java/com/eatssu/android/data/usecase/AlarmUsecase.kt b/app/src/main/java/com/eatssu/android/data/usecase/AlarmUsecase.kt
new file mode 100644
index 00000000..94a65105
--- /dev/null
+++ b/app/src/main/java/com/eatssu/android/data/usecase/AlarmUsecase.kt
@@ -0,0 +1,44 @@
+package com.eatssu.android.data.usecase
+
+import android.app.AlarmManager
+import android.app.PendingIntent
+import android.content.Context
+import android.content.Intent
+import com.eatssu.android.util.NotificationReceiver
+import java.util.Calendar
+import javax.inject.Inject
+
+class AlarmUseCase @Inject constructor(private val context: Context) {
+
+ fun scheduleAlarm() {
+ val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
+ val intent = Intent(context, NotificationReceiver::class.java)
+ val pendingIntent = PendingIntent.getBroadcast(
+ context, 0, intent, PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
+ )
+
+ val calendar = Calendar.getInstance().apply {
+ set(Calendar.HOUR_OF_DAY, 11)
+ set(Calendar.MINUTE, 0)
+ set(Calendar.SECOND, 0)
+ set(Calendar.MILLISECOND, 0)
+ }
+
+ if (calendar.timeInMillis <= System.currentTimeMillis()) {
+ calendar.add(Calendar.DAY_OF_YEAR, 1)
+ }
+
+ alarmManager.setRepeating(
+ AlarmManager.RTC_WAKEUP, calendar.timeInMillis, AlarmManager.INTERVAL_DAY, pendingIntent
+ )
+ }
+
+ fun cancelAlarm() {
+ val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
+ val intent = Intent(context, NotificationReceiver::class.java)
+ val pendingIntent = PendingIntent.getBroadcast(
+ context, 0, intent, PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
+ )
+ alarmManager.cancel(pendingIntent)
+ }
+}
diff --git a/app/src/main/java/com/eatssu/android/data/usecase/GetDailyNotificationStatusUseCase.kt b/app/src/main/java/com/eatssu/android/data/usecase/GetDailyNotificationStatusUseCase.kt
new file mode 100644
index 00000000..de4cd4e4
--- /dev/null
+++ b/app/src/main/java/com/eatssu/android/data/usecase/GetDailyNotificationStatusUseCase.kt
@@ -0,0 +1,13 @@
+package com.eatssu.android.data.usecase
+
+import com.eatssu.android.data.repository.PreferencesRepository
+import kotlinx.coroutines.flow.Flow
+import javax.inject.Inject
+
+class GetDailyNotificationStatusUseCase @Inject constructor(
+ private val preferencesRepository: PreferencesRepository
+) {
+ operator fun invoke(): Flow {
+ return preferencesRepository.dailyNotificationStatus
+ }
+}
diff --git a/app/src/main/java/com/eatssu/android/data/usecase/SetDailyNotificationStatusUseCase.kt b/app/src/main/java/com/eatssu/android/data/usecase/SetDailyNotificationStatusUseCase.kt
new file mode 100644
index 00000000..4eba7b51
--- /dev/null
+++ b/app/src/main/java/com/eatssu/android/data/usecase/SetDailyNotificationStatusUseCase.kt
@@ -0,0 +1,13 @@
+package com.eatssu.android.data.usecase
+
+import com.eatssu.android.data.repository.PreferencesRepository
+import javax.inject.Inject
+
+
+class SetDailyNotificationStatusUseCase @Inject constructor(
+ private val preferencesRepository: PreferencesRepository
+) {
+ suspend operator fun invoke(status: Boolean) {
+ preferencesRepository.setDailyNotificationStatus(status)
+ }
+}
diff --git a/app/src/main/java/com/eatssu/android/di/AppModule.kt b/app/src/main/java/com/eatssu/android/di/AppModule.kt
new file mode 100644
index 00000000..5b4f9e17
--- /dev/null
+++ b/app/src/main/java/com/eatssu/android/di/AppModule.kt
@@ -0,0 +1,28 @@
+package com.eatssu.android.di
+
+import android.app.Application
+import android.content.Context
+import com.eatssu.android.data.repository.PreferencesRepository
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.android.qualifiers.ApplicationContext
+import dagger.hilt.components.SingletonComponent
+import javax.inject.Singleton
+
+@Module
+@InstallIn(SingletonComponent::class)
+object AppModule {
+
+ @Provides
+ @Singleton
+ fun provideContext(application: Application): Context {
+ return application.applicationContext
+ }
+
+ @Provides
+ @Singleton
+ fun providePreferencesRepository(@ApplicationContext context: Context): PreferencesRepository {
+ return PreferencesRepository(context)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/eatssu/android/ui/login/LoginActivity.kt b/app/src/main/java/com/eatssu/android/ui/login/LoginActivity.kt
index caf6e26a..82cd2393 100644
--- a/app/src/main/java/com/eatssu/android/ui/login/LoginActivity.kt
+++ b/app/src/main/java/com/eatssu/android/ui/login/LoginActivity.kt
@@ -5,7 +5,7 @@ import android.view.View
import androidx.activity.viewModels
import androidx.lifecycle.lifecycleScope
import com.eatssu.android.base.BaseActivity
-import com.eatssu.android.databinding.ActivitySocialLoginBinding
+import com.eatssu.android.databinding.ActivityLoginBinding
import com.eatssu.android.ui.main.MainActivity
import com.eatssu.android.util.extension.showToast
import com.eatssu.android.util.extension.startActivity
@@ -20,7 +20,7 @@ import timber.log.Timber
@AndroidEntryPoint
class LoginActivity :
- BaseActivity(ActivitySocialLoginBinding::inflate) {
+ BaseActivity(ActivityLoginBinding::inflate) {
private val loginViewModel: LoginViewModel by viewModels()
diff --git a/app/src/main/java/com/eatssu/android/ui/main/MainActivity.kt b/app/src/main/java/com/eatssu/android/ui/main/MainActivity.kt
index 62066911..a7d1ab43 100644
--- a/app/src/main/java/com/eatssu/android/ui/main/MainActivity.kt
+++ b/app/src/main/java/com/eatssu/android/ui/main/MainActivity.kt
@@ -2,6 +2,7 @@ package com.eatssu.android.ui.main
import android.annotation.SuppressLint
import android.content.Intent
+import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
import android.view.Menu
@@ -10,6 +11,8 @@ import android.view.View
import android.widget.TextView
import androidx.activity.viewModels
import androidx.annotation.RequiresApi
+import androidx.core.app.ActivityCompat
+import androidx.core.content.ContextCompat
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.GridLayoutManager
@@ -22,6 +25,7 @@ import com.eatssu.android.ui.main.calendar.CalendarAdapter
import com.eatssu.android.ui.main.calendar.CalendarAdapter.OnItemListener
import com.eatssu.android.ui.main.calendar.CalendarViewModel
import com.eatssu.android.ui.mypage.MyPageActivity
+import com.eatssu.android.ui.mypage.MyPageViewModel
import com.eatssu.android.ui.mypage.usernamechange.UserNameChangeActivity
import com.eatssu.android.util.CalendarUtils
import com.eatssu.android.util.CalendarUtils.daysInWeekArray
@@ -30,18 +34,20 @@ import com.eatssu.android.util.extension.showToast
import com.eatssu.android.util.extension.startActivity
import com.google.android.material.tabs.TabLayout
import com.google.android.material.tabs.TabLayoutMediator
-import com.prolificinteractive.materialcalendarview.*
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import timber.log.Timber
+import java.text.SimpleDateFormat
import java.time.LocalDate
-import java.util.*
+import java.util.Locale
@AndroidEntryPoint
class MainActivity : BaseActivity(ActivityMainBinding::inflate), OnItemListener {
private val mainViewModel: MainViewModel by viewModels()
+ private val myPageViewModel: MyPageViewModel by viewModels()
+
private lateinit var calendarViewModel: CalendarViewModel
@@ -57,6 +63,24 @@ class MainActivity : BaseActivity(ActivityMainBinding::infl
setupNoToolbar()
+ // 알림 퍼미션 있는지 자가 진단
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ if (ContextCompat.checkSelfPermission(
+ this,
+ android.Manifest.permission.POST_NOTIFICATIONS
+ ) != PackageManager.PERMISSION_GRANTED
+ ) {
+ // 권한이 없다면 요청
+ ActivityCompat.requestPermissions(
+ this,
+ arrayOf(android.Manifest.permission.POST_NOTIFICATIONS),
+ 1000
+ )
+ } else {
+ // 권한이 이미 있어
+ }
+ }
+
checkNicknameIsNull()
// 1) ViewPager2 참조
@@ -192,4 +216,26 @@ class MainActivity : BaseActivity(ActivityMainBinding::infl
}
}
+ // 권한 요청 결과 처리
+ override fun onRequestPermissionsResult(
+ requestCode: Int,
+ permissions: Array,
+ grantResults: IntArray
+ ) {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults)
+
+ val dateFormat = SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.getDefault())
+
+ if (requestCode == 1000) {
+ if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
+ // 권한이 승인됨
+ showToast("EAT-SSU 알림 수신을 동의하였습니다.")
+ myPageViewModel.setNotificationOn() //바로 알림 받도록 설정
+ } else {
+ // 권한이 거부됨
+ showToast("EAT-SSU 알림 수신을 거부하였습니다.\n$dateFormat")
+ myPageViewModel.setNotificationOff() //바로 알림 받도록 설정
+ }
+ }
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/eatssu/android/ui/main/calendar/CalendarAdapter.kt b/app/src/main/java/com/eatssu/android/ui/main/calendar/CalendarAdapter.kt
index 421ee62d..372ad2b6 100644
--- a/app/src/main/java/com/eatssu/android/ui/main/calendar/CalendarAdapter.kt
+++ b/app/src/main/java/com/eatssu/android/ui/main/calendar/CalendarAdapter.kt
@@ -1,7 +1,9 @@
package com.eatssu.android.ui.main.calendar
+import android.os.Build
import android.view.LayoutInflater
import android.view.ViewGroup
+import androidx.annotation.RequiresApi
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.RecyclerView
import com.eatssu.android.R
@@ -32,18 +34,36 @@ internal class CalendarAdapter(
}
+ @RequiresApi(Build.VERSION_CODES.O)
override fun onBindViewHolder(holder: CalendarViewHolder, position: Int) {
val date = days[position]
holder.dayOfMonth.text = date.dayOfMonth.toString()
holder.dayText.text =
date.dayOfWeek.getDisplayName(TextStyle.SHORT, Locale.KOREAN).toString()
- if (date == CalendarUtils.selectedDate) {
- //날짜
+ /**
+ * iOS의 FSCalendar를 Custom으로 만들었습니다.
+ * 1. 선택한 날짜는 primary color로 select합니다.
+ * 2. 오늘 날짜 != 선택한 날짜 일 경우에는, 오늘 날짜의 text 색상을 primary color 표기하여, 오늘 날짜를 강조합니다.
+ */
+
+ if (date == CalendarUtils.selectedDate) { //셀렉트 된 날짜
holder.dayOfMonth.setBackgroundResource(R.drawable.selector_background_blue)
- holder.dayOfMonth.setTextColor(ContextCompat.getColor(holder.itemView.context, R.color.selector_calendar_colortext))
- }
- else {
+ holder.dayOfMonth.setTextColor(
+ ContextCompat.getColor(
+ holder.itemView.context,
+ R.color.selector_calendar_colortext
+ )
+ )
+ } else if (date == LocalDate.now() && date != CalendarUtils.selectedDate) {
+ //오늘 날짜가 선택 되지 않았을 때, 오늘 날 text 색 지정
+ holder.dayOfMonth.setTextColor(
+ ContextCompat.getColor(
+ holder.itemView.context,
+ R.color.primary
+ )
+ )
+ } else { //다른 날짜들
holder.parentView.setBackgroundResource(R.drawable.ic_selector_background_white)
}
}
diff --git a/app/src/main/java/com/eatssu/android/ui/mypage/MyPageActivity.kt b/app/src/main/java/com/eatssu/android/ui/mypage/MyPageActivity.kt
index eaa816d9..4645b303 100644
--- a/app/src/main/java/com/eatssu/android/ui/mypage/MyPageActivity.kt
+++ b/app/src/main/java/com/eatssu/android/ui/mypage/MyPageActivity.kt
@@ -1,19 +1,26 @@
package com.eatssu.android.ui.mypage
+
+import android.app.Activity
+import android.content.Context
import android.content.Intent
+import android.content.pm.PackageManager
+import android.graphics.Paint
import android.net.Uri
+import android.os.Build
import android.os.Bundle
+import android.provider.Settings
import androidx.activity.viewModels
+import androidx.annotation.RequiresApi
import androidx.appcompat.app.AlertDialog
-import androidx.lifecycle.ViewModelProvider
+import androidx.core.app.ActivityCompat
+import androidx.core.content.ContextCompat
+import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
-import com.eatssu.android.BuildConfig
+import androidx.lifecycle.repeatOnLifecycle
import com.eatssu.android.R
import com.eatssu.android.base.BaseActivity
-import com.eatssu.android.data.repository.FirebaseRemoteConfigRepository
import com.eatssu.android.databinding.ActivityMyPageBinding
-import com.eatssu.android.ui.common.VersionViewModel
-import com.eatssu.android.ui.common.VersionViewModelFactory
import com.eatssu.android.ui.login.LoginActivity
import com.eatssu.android.ui.mypage.myreview.MyReviewListActivity
import com.eatssu.android.ui.mypage.terms.WebViewActivity
@@ -23,41 +30,56 @@ import com.eatssu.android.util.extension.startActivity
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
-import timber.log.Timber
@AndroidEntryPoint
class MyPageActivity : BaseActivity(ActivityMyPageBinding::inflate) {
private val myPageViewModel: MyPageViewModel by viewModels()
- private lateinit var versionViewModel: VersionViewModel
-
- private lateinit var firebaseRemoteConfigRepository: FirebaseRemoteConfigRepository
-
+ @RequiresApi(Build.VERSION_CODES.O)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
toolbarTitle.text = "마이페이지" // 툴바 제목 설정
-
- initViewModel()
+ binding.tvSignout.paintFlags = Paint.UNDERLINE_TEXT_FLAG
+ setupObservers()
setOnClickListener()
- setData()
- }
-
- override fun onResume() {
- super.onResume()
- setData() //Todo 최선일까?
}
- override fun onRestart() {
- super.onRestart()
+ private fun setupObservers() {
+ lifecycleScope.launch {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ myPageViewModel.uiState.collect {
+ binding.tvAppVersion.text = it.appVersion
+
+ if (it.nickname.isNotEmpty()) {
+ binding.tvNickname.text = it.nickname
+ }
- setData()
+ binding.alarmSwitch.isChecked = it.isAlarmOn
+ }
+ }
+ }
}
+ @RequiresApi(Build.VERSION_CODES.O)
private fun setOnClickListener() {
+ binding.alarmSwitch.setOnCheckedChangeListener { _, isChecked ->
+ if (isChecked) {
+ if (checkNotificationPermission(this)) { //허용되어 있는 상태
+ myPageViewModel.setNotificationOn()
+ showToast("EAT-SSU 알림 수신을 동의하였습니다.")
+ } else { // 알림 권한이 없을 때 사용자에게 설정 화면으로 이동하라고 알림
+ showNotificationPermissionDialog()
+ }
+ } else {
+ myPageViewModel.setNotificationOff()
+ showToast("EAT-SSU 알림 수신을 거부하였습니다.")
+ }
+ }
+
binding.llNickname.setOnClickListener {
startActivity()
}
@@ -77,18 +99,17 @@ class MyPageActivity : BaseActivity(ActivityMyPageBinding
showLogoutDialog()
}
- binding.tvSignout.setOnClickListener {
+ binding.llSignout.setOnClickListener {
val intent = Intent(this, SignOutActivity::class.java)
intent.putExtra("nickname", myPageViewModel.uiState.value.nickname)
startActivity(intent)
-// showSignoutDialog()
}
binding.llDeveloper.setOnClickListener {
startActivity()
}
- binding.llStoreAppVersion.setOnClickListener {
+ binding.llAppVersion.setOnClickListener {
moveToPlayStore()
}
@@ -108,34 +129,29 @@ class MyPageActivity : BaseActivity(ActivityMyPageBinding
}
- private fun setData() {
- binding.tvAppVersion.text = BuildConfig.VERSION_NAME + " (" + BuildConfig.VERSION_CODE + ")"
- binding.tvStoreAppVersion.text = versionViewModel.checkVersionCode().toString()
-
- myPageViewModel.getMyInfo()
-
- lifecycleScope.launch {
- Timber.d("관찰시작")
- myPageViewModel.uiState.collectLatest {
- if (it.nickname.isNotEmpty()) {
- binding.tvNickname.text = it.nickname
- }
+ @RequiresApi(Build.VERSION_CODES.O)
+ private fun showNotificationPermissionDialog() {
+ AlertDialog.Builder(this)
+ .setTitle("알림 권한 필요")
+ .setMessage("알림을 받으려면 알림 권한을 활성화해야 합니다. 설정 화면으로 이동하시겠습니까?")
+ .setPositiveButton("설정으로 이동") { _, _ ->
+ openAppNotificationSettings(this)
}
- }
+ .setNegativeButton("취소", null)
+ .show()
}
- private fun initViewModel() { //Todo 리팩토링하기
-
- firebaseRemoteConfigRepository = FirebaseRemoteConfigRepository()
-
- versionViewModel = ViewModelProvider(
- this,
- VersionViewModelFactory(firebaseRemoteConfigRepository)
- )[VersionViewModel::class.java]
+ private fun checkNotificationPermission(context: Context): Boolean {
+ return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ ContextCompat.checkSelfPermission(
+ context,
+ android.Manifest.permission.POST_NOTIFICATIONS
+ ) == PackageManager.PERMISSION_GRANTED
+ } else {
+ true // Android 13 이전 버전에서는 알림 권한이 필요하지 않음
+ }
}
-
-
private fun showLogoutDialog() {
// 다이얼로그를 생성하기 위해 Builder 클래스 생성자를 이용해 줍니다.
@@ -213,4 +229,50 @@ class MyPageActivity : BaseActivity(ActivityMyPageBinding
)
}
}
+
+ // 알림 권한 요청 함수
+ private fun requestNotificationPermission(activity: Activity) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ ActivityCompat.requestPermissions(
+ activity,
+ arrayOf(android.Manifest.permission.POST_NOTIFICATIONS),
+ REQUEST_NOTIFICATION_PERMISSION
+ )
+ }
+ }
+
+
+ @RequiresApi(Build.VERSION_CODES.O)
+ override fun onRequestPermissionsResult(
+ requestCode: Int,
+ permissions: Array,
+ grantResults: IntArray
+ ) {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults)
+ if (requestCode == REQUEST_NOTIFICATION_PERMISSION) {
+ if ((grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED)) {
+ // 권한이 허용되었을 때 알림 설정
+ myPageViewModel.setNotificationOn()
+ } else {
+ // 권한이 거부되었을 때 처리
+ showToast("EAT-SSU 알림 수신을 거부하였습니다.")
+ openAppNotificationSettings(this)
+ myPageViewModel.setNotificationOff()
+ }
+ }
+ }
+
+
+ @RequiresApi(Build.VERSION_CODES.O)
+ private fun openAppNotificationSettings(context: Context) {
+ val intent = Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS).apply {
+ putExtra(Settings.EXTRA_APP_PACKAGE, context.packageName)
+ }
+ context.startActivity(intent)
+ }
+
+
+ companion object {
+ private const val REQUEST_NOTIFICATION_PERMISSION = 1001
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/eatssu/android/ui/mypage/MyPageViewModel.kt b/app/src/main/java/com/eatssu/android/ui/mypage/MyPageViewModel.kt
index b0c840fb..923213e3 100644
--- a/app/src/main/java/com/eatssu/android/ui/mypage/MyPageViewModel.kt
+++ b/app/src/main/java/com/eatssu/android/ui/mypage/MyPageViewModel.kt
@@ -3,9 +3,14 @@ package com.eatssu.android.ui.mypage
import android.util.Log
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
+import com.eatssu.android.BuildConfig
+import com.eatssu.android.data.repository.PreferencesRepository
+import com.eatssu.android.data.usecase.AlarmUseCase
+import com.eatssu.android.data.usecase.GetDailyNotificationStatusUseCase
import com.eatssu.android.data.usecase.GetUserInfoUseCase
import com.eatssu.android.data.usecase.LogoutUseCase
import com.eatssu.android.data.usecase.SetAccessTokenUseCase
+import com.eatssu.android.data.usecase.SetDailyNotificationStatusUseCase
import com.eatssu.android.data.usecase.SetRefreshTokenUseCase
import com.eatssu.android.data.usecase.SignOutUseCase
import dagger.hilt.android.lifecycle.HiltViewModel
@@ -27,16 +32,37 @@ class MyPageViewModel @Inject constructor(
private val getUserInfoUseCase: GetUserInfoUseCase,
private val setAccessTokenUseCase: SetAccessTokenUseCase,
private val setRefreshTokenUseCase: SetRefreshTokenUseCase,
+ private val setNotificationStatusUseCase: SetDailyNotificationStatusUseCase,
+ private val getDailyNotificationStatusUseCase: GetDailyNotificationStatusUseCase,
+ private val alarmUseCase: AlarmUseCase,
+ private val preferencesRepository: PreferencesRepository // Assuming you're using DataStore here
) : ViewModel() {
private val _uiState: MutableStateFlow = MutableStateFlow(MyPageState())
val uiState: StateFlow = _uiState.asStateFlow()
init {
+ setAppVersion()
getMyInfo()
+ getNotificationStatus()
}
- fun getMyInfo() {
+ private fun setAppVersion() {
+ viewModelScope.launch {
+ _uiState.value =
+ _uiState.value.copy(appVersion = "${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE})")
+ }
+ }
+
+ private fun getNotificationStatus() {
+ viewModelScope.launch {
+ preferencesRepository.dailyNotificationStatus.collect { isAlarmOn ->
+ _uiState.value = _uiState.value.copy(isAlarmOn = isAlarmOn)
+ }
+ }
+ }
+
+ private fun getMyInfo() {
viewModelScope.launch {
getUserInfoUseCase().onStart {
_uiState.update { it.copy(loading = true) }
@@ -87,7 +113,6 @@ class MyPageViewModel @Inject constructor(
}
}
-
fun signOut() {
viewModelScope.launch {
signOutUseCase().onStart {
@@ -113,6 +138,22 @@ class MyPageViewModel @Inject constructor(
}
}
+ fun setNotificationOn() {
+ viewModelScope.launch {
+ setNotificationStatusUseCase(true) //로컬 디비 저장
+ alarmUseCase.scheduleAlarm() //알람 매니저
+ }
+ }
+
+ fun setNotificationOff() {
+ viewModelScope.launch {
+ setNotificationStatusUseCase(false)
+ alarmUseCase.cancelAlarm()
+ }
+ }
+
+
+
companion object {
val TAG = "MyPageViewModel"
}
@@ -127,6 +168,8 @@ data class MyPageState(
var nickname: String = "",
var platform: String = "",
+ var isAlarmOn: Boolean = false,
+ var appVersion: String = "0.0.0",
var isNicknameNull: Boolean = false,
var isLoginOuted: Boolean = false,
diff --git a/app/src/main/java/com/eatssu/android/util/MySharedPreferences.kt b/app/src/main/java/com/eatssu/android/util/MySharedPreferences.kt
index 30a3a4e0..241f0de5 100644
--- a/app/src/main/java/com/eatssu/android/util/MySharedPreferences.kt
+++ b/app/src/main/java/com/eatssu/android/util/MySharedPreferences.kt
@@ -69,7 +69,7 @@ object MySharedPreferences {
context.getSharedPreferences(MY_ACCOUNT, Context.MODE_PRIVATE)
val editor: SharedPreferences.Editor = prefs.edit()
editor.putString("REFRESH_TOKEN", input)
- editor.commit()
+ editor.apply()
}
fun getRefreshToken(context: Context): String {
@@ -83,6 +83,21 @@ object MySharedPreferences {
context.getSharedPreferences(MY_ACCOUNT, Context.MODE_PRIVATE)
val editor: SharedPreferences.Editor = prefs.edit()
editor.clear()
- editor.commit()
+ editor.apply()
+ }
+
+ fun setDailyNotification(context: Context, input: Boolean) {
+ val prefs: SharedPreferences =
+ context.getSharedPreferences(MY_ACCOUNT, Context.MODE_PRIVATE)
+ val editor: SharedPreferences.Editor = prefs.edit()
+
+ editor.putBoolean("ALARM_ON", input)
+ editor.apply()
+ }
+
+ fun getDailyNotification(context: Context): Boolean {
+ val prefs: SharedPreferences =
+ context.getSharedPreferences(MY_ACCOUNT, Context.MODE_PRIVATE)
+ return prefs.getBoolean("ALARM_ON", false)
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/eatssu/android/util/NotificationReceiver.kt b/app/src/main/java/com/eatssu/android/util/NotificationReceiver.kt
new file mode 100644
index 00000000..3d40135d
--- /dev/null
+++ b/app/src/main/java/com/eatssu/android/util/NotificationReceiver.kt
@@ -0,0 +1,68 @@
+package com.eatssu.android.util
+
+import android.app.NotificationChannel
+import android.app.NotificationManager
+import android.app.PendingIntent
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.os.Build
+import androidx.core.app.NotificationCompat
+import com.eatssu.android.R
+import com.eatssu.android.ui.main.MainActivity
+
+class NotificationReceiver : BroadcastReceiver() {
+
+ override fun onReceive(context: Context, intent: Intent) {
+ showNotification(context)
+ }
+
+ private fun showNotification(context: Context) {
+ val notificationManager =
+ context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ val channel = NotificationChannel(
+ CHANNEL_ID,
+ "점심시간 전 알림",
+ NotificationManager.IMPORTANCE_HIGH // 중요도를 높게 설정
+ ).apply {
+ description = "점심시간 전, 푸시알림을 발송합니다."
+ enableLights(true)
+ enableVibration(true) // 진동도 활성화
+ lockscreenVisibility = NotificationCompat.VISIBILITY_PUBLIC // 잠금 화면에서도 표시
+ }
+ notificationManager.createNotificationChannel(channel)
+ }
+
+
+ val intent = Intent(context, MainActivity::class.java).apply {
+ flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
+ }
+
+ val pendingIntent = PendingIntent.getActivity(
+ context,
+ 0,
+ intent,
+ PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
+ )
+
+ val notification = NotificationCompat.Builder(context, CHANNEL_ID)
+ .setSmallIcon(R.drawable.ic_alarm_logo)
+ .setContentTitle(context.getString(R.string.notification_context_title))
+ .setContentText(context.getString(R.string.notification_context_text))
+ .setPriority(NotificationCompat.PRIORITY_HIGH)
+ .setCategory(NotificationCompat.CATEGORY_ALARM)
+ .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
+ .setAutoCancel(true)
+ .setContentIntent(pendingIntent)
+ .build()
+
+ notificationManager.notify(NOTIFICATION_ID, notification)
+ }
+
+ companion object {
+ private const val CHANNEL_ID = "DailyNotificationChannel"
+ private const val NOTIFICATION_ID = 1
+ }
+}
diff --git a/app/src/main/res/drawable/ic_alarm_logo.xml b/app/src/main/res/drawable/ic_alarm_logo.xml
new file mode 100644
index 00000000..9de0b64c
--- /dev/null
+++ b/app/src/main/res/drawable/ic_alarm_logo.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_unsubscribe_16.png b/app/src/main/res/drawable/ic_unsubscribe_16.png
new file mode 100644
index 00000000..dc3f0fad
Binary files /dev/null and b/app/src/main/res/drawable/ic_unsubscribe_16.png differ
diff --git a/app/src/main/res/drawable/img_kakao_login_btn.png b/app/src/main/res/drawable/img_kakao_login_btn.png
new file mode 100644
index 00000000..932d4b0f
Binary files /dev/null and b/app/src/main/res/drawable/img_kakao_login_btn.png differ
diff --git a/app/src/main/res/drawable/img_kakao_login_large_wide.png b/app/src/main/res/drawable/img_kakao_login_large_wide.png
deleted file mode 100644
index 77f6df4d..00000000
Binary files a/app/src/main/res/drawable/img_kakao_login_large_wide.png and /dev/null differ
diff --git a/app/src/main/res/drawable/img_logo2.png b/app/src/main/res/drawable/img_logo2.png
deleted file mode 100644
index eda4a6fa..00000000
Binary files a/app/src/main/res/drawable/img_logo2.png and /dev/null differ
diff --git a/app/src/main/res/drawable/img_look.png b/app/src/main/res/drawable/img_look.png
deleted file mode 100644
index 682b8701..00000000
Binary files a/app/src/main/res/drawable/img_look.png and /dev/null differ
diff --git a/app/src/main/res/drawable/img_new_logo_primary.png b/app/src/main/res/drawable/img_new_logo_primary.png
new file mode 100644
index 00000000..3e3f2ef8
Binary files /dev/null and b/app/src/main/res/drawable/img_new_logo_primary.png differ
diff --git a/app/src/main/res/drawable/img_new_logo_white.png b/app/src/main/res/drawable/img_new_logo_white.png
new file mode 100644
index 00000000..6cceac1d
Binary files /dev/null and b/app/src/main/res/drawable/img_new_logo_white.png differ
diff --git a/app/src/main/res/drawable/img_splash.png b/app/src/main/res/drawable/img_splash.png
deleted file mode 100644
index 5b0aac6f..00000000
Binary files a/app/src/main/res/drawable/img_splash.png and /dev/null differ
diff --git a/app/src/main/res/drawable/imgkakao_login_large.png b/app/src/main/res/drawable/imgkakao_login_large.png
deleted file mode 100644
index 4e8c55c8..00000000
Binary files a/app/src/main/res/drawable/imgkakao_login_large.png and /dev/null differ
diff --git a/app/src/main/res/drawable/selector_toggle.xml b/app/src/main/res/drawable/selector_toggle.xml
new file mode 100644
index 00000000..7d1ff9df
--- /dev/null
+++ b/app/src/main/res/drawable/selector_toggle.xml
@@ -0,0 +1,17 @@
+
+
+ -
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/shape_toggle.xml b/app/src/main/res/drawable/shape_toggle.xml
new file mode 100644
index 00000000..46a1241c
--- /dev/null
+++ b/app/src/main/res/drawable/shape_toggle.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_intro.xml b/app/src/main/res/layout/activity_intro.xml
index b0acc86d..01130b7d 100644
--- a/app/src/main/res/layout/activity_intro.xml
+++ b/app/src/main/res/layout/activity_intro.xml
@@ -4,13 +4,15 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
+ android:background="@color/primary"
tools:context=".ui.login.IntroActivity">
-
+ app:layout_constraintTop_toBottomOf="@+id/iv_logo">
-
+
+
+
+
+
-
-
-
+ app:layout_constraintStart_toStartOf="parent" />
-
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
index 24ae615b..a6d59059 100644
--- a/app/src/main/res/layout/activity_main.xml
+++ b/app/src/main/res/layout/activity_main.xml
@@ -24,7 +24,7 @@
android:layout_width="wrap_content"
android:layout_height="28dp"
android:background="@color/white"
- android:src="@drawable/img_logo2"
+ android:src="@drawable/img_new_logo_primary"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
diff --git a/app/src/main/res/layout/activity_my_page.xml b/app/src/main/res/layout/activity_my_page.xml
index 6c6ce5a4..aff22628 100644
--- a/app/src/main/res/layout/activity_my_page.xml
+++ b/app/src/main/res/layout/activity_my_page.xml
@@ -13,15 +13,13 @@
+ android:orientation="vertical">
@@ -39,9 +37,9 @@
@@ -63,7 +61,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
- android:layout_marginTop="16dp">
+ android:layout_marginTop="4dp">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -130,7 +169,7 @@
android:background="@android:color/transparent"
android:scaleType="fitCenter"
android:src="@drawable/ic_arrow_right"
- android:tint="@color/gray300" />
+ app:tint="@color/gray300" />
@@ -165,8 +204,7 @@
android:background="@android:color/transparent"
android:scaleType="fitCenter"
android:src="@drawable/ic_arrow_right"
- android:tint="@color/gray300" />
-
+ app:tint="@color/gray300" />
@@ -201,7 +239,7 @@
android:background="@android:color/transparent"
android:scaleType="fitCenter"
android:src="@drawable/ic_arrow_right"
- android:tint="@color/gray300" />
+ app:tint="@color/gray300" />
@@ -216,7 +254,6 @@
android:paddingEnd="24dp"
android:paddingBottom="18dp">
-
+ app:tint="@color/gray300" />
-
-
-
-
-
-
-
+ app:tint="@color/gray300" />
-
+
+ android:paddingBottom="18dp"
+ android:text="@string/logout"
+ android:textColor="@color/black" />
+
+
@@ -333,52 +356,41 @@
style="@style/Caption2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginEnd="16dp"
- android:textColor="@color/black"
+ android:textColor="@color/gray400"
app:layout_constraintBottom_toBottomOf="@+id/tv_app_version_title"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@+id/tv_app_version_title" />
+
-
-
-
-
+ android:paddingBottom="10dp">
+ android:background="?android:attr/selectableItemBackground"
+ android:gravity="end"
+ android:text="@string/signout"
+ android:textColor="@color/gray400" />
+
+
-
-
diff --git a/app/src/main/res/values-night/themes.xml b/app/src/main/res/values-night/themes.xml
index 7eab13c7..e2b97086 100644
--- a/app/src/main/res/values-night/themes.xml
+++ b/app/src/main/res/values-night/themes.xml
@@ -6,7 +6,7 @@
- @color/gray600
- @color/white
- - @color/primary
+ - @color/secondary
- @color/gray200
- @color/black
@@ -19,8 +19,9 @@
- @color/gray200
- - @color/primary
- - @color/primary
+ - @color/secondary
+ - @color/secondary
+ - true
@@ -53,96 +54,94 @@
-
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index b0eda426..85a4d813 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -28,15 +28,13 @@ lifecycle-livedata = "2.7.0"
kakao-login = "2.8.6"
hilt = "2.50"
play-services-base = "18.0.1"
-firebase-config = "21.4.1"
-firebase-bom = "32.2.2"
+firebase-bom = "32.6.0"
firebase-crashlytics = "2.9.9"
timber = "5.0.1"
google-services = "4.4.2"
kotlin-android = "1.8.10"
activity-version = "1.9.1"
-
[libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "androidx-core" }
androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "androidx-appcompat" }
@@ -76,17 +74,18 @@ kakao-login = { group = "com.kakao.sdk", name = "v2-user", version.ref = "kakao-
hilt = { group = "com.google.dagger", name = "hilt-android", version.ref = "hilt" }
hilt-compiler = { group = "com.google.dagger", name = "hilt-android-compiler", version.ref = "hilt" }
-
play_services_base = { group = "com.google.android.gms", name = "play-services-base", version.ref = "play-services-base" }
-firebase-config = { group = "com.google.firebase", name = "firebase-config-ktx", version.ref = "firebase-config" }
-firebase-bom = { group = "com.google.firebase", name = "firebase-bom", version.ref = "firebase-bom" }
-firebase-analytics = { group = "com.google.firebase", name = "firebase-analytics-ktx" }
-firebase-crashlytics = { group = "com.google.firebase", name = "firebase-crashlytics" }
+
+firebase-bom = { module = "com.google.firebase:firebase-bom", version.ref = "firebase-bom" }
+firebase-config = { module = "com.google.firebase:firebase-config" }
+firebase-analytics = { module = "com.google.firebase:firebase-analytics" }
+firebase-crashlytics = { module = "com.google.firebase:firebase-crashlytics" }
timber = { group = "com.jakewharton.timber", name = "timber", version.ref = "timber" }
androidx-activity = { group = "androidx.activity", name = "activity", version.ref = "activity-version" }
+
[plugins]
android-application = { id = "com.android.application", version.ref = "android" }
android-library = { id = "com.android.library", version.ref = "android" }