fixed text selection crashes

This commit is contained in:
Mariotaku Lee 2015-11-20 16:02:35 +08:00
parent c9a93d4036
commit 871b0a98d9
20 changed files with 165 additions and 134 deletions

View File

@ -22,6 +22,7 @@ android {
lintOptions {
abortOnError false
lintConfig rootProject.file('lint.xml')
}
packagingOptions {

25
lint.xml Normal file
View File

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?><!--
~ 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/>.
-->
<lint>
<!-- Disable the given check in this project -->
<issue id="MissingTranslation" severity="ignore" />
<issue id="PluralsCandidate" severity="ignore" />
</lint>

View File

@ -14,7 +14,7 @@ android {
applicationId "org.mariotaku.twidere"
minSdkVersion 14
targetSdkVersion 23
versionCode 131
versionCode 133
versionName "0.3.0"
multiDexEnabled true
}

View File

@ -27,7 +27,9 @@ import android.content.SharedPreferences;
import android.location.Location;
import android.os.BatteryManager;
import android.text.TextUtils;
import android.util.Log;
import org.mariotaku.twidere.BuildConfig;
import org.mariotaku.twidere.Constants;
import org.mariotaku.twidere.app.TwidereApplication;
import org.mariotaku.twidere.util.JsonSerializer;

View File

@ -56,7 +56,6 @@ import android.support.v7.widget.RecyclerView.ViewHolder;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.TextUtils;
import android.text.method.ArrowKeyMovementMethod;
import android.text.method.LinkMovementMethod;
import android.text.style.ClickableSpan;
import android.text.style.URLSpan;
@ -859,7 +858,10 @@ public class StatusFragment extends BaseSupportFragment implements LoaderCallbac
quotedNameView.setText(manager.getUserNickname(status.quoted_user_id, status.quoted_user_name, false));
quotedScreenNameView.setText("@" + status.quoted_user_screen_name);
quotedTextView.setText(HtmlSpanBuilder.fromHtml(status.quoted_text_html));
final Spanned quotedText = HtmlSpanBuilder.fromHtml(status.quoted_text_html);
if (!TextUtils.equals(quotedTextView.getText(), quotedText)) {
quotedTextView.setText(quotedText);
}
linkify.applyAllLinks(quotedTextView, status.account_id, layoutPosition, status.is_possibly_sensitive);
ThemeUtils.applyParagraphSpacing(quotedTextView, 1.1f);
@ -910,7 +912,9 @@ public class StatusFragment extends BaseSupportFragment implements LoaderCallbac
}
timeSourceView.setMovementMethod(LinkMovementMethod.getInstance());
textView.setText(HtmlSpanBuilder.fromHtml(status.text_html));
final Spanned text = HtmlSpanBuilder.fromHtml(status.text_html);
if (!TextUtils.equals(textView.getText(), text))
textView.setText(text);
linkify.applyAllLinks(textView, status.account_id, layoutPosition, status.is_possibly_sensitive);
ThemeUtils.applyParagraphSpacing(textView, 1.1f);
@ -982,8 +986,8 @@ public class StatusFragment extends BaseSupportFragment implements LoaderCallbac
textView.setTextIsSelectable(true);
quotedTextView.setTextIsSelectable(true);
textView.setMovementMethod(ArrowKeyMovementMethod.getInstance());
quotedTextView.setMovementMethod(ArrowKeyMovementMethod.getInstance());
textView.setMovementMethod(LinkMovementMethod.getInstance());
quotedTextView.setMovementMethod(LinkMovementMethod.getInstance());
}
@Override

View File

