diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index d8177e9d..bfd7e73d 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -79,16 +79,13 @@ - + android:theme="@style/TuskyBaseTheme" + android:configChanges="orientation|screenSize|keyboardHidden"/> diff --git a/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java b/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java index 8a96fd77..00685e13 100644 --- a/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java +++ b/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java @@ -15,31 +15,22 @@ package com.keylesspalace.tusky; -import android.Manifest; -import android.app.DownloadManager; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; -import android.content.pm.PackageManager; import android.graphics.Color; import android.graphics.PorterDuff; import android.graphics.drawable.Drawable; -import android.net.Uri; -import android.os.Build; import android.os.Bundle; -import android.os.Environment; import android.preference.PreferenceManager; import android.support.annotation.Nullable; import android.support.annotation.StringRes; import android.support.design.widget.Snackbar; -import android.support.v4.app.ActivityCompat; -import android.support.v4.content.ContextCompat; import android.support.v7.app.AppCompatActivity; import android.util.Log; import android.util.TypedValue; import android.view.Menu; import android.view.View; -import android.widget.Toast; import com.evernote.android.job.JobManager; import com.evernote.android.job.JobRequest; @@ -48,7 +39,6 @@ import com.keylesspalace.tusky.db.AccountManager; import com.keylesspalace.tusky.di.Injectable; import com.keylesspalace.tusky.util.ThemeUtils; -import java.io.File; import java.util.ArrayList; import java.util.List; @@ -192,28 +182,6 @@ public abstract class BaseActivity extends AppCompatActivity implements Injectab .scheduleAsync(); } - protected void downloadFile(String url) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && - ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) - != PackageManager.PERMISSION_GRANTED) { - ActivityCompat.requestPermissions(this, - new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, - PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE); - } else { - String filename = new File(url).getName(); - - String toastText = String.format(getResources().getString(R.string.download_image), filename); - Toast.makeText(getApplicationContext(), toastText, Toast.LENGTH_SHORT).show(); - - DownloadManager downloadManager = (DownloadManager) getSystemService(Context.DOWNLOAD_SERVICE); - DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url)); - request.allowScanningByMediaScanner(); - request.setDestinationInExternalPublicDir(Environment.DIRECTORY_PICTURES, - getString(R.string.app_name) + "/" + filename); - downloadManager.enqueue(request); - } - } - protected void showErrorDialog(View anyView, @StringRes int descriptionId, @StringRes int actionId, View.OnClickListener listener) { if (anyView != null) { Snackbar bar = Snackbar.make(anyView, getString(descriptionId), Snackbar.LENGTH_SHORT); diff --git a/app/src/main/java/com/keylesspalace/tusky/ViewMediaActivity.kt b/app/src/main/java/com/keylesspalace/tusky/ViewMediaActivity.kt index 443173e1..1498ad79 100644 --- a/app/src/main/java/com/keylesspalace/tusky/ViewMediaActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/ViewMediaActivity.kt @@ -15,8 +15,10 @@ package com.keylesspalace.tusky +import android.Manifest import android.animation.Animator import android.animation.AnimatorListenerAdapter +import android.app.DownloadManager import android.content.Context import android.content.Intent import android.content.pm.PackageManager @@ -24,16 +26,23 @@ import android.graphics.Bitmap import android.graphics.Color import android.graphics.drawable.Drawable import android.net.Uri +import android.os.Build import android.os.Bundle +import android.os.Environment +import android.support.v4.app.ActivityCompat +import android.support.v4.content.ContextCompat import android.support.v4.content.FileProvider import android.support.v4.view.ViewPager import android.util.Log import android.view.Menu import android.view.MenuItem import android.view.View +import android.webkit.MimeTypeMap +import android.widget.Toast import com.keylesspalace.tusky.BuildConfig.APPLICATION_ID +import com.keylesspalace.tusky.entity.Attachment +import com.keylesspalace.tusky.fragment.ViewImageFragment -import com.keylesspalace.tusky.fragment.ViewMediaFragment import com.keylesspalace.tusky.pager.AvatarImagePagerAdapter import com.keylesspalace.tusky.pager.ImagePagerAdapter import com.keylesspalace.tusky.util.CollectionUtil.map @@ -50,7 +59,7 @@ import java.io.FileOutputStream import java.io.IOException import java.util.ArrayList -class ViewMediaActivity : BaseActivity(), ViewMediaFragment.PhotoActionsListener { +class ViewMediaActivity : BaseActivity(), ViewImageFragment.PhotoActionsListener { companion object { private const val EXTRA_ATTACHMENTS = "attachments" private const val EXTRA_ATTACHMENT_INDEX = "index" @@ -114,12 +123,10 @@ class ViewMediaActivity : BaseActivity(), ViewMediaFragment.PhotoActionsListener viewPager.adapter = adapter viewPager.currentItem = initialPosition - viewPager.addOnPageChangeListener(object: ViewPager.OnPageChangeListener { + viewPager.addOnPageChangeListener(object: ViewPager.SimpleOnPageChangeListener() { override fun onPageSelected(position: Int) { toolbar.title = adapter.getPageTitle(position) } - override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {} - override fun onPageScrollStateChanged(state: Int) {} }) // Setup the toolbar. @@ -133,9 +140,9 @@ class ViewMediaActivity : BaseActivity(), ViewMediaFragment.PhotoActionsListener toolbar.setNavigationOnClickListener { _ -> supportFinishAfterTransition() } toolbar.setOnMenuItemClickListener { item: MenuItem -> when (item.itemId) { - R.id.action_download -> downloadImage() + R.id.action_download -> downloadMedia() R.id.action_open_status -> onOpenStatus() - R.id.action_share_media -> shareImage() + R.id.action_share_media -> shareMedia() } true } @@ -182,16 +189,34 @@ class ViewMediaActivity : BaseActivity(), ViewMediaFragment.PhotoActionsListener when (requestCode) { PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE -> { if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { - downloadImage() + downloadMedia() } else { - showErrorDialog(toolbar, R.string.error_media_download_permission, R.string.action_retry) { _ -> downloadImage() } + showErrorDialog(toolbar, R.string.error_media_download_permission, R.string.action_retry) { _ -> downloadMedia() } } } } } - private fun downloadImage() { - downloadFile(attachments!![viewPager.currentItem].attachment.url) + private fun downloadMedia() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && + ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) + != PackageManager.PERMISSION_GRANTED) { + ActivityCompat.requestPermissions(this, + arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), + PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE) + } else { + val url = attachments!![viewPager.currentItem].attachment.url + val filename = File(url).name + + val toastText = String.format(resources.getString(R.string.download_image), filename) + Toast.makeText(applicationContext, toastText, Toast.LENGTH_SHORT).show() + + val downloadManager = getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager + val request = DownloadManager.Request(Uri.parse(url)) + request.setDestinationInExternalPublicDir(Environment.DIRECTORY_PICTURES, + getString(R.string.app_name) + "/" + filename) + downloadManager.enqueue(request) + } } private fun onOpenStatus() { @@ -199,7 +224,7 @@ class ViewMediaActivity : BaseActivity(), ViewMediaFragment.PhotoActionsListener startActivityWithSlideInAnimation(ViewThreadActivity.startIntent(this, attach.statusId, attach.statusUrl)) } - private fun shareImage() { + private fun shareMedia() { val directory = applicationContext.getExternalFilesDir("Tusky") if (directory == null || !(directory.exists())) { Log.e(TAG, "Error obtaining directory to save temporary media.") @@ -207,10 +232,27 @@ class ViewMediaActivity : BaseActivity(), ViewMediaFragment.PhotoActionsListener } val attachment = attachments!![viewPager.currentItem].attachment - val context = applicationContext + when(attachment.type) { + Attachment.Type.IMAGE -> shareImage(directory, attachment.url) + Attachment.Type.VIDEO, + Attachment.Type.GIFV -> shareVideo(directory, attachment.url) + else -> Log.e(TAG, "Unknown media format for sharing.") + } + } + + private fun shareFile(file: File, mimeType: String?) { + val sendIntent = Intent() + sendIntent.action = Intent.ACTION_SEND + sendIntent.putExtra(Intent.EXTRA_STREAM, FileProvider.getUriForFile(applicationContext, "$APPLICATION_ID.fileprovider", file)) + sendIntent.type = mimeType + startActivity(Intent.createChooser(sendIntent, resources.getText(R.string.send_media_to))) + } + + + private fun shareImage(directory: File, url: String) { val file = File(directory, getTemporaryMediaFilename("png")) - Picasso.with(context).load(Uri.parse(attachment.url)).into(object: Target { + Picasso.with(applicationContext).load(Uri.parse(url)).into(object: Target { override fun onBitmapLoaded(bitmap: Bitmap, from: Picasso.LoadedFrom) { try { val stream = FileOutputStream(file) @@ -230,10 +272,23 @@ class ViewMediaActivity : BaseActivity(), ViewMediaFragment.PhotoActionsListener override fun onPrepareLoad(placeHolderDrawable: Drawable) { } }) - val sendIntent = Intent() - sendIntent.action = Intent.ACTION_SEND - sendIntent.putExtra(Intent.EXTRA_STREAM, FileProvider.getUriForFile(context, "$APPLICATION_ID.fileprovider", file)) - sendIntent.type = "image/png" - startActivity(Intent.createChooser(sendIntent, resources.getText(R.string.send_media_to))) + shareFile(file, "image/png") + } + + private fun shareVideo(directory: File, url: String) { + val uri = Uri.parse(url) + val mimeTypeMap = MimeTypeMap.getSingleton() + val extension = MimeTypeMap.getFileExtensionFromUrl(url) + val mimeType = mimeTypeMap.getMimeTypeFromExtension(extension) + val filename = getTemporaryMediaFilename(extension) + val file = File(directory, filename) + + val downloadManager = getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager + val request = DownloadManager.Request(uri) + request.setDestinationUri(Uri.fromFile(file)) + request.setVisibleInDownloadsUi(false) + downloadManager.enqueue(request) + + shareFile(file, mimeType) } } diff --git a/app/src/main/java/com/keylesspalace/tusky/ViewVideoActivity.kt b/app/src/main/java/com/keylesspalace/tusky/ViewVideoActivity.kt deleted file mode 100644 index 4f3d681c..00000000 --- a/app/src/main/java/com/keylesspalace/tusky/ViewVideoActivity.kt +++ /dev/null @@ -1,185 +0,0 @@ -/* Copyright 2017 Andrew Dawson - * - * This file is a part of Tusky. - * - * This program is free software; you can redistribute it and/or modify it under the terms of the - * GNU General Public License as published by the Free Software Foundation; either version 3 of the - * License, or (at your option) any later version. - * - * Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even - * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General - * Public License for more details. - * - * You should have received a copy of the GNU General Public License along with Tusky; if not, - * see . */ - -package com.keylesspalace.tusky - -import android.animation.Animator -import android.animation.AnimatorListenerAdapter -import android.annotation.SuppressLint -import android.app.DownloadManager -import android.content.Context -import android.content.Intent -import android.content.pm.PackageManager -import android.graphics.Color -import android.net.Uri -import android.os.Bundle -import android.os.Handler -import android.os.Looper -import android.support.v4.content.FileProvider -import android.util.Log -import android.view.Menu -import android.view.MenuItem -import android.view.MotionEvent -import android.view.View -import android.webkit.MimeTypeMap -import android.widget.MediaController - -import kotlinx.android.synthetic.main.activity_view_video.* - -import java.io.File - -import com.keylesspalace.tusky.BuildConfig.APPLICATION_ID -import com.keylesspalace.tusky.util.getTemporaryMediaFilename -import com.keylesspalace.tusky.util.hide -import com.keylesspalace.tusky.util.show - -class ViewVideoActivity: BaseActivity() { - - private val handler = Handler(Looper.getMainLooper()) - private lateinit var url: String - private lateinit var statusID: String - private lateinit var statusURL: String - - companion object { - private const val TAG = "ViewVideoActivity" - const val URL_EXTRA = "url" - const val STATUS_ID_EXTRA = "statusID" - const val STATUS_URL_EXTRA = "statusURL" - } - - @SuppressLint("ClickableViewAccessibility") - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContentView(R.layout.activity_view_video) - - setSupportActionBar(toolbar) - val bar = supportActionBar - if (bar != null) { - bar.title = null - bar.setDisplayHomeAsUpEnabled(true) - bar.setDisplayShowHomeEnabled(true) - } - toolbar.setOnMenuItemClickListener {item -> - val id = item.itemId - when (id) { - R.id.action_download -> downloadFile(url) - R.id.action_open_status -> onOpenStatus() - R.id.action_share_media -> shareVideo() - } - true - } - - url = intent.getStringExtra(URL_EXTRA) - statusID = intent.getStringExtra(STATUS_ID_EXTRA) - statusURL = intent.getStringExtra(STATUS_URL_EXTRA) - - videoPlayer.setVideoPath(url) - val controller = MediaController(this) - controller.setMediaPlayer(videoPlayer) - videoPlayer.setMediaController(controller) - videoPlayer.requestFocus() - videoPlayer.setOnPreparedListener { mp -> - videoProgressBar.hide() - mp.isLooping = true - hideToolbarAfterDelay() - } - videoPlayer.start() - - videoPlayer.setOnTouchListener { _, event -> - if (event.action == MotionEvent.ACTION_DOWN) { - handler.removeCallbacksAndMessages(null) - toolbar.animate().cancel() - toolbar.alpha = 1.0f - toolbar.show() - hideToolbarAfterDelay() - } - false - } - - window.statusBarColor = Color.BLACK - } - - override fun onOptionsItemSelected(item: MenuItem): Boolean { - when (item.itemId) { - android.R.id.home -> { - onBackPressed() - return true - } - } - return super.onOptionsItemSelected(item) - } - - override fun onCreateOptionsMenu(menu: Menu): Boolean { - menuInflater.inflate(R.menu.view_media_toolbar, menu) - return true - } - - override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { - when (requestCode) { - PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE -> { - if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { - downloadFile(url) - } else { - showErrorDialog(toolbar, R.string.error_media_download_permission, R.string.action_retry) { _ -> downloadFile(url) } - } - } - } - } - - private fun hideToolbarAfterDelay() { - handler.postDelayed({ - toolbar.animate().alpha(0.0f).setListener(object: AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator) { - val decorView = window.decorView - val uiOptions = View.SYSTEM_UI_FLAG_LOW_PROFILE - decorView.systemUiVisibility = uiOptions - toolbar.hide() - animation.removeListener(this) - } - }) - }, 3000) - } - - private fun onOpenStatus() { - startActivityWithSlideInAnimation(ViewThreadActivity.startIntent(this, statusID, statusURL)) - } - - private fun shareVideo() { - val directory = applicationContext.getExternalFilesDir("Tusky") - if (directory == null || !(directory.exists())) { - Log.e(TAG, "Error obtaining directory to save temporary media.") - return - } - - val uri = Uri.parse(url) - val mimeTypeMap = MimeTypeMap.getSingleton() - val extension = MimeTypeMap.getFileExtensionFromUrl(url) - val mimeType = mimeTypeMap.getMimeTypeFromExtension(extension) - val filename = getTemporaryMediaFilename(extension) - val file = File(directory, filename) - - val downloadManager = getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager - val request = DownloadManager.Request(uri) - request.setDestinationUri(Uri.fromFile(file)) - request.setVisibleInDownloadsUi(false) - downloadManager.enqueue(request) - - val sendIntent = Intent() - sendIntent.action = Intent.ACTION_SEND - sendIntent.putExtra(Intent.EXTRA_STREAM, FileProvider.getUriForFile(applicationContext, "$APPLICATION_ID.fileprovider", file)) - sendIntent.type = mimeType - startActivity(Intent.createChooser(sendIntent, resources.getText(R.string.send_media_to))) - } -} diff --git a/app/src/main/java/com/keylesspalace/tusky/di/ActivitiesModule.kt b/app/src/main/java/com/keylesspalace/tusky/di/ActivitiesModule.kt index 01cdab11..2559bbd4 100644 --- a/app/src/main/java/com/keylesspalace/tusky/di/ActivitiesModule.kt +++ b/app/src/main/java/com/keylesspalace/tusky/di/ActivitiesModule.kt @@ -83,9 +83,6 @@ abstract class ActivitiesModule { @ContributesAndroidInjector abstract fun contributesViewMediaActivity(): ViewMediaActivity - @ContributesAndroidInjector - abstract fun contributesViewVideoActivity(): ViewVideoActivity - @ContributesAndroidInjector abstract fun contributesLicenseActivity(): LicenseActivity diff --git a/app/src/main/java/com/keylesspalace/tusky/fragment/AccountMediaFragment.kt b/app/src/main/java/com/keylesspalace/tusky/fragment/AccountMediaFragment.kt index 1c168ba8..a9bf429f 100644 --- a/app/src/main/java/com/keylesspalace/tusky/fragment/AccountMediaFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/fragment/AccountMediaFragment.kt @@ -30,7 +30,6 @@ import android.view.ViewGroup import android.widget.ImageView import com.keylesspalace.tusky.R import com.keylesspalace.tusky.ViewMediaActivity -import com.keylesspalace.tusky.ViewVideoActivity import com.keylesspalace.tusky.di.Injectable import com.keylesspalace.tusky.entity.Attachment import com.keylesspalace.tusky.entity.Status @@ -207,7 +206,9 @@ class AccountMediaFragment : BaseFragment(), Injectable { val type = items[currentIndex].attachment.type when (type) { - Attachment.Type.IMAGE -> { + Attachment.Type.IMAGE, + Attachment.Type.GIFV, + Attachment.Type.VIDEO -> { val intent = ViewMediaActivity.newIntent(context, items, currentIndex) if (view != null && activity != null) { val url = items[currentIndex].attachment.url @@ -218,13 +219,6 @@ class AccountMediaFragment : BaseFragment(), Injectable { startActivity(intent) } } - Attachment.Type.GIFV, Attachment.Type.VIDEO -> { - val intent = Intent(context, ViewVideoActivity::class.java) - intent.putExtra(ViewVideoActivity.URL_EXTRA, items[currentIndex].attachment.url) - intent.putExtra(ViewVideoActivity.STATUS_ID_EXTRA, items[currentIndex].statusId) - intent.putExtra(ViewVideoActivity.STATUS_URL_EXTRA, items[currentIndex].statusUrl) - startActivity(intent) - } Attachment.Type.UNKNOWN -> { }/* Intentionally do nothing. This case is here is to handle when new attachment * types are added to the API before code is added here to handle them. So, the diff --git a/app/src/main/java/com/keylesspalace/tusky/fragment/SFragment.java b/app/src/main/java/com/keylesspalace/tusky/fragment/SFragment.java index ac34f2e7..660f497b 100644 --- a/app/src/main/java/com/keylesspalace/tusky/fragment/SFragment.java +++ b/app/src/main/java/com/keylesspalace/tusky/fragment/SFragment.java @@ -36,7 +36,6 @@ import com.keylesspalace.tusky.R; import com.keylesspalace.tusky.ReportActivity; import com.keylesspalace.tusky.ViewMediaActivity; import com.keylesspalace.tusky.ViewTagActivity; -import com.keylesspalace.tusky.ViewVideoActivity; import com.keylesspalace.tusky.db.AccountEntity; import com.keylesspalace.tusky.db.AccountManager; import com.keylesspalace.tusky.entity.Attachment; @@ -234,6 +233,8 @@ public abstract class SFragment extends BaseFragment { final Attachment active = actionable.getAttachments().get(urlIndex); Attachment.Type type = active.getType(); switch (type) { + case GIFV: + case VIDEO: case IMAGE: { final List attachments = AttachmentViewData.list(actionable); final Intent intent = ViewMediaActivity.newIntent(getContext(), attachments, @@ -250,15 +251,6 @@ public abstract class SFragment extends BaseFragment { } break; } - case GIFV: - case VIDEO: { - Intent intent = new Intent(getContext(), ViewVideoActivity.class); - intent.putExtra(ViewVideoActivity.URL_EXTRA, active.getUrl()); - intent.putExtra(ViewVideoActivity.STATUS_ID_EXTRA, actionable.getId()); - intent.putExtra(ViewVideoActivity.STATUS_URL_EXTRA, actionable.getUrl()); - startActivity(intent); - break; - } case UNKNOWN: { /* Intentionally do nothing. This case is here is to handle when new attachment * types are added to the API before code is added here to handle them. So, the diff --git a/app/src/main/java/com/keylesspalace/tusky/fragment/ViewImageFragment.kt b/app/src/main/java/com/keylesspalace/tusky/fragment/ViewImageFragment.kt new file mode 100644 index 00000000..467b684c --- /dev/null +++ b/app/src/main/java/com/keylesspalace/tusky/fragment/ViewImageFragment.kt @@ -0,0 +1,217 @@ +/* Copyright 2017 Andrew Dawson + * + * This file is a part of Tusky. + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with Tusky; if not, + * see . */ + +package com.keylesspalace.tusky.fragment + +import android.animation.Animator +import android.animation.AnimatorListenerAdapter +import android.content.Context +import android.os.Bundle +import android.support.v4.view.ViewCompat +import android.text.TextUtils +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.ImageView + +import com.github.chrisbanes.photoview.PhotoViewAttacher +import com.keylesspalace.tusky.R +import com.keylesspalace.tusky.ViewMediaActivity +import com.keylesspalace.tusky.entity.Attachment +import com.keylesspalace.tusky.util.hide +import com.keylesspalace.tusky.util.show +import com.squareup.picasso.Callback +import com.squareup.picasso.NetworkPolicy +import com.squareup.picasso.Picasso +import kotlinx.android.synthetic.main.activity_view_media.* +import kotlinx.android.synthetic.main.fragment_view_image.* + +class ViewImageFragment : ViewMediaFragment() { + interface PhotoActionsListener { + fun onBringUp() + fun onDismiss() + fun onPhotoTap() + } + + private lateinit var attacher: PhotoViewAttacher + private lateinit var photoActionsListener: PhotoActionsListener + private lateinit var toolbar: View + + private var showingDescription = false + private var isDescriptionVisible = false + + companion object { + private const val TAG = "ViewImageFragment" + } + + override fun onAttach(context: Context) { + super.onAttach(context) + photoActionsListener = context as PhotoActionsListener + } + + override fun setupMediaView(url: String) { + attacher = PhotoViewAttacher(photoView) + + // Clicking outside the photo closes the viewer. + attacher.setOnOutsidePhotoTapListener { _ -> photoActionsListener.onDismiss() } + + attacher.setOnClickListener { _ -> onMediaTap() } + + /* A vertical swipe motion also closes the viewer. This is especially useful when the photo + * mostly fills the screen so clicking outside is difficult. */ + attacher.setOnSingleFlingListener { _, _, velocityX, velocityY -> + var result = false + if (Math.abs(velocityY) > Math.abs(velocityX)) { + photoActionsListener.onDismiss() + result = true + } + result + } + + // If we are the view to be shown initially... + if (arguments!!.getBoolean(ViewMediaFragment.ARG_START_POSTPONED_TRANSITION)) { + // Try to load image from disk. + Picasso.with(context) + .load(url) + .noFade() + .networkPolicy(NetworkPolicy.OFFLINE) + .into(photoView, object : Callback { + override fun onSuccess() { + // if we loaded image from disk, we should check that view is attached. + if (ViewCompat.isAttachedToWindow(photoView)) { + finishLoadingSuccessfully() + } else { + // if view is not attached yet, wait for an attachment and + // start transition when it's finally ready. + photoView.addOnAttachStateChangeListener( + object : View.OnAttachStateChangeListener { + override fun onViewAttachedToWindow(v: View?) { + finishLoadingSuccessfully() + photoView.removeOnAttachStateChangeListener(this) + } + + override fun onViewDetachedFromWindow(v: View?) {} + }) + } + } + + override fun onError() { + // if there's no image in cache, load from network and start transition + // immediately. + photoActionsListener.onBringUp() + loadImageFromNetwork(url, photoView) + } + }) + } else { + // if we're not initial page, don't bother. + loadImageFromNetwork(url, photoView) + } + + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { + toolbar = activity!!.toolbar + return inflater.inflate(R.layout.fragment_view_image, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + val arguments = this.arguments!! + val attachment = arguments.getParcelable(ARG_ATTACHMENT) + val url: String? + + if (attachment != null) { + url = attachment.url + + val description = attachment.description + + descriptionView.text = description + showingDescription = !TextUtils.isEmpty(description) + isDescriptionVisible = showingDescription + } else { + url = arguments.getString(ARG_AVATAR_URL) + if (url == null) { + throw IllegalArgumentException("attachment or avatar url has to be set") + } + + showingDescription = false + isDescriptionVisible = false + } + + // Setting visibility without animations so it looks nice when you scroll images + if (showingDescription && (activity as ViewMediaActivity).isToolbarVisible()) { + descriptionView.show() + } else { + descriptionView.hide() + } + + setupMediaView(url) + + setupToolbarVisibilityListener() + } + + + private fun onMediaTap() { + photoActionsListener.onPhotoTap() + } + + override fun onToolbarVisibilityChange(visible: Boolean) { + if (photoView == null || !userVisibleHint) { + return + } + isDescriptionVisible = showingDescription && visible + val alpha = if (isDescriptionVisible) 1.0f else 0.0f + descriptionView.animate().alpha(alpha) + .setListener(object : AnimatorListenerAdapter() { + override fun onAnimationEnd(animation: Animator) { + if (isDescriptionVisible) { + descriptionView.show() + } else { + descriptionView.hide() + } + animation.removeListener(this) + } + }) + .start() + } + + override fun onDetach() { + super.onDetach() + Picasso.with(context).cancelRequest(photoView) + } + + private fun loadImageFromNetwork(url: String, photoView: ImageView) { + Picasso.with(context) + .load(url) + .noPlaceholder() + .networkPolicy(NetworkPolicy.NO_STORE) + .into(photoView, object : Callback { + override fun onSuccess() { + finishLoadingSuccessfully() + } + + override fun onError() { + progressBar.hide() + } + }) + } + + private fun finishLoadingSuccessfully() { + progressBar.hide() + attacher.update() + photoActionsListener.onBringUp() + } +} diff --git a/app/src/main/java/com/keylesspalace/tusky/fragment/ViewMediaFragment.java b/app/src/main/java/com/keylesspalace/tusky/fragment/ViewMediaFragment.java deleted file mode 100644 index e27b9bc5..00000000 --- a/app/src/main/java/com/keylesspalace/tusky/fragment/ViewMediaFragment.java +++ /dev/null @@ -1,255 +0,0 @@ -/* Copyright 2017 Andrew Dawson - * - * This file is a part of Tusky. - * - * This program is free software; you can redistribute it and/or modify it under the terms of the - * GNU General Public License as published by the Free Software Foundation; either version 3 of the - * License, or (at your option) any later version. - * - * Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even - * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General - * Public License for more details. - * - * You should have received a copy of the GNU General Public License along with Tusky; if not, - * see . */ - -package com.keylesspalace.tusky.fragment; - -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.content.Context; -import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.v4.view.ViewCompat; -import android.text.TextUtils; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageView; -import android.widget.TextView; - -import com.github.chrisbanes.photoview.PhotoView; -import com.github.chrisbanes.photoview.PhotoViewAttacher; -import com.keylesspalace.tusky.R; -import com.keylesspalace.tusky.ViewMediaActivity; -import com.keylesspalace.tusky.entity.Attachment; -import com.squareup.picasso.Callback; -import com.squareup.picasso.NetworkPolicy; -import com.squareup.picasso.Picasso; - -import java.util.Objects; - -import kotlin.jvm.functions.Function0; - -public final class ViewMediaFragment extends BaseFragment { - public interface PhotoActionsListener { - void onBringUp(); - - void onDismiss(); - - void onPhotoTap(); - } - - private PhotoViewAttacher attacher; - private PhotoActionsListener photoActionsListener; - private View rootView; - private PhotoView photoView; - private TextView descriptionView; - - private boolean showingDescription; - private boolean isDescriptionVisible; - private Function0 toolbarVisibiltyDisposable; - - private static final String ARG_START_POSTPONED_TRANSITION = "startPostponedTransition"; - private static final String ARG_ATTACHMENT = "attach"; - private static final String ARG_AVATAR_URL = "avatarUrl"; - - public static ViewMediaFragment newInstance(@NonNull Attachment attachment, - boolean shouldStartPostponedTransition) { - Bundle arguments = new Bundle(2); - ViewMediaFragment fragment = new ViewMediaFragment(); - arguments.putParcelable(ARG_ATTACHMENT, attachment); - arguments.putBoolean(ARG_START_POSTPONED_TRANSITION, shouldStartPostponedTransition); - - fragment.setArguments(arguments); - return fragment; - } - - public static ViewMediaFragment newAvatarInstance(@NonNull String avatarUrl) { - Bundle arguments = new Bundle(2); - ViewMediaFragment fragment = new ViewMediaFragment(); - arguments.putString(ARG_AVATAR_URL, avatarUrl); - arguments.putBoolean(ARG_START_POSTPONED_TRANSITION, true); - - fragment.setArguments(arguments); - return fragment; - } - - @Override - public void onAttach(Context context) { - super.onAttach(context); - photoActionsListener = (PhotoActionsListener) context; - } - - @Override - public View onCreateView(@NonNull LayoutInflater inflater, final ViewGroup container, - Bundle savedInstanceState) { - rootView = inflater.inflate(R.layout.fragment_view_media, container, false); - photoView = rootView.findViewById(R.id.view_media_image); - descriptionView = rootView.findViewById(R.id.tv_media_description); - - final Bundle arguments = Objects.requireNonNull(getArguments(), "Empty arguments"); - final Attachment attachment = arguments.getParcelable(ARG_ATTACHMENT); - final String url; - - if(attachment != null) { - url = attachment.getUrl(); - - @Nullable final String description = attachment.getDescription(); - - descriptionView.setText(description); - showingDescription = !TextUtils.isEmpty(description); - isDescriptionVisible = showingDescription; - } else { - url = arguments.getString(ARG_AVATAR_URL); - if(url == null) { - throw new IllegalArgumentException("attachment or avatar url has to be set"); - } - - showingDescription = false; - isDescriptionVisible = false; - } - - // Setting visibility without animations so it looks nice when you scroll images - //noinspection ConstantConditions - descriptionView.setVisibility(showingDescription - && (((ViewMediaActivity) getActivity())).isToolbarVisible() - ? View.VISIBLE : View.GONE); - - attacher = new PhotoViewAttacher(photoView); - - // Clicking outside the photo closes the viewer. - attacher.setOnOutsidePhotoTapListener(imageView -> photoActionsListener.onDismiss()); - - attacher.setOnClickListener(v -> onMediaTap()); - - /* A vertical swipe motion also closes the viewer. This is especially useful when the photo - * mostly fills the screen so clicking outside is difficult. */ - attacher.setOnSingleFlingListener((e1, e2, velocityX, velocityY) -> { - if (Math.abs(velocityY) > Math.abs(velocityX)) { - photoActionsListener.onDismiss(); - return true; - } - return false; - }); - - ViewCompat.setTransitionName(photoView, url); - - // If we are the view to be shown initially... - if (arguments.getBoolean(ARG_START_POSTPONED_TRANSITION)) { - // Try to load image from disk. - Picasso.with(getContext()) - .load(url) - .noFade() - .networkPolicy(NetworkPolicy.OFFLINE) - .into(photoView, new Callback() { - @Override - public void onSuccess() { - // if we loaded image from disk, we should check that view is attached. - if (ViewCompat.isAttachedToWindow(photoView)) { - finishLoadingSuccessfully(); - } else { - // if view is not attached yet, wait for an attachment and - // start transition when it's finally ready. - photoView.addOnAttachStateChangeListener( - new View.OnAttachStateChangeListener() { - @Override - public void onViewAttachedToWindow(View v) { - finishLoadingSuccessfully(); - photoView.removeOnAttachStateChangeListener(this); - } - - @Override - public void onViewDetachedFromWindow(View v) { - } - }); - } - } - - @Override - public void onError() { - // if there's no image in cache, load from network and start transition - // immediately. - photoActionsListener.onBringUp(); - - loadImageFromNetwork(url, photoView); - } - }); - } else { - // if we're not initial page, don't bother. - loadImageFromNetwork(url, photoView); - } - - toolbarVisibiltyDisposable = ((ViewMediaActivity) getActivity()) - .addToolbarVisibilityListener(this::onToolbarVisibilityChange); - - return rootView; - } - - @Override - public void onDestroyView() { - if (toolbarVisibiltyDisposable != null) toolbarVisibiltyDisposable.invoke(); - super.onDestroyView(); - } - - private void onMediaTap() { - photoActionsListener.onPhotoTap(); - } - - private void onToolbarVisibilityChange(boolean visible) { - isDescriptionVisible = showingDescription && visible; - final int visibility = isDescriptionVisible ? View.VISIBLE : View.INVISIBLE; - int alpha = isDescriptionVisible ? 1 : 0; - descriptionView.animate().alpha(alpha) - .setListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - descriptionView.setVisibility(visibility); - animation.removeListener(this); - } - }) - .start(); - } - - @Override - public void onDetach() { - super.onDetach(); - Picasso.with(getContext()) - .cancelRequest(photoView); - } - - private void loadImageFromNetwork(String url, ImageView photoView) { - Picasso.with(getContext()) - .load(url) - .noPlaceholder() - .networkPolicy(NetworkPolicy.NO_STORE) - .into(photoView, new Callback() { - @Override - public void onSuccess() { - finishLoadingSuccessfully(); - } - - @Override - public void onError() { - rootView.findViewById(R.id.view_media_progress).setVisibility(View.GONE); - } - }); - } - - private void finishLoadingSuccessfully() { - rootView.findViewById(R.id.view_media_progress).setVisibility(View.GONE); - attacher.update(); - photoActionsListener.onBringUp(); - } -} diff --git a/app/src/main/java/com/keylesspalace/tusky/fragment/ViewMediaFragment.kt b/app/src/main/java/com/keylesspalace/tusky/fragment/ViewMediaFragment.kt new file mode 100644 index 00000000..628ca568 --- /dev/null +++ b/app/src/main/java/com/keylesspalace/tusky/fragment/ViewMediaFragment.kt @@ -0,0 +1,75 @@ +/* Copyright 2017 Andrew Dawson + * + * This file is a part of Tusky. + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with Tusky; if not, + * see . */ + +package com.keylesspalace.tusky.fragment + +import android.os.Bundle + +import com.keylesspalace.tusky.ViewMediaActivity +import com.keylesspalace.tusky.entity.Attachment + +abstract class ViewMediaFragment : BaseFragment() { + private var toolbarVisibiltyDisposable: Function0? = null + + abstract fun setupMediaView(url: String) + abstract fun onToolbarVisibilityChange(visible: Boolean) + + companion object { + @JvmStatic protected val ARG_START_POSTPONED_TRANSITION = "startPostponedTransition" + @JvmStatic protected val ARG_ATTACHMENT = "attach" + @JvmStatic protected val ARG_AVATAR_URL = "avatarUrl" + private const val TAG = "ViewMediaFragment" + + @JvmStatic + fun newInstance(attachment: Attachment, shouldStartPostponedTransition: Boolean): ViewMediaFragment { + val arguments = Bundle(2) + arguments.putParcelable(ARG_ATTACHMENT, attachment) + arguments.putBoolean(ARG_START_POSTPONED_TRANSITION, shouldStartPostponedTransition) + + val fragment = when (attachment.type) { + Attachment.Type.IMAGE -> ViewImageFragment() + Attachment.Type.VIDEO, + Attachment.Type.GIFV -> ViewVideoFragment() + else -> throw Exception("Unknown media type: $attachment") + } + fragment.arguments = arguments + return fragment + } + + @JvmStatic + fun newAvatarInstance(avatarUrl: String): ViewMediaFragment { + val arguments = Bundle(2) + val fragment = ViewImageFragment() + arguments.putString(ARG_AVATAR_URL, avatarUrl) + arguments.putBoolean(ARG_START_POSTPONED_TRANSITION, true) + + fragment.arguments = arguments + return fragment + } + } + + protected fun setupToolbarVisibilityListener() { + toolbarVisibiltyDisposable = (activity as ViewMediaActivity).addToolbarVisibilityListener(object: ViewMediaActivity.ToolbarVisibilityListener { + override fun onToolbarVisiblityChanged(isVisible: Boolean) { + onToolbarVisibilityChange(isVisible) + } + }) + } + + override fun onDestroyView() { + toolbarVisibiltyDisposable?.invoke() + super.onDestroyView() + } +} diff --git a/app/src/main/java/com/keylesspalace/tusky/fragment/ViewVideoFragment.kt b/app/src/main/java/com/keylesspalace/tusky/fragment/ViewVideoFragment.kt new file mode 100644 index 00000000..924b428f --- /dev/null +++ b/app/src/main/java/com/keylesspalace/tusky/fragment/ViewVideoFragment.kt @@ -0,0 +1,169 @@ +/* Copyright 2017 Andrew Dawson + * + * This file is a part of Tusky. + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with Tusky; if not, + * see . */ + +package com.keylesspalace.tusky.fragment + +import android.animation.Animator +import android.animation.AnimatorListenerAdapter +import android.annotation.SuppressLint +import android.os.Bundle +import android.os.Handler +import android.os.Looper +import android.support.v4.view.ViewCompat +import android.text.TextUtils +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.MediaController + +import com.keylesspalace.tusky.R +import com.keylesspalace.tusky.ViewMediaActivity +import com.keylesspalace.tusky.entity.Attachment +import com.keylesspalace.tusky.util.hide +import com.keylesspalace.tusky.util.show +import kotlinx.android.synthetic.main.activity_view_media.* +import kotlinx.android.synthetic.main.fragment_view_video.* + +class ViewVideoFragment : ViewMediaFragment() { + private lateinit var toolbar: View + private val handler = Handler(Looper.getMainLooper()) + private val hideToolbar = Runnable { + // Hoist toolbar hiding to activity so it can track state across different fragments + // This is explicitly stored as runnable so that we pass it to the handler later for cancellation + mediaActivity.onPhotoTap() + } + private lateinit var mediaActivity: ViewMediaActivity + private val TOOLBAR_HIDE_DELAY_MS = 3000L + + private var showingDescription = false + private var isDescriptionVisible = false + + companion object { + private const val TAG = "ViewVideoFragment" + } + + override fun setUserVisibleHint(isVisibleToUser: Boolean) { + // Start/pause/resume video playback as fragment is shown/hidden + super.setUserVisibleHint(isVisibleToUser) + if (videoPlayer == null) { + return + } + + if (isVisibleToUser) { + if (mediaActivity.isToolbarVisible()) { + handler.postDelayed(hideToolbar, TOOLBAR_HIDE_DELAY_MS) + } + videoPlayer?.start() + } else { + handler.removeCallbacks(hideToolbar) + videoPlayer?.pause() + } + } + + @SuppressLint("ClickableViewAccessibility") + override fun setupMediaView(url: String) { + val videoView = videoPlayer + videoView.setVideoPath(url) + val controller = MediaController(mediaActivity) + controller.setMediaPlayer(videoView) + videoView.setMediaController(controller) + videoView.requestFocus() + videoView.setOnTouchListener { _, _ -> + mediaActivity.onPhotoTap() + false + } + videoView.setOnPreparedListener { mp -> + progressBar.hide() + mp.isLooping = true + if (arguments!!.getBoolean(ViewMediaFragment.ARG_START_POSTPONED_TRANSITION)) { + hideToolbarAfterDelay(TOOLBAR_HIDE_DELAY_MS) + videoView.start() + } + } + + if (arguments!!.getBoolean(ViewMediaFragment.ARG_START_POSTPONED_TRANSITION)) { + mediaActivity.onBringUp() + } + } + + private fun hideToolbarAfterDelay(delayMilliseconds: Long) { + handler.postDelayed(hideToolbar, delayMilliseconds) + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { + toolbar = activity!!.toolbar + mediaActivity = activity as ViewMediaActivity + return inflater.inflate(R.layout.fragment_view_video, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + val arguments = this.arguments!! + val attachment = arguments.getParcelable(ViewMediaFragment.ARG_ATTACHMENT) + val url: String + + if (attachment == null) { + throw IllegalArgumentException("attachment has to be set") + } + url = attachment.url + val description = attachment.description + mediaDescription.text = description + showingDescription = !TextUtils.isEmpty(description) + isDescriptionVisible = showingDescription + + // Setting visibility without animations so it looks nice when you scroll media + //noinspection ConstantConditions + if (showingDescription && mediaActivity.isToolbarVisible()) { + mediaDescription.show() + } else { + mediaDescription.hide() + + } + + ViewCompat.setTransitionName(videoPlayer!!, url) + + setupMediaView(url) + + setupToolbarVisibilityListener() + } + + override fun onToolbarVisibilityChange(visible: Boolean) { + if (videoPlayer == null || !userVisibleHint) { + return + } + + isDescriptionVisible = showingDescription && visible + val alpha = if (isDescriptionVisible) 1.0f else 0.0f + mediaDescription.animate().alpha(alpha) + .setListener(object : AnimatorListenerAdapter() { + override fun onAnimationEnd(animation: Animator) { + if (isDescriptionVisible) { + mediaDescription.show() + } else { + mediaDescription.hide() + } + animation.removeListener(this) + } + }) + .start() + + if (visible) { + hideToolbarAfterDelay(TOOLBAR_HIDE_DELAY_MS) + } else { + handler.removeCallbacks(hideToolbar) + } + } +} diff --git a/app/src/main/java/com/keylesspalace/tusky/pager/ImagePagerAdapter.java b/app/src/main/java/com/keylesspalace/tusky/pager/ImagePagerAdapter.java deleted file mode 100644 index 5fce0b90..00000000 --- a/app/src/main/java/com/keylesspalace/tusky/pager/ImagePagerAdapter.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.keylesspalace.tusky.pager; - -import android.support.v4.app.Fragment; -import android.support.v4.app.FragmentManager; -import android.support.v4.app.FragmentPagerAdapter; - -import com.keylesspalace.tusky.entity.Attachment; -import com.keylesspalace.tusky.fragment.ViewMediaFragment; - -import java.util.List; -import java.util.Locale; - -public final class ImagePagerAdapter extends FragmentPagerAdapter { - - private List attachments; - private int initialPosition; - - public ImagePagerAdapter(FragmentManager fragmentManager, List attachments, int initialPosition) { - super(fragmentManager); - this.attachments = attachments; - this.initialPosition = initialPosition; - } - - @Override - public Fragment getItem(int position) { - if (position >= 0 && position < attachments.size()) { - return ViewMediaFragment.newInstance(attachments.get(position), position == initialPosition); - } else { - return null; - } - } - - @Override - public int getCount() { - return attachments.size(); - } - - @Override - public CharSequence getPageTitle(int position) { - return String.format(Locale.getDefault(), "%d/%d", position + 1, attachments.size()); - } -} diff --git a/app/src/main/java/com/keylesspalace/tusky/pager/ImagePagerAdapter.kt b/app/src/main/java/com/keylesspalace/tusky/pager/ImagePagerAdapter.kt new file mode 100644 index 00000000..8ebc7a8a --- /dev/null +++ b/app/src/main/java/com/keylesspalace/tusky/pager/ImagePagerAdapter.kt @@ -0,0 +1,33 @@ +package com.keylesspalace.tusky.pager + +import android.support.v4.app.Fragment +import android.support.v4.app.FragmentManager +import android.support.v4.app.FragmentStatePagerAdapter + +import com.keylesspalace.tusky.entity.Attachment +import com.keylesspalace.tusky.fragment.ViewMediaFragment + +import java.util.Locale + +class ImagePagerAdapter( + fragmentManager: FragmentManager, + private val attachments: List, + private val initialPosition: Int +) : FragmentStatePagerAdapter(fragmentManager) { + + override fun getItem(position: Int): Fragment? { + return if (position >= 0 && position < attachments.size) { + ViewMediaFragment.newInstance(attachments[position], position == initialPosition) + } else { + null + } + } + + override fun getCount(): Int { + return attachments.size + } + + override fun getPageTitle(position: Int): CharSequence { + return String.format(Locale.getDefault(), "%d/%d", position + 1, attachments.size) + } +} diff --git a/app/src/main/res/layout/activity_view_video.xml b/app/src/main/res/layout/activity_view_video.xml deleted file mode 100644 index 03602823..00000000 --- a/app/src/main/res/layout/activity_view_video.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_view_media.xml b/app/src/main/res/layout/fragment_view_image.xml similarity index 63% rename from app/src/main/res/layout/fragment_view_media.xml rename to app/src/main/res/layout/fragment_view_image.xml index 39f7b17b..0ff03b2a 100644 --- a/app/src/main/res/layout/fragment_view_media.xml +++ b/app/src/main/res/layout/fragment_view_image.xml @@ -1,6 +1,7 @@ - - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_view_video.xml b/app/src/main/res/layout/fragment_view_video.xml new file mode 100644 index 00000000..c1d0568e --- /dev/null +++ b/app/src/main/res/layout/fragment_view_video.xml @@ -0,0 +1,46 @@ + + + + + + + + + + \ No newline at end of file