improved link click
This commit is contained in:
parent
738bdb1acc
commit
d3c3f05e43
|
@ -35,7 +35,7 @@ subprojects {
|
|||
if (project.hasProperty('android')) {
|
||||
android {
|
||||
compileSdkVersion 24
|
||||
buildToolsVersion '24.0.0'
|
||||
buildToolsVersion '24.0.1'
|
||||
|
||||
lintOptions {
|
||||
abortOnError false
|
||||
|
|
|
@ -28,6 +28,8 @@ import org.mariotaku.restfu.annotation.method.POST;
|
|||
import org.mariotaku.restfu.annotation.param.KeyValue;
|
||||
import org.mariotaku.restfu.annotation.param.Param;
|
||||
import org.mariotaku.restfu.annotation.param.Params;
|
||||
import org.mariotaku.restfu.annotation.param.Queries;
|
||||
import org.mariotaku.restfu.annotation.param.Query;
|
||||
import org.mariotaku.restfu.annotation.param.Raw;
|
||||
import org.mariotaku.restfu.http.BodyType;
|
||||
import org.mariotaku.restfu.http.mime.Body;
|
||||
|
@ -59,8 +61,8 @@ public interface TwitterUpload {
|
|||
MediaUploadResponse finalizeUploadMedia(@Param("media_id") String mediaId) throws MicroBlogException;
|
||||
|
||||
@GET("/media/upload.json")
|
||||
@Params(@KeyValue(key = "command", value = "STATUS"))
|
||||
MediaUploadResponse getUploadMediaStatus(@Param("media_id") String mediaId) throws MicroBlogException;
|
||||
@Queries(@KeyValue(key = "command", value = "STATUS"))
|
||||
MediaUploadResponse getUploadMediaStatus(@Query("media_id") String mediaId) throws MicroBlogException;
|
||||
|
||||
@POST("/media/metadata/create.json")
|
||||
ResponseCode createMetadata(@Raw NewMediaMetadata metadata) throws MicroBlogException;
|
||||
|
|
|
@ -454,24 +454,21 @@ class UpdateStatusTask(internal val context: Context, internal val stateCallback
|
|||
upload.appendUploadMedia(response.id, segmentIndex, bulk)
|
||||
}
|
||||
response = upload.finalizeUploadMedia(response.id)
|
||||
run {
|
||||
var info: MediaUploadResponse.ProcessingInfo? = response.processingInfo
|
||||
while (info != null && shouldWaitForProcess(info)) {
|
||||
val checkAfterSecs = info.checkAfterSecs
|
||||
if (checkAfterSecs <= 0) {
|
||||
break
|
||||
}
|
||||
try {
|
||||
Thread.sleep(TimeUnit.SECONDS.toMillis(checkAfterSecs))
|
||||
} catch (e: InterruptedException) {
|
||||
break
|
||||
}
|
||||
|
||||
response = upload.getUploadMediaStatus(response.id)
|
||||
info = response.processingInfo
|
||||
var info: MediaUploadResponse.ProcessingInfo? = response.processingInfo
|
||||
while (info != null && shouldWaitForProcess(info)) {
|
||||
val checkAfterSecs = info.checkAfterSecs
|
||||
if (checkAfterSecs <= 0) {
|
||||
break
|
||||
}
|
||||
try {
|
||||
Thread.sleep(TimeUnit.SECONDS.toMillis(checkAfterSecs))
|
||||
} catch (e: InterruptedException) {
|
||||
break
|
||||
}
|
||||
|
||||
response = upload.getUploadMediaStatus(response.id)
|
||||
info = response.processingInfo
|
||||
}
|
||||
val info = response.processingInfo
|
||||
if (info != null && MediaUploadResponse.ProcessingInfo.State.FAILED == info.state) {
|
||||
val exception = MicroBlogException()
|
||||
val errorInfo = info.error
|
||||
|
|
|
@ -214,7 +214,6 @@ import edu.tsinghua.hotmobi.model.NotificationEvent;
|
|||
import static org.mariotaku.twidere.provider.TwidereDataStore.DIRECT_MESSAGES_URIS;
|
||||
import static org.mariotaku.twidere.provider.TwidereDataStore.STATUSES_URIS;
|
||||
import static org.mariotaku.twidere.util.TwidereLinkify.PATTERN_TWITTER_PROFILE_IMAGES;
|
||||
import static org.mariotaku.twidere.util.TwidereLinkify.TWITTER_PROFILE_IMAGES_AVAILABLE_SIZES;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public final class Utils implements Constants {
|
||||
|
@ -1335,8 +1334,9 @@ public final class Utils implements Constants {
|
|||
|
||||
public static String getOriginalTwitterProfileImage(final String url) {
|
||||
if (url == null) return null;
|
||||
if (PATTERN_TWITTER_PROFILE_IMAGES.matcher(url).matches())
|
||||
return replaceLast(url, "_" + TWITTER_PROFILE_IMAGES_AVAILABLE_SIZES, "");
|
||||
final Matcher matcher = PATTERN_TWITTER_PROFILE_IMAGES.matcher(url);
|
||||
if (matcher.matches())
|
||||
return matcher.replaceFirst("$1$2/profile_images/$3/$4$6");
|
||||
return url;
|
||||
}
|
||||
|
||||
|
@ -1496,8 +1496,10 @@ public final class Utils implements Constants {
|
|||
|
||||
public static String getTwitterProfileImageOfSize(final String url, final String size) {
|
||||
if (url == null) return null;
|
||||
if (PATTERN_TWITTER_PROFILE_IMAGES.matcher(url).matches())
|
||||
return replaceLast(url, "_" + TWITTER_PROFILE_IMAGES_AVAILABLE_SIZES, String.format("_%s", size));
|
||||
final Matcher matcher = PATTERN_TWITTER_PROFILE_IMAGES.matcher(url);
|
||||
if (matcher.matches()) {
|
||||
return matcher.replaceFirst("$1$2/profile_images/$3/$4_" + size + "$6");
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
|
@ -1711,11 +1713,6 @@ public final class Utils implements Constants {
|
|||
return top - actionBarHeight;
|
||||
}
|
||||
|
||||
public static String replaceLast(final String text, final String regex, final String replacement) {
|
||||
if (text == null || regex == null || replacement == null) return text;
|
||||
return text.replaceFirst("(?s)" + regex + "(?!.*?" + regex + ")", replacement);
|
||||
}
|
||||
|
||||
public static void restartActivity(final Activity activity) {
|
||||
if (activity == null) return;
|
||||
final int enterAnim = android.R.anim.fade_in;
|
||||
|
|
|
@ -1,109 +0,0 @@
|
|||
/*
|
||||
* Twidere - Twitter client for Android
|
||||
*
|
||||
* Copyright (C) 2012-2015 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/>.
|
||||
*/
|
||||
|
||||
package org.mariotaku.twidere.view;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.v7.widget.AppCompatTextView;
|
||||
import android.text.Layout;
|
||||
import android.text.Spannable;
|
||||
import android.text.method.LinkMovementMethod;
|
||||
import android.text.method.MovementMethod;
|
||||
import android.text.style.ClickableSpan;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.MotionEvent;
|
||||
|
||||
import org.mariotaku.twidere.util.EmojiSupportUtils;
|
||||
|
||||
/**
|
||||
* Returns true when not clicking links
|
||||
* Created by mariotaku on 15/11/20.
|
||||
*/
|
||||
public class TimelineContentTextView extends AppCompatTextView {
|
||||
private boolean mFirstNotLink;
|
||||
|
||||
public TimelineContentTextView(Context context) {
|
||||
super(context);
|
||||
EmojiSupportUtils.initForTextView(this);
|
||||
}
|
||||
|
||||
public TimelineContentTextView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
EmojiSupportUtils.initForTextView(this);
|
||||
}
|
||||
|
||||
public TimelineContentTextView(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
EmojiSupportUtils.initForTextView(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean dispatchTouchEvent(MotionEvent event) {
|
||||
// FIXME simple workaround to https://code.google.com/p/android/issues/detail?id=191430
|
||||
// Android clears TextView when setText(), so setText before touch
|
||||
if (event.getActionMasked() == MotionEvent.ACTION_DOWN && isTextSelectable()) {
|
||||
if (getSelectionEnd() != getSelectionStart()) {
|
||||
final CharSequence text = getText();
|
||||
setText(null);
|
||||
setText(text);
|
||||
}
|
||||
}
|
||||
return super.dispatchTouchEvent(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
if (isTextSelectable()) {
|
||||
return super.onTouchEvent(event);
|
||||
}
|
||||
switch (event.getActionMasked()) {
|
||||
case MotionEvent.ACTION_DOWN: {
|
||||
Layout layout = getLayout();
|
||||
final float x = event.getX() - getPaddingLeft() + getScrollX();
|
||||
final float y = event.getY() - getPaddingTop() + getScrollY();
|
||||
final int line = layout.getLineForVertical(Math.round(y));
|
||||
int offset = layout.getOffsetForHorizontal(line, x);
|
||||
final CharSequence text = getText();
|
||||
if (text instanceof Spannable) {
|
||||
final ClickableSpan[] spans = ((Spannable) text).getSpans(offset, offset, ClickableSpan.class);
|
||||
mFirstNotLink = spans.length == 0;
|
||||
} else {
|
||||
mFirstNotLink = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case MotionEvent.ACTION_UP: {
|
||||
mFirstNotLink = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (mFirstNotLink) {
|
||||
super.onTouchEvent(event);
|
||||
return false;
|
||||
} else {
|
||||
return super.onTouchEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected MovementMethod getDefaultMovementMethod() {
|
||||
return LinkMovementMethod.getInstance();
|
||||
}
|
||||
|
||||
}
|
|
@ -606,8 +606,8 @@ class StatusViewHolder(private val adapter: IStatusesAdapter<*>, itemView: View)
|
|||
val holder = holderRef.get() ?: return false
|
||||
val listener = holder.statusClickListener ?: return false
|
||||
val position = holder.layoutPosition
|
||||
when (v.id) {
|
||||
R.id.itemContent -> {
|
||||
when (v) {
|
||||
holder.itemContent -> {
|
||||
if (!holder.isCardActionsShown) {
|
||||
holder.showCardActions()
|
||||
return true
|
||||
|
|
|
@ -399,7 +399,7 @@ class ParcelableActivitiesAdapter(
|
|||
}
|
||||
|
||||
override fun onStatusLongClick(holder: IStatusViewHolder, position: Int): Boolean {
|
||||
return false
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onUserProfileClick(holder: IStatusViewHolder, position: Int) {
|
||||
|
|
|
@ -526,7 +526,7 @@ class UserFragment : BaseSupportFragment(), OnClickListener, OnLinkClickListener
|
|||
} else {
|
||||
lm.restartLoader(LOADER_ID_USER, args, mUserInfoLoaderCallbacks)
|
||||
}
|
||||
if (accountKey == null || userKey == null && screenName == null) {
|
||||
if (userKey == null && screenName == null) {
|
||||
cardContent!!.visibility = View.GONE
|
||||
errorContainer!!.visibility = View.GONE
|
||||
}
|
||||
|
@ -625,7 +625,7 @@ class UserFragment : BaseSupportFragment(), OnClickListener, OnLinkClickListener
|
|||
|
||||
|
||||
userFragmentView.setWindowInsetsListener { left, top, right, bottom ->
|
||||
profileContentContainer!!.setPadding(0, top, 0, 0)
|
||||
profileContentContainer.setPadding(0, top, 0, 0)
|
||||
profileBannerSpace.statusBarHeight = top
|
||||
|
||||
if (profileBannerSpace.toolbarHeight == 0) {
|
||||
|
@ -636,13 +636,13 @@ class UserFragment : BaseSupportFragment(), OnClickListener, OnLinkClickListener
|
|||
profileBannerSpace.toolbarHeight = toolbarHeight
|
||||
}
|
||||
}
|
||||
profileContentContainer!!.setOnSizeChangedListener { view, w, h, oldw, oldh ->
|
||||
profileContentContainer.setOnSizeChangedListener { view, w, h, oldw, oldh ->
|
||||
val toolbarHeight = toolbar.measuredHeight
|
||||
userProfileDrawer!!.setPadding(0, toolbarHeight, 0, 0)
|
||||
userProfileDrawer.setPadding(0, toolbarHeight, 0, 0)
|
||||
profileBannerSpace.toolbarHeight = toolbarHeight
|
||||
}
|
||||
|
||||
userProfileDrawer!!.setDrawerCallback(this)
|
||||
userProfileDrawer.setDrawerCallback(this)
|
||||
|
||||
pagerAdapter = SupportTabsAdapter(activity, childFragmentManager)
|
||||
|
||||
|
|
|
@ -0,0 +1,145 @@
|
|||
/*
|
||||
* Twidere - Twitter client for Android
|
||||
*
|
||||
* Copyright (C) 2012-2015 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/>.
|
||||
*/
|
||||
|
||||
package org.mariotaku.twidere.view
|
||||
|
||||
import android.content.Context
|
||||
import android.support.v7.widget.AppCompatTextView
|
||||
import android.text.Spannable
|
||||
import android.text.method.MovementMethod
|
||||
import android.text.style.ClickableSpan
|
||||
import android.util.AttributeSet
|
||||
import android.view.KeyEvent
|
||||
import android.view.MotionEvent
|
||||
import android.widget.TextView
|
||||
import org.mariotaku.twidere.util.EmojiSupportUtils
|
||||
|
||||
/**
|
||||
* Returns true when not clicking links
|
||||
* Created by mariotaku on 15/11/20.
|
||||
*/
|
||||
class TimelineContentTextView : AppCompatTextView {
|
||||
|
||||
constructor(context: Context) : super(context) {
|
||||
EmojiSupportUtils.initForTextView(this)
|
||||
}
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
|
||||
EmojiSupportUtils.initForTextView(this)
|
||||
}
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context, attrs, defStyle) {
|
||||
EmojiSupportUtils.initForTextView(this)
|
||||
}
|
||||
|
||||
override fun dispatchTouchEvent(event: MotionEvent): Boolean {
|
||||
// FIXME simple workaround to https://code.google.com/p/android/issues/detail?id=191430
|
||||
// Android clears TextView when setText(), so setText before touch
|
||||
if (event.actionMasked == MotionEvent.ACTION_DOWN && isTextSelectable) {
|
||||
if (selectionEnd != selectionStart) {
|
||||
val text = text
|
||||
setText(null)
|
||||
setText(text)
|
||||
}
|
||||
}
|
||||
return super.dispatchTouchEvent(event)
|
||||
}
|
||||
|
||||
override fun onTouchEvent(event: MotionEvent): Boolean {
|
||||
if (isTextSelectable) {
|
||||
return super.onTouchEvent(event)
|
||||
}
|
||||
return super.onTouchEvent(event)
|
||||
}
|
||||
|
||||
override fun getDefaultMovementMethod(): MovementMethod {
|
||||
return InternalMovementMethod()
|
||||
}
|
||||
|
||||
override fun setClickable(clickable: Boolean) {
|
||||
super.setClickable(false)
|
||||
}
|
||||
|
||||
override fun setLongClickable(longClickable: Boolean) {
|
||||
super.setLongClickable(false)
|
||||
}
|
||||
|
||||
internal class InternalMovementMethod : MovementMethod {
|
||||
private var targetSpan: ClickableSpan? = null
|
||||
|
||||
override fun initialize(widget: TextView, text: Spannable) {
|
||||
|
||||
}
|
||||
|
||||
override fun onKeyDown(widget: TextView, text: Spannable, keyCode: Int, keyEvent: KeyEvent): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun onKeyUp(widget: TextView, text: Spannable, keyCode: Int, keyEvent: KeyEvent): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun onKeyOther(widget: TextView, text: Spannable, keyEvent: KeyEvent): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun onTakeFocus(widget: TextView, text: Spannable, direction: Int) {
|
||||
|
||||
}
|
||||
|
||||
override fun onTrackballEvent(widget: TextView, text: Spannable, event: MotionEvent): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun onTouchEvent(widget: TextView, text: Spannable, event: MotionEvent): Boolean {
|
||||
when (event.actionMasked) {
|
||||
MotionEvent.ACTION_DOWN -> {
|
||||
val layout = widget.layout
|
||||
val x = event.x - widget.paddingLeft + widget.scrollX
|
||||
val y = event.y - widget.paddingTop + widget.scrollY
|
||||
val line = layout.getLineForVertical(Math.round(y))
|
||||
val offset = layout.getOffsetForHorizontal(line, x)
|
||||
if (x <= layout.getLineWidth(line)) {
|
||||
targetSpan = text.getSpans(offset, offset, ClickableSpan::class.java).firstOrNull()
|
||||
} else {
|
||||
targetSpan = null
|
||||
}
|
||||
}
|
||||
MotionEvent.ACTION_UP -> {
|
||||
targetSpan?.onClick(widget)
|
||||
val handled = targetSpan != null
|
||||
targetSpan = null
|
||||
return handled
|
||||
}
|
||||
MotionEvent.ACTION_CANCEL -> {
|
||||
targetSpan = null
|
||||
}
|
||||
}
|
||||
return targetSpan != null
|
||||
}
|
||||
|
||||
override fun onGenericMotionEvent(widget: TextView, text: Spannable, event: MotionEvent): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun canSelectArbitrarily(): Boolean {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue