Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions app/src/main/java/com/paulcoding/hviewer/model/PostModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,16 @@ data class PostItem(
val size: Int? = null,
val views: Int? = null,
val quantity: Int? = null,
)
) {
fun getHost(): String {
return url.split("/")[2]
}

fun getSiteConfig(hostsMap: Map<String, SiteConfig>): SiteConfig? {
val host = getHost()
return hostsMap[host]
}
}

// duplicated?
@Entity(tableName = "history")
Expand All @@ -38,7 +47,7 @@ data class PostHistory(
val views: Int? = null,
val quantity: Int? = null,
) {
fun toPostItem():PostItem {
fun toPostItem(): PostItem {
return PostItem(
url = url,
name = name,
Expand Down
8 changes: 7 additions & 1 deletion app/src/main/java/com/paulcoding/hviewer/model/SiteModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,10 @@ data class SiteConfig(
data class SiteConfigs(
val version: Int = 1,
val sites: Map<String, SiteConfig> = mapOf()
)
) {
fun toHostsMap(): Map<String, SiteConfig> {
return sites.map {
it.value.baseUrl.split('/')[2] to it.value
}.toMap()
}
}
20 changes: 14 additions & 6 deletions app/src/main/java/com/paulcoding/hviewer/ui/page/AppEntry.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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.tabs.TabsPage
import com.paulcoding.hviewer.ui.page.web.WebPage

@Composable
Expand Down Expand Up @@ -88,12 +89,12 @@ fun AppEntry() {
navController.navigate(Route.LIST_SCRIPT + "/crash_log")
},
onLockEnabled = {
navController.navigate(Route.LOCK) {
popUpTo(navController.graph.startDestinationId) { inclusive = true }
launchSingleTop = true
restoreState = false
}
}, goBack = { navController.popBackStack() })
navController.navigate(Route.LOCK) {
popUpTo(navController.graph.startDestinationId) { inclusive = true }
launchSingleTop = true
restoreState = false
}
}, goBack = { navController.popBackStack() })
}
animatedComposable(Route.POSTS) {
PostsPage(
Expand All @@ -103,6 +104,7 @@ fun AppEntry() {
},
navToSearch = { navController.navigate(Route.SEARCH) },
navToCustomTag = { navToCustomTag(it) },
navToTabs = { navController.navigate(Route.TABS) },
goBack = { navController.popBackStack() },
)
}
Expand Down Expand Up @@ -199,6 +201,12 @@ fun AppEntry() {
val url = appViewModel.getWebViewUrl()
WebPage(goBack = { navController.popBackStack() }, url = url)
}
animatedComposable(Route.TABS) {
TabsPage(goBack = {
navController.popBackStack()
appViewModel.clearTabs()
}, appViewModel = appViewModel, siteConfigs = siteConfigs)
}
}
}

Expand Down
22 changes: 22 additions & 0 deletions app/src/main/java/com/paulcoding/hviewer/ui/page/AppViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ class AppViewModel : ViewModel() {
private var _stateFlow = MutableStateFlow(UiState())
val stateFlow = _stateFlow.asStateFlow()

private var _tabs = MutableStateFlow(listOf<PostItem>())
val tabs = _tabs.asStateFlow()

val favoritePosts = DatabaseProvider.getInstance().favoritePostDao().getAll()
val historyPosts = DatabaseProvider.getInstance().historyDao().getAll()

Expand Down Expand Up @@ -107,4 +110,23 @@ class AppViewModel : ViewModel() {
DatabaseProvider.getInstance().historyDao().delete(history)
}
}

fun addTab(postItem: PostItem) {
if (!_tabs.value.contains(postItem))
_tabs.update {
it + postItem
}
}

fun removeTab(postItem: PostItem) {
_tabs.update {
it - postItem
}
}

