Skip to content

Commit

Permalink
Merge pull request #11 from Nexters/feature/#10-웹뷰브릿지
Browse files Browse the repository at this point in the history
[feat] 웹뷰 브릿지 기초 구현
  • Loading branch information
sxunea authored Feb 3, 2025
2 parents 15e2213 + 1d072d1 commit d4d93f2
Show file tree
Hide file tree
Showing 16 changed files with 281 additions and 95 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,8 @@ jobs:
run: ./gradlew spotlessCheck

# 6. Run Unit Tests
- name: Run Unit Tests
run: ./gradlew test
# - name: Run Unit Tests
# run: ./gradlew test

# 7. Run Lint
- name: Run Lint
Expand Down
6 changes: 5 additions & 1 deletion app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,8 @@ plugins {

android {
namespace = "com.nexters.misik"
}
}

dependencies {
implementation(projects.feature.webview)
}

This file was deleted.

2 changes: 1 addition & 1 deletion app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
android:theme="@style/Theme.MisikAndroid"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:name="com.nexters.misik.webview.MainActivity"
android:exported="true"
android:label="@string/app_name"
android:theme="@style/Theme.MisikAndroid">
Expand Down
50 changes: 0 additions & 50 deletions app/src/main/java/com/nexters/misik/MainActivity.kt

This file was deleted.

16 changes: 0 additions & 16 deletions app/src/test/java/com/nexters/misik/ExampleUnitTest.kt

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.nexters.misik.domain

interface WebBridgeContract {
fun openCamera()
fun openGallery()
fun share(content: String)
fun createReview(ocrText: String, hashTags: List<String>, reviewStyle: String)
fun copy(review: String)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.nexters.misik.webview

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Scaffold
import androidx.compose.ui.Modifier
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject

@AndroidEntryPoint
class MainActivity : ComponentActivity() {

@Inject
lateinit var webAppInterface: WebInterface

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()

setContent {
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
WebViewScreen(webAppInterface = webAppInterface, modifier = Modifier.padding(innerPadding))
}
}
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.nexters.misik.webview

import android.content.Context
import com.nexters.misik.domain.WebBridgeContract
import javax.inject.Inject

class WebBridgeImpl @Inject constructor(
private val context: Context,
) : WebBridgeContract {
override fun openCamera() {
// 카메라 실행
}

override fun openGallery() {
// 갤러리 실행
}

override fun share(content: String) {
// 공유
}

override fun createReview(ocrText: String, hashTags: List<String>, reviewStyle: String) {
// 리뷰 생성
}

override fun copy(review: String) {
// 텍스트 복사
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.nexters.misik.webview

import android.app.Application
import android.content.Context
import com.nexters.misik.domain.WebBridgeContract
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent

@Module
@InstallIn(SingletonComponent::class)
object WebBridgeModule {

@Provides
fun provideWebBridgeImpl(context: Context): WebBridgeContract {
return WebBridgeImpl(context)
}

@Provides
fun provideContext(application: Application): Context {
return application.applicationContext
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package com.nexters.misik.webview

import android.webkit.JavascriptInterface
import com.nexters.misik.domain.WebBridgeContract
import org.json.JSONObject
import javax.inject.Inject

class WebInterface @Inject constructor(
private val bridge: WebBridgeContract,
) {

@JavascriptInterface
fun openCamera() {
bridge.openCamera()
}

@JavascriptInterface
fun openGallery() {
bridge.openGallery()
}

@JavascriptInterface
fun share(content: String) {
bridge.share(content)
}

@JavascriptInterface
fun createReview(json: String) {
val jsonObject = JSONObject(json)
val ocrText = jsonObject.getString("ocrText")
val hashTags = jsonObject.getJSONArray("hashTag").let { array ->
List(array.length()) { array.getString(it) }
}
val reviewStyle = jsonObject.getString("reviewStyle")
bridge.createReview(ocrText, hashTags, reviewStyle)
}

@JavascriptInterface
fun copy(json: String) {
val jsonObject = JSONObject(json)
val review = jsonObject.getString("review")
bridge.copy(review)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.nexters.misik.webview

sealed interface WebViewEvent {
data object LoadPage : WebViewEvent // 페이지 로딩 이벤트
data object PageLoaded : WebViewEvent // 페이지 로딩 완료 이벤트
data class JsResponse(val response: String) : WebViewEvent // JavaScript 응답 이벤트
data class JsError(val error: String) : WebViewEvent
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package com.nexters.misik.webview

import android.webkit.WebChromeClient
import android.webkit.WebResourceError
import android.webkit.WebResourceRequest
import android.webkit.WebView
import android.webkit.WebViewClient
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.viewinterop.AndroidView
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import timber.log.Timber

@Composable
fun WebViewScreen(
webAppInterface: WebInterface,
modifier: Modifier = Modifier,
viewModel: WebViewViewModel = hiltViewModel(),
) {
val state by viewModel.state.collectAsStateWithLifecycle()

// 페이지 로딩 이벤트 발생 (WebView 로드 시작 시)
LaunchedEffect(Unit) {
viewModel.onEvent(WebViewEvent.LoadPage)
}

Box(modifier = modifier.fillMaxSize()) {
// 로딩 상태 UI
if (state.isLoading) {
Timber.d("WebViewScreen_UiState", "Loading")
CircularProgressIndicator(
modifier = Modifier.align(Alignment.Center),
)
}

// 에러 상태 UI
state.error?.let {
Timber.d("WebViewScreen_UiState", "Error: $it")
Text(
text = "Error: $it",
color = Color.Red,
modifier = Modifier.align(Alignment.Center),
)
}

// 콘텐츠가 있을 경우, WebView를 보여줌
if (!state.isLoading) {
Timber.d("WebViewScreen_UiState", "Loaded")
AndroidView(
modifier = Modifier.fillMaxSize(),
factory = { context ->
WebView(context).apply {
settings.javaScriptEnabled = true
webViewClient = WebViewClient()
webChromeClient = WebChromeClient()

addJavascriptInterface(webAppInterface, "AndroidBridge")

loadUrl("https://misik-web.vercel.app/")

// 페이지 로딩 완료 후 이벤트 처리
webViewClient = object : WebViewClient() {
override fun onPageFinished(view: WebView?, url: String?) {
super.onPageFinished(view, url)
viewModel.onEvent(WebViewEvent.PageLoaded)
}

override fun onReceivedError(
view: WebView?,
request: WebResourceRequest?,
error: WebResourceError?,
) {
super.onReceivedError(view, request, error)
// 오류 발생 시 이벤트 호출
viewModel.onEvent(WebViewEvent.JsError("Error loading page: ${error?.description}"))
}
}
}
},
)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.nexters.misik.webview

data class WebViewState(
val isLoading: Boolean = false,
val content: String? = null,
val error: String? = null,
)
Loading

0 comments on commit d4d93f2

Please sign in to comment.