From fd8c4e726d38c21f7cd1dea90b6485cf7ceded6a Mon Sep 17 00:00:00 2001 From: klaus Date: Tue, 25 Jul 2023 02:40:41 +0900 Subject: [PATCH] =?UTF-8?q?Firebase=20=EC=B6=94=EA=B0=80=20Crashlytics=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20RemoteConfig=20=EC=9D=B4=EC=9A=A9=ED=95=9C?= =?UTF-8?q?=20=EA=B0=95=EC=A0=9C=20=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 7 +- app/build.gradle | 10 ++ app/src/debug/google-services.json | 39 +++++ .../co/vividnext/sodalive/base/SodaDialog.kt | 74 ++++++++ .../co/vividnext/sodalive/common/Constants.kt | 4 + .../sodalive/splash/SplashActivity.kt | 164 +++++++++++++++++- .../bg_round_corner_10_339970ff_9970ff.xml | 8 + app/src/main/res/layout/dialog_soda.xml | 79 +++++++++ app/src/main/res/values/colors.xml | 1 + build.gradle | 2 + 10 files changed, 380 insertions(+), 8 deletions(-) create mode 100644 app/src/debug/google-services.json create mode 100644 app/src/main/java/kr/co/vividnext/sodalive/base/SodaDialog.kt create mode 100644 app/src/main/res/drawable/bg_round_corner_10_339970ff_9970ff.xml create mode 100644 app/src/main/res/layout/dialog_soda.xml diff --git a/.gitignore b/.gitignore index 4785e26..a16c72f 100644 --- a/.gitignore +++ b/.gitignore @@ -20,7 +20,8 @@ bin/ gen/ out/ # Uncomment the following line in case you need and you don't have the release build type files in your app -release/ +/release/ +/debug/ # Gradle files .gradle/ @@ -302,7 +303,7 @@ fabric.properties ### AndroidStudio Patch ### !/gradle/wrapper/gradle-wrapper.jar -app/debug/* -app/release/* +app/debug/ +app/release/ # End of https://www.toptal.com/developers/gitignore/api/macos,android,androidstudio,visualstudiocode,git,kotlin,java diff --git a/app/build.gradle b/app/build.gradle index 1082a6e..cb006b1 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,6 +1,7 @@ plugins { id 'com.android.application' id 'org.jetbrains.kotlin.android' + id 'com.google.gms.google-services' id 'com.google.android.gms.oss-licenses-plugin' id 'kotlin-kapt' @@ -8,6 +9,7 @@ plugins { id 'org.jlleitschuh.gradle.ktlint' id 'io.objectbox' + id("com.google.firebase.crashlytics") } android { @@ -115,4 +117,12 @@ dependencies { implementation 'com.github.dhaval2404:imagepicker:2.1' implementation 'com.google.android.gms:play-services-oss-licenses:17.0.1' + + // Firebase + implementation platform('com.google.firebase:firebase-bom:32.2.0') + implementation 'com.google.firebase:firebase-dynamic-links-ktx' + implementation 'com.google.firebase:firebase-crashlytics-ktx' + implementation 'com.google.firebase:firebase-analytics-ktx' + implementation 'com.google.firebase:firebase-messaging-ktx' + implementation 'com.google.firebase:firebase-config-ktx' } diff --git a/app/src/debug/google-services.json b/app/src/debug/google-services.json new file mode 100644 index 0000000..e0f29ec --- /dev/null +++ b/app/src/debug/google-services.json @@ -0,0 +1,39 @@ +{ + "project_info": { + "project_number": "758414412471", + "project_id": "sodalive-test", + "storage_bucket": "sodalive-test.appspot.com" + }, + "client": [ + { + "client_info": { + "mobilesdk_app_id": "1:758414412471:android:dcea9dff87fa125c7a5b32", + "android_client_info": { + "package_name": "kr.co.vividnext.sodalive.debug" + } + }, + "oauth_client": [ + { + "client_id": "758414412471-g35socquiplhaamhfl4e6bsta5blabi7.apps.googleusercontent.com", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "AIzaSyAeNDVDY_r5afz97L1NPvQC6oFy5lPXHNI" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [ + { + "client_id": "758414412471-g35socquiplhaamhfl4e6bsta5blabi7.apps.googleusercontent.com", + "client_type": 3 + } + ] + } + } + } + ], + "configuration_version": "1" +} \ No newline at end of file diff --git a/app/src/main/java/kr/co/vividnext/sodalive/base/SodaDialog.kt b/app/src/main/java/kr/co/vividnext/sodalive/base/SodaDialog.kt new file mode 100644 index 0000000..1e8c4dd --- /dev/null +++ b/app/src/main/java/kr/co/vividnext/sodalive/base/SodaDialog.kt @@ -0,0 +1,74 @@ +package kr.co.vividnext.sodalive.base + +import android.app.Activity +import android.graphics.Color +import android.graphics.drawable.ColorDrawable +import android.view.LayoutInflater +import android.view.View +import android.view.WindowManager +import androidx.appcompat.app.AlertDialog +import kr.co.vividnext.sodalive.databinding.DialogSodaBinding +import kr.co.vividnext.sodalive.extensions.dpToPx + +open class SodaDialog( + activity: Activity, + layoutInflater: LayoutInflater, + title: String, + desc: String, + confirmButtonTitle: String, + confirmButtonClick: () -> Unit, + cancelButtonTitle: String = "", + cancelButtonClick: (() -> Unit)? = null, +) { + + private val alertDialog: AlertDialog + + val dialogView = DialogSodaBinding.inflate(layoutInflater) + + init { + val dialogBuilder = AlertDialog.Builder(activity) + dialogBuilder.setView(dialogView.root) + + alertDialog = dialogBuilder.create() + alertDialog.setCancelable(false) + alertDialog.window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) + + dialogView.tvTitle.text = title + dialogView.tvDesc.text = desc + + dialogView.tvCancel.text = cancelButtonTitle + dialogView.tvCancel.setOnClickListener { + alertDialog.dismiss() + cancelButtonClick?.let { it() } + } + + dialogView.tvConfirm.text = confirmButtonTitle + dialogView.tvConfirm.setOnClickListener { + alertDialog.dismiss() + confirmButtonClick() + } + + dialogView.tvCancel.visibility = if (cancelButtonTitle.isNotBlank()) { + View.VISIBLE + } else { + View.GONE + } + + dialogView.tvConfirm.visibility = if (confirmButtonTitle.isNotBlank()) { + View.VISIBLE + } else { + View.GONE + } + } + + fun show(width: Int) { + alertDialog.show() + + val lp = WindowManager.LayoutParams() + lp.copyFrom(alertDialog.window?.attributes) + lp.width = width - (26.7f.dpToPx()).toInt() + lp.height = WindowManager.LayoutParams.WRAP_CONTENT + + alertDialog.window?.attributes = lp + } +} diff --git a/app/src/main/java/kr/co/vividnext/sodalive/common/Constants.kt b/app/src/main/java/kr/co/vividnext/sodalive/common/Constants.kt index f71e64b..a0bd7ed 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/common/Constants.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/common/Constants.kt @@ -12,4 +12,8 @@ object Constants { const val EXTRA_DATA = "extra_data" const val EXTRA_TERMS = "extra_terms" + const val EXTRA_USER_ID = "extra_user_id" + const val EXTRA_ROOM_ID = "extra_room_id" + + const val EXTRA_AUDIO_CONTENT_ID = "extra_audio_content_id" } diff --git a/app/src/main/java/kr/co/vividnext/sodalive/splash/SplashActivity.kt b/app/src/main/java/kr/co/vividnext/sodalive/splash/SplashActivity.kt index 7199506..8bbba0a 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/splash/SplashActivity.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/splash/SplashActivity.kt @@ -2,10 +2,21 @@ package kr.co.vividnext.sodalive.splash import android.annotation.SuppressLint import android.content.Intent +import android.net.Uri import android.os.Bundle import android.os.Handler import android.os.Looper +import androidx.core.os.bundleOf +import com.google.firebase.dynamiclinks.PendingDynamicLinkData +import com.google.firebase.dynamiclinks.ktx.dynamicLinks +import com.google.firebase.ktx.Firebase +import com.google.firebase.remoteconfig.ktx.get +import com.google.firebase.remoteconfig.ktx.remoteConfig +import com.google.firebase.remoteconfig.ktx.remoteConfigSettings +import kr.co.vividnext.sodalive.BuildConfig import kr.co.vividnext.sodalive.base.BaseActivity +import kr.co.vividnext.sodalive.base.SodaDialog +import kr.co.vividnext.sodalive.common.Constants import kr.co.vividnext.sodalive.common.SharedPreferenceManager import kr.co.vividnext.sodalive.databinding.ActivitySplashBinding import kr.co.vividnext.sodalive.main.MainActivity @@ -15,21 +26,163 @@ import kr.co.vividnext.sodalive.user.login.LoginActivity class SplashActivity : BaseActivity(ActivitySplashBinding::inflate) { private val handler = Handler(Looper.getMainLooper()) + private val remoteConfig = Firebase.remoteConfig override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - if (SharedPreferenceManager.token.isBlank()) { - showLoginActivity() + setupRemoteConfig() + fetchAndroidLatestVersion() + } + + private fun setupRemoteConfig() { + val configSettings = remoteConfigSettings { + minimumFetchIntervalInSeconds = 300 + } + remoteConfig.setConfigSettingsAsync(configSettings) + } + + private fun fetchAndroidLatestVersion() { + remoteConfig.fetchAndActivate() + .addOnCompleteListener(this) { task -> + if (task.isSuccessful) { + handler.post { + checkAppVersion(remoteConfig["android_latest_version"].asString()) + } + } else { + checkFirebaseDynamicLink() + } + } + } + + private fun checkAppVersion(latestVersion: String) { + val versions = BuildConfig.VERSION_NAME.split(".") + val latestVersions = latestVersion.split(".") + + if (latestVersions.isNotEmpty() && latestVersions.size == versions.size) { + val latestMajor = latestVersions[0].toInt() + val latestMinor = latestVersions[1].toInt() + val latestPatch = latestVersions[2].toInt() + + val major = versions[0].toInt() + val minor = versions[1].toInt() + val patch = versions[2].toInt() + + if (latestMajor > major || (latestMajor == major && latestMinor > minor)) { + showUpdateDialog(isEssential = true) + } else if (latestMajor == major && latestMinor == minor && latestPatch > patch) { + showUpdateDialog(isEssential = false) + } else { + checkFirebaseDynamicLink() + } } else { - showMainActivity() + checkFirebaseDynamicLink() } } - private fun showMainActivity() { + private fun showUpdateDialog(isEssential: Boolean = false) { + val desc = if (isEssential) { + "필수 업데이트가 있습니다.\n업데이트 후 사용가능합니다." + } else { + "최신 업데이트가 있습니다.\n업데이트 하시겠습니까?" + } + + val cancelButtonClick = if (!isEssential) { + { checkFirebaseDynamicLink() } + } else { + null + } + + SodaDialog( + activity = this, + layoutInflater = layoutInflater, + title = "업데이트", + desc = desc, + confirmButtonTitle = "업데이트", + confirmButtonClick = { + startActivity( + Intent( + Intent.ACTION_VIEW, + Uri.parse("market://details?id=${BuildConfig.APPLICATION_ID}") + ) + ) + finish() + }, + cancelButtonTitle = if (isEssential) { + "" + } else { + "다음에" + }, + cancelButtonClick = cancelButtonClick + ).show(screenWidth) + } + + private fun checkFirebaseDynamicLink() { + Firebase.dynamicLinks + .getDynamicLink(intent) + .addOnSuccessListener(this) { getDynamicLinkSuccess(it) } + .addOnFailureListener(this) { getDynamicLinkFailure() } + } + + private fun getDynamicLinkSuccess(pendingDynamicLinkData: PendingDynamicLinkData?) { + var deepLink: Uri? = null + if (pendingDynamicLinkData != null) { + deepLink = pendingDynamicLinkData.link + } + + val extras = if (deepLink != null) { + val roomIdString = deepLink.getQueryParameter("room_id") + val channelIdString = deepLink.getQueryParameter("channel_id") + val audioContentIdString = deepLink.getQueryParameter("audio_content_id") + + if (roomIdString != null) { + bundleOf( + Constants.EXTRA_ROOM_ID to roomIdString.toLong() + ) + } else if (channelIdString != null) { + bundleOf( + Constants.EXTRA_USER_ID to channelIdString.toLong() + ) + } else if (audioContentIdString != null) { + bundleOf( + Constants.EXTRA_AUDIO_CONTENT_ID to audioContentIdString.toLong() + ) + } else { + null + } + } else if (intent.extras != null) { + intent.extras + } else { + null + } + + startNextActivity(extras = extras) + } + + private fun getDynamicLinkFailure() { + val extras = intent.getBundleExtra(Constants.EXTRA_DATA) + ?: if (intent.extras != null) { + intent.extras + } else { + null + } + + startNextActivity(extras = extras) + } + + private fun startNextActivity(extras: Bundle? = null) { + if (SharedPreferenceManager.token.isBlank()) { + showLoginActivity(extras) + } else { + showMainActivity(extras) + } + } + + private fun showMainActivity(extras: Bundle?) { handler.postDelayed({ startActivity( Intent(applicationContext, MainActivity::class.java).apply { + putExtra(Constants.EXTRA_DATA, extras) intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP) } @@ -38,10 +191,11 @@ class SplashActivity : BaseActivity(ActivitySplashBinding }, 500) } - private fun showLoginActivity() { + private fun showLoginActivity(extras: Bundle?) { handler.postDelayed({ startActivity( Intent(applicationContext, LoginActivity::class.java).apply { + putExtra(Constants.EXTRA_DATA, extras) intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP) } diff --git a/app/src/main/res/drawable/bg_round_corner_10_339970ff_9970ff.xml b/app/src/main/res/drawable/bg_round_corner_10_339970ff_9970ff.xml new file mode 100644 index 0000000..ac6052e --- /dev/null +++ b/app/src/main/res/drawable/bg_round_corner_10_339970ff_9970ff.xml @@ -0,0 +1,8 @@ + + + + + + diff --git a/app/src/main/res/layout/dialog_soda.xml b/app/src/main/res/layout/dialog_soda.xml new file mode 100644 index 0000000..8eda466 --- /dev/null +++ b/app/src/main/res/layout/dialog_soda.xml @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 528c59b..f729221 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -16,4 +16,5 @@ #B3909090 #88909090 + #339970FF diff --git a/build.gradle b/build.gradle index a77ee0e..da632e2 100644 --- a/build.gradle +++ b/build.gradle @@ -22,6 +22,8 @@ plugins { id 'org.jetbrains.kotlin.android' version '1.8.20' apply false id "org.jlleitschuh.gradle.ktlint" version "11.2.0" + id 'com.google.gms.google-services' version '4.3.15' apply false + id("com.google.firebase.crashlytics") version "2.9.7" apply false } task clean(type: Delete) {