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