Refactor media views (#866)
* Migrate ImagePagerAdapter to kotlin * Migrate ViewMediaFragment to kotlin * Make images and videos share the same activity/pager * Show descriptions above videos * Cleanup * Address code review feedback * Migrate media fragments to constraint layout
This commit is contained in:
parent
1556a88d05
commit
952d2a6512
@ -79,16 +79,13 @@
|
|||||||
<data android:mimeType="video/*" />
|
<data android:mimeType="video/*" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
<activity
|
|
||||||
android:name=".ViewVideoActivity"
|
|
||||||
android:theme="@style/TuskyBaseTheme"
|
|
||||||
android:configChanges="orientation|keyboardHidden|screenSize" />
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".ViewThreadActivity"
|
android:name=".ViewThreadActivity"
|
||||||
android:configChanges="orientation|screenSize" />
|
android:configChanges="orientation|screenSize" />
|
||||||
<activity android:name=".ViewTagActivity" />
|
<activity android:name=".ViewTagActivity" />
|
||||||
<activity android:name=".ViewMediaActivity"
|
<activity android:name=".ViewMediaActivity"
|
||||||
android:theme="@style/TuskyBaseTheme" />
|
android:theme="@style/TuskyBaseTheme"
|
||||||
|
android:configChanges="orientation|screenSize|keyboardHidden"/>
|
||||||
<activity android:name=".AccountActivity"
|
<activity android:name=".AccountActivity"
|
||||||
android:configChanges="orientation|screenSize|keyboardHidden"/>
|
android:configChanges="orientation|screenSize|keyboardHidden"/>
|
||||||
<activity android:name=".EditProfileActivity" />
|
<activity android:name=".EditProfileActivity" />
|
||||||
|
@ -15,31 +15,22 @@
|
|||||||
|
|
||||||
package com.keylesspalace.tusky;
|
package com.keylesspalace.tusky;
|
||||||
|
|
||||||
import android.Manifest;
|
|
||||||
import android.app.DownloadManager;
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.content.pm.PackageManager;
|
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
import android.graphics.PorterDuff;
|
import android.graphics.PorterDuff;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Build;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Environment;
|
|
||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import android.support.annotation.StringRes;
|
import android.support.annotation.StringRes;
|
||||||
import android.support.design.widget.Snackbar;
|
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.support.v7.app.AppCompatActivity;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.util.TypedValue;
|
import android.util.TypedValue;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.Toast;
|
|
||||||
|
|
||||||
import com.evernote.android.job.JobManager;
|
import com.evernote.android.job.JobManager;
|
||||||
import com.evernote.android.job.JobRequest;
|
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.di.Injectable;
|
||||||
import com.keylesspalace.tusky.util.ThemeUtils;
|
import com.keylesspalace.tusky.util.ThemeUtils;
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@ -192,28 +182,6 @@ public abstract class BaseActivity extends AppCompatActivity implements Injectab
|
|||||||
.scheduleAsync();
|
.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) {
|
protected void showErrorDialog(View anyView, @StringRes int descriptionId, @StringRes int actionId, View.OnClickListener listener) {
|
||||||
if (anyView != null) {
|
if (anyView != null) {
|
||||||
Snackbar bar = Snackbar.make(anyView, getString(descriptionId), Snackbar.LENGTH_SHORT);
|
Snackbar bar = Snackbar.make(anyView, getString(descriptionId), Snackbar.LENGTH_SHORT);
|
||||||
|
@ -15,8 +15,10 @@
|
|||||||
|
|
||||||
package com.keylesspalace.tusky
|
package com.keylesspalace.tusky
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
import android.animation.Animator
|
import android.animation.Animator
|
||||||
import android.animation.AnimatorListenerAdapter
|
import android.animation.AnimatorListenerAdapter
|
||||||
|
import android.app.DownloadManager
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
@ -24,16 +26,23 @@ import android.graphics.Bitmap
|
|||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import android.os.Build
|
||||||
import android.os.Bundle
|
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.content.FileProvider
|
||||||
import android.support.v4.view.ViewPager
|
import android.support.v4.view.ViewPager
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import android.webkit.MimeTypeMap
|
||||||
|
import android.widget.Toast
|
||||||
import com.keylesspalace.tusky.BuildConfig.APPLICATION_ID
|
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.AvatarImagePagerAdapter
|
||||||
import com.keylesspalace.tusky.pager.ImagePagerAdapter
|
import com.keylesspalace.tusky.pager.ImagePagerAdapter
|
||||||
import com.keylesspalace.tusky.util.CollectionUtil.map
|
import com.keylesspalace.tusky.util.CollectionUtil.map
|
||||||
@ -50,7 +59,7 @@ import java.io.FileOutputStream
|
|||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.util.ArrayList
|
import java.util.ArrayList
|
||||||
|
|
||||||
class ViewMediaActivity : BaseActivity(), ViewMediaFragment.PhotoActionsListener {
|
class ViewMediaActivity : BaseActivity(), ViewImageFragment.PhotoActionsListener {
|
||||||
companion object {
|
companion object {
|
||||||
private const val EXTRA_ATTACHMENTS = "attachments"
|
private const val EXTRA_ATTACHMENTS = "attachments"
|
||||||
private const val EXTRA_ATTACHMENT_INDEX = "index"
|
private const val EXTRA_ATTACHMENT_INDEX = "index"
|
||||||
@ -114,12 +123,10 @@ class ViewMediaActivity : BaseActivity(), ViewMediaFragment.PhotoActionsListener
|
|||||||
|
|
||||||
viewPager.adapter = adapter
|
viewPager.adapter = adapter
|
||||||
viewPager.currentItem = initialPosition
|
viewPager.currentItem = initialPosition
|
||||||
viewPager.addOnPageChangeListener(object: ViewPager.OnPageChangeListener {
|
viewPager.addOnPageChangeListener(object: ViewPager.SimpleOnPageChangeListener() {
|
||||||
override fun onPageSelected(position: Int) {
|
override fun onPageSelected(position: Int) {
|
||||||
toolbar.title = adapter.getPageTitle(position)
|
toolbar.title = adapter.getPageTitle(position)
|
||||||
}
|
}
|
||||||
override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {}
|
|
||||||
override fun onPageScrollStateChanged(state: Int) {}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// Setup the toolbar.
|
// Setup the toolbar.
|
||||||
@ -133,9 +140,9 @@ class ViewMediaActivity : BaseActivity(), ViewMediaFragment.PhotoActionsListener
|
|||||||
toolbar.setNavigationOnClickListener { _ -> supportFinishAfterTransition() }
|
toolbar.setNavigationOnClickListener { _ -> supportFinishAfterTransition() }
|
||||||
toolbar.setOnMenuItemClickListener { item: MenuItem ->
|
toolbar.setOnMenuItemClickListener { item: MenuItem ->
|
||||||
when (item.itemId) {
|
when (item.itemId) {
|
||||||
R.id.action_download -> downloadImage()
|
R.id.action_download -> downloadMedia()
|
||||||
R.id.action_open_status -> onOpenStatus()
|
R.id.action_open_status -> onOpenStatus()
|
||||||
R.id.action_share_media -> shareImage()
|
R.id.action_share_media -> shareMedia()
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
@ -182,16 +189,34 @@ class ViewMediaActivity : BaseActivity(), ViewMediaFragment.PhotoActionsListener
|
|||||||
when (requestCode) {
|
when (requestCode) {
|
||||||
PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE -> {
|
PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE -> {
|
||||||
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||||
downloadImage()
|
downloadMedia()
|
||||||
} else {
|
} 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() {
|
private fun downloadMedia() {
|
||||||
downloadFile(attachments!![viewPager.currentItem].attachment.url)
|
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() {
|
private fun onOpenStatus() {
|
||||||
@ -199,7 +224,7 @@ class ViewMediaActivity : BaseActivity(), ViewMediaFragment.PhotoActionsListener
|
|||||||
startActivityWithSlideInAnimation(ViewThreadActivity.startIntent(this, attach.statusId, attach.statusUrl))
|
startActivityWithSlideInAnimation(ViewThreadActivity.startIntent(this, attach.statusId, attach.statusUrl))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun shareImage() {
|
private fun shareMedia() {
|
||||||
val directory = applicationContext.getExternalFilesDir("Tusky")
|
val directory = applicationContext.getExternalFilesDir("Tusky")
|
||||||
if (directory == null || !(directory.exists())) {
|
if (directory == null || !(directory.exists())) {
|
||||||
Log.e(TAG, "Error obtaining directory to save temporary media.")
|
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 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"))
|
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) {
|
override fun onBitmapLoaded(bitmap: Bitmap, from: Picasso.LoadedFrom) {
|
||||||
try {
|
try {
|
||||||
val stream = FileOutputStream(file)
|
val stream = FileOutputStream(file)
|
||||||
@ -230,10 +272,23 @@ class ViewMediaActivity : BaseActivity(), ViewMediaFragment.PhotoActionsListener
|
|||||||
override fun onPrepareLoad(placeHolderDrawable: Drawable) { }
|
override fun onPrepareLoad(placeHolderDrawable: Drawable) { }
|
||||||
})
|
})
|
||||||
|
|
||||||
val sendIntent = Intent()
|
shareFile(file, "image/png")
|
||||||
sendIntent.action = Intent.ACTION_SEND
|
}
|
||||||
sendIntent.putExtra(Intent.EXTRA_STREAM, FileProvider.getUriForFile(context, "$APPLICATION_ID.fileprovider", file))
|
|
||||||
sendIntent.type = "image/png"
|
private fun shareVideo(directory: File, url: String) {
|
||||||
startActivity(Intent.createChooser(sendIntent, resources.getText(R.string.send_media_to)))
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 <http://www.gnu.org/licenses>. */
|
|
||||||
|
|
||||||
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<out String>, 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)))
|
|
||||||
}
|
|
||||||
}
|
|
@ -83,9 +83,6 @@ abstract class ActivitiesModule {
|
|||||||
@ContributesAndroidInjector
|
@ContributesAndroidInjector
|
||||||
abstract fun contributesViewMediaActivity(): ViewMediaActivity
|
abstract fun contributesViewMediaActivity(): ViewMediaActivity
|
||||||
|
|
||||||
@ContributesAndroidInjector
|
|
||||||
abstract fun contributesViewVideoActivity(): ViewVideoActivity
|
|
||||||
|
|
||||||
@ContributesAndroidInjector
|
@ContributesAndroidInjector
|
||||||
abstract fun contributesLicenseActivity(): LicenseActivity
|
abstract fun contributesLicenseActivity(): LicenseActivity
|
||||||
|
|
||||||
|
@ -30,7 +30,6 @@ import android.view.ViewGroup
|
|||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import com.keylesspalace.tusky.R
|
import com.keylesspalace.tusky.R
|
||||||
import com.keylesspalace.tusky.ViewMediaActivity
|
import com.keylesspalace.tusky.ViewMediaActivity
|
||||||
import com.keylesspalace.tusky.ViewVideoActivity
|
|
||||||
import com.keylesspalace.tusky.di.Injectable
|
import com.keylesspalace.tusky.di.Injectable
|
||||||
import com.keylesspalace.tusky.entity.Attachment
|
import com.keylesspalace.tusky.entity.Attachment
|
||||||
import com.keylesspalace.tusky.entity.Status
|
import com.keylesspalace.tusky.entity.Status
|
||||||
@ -207,7 +206,9 @@ class AccountMediaFragment : BaseFragment(), Injectable {
|
|||||||
val type = items[currentIndex].attachment.type
|
val type = items[currentIndex].attachment.type
|
||||||
|
|
||||||
when (type) {
|
when (type) {
|
||||||
Attachment.Type.IMAGE -> {
|
Attachment.Type.IMAGE,
|
||||||
|
Attachment.Type.GIFV,
|
||||||
|
Attachment.Type.VIDEO -> {
|
||||||
val intent = ViewMediaActivity.newIntent(context, items, currentIndex)
|
val intent = ViewMediaActivity.newIntent(context, items, currentIndex)
|
||||||
if (view != null && activity != null) {
|
if (view != null && activity != null) {
|
||||||
val url = items[currentIndex].attachment.url
|
val url = items[currentIndex].attachment.url
|
||||||
@ -218,13 +219,6 @@ class AccountMediaFragment : BaseFragment(), Injectable {
|
|||||||
startActivity(intent)
|
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 -> {
|
Attachment.Type.UNKNOWN -> {
|
||||||
}/* Intentionally do nothing. This case is here is to handle when new attachment
|
}/* 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
|
* types are added to the API before code is added here to handle them. So, the
|
||||||
|
@ -36,7 +36,6 @@ import com.keylesspalace.tusky.R;
|
|||||||
import com.keylesspalace.tusky.ReportActivity;
|
import com.keylesspalace.tusky.ReportActivity;
|
||||||
import com.keylesspalace.tusky.ViewMediaActivity;
|
import com.keylesspalace.tusky.ViewMediaActivity;
|
||||||
import com.keylesspalace.tusky.ViewTagActivity;
|
import com.keylesspalace.tusky.ViewTagActivity;
|
||||||
import com.keylesspalace.tusky.ViewVideoActivity;
|
|
||||||
import com.keylesspalace.tusky.db.AccountEntity;
|
import com.keylesspalace.tusky.db.AccountEntity;
|
||||||
import com.keylesspalace.tusky.db.AccountManager;
|
import com.keylesspalace.tusky.db.AccountManager;
|
||||||
import com.keylesspalace.tusky.entity.Attachment;
|
import com.keylesspalace.tusky.entity.Attachment;
|
||||||
@ -234,6 +233,8 @@ public abstract class SFragment extends BaseFragment {
|
|||||||
final Attachment active = actionable.getAttachments().get(urlIndex);
|
final Attachment active = actionable.getAttachments().get(urlIndex);
|
||||||
Attachment.Type type = active.getType();
|
Attachment.Type type = active.getType();
|
||||||
switch (type) {
|
switch (type) {
|
||||||
|
case GIFV:
|
||||||
|
case VIDEO:
|
||||||
case IMAGE: {
|
case IMAGE: {
|
||||||
final List<AttachmentViewData> attachments = AttachmentViewData.list(actionable);
|
final List<AttachmentViewData> attachments = AttachmentViewData.list(actionable);
|
||||||
final Intent intent = ViewMediaActivity.newIntent(getContext(), attachments,
|
final Intent intent = ViewMediaActivity.newIntent(getContext(), attachments,
|
||||||
@ -250,15 +251,6 @@ public abstract class SFragment extends BaseFragment {
|
|||||||
}
|
}
|
||||||
break;
|
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: {
|
case UNKNOWN: {
|
||||||
/* Intentionally do nothing. This case is here is to handle when new attachment
|
/* 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
|
* types are added to the API before code is added here to handle them. So, the
|
||||||
|
@ -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 <http://www.gnu.org/licenses>. */
|
||||||
|
|
||||||
|
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<Attachment>(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()
|
||||||
|
}
|
||||||
|
}
|
@ -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 <http://www.gnu.org/licenses>. */
|
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
@ -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 <http://www.gnu.org/licenses>. */
|
||||||
|
|
||||||
|
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<Boolean>? = 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()
|
||||||
|
}
|
||||||
|
}
|
@ -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 <http://www.gnu.org/licenses>. */
|
||||||
|
|
||||||
|
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<Attachment>(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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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<Attachment> attachments;
|
|
||||||
private int initialPosition;
|
|
||||||
|
|
||||||
public ImagePagerAdapter(FragmentManager fragmentManager, List<Attachment> 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());
|
|
||||||
}
|
|
||||||
}
|
|
@ -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<Attachment>,
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
@ -1,28 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:background="@color/view_video_background"
|
|
||||||
android:id="@+id/view_video_container"
|
|
||||||
tools:context=".ViewVideoActivity">
|
|
||||||
<VideoView
|
|
||||||
android:id="@+id/videoPlayer"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_gravity="center" />
|
|
||||||
<ProgressBar
|
|
||||||
android:id="@+id/videoProgressBar"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content" />
|
|
||||||
<android.support.v7.widget.Toolbar
|
|
||||||
android:id="@+id/toolbar"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="?attr/actionBarSize"
|
|
||||||
android:theme="@style/AppTheme.Account.AppBarLayout"
|
|
||||||
app:popupTheme="?attr/toolbar_popup_theme"
|
|
||||||
android:background="@color/toolbar_view_media" />
|
|
||||||
</android.support.design.widget.CoordinatorLayout>
|
|
@ -1,6 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_gravity="center"
|
android:layout_gravity="center"
|
||||||
@ -8,23 +9,27 @@
|
|||||||
android:focusable="true">
|
android:focusable="true">
|
||||||
|
|
||||||
<com.github.chrisbanes.photoview.PhotoView
|
<com.github.chrisbanes.photoview.PhotoView
|
||||||
android:id="@+id/view_media_image"
|
android:id="@+id/photoView"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent" />
|
android:layout_height="match_parent" />
|
||||||
|
|
||||||
<ProgressBar
|
<ProgressBar
|
||||||
android:id="@+id/view_media_progress"
|
android:id="@+id/progressBar"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_constraintLeft_toLeftOf="parent"
|
||||||
|
app:layout_constraintRight_toRightOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
android:layout_centerHorizontal="true"
|
android:layout_centerHorizontal="true"
|
||||||
android:layout_centerVertical="true"
|
android:layout_centerVertical="true"
|
||||||
android:layout_gravity="center" />
|
android:layout_gravity="center" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/tv_media_description"
|
android:id="@+id/descriptionView"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_alignParentBottom="true"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
android:background="#60000000"
|
android:background="#60000000"
|
||||||
android:lineSpacingMultiplier="1.1"
|
android:lineSpacingMultiplier="1.1"
|
||||||
android:padding="8dp"
|
android:padding="8dp"
|
||||||
@ -33,4 +38,4 @@
|
|||||||
android:textSize="?attr/status_text_medium"
|
android:textSize="?attr/status_text_medium"
|
||||||
tools:text="Some media description" />
|
tools:text="Some media description" />
|
||||||
|
|
||||||
</RelativeLayout>
|
</android.support.constraint.ConstraintLayout>
|
46
app/src/main/res/layout/fragment_view_video.xml
Normal file
46
app/src/main/res/layout/fragment_view_video.xml
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/mediaDescription"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/toolbar"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/videoPlayer"
|
||||||
|
android:background="#60000000"
|
||||||
|
android:lineSpacingMultiplier="1.1"
|
||||||
|
android:padding="8dp"
|
||||||
|
android:textAlignment="center"
|
||||||
|
android:textColor="#eee"
|
||||||
|
android:textSize="?attr/status_text_medium"
|
||||||
|
tools:text="Some media description" />
|
||||||
|
|
||||||
|
<VideoView
|
||||||
|
android:id="@+id/videoPlayer"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_constraintLeft_toLeftOf="parent"
|
||||||
|
app:layout_constraintRight_toRightOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/progressBar"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_constraintLeft_toLeftOf="parent"
|
||||||
|
app:layout_constraintRight_toRightOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
android:layout_gravity="center" />
|
||||||
|
|
||||||
|
</android.support.constraint.ConstraintLayout>
|
Loading…
x
Reference in New Issue
Block a user