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
10 changes: 10 additions & 0 deletions app/src/main/java/com/capyreader/app/common/ContextShareLinkExt.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.capyreader.app.common

import android.content.Context
import android.content.Intent
import android.net.Uri

fun Context.shareLink(url: String, title: String) {
val share = Intent.createChooser(Intent().apply {
Expand All @@ -12,3 +13,12 @@ fun Context.shareLink(url: String, title: String) {
}, null)
startActivity(share)
}

fun Context.shareImage(uri: Uri) {
val shareIntent = Intent(Intent.ACTION_SEND).apply {
putExtra(Intent.EXTRA_STREAM, uri)
setDataAndType(uri, "image/jpeg")
flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
}
startActivity(Intent.createChooser(shareIntent, null))
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import kotlinx.serialization.json.Json
class WebViewInterface(
private val navigateToMedia: (media: Media) -> Unit,
private val onRequestLinkDialog: (link: ShareLink) -> Unit,
private val onRequestImageDialog: (imageUrl: String) -> Unit = {},
private val onOpenAudioPlayer: (audio: AudioEnclosure) -> Unit = {},
private val onPauseAudio: () -> Unit = {},
) {
Expand Down Expand Up @@ -36,6 +37,13 @@ class WebViewInterface(
}
}

@JavascriptInterface
fun showImageDialog(imageUrl: String) {
optionalURL(imageUrl)?.let {
onRequestImageDialog(it.toString())
}
}

@JavascriptInterface
fun openAudioPlayer(audioJson: String) {
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ import com.capyreader.app.ui.components.SearchState
import com.capyreader.app.ui.provideLinkOpener
import com.capyreader.app.ui.rememberLazyListState
import com.capyreader.app.ui.rememberLocalConnectivity
import com.capyreader.app.ui.settings.LocalSnackbarHost
import com.jocmp.capy.Article
import com.jocmp.capy.ArticleFilter
import com.jocmp.capy.ArticleStatus
Expand Down Expand Up @@ -160,6 +161,8 @@ fun ArticleScreen(
state = searchState,
)

val snackbarHostState = remember { SnackbarHostState() }

CompositionLocalProvider(
LocalFullContent provides fullContent,
LocalArticleActions provides articleActions,
Expand All @@ -170,6 +173,7 @@ fun ArticleScreen(
LocalLinkOpener provides provideLinkOpener(context),
LocalMarkAllReadButtonPosition provides markAllReadButtonPosition,
LocalUnreadCount provides unreadCount,
LocalSnackbarHost provides snackbarHostState,
) {
val openNextFeedOnReadAll = afterReadAll == AfterReadAllBehavior.OPEN_NEXT_FEED

Expand All @@ -187,8 +191,6 @@ fun ArticleScreen(
val scaffoldNavigator = rememberArticleScaffoldNavigator()
val showMultipleColumns = scaffoldNavigator.scaffoldDirective.maxHorizontalPartitions > 1
var isPullToRefreshing by remember { mutableStateOf(false) }

val snackbarHostState = remember { SnackbarHostState() }
val addFeedSuccessMessage = stringResource(R.string.add_feed_success)
val currentFeed by viewModel.currentFeed.collectAsStateWithLifecycle(null)
val scrollBehavior = pinnedScrollBehavior()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,25 +11,36 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import com.capyreader.app.R
import com.capyreader.app.common.AudioEnclosure
import com.capyreader.app.common.Media
import com.capyreader.app.common.rememberTalkbackPreference
import com.capyreader.app.common.shareImage
import com.capyreader.app.preferences.AppPreferences
import com.capyreader.app.preferences.ReaderImageVisibility
import com.capyreader.app.ui.ConnectivityType
import com.capyreader.app.ui.LocalConnectivity
import com.capyreader.app.ui.LocalLinkOpener
import com.capyreader.app.ui.articles.ColumnScrollbar
import com.capyreader.app.ui.articles.media.ImageSaver
import com.capyreader.app.ui.components.WebView
import com.capyreader.app.ui.components.WebViewState
import com.capyreader.app.ui.components.rememberSaveableShareLink
import com.capyreader.app.ui.components.rememberWebViewState
import com.capyreader.app.ui.settings.LocalSnackbarHost
import com.jocmp.capy.Article
import com.jocmp.capy.common.launchIO
import com.jocmp.capy.common.launchUI
import com.jocmp.capy.common.withUIContext
import org.koin.compose.koinInject
import kotlin.math.roundToInt

Expand All @@ -43,12 +54,62 @@ fun ArticleReader(
isAudioPlaying: Boolean = false,
) {
val (shareLink, setShareLink) = rememberSaveableShareLink()
val (shareImageUrl, setImageUrl) = rememberSaveable { mutableStateOf<String?>(null) }
val linkOpener = LocalLinkOpener.current
val context = LocalContext.current
val scope = rememberCoroutineScope()
val snackbar = LocalSnackbarHost.current
val successMessage = stringResource(R.string.media_save_success)
val failureMessage = stringResource(R.string.media_save_failure)
val shareFailureMessage = stringResource(R.string.media_share_failure)

fun showSnackbar(message: String) {
scope.launchUI {
snackbar.showSnackbar(message)
}
}

fun saveImage(imageUrl: String) {
scope.launchIO {
val result = ImageSaver.saveImage(imageUrl, context = context)

withUIContext {
result.fold(
onSuccess = {
showSnackbar(successMessage)
},
onFailure = {
showSnackbar(failureMessage)
}
)

}
}

setImageUrl(null)
}

fun shareImage(imageUrl: String) {
scope.launchIO {
ImageSaver.shareImage(imageUrl, context = context)
.fold(
onSuccess = { uri ->
context.shareImage(uri)
},
onFailure = {
showSnackbar(shareFailureMessage)
}
)
}

setImageUrl(null)
}

val webViewState = rememberWebViewState(
key = article.id,
onNavigateToMedia = onSelectMedia,
onRequestLinkDialog = { setShareLink(it) },
onRequestImageDialog = { setImageUrl(it) },
onOpenLink = { linkOpener.open(it) },
onOpenAudioPlayer = onSelectAudio,
onPauseAudio = onPauseAudio,
Expand Down Expand Up @@ -88,6 +149,17 @@ fun ArticleReader(
link = shareLink,
)
}

if (shareImageUrl != null) {
ShareImageDialog(
onClose = {
setImageUrl(null)
},
imageUrl = shareImageUrl,
onSave = { saveImage(shareImageUrl) },
onShare = { shareImage(shareImageUrl) },
)
}
}

@Composable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,11 @@ import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.FlexibleBottomAppBar
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.Stable
Expand Down Expand Up @@ -51,6 +54,7 @@ import com.capyreader.app.ui.LocalLinkOpener
import com.capyreader.app.ui.articles.LocalFullContent
import com.capyreader.app.ui.collectChangesWithDefault
import com.capyreader.app.ui.components.pullrefresh.SwipeRefresh
import com.capyreader.app.ui.settings.LocalSnackbarHost
import com.jocmp.capy.Article
import org.koin.compose.koinInject

Expand Down Expand Up @@ -215,6 +219,8 @@ private fun ArticleViewScaffold(
bottomScrollBehavior: BottomAppBarScrollBehavior,
topToolbarPreference: ToolbarPreferences,
) {
val snackbarHostState = remember { SnackbarHostState() }

Scaffold(
modifier = Modifier
.nestedScroll(bottomScrollBehavior.nestedScrollConnection)
Expand All @@ -223,33 +229,40 @@ private fun ArticleViewScaffold(
if (topToolbarPreference.pinned) {
topBar()
}
},
snackbarHost = {
SnackbarHost(hostState = snackbarHostState)
}
) { innerPadding ->
Box(
Modifier
.fillMaxSize()
CompositionLocalProvider(
LocalSnackbarHost provides snackbarHostState,
) {
Box(
modifier = Modifier
.padding(innerPadding)
Modifier
.fillMaxSize()
) {
Column {
reader()
Box(
modifier = Modifier
.padding(innerPadding)
.fillMaxSize()
) {
Column {
reader()
}
}
}

if (!topToolbarPreference.pinned) {
topBar()
}
if (!topToolbarPreference.pinned) {
topBar()
}

if (enableBottomBar) {
Box(
Modifier
.align(Alignment.BottomStart)
.fillMaxWidth()
) {
bottomBar()
if (enableBottomBar) {
Box(
Modifier
.align(Alignment.BottomStart)
.fillMaxWidth()
) {
bottomBar()
}
}
}
}
Expand Down
Loading