@ -509,12 +509,10 @@ public class UserFragment extends BaseSupportFragment implements OnClickListener
mDescriptionView.setText(user.description_html != null ? HtmlSpanBuilder.fromHtml(user.description_html) : user.description_plain);
final TwidereLinkify linkify = new TwidereLinkify(this);
linkify.applyAllLinks(mDescriptionView, user.account_id, false);
mDescriptionView.setMovementMethod(null);
mLocationContainer.setVisibility(TextUtils.isEmpty(user.location) ? View.GONE : View.VISIBLE);
mLocationView.setText(user.location);
mURLContainer.setVisibility(TextUtils.isEmpty(user.url) && TextUtils.isEmpty(user.url_expanded) ? View.GONE : View.VISIBLE);
mURLView.setText(TextUtils.isEmpty(user.url_expanded) ? user.url : user.url_expanded);
mURLView.setMovementMethod(null);
final String createdAt = Utils.formatToLongTimeString(activity, user.created_at);
final float daysSinceCreation = (System.currentTimeMillis() - user.created_at) / 1000 / 60 / 60 / 24;
final int dailyTweets = Math.round(user.statuses_count / Math.max(1, daysSinceCreation));

View File

@ -156,7 +156,7 @@ public class RefreshService extends Service implements Constants {
break;
}
case Intent.ACTION_SCREEN_OFF: {
HotMobiLogger.logScreenEvent(context, ScreenEvent.Action.ON);
HotMobiLogger.logScreenEvent(context, ScreenEvent.Action.OFF);
break;
}
}
@ -173,7 +173,6 @@ public class RefreshService extends Service implements Constants {
super.onCreate();
DaggerGeneralComponent.builder().applicationModule(ApplicationModule.get(this)).build().inject(this);
mAlarmManager = (AlarmManager) getSystemService(ALARM_SERVICE);
final TwidereApplication app = TwidereApplication.getInstance(this);
mPendingRefreshHomeTimelineIntent = PendingIntent.getBroadcast(this, 0, new Intent(
BROADCAST_REFRESH_HOME_TIMELINE), 0);
mPendingRefreshMentionsIntent = PendingIntent.getBroadcast(this, 0, new Intent(BROADCAST_REFRESH_MENTIONS), 0);

View File

@ -30,6 +30,7 @@ import com.squareup.okhttp.internal.Network;
import org.mariotaku.twidere.BuildConfig;
import org.mariotaku.twidere.Constants;
import org.mariotaku.twidere.util.HostsFileParser;
import org.mariotaku.twidere.util.SharedPreferencesWrapper;
import org.xbill.DNS.AAAARecord;
import org.xbill.DNS.ARecord;
import org.xbill.DNS.CNAMERecord;
@ -65,7 +66,7 @@ public class TwidereNetwork implements Constants, Network {
private Resolver mDns;
public TwidereNetwork(final Context context) {
mHostMapping = context.getSharedPreferences(HOST_MAPPING_PREFERENCES_NAME, Context.MODE_PRIVATE);
mHostMapping = SharedPreferencesWrapper.getInstance(context, HOST_MAPPING_PREFERENCES_NAME, Context.MODE_PRIVATE);
mPreferences = context.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
final String address = mPreferences.getString(KEY_DNS_SERVER, DEFAULT_DNS_SERVER_ADDRESS);
mDnsAddress = isValidIpAddress(address) ? address : DEFAULT_DNS_SERVER_ADDRESS;

View File

@ -1,102 +0,0 @@
/*
* 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/>.
*/
package org.mariotaku.twidere.view;
import android.content.Context;
import android.support.annotation.NonNull;
import android.text.Layout;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.style.ClickableSpan;
import android.util.AttributeSet;
import android.view.MotionEvent;
import org.mariotaku.twidere.view.themed.ThemedTextView;
public class HandleSpanClickTextView extends ThemedTextView {
private boolean mLongClickPerformed;
public HandleSpanClickTextView(final Context context) {
super(context);
}
public HandleSpanClickTextView(final Context context, final AttributeSet attrs) {
super(context, attrs);
}
public HandleSpanClickTextView(final Context context, final AttributeSet attrs, final int defStyle) {
super(context, attrs, defStyle);
}
@Override
public boolean onTouchEvent(@NonNull final MotionEvent event) {
final Spannable buffer = SpannableString.valueOf(getText());
final int action = event.getAction();
if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_DOWN) {
int x = (int) event.getX();
int y = (int) event.getY();
x -= getTotalPaddingLeft();
y -= getTotalPaddingTop();
x += getScrollX();
y += getScrollY();
final Layout layout = getLayout();
final int line = layout.getLineForVertical(y);
final int off;
try {
off = layout.getOffsetForHorizontal(line, x);
} catch (IndexOutOfBoundsException e) {
throw new IndexOutOfBoundsException("Line count " + layout.getLineCount() +
", line for y " + y + " is " + line + ", x is " + x);
}
final float lineWidth = layout.getLineWidth(line);
final ClickableSpan[] links = buffer.getSpans(off, off, ClickableSpan.class);
if (links.length != 0 && x <= lineWidth) {
final ClickableSpan link = links[0];
if (action == MotionEvent.ACTION_UP) {
setClickable(false);
if (!mLongClickPerformed) {
link.onClick(this);
}
return true;
} else {
mLongClickPerformed = false;
setClickable(true);
}
} else {
setClickable(false);
}
}
return super.onTouchEvent(event);
}
@Override
public boolean performLongClick() {
final boolean result = super.performLongClick();
mLongClickPerformed = true;
return result;
}
}

View File

@ -1,26 +1,45 @@
package org.mariotaku.twidere.view;
import android.content.Context;
import android.support.v7.widget.AppCompatTextView;
import android.text.Editable;
import android.text.Spannable;
import android.util.AttributeSet;
import android.view.MotionEvent;
import org.mariotaku.twidere.text.SafeSpannableString;
import org.mariotaku.twidere.text.SafeSpannableStringBuilder;
import org.mariotaku.twidere.view.themed.ThemedTextView;
public class StatusTextView extends HandleSpanClickTextView {
public class StatusTextView extends ThemedTextView {
public StatusTextView(final Context context) {
this(context, null);
super(context);
init();
}
public StatusTextView(final Context context, final AttributeSet attrs) {
this(context, attrs, 0);
super(context, attrs);
init();
}
public StatusTextView(final Context context, final AttributeSet attrs, final int defStyle) {
super(context, attrs, defStyle);
init();
}
@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) {
final CharSequence text = getText();
setText(null);
setText(text);
}
return super.dispatchTouchEvent(event);
}
private void init() {
setEditableFactory(new SafeEditableFactory());
setSpannableFactory(new SafeSpannableFactory());
}

View File

@ -0,0 +1,87 @@
/*
* 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.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.view.themed.ThemedTextView;
/**
* Returns true when not clicking links
* Created by mariotaku on 15/11/20.
*/
public class TimelineContentTextView extends ThemedTextView {
private boolean mFirstNotLink;
public TimelineContentTextView(Context context) {
super(context);
}
public TimelineContentTextView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public TimelineContentTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
protected MovementMethod getDefaultMovementMethod() {
return LinkMovementMethod.getInstance();
}
@Override
public boolean onTouchEvent(MotionEvent 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);
}
}
}