fun clearTabs() {
_tabs.update {
emptyList()
}
}
}
1 change: 1 addition & 0 deletions app/src/main/java/com/paulcoding/hviewer/ui/page/Route.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@ object Route {
const val LOCK = "lock"
const val HISTORY = "history"
const val WEBVIEW = "webview"
const val TABS = "tabs"
}
209 changes: 209 additions & 0 deletions app/src/main/java/com/paulcoding/hviewer/ui/page/post/Images.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
package com.paulcoding.hviewer.ui.page.post

import android.annotation.SuppressLint
import android.widget.Toast
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.statusBarsPadding
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
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
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.res.stringResource
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.R
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.model.SiteConfig
import com.paulcoding.hviewer.ui.component.HBackIcon
import com.paulcoding.hviewer.ui.component.HGoTop
import com.paulcoding.hviewer.ui.component.HImage
import com.paulcoding.hviewer.ui.component.HLoading
import com.paulcoding.hviewer.ui.component.HideSystemBars
import com.paulcoding.hviewer.ui.page.fadeInWithBlur
import com.paulcoding.hviewer.ui.page.fadeOutWithBlur
import me.saket.telephoto.zoomable.DoubleClickToZoomListener
import me.saket.telephoto.zoomable.ZoomSpec
import me.saket.telephoto.zoomable.rememberZoomableState
import me.saket.telephoto.zoomable.zoomable


@Composable
fun ImageList(postUrl: String, siteConfig: SiteConfig, goBack: () -> Unit) {
val viewModel: PostViewModel = viewModel(
key = postUrl,
factory = PostViewModelFactory(postUrl, siteConfig = siteConfig)
)

val uiState by viewModel.stateFlow.collectAsState()
var selectedImage by remember { mutableStateOf<String?>(null) }
val listState = rememberLazyListState()

LaunchedEffect(uiState.error) {
uiState.error?.let {
Toast.makeText(appContext, it.message ?: it.toString(), Toast.LENGTH_SHORT).show()
}
}

LaunchedEffect(Unit) {
viewModel.getImages()
}

LaunchedEffect(listState.firstVisibleItemIndex) {
if (viewModel.canLoadMorePostData() && !uiState.isLoading && listState.isScrolledToEnd()) {
viewModel.getNextImages()
}
}

HideSystemBars()

Box(modifier = Modifier.fillMaxSize()) {
LazyColumn(
state = listState,
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
items(uiState.images, key = { it }) { image ->
PostImage(url = image) {
selectedImage = image
}
}
if (uiState.isLoading)
item {
Box(
modifier = Modifier.statusBarsPadding()
) {
HLoading()
}
}
}

HGoTop(listState)

AnimatedVisibility(
listState.isScrollingUp().value,
modifier = Modifier
.align(Alignment.TopStart)
.padding(16.dp)
.statusBarsPadding()
.statusBarsPadding(),
enter = fadeInWithBlur(),
exit = fadeOutWithBlur(),
) {
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
HBackIcon { goBack() }
Text("${uiState.postPage}/${uiState.postTotalPage}")
}
}

if (selectedImage != null) {
ImageModal(url = selectedImage!!) {
selectedImage = null
}
}
}
}

@Composable
fun ImageModal(url: String, dismiss: () -> Unit) {
val zoomableState = rememberZoomableState(ZoomSpec(maxZoomFactor = 5f))

val doubleClickToZoomListener =
DoubleClickToZoomListener { _, _ ->
dismiss()
}

Dialog(
onDismissRequest = { dismiss() },
properties = DialogProperties(usePlatformDefaultWidth = false)
) {
Surface(
shape = RoundedCornerShape(16.dp),
modifier = Modifier
.fillMaxWidth(),
) {
Box(
modifier = Modifier
.zoomable(
state = zoomableState,
onClick = { makeToast(R.string.double_click_to_dismiss) },
onDoubleClick = doubleClickToZoomListener
)
) {
HImage(
url,
modifier = Modifier.align(Alignment.Center),
)
}
}
}
}

@SuppressLint("ContextCastToActivity")
@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(stringResource(R.string.open_in_browser))
}
)
}
}
}
Loading