Enhance PhotoViewer with share and open-in actions, improve reply UI
This commit introduces "Share" and "Open in..." actions to the `PhotoViewScreen`, allowing users to share images via other apps or open them in external viewers.
**Key changes:**
- **PhotoViewer:**
- Added "Share" and "Open in..." options to the `PhotoViewScreen` dropdown menu.
- `PhotoViewViewModel`:
- Implemented `onShareClicked()` and `onOpenInClicked()` to handle these new actions.
- Added `shareRequest` StateFlow to manage image sharing intents.
- Introduced `downloadAndStoreImageToCache()` to download and cache images for sharing.
- `onImageShared()` resets `shareRequest` after sharing.
- Updated `TopBar` to include the new menu items.
- Added string resources for "Open in…" and "Share".
- **Reply UI:**
- `Reply.kt`: Title and summary text now use `TextOverflow.Ellipsis` to prevent long text from breaking the layout.
- **API Model:**
- `MessagesResponse.kt`: Added `MessagesMarkAsImportantResponse` data class to handle the response for marking messages as important.
- **Data Layer:**
- `MessagesRepositoryImpl`: Updated `markAsImportant` to correctly map the API response using `MessagesMarkAsImportantResponse`.
- **Minor:**
- `README.md`: Updated feature checklist for external viewer.
- `ApplicationModule.kt`: Added experimental Coil API opt-in.
This commit is contained in:
@@ -38,6 +38,12 @@ Unofficial messenger for russian social network VKontakte
|
|||||||
- [x] Audio
|
- [x] Audio
|
||||||
- [x] File
|
- [x] File
|
||||||
- [x] Link
|
- [x] Link
|
||||||
|
- [x] Sticker
|
||||||
|
- [x] Reply
|
||||||
|
- [ ] Forwarded messages
|
||||||
|
- [ ] Wall post
|
||||||
|
- [ ] Comment in wall post
|
||||||
|
- [ ] Poll
|
||||||
- [ ] TODO
|
- [ ] TODO
|
||||||
- [x] Send messages
|
- [x] Send messages
|
||||||
- [x] Pinned message
|
- [x] Pinned message
|
||||||
@@ -57,7 +63,7 @@ Unofficial messenger for russian social network VKontakte
|
|||||||
- [x] View attachments
|
- [x] View attachments
|
||||||
- [x] Open photo
|
- [x] Open photo
|
||||||
- [x] Internal viewer
|
- [x] Internal viewer
|
||||||
- [ ] External viewer
|
- [x] External viewer
|
||||||
- [ ] Open video in external player
|
- [ ] Open video in external player
|
||||||
- [ ] TODO
|
- [ ] TODO
|
||||||
- [ ] Caching
|
- [ ] Caching
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import android.content.res.Resources
|
|||||||
import android.os.PowerManager
|
import android.os.PowerManager
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import coil.ImageLoader
|
import coil.ImageLoader
|
||||||
|
import coil.annotation.ExperimentalCoilApi
|
||||||
import dev.meloda.fast.MainViewModelImpl
|
import dev.meloda.fast.MainViewModelImpl
|
||||||
import dev.meloda.fast.auth.captcha.di.captchaModule
|
import dev.meloda.fast.auth.captcha.di.captchaModule
|
||||||
import dev.meloda.fast.auth.login.di.loginModule
|
import dev.meloda.fast.auth.login.di.loginModule
|
||||||
@@ -33,6 +34,7 @@ import org.koin.core.qualifier.qualifier
|
|||||||
import org.koin.dsl.bind
|
import org.koin.dsl.bind
|
||||||
import org.koin.dsl.module
|
import org.koin.dsl.module
|
||||||
|
|
||||||
|
@OptIn(ExperimentalCoilApi::class)
|
||||||
val applicationModule = module {
|
val applicationModule = module {
|
||||||
includes(domainModule)
|
includes(domainModule)
|
||||||
includes(
|
includes(
|
||||||
@@ -66,6 +68,7 @@ val applicationModule = module {
|
|||||||
ImageLoader.Builder(get())
|
ImageLoader.Builder(get())
|
||||||
.crossfade(true)
|
.crossfade(true)
|
||||||
.build()
|
.build()
|
||||||
|
.also { it.diskCache?.directory?.toFile()?.listFiles() }
|
||||||
}
|
}
|
||||||
|
|
||||||
singleOf(::LongPollControllerImpl) bind LongPollController::class
|
singleOf(::LongPollControllerImpl) bind LongPollController::class
|
||||||
|
|||||||
+6
-1
@@ -320,7 +320,12 @@ class MessagesRepositoryImpl(
|
|||||||
messagesIds = messageIds.orEmpty(),
|
messagesIds = messageIds.orEmpty(),
|
||||||
important = important
|
important = important
|
||||||
)
|
)
|
||||||
messagesService.markAsImportant(requestModel.map).mapApiDefault()
|
messagesService.markAsImportant(requestModel.map).mapApiResult(
|
||||||
|
successMapper = { apiResponse ->
|
||||||
|
apiResponse.requireResponse().marked.map { it.cmId }
|
||||||
|
},
|
||||||
|
errorMapper = { error -> error?.toDomain() }
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun delete(
|
override suspend fun delete(
|
||||||
|
|||||||
@@ -58,3 +58,15 @@ data class MessagesSendResponse(
|
|||||||
@Json(name = "message_id") val messageId: Long,
|
@Json(name = "message_id") val messageId: Long,
|
||||||
@Json(name = "cmid") val cmId: Long
|
@Json(name = "cmid") val cmId: Long
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
data class MessagesMarkAsImportantResponse(
|
||||||
|
@Json(name = "marked") val marked: List<Mark>
|
||||||
|
) {
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
data class Mark(
|
||||||
|
@Json(name = "cmid") val cmId: Long,
|
||||||
|
@Json(name = "message_id") val messageId: Long,
|
||||||
|
@Json(name = "peer_id") val peerId: Long
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|||||||
+2
-1
@@ -9,6 +9,7 @@ import dev.meloda.fast.model.api.responses.MessagesGetByIdResponse
|
|||||||
import dev.meloda.fast.model.api.responses.MessagesGetConversationMembersResponse
|
import dev.meloda.fast.model.api.responses.MessagesGetConversationMembersResponse
|
||||||
import dev.meloda.fast.model.api.responses.MessagesGetHistoryAttachmentsResponse
|
import dev.meloda.fast.model.api.responses.MessagesGetHistoryAttachmentsResponse
|
||||||
import dev.meloda.fast.model.api.responses.MessagesGetHistoryResponse
|
import dev.meloda.fast.model.api.responses.MessagesGetHistoryResponse
|
||||||
|
import dev.meloda.fast.model.api.responses.MessagesMarkAsImportantResponse
|
||||||
import dev.meloda.fast.model.api.responses.MessagesSendResponse
|
import dev.meloda.fast.model.api.responses.MessagesSendResponse
|
||||||
import dev.meloda.fast.network.ApiResponse
|
import dev.meloda.fast.network.ApiResponse
|
||||||
import dev.meloda.fast.network.RestApiError
|
import dev.meloda.fast.network.RestApiError
|
||||||
@@ -76,7 +77,7 @@ interface MessagesService {
|
|||||||
@POST(MessagesUrls.MARK_AS_IMPORTANT)
|
@POST(MessagesUrls.MARK_AS_IMPORTANT)
|
||||||
suspend fun markAsImportant(
|
suspend fun markAsImportant(
|
||||||
@FieldMap params: Map<String, String>
|
@FieldMap params: Map<String, String>
|
||||||
): ApiResult<ApiResponse<List<Long>>, RestApiError>
|
): ApiResult<ApiResponse<MessagesMarkAsImportantResponse>, RestApiError>
|
||||||
|
|
||||||
@FormUrlEncoded
|
@FormUrlEncoded
|
||||||
@POST(MessagesUrls.DELETE)
|
@POST(MessagesUrls.DELETE)
|
||||||
|
|||||||
@@ -277,4 +277,6 @@
|
|||||||
<string name="action_copy_link">Скопировать ссылку</string>
|
<string name="action_copy_link">Скопировать ссылку</string>
|
||||||
<string name="action_copy">Скопировать</string>
|
<string name="action_copy">Скопировать</string>
|
||||||
<string name="action_copy_image">Скопировать изображение</string>
|
<string name="action_copy_image">Скопировать изображение</string>
|
||||||
|
<string name="action_open_in">Открыть в…</string>
|
||||||
|
<string name="action_share">Поделиться</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -354,4 +354,6 @@
|
|||||||
<string name="action_copy_link">Copy link</string>
|
<string name="action_copy_link">Copy link</string>
|
||||||
<string name="action_copy">Copy</string>
|
<string name="action_copy">Copy</string>
|
||||||
<string name="action_copy_image">Copy image</string>
|
<string name="action_copy_image">Copy image</string>
|
||||||
|
<string name="action_open_in">Open in…</string>
|
||||||
|
<string name="action_share">Share</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
+5
-3
@@ -21,6 +21,7 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.Shape
|
import androidx.compose.ui.graphics.Shape
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.Dp
|
import androidx.compose.ui.unit.Dp
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
@@ -50,7 +51,6 @@ fun Reply(
|
|||||||
bottom = bottomPadding
|
bottom = bottomPadding
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.clip(RoundedCornerShape(12.dp))
|
.clip(RoundedCornerShape(12.dp))
|
||||||
@@ -74,14 +74,16 @@ fun Reply(
|
|||||||
Text(
|
Text(
|
||||||
text = title,
|
text = title,
|
||||||
style = MaterialTheme.typography.labelMedium,
|
style = MaterialTheme.typography.labelMedium,
|
||||||
maxLines = 1
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis
|
||||||
)
|
)
|
||||||
|
|
||||||
summary?.let {
|
summary?.let {
|
||||||
Text(
|
Text(
|
||||||
text = summary,
|
text = summary,
|
||||||
style = MaterialTheme.typography.labelSmall,
|
style = MaterialTheme.typography.labelSmall,
|
||||||
maxLines = 1
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+77
-12
@@ -3,10 +3,13 @@ package dev.meloda.fast.photoviewer
|
|||||||
import android.content.ClipData
|
import android.content.ClipData
|
||||||
import android.content.ClipboardManager
|
import android.content.ClipboardManager
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
|
import android.net.Uri
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.core.content.FileProvider
|
import androidx.core.content.FileProvider
|
||||||
import androidx.core.graphics.drawable.toBitmapOrNull
|
import androidx.core.graphics.drawable.toBitmapOrNull
|
||||||
|
import androidx.core.net.toUri
|
||||||
import androidx.lifecycle.SavedStateHandle
|
import androidx.lifecycle.SavedStateHandle
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
@@ -26,13 +29,21 @@ import java.io.FileOutputStream
|
|||||||
import java.net.URLDecoder
|
import java.net.URLDecoder
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
||||||
|
import dev.meloda.fast.ui.R as UiR
|
||||||
|
|
||||||
interface PhotoViewViewModel {
|
interface PhotoViewViewModel {
|
||||||
val screenState: StateFlow<PhotoViewScreenState>
|
val screenState: StateFlow<PhotoViewScreenState>
|
||||||
|
|
||||||
|
val shareRequest: StateFlow<Uri?>
|
||||||
|
|
||||||
fun onPageChanged(newPage: Int)
|
fun onPageChanged(newPage: Int)
|
||||||
|
|
||||||
|
fun onShareClicked()
|
||||||
|
fun onOpenInClicked()
|
||||||
fun onCopyLinkClicked()
|
fun onCopyLinkClicked()
|
||||||
fun onCopyClicked()
|
fun onCopyClicked()
|
||||||
|
|
||||||
|
fun onImageShared()
|
||||||
}
|
}
|
||||||
|
|
||||||
class PhotoViewViewModelImpl(
|
class PhotoViewViewModelImpl(
|
||||||
@@ -42,6 +53,8 @@ class PhotoViewViewModelImpl(
|
|||||||
|
|
||||||
override val screenState = MutableStateFlow(PhotoViewScreenState.EMPTY)
|
override val screenState = MutableStateFlow(PhotoViewScreenState.EMPTY)
|
||||||
|
|
||||||
|
override val shareRequest = MutableStateFlow<Uri?>(null)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
val arguments = PhotoView.from(savedStateHandle).arguments
|
val arguments = PhotoView.from(savedStateHandle).arguments
|
||||||
|
|
||||||
@@ -59,6 +72,47 @@ class PhotoViewViewModelImpl(
|
|||||||
screenState.setValue { old -> old.copy(selectedPage = newPage) }
|
screenState.setValue { old -> old.copy(selectedPage = newPage) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onShareClicked() {
|
||||||
|
val url = screenState.value.images
|
||||||
|
.getOrNull(screenState.value.selectedPage)
|
||||||
|
?.extractUrl() ?: return
|
||||||
|
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
val imageFile = downloadAndStoreImageToCache(url) ?: return@launch
|
||||||
|
|
||||||
|
val uri = FileProvider.getUriForFile(
|
||||||
|
applicationContext,
|
||||||
|
"${applicationContext.packageName}.provider",
|
||||||
|
imageFile
|
||||||
|
)
|
||||||
|
|
||||||
|
shareRequest.setValue { uri }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onOpenInClicked() {
|
||||||
|
val url = screenState.value.images
|
||||||
|
.getOrNull(screenState.value.selectedPage)
|
||||||
|
?.extractUrl() ?: return
|
||||||
|
|
||||||
|
val intent = Intent(Intent.ACTION_VIEW, url.toUri())
|
||||||
|
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||||
|
|
||||||
|
try {
|
||||||
|
applicationContext.startActivity(intent)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
|
||||||
|
viewModelScope.launch(Dispatchers.Main) {
|
||||||
|
Toast.makeText(
|
||||||
|
applicationContext,
|
||||||
|
UiR.string.error_occurred,
|
||||||
|
Toast.LENGTH_LONG
|
||||||
|
).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCopyLinkClicked() {
|
override fun onCopyLinkClicked() {
|
||||||
val url = screenState.value.images
|
val url = screenState.value.images
|
||||||
.getOrNull(screenState.value.selectedPage)
|
.getOrNull(screenState.value.selectedPage)
|
||||||
@@ -85,18 +139,7 @@ class PhotoViewViewModelImpl(
|
|||||||
?.extractUrl() ?: return
|
?.extractUrl() ?: return
|
||||||
|
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
val drawable = applicationContext.imageLoader.execute(
|
val imageFile = downloadAndStoreImageToCache(url) ?: return@launch
|
||||||
ImageRequest.Builder(applicationContext)
|
|
||||||
.data(url)
|
|
||||||
.build()
|
|
||||||
).drawable ?: return@launch
|
|
||||||
|
|
||||||
val imagesDir = File(applicationContext.cacheDir, "images")
|
|
||||||
if (!imagesDir.exists()) imagesDir.mkdirs()
|
|
||||||
val imageFile = File(imagesDir, "shared_image_id${UUID.randomUUID()}.png")
|
|
||||||
FileOutputStream(imageFile).use {
|
|
||||||
drawable.toBitmapOrNull()?.compress(Bitmap.CompressFormat.PNG, 100, it)
|
|
||||||
}
|
|
||||||
|
|
||||||
val uri = FileProvider.getUriForFile(
|
val uri = FileProvider.getUriForFile(
|
||||||
applicationContext,
|
applicationContext,
|
||||||
@@ -116,4 +159,26 @@ class PhotoViewViewModelImpl(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onImageShared() {
|
||||||
|
shareRequest.setValue { null }
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun downloadAndStoreImageToCache(url: String): File? =
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
val drawable = applicationContext.imageLoader.execute(
|
||||||
|
ImageRequest.Builder(applicationContext)
|
||||||
|
.data(url)
|
||||||
|
.build()
|
||||||
|
).drawable ?: return@withContext null
|
||||||
|
|
||||||
|
val imagesDir = File(applicationContext.cacheDir, "images")
|
||||||
|
if (!imagesDir.exists()) imagesDir.mkdirs()
|
||||||
|
val imageFile = File(imagesDir, "shared_image_id${UUID.randomUUID()}.png")
|
||||||
|
FileOutputStream(imageFile).use {
|
||||||
|
drawable.toBitmapOrNull()?.compress(Bitmap.CompressFormat.PNG, 100, it)
|
||||||
|
}
|
||||||
|
|
||||||
|
imageFile
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+68
-4
@@ -1,10 +1,13 @@
|
|||||||
package dev.meloda.fast.photoviewer.presentation
|
package dev.meloda.fast.photoviewer.presentation
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import android.widget.Toast
|
||||||
import androidx.compose.animation.core.animateFloatAsState
|
import androidx.compose.animation.core.animateFloatAsState
|
||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.Image
|
||||||
import androidx.compose.foundation.gestures.Orientation
|
import androidx.compose.foundation.gestures.Orientation
|
||||||
import androidx.compose.foundation.gestures.draggable
|
import androidx.compose.foundation.gestures.draggable
|
||||||
import androidx.compose.foundation.gestures.rememberDraggableState
|
import androidx.compose.foundation.gestures.rememberDraggableState
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.defaultMinSize
|
import androidx.compose.foundation.layout.defaultMinSize
|
||||||
@@ -33,6 +36,7 @@ import androidx.compose.runtime.getValue
|
|||||||
import androidx.compose.runtime.mutableFloatStateOf
|
import androidx.compose.runtime.mutableFloatStateOf
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.runtime.rememberUpdatedState
|
import androidx.compose.runtime.rememberUpdatedState
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.runtime.snapshotFlow
|
import androidx.compose.runtime.snapshotFlow
|
||||||
@@ -41,6 +45,7 @@ import androidx.compose.ui.graphics.Color
|
|||||||
import androidx.compose.ui.graphics.graphicsLayer
|
import androidx.compose.ui.graphics.graphicsLayer
|
||||||
import androidx.compose.ui.graphics.painter.ColorPainter
|
import androidx.compose.ui.graphics.painter.ColorPainter
|
||||||
import androidx.compose.ui.graphics.painter.Painter
|
import androidx.compose.ui.graphics.painter.Painter
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.DpOffset
|
import androidx.compose.ui.unit.DpOffset
|
||||||
@@ -53,6 +58,8 @@ import dev.meloda.fast.photoviewer.PhotoViewViewModel
|
|||||||
import dev.meloda.fast.photoviewer.PhotoViewViewModelImpl
|
import dev.meloda.fast.photoviewer.PhotoViewViewModelImpl
|
||||||
import dev.meloda.fast.photoviewer.model.PhotoViewScreenState
|
import dev.meloda.fast.photoviewer.model.PhotoViewScreenState
|
||||||
import dev.meloda.fast.ui.util.getImage
|
import dev.meloda.fast.ui.util.getImage
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import org.koin.androidx.compose.koinViewModel
|
import org.koin.androidx.compose.koinViewModel
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
import dev.meloda.fast.ui.R as UiR
|
import dev.meloda.fast.ui.R as UiR
|
||||||
@@ -63,11 +70,46 @@ fun PhotoViewRoute(
|
|||||||
viewModel: PhotoViewViewModel = koinViewModel<PhotoViewViewModelImpl>()
|
viewModel: PhotoViewViewModel = koinViewModel<PhotoViewViewModelImpl>()
|
||||||
) {
|
) {
|
||||||
val screenState by viewModel.screenState.collectAsStateWithLifecycle()
|
val screenState by viewModel.screenState.collectAsStateWithLifecycle()
|
||||||
|
val shareRequest by viewModel.shareRequest.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
|
val context = LocalContext.current
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
|
LaunchedEffect(shareRequest) {
|
||||||
|
if (shareRequest != null) {
|
||||||
|
viewModel.onImageShared()
|
||||||
|
|
||||||
|
val intent = Intent(Intent.ACTION_SEND).apply {
|
||||||
|
setType("image/png")
|
||||||
|
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||||
|
putExtra(Intent.EXTRA_STREAM, shareRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
val chooserIntent = Intent.createChooser(intent, null)
|
||||||
|
chooserIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||||
|
|
||||||
|
try {
|
||||||
|
context.startActivity(chooserIntent)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
|
||||||
|
scope.launch(Dispatchers.Main) {
|
||||||
|
Toast.makeText(
|
||||||
|
context,
|
||||||
|
UiR.string.error_occurred,
|
||||||
|
Toast.LENGTH_LONG
|
||||||
|
).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
PhotoViewScreen(
|
PhotoViewScreen(
|
||||||
screenState = screenState,
|
screenState = screenState,
|
||||||
onBack = onBack,
|
onBack = onBack,
|
||||||
onPageChanged = viewModel::onPageChanged,
|
onPageChanged = viewModel::onPageChanged,
|
||||||
|
onShareClicked = viewModel::onShareClicked,
|
||||||
|
onOpenInClicked = viewModel::onOpenInClicked,
|
||||||
onCopyLinkClicked = viewModel::onCopyLinkClicked,
|
onCopyLinkClicked = viewModel::onCopyLinkClicked,
|
||||||
onCopyClicked = viewModel::onCopyClicked
|
onCopyClicked = viewModel::onCopyClicked
|
||||||
)
|
)
|
||||||
@@ -78,6 +120,8 @@ fun PhotoViewScreen(
|
|||||||
screenState: PhotoViewScreenState = PhotoViewScreenState.EMPTY,
|
screenState: PhotoViewScreenState = PhotoViewScreenState.EMPTY,
|
||||||
onBack: () -> Unit = {},
|
onBack: () -> Unit = {},
|
||||||
onPageChanged: (index: Int) -> Unit = {},
|
onPageChanged: (index: Int) -> Unit = {},
|
||||||
|
onShareClicked: () -> Unit = {},
|
||||||
|
onOpenInClicked: () -> Unit = {},
|
||||||
onCopyLinkClicked: () -> Unit = {},
|
onCopyLinkClicked: () -> Unit = {},
|
||||||
onCopyClicked: () -> Unit = {}
|
onCopyClicked: () -> Unit = {}
|
||||||
) {
|
) {
|
||||||
@@ -108,6 +152,8 @@ fun PhotoViewScreen(
|
|||||||
topBar = {
|
topBar = {
|
||||||
TopBar(
|
TopBar(
|
||||||
onBack = onBack,
|
onBack = onBack,
|
||||||
|
onShareClicked = onShareClicked,
|
||||||
|
onOpenInClicked = onOpenInClicked,
|
||||||
onCopyClicked = onCopyClicked,
|
onCopyClicked = onCopyClicked,
|
||||||
onCopyLinkClicked = onCopyLinkClicked,
|
onCopyLinkClicked = onCopyLinkClicked,
|
||||||
)
|
)
|
||||||
@@ -116,7 +162,7 @@ fun PhotoViewScreen(
|
|||||||
alpha = calculatedAlpha
|
alpha = calculatedAlpha
|
||||||
)
|
)
|
||||||
) { padding ->
|
) { padding ->
|
||||||
Column(modifier = Modifier.fillMaxSize()) {
|
Box(modifier = Modifier.fillMaxSize()) {
|
||||||
Pager(
|
Pager(
|
||||||
pagerState = pagerState,
|
pagerState = pagerState,
|
||||||
state = screenState,
|
state = screenState,
|
||||||
@@ -134,6 +180,8 @@ fun PhotoViewScreen(
|
|||||||
fun TopBar(
|
fun TopBar(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
onBack: () -> Unit,
|
onBack: () -> Unit,
|
||||||
|
onShareClicked: () -> Unit,
|
||||||
|
onOpenInClicked: () -> Unit,
|
||||||
onCopyClicked: () -> Unit,
|
onCopyClicked: () -> Unit,
|
||||||
onCopyLinkClicked: () -> Unit
|
onCopyLinkClicked: () -> Unit
|
||||||
) {
|
) {
|
||||||
@@ -141,9 +189,7 @@ fun TopBar(
|
|||||||
mutableStateOf(false)
|
mutableStateOf(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
val hideDropDownMenu by rememberUpdatedState(
|
val hideDropDownMenu by rememberUpdatedState { dropdownMenuShown = false }
|
||||||
{ dropdownMenuShown = false }
|
|
||||||
)
|
|
||||||
|
|
||||||
TopAppBar(
|
TopAppBar(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
@@ -172,6 +218,24 @@ fun TopBar(
|
|||||||
onDismissRequest = { dropdownMenuShown = false },
|
onDismissRequest = { dropdownMenuShown = false },
|
||||||
offset = DpOffset(x = (10).dp, y = (-60).dp)
|
offset = DpOffset(x = (10).dp, y = (-60).dp)
|
||||||
) {
|
) {
|
||||||
|
DropdownMenuItem(
|
||||||
|
onClick = {
|
||||||
|
hideDropDownMenu()
|
||||||
|
onShareClicked()
|
||||||
|
},
|
||||||
|
text = {
|
||||||
|
Text(text = stringResource(UiR.string.action_share))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
DropdownMenuItem(
|
||||||
|
onClick = {
|
||||||
|
hideDropDownMenu()
|
||||||
|
onOpenInClicked()
|
||||||
|
},
|
||||||
|
text = {
|
||||||
|
Text(text = stringResource(UiR.string.action_open_in))
|
||||||
|
}
|
||||||
|
)
|
||||||
DropdownMenuItem(
|
DropdownMenuItem(
|
||||||
onClick = {
|
onClick = {
|
||||||
hideDropDownMenu()
|
hideDropDownMenu()
|
||||||
|
|||||||
Reference in New Issue
Block a user