From 95c7be4051cea4cfc699929c9a8acf10d92b6856 Mon Sep 17 00:00:00 2001 From: Konrad Pozniak Date: Tue, 29 Apr 2025 19:38:46 +0200 Subject: [PATCH] allow viewing media fullscreen in edge to edge mode (#5067) This will change the image viewer layout so the image wil occupy a larger portion of the screen when zoomed in and even the whole screen in edge to edge mode. I think this is really nice. Before / After: ~~Currently blocked by a bug in the TouchImageView library we are using. https://github.com/MikeOrtiz/TouchImageView/issues/654~~ They accepted my PR and made a new release, nice --- .../keylesspalace/tusky/ViewMediaActivity.kt | 39 ++++++++----------- .../account/media/AccountMediaFragment.kt | 2 +- .../fragments/ReportStatusesFragment.kt | 2 +- .../fragments/SearchStatusesFragment.kt | 2 +- .../keylesspalace/tusky/fragment/SFragment.kt | 2 +- .../tusky/fragment/ViewImageFragment.kt | 12 ++++-- .../main/res/layout/activity_view_media.xml | 12 +++--- .../main/res/layout/fragment_view_image.xml | 8 ++-- app/src/main/res/values/dimens.xml | 3 +- gradle/libs.versions.toml | 2 +- gradle/verification-metadata.xml | 16 ++++++++ 11 files changed, 55 insertions(+), 45 deletions(-) diff --git a/app/src/main/java/com/keylesspalace/tusky/ViewMediaActivity.kt b/app/src/main/java/com/keylesspalace/tusky/ViewMediaActivity.kt index 1430327ab..1d45ad404 100644 --- a/app/src/main/java/com/keylesspalace/tusky/ViewMediaActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/ViewMediaActivity.kt @@ -38,6 +38,7 @@ import android.widget.Toast import androidx.activity.result.contract.ActivityResultContracts import androidx.core.app.ShareCompat import androidx.core.content.FileProvider +import androidx.core.net.toUri import androidx.fragment.app.FragmentActivity import androidx.lifecycle.lifecycleScope import androidx.viewpager2.adapter.FragmentStateAdapter @@ -63,7 +64,6 @@ import dagger.hilt.android.AndroidEntryPoint import java.io.File import java.io.FileOutputStream import java.io.IOException -import java.util.Locale import kotlinx.coroutines.CancellationException import kotlinx.coroutines.launch @@ -77,9 +77,6 @@ class ViewMediaActivity : private val binding by viewBinding(ActivityViewMediaBinding::inflate) - val toolbar: View - get() = binding.toolbar - var isToolbarVisible = true private set @@ -114,9 +111,6 @@ class ViewMediaActivity : attachments = intent.getParcelableArrayListExtraCompat(EXTRA_ATTACHMENTS) val initialPosition = intent.getIntExtra(EXTRA_ATTACHMENT_INDEX, 0) - // Adapter is actually of existential type PageAdapter & SharedElementsTransitionListener - // but it cannot be expressed and if I don't specify type explicitly compilation fails - // (probably a bug in compiler) val adapter: ViewMediaAdapter = if (attachments != null) { val realAttachs = attachments!!.map(AttachmentViewData::attachment) // Setup the view pager. @@ -139,11 +133,10 @@ class ViewMediaActivity : // Setup the toolbar. setSupportActionBar(binding.toolbar) - val actionBar = supportActionBar - if (actionBar != null) { - actionBar.setDisplayHomeAsUpEnabled(true) - actionBar.setDisplayShowHomeEnabled(true) - actionBar.title = getPageTitle(initialPosition) + supportActionBar?.run { + setDisplayHomeAsUpEnabled(true) + setDisplayShowHomeEnabled(true) + title = getPageTitle(initialPosition) } binding.toolbar.setNavigationOnClickListener { supportFinishAfterTransition() } binding.toolbar.setOnMenuItemClickListener { item: MenuItem -> @@ -203,14 +196,14 @@ class ViewMediaActivity : val alpha = if (isToolbarVisible) 1.0f else 0.0f if (isToolbarVisible) { // If to be visible, need to make visible immediately and animate alpha - binding.toolbar.alpha = 0.0f - binding.toolbar.visibility = visibility + binding.appBarLayout.alpha = 0.0f + binding.appBarLayout.visibility = visibility } - binding.toolbar.animate().alpha(alpha) + binding.appBarLayout.animate().alpha(alpha) .setListener(object : AnimatorListenerAdapter() { override fun onAnimationEnd(animation: Animator) { - binding.toolbar.visibility = visibility + binding.appBarLayout.visibility = visibility animation.removeListener(this) } }) @@ -221,20 +214,20 @@ class ViewMediaActivity : if (attachments == null) { return "" } - return String.format(Locale.getDefault(), "%d/%d", position + 1, attachments?.size) + return "${position + 1}/${attachments?.size}" } private fun downloadMedia() { val url = imageUrl ?: attachments!![binding.viewPager.currentItem].attachment.url - val filename = Uri.parse(url).lastPathSegment + val filename = url.toUri().lastPathSegment Toast.makeText( applicationContext, resources.getString(R.string.download_image, filename), Toast.LENGTH_SHORT ).show() - val downloadManager = getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager - val request = DownloadManager.Request(Uri.parse(url)) + val downloadManager = getSystemService(DOWNLOAD_SERVICE) as DownloadManager + val request = DownloadManager.Request(url.toUri()) request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, filename) downloadManager.enqueue(request) } @@ -303,7 +296,7 @@ class ViewMediaActivity : val file = File(directory, getTemporaryMediaFilename("png")) val result = try { val bitmap = - Glide.with(applicationContext).asBitmap().load(Uri.parse(url)).submitAsync() + Glide.with(applicationContext).asBitmap().load(url.toUri()).submitAsync() try { FileOutputStream(file).use { stream -> bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream) @@ -332,7 +325,7 @@ class ViewMediaActivity : } private fun shareMediaFile(directory: File, url: String) { - val uri = Uri.parse(url) + val uri = url.toUri() val mimeTypeMap = MimeTypeMap.getSingleton() val extension = MimeTypeMap.getFileExtensionFromUrl(url) val mimeType = mimeTypeMap.getMimeTypeFromExtension(extension) @@ -367,7 +360,7 @@ class ViewMediaActivity : @JvmStatic fun newIntent( - context: Context?, + context: Context, attachments: List, index: Int ): Intent { diff --git a/app/src/main/java/com/keylesspalace/tusky/components/account/media/AccountMediaFragment.kt b/app/src/main/java/com/keylesspalace/tusky/components/account/media/AccountMediaFragment.kt index a031a18fb..59e84345d 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/account/media/AccountMediaFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/account/media/AccountMediaFragment.kt @@ -182,7 +182,7 @@ class AccountMediaFragment : Attachment.Type.VIDEO, Attachment.Type.AUDIO -> { val intent = ViewMediaActivity.newIntent( - context, + view.context, attachmentsFromSameStatus, currentIndex ) diff --git a/app/src/main/java/com/keylesspalace/tusky/components/report/fragments/ReportStatusesFragment.kt b/app/src/main/java/com/keylesspalace/tusky/components/report/fragments/ReportStatusesFragment.kt index ddc9d914d..5ac703581 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/report/fragments/ReportStatusesFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/report/fragments/ReportStatusesFragment.kt @@ -83,7 +83,7 @@ class ReportStatusesFragment : when (status.attachments[idx].type) { Attachment.Type.GIFV, Attachment.Type.VIDEO, Attachment.Type.IMAGE, Attachment.Type.AUDIO -> { val attachments = AttachmentViewData.list(status) - val intent = ViewMediaActivity.newIntent(context, attachments, idx) + val intent = ViewMediaActivity.newIntent(requireContext(), attachments, idx) if (v != null) { val url = status.attachments[idx].url ViewCompat.setTransitionName(v, url) diff --git a/app/src/main/java/com/keylesspalace/tusky/components/search/fragments/SearchStatusesFragment.kt b/app/src/main/java/com/keylesspalace/tusky/components/search/fragments/SearchStatusesFragment.kt index c88c5b41d..62274e293 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/search/fragments/SearchStatusesFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/search/fragments/SearchStatusesFragment.kt @@ -197,7 +197,7 @@ class SearchStatusesFragment : SearchFragment(), Status Attachment.Type.GIFV, Attachment.Type.VIDEO, Attachment.Type.IMAGE, Attachment.Type.AUDIO -> { val attachments = AttachmentViewData.list(status) val intent = ViewMediaActivity.newIntent( - context, + requireContext(), attachments, attachmentIndex ) diff --git a/app/src/main/java/com/keylesspalace/tusky/fragment/SFragment.kt b/app/src/main/java/com/keylesspalace/tusky/fragment/SFragment.kt index 0e2fa110e..60514ce1e 100644 --- a/app/src/main/java/com/keylesspalace/tusky/fragment/SFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/fragment/SFragment.kt @@ -398,7 +398,7 @@ abstract class SFragment(@LayoutRes contentLayoutId: Int) : Fragment(contentLayo val (attachment) = attachments[urlIndex] when (attachment.type) { Attachment.Type.GIFV, Attachment.Type.VIDEO, Attachment.Type.IMAGE, Attachment.Type.AUDIO -> { - val intent = newIntent(context, attachments, urlIndex) + val intent = newIntent(requireContext(), attachments, urlIndex) if (view != null) { val url = attachment.url view.transitionName = url diff --git a/app/src/main/java/com/keylesspalace/tusky/fragment/ViewImageFragment.kt b/app/src/main/java/com/keylesspalace/tusky/fragment/ViewImageFragment.kt index 8e8df5893..ae2e38cd1 100644 --- a/app/src/main/java/com/keylesspalace/tusky/fragment/ViewImageFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/fragment/ViewImageFragment.kt @@ -28,8 +28,8 @@ import android.view.View import android.view.ViewGroup import android.widget.ImageView import androidx.core.view.ViewCompat +import androidx.core.view.WindowInsetsCompat.Type.displayCutout import androidx.core.view.WindowInsetsCompat.Type.systemBars -import androidx.core.view.updateLayoutParams import androidx.core.view.updatePadding import androidx.lifecycle.lifecycleScope import com.bumptech.glide.Glide @@ -115,14 +115,18 @@ class ViewImageFragment : ViewMediaFragment() { val descriptionBottomSheet = BottomSheetBehavior.from(binding.captionSheet) ViewCompat.setOnApplyWindowInsetsListener(binding.root) { _, insets -> + val topInsets = insets.getInsets(displayCutout()).top val bottomInsets = insets.getInsets(systemBars()).bottom val mediaDescriptionBottomPadding = requireContext().resources.getDimensionPixelSize(R.dimen.media_description_sheet_bottom_padding) val mediaDescriptionPeekHeight = requireContext().resources.getDimensionPixelSize(R.dimen.media_description_sheet_peek_height) - val imageViewBottomMargin = requireContext().resources.getDimensionPixelSize(R.dimen.media_image_view_bottom_margin) binding.mediaDescription.updatePadding(bottom = mediaDescriptionBottomPadding + bottomInsets) descriptionBottomSheet.setPeekHeight(mediaDescriptionPeekHeight + bottomInsets, false) - binding.photoView.updateLayoutParams { bottomMargin = imageViewBottomMargin + bottomInsets } - insets.inset(0, 0, 0, bottomInsets) + binding.photoView.updatePadding( + top = topInsets, + bottom = bottomInsets + ) + binding.photoView.invalidate() + insets.inset(0, topInsets, 0, bottomInsets) } val singleTapDetector = GestureDetector( diff --git a/app/src/main/res/layout/activity_view_media.xml b/app/src/main/res/layout/activity_view_media.xml index d29d34538..ba3ac7f4a 100644 --- a/app/src/main/res/layout/activity_view_media.xml +++ b/app/src/main/res/layout/activity_view_media.xml @@ -5,7 +5,13 @@ android:layout_height="match_parent" android:background="@color/black"> + + - - + android:layout_height="match_parent"/> + android:layout_width="match_parent" + android:layout_height="wrap_content" /> 24dp - 8dp + 16dp 90dp - 24dp 64dp diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 05263c111..4772549b8 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -45,7 +45,7 @@ okio = "3.11.0" retrofit = "2.11.0" robolectric = "4.14.1" sparkbutton = "4.2.0" -touchimageview = "3.6" +touchimageview = "3.7" turbine = "1.2.0" unified-push = "2.4.0" xmlwriter = "1.0.4" diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index ceff45874..72f60cada 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -12090,6 +12090,14 @@ + + + + + + + + @@ -18337,6 +18345,14 @@ + + + + + + + +