close #691
trying to improve #692
tweaked media viewer swipe
This commit is contained in:
Mariotaku Lee 2017-02-01 22:00:55 +08:00
parent 857ba24bb0
commit e7e37caff8
No known key found for this signature in database
GPG Key ID: 15C10F89D7C33535
12 changed files with 253 additions and 169 deletions

View File

@ -6,10 +6,10 @@ import org.mariotaku.twidere.model.UserKey;
* Created by mariotaku on 16/1/28. * Created by mariotaku on 16/1/28.
*/ */
public class MediaExtra { public class MediaExtra {
UserKey accountKey; private UserKey accountKey;
boolean useThumbor = true; private boolean useThumbor = true;
String fallbackUrl; private String fallbackUrl;
boolean skipUrlReplacing; private boolean skipUrlReplacing;
public UserKey getAccountKey() { public UserKey getAccountKey() {
return accountKey; return accountKey;

View File

@ -48,37 +48,37 @@ import java.io.InputStream;
*/ */
public class TwidereMediaDownloader implements MediaDownloader, Constants { public class TwidereMediaDownloader implements MediaDownloader, Constants {
private final Context mContext; private final Context context;
private final SharedPreferencesWrapper mPreferences; private final SharedPreferencesWrapper preferences;
private final RestHttpClient mClient; private final RestHttpClient client;
private final String mUserAgent; private final String userAgent;
private Thumbor mThumbor; private Thumbor thumbor;
public TwidereMediaDownloader(final Context context, SharedPreferencesWrapper preferences, public TwidereMediaDownloader(final Context context, SharedPreferencesWrapper preferences,
RestHttpClient client) { RestHttpClient client) {
mContext = context; this.context = context;
mPreferences = preferences; this.preferences = preferences;
mClient = client; this.client = client;
mUserAgent = UserAgentUtils.getDefaultUserAgentStringSafe(context); userAgent = UserAgentUtils.getDefaultUserAgentStringSafe(context);
reloadConnectivitySettings(); reloadConnectivitySettings();
} }
public void reloadConnectivitySettings() { public void reloadConnectivitySettings() {
if (mPreferences.getBoolean(KEY_THUMBOR_ENABLED)) { if (preferences.getBoolean(KEY_THUMBOR_ENABLED)) {
final String address = mPreferences.getString(KEY_THUMBOR_ADDRESS, null); final String address = preferences.getString(KEY_THUMBOR_ADDRESS, null);
final String securityKey = mPreferences.getString(KEY_THUMBOR_SECURITY_KEY, null); final String securityKey = preferences.getString(KEY_THUMBOR_SECURITY_KEY, null);
if (address != null && URLUtil.isValidUrl(address)) { if (address != null && URLUtil.isValidUrl(address)) {
if (TextUtils.isEmpty(securityKey)) { if (TextUtils.isEmpty(securityKey)) {
mThumbor = Thumbor.create(address); thumbor = Thumbor.create(address);
} else { } else {
mThumbor = Thumbor.create(address, securityKey); thumbor = Thumbor.create(address, securityKey);
} }
} else { } else {
mThumbor = null; thumbor = null;
} }
} else { } else {
mThumbor = null; thumbor = null;
} }
} }
@ -91,7 +91,7 @@ public class TwidereMediaDownloader implements MediaDownloader, Constants {
skipUrlReplacing = ((MediaExtra) extra).isSkipUrlReplacing(); skipUrlReplacing = ((MediaExtra) extra).isSkipUrlReplacing();
} }
if (!skipUrlReplacing) { if (!skipUrlReplacing) {
final ParcelableMedia media = PreviewMediaExtractor.fromLink(url, mClient, extra); final ParcelableMedia media = PreviewMediaExtractor.fromLink(url, client, extra);
if (media != null && media.media_url != null) { if (media != null && media.media_url != null) {
return getInternal(media.media_url, extra); return getInternal(media.media_url, extra);
} }
@ -102,7 +102,7 @@ public class TwidereMediaDownloader implements MediaDownloader, Constants {
final String fallbackUrl = ((MediaExtra) extra).getFallbackUrl(); final String fallbackUrl = ((MediaExtra) extra).getFallbackUrl();
if (fallbackUrl != null) { if (fallbackUrl != null) {
final ParcelableMedia media = PreviewMediaExtractor.fromLink(fallbackUrl, final ParcelableMedia media = PreviewMediaExtractor.fromLink(fallbackUrl,
mClient, extra); client, extra);
if (media != null && media.media_url != null) { if (media != null && media.media_url != null) {
return getInternal(media.media_url, extra); return getInternal(media.media_url, extra);
} else { } else {
@ -124,7 +124,7 @@ public class TwidereMediaDownloader implements MediaDownloader, Constants {
useThumbor = ((MediaExtra) extra).isUseThumbor(); useThumbor = ((MediaExtra) extra).isUseThumbor();
UserKey accountKey = ((MediaExtra) extra).getAccountKey(); UserKey accountKey = ((MediaExtra) extra).getAccountKey();
if (accountKey != null) { if (accountKey != null) {
account = AccountUtils.getAccountDetails(AccountManager.get(mContext), accountKey, true); account = AccountUtils.getAccountDetails(AccountManager.get(context), accountKey, true);
if (account != null) { if (account != null) {
auth = CredentialsExtensionsKt.getAuthorization(account.credentials); auth = CredentialsExtensionsKt.getAuthorization(account.credentials);
} }
@ -132,7 +132,7 @@ public class TwidereMediaDownloader implements MediaDownloader, Constants {
} }
final Uri modifiedUri = getReplacedUri(uri, account != null ? account.credentials.api_url_format : null); final Uri modifiedUri = getReplacedUri(uri, account != null ? account.credentials.api_url_format : null);
final MultiValueMap<String> additionalHeaders = new MultiValueMap<>(); final MultiValueMap<String> additionalHeaders = new MultiValueMap<>();
additionalHeaders.add("User-Agent", mUserAgent); additionalHeaders.add("User-Agent", userAgent);
final String method = GET.METHOD; final String method = GET.METHOD;
final String requestUri; final String requestUri;
if (isAuthRequired(uri, account) && auth != null && auth.hasAuthorization()) { if (isAuthRequired(uri, account) && auth != null && auth.hasAuthorization()) {
@ -152,8 +152,8 @@ public class TwidereMediaDownloader implements MediaDownloader, Constants {
queries, null, null, null, null); queries, null, null, null, null);
additionalHeaders.add("Authorization", auth.getHeader(endpoint, info)); additionalHeaders.add("Authorization", auth.getHeader(endpoint, info));
requestUri = modifiedUri.toString(); requestUri = modifiedUri.toString();
} else if (mThumbor != null && useThumbor) { } else if (thumbor != null && useThumbor) {
requestUri = mThumbor.buildImage(modifiedUri.toString()).filter(ThumborUrlBuilder.quality(85)).toUrl(); requestUri = thumbor.buildImage(Uri.encode(modifiedUri.toString())).filter(ThumborUrlBuilder.quality(85)).toUrl();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
additionalHeaders.add("Accept", "image/webp, */*"); additionalHeaders.add("Accept", "image/webp, */*");
} }
@ -165,7 +165,7 @@ public class TwidereMediaDownloader implements MediaDownloader, Constants {
builder.url(requestUri); builder.url(requestUri);
builder.headers(additionalHeaders); builder.headers(additionalHeaders);
builder.tag(NoIntercept.INSTANCE); builder.tag(NoIntercept.INSTANCE);
final HttpResponse resp = mClient.newCall(builder.build()).execute(); final HttpResponse resp = client.newCall(builder.build()).execute();
if (!resp.isSuccessful()) { if (!resp.isSuccessful()) {
final String detailMessage = "Unable to get " + requestUri + ", response code: " final String detailMessage = "Unable to get " + requestUri + ", response code: "
+ resp.getStatus(); + resp.getStatus();

View File

@ -338,6 +338,7 @@ class SignInActivity : BaseActivity(), OnClickListener, TextWatcher, APIEditorDi
setSignInButton() setSignInButton()
if (result.alreadyLoggedIn) { if (result.alreadyLoggedIn) {
result.updateAccount(am) result.updateAccount(am)
deleteAccountData(contentResolver, result.user.key)
Toast.makeText(this, R.string.message_toast_already_logged_in, Toast.LENGTH_SHORT).show() Toast.makeText(this, R.string.message_toast_already_logged_in, Toast.LENGTH_SHORT).show()
} else { } else {
result.addAccount(am, preferences[randomizeAccountNameKey]) result.addAccount(am, preferences[randomizeAccountNameKey])

View File

@ -29,7 +29,7 @@ interface IControlBarActivity {
fun notifyControlBarOffsetChanged() {} fun notifyControlBarOffsetChanged() {}
interface ControlBarOffsetListener { interface ControlBarOffsetListener {
fun onControlBarOffsetChanged(activity: IControlBarActivity, offset: Float) {} fun onControlBarOffsetChanged(activity: IControlBarActivity, offset: Float)
} }
class ControlBarShowHideHelper(private val activity: IControlBarActivity) { class ControlBarShowHideHelper(private val activity: IControlBarActivity) {

View File

@ -16,4 +16,6 @@ fun parcelableMediaTypeString(@ParcelableMedia.Type type: Int): String? {
ParcelableMedia.Type.VARIABLE_TYPE -> "variable" ParcelableMedia.Type.VARIABLE_TYPE -> "variable"
else -> null else -> null
} }
} }
val ParcelableMedia.aspect_ratio: Double get() = this.width / this.height.toDouble()

View File

@ -350,6 +350,8 @@ class AccountsDashboardFragment : BaseFragment(), LoaderCallbacks<AccountsInfo>,
val color = ContextCompat.getColor(context, R.color.material_red) val color = ContextCompat.getColor(context, R.color.material_red)
val size = resources.getDimensionPixelSize(R.dimen.element_spacing_msmall) val size = resources.getDimensionPixelSize(R.dimen.element_spacing_msmall)
menu.setMenuItemIcon(R.id.premium_features, BadgeDrawable(icon, color, size)) menu.setMenuItemIcon(R.id.premium_features, BadgeDrawable(icon, color, size))
} else {
menu.setMenuItemIcon(R.id.premium_features, R.drawable.ic_action_infinity)
} }
var hasLists = false var hasLists = false
var hasGroups = false var hasGroups = false

View File

@ -38,11 +38,11 @@ import org.mariotaku.twidere.extension.model.setPosition
import org.mariotaku.twidere.loader.AccountDetailsLoader import org.mariotaku.twidere.loader.AccountDetailsLoader
import org.mariotaku.twidere.model.AccountDetails import org.mariotaku.twidere.model.AccountDetails
import org.mariotaku.twidere.model.UserKey import org.mariotaku.twidere.model.UserKey
import org.mariotaku.twidere.provider.TwidereDataStore.* import org.mariotaku.twidere.provider.TwidereDataStore.Activities
import org.mariotaku.twidere.provider.TwidereDataStore.DirectMessages.Inbox import org.mariotaku.twidere.provider.TwidereDataStore.Statuses
import org.mariotaku.twidere.provider.TwidereDataStore.DirectMessages.Outbox
import org.mariotaku.twidere.util.DataStoreUtils import org.mariotaku.twidere.util.DataStoreUtils
import org.mariotaku.twidere.util.IntentUtils import org.mariotaku.twidere.util.IntentUtils
import org.mariotaku.twidere.util.deleteAccountData
import org.mariotaku.twidere.util.support.removeAccountSupport import org.mariotaku.twidere.util.support.removeAccountSupport
/** /**
@ -206,6 +206,9 @@ class AccountsManagerFragment : BaseFragment(), LoaderManager.LoaderCallbacks<Li
} }
} }
/**
* DELETE YOUR ACCOUNT
*/
class AccountDeletionDialogFragment : BaseDialogFragment(), DialogInterface.OnClickListener { class AccountDeletionDialogFragment : BaseDialogFragment(), DialogInterface.OnClickListener {
override fun onClick(dialog: DialogInterface, which: Int) { override fun onClick(dialog: DialogInterface, which: Int) {
@ -215,19 +218,13 @@ class AccountsManagerFragment : BaseFragment(), LoaderManager.LoaderCallbacks<Li
when (which) { when (which) {
DialogInterface.BUTTON_POSITIVE -> { DialogInterface.BUTTON_POSITIVE -> {
val accountKey = account.getAccountKey(am) val accountKey = account.getAccountKey(am)
deleteAccountData(resolver, accountKey)
am.removeAccountSupport(account) am.removeAccountSupport(account)
val where = Expression.equalsArgs(AccountSupportColumns.ACCOUNT_KEY).sql
val whereArgs = arrayOf(accountKey.toString())
// Also delete tweets related to the account we previously
// deleted.
resolver.delete(Statuses.CONTENT_URI, where, whereArgs)
resolver.delete(Mentions.CONTENT_URI, where, whereArgs)
resolver.delete(Inbox.CONTENT_URI, where, whereArgs)
resolver.delete(Outbox.CONTENT_URI, where, whereArgs)
} }
} }
} }
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val context = context val context = context
val builder = AlertDialog.Builder(context) val builder = AlertDialog.Builder(context)

View File

@ -14,7 +14,9 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.MediaController import android.widget.MediaController
import android.widget.ProgressBar import android.widget.ProgressBar
import android.widget.SeekBar
import android.widget.TextView import android.widget.TextView
import com.commonsware.cwac.layouts.AspectLockedFrameLayout.AspectRatioSource
import edu.tsinghua.hotmobi.HotMobiLogger import edu.tsinghua.hotmobi.HotMobiLogger
import edu.tsinghua.hotmobi.model.MediaDownloadEvent import edu.tsinghua.hotmobi.model.MediaDownloadEvent
import kotlinx.android.synthetic.main.layout_media_viewer_texture_video_view.* import kotlinx.android.synthetic.main.layout_media_viewer_texture_video_view.*
@ -25,6 +27,7 @@ import org.mariotaku.twidere.R
import org.mariotaku.twidere.TwidereConstants.EXTRA_ACCOUNT_KEY import org.mariotaku.twidere.TwidereConstants.EXTRA_ACCOUNT_KEY
import org.mariotaku.twidere.TwidereConstants.EXTRA_MEDIA import org.mariotaku.twidere.TwidereConstants.EXTRA_MEDIA
import org.mariotaku.twidere.activity.MediaViewerActivity import org.mariotaku.twidere.activity.MediaViewerActivity
import org.mariotaku.twidere.activity.iface.IControlBarActivity
import org.mariotaku.twidere.model.ParcelableMedia import org.mariotaku.twidere.model.ParcelableMedia
import org.mariotaku.twidere.model.UserKey import org.mariotaku.twidere.model.UserKey
import org.mariotaku.twidere.util.media.MediaExtra import org.mariotaku.twidere.util.media.MediaExtra
@ -32,14 +35,105 @@ import java.util.*
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
class VideoPageFragment : CacheDownloadMediaViewerFragment(), MediaPlayer.OnPreparedListener, class VideoPageFragment : CacheDownloadMediaViewerFragment(), MediaPlayer.OnPreparedListener,
MediaPlayer.OnErrorListener, MediaPlayer.OnCompletionListener, View.OnClickListener { MediaPlayer.OnErrorListener, MediaPlayer.OnCompletionListener, View.OnClickListener, IControlBarActivity.ControlBarOffsetListener {
private var mPlayAudio: Boolean = false private var playAudio: Boolean = false
private var mVideoProgressRunnable: VideoPlayProgressRunnable? = null
private var mediaPlayer: MediaPlayer? = null private var mediaPlayer: MediaPlayer? = null
private var mMediaPlayerError: Int = 0 private var mediaPlayerError: Int = 0
private var videoProgressRunnable: VideoPlayProgressRunnable? = null
private var mediaDownloadEvent: MediaDownloadEvent? = null private var mediaDownloadEvent: MediaDownloadEvent? = null
private val isLoopEnabled: Boolean get() = arguments.getBoolean(EXTRA_LOOP, false)
private val media: ParcelableMedia? get() = arguments.getParcelable<ParcelableMedia>(EXTRA_MEDIA)
private val accountKey: UserKey get() = arguments.getParcelable<UserKey>(EXTRA_ACCOUNT_KEY)
private var aspectRatioSource = object : AspectRatioSource {
override fun getHeight(): Int {
val height = media?.height ?: 0
if (height <= 0) return view!!.measuredHeight
return height
}
override fun getWidth(): Int {
val width = media?.width ?: 0
if (width <= 0) return view!!.measuredWidth
return width
}
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
setHasOptionsMenu(true)
var handler: Handler? = videoViewProgress.handler
if (handler == null) {
handler = Handler(activity.mainLooper)
}
val am = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
// Play audio by default if ringer mode on
playAudio = am.ringerMode == AudioManager.RINGER_MODE_NORMAL
videoProgressRunnable = VideoPlayProgressRunnable(handler, videoViewProgress,
durationLabel, positionLabel, videoView)
videoViewOverlay.setOnClickListener(this)
videoView.setOnPreparedListener(this)
videoView.setOnErrorListener(this)
videoView.setOnCompletionListener(this)
playPauseButton.setOnClickListener(this)
volumeButton.setOnClickListener(this)
videoControl.visibility = View.GONE
videoContainer.setAspectRatioSource(aspectRatioSource)
videoViewProgress.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
private var paused: Boolean = false
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
if (!fromUser) return
val mp = mediaPlayer ?: return
val duration = mp.duration
if (duration <= 0) return
mp.seekTo(Math.round(duration * (progress.toFloat() / seekBar.max)))
}
override fun onStartTrackingTouch(seekBar: SeekBar) {
paused = pauseVideo()
}
override fun onStopTrackingTouch(seekBar: SeekBar) {
if (paused) {
resumeVideo()
}
}
})
startLoading(false)
setMediaViewVisible(false)
updateVolume()
}
override fun onAttach(context: Context?) {
super.onAttach(context)
if (context is IControlBarActivity) {
context.registerControlBarOffsetListener(this)
}
}
override fun onDetach() {
val activity = activity
if (activity is IControlBarActivity) {
activity.unregisterControlBarOffsetListener(this)
}
super.onDetach()
}
override fun getDownloadExtra(): Any? { override fun getDownloadExtra(): Any? {
val extra = MediaExtra() val extra = MediaExtra()
extra.isUseThumbor = false extra.isUseThumbor = false
@ -50,29 +144,24 @@ class VideoPageFragment : CacheDownloadMediaViewerFragment(), MediaPlayer.OnPrep
return extra return extra
} }
val isLoopEnabled: Boolean
get() = arguments.getBoolean(EXTRA_LOOP, false)
override fun isAbleToLoad(): Boolean { override fun isAbleToLoad(): Boolean {
return downloadUri != null return downloadUri != null
} }
override fun getDownloadUri(): Uri? { override fun getDownloadUri(): Uri? {
val bestVideoUrlAndType = getBestVideoUrlAndType(media, val bestVideoUrlAndType = getBestVideoUrlAndType(media, SUPPORTED_VIDEO_TYPES)
SUPPORTED_VIDEO_TYPES)
if (bestVideoUrlAndType != null && bestVideoUrlAndType.first != null) { if (bestVideoUrlAndType != null && bestVideoUrlAndType.first != null) {
return Uri.parse(bestVideoUrlAndType.first) return Uri.parse(bestVideoUrlAndType.first)
} }
return arguments.getParcelable<Uri>(SubsampleImageViewerFragment.EXTRA_MEDIA_URI) return arguments.getParcelable<Uri>(SubsampleImageViewerFragment.EXTRA_MEDIA_URI)
} }
override fun displayMedia(result: CacheDownloadLoader.Result) { override fun displayMedia(result: CacheDownloadLoader.Result) {
videoView.setVideoURI(result.cacheUri) videoView.setVideoURI(result.cacheUri)
videoControl.visibility = View.GONE videoControl.visibility = View.GONE
setMediaViewVisible(true) setMediaViewVisible(true)
val activity = activity activity.supportInvalidateOptionsMenu()
activity?.supportInvalidateOptionsMenu()
} }
override fun recycleMedia() { override fun recycleMedia() {
@ -81,29 +170,31 @@ class VideoPageFragment : CacheDownloadMediaViewerFragment(), MediaPlayer.OnPrep
override fun onCompletion(mp: MediaPlayer) { override fun onCompletion(mp: MediaPlayer) {
updatePlayerState() updatePlayerState()
// mVideoViewProgress.removeCallbacks(mVideoProgressRunnable); }
// mVideoViewProgress.setVisibility(View.GONE);
override fun onControlBarOffsetChanged(activity: IControlBarActivity, offset: Float) {
videoControl.translationY = (1 - offset) * videoControl.height
} }
override fun onError(mp: MediaPlayer, what: Int, extra: Int): Boolean { override fun onError(mp: MediaPlayer, what: Int, extra: Int): Boolean {
mediaPlayer = null mediaPlayer = null
videoViewProgress.removeCallbacks(mVideoProgressRunnable) videoViewProgress.removeCallbacks(videoProgressRunnable)
videoViewProgress.visibility = View.GONE videoViewProgress.visibility = View.GONE
videoControl.visibility = View.GONE videoControl.visibility = View.GONE
mMediaPlayerError = what mediaPlayerError = what
return true return true
} }
override fun onPrepared(mp: MediaPlayer) { override fun onPrepared(mp: MediaPlayer) {
if (userVisibleHint) { if (userVisibleHint) {
mediaPlayer = mp mediaPlayer = mp
mMediaPlayerError = 0 mediaPlayerError = 0
mp.setScreenOnWhilePlaying(true) mp.setScreenOnWhilePlaying(true)
updateVolume() updateVolume()
mp.isLooping = isLoopEnabled mp.isLooping = isLoopEnabled
mp.start() mp.start()
videoViewProgress.visibility = View.VISIBLE videoViewProgress.visibility = View.VISIBLE
videoViewProgress.post(mVideoProgressRunnable) videoViewProgress.post(videoProgressRunnable)
updatePlayerState() updatePlayerState()
videoControl.visibility = View.VISIBLE videoControl.visibility = View.VISIBLE
} }
@ -111,10 +202,10 @@ class VideoPageFragment : CacheDownloadMediaViewerFragment(), MediaPlayer.OnPrep
private fun updateVolume() { private fun updateVolume() {
volumeButton.setImageResource(if (mPlayAudio) R.drawable.ic_action_speaker_max else R.drawable.ic_action_speaker_muted) volumeButton.setImageResource(if (playAudio) R.drawable.ic_action_speaker_max else R.drawable.ic_action_speaker_muted)
val mp = mediaPlayer ?: return val mp = mediaPlayer ?: return
try { try {
if (mPlayAudio) { if (playAudio) {
mp.setVolume(1f, 1f) mp.setVolume(1f, 1f)
} else { } else {
mp.setVolume(0f, 0f) mp.setVolume(0f, 0f)
@ -136,103 +227,28 @@ class VideoPageFragment : CacheDownloadMediaViewerFragment(), MediaPlayer.OnPrep
} }
} }
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
setHasOptionsMenu(true)
var handler: Handler? = videoViewProgress.handler
if (handler == null) {
handler = Handler(activity.mainLooper)
}
val am = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
// Play audio by default if ringer mode on
mPlayAudio = am.ringerMode == AudioManager.RINGER_MODE_NORMAL
mVideoProgressRunnable = VideoPlayProgressRunnable(handler, videoViewProgress,
durationLabel, positionLabel, videoView)
videoViewOverlay.setOnClickListener(this)
videoView.setOnPreparedListener(this)
videoView.setOnErrorListener(this)
videoView.setOnCompletionListener(this)
playPauseButton.setOnClickListener(this)
volumeButton.setOnClickListener(this)
videoControl.visibility = View.GONE
startLoading(false)
setMediaViewVisible(false)
updateVolume()
}
@SuppressLint("SwitchIntDef")
private fun getBestVideoUrlAndType(media: ParcelableMedia?,
supportedTypes: Array<String>): Pair<String, String>? {
if (media == null) return null
when (media.type) {
ParcelableMedia.Type.VIDEO, ParcelableMedia.Type.ANIMATED_GIF -> {
if (media.video_info == null) {
return Pair.create<String, String>(media.media_url, null)
}
val firstMatch = media.video_info.variants.first { variant ->
supportedTypes.any { it.equals(variant.content_type, ignoreCase = true) }
} ?: return null
return Pair.create(firstMatch.url, firstMatch.content_type)
}
ParcelableMedia.Type.CARD_ANIMATED_GIF -> {
return Pair.create<String, String>(media.media_url, "video/mp4")
}
else -> {
return null
}
}
}
override fun onClick(v: View) { override fun onClick(v: View) {
when (v.id) { when (v.id) {
R.id.volumeButton -> { R.id.volumeButton -> {
mPlayAudio = !mPlayAudio playAudio = !playAudio
updateVolume() updateVolume()
} }
R.id.playPauseButton -> { R.id.playPauseButton -> {
val mp = mediaPlayer val mp = mediaPlayer ?: return
if (mp != null) { if (mp.isPlaying) {
if (mp.isPlaying) { mp.pause()
mp.pause() } else {
} else { mp.start()
mp.start()
}
} }
updatePlayerState() updatePlayerState()
} }
R.id.videoViewOverlay -> { R.id.videoViewOverlay -> {
val activity = activity as MediaViewerActivity val activity = activity as MediaViewerActivity
if (videoControl.visibility == View.VISIBLE) { activity.setBarVisibility(!activity.isBarShowing)
videoControl.visibility = View.GONE
activity.setBarVisibility(false)
} else {
videoControl.visibility = View.VISIBLE
activity.setBarVisibility(true)
}
} }
} }
} }
private fun updatePlayerState() {
val mp = mediaPlayer
if (mp != null) {
val playing = mp.isPlaying
playPauseButton.contentDescription = getString(if (playing) R.string.pause else R.string.play)
playPauseButton.setImageResource(if (playing) R.drawable.ic_action_pause else R.drawable.ic_action_play_arrow)
} else {
playPauseButton.contentDescription = getString(R.string.play)
playPauseButton.setImageResource(R.drawable.ic_action_play_arrow)
}
}
override fun onCreateMediaView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { override fun onCreateMediaView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
return inflater.inflate(R.layout.layout_media_viewer_texture_video_view, container, false) return inflater.inflate(R.layout.layout_media_viewer_texture_video_view, container, false)
} }
@ -267,11 +283,62 @@ class VideoPageFragment : CacheDownloadMediaViewerFragment(), MediaPlayer.OnPrep
} }
} }
private val media: ParcelableMedia?
get() = arguments.getParcelable<ParcelableMedia>(EXTRA_MEDIA)
private val accountKey: UserKey @SuppressLint("SwitchIntDef")
get() = arguments.getParcelable<UserKey>(EXTRA_ACCOUNT_KEY) private fun getBestVideoUrlAndType(media: ParcelableMedia?, supportedTypes: Array<String>): Pair<String, String>? {
if (media == null) return null
when (media.type) {
ParcelableMedia.Type.VIDEO, ParcelableMedia.Type.ANIMATED_GIF -> {
if (media.video_info == null) {
return Pair.create<String, String>(media.media_url, null)
}
val firstMatch = media.video_info.variants.first { variant ->
supportedTypes.any { it.equals(variant.content_type, ignoreCase = true) }
} ?: return null
return Pair.create(firstMatch.url, firstMatch.content_type)
}
ParcelableMedia.Type.CARD_ANIMATED_GIF -> {
return Pair.create<String, String>(media.media_url, "video/mp4")
}
else -> {
return null
}
}
}
private fun updatePlayerState() {
val mp = mediaPlayer
if (mp != null) {
val playing = mp.isPlaying
playPauseButton.contentDescription = getString(if (playing) R.string.pause else R.string.play)
playPauseButton.setImageResource(if (playing) R.drawable.ic_action_pause else R.drawable.ic_action_play_arrow)
} else {
playPauseButton.contentDescription = getString(R.string.play)
playPauseButton.setImageResource(R.drawable.ic_action_play_arrow)
}
}
private fun pauseVideo(): Boolean {
val mp = mediaPlayer ?: return false
var result = false
if (mp.isPlaying) {
mp.pause()
result = true
}
updatePlayerState()
return result
}
private fun resumeVideo(): Boolean {
val mp = mediaPlayer ?: return false
var result = false
if (!mp.isPlaying) {
mp.start()
result = true
}
updatePlayerState()
return result
}
private class VideoPlayProgressRunnable internal constructor( private class VideoPlayProgressRunnable internal constructor(
private val handler: Handler, private val handler: Handler,
@ -302,7 +369,7 @@ class VideoPageFragment : CacheDownloadMediaViewerFragment(), MediaPlayer.OnPrep
const val EXTRA_LOOP = "loop" const val EXTRA_LOOP = "loop"
private val SUPPORTED_VIDEO_TYPES: Array<String> private val SUPPORTED_VIDEO_TYPES: Array<String>
private val FALLBACK_VIDEO_TYPES: Array<String> private val FALLBACK_VIDEO_TYPES: Array<String> = arrayOf("video/mp4")
init { init {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
@ -310,7 +377,6 @@ class VideoPageFragment : CacheDownloadMediaViewerFragment(), MediaPlayer.OnPrep
} else { } else {
SUPPORTED_VIDEO_TYPES = arrayOf("video/webm", "video/mp4") SUPPORTED_VIDEO_TYPES = arrayOf("video/webm", "video/mp4")
} }
FALLBACK_VIDEO_TYPES = arrayOf("video/mp4")
} }
} }
} }

View File

@ -1,6 +1,7 @@
package org.mariotaku.twidere.util package org.mariotaku.twidere.util
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.ContentResolver
import android.content.Context import android.content.Context
import android.content.SharedPreferences import android.content.SharedPreferences
import android.net.Uri import android.net.Uri
@ -11,6 +12,7 @@ import org.mariotaku.twidere.constant.filterPossibilitySensitiveStatusesKey
import org.mariotaku.twidere.constant.filterUnavailableQuoteStatusesKey import org.mariotaku.twidere.constant.filterUnavailableQuoteStatusesKey
import org.mariotaku.twidere.model.DraftCursorIndices import org.mariotaku.twidere.model.DraftCursorIndices
import org.mariotaku.twidere.model.ParcelableStatus.FilterFlags import org.mariotaku.twidere.model.ParcelableStatus.FilterFlags
import org.mariotaku.twidere.model.UserKey
import org.mariotaku.twidere.provider.TwidereDataStore.* import org.mariotaku.twidere.provider.TwidereDataStore.*
/** /**
@ -100,3 +102,14 @@ fun deleteDrafts(context: Context, draftIds: LongArray): Int {
} }
return context.contentResolver.delete(Drafts.CONTENT_URI, where, whereArgs) return context.contentResolver.delete(Drafts.CONTENT_URI, where, whereArgs)
} }
fun deleteAccountData(resolver: ContentResolver, accountKey: UserKey) {
val where = Expression.equalsArgs(AccountSupportColumns.ACCOUNT_KEY).sql
val whereArgs = arrayOf(accountKey.toString())
// Also delete tweets related to the account we previously
// deleted.
resolver.delete(Statuses.CONTENT_URI, where, whereArgs)
resolver.delete(Mentions.CONTENT_URI, where, whereArgs)
resolver.delete(DirectMessages.Inbox.CONTENT_URI, where, whereArgs)
resolver.delete(DirectMessages.Outbox.CONTENT_URI, where, whereArgs)
}

View File

@ -69,7 +69,7 @@ class ReadStateManager(context: Context) {
preferences.unregisterOnSharedPreferenceChangeListener(listener) preferences.unregisterOnSharedPreferenceChangeListener(listener)
} }
@JvmOverloads fun setPosition(key: String, keyId: String, position: Long, acceptOlder: Boolean = false): Boolean { fun setPosition(key: String, keyId: String, position: Long, acceptOlder: Boolean = false): Boolean {
if (TextUtils.isEmpty(key)) return false if (TextUtils.isEmpty(key)) return false
val set: MutableSet<String> = preferences.getStringSet(key, null) ?: CompactHashSet<String>() val set: MutableSet<String> = preferences.getStringSet(key, null) ?: CompactHashSet<String>()
val prefix = keyId + ":" val prefix = keyId + ":"

View File

@ -46,25 +46,22 @@ class MediaSwipeCloseContainer(context: Context, attrs: AttributeSet? = null) :
val container = this@MediaSwipeCloseContainer val container = this@MediaSwipeCloseContainer
val minVel = ViewConfiguration.get(context).scaledMinimumFlingVelocity val minVel = ViewConfiguration.get(context).scaledMinimumFlingVelocity
when { when {
yvel > minVel -> { yvel > minVel && childTop > 0 -> {
// Settle downward // Settle downward
container.dragHelper.settleCapturedViewAt(0, container.height) container.dragHelper.settleCapturedViewAt(0, container.height)
} }
yvel < -minVel -> { yvel < -minVel && childTop < 0 -> {
// Settle upward // Settle upward
container.dragHelper.settleCapturedViewAt(0, -container.height) container.dragHelper.settleCapturedViewAt(0, -container.height)
} }
else -> when { yvel <= 0 && childTop < -container.height / 4 -> {
childTop < -container.height / 4 -> { container.dragHelper.smoothSlideViewTo(releasedChild, 0, -container.height)
container.dragHelper.smoothSlideViewTo(releasedChild, 0, -container.height) }
} yvel >= 0 && childTop > container.height / 4 -> {
childTop > container.height / 4 -> { container.dragHelper.smoothSlideViewTo(releasedChild, 0, container.height)
container.dragHelper.smoothSlideViewTo(releasedChild, 0, container.height) }
} else -> {
else -> { container.dragHelper.smoothSlideViewTo(releasedChild, 0, 0)
container.dragHelper.smoothSlideViewTo(releasedChild, 0, 0)
}
} }
} }
ViewCompat.postInvalidateOnAnimation(container) ViewCompat.postInvalidateOnAnimation(container)

View File

@ -26,11 +26,18 @@
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_gravity="center"> android:layout_gravity="center">
<com.sprylab.android.widget.TextureVideoView <com.commonsware.cwac.layouts.AspectLockedFrameLayout
android:id="@+id/videoView" android:id="@+id/videoContainer"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="match_parent"
android:layout_centerInParent="true"/> android:layout_centerInParent="true">
<com.sprylab.android.widget.TextureVideoView
android:id="@+id/videoView"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</com.commonsware.cwac.layouts.AspectLockedFrameLayout>
<FrameLayout <FrameLayout
android:id="@+id/videoViewOverlay" android:id="@+id/videoViewOverlay"
@ -72,7 +79,7 @@
android:textSize="@dimen/text_size_extra_small" android:textSize="@dimen/text_size_extra_small"
tools:text="--:--"/> tools:text="--:--"/>
<ProgressBar <SeekBar
android:id="@+id/videoViewProgress" android:id="@+id/videoViewProgress"
style="?android:progressBarStyleHorizontal" style="?android:progressBarStyleHorizontal"
android:layout_width="0dp" android:layout_width="0dp"
@ -81,8 +88,7 @@
android:layout_toEndOf="@+id/playPauseButton" android:layout_toEndOf="@+id/playPauseButton"
android:layout_toLeftOf="@+id/volumeButton" android:layout_toLeftOf="@+id/volumeButton"
android:layout_toRightOf="@+id/playPauseButton" android:layout_toRightOf="@+id/playPauseButton"
android:layout_toStartOf="@+id/volumeButton" android:layout_toStartOf="@+id/volumeButton"/>
android:indeterminate="false"/>
<TextView <TextView
android:id="@+id/durationLabel" android:id="@+id/durationLabel"