diff --git a/build.gradle b/build.gradle index d6f79eed6..fc0b0a3e8 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ buildscript { maven { url 'https://maven.google.com' } } dependencies { - classpath 'com.android.tools.build:gradle:3.0.0-beta7' + classpath 'com.android.tools.build:gradle:3.0.0' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } diff --git a/twidere/src/main/kotlin/org/mariotaku/ktextension/ArrayExtensions.kt b/twidere/src/main/kotlin/org/mariotaku/ktextension/ArrayExtensions.kt index b1401b949..7fb1265c1 100644 --- a/twidere/src/main/kotlin/org/mariotaku/ktextension/ArrayExtensions.kt +++ b/twidere/src/main/kotlin/org/mariotaku/ktextension/ArrayExtensions.kt @@ -26,6 +26,10 @@ inline fun Array.mapToArray(transform: (T) -> R): Array { return Array(size) { transform(this[it]) } } +inline fun Array.mapToIntArray(transform: (T) -> Int): IntArray { + return IntArray(size) { transform(this[it]) } +} + inline fun Array.mapIndexedToArray(transform: (Int, T) -> R): Array { return Array(size) { transform(it, this[it]) } } diff --git a/twidere/src/main/kotlin/org/mariotaku/ktextension/TextExtensions.kt b/twidere/src/main/kotlin/org/mariotaku/ktextension/TextExtensions.kt index 3beccb2f1..dbe09c061 100644 --- a/twidere/src/main/kotlin/org/mariotaku/ktextension/TextExtensions.kt +++ b/twidere/src/main/kotlin/org/mariotaku/ktextension/TextExtensions.kt @@ -19,6 +19,14 @@ package org.mariotaku.ktextension +import java.text.Normalizer + fun CharSequence.appendTo(sb: StringBuilder) { sb.append(this) +} + +operator fun CharSequence.times(n: Int): String = repeat(n) + +fun CharSequence.normalized(form: Normalizer.Form): String { + return Normalizer.normalize(this, form) } \ No newline at end of file diff --git a/twidere/src/main/kotlin/org/mariotaku/ktextension/TextViewExtensions.kt b/twidere/src/main/kotlin/org/mariotaku/ktextension/TextViewExtensions.kt index f6aa28061..1c0a89871 100644 --- a/twidere/src/main/kotlin/org/mariotaku/ktextension/TextViewExtensions.kt +++ b/twidere/src/main/kotlin/org/mariotaku/ktextension/TextViewExtensions.kt @@ -25,6 +25,9 @@ inline var TextView.charSequence: CharSequence? text = value } +inline val TextView.textIfVisible: CharSequence? + get() = if (visibility == View.VISIBLE) text else null + fun TextView.applyFontFamily(lightFont: Boolean) { if (lightFont) { typeface = Typeface.create("sans-serif-light", typeface?.style ?: Typeface.NORMAL) diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/activity/ComposeActivity.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/activity/ComposeActivity.kt index 497a022cd..c7e1d0875 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/activity/ComposeActivity.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/activity/ComposeActivity.kt @@ -57,7 +57,6 @@ import android.widget.ImageView import android.widget.Toast import com.bumptech.glide.Glide import com.twitter.Extractor -import com.twitter.Validator import kotlinx.android.synthetic.main.activity_compose.* import nl.komponents.kovenant.task import org.mariotaku.abstask.library.AbstractTask @@ -99,6 +98,7 @@ import org.mariotaku.twidere.util.* import org.mariotaku.twidere.util.EditTextEnterHandler.EnterListener import org.mariotaku.twidere.util.dagger.GeneralComponent import org.mariotaku.twidere.util.premium.ExtraFeaturesService +import org.mariotaku.twidere.util.text.StatusTextValidator import org.mariotaku.twidere.util.view.SimpleTextWatcher import org.mariotaku.twidere.util.view.ViewAnimator import org.mariotaku.twidere.util.view.ViewProperties @@ -123,8 +123,6 @@ class ComposeActivity : BaseActivity(), OnMenuItemClickListener, OnClickListener @Inject lateinit var extractor: Extractor @Inject - lateinit var validator: Validator - @Inject lateinit var locationManager: LocationManager private lateinit var itemTouchHelper: ItemTouchHelper @@ -1539,20 +1537,12 @@ class ComposeActivity : BaseActivity(), OnMenuItemClickListener, OnClickListener } private fun getStatusUpdate(checkLength: Boolean): ParcelableStatusUpdate { - val accountKeys = accountsAdapter.selectedAccountKeys - if (accountKeys.isEmpty()) throw NoAccountException() + val accounts = accountsAdapter.selectedAccounts + if (accounts.isEmpty()) throw NoAccountException() val update = ParcelableStatusUpdate() val media = this.media - val text = editText.string?.let { Normalizer.normalize(it, Normalizer.Form.NFC) }.orEmpty() - var summary: String? = null - var summaryLength = 0 - if (editSummary.visibility == View.VISIBLE) { - summary = editSummary.string?.takeIf(String::isNotEmpty)?.let { - Normalizer.normalize(it, Normalizer.Form.NFC) - } - summaryLength = summary?.let { validator.getTweetLength(it) } ?: 0 - } - val accounts = AccountUtils.getAllAccountDetails(AccountManager.get(this), accountKeys, true) + val text = editText.text?.normalized(Normalizer.Form.NFC).orEmpty() + val summary = editSummary.textIfVisible?.normalized(Normalizer.Form.NFC) val maxLength = statusTextCount.maxLength val inReplyTo = inReplyToStatus val replyTextAndMentions = getTwitterReplyTextAndMentions(text, accounts) @@ -1560,10 +1550,10 @@ class ComposeActivity : BaseActivity(), OnMenuItemClickListener, OnClickListener val (replyStartIndex, replyText, _, excludedMentions, replyToOriginalUser) = replyTextAndMentions if (replyText.isEmpty() && media.isEmpty()) throw NoContentException() - val totalLength = summaryLength + validator.getTweetLength(replyText) + val totalLength = StatusTextValidator.calculateLength(accounts, summary, text) if (checkLength && !statusShortenerUsed && maxLength > 0 && totalLength > maxLength) { throw StatusTooLongException(replyStartIndex + - replyText.offsetByCodePoints(0, maxLength - summaryLength)) + replyText.offsetByCodePoints(0, maxLength)) } update.text = replyText update.extended_reply_mode = true @@ -1575,9 +1565,9 @@ class ComposeActivity : BaseActivity(), OnMenuItemClickListener, OnClickListener } } else { if (text.isEmpty() && media.isEmpty()) throw NoContentException() - val totalLength = summaryLength + validator.getTweetLength(text) + val totalLength = StatusTextValidator.calculateLength(accounts, summary, text) if (checkLength && !statusShortenerUsed && maxLength > 0 && totalLength > maxLength) { - throw StatusTooLongException(text.offsetByCodePoints(0, maxLength - summaryLength)) + throw StatusTooLongException(text.offsetByCodePoints(0, maxLength)) } update.text = text update.extended_reply_mode = false @@ -1604,27 +1594,28 @@ class ComposeActivity : BaseActivity(), OnMenuItemClickListener, OnClickListener private fun updateTextCount() { val editable = editText.editableText ?: return - var summaryLength = 0 - if (editSummary.visibility == View.VISIBLE) { - summaryLength = validator.getTweetLength(editSummary.string.orEmpty()) - } + val summary = editSummary.textIfVisible?.toString() + val accounts = accountsAdapter.selectedAccounts val text = editable.toString() val textAndMentions = getTwitterReplyTextAndMentions(text) if (textAndMentions == null) { hintLabel.visibility = View.GONE editable.clearSpans(MentionColorSpan::class.java) - statusTextCount.textCount = summaryLength + validator.getTweetLength(text) + statusTextCount.textCount = StatusTextValidator.calculateLength(accounts, summary, text, + false, null) } else if (textAndMentions.replyToOriginalUser || replyToSelf) { hintLabel.visibility = View.GONE val mentionColor = ThemeUtils.getTextColorSecondary(this) editable.clearSpans(MentionColorSpan::class.java) editable.setSpan(MentionColorSpan(mentionColor), 0, textAndMentions.replyStartIndex, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) - statusTextCount.textCount = summaryLength + validator.getTweetLength(textAndMentions.replyText) + statusTextCount.textCount = StatusTextValidator.calculateLength(accounts, summary, + textAndMentions.replyText, false, null) } else { hintLabel.visibility = View.VISIBLE editable.clearSpans(MentionColorSpan::class.java) - statusTextCount.textCount = summaryLength + validator.getTweetLength(textAndMentions.replyText) + statusTextCount.textCount = StatusTextValidator.calculateLength(accounts, summary, + textAndMentions.replyText, false, null) } } diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/alias/TwitterAliases.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/alias/TwitterAliases.kt new file mode 100644 index 000000000..d14098ebf --- /dev/null +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/alias/TwitterAliases.kt @@ -0,0 +1,22 @@ +/* + * Twidere - Twitter client for Android + * + * Copyright (C) 2012-2017 Mariotaku Lee + * + * 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 . + */ + +package org.mariotaku.twidere.alias + +typealias TwitterRegex = com.twitter.Regex diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/extension/model/AccountDetailsExtensions.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/extension/model/AccountDetailsExtensions.kt index 8a5242024..ed59385da 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/extension/model/AccountDetailsExtensions.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/extension/model/AccountDetailsExtensions.kt @@ -1,7 +1,6 @@ package org.mariotaku.twidere.extension.model import android.content.Context -import com.twitter.Validator import org.mariotaku.microblog.library.twitter.annotation.MediaCategory import org.mariotaku.twidere.annotation.AccountType import org.mariotaku.twidere.model.AccountDetails @@ -13,6 +12,9 @@ import org.mariotaku.twidere.model.account.cred.Credentials import org.mariotaku.twidere.model.account.cred.OAuthCredentials import org.mariotaku.twidere.task.twitter.UpdateStatusTask import org.mariotaku.twidere.util.InternalTwitterContentUtils +import org.mariotaku.twidere.util.text.FanfouValidator +import org.mariotaku.twidere.util.text.MastodonValidator +import org.mariotaku.twidere.util.text.TwitterValidator fun AccountDetails.isOfficial(context: Context): Boolean { val extra = this.extras @@ -73,26 +75,23 @@ fun AccountDetails.getMediaSizeLimit(@MediaCategory mediaCategory: String? = nul /** * Text limit when composing a status, 0 for no limit */ -val AccountDetails.textLimit: Int get() { - if (type == null) { - return Validator.MAX_TWEET_LENGTH - } - when (type) { +val AccountDetails.textLimit: Int + get() = when (type) { AccountType.STATUSNET -> { - val extras = this.extras as? StatusNetAccountExtras - if (extras != null) { - return extras.textLimit - } + (this.extras as? StatusNetAccountExtras)?.textLimit ?: 140 } AccountType.MASTODON -> { - val extras = this.extras as? MastodonAccountExtras - if (extras != null) { - return extras.statusTextLimit - } + (this.extras as? MastodonAccountExtras)?.statusTextLimit ?: MastodonValidator.textLimit } + AccountType.FANFOU -> { + FanfouValidator.textLimit + } + AccountType.TWITTER -> { + TwitterValidator.maxWeightedTweetLength + } + else -> 140 } - return Validator.MAX_TWEET_LENGTH -} + val Array.textLimit: Int get() { diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/BaseDialogFragment.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/BaseDialogFragment.kt index f0fe1b9bd..15fe00fb2 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/BaseDialogFragment.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/BaseDialogFragment.kt @@ -26,7 +26,6 @@ import android.support.v4.app.DialogFragment import com.bumptech.glide.Glide import com.bumptech.glide.RequestManager import com.squareup.otto.Bus -import com.twitter.Validator import okhttp3.Dns import org.mariotaku.kpreferences.KPreferences import org.mariotaku.restfu.http.RestHttpClient @@ -49,8 +48,6 @@ open class BaseDialogFragment : DialogFragment() { @Inject lateinit var kPreferences: KPreferences @Inject - lateinit var validator: Validator - @Inject lateinit var keyboardShortcutsHandler: KeyboardShortcutsHandler @Inject lateinit var bus: Bus diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/BaseFragment.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/BaseFragment.kt index cd99744a6..0ba270bc0 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/BaseFragment.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/BaseFragment.kt @@ -27,7 +27,6 @@ import android.support.v4.text.BidiFormatter import com.bumptech.glide.Glide import com.bumptech.glide.RequestManager import com.squareup.otto.Bus -import com.twitter.Validator import nl.komponents.kovenant.Promise import okhttp3.Dns import org.mariotaku.restfu.http.RestHttpClient @@ -65,8 +64,6 @@ open class BaseFragment : Fragment(), IBaseFragment { @Inject lateinit var errorInfoStore: ErrorInfoStore @Inject - lateinit var validator: Validator - @Inject lateinit var extraFeaturesService: ExtraFeaturesService @Inject lateinit var permissionsManager: PermissionsManager diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/status/RetweetQuoteDialogFragment.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/status/RetweetQuoteDialogFragment.kt index 55a4e5289..da36226fa 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/status/RetweetQuoteDialogFragment.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/status/RetweetQuoteDialogFragment.kt @@ -31,7 +31,6 @@ import android.view.View import android.widget.CheckBox import android.widget.RelativeLayout import android.widget.Toast -import com.twitter.Validator import org.mariotaku.kpreferences.get import org.mariotaku.ktextension.* import org.mariotaku.library.objectcursor.ObjectCursor @@ -53,6 +52,8 @@ import org.mariotaku.twidere.provider.TwidereDataStore.Drafts import org.mariotaku.twidere.service.LengthyOperationsService import org.mariotaku.twidere.util.EditTextEnterHandler import org.mariotaku.twidere.util.LinkCreator +import org.mariotaku.twidere.util.text.FanfouValidator +import org.mariotaku.twidere.util.text.StatusTextValidator import org.mariotaku.twidere.util.view.SimpleTextWatcher import org.mariotaku.twidere.view.ComposeEditText import org.mariotaku.twidere.view.StatusTextCountView @@ -184,7 +185,8 @@ class RetweetQuoteDialogFragment : AbsStatusDialogFragment() { positiveButton.setText(R.string.action_retweet) positiveButton.isEnabled = status.can_retweet } - textCountView.textCount = validator.getTweetLength(s.toString()) + textCountView.textCount = StatusTextValidator.calculateLength(account.type, account.key, + null, s.toString()) } private fun DialogInterface.shouldQuoteRetweet(account: AccountDetails): Boolean { @@ -224,8 +226,8 @@ class RetweetQuoteDialogFragment : AbsStatusDialogFragment() { status.quoted_user_screen_name, status.quoted_text_plain) update.repost_status_id = status.quoted_id } - if (commentText.length > Validator.MAX_TWEET_LENGTH) { - commentText = commentText.substring(0, Math.max(Validator.MAX_TWEET_LENGTH, + if (FanfouValidator.calculateLength(commentText) > FanfouValidator.textLimit) { + commentText = commentText.substring(0, Math.max(FanfouValidator.textLimit, editingComment.length)) } } diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/service/BaseIntentService.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/service/BaseIntentService.kt index 8719a7d78..e17da56f3 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/service/BaseIntentService.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/service/BaseIntentService.kt @@ -3,7 +3,6 @@ package org.mariotaku.twidere.service import android.app.IntentService import android.content.SharedPreferences import com.twitter.Extractor -import com.twitter.Validator import org.mariotaku.twidere.util.AsyncTwitterWrapper import org.mariotaku.twidere.util.NotificationManagerWrapper import org.mariotaku.twidere.util.UserColorNameManager @@ -19,8 +18,6 @@ abstract class BaseIntentService(tag: String) : IntentService(tag) { @Inject lateinit var notificationManager: NotificationManagerWrapper @Inject - lateinit var validator: Validator - @Inject lateinit var extractor: Extractor @Inject lateinit var userColorNameManager: UserColorNameManager diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/service/BaseService.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/service/BaseService.kt index a9d0a16df..7ff5b119b 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/service/BaseService.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/service/BaseService.kt @@ -23,7 +23,6 @@ import android.app.Service import android.content.SharedPreferences import android.net.ConnectivityManager import com.twitter.Extractor -import com.twitter.Validator import org.mariotaku.twidere.util.* import org.mariotaku.twidere.util.dagger.GeneralComponent import org.mariotaku.twidere.util.notification.ContentNotificationManager @@ -38,8 +37,6 @@ abstract class BaseService : Service() { @Inject lateinit var notificationManager: NotificationManagerWrapper @Inject - lateinit var validator: Validator - @Inject lateinit var extractor: Extractor @Inject lateinit var userColorNameManager: UserColorNameManager diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/task/twitter/UpdateStatusTask.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/task/twitter/UpdateStatusTask.kt index 565190668..805208147 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/task/twitter/UpdateStatusTask.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/task/twitter/UpdateStatusTask.kt @@ -14,7 +14,6 @@ import android.support.annotation.WorkerThread import android.support.media.ExifInterface import android.text.TextUtils import android.webkit.MimeTypeMap -import com.twitter.Validator import net.ypresto.androidtranscoder.MediaTranscoder import net.ypresto.androidtranscoder.format.MediaFormatStrategyPresets import org.mariotaku.ktextension.* @@ -43,7 +42,6 @@ import org.mariotaku.twidere.extension.calculateInSampleSize import org.mariotaku.twidere.extension.model.* import org.mariotaku.twidere.extension.model.api.mastodon.toParcelable import org.mariotaku.twidere.extension.model.api.toParcelable -import org.mariotaku.twidere.extension.text.twitter.getTweetLength import org.mariotaku.twidere.model.* import org.mariotaku.twidere.model.account.AccountExtras import org.mariotaku.twidere.model.analyzer.UpdateStatus @@ -55,6 +53,7 @@ import org.mariotaku.twidere.task.BaseAbstractTask import org.mariotaku.twidere.util.* import org.mariotaku.twidere.util.io.ContentLengthInputStream import org.mariotaku.twidere.util.premium.ExtraFeaturesService +import org.mariotaku.twidere.util.text.StatusTextValidator import java.io.Closeable import java.io.File import java.io.FileNotFoundException @@ -222,7 +221,6 @@ class UpdateStatusTask( update: ParcelableStatusUpdate, pending: PendingStatusUpdate) { if (shortener == null) return - val validator = Validator() stateCallback.onShorteningStatus() val sharedShortened = HashMap() for (i in 0 until pending.length) { @@ -230,8 +228,8 @@ class UpdateStatusTask( val text = pending.overrideTexts[i] val textLimit = account.textLimit val ignoreMentions = account.type == AccountType.TWITTER - if (textLimit >= 0 && validator.getTweetLength(text, ignoreMentions, - update.in_reply_to_status, account.key) <= textLimit) { + if (textLimit >= 0 && StatusTextValidator.calculateLength(account.type, account.key, + update.summary, text, ignoreMentions, update.in_reply_to_status) <= textLimit) { continue } shortener.waitForService() diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/util/dagger/ApplicationModule.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/util/dagger/ApplicationModule.kt index 51c6e2874..c1d959267 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/util/dagger/ApplicationModule.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/util/dagger/ApplicationModule.kt @@ -34,7 +34,6 @@ import com.google.android.exoplayer2.upstream.DataSource import com.squareup.otto.Bus import com.squareup.otto.ThreadEnforcer import com.twitter.Extractor -import com.twitter.Validator import dagger.Module import dagger.Provides import okhttp3.Cache @@ -199,12 +198,6 @@ class ApplicationModule(private val context: Context) { return TwidereMediaDownloader(context, client, thumbor) } - @Provides - @Singleton - fun validator(): Validator { - return Validator() - } - @Provides @Singleton fun extractor(): Extractor { diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/util/dagger/DependencyHolder.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/util/dagger/DependencyHolder.kt index cbf7686b5..f60e4a723 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/util/dagger/DependencyHolder.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/util/dagger/DependencyHolder.kt @@ -21,7 +21,6 @@ package org.mariotaku.twidere.util.dagger import android.content.Context import android.content.SharedPreferences -import com.twitter.Validator import okhttp3.Cache import okhttp3.ConnectionPool import okhttp3.Dns @@ -55,9 +54,6 @@ class DependencyHolder internal constructor(context: Context) { lateinit var dns: Dns internal set @Inject - lateinit var validator: Validator - internal set - @Inject lateinit var preferences: SharedPreferences internal set @Inject diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/util/text/FanfouValidator.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/util/text/FanfouValidator.kt new file mode 100644 index 000000000..02a81ec52 --- /dev/null +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/util/text/FanfouValidator.kt @@ -0,0 +1,30 @@ +/* + * Twidere - Twitter client for Android + * + * Copyright (C) 2012-2017 Mariotaku Lee + * + * 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 . + */ + +package org.mariotaku.twidere.util.text + +object FanfouValidator { + + const val textLimit = 140 + + fun calculateLength(text: String): Int { + return text.codePointCount(0, text.length) + } + +} diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/util/text/MastodonValidator.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/util/text/MastodonValidator.kt new file mode 100644 index 000000000..7a0545ac9 --- /dev/null +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/util/text/MastodonValidator.kt @@ -0,0 +1,53 @@ +/* + * Twidere - Twitter client for Android + * + * Copyright (C) 2012-2017 Mariotaku Lee + * + * 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 . + */ + +package org.mariotaku.twidere.util.text + +import org.mariotaku.ktextension.times +import org.mariotaku.twidere.alias.TwitterRegex + + +object MastodonValidator { + + const val textLimit: Int = 500 + + private val mentionRegex = Regex("(?<=^|[^/[\\w]])@(([a-z0-9_]+)(?:@[a-z0-9.\\-]+[a-z0-9]+)?)", RegexOption.IGNORE_CASE) + + private val urlRegex = TwitterRegex.VALID_URL.toRegex() + + fun getCountableLength(summary: String?, text: String): Int { + var length = 0 + if (summary != null) { + length += summary.codePointCount + } + length += text.countableText.codePointCount + return length + } + + private inline val String.codePointCount: Int + get() = codePointCount(0, length) + + private inline val String.countableText: String + get() = replace(urlRegex) { + it.groupValues[TwitterRegex.VALID_URL_GROUP_BEFORE] + "x" * 23 + }.replace(mentionRegex) { mr -> + "@${mr.groupValues[2]}" + } + +} diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/util/text/StatusTextValidator.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/util/text/StatusTextValidator.kt new file mode 100644 index 000000000..55f72c5ea --- /dev/null +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/util/text/StatusTextValidator.kt @@ -0,0 +1,63 @@ +/* + * Twidere - Twitter client for Android + * + * Copyright (C) 2012-2017 Mariotaku Lee + * + * 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 . + */ + +package org.mariotaku.twidere.util.text + +import org.mariotaku.ktextension.mapToIntArray +import org.mariotaku.twidere.annotation.AccountType +import org.mariotaku.twidere.extension.text.twitter.getTweetLength +import org.mariotaku.twidere.model.AccountDetails +import org.mariotaku.twidere.model.ParcelableStatus +import org.mariotaku.twidere.model.UserKey + +object StatusTextValidator { + + fun calculateLength(@AccountType accountType: String, accountKey: UserKey?, summary: String?, + text: String, ignoreMentions: Boolean = false, inReplyTo: ParcelableStatus? = null): Int { + when (accountType) { + AccountType.TWITTER -> { + return TwitterValidator.getTweetLength(text, ignoreMentions, inReplyTo, accountKey) + } + AccountType.MASTODON -> { + return MastodonValidator.getCountableLength(summary, text) + } + AccountType.FANFOU -> { + return FanfouValidator.calculateLength(text) + } + AccountType.STATUSNET -> { + return text.codePointCount(0, text.length) + } + else -> { + return text.codePointCount(0, text.length) + } + } + } + + fun calculateLengths(accounts: Array, summary: String?, text: String, + ignoreMentions: Boolean = false, inReplyTo: ParcelableStatus? = null): IntArray { + return accounts.mapToIntArray { + calculateLength(it.type, it.key, summary, text, ignoreMentions, inReplyTo) + } + } + + fun calculateLength(accounts: Array, summary: String?, text: String, + ignoreMentions: Boolean = false, inReplyTo: ParcelableStatus? = null): Int { + return calculateLengths(accounts, summary, text, ignoreMentions, inReplyTo).max() ?: 0 + } +} diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/util/text/TwitterValidator.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/util/text/TwitterValidator.kt new file mode 100644 index 000000000..cb1a255b5 --- /dev/null +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/util/text/TwitterValidator.kt @@ -0,0 +1,90 @@ +/* + * Twidere - Twitter client for Android + * + * Copyright (C) 2012-2017 Mariotaku Lee + * + * 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 . + */ + +package org.mariotaku.twidere.util.text + +import com.twitter.Extractor +import com.twitter.Validator +import java.text.Normalizer + +object TwitterValidator : Validator() { + + const val maxWeightedTweetLength: Int = 280 + + const val defaultWeight: Int = 200 + + var ranges: Array = arrayOf( + WeightRange(0, 4351, 100), + WeightRange(8192, 8205, 100), + WeightRange(8208, 8223, 100), + WeightRange(8242, 8247, 100) + ) + + private val extractor = Extractor() + + override fun getTweetLength(text: String): Int { + val normalized = Normalizer.normalize(text, Normalizer.Form.NFC) + var weightedLength = 0 + val inputLength = normalized.length + + var charOffset = 0 + while (charOffset < inputLength) { + val codePoint = Character.codePointAt(normalized, charOffset) + weightedLength += weightForCodePoint(codePoint) + charOffset += Character.charCount(codePoint) + } + + var length = weightedLength / 100 + + for (urlEntity in extractor.extractURLsWithIndices(normalized)) { + length += urlEntity.start - urlEntity.end + length += if (urlEntity.value.toLowerCase().startsWith("https://")) shortUrlLengthHttps else shortUrlLength + } + + return length + } + + override fun isValidTweet(text: String?): Boolean { + if (text == null || text.isEmpty()) { + return false + } + + for (c in text.toCharArray()) { + if (c == '\uFFFE' || c == '\uFEFF' || // BOM + c == '\uFFFF' || // Special + c in '\u202A'..'\u202E') { // Direction change + return false + } + } + + return getTweetLength(text) <= maxWeightedTweetLength + } + + private fun weightForCodePoint(codePoint: Int): Int { + val range = ranges.find { codePoint in it } ?: return defaultWeight + return range.weight + } + + data class WeightRange(val start: Int, val end: Int, val weight: Int) { + operator fun contains(codePoint: Int): Boolean { + return codePoint in start..end + } + } + +}