implemented swipe down close media, close #684

This commit is contained in:
Mariotaku Lee 2017-01-29 19:38:12 +08:00
parent 9e73c8b31c
commit 37631054ab
No known key found for this signature in database
GPG Key ID: 15C10F89D7C33535
19 changed files with 464 additions and 294 deletions

View File

@ -0,0 +1,13 @@
package org.mariotaku.microblog.library.twitter.api;
import org.mariotaku.microblog.library.twitter.template.StatusAnnotationTemplate;
import org.mariotaku.restfu.annotation.param.Queries;
/**
* Created by mariotaku on 2017/1/29.
*/
@SuppressWarnings("RedundantThrows")
@Queries(template = StatusAnnotationTemplate.class)
public interface CollectionResources {
}

View File

@ -157,7 +157,7 @@ dependencies {
compile 'com.bluelinelabs:logansquare:1.3.7'
compile 'com.soundcloud.android:android-crop:1.0.1@aar'
compile 'com.hannesdorfmann.parcelableplease:annotation:1.0.2'
compile 'com.github.mariotaku:PickNCrop:0.9.15'
compile 'com.github.mariotaku:PickNCrop:0.9.16'
compile "com.github.mariotaku.RestFu:library:$mariotaku_restfu_version"
compile "com.github.mariotaku.RestFu:okhttp3:$mariotaku_restfu_version"
compile 'com.squareup.okhttp3:okhttp:3.5.0'
@ -177,7 +177,7 @@ dependencies {
compile "com.github.mariotaku.CommonsLibrary:text:$mariotaku_commons_library_version"
compile "com.github.mariotaku.CommonsLibrary:text-kotlin:$mariotaku_commons_library_version"
compile 'com.github.mariotaku:KPreferences:0.9.5'
compile 'com.github.mariotaku:Chameleon:0.9.11'
compile 'com.github.mariotaku:Chameleon:0.9.12'
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
compile 'nl.komponents.kovenant:kovenant:3.3.0'
compile 'nl.komponents.kovenant:kovenant-android:3.3.0'

View File

@ -130,10 +130,14 @@ class FabricAnalyzer : Analyzer(), Constants {
Crashlytics.setString("build.model", Build.MODEL)
Crashlytics.setString("build.product", Build.PRODUCT)
val am = AccountManager.get(application)
am.addOnAccountsUpdatedListenerSafe(OnAccountsUpdateListener { accounts ->
Crashlytics.setString("twidere.accounts", accounts.filter { it.type == ACCOUNT_TYPE }
.joinToString(transform = Account::name))
}, updateImmediately = true)
try {
am.addOnAccountsUpdatedListenerSafe(OnAccountsUpdateListener { accounts ->
Crashlytics.setString("twidere.accounts", accounts.filter { it.type == ACCOUNT_TYPE }
.joinToString(transform = Account::name))
}, updateImmediately = true)
} catch (e: SecurityException) {
// Permission managers (like some Xposed plugins) may block Twidere from getting accounts
}
}
private fun AnswersEvent<*>.putAttributes(event: Analyzer.Event) {

View File

@ -1,124 +0,0 @@
package org.mariotaku.twidere.activity.iface;
import android.animation.Animator;
import android.animation.Animator.AnimatorListener;
import android.animation.ObjectAnimator;
import android.util.Property;
import android.view.animation.DecelerateInterpolator;
/**
* Created by mariotaku on 14/10/21.
*/
public interface IControlBarActivity {
/**
* @param offset 0: invisible, 1: visible
*/
void setControlBarOffset(float offset);
void setControlBarVisibleAnimate(boolean visible);
void setControlBarVisibleAnimate(boolean visible, ControlBarShowHideHelper.ControlBarAnimationListener listener);
float getControlBarOffset();
int getControlBarHeight();
void notifyControlBarOffsetChanged();
void registerControlBarOffsetListener(ControlBarOffsetListener listener);
void unregisterControlBarOffsetListener(ControlBarOffsetListener listener);
interface ControlBarOffsetListener {
void onControlBarOffsetChanged(IControlBarActivity activity, float offset);
}
final class ControlBarShowHideHelper {
private static final long DURATION = 200L;
private final IControlBarActivity activity;
private int controlAnimationDirection;
private ObjectAnimator currentControlAnimation;
public ControlBarShowHideHelper(IControlBarActivity activity) {
this.activity = activity;
}
private static class ControlBarOffsetProperty extends Property<IControlBarActivity, Float> {
public static final ControlBarOffsetProperty SINGLETON = new ControlBarOffsetProperty();
@Override
public void set(IControlBarActivity object, Float value) {
object.setControlBarOffset(value);
}
public ControlBarOffsetProperty() {
super(Float.TYPE, null);
}
@Override
public Float get(IControlBarActivity object) {
return object.getControlBarOffset();
}
}
public interface ControlBarAnimationListener {
void onControlBarVisibleAnimationFinish(boolean visible);
}
public void setControlBarVisibleAnimate(boolean visible) {
setControlBarVisibleAnimate(visible, null);
}
public void setControlBarVisibleAnimate(final boolean visible, final ControlBarAnimationListener listener) {
final int newDirection = visible ? 1 : -1;
if (controlAnimationDirection == newDirection) return;
if (currentControlAnimation != null && controlAnimationDirection != 0) {
currentControlAnimation.cancel();
currentControlAnimation = null;
controlAnimationDirection = newDirection;
}
final ObjectAnimator animator;
final float offset = activity.getControlBarOffset();
if (visible) {
if (offset >= 1) return;
animator = ObjectAnimator.ofFloat(activity, ControlBarOffsetProperty.SINGLETON, offset, 1);
} else {
if (offset <= 0) return;
animator = ObjectAnimator.ofFloat(activity, ControlBarOffsetProperty.SINGLETON, offset, 0);
}
animator.setInterpolator(new DecelerateInterpolator());
animator.addListener(new AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
}
@Override
public void onAnimationEnd(Animator animation) {
controlAnimationDirection = 0;
currentControlAnimation = null;
if (listener != null) {
listener.onControlBarVisibleAnimationFinish(visible);
}
}
@Override
public void onAnimationCancel(Animator animation) {
controlAnimationDirection = 0;
currentControlAnimation = null;
}
@Override
public void onAnimationRepeat(Animator animation) {
}
});
animator.setDuration(DURATION);
animator.start();
currentControlAnimation = animator;
controlAnimationDirection = newDirection;
}
}
}

View File

@ -0,0 +1,5 @@
package android.support.v7.app
import android.support.v7.widget.ActionBarContainer
val WindowDecorActionBar.containerView: ActionBarContainer get() = mContainerView

View File

@ -240,26 +240,6 @@ open class BaseActivity : ChameleonActivity(), IExtendedActivity<BaseActivity>,
super.onPause()
}
override fun setControlBarOffset(offset: Float) {
}
override fun setControlBarVisibleAnimate(visible: Boolean) {
}
override fun setControlBarVisibleAnimate(visible: Boolean, listener: IControlBarActivity.ControlBarShowHideHelper.ControlBarAnimationListener) {
}
override fun getControlBarOffset(): Float {
return 0f
}
override fun getControlBarHeight(): Int {
return 0
}
override fun notifyControlBarOffsetChanged() {
val offset = controlBarOffset
for (l in controlBarOffsetListeners) {

View File

@ -139,6 +139,11 @@ class HomeActivity : BaseActivity(), OnClickListener, OnPageChangeListener, Supp
}
}
override val controlBarHeight: Int
get() {
return mainTabs.height - mainTabs.stripHeight
}
fun closeAccountsDrawer() {
if (homeMenu == null) return
homeMenu.closeDrawers()
@ -175,11 +180,7 @@ class HomeActivity : BaseActivity(), OnClickListener, OnPageChangeListener, Supp
return true
}
override fun setControlBarVisibleAnimate(visible: Boolean) {
controlBarShowHideHelper.setControlBarVisibleAnimate(visible)
}
override fun setControlBarVisibleAnimate(visible: Boolean, listener: IControlBarActivity.ControlBarShowHideHelper.ControlBarAnimationListener) {
override fun setControlBarVisibleAnimate(visible: Boolean, listener: IControlBarActivity.ControlBarShowHideHelper.ControlBarAnimationListener?) {
controlBarShowHideHelper.setControlBarVisibleAnimate(visible, listener)
}
@ -555,33 +556,33 @@ class HomeActivity : BaseActivity(), OnClickListener, OnPageChangeListener, Supp
drawerToggle.onConfigurationChanged(newConfig)
}
override fun getControlBarOffset(): Float {
if (mainTabs.columns > 1) {
val lp = actionsButton.layoutParams
val total: Float
if (lp is MarginLayoutParams) {
total = (lp.bottomMargin + actionsButton.height).toFloat()
} else {
total = actionsButton.height.toFloat()
override var controlBarOffset: Float
get() {
if (mainTabs.columns > 1) {
val lp = actionsButton.layoutParams
val total: Float
if (lp is MarginLayoutParams) {
total = (lp.bottomMargin + actionsButton.height).toFloat()
} else {
total = actionsButton.height.toFloat()
}
return 1 - actionsButton.translationY / total
}
return 1 - actionsButton.translationY / total
val totalHeight = controlBarHeight.toFloat()
return 1 + toolbar.translationY / totalHeight
}
val totalHeight = controlBarHeight.toFloat()
return 1 + toolbar.translationY / totalHeight
}
override fun setControlBarOffset(offset: Float) {
val translationY = if (mainTabs.columns > 1) 0 else (controlBarHeight * (offset - 1)).toInt()
toolbar.translationY = translationY.toFloat()
windowOverlay.translationY = translationY.toFloat()
val lp = actionsButton.layoutParams
if (lp is MarginLayoutParams) {
actionsButton.translationY = (lp.bottomMargin + actionsButton.height) * (1 - offset)
} else {
actionsButton.translationY = actionsButton.height * (1 - offset)
set(offset) {
val translationY = if (mainTabs.columns > 1) 0 else (controlBarHeight * (offset - 1)).toInt()
toolbar.translationY = translationY.toFloat()
windowOverlay.translationY = translationY.toFloat()
val lp = actionsButton.layoutParams
if (lp is MarginLayoutParams) {
actionsButton.translationY = (lp.bottomMargin + actionsButton.height) * (1 - offset)
} else {
actionsButton.translationY = actionsButton.height * (1 - offset)
}
notifyControlBarOffsetChanged()
}
notifyControlBarOffsetChanged()
}
override fun onDrawerSlide(drawerView: View, slideOffset: Float) {
@ -606,10 +607,6 @@ class HomeActivity : BaseActivity(), OnClickListener, OnPageChangeListener, Supp
return homeDrawerToggleDelegate
}
override fun getControlBarHeight(): Int {
return mainTabs.height - mainTabs.stripHeight
}
private val keyboardShortcutRecipient: Fragment?
get() {
if (homeMenu.isDrawerOpen(GravityCompat.START)) {

View File

@ -183,6 +183,7 @@ class LinkHandlerActivity : BaseActivity(), SystemWindowsInsetsCallback, IContro
} else {
NavUtils.navigateUpFromSameTask(this)
}
return true
}
}
return super.onOptionsItemSelected(item)
@ -239,58 +240,53 @@ class LinkHandlerActivity : BaseActivity(), SystemWindowsInsetsCallback, IContro
setupActionBarOption()
}
override fun setControlBarVisibleAnimate(visible: Boolean) {
// Currently only search page needs this pattern, so we only enable this feature for it.
if (currentVisibleFragment !is HideUiOnScroll) return
controlBarShowHideHelper.setControlBarVisibleAnimate(visible)
}
override fun setControlBarVisibleAnimate(visible: Boolean, listener: ControlBarShowHideHelper.ControlBarAnimationListener) {
override fun setControlBarVisibleAnimate(visible: Boolean, listener: ControlBarShowHideHelper.ControlBarAnimationListener?) {
// Currently only search page needs this pattern, so we only enable this feature for it.
if (currentVisibleFragment !is HideUiOnScroll) return
controlBarShowHideHelper.setControlBarVisibleAnimate(visible, listener)
}
override fun getControlBarOffset(): Float {
val fragment = currentVisibleFragment
val actionBar = supportActionBar
if (fragment is IToolBarSupportFragment) {
return fragment.controlBarOffset
} else if (actionBar != null) {
return actionBar.hideOffset / controlBarHeight.toFloat()
}
return 0f
}
override fun setControlBarOffset(offset: Float) {
val fragment = currentVisibleFragment
val actionBar = supportActionBar
if (fragment is IToolBarSupportFragment) {
fragment.controlBarOffset = offset
} else if (actionBar != null && !hideOffsetNotSupported) {
try {
actionBar.hideOffset = (controlBarHeight * offset).toInt()
} catch (e: UnsupportedOperationException) {
// Some device will throw this exception
hideOffsetNotSupported = true
override var controlBarOffset: Float
get() {
val fragment = currentVisibleFragment
val actionBar = supportActionBar
if (fragment is IToolBarSupportFragment) {
return fragment.controlBarOffset
} else if (actionBar != null) {
return actionBar.hideOffset / controlBarHeight.toFloat()
}
return 0f
}
notifyControlBarOffsetChanged()
}
set(offset) {
val fragment = currentVisibleFragment
val actionBar = supportActionBar
if (fragment is IToolBarSupportFragment) {
fragment.controlBarOffset = offset
} else if (actionBar != null && !hideOffsetNotSupported) {
try {
actionBar.hideOffset = (controlBarHeight * offset).toInt()
} catch (e: UnsupportedOperationException) {
// Some device will throw this exception
hideOffsetNotSupported = true
}
override fun getControlBarHeight(): Int {
val fragment = currentVisibleFragment
val actionBar = supportActionBar
if (fragment is IToolBarSupportFragment) {
return fragment.controlBarHeight
} else if (actionBar != null) {
return actionBar.height
}
notifyControlBarOffsetChanged()
}
override val controlBarHeight: Int
get() {
val fragment = currentVisibleFragment
val actionBar = supportActionBar
if (fragment is IToolBarSupportFragment) {
return fragment.controlBarHeight
} else if (actionBar != null) {
return actionBar.height
}
if (actionBarHeight != 0) return actionBarHeight
actionBarHeight = ThemeUtils.getActionBarHeight(this)
return actionBarHeight
}
if (actionBarHeight != 0) return actionBarHeight
actionBarHeight = ThemeUtils.getActionBarHeight(this)
return actionBarHeight
}
private fun setTitle(linkId: Int, uri: Uri): Boolean {
setSubtitle(null)

View File

@ -19,6 +19,7 @@ package org.mariotaku.twidere.activity
import android.annotation.SuppressLint
import android.content.ActivityNotFoundException
import android.content.Intent
import android.graphics.Color
import android.net.Uri
import android.os.Build
import android.os.Bundle
@ -27,7 +28,11 @@ import android.support.v4.app.DialogFragment
import android.support.v4.app.Fragment
import android.support.v4.app.hasRunningLoadersSafe
import android.support.v4.content.ContextCompat
import android.support.v4.graphics.ColorUtils
import android.support.v4.view.ViewPager
import android.support.v4.widget.ViewDragHelper
import android.support.v7.app.WindowDecorActionBar
import android.support.v7.app.containerView
import android.view.Menu
import android.view.MenuItem
import android.widget.Toast
@ -39,6 +44,7 @@ import org.mariotaku.mediaviewer.library.*
import org.mariotaku.mediaviewer.library.subsampleimageview.SubsampleImageViewerFragment.EXTRA_MEDIA_URI
import org.mariotaku.twidere.R
import org.mariotaku.twidere.TwidereConstants.*
import org.mariotaku.twidere.activity.iface.IControlBarActivity.ControlBarShowHideHelper
import org.mariotaku.twidere.activity.iface.IExtendedActivity
import org.mariotaku.twidere.fragment.*
import org.mariotaku.twidere.model.ParcelableMedia
@ -52,21 +58,26 @@ import org.mariotaku.twidere.util.IntentUtils
import org.mariotaku.twidere.util.MenuUtils
import org.mariotaku.twidere.util.PermissionUtils
import org.mariotaku.twidere.util.dagger.GeneralComponentHelper
import org.mariotaku.twidere.util.support.WindowSupport
import org.mariotaku.twidere.view.viewer.MediaSwipeCloseContainer
import java.io.File
import javax.inject.Inject
import android.Manifest.permission as AndroidPermissions
class MediaViewerActivity : BaseActivity(), IMediaViewerActivity {
class MediaViewerActivity : BaseActivity(), IMediaViewerActivity, MediaSwipeCloseContainer.Listener {
@Inject
lateinit var mediaFileCache: FileCache
@Inject
lateinit var mFileCache: FileCache
@Inject
lateinit var mMediaDownloader: MediaDownloader
lateinit var mediaDownloader: MediaDownloader
private var saveToStoragePosition = -1
private var shareMediaPosition = -1
private var shareMediaPosition = -1
private var wasBarShowing = 0
private var hideOffsetNotSupported = false
private lateinit var mediaViewerHelper: IMediaViewerActivity.Helper
private lateinit var controlBarShowHideHelper: ControlBarShowHideHelper
private var tempLocation = IntArray(2)
private val status: ParcelableStatus?
get() = intent.getParcelableExtra<ParcelableStatus>(EXTRA_STATUS)
@ -82,13 +93,16 @@ class MediaViewerActivity : BaseActivity(), IMediaViewerActivity {
super.onCreate(savedInstanceState)
GeneralComponentHelper.build(this).inject(this)
mediaViewerHelper = IMediaViewerActivity.Helper(this)
controlBarShowHideHelper = ControlBarShowHideHelper(this)
mediaViewerHelper.onCreate(savedInstanceState)
val actionBar = supportActionBar!!
actionBar.setDisplayHomeAsUpEnabled(true)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
swipeContainer.listener = this
swipeContainer.backgroundAlpha = 1f
WindowSupport.setStatusBarColor(window, Color.TRANSPARENT)
activityLayout.setStatusBarColor(overrideTheme.colorToolbar)
}
public override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
when (requestCode) {
REQUEST_SHARE_MEDIA -> {
ShareProvider.clearTempFiles(this)
@ -96,6 +110,7 @@ class MediaViewerActivity : BaseActivity(), IMediaViewerActivity {
}
}
override fun onContentChanged() {
super.onContentChanged()
mediaViewerHelper.onContentChanged()
@ -114,6 +129,7 @@ class MediaViewerActivity : BaseActivity(), IMediaViewerActivity {
val currentItem = viewPager.currentItem
if (currentItem < 0 || currentItem >= adapter.count) return false
val obj = adapter.instantiateItem(viewPager, currentItem) as? MediaViewerFragment ?: return false
if (obj.isDetached || obj.host == null) return false
if (obj is CacheDownloadMediaViewerFragment) {
val running = obj.loaderManager.hasRunningLoadersSafe()
val downloaded = obj.hasDownloadedData()
@ -219,25 +235,19 @@ class MediaViewerActivity : BaseActivity(), IMediaViewerActivity {
}
override fun isBarShowing(): Boolean {
val actionBar = supportActionBar
return actionBar != null && actionBar.isShowing
return controlBarOffset >= 1
}
override fun setBarVisibility(visible: Boolean) {
val actionBar = supportActionBar ?: return
if (visible) {
actionBar.show()
} else {
actionBar.hide()
}
setControlBarVisibleAnimate(visible)
}
override fun getDownloader(): MediaDownloader {
return mMediaDownloader
return mediaDownloader
}
override fun getFileCache(): FileCache {
return mFileCache
return mediaFileCache
}
@SuppressLint("SwitchIntDef")
@ -284,6 +294,71 @@ class MediaViewerActivity : BaseActivity(), IMediaViewerActivity {
return theme
}
override fun onSwipeCloseFinished() {
finish()
overridePendingTransition(0, 0)
}
override fun onSwipeOffsetChanged(offset: Int) {
val offsetFactor = 1 - (Math.abs(offset).toFloat() / swipeContainer.height)
swipeContainer.backgroundAlpha = offsetFactor
val colorToolbar = overrideTheme.colorToolbar
val alpha = Math.round(Color.alpha(colorToolbar) * offsetFactor)
activityLayout.setStatusBarColor(ColorUtils.setAlphaComponent(colorToolbar, alpha))
}
override fun onSwipeStateChanged(state: Int) {
supportActionBar?.let { bar ->
if (state == ViewDragHelper.STATE_IDLE) {
if (wasBarShowing == 1 && !isBarShowing) {
setBarVisibility(true)
}
wasBarShowing = 0
} else {
if (wasBarShowing == 0) {
wasBarShowing = if (isBarShowing) 1 else -1
}
if (isBarShowing) {
setBarVisibility(false)
}
}
}
}
override val controlBarHeight: Int
get() = supportActionBar?.height ?: 0
override var controlBarOffset: Float
get() {
val actionBar = supportActionBar
if (actionBar != null) {
return 1 - actionBar.hideOffset / controlBarHeight.toFloat()
}
return 0f
}
set(offset) {
val actionBar = supportActionBar
if (actionBar != null && !hideOffsetNotSupported) {
if (actionBar is WindowDecorActionBar) {
val toolbar = actionBar.containerView
toolbar.alpha = offset
}
try {
actionBar.hideOffset = Math.round(controlBarHeight * (1f - offset))
} catch (e: UnsupportedOperationException) {
// Some device will throw this exception
hideOffsetNotSupported = true
}
}
notifyControlBarOffsetChanged()
}
override fun setControlBarVisibleAnimate(visible: Boolean, listener: ControlBarShowHideHelper.ControlBarAnimationListener?) {
controlBarShowHideHelper.setControlBarVisibleAnimate(visible, listener)
}
private fun processShareIntent(intent: Intent) {
val status = status ?: return
intent.putExtra(Intent.EXTRA_SUBJECT, IntentUtils.getStatusShareSubject(this, status))
@ -406,3 +481,4 @@ class MediaViewerActivity : BaseActivity(), IMediaViewerActivity {
private val REQUEST_PERMISSION_SHARE_MEDIA = 203
}
}

View File

@ -2,6 +2,7 @@ package org.mariotaku.twidere.activity
import android.os.Bundle
import android.support.v4.app.DialogFragment
import android.support.v4.app.NavUtils
import android.view.Menu
import android.view.MenuItem
import android.view.View
@ -43,6 +44,9 @@ class PremiumDashboardActivity : BaseActivity() {
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
android.R.id.home -> {
NavUtils.navigateUpFromSameTask(this)
}
R.id.consume_purchase -> {
if (BuildConfig.DEBUG) {
return true

View File

@ -0,0 +1,102 @@
package org.mariotaku.twidere.activity.iface
import android.animation.Animator
import android.animation.Animator.AnimatorListener
import android.animation.ObjectAnimator
import android.util.Property
import android.view.animation.DecelerateInterpolator
/**
* Created by mariotaku on 14/10/21.
*/
interface IControlBarActivity {
/**
* 0: invisible, 1: visible
*/
var controlBarOffset: Float
get() = 0f
set(value) {}
val controlBarHeight: Int get() = 0
fun setControlBarVisibleAnimate(visible: Boolean, listener: ControlBarShowHideHelper.ControlBarAnimationListener? = null) {}
fun registerControlBarOffsetListener(listener: ControlBarOffsetListener) {}
fun unregisterControlBarOffsetListener(listener: ControlBarOffsetListener) {}
fun notifyControlBarOffsetChanged() {}
interface ControlBarOffsetListener {
fun onControlBarOffsetChanged(activity: IControlBarActivity, offset: Float) {}
}
class ControlBarShowHideHelper(private val activity: IControlBarActivity) {
private var controlAnimationDirection: Int = 0
private var currentControlAnimation: ObjectAnimator? = null
private object ControlBarOffsetProperty : Property<IControlBarActivity, Float>(Float::class.java, null) {
override fun set(obj: IControlBarActivity, value: Float) {
obj.controlBarOffset = (value)
}
override fun get(obj: IControlBarActivity): Float {
return obj.controlBarOffset
}
}
interface ControlBarAnimationListener {
fun onControlBarVisibleAnimationFinish(visible: Boolean)
}
fun setControlBarVisibleAnimate(visible: Boolean, listener: ControlBarAnimationListener? = null) {
val newDirection = if (visible) 1 else -1
if (controlAnimationDirection == newDirection) return
if (currentControlAnimation != null && controlAnimationDirection != 0) {
currentControlAnimation!!.cancel()
currentControlAnimation = null
controlAnimationDirection = newDirection
}
val animator: ObjectAnimator
val offset = activity.controlBarOffset
if (visible) {
if (offset >= 1) return
animator = ObjectAnimator.ofFloat(activity, ControlBarOffsetProperty, offset, 1f)
} else {
if (offset <= 0) return
animator = ObjectAnimator.ofFloat(activity, ControlBarOffsetProperty, offset, 0f)
}
animator.interpolator = DecelerateInterpolator()
animator.addListener(object : AnimatorListener {
override fun onAnimationStart(animation: Animator) {}
override fun onAnimationEnd(animation: Animator) {
controlAnimationDirection = 0
currentControlAnimation = null
listener?.onControlBarVisibleAnimationFinish(visible)
}
override fun onAnimationCancel(animation: Animator) {
controlAnimationDirection = 0
currentControlAnimation = null
}
override fun onAnimationRepeat(animation: Animator) {
}
})
animator.duration = DURATION
animator.start()
currentControlAnimation = animator
controlAnimationDirection = newDirection
}
companion object {
private const val DURATION = 200L
}
}
}

View File

@ -218,15 +218,18 @@ abstract class AbsActivitiesFragment protected constructor() :
var lastReadId: Long = -1
var lastReadViewTop: Int = 0
var loadMore = false
var wasAtTop = false
// 1. Save current read position if not first load
if (!firstLoad) {
val firstVisibleItemPosition = layoutManager.findFirstVisibleItemPosition()
val lastVisibleItemPosition = layoutManager.findLastVisibleItemPosition()
wasAtTop = firstVisibleItemPosition == 0
val statusRange = rangeOfSize(adapter.activityStartIndex, adapter.activityCount - 1)
val lastReadPosition = if (readFromBottom) {
lastVisibleItemPosition
} else {
layoutManager.findFirstVisibleItemPosition()
firstVisibleItemPosition
}.coerceIn(statusRange)
lastReadId = adapter.getTimestamp(lastReadPosition)
lastReadViewTop = layoutManager.findViewByPosition(lastReadPosition)?.top ?: 0
@ -251,7 +254,8 @@ abstract class AbsActivitiesFragment protected constructor() :
restorePosition = adapter.findPositionBySortTimestamp(lastReadId)
}
if (restorePosition != -1 && adapter.isActivity(restorePosition) && (loadMore || readFromBottom
if (restorePosition != -1 && adapter.isActivity(restorePosition) && (loadMore || !wasAtTop ||
readFromBottom
|| (rememberPosition && firstLoad))) {
if (layoutManager.height == 0) {
// RecyclerView has not currently laid out, ignore padding.
@ -292,7 +296,8 @@ abstract class AbsActivitiesFragment protected constructor() :
override fun onMediaClick(holder: IStatusViewHolder, view: View, media: ParcelableMedia, position: Int) {
val status = adapter.getActivity(position)?.getActivityStatus() ?: return
IntentUtils.openMedia(activity, status, media, preferences[newDocumentApiKey], preferences[displaySensitiveContentsKey],
IntentUtils.openMedia(activity, status, media, preferences[newDocumentApiKey],
preferences[displaySensitiveContentsKey],
null)
// BEGIN HotMobi
val event = MediaEvent.create(activity, status, media, timelineType, adapter.mediaPreviewEnabled)

View File

@ -30,7 +30,6 @@ import android.support.v4.content.Loader
import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.RecyclerView
import android.support.v7.widget.RecyclerView.OnScrollListener
import android.util.Log
import android.view.*
import com.squareup.otto.Subscribe
import edu.tsinghua.hotmobi.HotMobiLogger
@ -39,10 +38,8 @@ import kotlinx.android.synthetic.main.fragment_content_recyclerview.*
import org.mariotaku.kpreferences.get
import org.mariotaku.ktextension.isNullOrEmpty
import org.mariotaku.ktextension.rangeOfSize
import org.mariotaku.twidere.BuildConfig
import org.mariotaku.twidere.R
import org.mariotaku.twidere.TwidereConstants
import org.mariotaku.twidere.TwidereConstants.LOGTAG
import org.mariotaku.twidere.adapter.ParcelableStatusesAdapter
import org.mariotaku.twidere.adapter.decorator.DividerItemDecoration
import org.mariotaku.twidere.adapter.iface.ILoadMoreSupportAdapter
@ -275,14 +272,17 @@ abstract class AbsStatusesFragment protected constructor() :
var lastReadId: Long = -1
var lastReadViewTop: Int = 0
var loadMore = false
var wasAtTop = false
// 1. Save current read position if not first load
if (!firstLoad) {
val firstVisibleItemPosition = layoutManager.findFirstVisibleItemPosition()
val lastVisibleItemPosition = layoutManager.findLastVisibleItemPosition()
wasAtTop = firstVisibleItemPosition == 0
val statusRange = rangeOfSize(adapter.statusStartIndex, adapter.statusCount - 1)
val lastReadPosition = if (readFromBottom) {
lastVisibleItemPosition
} else {
layoutManager.findFirstVisibleItemPosition()
firstVisibleItemPosition
}.coerceIn(statusRange)
lastReadId = if (useSortIdAsReadPosition) {
adapter.getStatusSortId(lastReadPosition)
@ -318,8 +318,8 @@ abstract class AbsStatusesFragment protected constructor() :
} else {
onHasMoreDataChanged(false)
}
if (restorePosition != -1 && adapter.isStatus(restorePosition) && (loadMore || readFromBottom
|| (rememberPosition && firstLoad))) {
if (restorePosition != -1 && adapter.isStatus(restorePosition) && (loadMore || !wasAtTop
|| readFromBottom || (rememberPosition && firstLoad))) {
if (layoutManager.height == 0) {
// RecyclerView has not currently laid out, ignore padding.
layoutManager.scrollToPositionWithOffset(restorePosition, lastReadViewTop)

View File

@ -317,7 +317,8 @@ class StatusFragment : BaseFragment(), LoaderCallbacks<SingleResponse<Parcelable
override fun onQuotedStatusClick(holder: IStatusViewHolder, position: Int) {
val status = adapter.getStatus(position) ?: return
IntentUtils.openStatus(activity, status.account_key, status.quoted_id)
val quotedId = status.quoted_id ?: return
IntentUtils.openStatus(activity, status.account_key, quotedId)
}
override fun onStatusLongClick(holder: IStatusViewHolder, position: Int): Boolean {

View File

@ -0,0 +1,125 @@
package org.mariotaku.twidere.view.viewer
import android.content.Context
import android.support.annotation.FloatRange
import android.support.v4.view.ViewCompat
import android.support.v4.widget.ViewDragHelper
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
/**
* Created by mariotaku on 2017/1/29.
*/
class MediaSwipeCloseContainer(context: Context, attrs: AttributeSet? = null) : ViewGroup(context, attrs) {
private val dragHelper: ViewDragHelper = ViewDragHelper.create(this, object : ViewDragHelper.Callback() {
override fun onViewPositionChanged(changedView: View?, left: Int, top: Int, dx: Int, dy: Int) {
val container = this@MediaSwipeCloseContainer
container.childTop = top
container.listener?.onSwipeOffsetChanged(top)
}
override fun onViewDragStateChanged(state: Int) {
val container = this@MediaSwipeCloseContainer
container.listener?.onSwipeStateChanged(container.dragHelper.viewDragState)
}
override fun tryCaptureView(child: View, pointerId: Int): Boolean {
// Only when child can't scroll vertically
return !child.canScrollVertically(-1) && !child.canScrollVertically(1)
}
override fun clampViewPositionVertical(child: View, top: Int, dy: Int): Int {
val container = this@MediaSwipeCloseContainer
return top.coerceIn(-container.height, container.height)
}
override fun getViewVerticalDragRange(child: View?): Int {
val container = this@MediaSwipeCloseContainer
return container.height
}
override fun onViewReleased(releasedChild: View, xvel: Float, yvel: Float) {
val container = this@MediaSwipeCloseContainer
when {
yvel > 0 -> {
// Settle downward
container.dragHelper.settleCapturedViewAt(0, container.height)
}
yvel < 0 -> {
// Settle upward
container.dragHelper.settleCapturedViewAt(0, -container.height)
}
else -> when {
childTop < -container.height / 2 -> {
container.dragHelper.settleCapturedViewAt(0, -container.height)
}
childTop > container.height / 2 -> {
container.dragHelper.settleCapturedViewAt(0, container.height)
}
else -> {
container.dragHelper.settleCapturedViewAt(0, 0)
}
}
}
ViewCompat.postInvalidateOnAnimation(container)
}
})
private var childTop: Int = 0
var listener: Listener? = null
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
measureChildren(widthMeasureSpec, heightMeasureSpec)
setMeasuredDimension(getDefaultSize(suggestedMinimumWidth, widthMeasureSpec),
getDefaultSize(suggestedMinimumHeight, heightMeasureSpec))
}
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
for (i in 0 until childCount) {
val child = getChildAt(i)
if (child.visibility != View.GONE) {
child.layout(0, childTop, child.measuredWidth, childTop + child.measuredHeight)
}
}
}
override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
if (dragHelper.shouldInterceptTouchEvent(ev)) {
return true
}
return super.onInterceptTouchEvent(ev)
}
override fun onTouchEvent(event: MotionEvent): Boolean {
dragHelper.processTouchEvent(event)
return true
}
override fun computeScroll() {
if (dragHelper.continueSettling(true)) {
ViewCompat.postInvalidateOnAnimation(this)
} else if (childTop <= -height || childTop >= height) {
listener?.onSwipeCloseFinished()
}
}
interface Listener {
fun onSwipeCloseFinished() {}
fun onSwipeOffsetChanged(offset: Int) {}
fun onSwipeStateChanged(state: Int) {}
}
var backgroundAlpha: Float
get() = (background?.alpha ?: 0) / 255f
set(@FloatRange(from = 0.0, to = 1.0) value) {
background?.alpha = Math.round(value * 0xFF)
}
}

View File

@ -17,15 +17,27 @@
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<merge 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"
tools:menu="menu_media_viewer_image_page">
<org.mariotaku.twidere.view.TintedStatusFrameLayout
android:id="@+id/activityLayout"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
app:setPadding="false"
tools:menu="menu_media_viewer_image_page">
<org.mariotaku.twidere.view.MediaViewPager
android:id="@+id/viewPager"
<org.mariotaku.twidere.view.viewer.MediaSwipeCloseContainer
android:id="@+id/swipeContainer"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
android:layout_height="match_parent"
android:background="#000000">
</merge>
<org.mariotaku.twidere.view.MediaViewPager
android:id="@+id/viewPager"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</org.mariotaku.twidere.view.viewer.MediaSwipeCloseContainer>
</org.mariotaku.twidere.view.TintedStatusFrameLayout>

View File

@ -1,26 +0,0 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Twidere - Twitter client for Android
~
~ Copyright (C) 2012-2014 Mariotaku Lee <mariotaku.lee@gmail.com>
~
~ 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.
~
~ This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
-->
<resources>
<style name="Theme.AppCompat.TranslucentDecor">
<item name="android:windowTranslucentStatus">true</item>
<item name="android:windowTranslucentNavigation">true</item>
</style>
</resources>

View File

@ -39,7 +39,8 @@
<style name="Theme.Twidere.Viewer" parent="Theme.Twidere.Dark">
<!-- Window attributes -->
<item name="android:windowBackground">@color/background_color_window_viewer</item>
<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:windowIsTranslucent">true</item>
<item name="windowActionBarOverlay">true</item>
<item name="actionBarStyle">@style/Widget.Twidere.Viewer.ActionBar</item>

View File

@ -18,8 +18,7 @@
-->
<resources>
<style name="Animation.Base.Dialog" parent="android:Animation.Dialog"/>
<style name="Theme.AppCompat.TranslucentDecor"/>
</resources>