View File

@ -124,7 +124,6 @@ public class StatusViewHolder extends ViewHolder implements Constants, OnClickLi
} else {
textView.setText(toPlainText(TWIDERE_PREVIEW_TEXT_HTML));
}
textView.setMovementMethod(null);
timeView.setTime(System.currentTimeMillis());
mediaPreview.setVisibility(adapter.isMediaPreviewEnabled() ? View.VISIBLE : View.GONE);
mediaPreview.displayMedia(R.drawable.nyan_stars_background);
@ -185,7 +184,6 @@ public class StatusViewHolder extends ViewHolder implements Constants, OnClickLi
quotedTextView.setText(text);
linkify.applyAllLinks(quotedTextView, status.account_id, getLayoutPosition(),
status.is_possibly_sensitive, adapter.getLinkHighlightingStyle());
quotedTextView.setMovementMethod(null);
} else {
final String text = status.quoted_text_unescaped;
quotedTextView.setText(text);
@ -265,7 +263,6 @@ public class StatusViewHolder extends ViewHolder implements Constants, OnClickLi
linkify.applyAllLinks(textView, status.account_id, getLayoutPosition(),
status.is_possibly_sensitive,
adapter.getLinkHighlightingStyle());
textView.setMovementMethod(null);
}
final Locale locale = Locale.getDefault();

View File

@ -39,7 +39,7 @@
android:orientation="vertical"
android:padding="@dimen/element_spacing_normal">
<org.mariotaku.twidere.view.HandleSpanClickTextView
<org.mariotaku.twidere.view.TimelineContentTextView
android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"

