improved link click

This commit is contained in:
Mariotaku Lee 2016-08-09 15:48:16 +08:00
parent 738bdb1acc
commit d3c3f05e43
29 changed files with 178 additions and 146 deletions

View File

@ -35,7 +35,7 @@ subprojects {
if (project.hasProperty('android')) {
android {
compileSdkVersion 24
buildToolsVersion '24.0.0'
buildToolsVersion '24.0.1'
lintOptions {
abortOnError false

View File

@ -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;

View File

@ -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

View File

@ -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;

View File

@ -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();
}
}

View File

@ -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

View File

@ -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) {

View File

@ -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)

View File

@ -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
}
}
}