Merge branch '280ch' into develop

# Conflicts:
#	twidere/src/main/kotlin/org/mariotaku/ktextension/TextExtensions.kt
#	twidere/src/main/kotlin/org/mariotaku/twidere/activity/ComposeActivity.kt
#	twidere/src/main/kotlin/org/mariotaku/twidere/dagger/module/ApplicationModule.kt
#	twidere/src/main/kotlin/org/mariotaku/twidere/extension/model/AccountDetailsExtensions.kt
#	twidere/src/main/kotlin/org/mariotaku/twidere/fragment/status/RetweetQuoteDialogFragment.kt
#	twidere/src/main/kotlin/org/mariotaku/twidere/task/status/UpdateStatusTask.kt
#	twidere/src/main/kotlin/org/mariotaku/twidere/util/text/MastodonValidator.kt
#	twidere/src/main/kotlin/org/mariotaku/twidere/util/text/StatusTextValidator.kt
#	twidere/src/main/kotlin/org/mariotaku/twidere/util/text/TwitterValidator.kt
This commit is contained in:
Mariotaku Lee 2017-11-13 15:19:08 +08:00
commit d54c4cc5b1
No known key found for this signature in database
GPG Key ID: 15C10F89D7C33535
16 changed files with 200 additions and 105 deletions

View File

@ -0,0 +1,51 @@
{
"account": {
"name": "mariotaku@mastodon.social",
"type": "org.mariotaku.twidere.account"
},
"activated": true,
"color": "#000000",
"credentials": {
"access_token": "REDACTED",
"api_url_format": "https:\/\/mastodon.social\/api\/",
"no_version_suffix": true
},
"credentials_type": "oauth2",
"dummy": false,
"extras": {
"status_text_limit": 500
},
"key": "200092@mastodon.social",
"position": 1,
"test": true,
"type": "mastodon",
"user": {
"account_id": "200092@mastodon.social",
"background_color": null,
"created_at": 1505290270613,
"description_plain": "",
"description_spans": [],
"description_unescaped": "",
"favorites_count": -1,
"followers_count": 0,
"friends_count": 10,
"is_basic": false,
"is_cache": true,
"is_follow_request_sent": false,
"is_following": false,
"is_protected": false,
"is_verified": false,
"id": "200092@mastodon.social",
"link_color": null,
"listed_count": -1,
"media_count": -1,
"name": "Mariotaku Lee",
"position": 0,
"profile_banner_url": "https:\/\/mastodon.social\/headers\/original\/missing.png",
"profile_image_url": "https:\/\/files.mastodon.social\/accounts\/avatars\/000\/200\/092\/original\/pnc__picked_media_7f031c49-be65-4cbb-a949-2711564eacfd.jpeg",
"screen_name": "mariotaku",
"statuses_count": 2,
"text_color": null,
"url": "https:\/\/mastodon.social\/@mariotaku"
}
}

View File

@ -19,8 +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)
operator fun CharSequence.times(n: Int): String = repeat(n)
fun CharSequence.normalized(form: Normalizer.Form): String {
return Normalizer.normalize(this, form)
}

View File

