From 5d8e121a3260ddbbad88a641014d93429e3c9bcb Mon Sep 17 00:00:00 2001 From: paulcoding810 <41385034+longnghia@users.noreply.github.com> Date: Fri, 27 Dec 2024 10:54:06 +0700 Subject: [PATCH 1/6] Add repo url in build config --- app/build.gradle.kts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 28428b1..1b8d05d 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -17,6 +17,10 @@ android { namespace = "com.paulcoding.hviewer" compileSdk = 35 + val repoName = providers.exec { + commandLine = "git remote get-url origin".split(' ') + } + defaultConfig { applicationId = "com.paulcoding.hviewer" minSdk = 26 @@ -24,6 +28,8 @@ android { versionCode = 1 versionName = "1.3.2" + buildConfigField("String", "REPO_NAME", "\"$repoName\"") + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" splits { @@ -101,6 +107,7 @@ dependencies { implementation(libs.ktor.serialization.gson) implementation(libs.ktor.client.content.negotiation) implementation(libs.ktor.client.logging) + implementation(libs.androidx.webkit) implementation(libs.androidx.room.runtime) ksp(libs.androidx.room.compiler) From 81d40255b4f8edd8e59ff862bec3cea6b3f63048 Mon Sep 17 00:00:00 2001 From: paulcoding810 <41385034+longnghia@users.noreply.github.com> Date: Fri, 27 Dec 2024 11:00:51 +0700 Subject: [PATCH 2/6] Add repo url in build config --- app/build.gradle.kts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 1b8d05d..8ad7175 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,3 +1,5 @@ +import org.jetbrains.kotlin.util.prefixIfNot + plugins { alias(libs.plugins.android.application) alias(libs.plugins.kotlin.android) @@ -17,9 +19,12 @@ android { namespace = "com.paulcoding.hviewer" compileSdk = 35 - val repoName = providers.exec { + val repoUrl = providers.exec { commandLine = "git remote get-url origin".split(' ') - } + }.standardOutput.asText.get().trim().removePrefix("https://github.com/") + .removePrefix("git@github.com:") + .removeSuffix(".git") + .prefixIfNot("https://github.com/") defaultConfig { applicationId = "com.paulcoding.hviewer" @@ -28,7 +33,7 @@ android { versionCode = 1 versionName = "1.3.2" - buildConfigField("String", "REPO_NAME", "\"$repoName\"") + buildConfigField("String", "REPO_URL", "\"$repoUrl\"") testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" From 33499aedfd7224fa520f0491fb8962032049f8bf Mon Sep 17 00:00:00 2001 From: paulcoding810 <41385034+longnghia@users.noreply.github.com> Date: Fri, 27 Dec 2024 11:35:11 +0700 Subject: [PATCH 3/6] View image in browser --- .../hviewer/ui/page/post/PostPage.kt | 57 ++++++++++++++++--- 1 file changed, 48 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/com/paulcoding/hviewer/ui/page/post/PostPage.kt b/app/src/main/java/com/paulcoding/hviewer/ui/page/post/PostPage.kt index c9c9e40..3272e34 100644 --- a/app/src/main/java/com/paulcoding/hviewer/ui/page/post/PostPage.kt +++ b/app/src/main/java/com/paulcoding/hviewer/ui/page/post/PostPage.kt @@ -1,8 +1,11 @@ package com.paulcoding.hviewer.ui.page.post +import android.annotation.SuppressLint +import android.content.Intent +import android.net.Uri import android.widget.Toast import androidx.compose.animation.AnimatedVisibility -import androidx.compose.foundation.clickable +import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row @@ -14,6 +17,8 @@ import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.DropdownMenu +import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -25,10 +30,13 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.DialogProperties import androidx.lifecycle.viewmodel.compose.viewModel +import com.paulcoding.hviewer.MainActivity import com.paulcoding.hviewer.MainApp.Companion.appContext import com.paulcoding.hviewer.extensions.isScrolledToEnd import com.paulcoding.hviewer.extensions.isScrollingUp @@ -46,8 +54,9 @@ import me.saket.telephoto.zoomable.ZoomSpec import me.saket.telephoto.zoomable.rememberZoomableState import me.saket.telephoto.zoomable.zoomable +@SuppressLint("UseOfNonLambdaOffsetOverload") @Composable -fun PostPage(appViewModel: AppViewModel, goBack: () -> Unit) { +fun PostPage(appViewModel: AppViewModel, navToWebView: (String) -> Unit, goBack: () -> Unit) { val appState by appViewModel.stateFlow.collectAsState() val post = appState.post val siteConfig = appState.site.second @@ -59,6 +68,7 @@ fun PostPage(appViewModel: AppViewModel, goBack: () -> Unit) { val uiState by viewModel.stateFlow.collectAsState() var selectedImage by remember { mutableStateOf(null) } val listState = rememberLazyListState() + val context = LocalContext.current as MainActivity LaunchedEffect(uiState.error) { uiState.error?.let { @@ -84,17 +94,46 @@ fun PostPage(appViewModel: AppViewModel, goBack: () -> Unit) { verticalArrangement = Arrangement.spacedBy(8.dp) ) { items(uiState.images, key = { it }) { image -> - HImage( - modifier = Modifier.clickable { selectedImage = image }, - url = image - ) + val showMenu = remember { mutableStateOf(false) } + val menuOffset = remember { mutableStateOf(Pair(0f, 0f)) } + + Box(modifier = Modifier.pointerInput(Unit) { + detectTapGestures( + onLongPress = { offset -> + println("pressed $image") + showMenu.value = true + menuOffset.value = Pair(offset.x, offset.y) + }, + onTap = { + selectedImage = image + } + ) + }) { + HImage( + url = image + ) + + DropdownMenu( + expanded = showMenu.value, + onDismissRequest = { showMenu.value = false }, + ) { + DropdownMenuItem( + onClick = { + showMenu.value = false + val intent = Intent(Intent.ACTION_VIEW, Uri.parse(image)) + context.startActivity(intent) + }, + text = { + Text("Open in browser") + } + ) + } + } } if (uiState.isLoading) item { Box( - modifier = Modifier - .statusBarsPadding() - .statusBarsPadding() + modifier = Modifier.statusBarsPadding() ) { HLoading() } From 478cc89b71aa79bd2945b94863284be328efc658 Mon Sep 17 00:00:00 2001 From: paulcoding810 <41385034+longnghia@users.noreply.github.com> Date: Fri, 27 Dec 2024 11:36:18 +0700 Subject: [PATCH 4/6] Implement inapp webview --- .../paulcoding/hviewer/ui/page/AppEntry.kt | 9 +++++ .../hviewer/ui/page/AppViewModel.kt | 7 ++++ .../com/paulcoding/hviewer/ui/page/Route.kt | 1 + .../paulcoding/hviewer/ui/page/web/WebPage.kt | 29 ++++++++++++++ .../paulcoding/hviewer/ui/page/web/WebView.kt | 40 +++++++++++++++++++ gradle/libs.versions.toml | 2 + 6 files changed, 88 insertions(+) create mode 100644 app/src/main/java/com/paulcoding/hviewer/ui/page/web/WebPage.kt create mode 100644 app/src/main/java/com/paulcoding/hviewer/ui/page/web/WebView.kt diff --git a/app/src/main/java/com/paulcoding/hviewer/ui/page/AppEntry.kt b/app/src/main/java/com/paulcoding/hviewer/ui/page/AppEntry.kt index 7dfb75c..382a0cf 100644 --- a/app/src/main/java/com/paulcoding/hviewer/ui/page/AppEntry.kt +++ b/app/src/main/java/com/paulcoding/hviewer/ui/page/AppEntry.kt @@ -34,6 +34,7 @@ import com.paulcoding.hviewer.ui.page.posts.PostsPage import com.paulcoding.hviewer.ui.page.search.SearchPage import com.paulcoding.hviewer.ui.page.settings.SettingsPage import com.paulcoding.hviewer.ui.page.sites.SitesPage +import com.paulcoding.hviewer.ui.page.web.WebPage @Composable fun AppEntry() { @@ -116,6 +117,10 @@ fun AppEntry() { animatedComposable(Route.POST) { PostPage( appViewModel, + navToWebView = { + appViewModel.setWebViewUrl(it) + navController.navigate(Route.WEBVIEW) + }, goBack = { navController.popBackStack() }) @@ -189,6 +194,10 @@ fun AppEntry() { deleteHistory = appViewModel::deleteHistory ) } + animatedComposable(Route.WEBVIEW) { + val url = appViewModel.getWebViewUrl() + WebPage(goBack = { navController.popBackStack() }, url = url) + } } } diff --git a/app/src/main/java/com/paulcoding/hviewer/ui/page/AppViewModel.kt b/app/src/main/java/com/paulcoding/hviewer/ui/page/AppViewModel.kt index a7e248e..e770a08 100644 --- a/app/src/main/java/com/paulcoding/hviewer/ui/page/AppViewModel.kt +++ b/app/src/main/java/com/paulcoding/hviewer/ui/page/AppViewModel.kt @@ -48,11 +48,18 @@ class AppViewModel : ViewModel() { data class UiState( val post: PostItem = PostItem(), + val url: String = "", val site: Pair = "" to SiteConfig(), val tag: Tag = Tag(), val isDevMode: Boolean = BuildConfig.DEBUG, ) + fun setWebViewUrl(url: String) { + _stateFlow.update { it.copy(url = url) } + } + + fun getWebViewUrl() = _stateFlow.value.url + fun setDevMode(isDevMode: Boolean) { _stateFlow.update { it.copy(isDevMode = isDevMode) } } diff --git a/app/src/main/java/com/paulcoding/hviewer/ui/page/Route.kt b/app/src/main/java/com/paulcoding/hviewer/ui/page/Route.kt index b6ec0df..101a5e3 100644 --- a/app/src/main/java/com/paulcoding/hviewer/ui/page/Route.kt +++ b/app/src/main/java/com/paulcoding/hviewer/ui/page/Route.kt @@ -14,4 +14,5 @@ object Route { const val LIST_SCRIPT = "list_script" const val LOCK = "lock" const val HISTORY = "history" + const val WEBVIEW = "webview" } \ No newline at end of file diff --git a/app/src/main/java/com/paulcoding/hviewer/ui/page/web/WebPage.kt b/app/src/main/java/com/paulcoding/hviewer/ui/page/web/WebPage.kt new file mode 100644 index 0000000..fbf543a --- /dev/null +++ b/app/src/main/java/com/paulcoding/hviewer/ui/page/web/WebPage.kt @@ -0,0 +1,29 @@ +package com.paulcoding.hviewer.ui.page.web + +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Scaffold +import androidx.compose.material3.TopAppBar +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.paulcoding.hviewer.ui.component.HBackIcon + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun WebPage(goBack: () -> Unit, url: String) { + Scaffold( + modifier = Modifier.fillMaxSize(), + topBar = { + TopAppBar( + title = {}, + navigationIcon = { + HBackIcon { goBack() } + }, + ) + } + ) { paddings -> + HWebView(modifier = Modifier.padding(paddings), url = url) + } +} + diff --git a/app/src/main/java/com/paulcoding/hviewer/ui/page/web/WebView.kt b/app/src/main/java/com/paulcoding/hviewer/ui/page/web/WebView.kt new file mode 100644 index 0000000..0325cb0 --- /dev/null +++ b/app/src/main/java/com/paulcoding/hviewer/ui/page/web/WebView.kt @@ -0,0 +1,40 @@ +package com.paulcoding.hviewer.ui.page.web + +import android.annotation.SuppressLint +import android.webkit.JavascriptInterface +import android.webkit.WebView +import android.webkit.WebViewClient +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.viewinterop.AndroidView + +@SuppressLint("SetJavaScriptEnabled") +@Composable +fun HWebView( + modifier: Modifier = Modifier, + url: String, +) { + class WebAppInterface() { + @JavascriptInterface + fun senData(data: String) { + println(data) + } + } + AndroidView( + modifier = modifier, + factory = { context -> + WebView(context).apply { + settings.javaScriptEnabled = true + webViewClient = object : WebViewClient() { + override fun onPageFinished(webview: WebView, url: String?) { + super.onPageFinished(webview, url) + webview.loadUrl("javascript:window.HViewer.senData('Hello from WebView')") + } + } + val jsInterface = WebAppInterface() + addJavascriptInterface(jsInterface, "HViewer") + loadUrl(url) + } + } + ) +} \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ca806dc..f356cbe 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -30,6 +30,7 @@ mmkv = "2.0.0" navigationCompose = "2.8.5" rhino = "1.7.15" roomRuntime = "2.6.1" +webkit = "1.13.0-alpha02" zoomable = "0.14.0" material3 = "1.3.1" @@ -43,6 +44,7 @@ androidx-navigation-compose = { module = "androidx.navigation:navigation-compose androidx-room-compiler = { module = "androidx.room:room-compiler", version.ref = "roomRuntime" } androidx-room-ktx = { module = "androidx.room:room-ktx", version.ref = "roomRuntime" } androidx-room-runtime = { module = "androidx.room:room-runtime", version.ref = "roomRuntime" } +androidx-webkit = { module = "androidx.webkit:webkit", version.ref = "webkit" } bom = { module = "io.github.Rosemoe.sora-editor:bom", version.ref = "bom" } coil-compose = { module = "io.coil-kt.coil3:coil-compose", version.ref = "coilNetworkOkhttp" } coil-gif = { module = "io.coil-kt.coil3:coil-gif", version.ref = "coilNetworkOkhttp" } From 7c5f0b1f0b02fc280bb06871c5bf6e8f9a28e3d1 Mon Sep 17 00:00:00 2001 From: paulcoding810 <41385034+longnghia@users.noreply.github.com> Date: Fri, 27 Dec 2024 11:42:37 +0700 Subject: [PATCH 5/6] Create openInBrowser extension --- .../paulcoding/hviewer/extensions/Activity.kt | 10 ++++++++ .../hviewer/ui/page/post/PostPage.kt | 6 ++--- .../hviewer/ui/page/posts/PostCard.kt | 23 ++++++++++++++----- 3 files changed, 29 insertions(+), 10 deletions(-) create mode 100644 app/src/main/java/com/paulcoding/hviewer/extensions/Activity.kt diff --git a/app/src/main/java/com/paulcoding/hviewer/extensions/Activity.kt b/app/src/main/java/com/paulcoding/hviewer/extensions/Activity.kt new file mode 100644 index 0000000..6d0a116 --- /dev/null +++ b/app/src/main/java/com/paulcoding/hviewer/extensions/Activity.kt @@ -0,0 +1,10 @@ +package com.paulcoding.hviewer.extensions + +import android.app.Activity +import android.content.Intent +import android.net.Uri + +fun Activity.openInBrowser(url: String) { + val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url)) + startActivity(intent) +} \ No newline at end of file diff --git a/app/src/main/java/com/paulcoding/hviewer/ui/page/post/PostPage.kt b/app/src/main/java/com/paulcoding/hviewer/ui/page/post/PostPage.kt index 3272e34..7d4f4ae 100644 --- a/app/src/main/java/com/paulcoding/hviewer/ui/page/post/PostPage.kt +++ b/app/src/main/java/com/paulcoding/hviewer/ui/page/post/PostPage.kt @@ -1,8 +1,6 @@ package com.paulcoding.hviewer.ui.page.post import android.annotation.SuppressLint -import android.content.Intent -import android.net.Uri import android.widget.Toast import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.gestures.detectTapGestures @@ -40,6 +38,7 @@ import com.paulcoding.hviewer.MainActivity import com.paulcoding.hviewer.MainApp.Companion.appContext import com.paulcoding.hviewer.extensions.isScrolledToEnd import com.paulcoding.hviewer.extensions.isScrollingUp +import com.paulcoding.hviewer.extensions.openInBrowser import com.paulcoding.hviewer.helper.makeToast import com.paulcoding.hviewer.ui.component.HBackIcon import com.paulcoding.hviewer.ui.component.HGoTop @@ -120,8 +119,7 @@ fun PostPage(appViewModel: AppViewModel, navToWebView: (String) -> Unit, goBack: DropdownMenuItem( onClick = { showMenu.value = false - val intent = Intent(Intent.ACTION_VIEW, Uri.parse(image)) - context.startActivity(intent) + context.openInBrowser(image) }, text = { Text("Open in browser") diff --git a/app/src/main/java/com/paulcoding/hviewer/ui/page/posts/PostCard.kt b/app/src/main/java/com/paulcoding/hviewer/ui/page/posts/PostCard.kt index b583b71..96fbeb3 100644 --- a/app/src/main/java/com/paulcoding/hviewer/ui/page/posts/PostCard.kt +++ b/app/src/main/java/com/paulcoding/hviewer/ui/page/posts/PostCard.kt @@ -28,9 +28,12 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import com.paulcoding.hviewer.MainActivity +import com.paulcoding.hviewer.extensions.openInBrowser import com.paulcoding.hviewer.model.PostItem import com.paulcoding.hviewer.model.Tag import com.paulcoding.hviewer.ui.component.HFavoriteIcon @@ -69,6 +72,7 @@ fun PostCard( ) { var isBottomSheetVisible by remember { mutableStateOf(false) } val bottomSheetState = rememberModalBottomSheetState() + val context = LocalContext.current as MainActivity LaunchedEffect(isBottomSheetVisible) { if (isBottomSheetVisible) { @@ -123,12 +127,19 @@ fun PostCard( SelectionContainer { Text(text = name, fontSize = 20.sp) } - Text( - text = url, - textDecoration = TextDecoration.Underline, - fontSize = 12.sp, - color = Color.Blue - ) + TextButton( + onClick = { + isBottomSheetVisible = false + context.openInBrowser(url) + }, + ) { + Text( + text = url, + textDecoration = TextDecoration.Underline, + fontSize = 12.sp, + color = Color.Blue + ) + } if (size != null) { Text(text = "Size: $size") } From 29e6f8b84296ee630801011ea88ab72b7844f349 Mon Sep 17 00:00:00 2001 From: paulcoding810 <41385034+longnghia@users.noreply.github.com> Date: Fri, 27 Dec 2024 11:46:13 +0700 Subject: [PATCH 6/6] Refactor PostImage --- .../hviewer/ui/page/post/PostPage.kt | 73 ++++++++++--------- 1 file changed, 39 insertions(+), 34 deletions(-) diff --git a/app/src/main/java/com/paulcoding/hviewer/ui/page/post/PostPage.kt b/app/src/main/java/com/paulcoding/hviewer/ui/page/post/PostPage.kt index 7d4f4ae..e89089d 100644 --- a/app/src/main/java/com/paulcoding/hviewer/ui/page/post/PostPage.kt +++ b/app/src/main/java/com/paulcoding/hviewer/ui/page/post/PostPage.kt @@ -67,7 +67,6 @@ fun PostPage(appViewModel: AppViewModel, navToWebView: (String) -> Unit, goBack: val uiState by viewModel.stateFlow.collectAsState() var selectedImage by remember { mutableStateOf(null) } val listState = rememberLazyListState() - val context = LocalContext.current as MainActivity LaunchedEffect(uiState.error) { uiState.error?.let { @@ -93,39 +92,8 @@ fun PostPage(appViewModel: AppViewModel, navToWebView: (String) -> Unit, goBack: verticalArrangement = Arrangement.spacedBy(8.dp) ) { items(uiState.images, key = { it }) { image -> - val showMenu = remember { mutableStateOf(false) } - val menuOffset = remember { mutableStateOf(Pair(0f, 0f)) } - - Box(modifier = Modifier.pointerInput(Unit) { - detectTapGestures( - onLongPress = { offset -> - println("pressed $image") - showMenu.value = true - menuOffset.value = Pair(offset.x, offset.y) - }, - onTap = { - selectedImage = image - } - ) - }) { - HImage( - url = image - ) - - DropdownMenu( - expanded = showMenu.value, - onDismissRequest = { showMenu.value = false }, - ) { - DropdownMenuItem( - onClick = { - showMenu.value = false - context.openInBrowser(image) - }, - text = { - Text("Open in browser") - } - ) - } + PostImage(url = image) { + selectedImage = image } } if (uiState.isLoading) @@ -201,4 +169,41 @@ fun ImageModal(url: String, dismiss: () -> Unit) { } } } +} + +@Composable +fun PostImage(url: String, onTap: () -> Unit = {}) { + val showMenu = remember { mutableStateOf(false) } + val menuOffset = remember { mutableStateOf(Pair(0f, 0f)) } + val context = LocalContext.current as MainActivity + + Box(modifier = Modifier.pointerInput(Unit) { + detectTapGestures( + onLongPress = { offset -> + println("pressed $url") + showMenu.value = true + menuOffset.value = Pair(offset.x, offset.y) + }, + onTap = { onTap() } + ) + }) { + HImage( + url = url + ) + + DropdownMenu( + expanded = showMenu.value, + onDismissRequest = { showMenu.value = false }, + ) { + DropdownMenuItem( + onClick = { + showMenu.value = false + context.openInBrowser(url) + }, + text = { + Text("Open in browser") + } + ) + } + } } \ No newline at end of file