From 14eb4c7be666d6448c39f503df215c50fab7ea55 Mon Sep 17 00:00:00 2001 From: "HyunWoo Lee (Nunu Lee)" <54518925+l2hyunwoo@users.noreply.github.com> Date: Tue, 29 Oct 2024 14:58:56 +0900 Subject: [PATCH] =?UTF-8?q?[#948]=20=EC=A3=BC=EC=9A=94=20=ED=99=94?= =?UTF-8?q?=EB=A9=B4=20DeepLink=20=EC=B2=98=EB=A6=AC=20(#950)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 6 ++ app/src/main/AndroidManifest.xml | 42 ++++++------ .../official/deeplink/AppDeeplinkModule.kt | 30 +++++++++ .../feature/attendance/AttendanceActivity.kt | 2 + .../deeplink/DeepLinkSchemeActivity.kt | 67 +++++++++++++++++++ .../official/feature/home/HomeActivity.kt | 3 +- build-logic/convention/build.gradle.kts | 4 ++ .../sopt/official/plugin/DeeplinkPlugin.kt | 21 ++++++ core/webview/build.gradle.kts | 6 ++ .../webview/deeplink/WebDeeplinkModule.kt | 30 +++++++++ .../official/webview/view/WebViewActivity.kt | 13 +++- feature/fortune/build.gradle.kts | 6 ++ .../feature/fortune/FortuneActivity.kt | 2 + .../fortune/deeplink/FortuneDeeplinkModule.kt | 30 +++++++++ gradle/libs.versions.toml | 3 + 15 files changed, 241 insertions(+), 24 deletions(-) create mode 100644 app/src/main/java/org/sopt/official/deeplink/AppDeeplinkModule.kt create mode 100644 app/src/main/java/org/sopt/official/feature/deeplink/DeepLinkSchemeActivity.kt create mode 100644 build-logic/convention/src/main/kotlin/org/sopt/official/plugin/DeeplinkPlugin.kt create mode 100644 core/webview/src/main/java/org/sopt/official/webview/deeplink/WebDeeplinkModule.kt create mode 100644 feature/fortune/src/main/java/org/sopt/official/feature/fortune/deeplink/FortuneDeeplinkModule.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 5c5663364..321a4bd46 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -29,6 +29,7 @@ plugins { sopt("application") sopt("test") sopt("compose") + sopt("deeplink") alias(libs.plugins.google.services) alias(libs.plugins.crashlytics) alias(libs.plugins.ktlint) @@ -112,6 +113,11 @@ android { } } +ksp { + arg("deepLink.incremental", "true") + arg("deepLink.customAnnotations", "com.airbnb.AppDeepLink|com.airbnb.WebDeepLink") +} + dependencies { implementation(projects.core.designsystem) implementation(projects.domain.soptamp) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 3792c53ca..0533c2d0f 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -62,53 +62,55 @@ + android:exported="true"> + + + + + + + + + + android:launchMode="singleTop" /> + android:launchMode="singleTop" /> + android:launchMode="singleTop" /> + android:launchMode="singleTop" /> + android:exported="false" /> + android:exported="false" /> + android:exported="false" /> + android:exported="false" /> + android:exported="false" /> diff --git a/app/src/main/java/org/sopt/official/deeplink/AppDeeplinkModule.kt b/app/src/main/java/org/sopt/official/deeplink/AppDeeplinkModule.kt new file mode 100644 index 000000000..e480bbbe0 --- /dev/null +++ b/app/src/main/java/org/sopt/official/deeplink/AppDeeplinkModule.kt @@ -0,0 +1,30 @@ +/* + * MIT License + * Copyright 2024 SOPT - Shout Our Passion Together + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package org.sopt.official.deeplink + +import com.airbnb.deeplinkdispatch.DeepLinkModule + +@DeepLinkModule +class AppDeeplinkModule diff --git a/app/src/main/java/org/sopt/official/feature/attendance/AttendanceActivity.kt b/app/src/main/java/org/sopt/official/feature/attendance/AttendanceActivity.kt index 606c3bc8a..d63cda2f7 100644 --- a/app/src/main/java/org/sopt/official/feature/attendance/AttendanceActivity.kt +++ b/app/src/main/java/org/sopt/official/feature/attendance/AttendanceActivity.kt @@ -45,6 +45,7 @@ import androidx.core.view.isVisible import androidx.lifecycle.flowWithLifecycle import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.RecyclerView +import com.airbnb.deeplinkdispatch.DeepLink import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach @@ -65,6 +66,7 @@ import org.sopt.official.feature.attendance.model.AttendanceState import org.sopt.official.type.SoptColors @AndroidEntryPoint +@DeepLink("sopt://attendance") class AttendanceActivity : AppCompatActivity() { private lateinit var binding: ActivityAttendanceBinding private val viewModel by viewModels() diff --git a/app/src/main/java/org/sopt/official/feature/deeplink/DeepLinkSchemeActivity.kt b/app/src/main/java/org/sopt/official/feature/deeplink/DeepLinkSchemeActivity.kt new file mode 100644 index 000000000..660fa88ba --- /dev/null +++ b/app/src/main/java/org/sopt/official/feature/deeplink/DeepLinkSchemeActivity.kt @@ -0,0 +1,67 @@ +/* + * MIT License + * Copyright 2024 SOPT - Shout Our Passion Together + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package org.sopt.official.feature.deeplink + +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import com.airbnb.deeplinkdispatch.DeepLinkHandler +import dagger.hilt.android.AndroidEntryPoint +import org.sopt.official.common.navigator.NavigatorProvider +import org.sopt.official.deeplink.AppDeeplinkModule +import org.sopt.official.deeplink.AppDeeplinkModuleRegistry +import org.sopt.official.feature.fortune.deeplink.FortuneDeeplinkModule +import org.sopt.official.feature.fortune.deeplink.FortuneDeeplinkModuleRegistry +import org.sopt.official.network.persistence.SoptDataStore +import org.sopt.official.webview.deeplink.WebDeeplinkModule +import org.sopt.official.webview.deeplink.WebDeeplinkModuleRegistry +import javax.inject.Inject + +@AndroidEntryPoint +@DeepLinkHandler(value = [AppDeeplinkModule::class, FortuneDeeplinkModule::class, WebDeeplinkModule::class]) +class DeepLinkSchemeActivity : AppCompatActivity() { + + @Inject + lateinit var dataStore: SoptDataStore + + @Inject + lateinit var navigator: NavigatorProvider + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + if (dataStore.accessToken.isEmpty()) { + startActivity(navigator.getAuthActivityIntent()) + finish() + return + } + + DeepLinkDelegate( + AppDeeplinkModuleRegistry(), + FortuneDeeplinkModuleRegistry(), + WebDeeplinkModuleRegistry() + ).dispatchFrom(this) + finish() + } +} diff --git a/app/src/main/java/org/sopt/official/feature/home/HomeActivity.kt b/app/src/main/java/org/sopt/official/feature/home/HomeActivity.kt index a597abfaf..d38ac7d68 100644 --- a/app/src/main/java/org/sopt/official/feature/home/HomeActivity.kt +++ b/app/src/main/java/org/sopt/official/feature/home/HomeActivity.kt @@ -29,7 +29,6 @@ import android.content.Context import android.content.Intent import android.content.pm.PackageManager import android.graphics.Rect -import android.net.Uri import android.os.Build import android.os.Bundle import android.view.View @@ -44,6 +43,7 @@ import androidx.lifecycle.flowWithLifecycle import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView +import com.airbnb.deeplinkdispatch.DeepLink import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach @@ -83,6 +83,7 @@ import java.io.Serializable import javax.inject.Inject @AndroidEntryPoint +@DeepLink("sopt://home") class HomeActivity : AppCompatActivity() { private val binding by viewBinding(ActivitySoptMainBinding::inflate) private val viewModel by viewModels() diff --git a/build-logic/convention/build.gradle.kts b/build-logic/convention/build.gradle.kts index 0908cf66a..20077b227 100644 --- a/build-logic/convention/build.gradle.kts +++ b/build-logic/convention/build.gradle.kts @@ -62,5 +62,9 @@ gradlePlugin { id = "org.sopt.official.retrofit" implementationClass = "org.sopt.official.plugin.RetrofitPlugin" } + create("deeplink") { + id = "org.sopt.official.deeplink" + implementationClass = "org.sopt.official.plugin.DeeplinkPlugin" + } } } diff --git a/build-logic/convention/src/main/kotlin/org/sopt/official/plugin/DeeplinkPlugin.kt b/build-logic/convention/src/main/kotlin/org/sopt/official/plugin/DeeplinkPlugin.kt new file mode 100644 index 000000000..140da83a0 --- /dev/null +++ b/build-logic/convention/src/main/kotlin/org/sopt/official/plugin/DeeplinkPlugin.kt @@ -0,0 +1,21 @@ +package org.sopt.official.plugin + +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.api.artifacts.VersionCatalogsExtension +import org.gradle.kotlin.dsl.dependencies +import org.gradle.kotlin.dsl.getByType + +class DeeplinkPlugin : Plugin { + override fun apply(target: Project) = with(target) { + with(plugins) { + apply("com.google.devtools.ksp") + } + + val libs = extensions.getByType().named("libs") + dependencies { + "implementation"(libs.findLibrary("deeplink.dispatch").get()) + "ksp"(libs.findLibrary("deeplink.dispatch.processor").get()) + } + } +} diff --git a/core/webview/build.gradle.kts b/core/webview/build.gradle.kts index 5711f9b5d..3a4399a1b 100644 --- a/core/webview/build.gradle.kts +++ b/core/webview/build.gradle.kts @@ -25,6 +25,7 @@ plugins { sopt("feature") + sopt("deeplink") } android { @@ -34,6 +35,11 @@ android { } } +ksp { + arg("deepLink.incremental", "true") + arg("deepLink.customAnnotations", "com.airbnb.AppDeepLink|com.airbnb.WebDeepLink") +} + dependencies { implementation(projects.core.network) implementation(projects.core.common) diff --git a/core/webview/src/main/java/org/sopt/official/webview/deeplink/WebDeeplinkModule.kt b/core/webview/src/main/java/org/sopt/official/webview/deeplink/WebDeeplinkModule.kt new file mode 100644 index 000000000..ee2c9a5c2 --- /dev/null +++ b/core/webview/src/main/java/org/sopt/official/webview/deeplink/WebDeeplinkModule.kt @@ -0,0 +1,30 @@ +/* + * MIT License + * Copyright 2024 SOPT - Shout Our Passion Together + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package org.sopt.official.webview.deeplink + +import com.airbnb.deeplinkdispatch.DeepLinkModule + +@DeepLinkModule +class WebDeeplinkModule diff --git a/core/webview/src/main/java/org/sopt/official/webview/view/WebViewActivity.kt b/core/webview/src/main/java/org/sopt/official/webview/view/WebViewActivity.kt index e2de64672..700bbd871 100644 --- a/core/webview/src/main/java/org/sopt/official/webview/view/WebViewActivity.kt +++ b/core/webview/src/main/java/org/sopt/official/webview/view/WebViewActivity.kt @@ -35,11 +35,13 @@ import androidx.activity.addCallback import androidx.activity.result.PickVisualMediaRequest import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.app.AppCompatActivity +import com.airbnb.deeplinkdispatch.DeepLink import dagger.hilt.android.AndroidEntryPoint import org.sopt.official.common.util.viewBinding import org.sopt.official.webview.databinding.ActivityWebViewBinding @AndroidEntryPoint +@DeepLink("sopt://web") class WebViewActivity : AppCompatActivity() { private val binding by viewBinding(ActivityWebViewBinding::inflate) private var filePathCallback: ValueCallback>? = null @@ -79,8 +81,13 @@ class WebViewActivity : AppCompatActivity() { } private fun handleLinkUrl() { - val linkUrl = intent.getStringExtra(INTENT_URL) - linkUrl?.let { binding.webView.loadUrl(it) } + if (intent.getBooleanExtra(DeepLink.IS_DEEP_LINK, false)) { + val url = intent.extras?.getString(INTENT_URL) ?: intent.getStringExtra(INTENT_URL) ?: "https://google.com" + binding.webView.loadUrl(url) + } else { + val linkUrl = intent.getStringExtra(INTENT_URL) + linkUrl?.let { binding.webView.loadUrl(it) } + } } private fun handleOnBackPressed() { @@ -100,6 +107,6 @@ class WebViewActivity : AppCompatActivity() { } companion object { - const val INTENT_URL = "_intent_url" + const val INTENT_URL = "url" } } diff --git a/feature/fortune/build.gradle.kts b/feature/fortune/build.gradle.kts index 5f5e62f91..04566440d 100644 --- a/feature/fortune/build.gradle.kts +++ b/feature/fortune/build.gradle.kts @@ -26,12 +26,18 @@ plugins { sopt("feature") sopt("compose") sopt("test") + sopt("deeplink") } android { namespace = "org.sopt.official.feature.fortune" } +ksp { + arg("deepLink.incremental", "true") + arg("deepLink.customAnnotations", "com.airbnb.AppDeepLink|com.airbnb.WebDeepLink") +} + dependencies { // domain implementation(projects.domain.fortune) diff --git a/feature/fortune/src/main/java/org/sopt/official/feature/fortune/FortuneActivity.kt b/feature/fortune/src/main/java/org/sopt/official/feature/fortune/FortuneActivity.kt index 63f175309..7478a50ba 100644 --- a/feature/fortune/src/main/java/org/sopt/official/feature/fortune/FortuneActivity.kt +++ b/feature/fortune/src/main/java/org/sopt/official/feature/fortune/FortuneActivity.kt @@ -31,6 +31,7 @@ import androidx.activity.compose.setContent import androidx.appcompat.app.AppCompatActivity import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.staticCompositionLocalOf +import com.airbnb.deeplinkdispatch.DeepLink import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.EntryPointAccessors import org.sopt.official.analytics.AmplitudeTracker @@ -51,6 +52,7 @@ internal val LocalAmplitudeTracker = staticCompositionLocalOf } @AndroidEntryPoint +@DeepLink("sopt://fortune") class FortuneActivity : AppCompatActivity() { @Inject diff --git a/feature/fortune/src/main/java/org/sopt/official/feature/fortune/deeplink/FortuneDeeplinkModule.kt b/feature/fortune/src/main/java/org/sopt/official/feature/fortune/deeplink/FortuneDeeplinkModule.kt new file mode 100644 index 000000000..6b7948ebf --- /dev/null +++ b/feature/fortune/src/main/java/org/sopt/official/feature/fortune/deeplink/FortuneDeeplinkModule.kt @@ -0,0 +1,30 @@ +/* + * MIT License + * Copyright 2024 SOPT - Shout Our Passion Together + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package org.sopt.official.feature.fortune.deeplink + +import com.airbnb.deeplinkdispatch.DeepLinkModule + +@DeepLinkModule +class FortuneDeeplinkModule diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 7279f82a0..804527e01 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -55,6 +55,7 @@ compose-destination = "1.11.7" coil = "2.7.0" lottie = "6.6.0" dotsindicator = "5.1.0" +deepLinkDispatch = "6.2.2" google-services = "4.4.2" crashlytics = "3.0.2" @@ -180,6 +181,8 @@ constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayo benchmark-macro-junit4 = { group = "androidx.benchmark", name = "benchmark-macro-junit4", version.ref = "benchmark-macro-junit4" } profileinstaller = { group = "androidx.profileinstaller", name = "profileinstaller", version.ref = "profileinstaller" } dotsindicator = { module = "com.tbuonomo:dotsindicator", version.ref = "dotsindicator" } +deeplink-dispatch = { group = "com.airbnb", name = "deeplinkdispatch", version.ref = "deepLinkDispatch" } +deeplink-dispatch-processor = { group = "com.airbnb", name = "deeplinkdispatch-processor", version.ref = "deepLinkDispatch" } [bundles] compose = [