kopia lustrzana https://github.com/ryukoposting/Signal-Android
Update MediaPreviewV2 to use thumbnail rail & menu items.
rodzic
2edb9eeb52
commit
a9a64a3f60
|
@ -1,13 +1,22 @@
|
|||
package org.thoughtcrime.securesms.mediapreview
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.fragment.app.commit
|
||||
import org.thoughtcrime.securesms.R
|
||||
|
||||
class MediaPreviewV2Activity : AppCompatActivity(R.layout.activity_mediapreview_v2) {
|
||||
|
||||
override fun attachBaseContext(newBase: Context) {
|
||||
delegate.localNightMode = AppCompatDelegate.MODE_NIGHT_YES
|
||||
super.attachBaseContext(newBase)
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setTheme(R.style.TextSecure_MediaPreview)
|
||||
if (savedInstanceState == null) {
|
||||
val bundle = Bundle()
|
||||
val args = MediaIntentFactory.requireArguments(intent.extras!!)
|
||||
|
|
|
@ -1,23 +1,52 @@
|
|||
package org.thoughtcrime.securesms.mediapreview
|
||||
|
||||
import android.Manifest
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.view.Menu
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.ViewGroup.MarginLayoutParams
|
||||
import android.widget.Toast
|
||||
import androidx.core.app.ShareCompat
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback
|
||||
import com.google.android.material.appbar.MaterialToolbar
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||
import org.signal.core.util.concurrent.SignalExecutors
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.animation.DepthPageTransformer
|
||||
import org.thoughtcrime.securesms.attachments.DatabaseAttachment
|
||||
import org.thoughtcrime.securesms.components.ViewBinderDelegate
|
||||
import org.thoughtcrime.securesms.conversation.mutiselect.forward.MultiselectForwardFragment
|
||||
import org.thoughtcrime.securesms.conversation.mutiselect.forward.MultiselectForwardFragmentArgs
|
||||
import org.thoughtcrime.securesms.database.MediaDatabase
|
||||
import org.thoughtcrime.securesms.databinding.FragmentMediaPreviewV2Binding
|
||||
import org.thoughtcrime.securesms.mediaoverview.MediaOverviewActivity
|
||||
import org.thoughtcrime.securesms.mediasend.Media
|
||||
import org.thoughtcrime.securesms.mms.GlideApp
|
||||
import org.thoughtcrime.securesms.mms.PartAuthority
|
||||
import org.thoughtcrime.securesms.permissions.Permissions
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.util.DateUtils
|
||||
import org.thoughtcrime.securesms.util.FullscreenHelper
|
||||
import org.thoughtcrime.securesms.util.LifecycleDisposable
|
||||
import org.thoughtcrime.securesms.util.MediaUtil
|
||||
import org.thoughtcrime.securesms.util.SaveAttachmentTask
|
||||
import org.thoughtcrime.securesms.util.StorageUtil
|
||||
import java.util.Locale
|
||||
import java.util.Optional
|
||||
|
||||
class MediaPreviewV2Fragment : Fragment(R.layout.fragment_media_preview_v2), MediaPreviewFragment.Events {
|
||||
private val TAG = Log.tag(MediaPreviewV2Fragment::class.java)
|
||||
|
@ -35,7 +64,11 @@ class MediaPreviewV2Fragment : Fragment(R.layout.fragment_media_preview_v2), Med
|
|||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
initializeViewModel()
|
||||
|
||||
val args = MediaIntentFactory.requireArguments(requireArguments())
|
||||
|
||||
initializeViewModel(args)
|
||||
initializeToolbar(binding.toolbar, args)
|
||||
binding.mediaPager.offscreenPageLimit = 1
|
||||
binding.mediaPager.setPageTransformer(DepthPageTransformer())
|
||||
val adapter = MediaPreviewV2Adapter(this)
|
||||
|
@ -47,18 +80,59 @@ class MediaPreviewV2Fragment : Fragment(R.layout.fragment_media_preview_v2), Med
|
|||
}
|
||||
})
|
||||
initializeFullScreenUi()
|
||||
initializeAlbumRail()
|
||||
anchorMarginsToBottomInsets(binding.mediaPreviewDetailsContainer)
|
||||
lifecycleDisposable += viewModel.state.distinctUntilChanged().observeOn(AndroidSchedulers.mainThread()).subscribe {
|
||||
bindCurrentState(it)
|
||||
}
|
||||
}
|
||||
|
||||
private fun initializeFullScreenUi() {
|
||||
fullscreenHelper.configureToolbarLayout(binding.toolbarCutoutSpacer, binding.toolbar)
|
||||
fullscreenHelper.hideSystemUI()
|
||||
private fun initializeToolbar(toolbar: MaterialToolbar, args: MediaIntentFactory.MediaPreviewArgs) {
|
||||
toolbar.setNavigationOnClickListener {
|
||||
requireActivity().onBackPressed()
|
||||
}
|
||||
|
||||
binding.toolbar.inflateMenu(R.menu.media_preview)
|
||||
|
||||
// Restricted to API26 because of MemoryFileUtil not supporting lower API levels well
|
||||
binding.toolbar.menu.findItem(R.id.media_preview__share).isVisible = Build.VERSION.SDK_INT >= 26
|
||||
|
||||
if (args.hideAllMedia) {
|
||||
binding.toolbar.menu.findItem(R.id.media_preview__overview).isVisible = false
|
||||
}
|
||||
}
|
||||
|
||||
private fun initializeViewModel() {
|
||||
val args = MediaIntentFactory.requireArguments(requireArguments())
|
||||
private fun initializeAlbumRail() {
|
||||
binding.mediaPreviewAlbumRail.itemAnimator = null // Or can crash when set to INVISIBLE while animating by FullscreenHelper https://issuetracker.google.com/issues/148720682
|
||||
binding.mediaPreviewAlbumRail.layoutManager = LinearLayoutManager(requireContext(), LinearLayoutManager.HORIZONTAL, false)
|
||||
binding.mediaPreviewAlbumRail.adapter = MediaRailAdapter(
|
||||
GlideApp.with(this),
|
||||
object : MediaRailAdapter.RailItemListener {
|
||||
override fun onRailItemClicked(distanceFromActive: Int) {
|
||||
binding.mediaPager.currentItem += distanceFromActive
|
||||
}
|
||||
|
||||
override fun onRailItemDeleteClicked(distanceFromActive: Int) {
|
||||
throw UnsupportedOperationException("Callback unsupported.")
|
||||
}
|
||||
},
|
||||
false
|
||||
)
|
||||
}
|
||||
|
||||
private fun initializeFullScreenUi() {
|
||||
fullscreenHelper.configureToolbarLayout(binding.toolbarCutoutSpacer, binding.toolbar)
|
||||
fullscreenHelper.showAndHideWithSystemUI(requireActivity().window, binding.toolbarLayout, binding.mediaPreviewDetailsContainer)
|
||||
}
|
||||
|
||||
private fun initializeViewModel(args: MediaIntentFactory.MediaPreviewArgs) {
|
||||
if (!MediaUtil.isImageType(args.initialMediaType) && !MediaUtil.isVideoType(args.initialMediaType)) {
|
||||
Log.w(TAG, "Unsupported media type sent to MediaPreviewV2Fragment, finishing.")
|
||||
Snackbar.make(binding.root, R.string.MediaPreviewActivity_unssuported_media_type, Snackbar.LENGTH_LONG)
|
||||
.setAction(R.string.MediaPreviewActivity_dismiss_due_to_error) {
|
||||
requireActivity().finish()
|
||||
}.show()
|
||||
}
|
||||
viewModel.setShowThread(args.showThread)
|
||||
val sorting = MediaDatabase.Sorting.values()[args.sorting]
|
||||
viewModel.fetchAttachments(args.initialMediaUri, args.threadId, sorting)
|
||||
|
@ -67,7 +141,11 @@ class MediaPreviewV2Fragment : Fragment(R.layout.fragment_media_preview_v2), Med
|
|||
private fun bindCurrentState(currentState: MediaPreviewV2State) {
|
||||
when (currentState.loadState) {
|
||||
MediaPreviewV2State.LoadState.READY -> bindReadyState(currentState)
|
||||
// INIT, else -> no-op
|
||||
MediaPreviewV2State.LoadState.LOADED -> {
|
||||
bindReadyState(currentState)
|
||||
bindLoadedState(currentState)
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -76,6 +154,58 @@ class MediaPreviewV2Fragment : Fragment(R.layout.fragment_media_preview_v2), Med
|
|||
val currentItem: MediaDatabase.MediaRecord = currentState.mediaRecords[currentState.position]
|
||||
binding.toolbar.title = getTitleText(currentItem, currentState.showThread)
|
||||
binding.toolbar.subtitle = getSubTitleText(currentItem)
|
||||
|
||||
val menu: Menu = binding.toolbar.menu
|
||||
if (currentItem.threadId == MediaIntentFactory.NOT_IN_A_THREAD.toLong()) {
|
||||
menu.findItem(R.id.media_preview__overview).isVisible = false
|
||||
menu.findItem(R.id.delete).isVisible = false
|
||||
}
|
||||
|
||||
binding.toolbar.setOnMenuItemClickListener {
|
||||
when (it.itemId) {
|
||||
R.id.media_preview__overview -> showOverview(currentItem.threadId)
|
||||
R.id.media_preview__forward -> forward(currentItem)
|
||||
R.id.media_preview__share -> share(currentItem)
|
||||
R.id.save -> saveToDisk(currentItem)
|
||||
R.id.delete -> deleteMedia(currentItem)
|
||||
android.R.id.home -> requireActivity().finish()
|
||||
else -> return@setOnMenuItemClickListener false
|
||||
}
|
||||
return@setOnMenuItemClickListener true
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* These are binding steps that need a reference to the actual fragment within the pager.
|
||||
* This is not available until after a page has been chosen by the ViewPager, and we receive the
|
||||
* {@link OnPageChangeCallback}.
|
||||
*/
|
||||
private fun bindLoadedState(currentState: MediaPreviewV2State) {
|
||||
val currentItem: MediaDatabase.MediaRecord = currentState.mediaRecords[currentState.position]
|
||||
val currentFragment: Fragment? = childFragmentManager.findFragmentByTag("f${currentState.position}")
|
||||
val playbackControls = (currentFragment as? MediaPreviewFragment)?.playbackControls
|
||||
val albumThumbnailMedia = currentState.mediaRecords.map { it.toMedia() }
|
||||
val caption = currentItem.attachment?.caption
|
||||
if (albumThumbnailMedia.isEmpty() && caption == null && playbackControls == null) {
|
||||
binding.mediaPreviewDetailsContainer.visibility = View.GONE
|
||||
} else {
|
||||
binding.mediaPreviewDetailsContainer.visibility = View.VISIBLE
|
||||
}
|
||||
binding.mediaPreviewAlbumRail.visibility = if (albumThumbnailMedia.isEmpty()) View.GONE else View.VISIBLE
|
||||
(binding.mediaPreviewAlbumRail.adapter as MediaRailAdapter).setMedia(albumThumbnailMedia, currentState.position)
|
||||
binding.mediaPreviewAlbumRail.smoothScrollToPosition(currentState.position)
|
||||
|
||||
binding.mediaPreviewCaptionContainer.visibility = if (caption == null) View.GONE else View.VISIBLE
|
||||
binding.mediaPreviewCaption.text = caption
|
||||
|
||||
if (playbackControls != null) {
|
||||
val params = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
|
||||
playbackControls.layoutParams = params
|
||||
binding.mediaPreviewPlaybackControlsContainer.removeAllViews()
|
||||
binding.mediaPreviewPlaybackControlsContainer.addView(playbackControls)
|
||||
} else {
|
||||
binding.mediaPreviewPlaybackControlsContainer.removeAllViews()
|
||||
}
|
||||
}
|
||||
|
||||
private fun getTitleText(mediaRecord: MediaDatabase.MediaRecord, showThread: Boolean): String {
|
||||
|
@ -112,20 +242,148 @@ class MediaPreviewV2Fragment : Fragment(R.layout.fragment_media_preview_v2), Med
|
|||
getString(R.string.MediaPreviewActivity_draft)
|
||||
}
|
||||
|
||||
private fun anchorMarginsToBottomInsets(viewToAnchor: View) {
|
||||
ViewCompat.setOnApplyWindowInsetsListener(viewToAnchor) { view: View, windowInsetsCompat: WindowInsetsCompat ->
|
||||
val layoutParams = view.layoutParams as MarginLayoutParams
|
||||
val systemBarInsets = windowInsetsCompat.getInsets(WindowInsetsCompat.Type.systemBars())
|
||||
layoutParams.setMargins(
|
||||
systemBarInsets.left,
|
||||
layoutParams.topMargin,
|
||||
systemBarInsets.right,
|
||||
systemBarInsets.bottom
|
||||
)
|
||||
view.layoutParams = layoutParams
|
||||
windowInsetsCompat
|
||||
}
|
||||
}
|
||||
|
||||
private fun MediaDatabase.MediaRecord.toMedia(): Media? {
|
||||
val attachment = this.attachment
|
||||
val uri = attachment?.uri
|
||||
if (attachment == null || uri == null) {
|
||||
return null
|
||||
}
|
||||
|
||||
return Media(
|
||||
uri,
|
||||
this.contentType,
|
||||
this.date,
|
||||
attachment.width,
|
||||
attachment.height,
|
||||
attachment.size,
|
||||
0,
|
||||
attachment.isBorderless,
|
||||
attachment.isVideoGif,
|
||||
Optional.empty(),
|
||||
Optional.ofNullable(attachment.caption),
|
||||
Optional.empty()
|
||||
)
|
||||
}
|
||||
|
||||
override fun singleTapOnMedia(): Boolean {
|
||||
Log.d(TAG, "singleTapOnMedia()")
|
||||
fullscreenHelper.toggleUiVisibility()
|
||||
return true
|
||||
}
|
||||
|
||||
override fun mediaNotAvailable() {
|
||||
Log.d(TAG, "mediaNotAvailable()")
|
||||
Snackbar.make(binding.root, R.string.MediaPreviewActivity_media_no_longer_available, Snackbar.LENGTH_LONG)
|
||||
.setAction(R.string.MediaPreviewActivity_dismiss_due_to_error) {
|
||||
requireActivity().finish()
|
||||
}.show()
|
||||
}
|
||||
|
||||
override fun onMediaReady() {
|
||||
Log.d(TAG, "onMediaReady()")
|
||||
}
|
||||
|
||||
private fun showOverview(threadId: Long) {
|
||||
val context = requireContext()
|
||||
context.startActivity(MediaOverviewActivity.forThread(context, threadId))
|
||||
}
|
||||
|
||||
private fun forward(mediaItem: MediaDatabase.MediaRecord) {
|
||||
val attachment = mediaItem.attachment
|
||||
val uri = attachment?.uri
|
||||
if (attachment != null && uri != null) {
|
||||
MultiselectForwardFragmentArgs.create(
|
||||
requireContext(),
|
||||
mediaItem.threadId,
|
||||
uri,
|
||||
attachment.contentType
|
||||
) { args: MultiselectForwardFragmentArgs ->
|
||||
MultiselectForwardFragment.showBottomSheet(childFragmentManager, args)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun share(mediaItem: MediaDatabase.MediaRecord) {
|
||||
val attachment = mediaItem.attachment
|
||||
val uri = attachment?.uri
|
||||
if (attachment != null && uri != null) {
|
||||
val publicUri = PartAuthority.getAttachmentPublicUri(uri)
|
||||
val mimeType = Intent.normalizeMimeType(attachment.contentType)
|
||||
val shareIntent = ShareCompat.IntentBuilder(requireActivity())
|
||||
.setStream(publicUri)
|
||||
.setType(mimeType)
|
||||
.createChooserIntent()
|
||||
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
|
||||
try {
|
||||
startActivity(shareIntent)
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
Log.w(TAG, "No activity existed to share the media.", e)
|
||||
Toast.makeText(requireContext(), R.string.MediaPreviewActivity_cant_find_an_app_able_to_share_this_media, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun saveToDisk(mediaItem: MediaDatabase.MediaRecord) {
|
||||
SaveAttachmentTask.showWarningDialog(requireContext()) { _: DialogInterface?, _: Int ->
|
||||
if (StorageUtil.canWriteToMediaStore()) {
|
||||
performSaveToDisk(mediaItem)
|
||||
return@showWarningDialog
|
||||
}
|
||||
Permissions.with(this)
|
||||
.request(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||
.ifNecessary()
|
||||
.withPermanentDenialDialog(getString(R.string.MediaPreviewActivity_signal_needs_the_storage_permission_in_order_to_write_to_external_storage_but_it_has_been_permanently_denied))
|
||||
.onAnyDenied { Toast.makeText(requireContext(), R.string.MediaPreviewActivity_unable_to_write_to_external_storage_without_permission, Toast.LENGTH_LONG).show() }
|
||||
.onAllGranted { performSaveToDisk(mediaItem) }
|
||||
.execute()
|
||||
}
|
||||
}
|
||||
|
||||
fun performSaveToDisk(mediaItem: MediaDatabase.MediaRecord) {
|
||||
val saveTask = SaveAttachmentTask(requireContext())
|
||||
val saveDate = if (mediaItem.date > 0) mediaItem.date else System.currentTimeMillis()
|
||||
val attachment = mediaItem.attachment
|
||||
val uri = attachment?.uri
|
||||
if (attachment != null && uri != null) {
|
||||
saveTask.executeOnExecutor(SignalExecutors.BOUNDED_IO, SaveAttachmentTask.Attachment(uri, attachment.contentType, saveDate, null))
|
||||
}
|
||||
}
|
||||
|
||||
private fun deleteMedia(mediaItem: MediaDatabase.MediaRecord) {
|
||||
val attachment: DatabaseAttachment = mediaItem.attachment ?: return
|
||||
|
||||
MaterialAlertDialogBuilder(requireContext())
|
||||
.setIcon(R.drawable.ic_warning)
|
||||
.setTitle(R.string.MediaPreviewActivity_media_delete_confirmation_title)
|
||||
.setMessage(R.string.MediaPreviewActivity_media_delete_confirmation_message)
|
||||
.setCancelable(true)
|
||||
.setPositiveButton(R.string.delete) { _, _ ->
|
||||
viewModel.deleteItem(requireContext(), attachment, onSuccess = {
|
||||
requireActivity().finish()
|
||||
}, onError = {
|
||||
Log.e(TAG, "Delete failed!", it)
|
||||
requireActivity().finish()
|
||||
})
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.show()
|
||||
}
|
||||
|
||||
companion object {
|
||||
val ARGS_KEY: String = "args"
|
||||
const val ARGS_KEY: String = "args"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,5 +8,5 @@ data class MediaPreviewV2State(
|
|||
val position: Int = 0,
|
||||
val showThread: Boolean = false
|
||||
) {
|
||||
enum class LoadState { INIT, READY, }
|
||||
enum class LoadState { INIT, READY, LOADED }
|
||||
}
|
||||
|
|
|
@ -1,13 +1,19 @@
|
|||
package org.thoughtcrime.securesms.mediapreview
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import androidx.lifecycle.ViewModel
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.rxjava3.core.Flowable
|
||||
import io.reactivex.rxjava3.core.Single
|
||||
import io.reactivex.rxjava3.disposables.CompositeDisposable
|
||||
import io.reactivex.rxjava3.functions.Consumer
|
||||
import io.reactivex.rxjava3.kotlin.plusAssign
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.thoughtcrime.securesms.attachments.DatabaseAttachment
|
||||
import org.thoughtcrime.securesms.database.MediaDatabase
|
||||
import org.thoughtcrime.securesms.util.AttachmentUtil
|
||||
import org.thoughtcrime.securesms.util.rx.RxStore
|
||||
|
||||
class MediaPreviewV2ViewModel : ViewModel() {
|
||||
|
@ -19,7 +25,8 @@ class MediaPreviewV2ViewModel : ViewModel() {
|
|||
val state: Flowable<MediaPreviewV2State> = store.stateFlowable.observeOn(AndroidSchedulers.mainThread())
|
||||
|
||||
fun fetchAttachments(startingUri: Uri, threadId: Long, sorting: MediaDatabase.Sorting) {
|
||||
disposables += store.update(repository.getAttachments(startingUri, threadId, sorting)) { mediaRecords: List<MediaDatabase.MediaRecord>, oldState: MediaPreviewV2State ->
|
||||
disposables += store.update(repository.getAttachments(startingUri, threadId, sorting)) {
|
||||
mediaRecords: List<MediaDatabase.MediaRecord>, oldState: MediaPreviewV2State ->
|
||||
oldState.copy(
|
||||
mediaRecords = mediaRecords,
|
||||
loadState = MediaPreviewV2State.LoadState.READY
|
||||
|
@ -35,10 +42,17 @@ class MediaPreviewV2ViewModel : ViewModel() {
|
|||
|
||||
fun setCurrentPage(position: Int) {
|
||||
store.update { oldState ->
|
||||
oldState.copy(position = position)
|
||||
oldState.copy(position = position, loadState = MediaPreviewV2State.LoadState.LOADED)
|
||||
}
|
||||
}
|
||||
|
||||
fun deleteItem(context: Context, attachment: DatabaseAttachment, onSuccess: Consumer<in Unit>, onError: Consumer<in Throwable>) {
|
||||
disposables += Single.fromCallable { AttachmentUtil.deleteAttachment(context.applicationContext, attachment) }
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(onSuccess, onError)
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
disposables.dispose()
|
||||
store.dispose()
|
||||
|
|
|
@ -3,4 +3,5 @@
|
|||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/fragment_container_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/signal_dark_colorNeutral"/>
|
|
@ -1,11 +1,67 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/core_grey_95"
|
||||
android:theme="@style/TextSecure.MediaPreview">
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:background="@color/signal_dark_colorNeutral">
|
||||
<androidx.viewpager2.widget.ViewPager2
|
||||
android:id="@+id/media_pager"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clickable="true"
|
||||
android:focusable="true" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/media_preview_details_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom"
|
||||
android:animateLayoutChanges="true"
|
||||
android:background="@drawable/image_preview_shade"
|
||||
android:gravity="bottom"
|
||||
android:orientation="vertical"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible">
|
||||
|
||||
<org.thoughtcrime.securesms.components.MaxHeightScrollView
|
||||
android:id="@+id/media_preview_caption_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="32dp"
|
||||
android:animateLayoutChanges="true"
|
||||
app:scrollView_maxHeight="120dp">
|
||||
|
||||
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
|
||||
android:id="@+id/media_preview_caption"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:paddingBottom="8dp"
|
||||
style="@style/Signal.Text.Body"
|
||||
android:textColor="@color/core_white"
|
||||
android:gravity="bottom"
|
||||
tools:text="With great power comes great responsibility." />
|
||||
|
||||
</org.thoughtcrime.securesms.components.MaxHeightScrollView>
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/media_preview_album_rail"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_marginEnd="12dp"
|
||||
tools:layout_height="64dp"/>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/media_preview_playback_controls_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:animateLayoutChanges="true"/>
|
||||
|
||||
</LinearLayout>
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/toolbar_layout"
|
||||
android:layout_width="match_parent"
|
||||
|
@ -19,18 +75,14 @@
|
|||
android:layout_height="0dp"
|
||||
android:visibility="gone" />
|
||||
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:theme="?actionBarStyle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:background="@android:color/transparent" />
|
||||
android:background="@android:color/transparent"
|
||||
app:navigationIcon="@drawable/ic_arrow_left_white_24" />
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
<androidx.viewpager2.widget.ViewPager2
|
||||
android:id="@+id/media_pager"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clickable="true"
|
||||
android:focusable="true" />
|
||||
</FrameLayout>
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
|
@ -1965,6 +1965,7 @@
|
|||
<string name="MediaPreviewActivity_s_to_you">%1$s to you</string>
|
||||
<string name="MediaPreviewActivity_media_no_longer_available">Media no longer available.</string>
|
||||
<string name="MediaPreviewActivity_cant_find_an_app_able_to_share_this_media">Can\'t find an app able to share this media.</string>
|
||||
<string name="MediaPreviewActivity_dismiss_due_to_error">Close</string>
|
||||
|
||||
<!-- MessageNotifier -->
|
||||
<string name="MessageNotifier_d_new_messages_in_d_conversations">%1$d new messages in %2$d conversations</string>
|
||||
|
|
Ładowanie…
Reference in New Issue