@ -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.combine.and
import nl.komponents.kovenant.task
@ -126,8 +125,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
@ -1477,13 +1474,23 @@ class ComposeActivity : BaseActivity(), OnMenuItemClickListener, OnClickListener
getStatusUpdate(true)
} catch (e: NoAccountException) {
editText.error = getString(R.string.message_toast_no_account_selected)
editText.requestFocus()
return
} catch (e: NoContentException) {
editText.error = getString(R.string.error_message_no_content)
editText.requestFocus()
return
} catch (e: StatusTooLongException) {
editText.error = getString(R.string.error_message_status_too_long)
editText.setSelection(e.exceededStartIndex, editText.length())
editSummary.string = e.summary
editText.string = e.text
if (e.textExceededStartIndex >= 0) {
editText.setSelection(e.textExceededStartIndex, editText.length())
editText.requestFocus()
} else if (e.summaryExceededStartIndex >= 0) {
editSummary.setSelection(e.summaryExceededStartIndex, editSummary.length())
editSummary.requestFocus()
}
return
}
@ -1528,15 +1535,8 @@ class ComposeActivity : BaseActivity(), OnMenuItemClickListener, OnClickListener
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 summary = editSummary.textIfVisible?.normalized(Normalizer.Form.NFC)
val text = editText.text?.normalized(Normalizer.Form.NFC).orEmpty()
val maxLength = statusTextCount.maxLength
val inReplyTo = inReplyToStatus
val replyTextAndMentions = getTwitterReplyTextAndMentions(text, accounts)
@ -1544,10 +1544,15 @@ 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))
val summaryLength = StatusTextValidator.calculateSummaryLength(accounts, summary)
if (summary != null && summaryLength > maxLength) {
throw StatusTooLongException(summary.offsetByCodePoints(0, maxLength),
if (text.isEmpty()) -1 else 0, summary, text)
}
throw StatusTooLongException(-1, replyStartIndex + replyText.offsetByCodePoints(0,
maxLength - summaryLength), summary, text)
}
update.text = replyText
update.extended_reply_mode = true
@ -1559,9 +1564,15 @@ 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))
val summaryLength = StatusTextValidator.calculateSummaryLength(accounts, summary)
if (summary != null && summaryLength > maxLength) {
throw StatusTooLongException(summary.offsetByCodePoints(0, maxLength),
if (text.isEmpty()) -1 else 0, summary, text)
}
throw StatusTooLongException(-1, text.offsetByCodePoints(0,
maxLength - summaryLength), summary, text)
}
update.text = text
update.extended_reply_mode = false
@ -1595,24 +1606,21 @@ class ComposeActivity : BaseActivity(), OnMenuItemClickListener, OnClickListener
if (textAndMentions == null) {
hintLabel.visibility = View.GONE
editable.clearSpans(MentionColorSpan::class.java)
val lengths = StatusTextValidator.calculateLengths(accounts, summary, text,
statusTextCount.textCount = StatusTextValidator.calculateLength(accounts, summary, text,
false, null)
statusTextCount.textCount = lengths.max() ?: 0
} 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)
val lengths = StatusTextValidator.calculateLengths(accounts, summary, textAndMentions.replyText,
false, null)
statusTextCount.textCount = lengths.max() ?: 0
statusTextCount.textCount = StatusTextValidator.calculateLength(accounts, summary,
textAndMentions.replyText, false, null)
} else {
hintLabel.visibility = View.VISIBLE
editable.clearSpans(MentionColorSpan::class.java)
val lengths = StatusTextValidator.calculateLengths(accounts, summary, textAndMentions.replyText,
false, null)
statusTextCount.textCount = lengths.max() ?: 0
statusTextCount.textCount = StatusTextValidator.calculateLength(accounts, summary,
textAndMentions.replyText, false, null)
}
}
@ -2168,7 +2176,13 @@ class ComposeActivity : BaseActivity(), OnMenuItemClickListener, OnClickListener
private open class ComposeException : Exception()
private class StatusTooLongException(val exceededStartIndex: Int) : ComposeException()
private class StatusTooLongException(
val summaryExceededStartIndex: Int,
val textExceededStartIndex: Int,
val summary: String?,
val text: String
) : ComposeException()
private class NoContentException : ComposeException()
private class NoAccountException : ComposeException()

View File