View File

@ -164,7 +164,7 @@
</RelativeLayout>
<org.mariotaku.twidere.view.HandleSpanClickTextView
<org.mariotaku.twidere.view.TimelineContentTextView
android:id="@+id/text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -209,7 +209,7 @@
app:nv_secondaryTextColor="?android:textColorSecondary"
tools:visibility="visible" />
<org.mariotaku.twidere.view.HandleSpanClickTextView
<org.mariotaku.twidere.view.TimelineContentTextView
android:id="@+id/quoted_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"

View File

@ -144,7 +144,7 @@
</LinearLayout>
<org.mariotaku.twidere.view.HandleSpanClickTextView
<org.mariotaku.twidere.view.TimelineContentTextView
android:id="@+id/text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -187,7 +187,7 @@
app:nv_secondaryTextColor="?android:textColorSecondary"
tools:visibility="visible" />
<org.mariotaku.twidere.view.HandleSpanClickTextView
<org.mariotaku.twidere.view.TimelineContentTextView
android:id="@+id/quoted_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"

View File

@ -82,7 +82,7 @@
android:gravity="center_vertical"
android:orientation="horizontal">
<org.mariotaku.twidere.view.HandleSpanClickTextView
<org.mariotaku.twidere.view.TimelineContentTextView
android:id="@+id/name"
android:layout_width="0dp"
android:layout_height="wrap_content"
@ -92,7 +92,7 @@
android:textColor="?android:attr/textColorPrimary"
android:textStyle="bold" />
<org.mariotaku.twidere.view.HandleSpanClickTextView
<org.mariotaku.twidere.view.TimelineContentTextView
android:id="@+id/screen_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"

View File

@ -67,7 +67,7 @@
android:gravity="center_vertical"
android:orientation="vertical">
<org.mariotaku.twidere.view.HandleSpanClickTextView
<org.mariotaku.twidere.view.TimelineContentTextView
android:id="@+id/name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@ -77,7 +77,7 @@
android:textStyle="bold"
tools:text="List" />
<org.mariotaku.twidere.view.HandleSpanClickTextView
<org.mariotaku.twidere.view.TimelineContentTextView
android:id="@+id/created_by"
android:layout_width="wrap_content"
android:layout_height="wrap_content"

View File

@ -166,7 +166,7 @@
android:textStyle="bold"
android:visibility="gone" />
<org.mariotaku.twidere.view.HandleSpanClickTextView
<org.mariotaku.twidere.view.TimelineContentTextView
android:id="@+id/description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -221,7 +221,7 @@
android:color="?android:textColorPrimary"
android:src="@drawable/ic_action_web" />
<org.mariotaku.twidere.view.HandleSpanClickTextView
<org.mariotaku.twidere.view.TimelineContentTextView
android:id="@+id/url"
android:layout_width="match_parent"
android:layout_height="wrap_content"

View File

@ -73,7 +73,7 @@
android:layout_toStartOf="@id/time"
android:orientation="horizontal">
<org.mariotaku.twidere.view.HandleSpanClickTextView
<org.mariotaku.twidere.view.TimelineContentTextView
android:id="@+id/name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@ -81,7 +81,7 @@
android:textColor="?android:attr/textColorPrimary"
tools:text="Mariotaku Lee" />
<org.mariotaku.twidere.view.HandleSpanClickTextView
<org.mariotaku.twidere.view.TimelineContentTextView
android:id="@+id/screen_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"

View File

@ -816,7 +816,7 @@
<string name="twitter_optimized_searches">Twitter optimized searches</string>
<string name="twitter_optimized_searches_summary">Use special search terms to improve search results like exclude retweets</string>
<string name="i_want_my_stars_back">I want my stars back!</string>
<string name="i_want_my_stars_back_summary">Show use favorite (star) instead of like (heart)</string>
<string name="i_want_my_stars_back_summary">Use favorite (★) instead of like (♥︎)</string>
<string name="copy_link">Copy link</string>
<string name="link_copied_to_clipboard">Link copied to clipboard</string>
<string name="login_verification">Login verification</string>