@ -35,7 +35,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
@ -69,7 +68,7 @@ import org.mariotaku.twidere.util.text.TwitterValidator
import java.io.File
import javax.inject.Singleton
@Module()
@Module
class ApplicationModule(private val application: Application) {
init {
@ -195,12 +194,6 @@ class ApplicationModule(private val application: Application) {
return TwidereMediaDownloader(application, client, thumbor)
}
@Provides
@Singleton
fun validator(): Validator {
return TwitterValidator()
}
@Provides
@Singleton
fun extractor(): Extractor {

View File

@ -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.status.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
@ -77,27 +79,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
get() = when (type) {
AccountType.STATUSNET -> {
(this.extras as? StatusNetAccountExtras)?.textLimit ?: 140
}
when (type) {
AccountType.STATUSNET -> {
val extras = this.extras as? StatusNetAccountExtras
if (extras != null) {
return extras.textLimit
}
}
AccountType.MASTODON -> {
val extras = this.extras as? MastodonAccountExtras
if (extras != null) {
return extras.statusTextLimit
}
}
AccountType.MASTODON -> {
(this.extras as? MastodonAccountExtras)?.statusTextLimit ?: MastodonValidator.textLimit
}
return Validator.MAX_TWEET_LENGTH
AccountType.FANFOU -> {
FanfouValidator.textLimit
}
AccountType.TWITTER -> {
TwitterValidator.maxWeightedTweetLength
}
else -> 140
}
val Array<AccountDetails>.textLimit: Int
get() {
var limit = -1

View File

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

View File

@ -28,7 +28,6 @@ import android.view.View
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
@ -66,8 +65,6 @@ open class BaseFragment : Fragment(), IBaseFragment<BaseFragment> {
@Inject
lateinit var errorInfoStore: ErrorInfoStore
@Inject
lateinit var validator: Validator
@Inject
lateinit var extraFeaturesService: ExtraFeaturesService
@Inject
lateinit var permissionsManager: PermissionsManager

View File

@ -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
@ -54,6 +53,7 @@ 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
@ -187,7 +187,7 @@ class RetweetQuoteDialogFragment : AbsStatusDialogFragment() {
positiveButton.isEnabled = status.can_retweet
}
textCountView.textCount = StatusTextValidator.calculateLength(account.type, account.key,
null, text.toString(), false, null)
null, text.toString())
}
private fun DialogInterface.shouldQuoteRetweet(account: AccountDetails): Boolean {
@ -227,8 +227,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))
}
}

View File

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

View File

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

View File

@ -61,7 +61,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
@ -73,7 +72,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.TwitterValidator
import org.mariotaku.twidere.util.text.StatusTextValidator
import java.io.Closeable
import java.io.File
import java.io.FileNotFoundException
@ -241,7 +240,6 @@ class UpdateStatusTask(
update: ParcelableStatusUpdate,
pending: PendingStatusUpdate) {
if (shortener == null) return
val validator = TwitterValidator()
stateCallback.onShorteningStatus()
val sharedShortened = HashMap<UserKey, StatusShortenResult>()
for (i in 0 until pending.length) {
@ -249,8 +247,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()

View File

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

View File

@ -0,0 +1,30 @@
/*
* Twidere - Twitter client for Android
*
* Copyright (C) 2012-2017 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.util.text
object FanfouValidator {
const val textLimit = 140
fun calculateLength(text: String): Int {
return text.codePointCount(0, text.length)
}
}

View File

@ -23,9 +23,12 @@ import org.mariotaku.ktextension.times
import org.mariotaku.twidere.alias.TwitterRegex
class MastodonValidator {
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 {

View File

@ -28,35 +28,52 @@ import org.mariotaku.twidere.model.UserKey
object StatusTextValidator {
private val twitterValidator = TwitterValidator()
private val mastodonValidator = MastodonValidator()
fun calculateLength(@AccountType accountType: String, accountKey: UserKey?, summary: String?,
text: String, ignoreMentions: Boolean = false, inReplyTo: ParcelableStatus? = null) = when (accountType) {
AccountType.TWITTER -> {
TwitterValidator.getTweetLength(text, ignoreMentions, inReplyTo, accountKey)
}
AccountType.MASTODON -> {
MastodonValidator.getCountableLength(summary, text)
}
AccountType.FANFOU -> {
FanfouValidator.calculateLength(text)
}
AccountType.STATUSNET -> {
text.codePointCount(0, text.length)
}
else -> {
text.codePointCount(0, text.length)
}
}
fun calculateLengths(accounts: Array<AccountDetails>, summary: String?, text: String,
ignoreMentions: Boolean, inReplyTo: ParcelableStatus?): IntArray {
ignoreMentions: Boolean = false, inReplyTo: ParcelableStatus? = null): IntArray {
return accounts.mapToIntArray {
calculateLength(it.type, it.key, summary, text, ignoreMentions, inReplyTo)
}
}
fun calculateLength(@AccountType accountType: String, accountKey: UserKey?, summary: String?,
text: String, ignoreMentions: Boolean, inReplyTo: ParcelableStatus?): Int {
when (accountType) {
AccountType.TWITTER -> {
return twitterValidator.getTweetLength(text, ignoreMentions, inReplyTo, accountKey)
}
AccountType.MASTODON -> {
// TODO Algorithm according to https://github.com/tootsuite/mastodon/blob/master/app/validators/status_length_validator.rb
return mastodonValidator.getCountableLength(summary, text)
}
AccountType.FANFOU -> {
return text.codePointCount(0, text.length)
}
AccountType.STATUSNET -> {
return text.codePointCount(0, text.length)
}
else -> {
return text.codePointCount(0, text.length)
}
fun calculateLength(accounts: Array<AccountDetails>, summary: String?, text: String,
ignoreMentions: Boolean = false, inReplyTo: ParcelableStatus? = null): Int {
return calculateLengths(accounts, summary, text, ignoreMentions, inReplyTo).max() ?: 0
}
fun calculateSummaryLength(@AccountType accountType: String,
summary: String?) = when (accountType) {
AccountType.MASTODON -> {
MastodonValidator.getCountableLength(summary, "")
}
else -> 0
}
fun calculateSummaryLengths(accounts: Array<AccountDetails>, summary: String?): IntArray {
return accounts.mapToIntArray {
calculateSummaryLength(it.type, summary)
}
}
fun calculateSummaryLength(accounts: Array<AccountDetails>, summary: String?): Int {
return calculateSummaryLengths(accounts, summary).max() ?: 0
}
}

View File

@ -23,10 +23,11 @@ import com.twitter.Extractor
import com.twitter.Validator
import java.text.Normalizer
class TwitterValidator: Validator() {
val maxWeightedTweetLength: Int = 280
object TwitterValidator : Validator() {
val defaultWeight: Int = 200
const val maxWeightedTweetLength: Int = 280
const val defaultWeight: Int = 200
var ranges: Array<WeightRange> = arrayOf(
WeightRange(0, 4351, 100),