upgrade ktlint plugin to 12.0.3 (#4169)
There are some new rules, I think they mostly make sense, except for the max line length which I had to disable because we are over it in a lot of places. --------- Co-authored-by: Goooler <wangzongler@gmail.com>
This commit is contained in:
parent
33cd6fdb98
commit
5192fb08a5
215 changed files with 2813 additions and 1177 deletions
|
|
@ -22,10 +22,22 @@ import java.util.Locale
|
|||
import java.util.TimeZone
|
||||
|
||||
class AbsoluteTimeFormatter @JvmOverloads constructor(private val tz: TimeZone = TimeZone.getDefault()) {
|
||||
private val sameDaySdf = SimpleDateFormat("HH:mm", Locale.getDefault()).apply { this.timeZone = tz }
|
||||
private val sameYearSdf = SimpleDateFormat("dd MMM, HH:mm", Locale.getDefault()).apply { this.timeZone = tz }
|
||||
private val otherYearSdf = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).apply { this.timeZone = tz }
|
||||
private val otherYearCompleteSdf = SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.getDefault()).apply { this.timeZone = tz }
|
||||
private val sameDaySdf = SimpleDateFormat(
|
||||
"HH:mm",
|
||||
Locale.getDefault()
|
||||
).apply { this.timeZone = tz }
|
||||
private val sameYearSdf = SimpleDateFormat("dd MMM, HH:mm", Locale.getDefault()).apply {
|
||||
this.timeZone = tz
|
||||
}
|
||||
private val otherYearSdf = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).apply {
|
||||
this.timeZone = tz
|
||||
}
|
||||
private val otherYearCompleteSdf = SimpleDateFormat(
|
||||
"yyyy-MM-dd HH:mm",
|
||||
Locale.getDefault()
|
||||
).apply {
|
||||
this.timeZone = tz
|
||||
}
|
||||
|
||||
@JvmOverloads
|
||||
fun format(time: Date?, shortFormat: Boolean = true, now: Date = Date()): String {
|
||||
|
|
|
|||
|
|
@ -16,13 +16,13 @@
|
|||
package com.keylesspalace.tusky.util
|
||||
|
||||
import android.util.Base64
|
||||
import java.security.KeyPairGenerator
|
||||
import java.security.SecureRandom
|
||||
import java.security.Security
|
||||
import org.bouncycastle.jce.ECNamedCurveTable
|
||||
import org.bouncycastle.jce.interfaces.ECPrivateKey
|
||||
import org.bouncycastle.jce.interfaces.ECPublicKey
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider
|
||||
import java.security.KeyPairGenerator
|
||||
import java.security.SecureRandom
|
||||
import java.security.Security
|
||||
|
||||
object CryptoUtil {
|
||||
const val CURVE_PRIME256_V1 = "prime256v1"
|
||||
|
|
|
|||
|
|
@ -56,7 +56,13 @@ fun CharSequence.emojify(emojis: List<Emoji>?, view: View, animate: Boolean): Ch
|
|||
builder.setSpan(span, matcher.start(), matcher.end(), 0)
|
||||
Glide.with(view)
|
||||
.asDrawable()
|
||||
.load(if (animate) { url } else { staticUrl })
|
||||
.load(
|
||||
if (animate) {
|
||||
url
|
||||
} else {
|
||||
staticUrl
|
||||
}
|
||||
)
|
||||
.into(span.getTarget(animate))
|
||||
}
|
||||
}
|
||||
|
|
@ -66,7 +72,13 @@ fun CharSequence.emojify(emojis: List<Emoji>?, view: View, animate: Boolean): Ch
|
|||
class EmojiSpan(val viewWeakReference: WeakReference<View>) : ReplacementSpan() {
|
||||
var imageDrawable: Drawable? = null
|
||||
|
||||
override fun getSize(paint: Paint, text: CharSequence, start: Int, end: Int, fm: Paint.FontMetricsInt?): Int {
|
||||
override fun getSize(
|
||||
paint: Paint,
|
||||
text: CharSequence,
|
||||
start: Int,
|
||||
end: Int,
|
||||
fm: Paint.FontMetricsInt?
|
||||
): Int {
|
||||
if (fm != null) {
|
||||
/* update FontMetricsInt or otherwise span does not get drawn when
|
||||
* it covers the whole text */
|
||||
|
|
@ -80,7 +92,17 @@ class EmojiSpan(val viewWeakReference: WeakReference<View>) : ReplacementSpan()
|
|||
return (paint.textSize * 1.2).toInt()
|
||||
}
|
||||
|
||||
override fun draw(canvas: Canvas, text: CharSequence, start: Int, end: Int, x: Float, top: Int, y: Int, bottom: Int, paint: Paint) {
|
||||
override fun draw(
|
||||
canvas: Canvas,
|
||||
text: CharSequence,
|
||||
start: Int,
|
||||
end: Int,
|
||||
x: Float,
|
||||
top: Int,
|
||||
y: Int,
|
||||
bottom: Int,
|
||||
paint: Paint
|
||||
) {
|
||||
imageDrawable?.let { drawable ->
|
||||
canvas.save()
|
||||
|
||||
|
|
|
|||
|
|
@ -6,5 +6,9 @@ import androidx.paging.PagingState
|
|||
class EmptyPagingSource<T : Any> : PagingSource<Int, T>() {
|
||||
override fun getRefreshKey(state: PagingState<Int, T>): Int? = null
|
||||
|
||||
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, T> = LoadResult.Page(emptyList(), null, null)
|
||||
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, T> = LoadResult.Page(
|
||||
emptyList(),
|
||||
null,
|
||||
null
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,11 +17,11 @@
|
|||
|
||||
package com.keylesspalace.tusky.util
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlin.time.Duration
|
||||
import kotlin.time.TimeMark
|
||||
import kotlin.time.TimeSource
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flow
|
||||
|
||||
/**
|
||||
* Returns a flow that mirrors the original flow, but filters out values that occur within
|
||||
|
|
@ -53,15 +53,13 @@ import kotlin.time.TimeSource
|
|||
* @param timeout Emissions within this duration of the last emission are filtered
|
||||
* @param timeSource Used to measure elapsed time. Normally only overridden in tests
|
||||
*/
|
||||
fun <T> Flow<T>.throttleFirst(
|
||||
timeout: Duration,
|
||||
timeSource: TimeSource = TimeSource.Monotonic
|
||||
) = flow {
|
||||
var marker: TimeMark? = null
|
||||
collect {
|
||||
if (marker == null || marker!!.elapsedNow() >= timeout) {
|
||||
emit(it)
|
||||
marker = timeSource.markNow()
|
||||
fun <T> Flow<T>.throttleFirst(timeout: Duration, timeSource: TimeSource = TimeSource.Monotonic) =
|
||||
flow {
|
||||
var marker: TimeMark? = null
|
||||
collect {
|
||||
if (marker == null || marker!!.elapsedNow() >= timeout) {
|
||||
emit(it)
|
||||
marker = timeSource.markNow()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -144,12 +144,7 @@ object FocalPointUtil {
|
|||
* the image. So it won't put the very edge of the image in center, because that would
|
||||
* leave part of the view empty.
|
||||
*/
|
||||
fun focalOffset(
|
||||
view: Float,
|
||||
image: Float,
|
||||
scale: Float,
|
||||
focal: Float
|
||||
): Float {
|
||||
fun focalOffset(view: Float, image: Float, scale: Float, focal: Float): Float {
|
||||
// The fraction of the image that will be in view:
|
||||
val inView = view / (scale * image)
|
||||
var offset = 0f
|
||||
|
|
|
|||
|
|
@ -123,10 +123,7 @@ constructor(
|
|||
* @param relationType of the parameter "rel", commonly "next" or "prev"
|
||||
* @return the link matching the given relation type
|
||||
*/
|
||||
fun findByRelationType(
|
||||
links: List<HttpHeaderLink>,
|
||||
relationType: String
|
||||
): HttpHeaderLink? {
|
||||
fun findByRelationType(links: List<HttpHeaderLink>, relationType: String): HttpHeaderLink? {
|
||||
return links.find { link ->
|
||||
link.parameters.any { parameter ->
|
||||
parameter.name == "rel" && parameter.value == relationType
|
||||
|
|
|
|||
|
|
@ -36,10 +36,7 @@ fun Closeable?.closeQuietly() {
|
|||
}
|
||||
|
||||
@SuppressLint("Recycle") // The linter can't tell that the stream gets closed by a helper method
|
||||
fun Uri.copyToFile(
|
||||
contentResolver: ContentResolver,
|
||||
file: File
|
||||
): Boolean {
|
||||
fun Uri.copyToFile(contentResolver: ContentResolver, file: File): Boolean {
|
||||
val from: InputStream?
|
||||
val to: FileOutputStream
|
||||
|
||||
|
|
|
|||
|
|
@ -71,7 +71,13 @@ fun getDomain(urlString: String?): String {
|
|||
* @param mentions any '@' mentions which are known to be in the content
|
||||
* @param listener to notify about particular spans that are clicked
|
||||
*/
|
||||
fun setClickableText(view: TextView, content: CharSequence, mentions: List<Mention>, tags: List<HashTag>?, listener: LinkListener) {
|
||||
fun setClickableText(
|
||||
view: TextView,
|
||||
content: CharSequence,
|
||||
mentions: List<Mention>,
|
||||
tags: List<HashTag>?,
|
||||
listener: LinkListener
|
||||
) {
|
||||
val spannableContent = markupHiddenUrls(view, content)
|
||||
|
||||
view.text = spannableContent.apply {
|
||||
|
|
@ -93,7 +99,10 @@ fun markupHiddenUrls(view: TextView, content: CharSequence): SpannableStringBuil
|
|||
return@filter if (firstCharacter == '#' || firstCharacter == '@') {
|
||||
false
|
||||
} else {
|
||||
val text = spannableContent.subSequence(start, spannableContent.getSpanEnd(it)).toString()
|
||||
val text = spannableContent.subSequence(
|
||||
start,
|
||||
spannableContent.getSpanEnd(it)
|
||||
).toString()
|
||||
.split(' ').lastOrNull().orEmpty()
|
||||
var textDomain = getDomain(text)
|
||||
if (textDomain.isBlank()) {
|
||||
|
|
@ -107,8 +116,16 @@ fun markupHiddenUrls(view: TextView, content: CharSequence): SpannableStringBuil
|
|||
val start = spannableContent.getSpanStart(span)
|
||||
val end = spannableContent.getSpanEnd(span)
|
||||
val originalText = spannableContent.subSequence(start, end)
|
||||
val replacementText = view.context.getString(R.string.url_domain_notifier, originalText, getDomain(span.url))
|
||||
spannableContent.replace(start, end, replacementText) // this also updates the span locations
|
||||
val replacementText = view.context.getString(
|
||||
R.string.url_domain_notifier,
|
||||
originalText,
|
||||
getDomain(span.url)
|
||||
)
|
||||
spannableContent.replace(
|
||||
start,
|
||||
end,
|
||||
replacementText
|
||||
) // this also updates the span locations
|
||||
|
||||
val linkDrawable = AppCompatResources.getDrawable(view.context, R.drawable.ic_link)!!
|
||||
// ImageSpan does not always align the icon correctly in the line, let's use our custom emoji span for this
|
||||
|
|
@ -162,7 +179,12 @@ fun getTagName(text: CharSequence, tags: List<HashTag>?): String? {
|
|||
}
|
||||
}
|
||||
|
||||
private fun getCustomSpanForTag(text: CharSequence, tags: List<HashTag>?, span: URLSpan, listener: LinkListener): ClickableSpan? {
|
||||
private fun getCustomSpanForTag(
|
||||
text: CharSequence,
|
||||
tags: List<HashTag>?,
|
||||
span: URLSpan,
|
||||
listener: LinkListener
|
||||
): ClickableSpan? {
|
||||
return getTagName(text, tags)?.let {
|
||||
object : NoUnderlineURLSpan(span.url) {
|
||||
override fun onClick(view: View) = listener.onViewTag(it)
|
||||
|
|
@ -170,14 +192,22 @@ private fun getCustomSpanForTag(text: CharSequence, tags: List<HashTag>?, span:
|
|||
}
|
||||
}
|
||||
|
||||
private fun getCustomSpanForMention(mentions: List<Mention>, span: URLSpan, listener: LinkListener): ClickableSpan? {
|
||||
private fun getCustomSpanForMention(
|
||||
mentions: List<Mention>,
|
||||
span: URLSpan,
|
||||
listener: LinkListener
|
||||
): ClickableSpan? {
|
||||
// https://github.com/tuskyapp/Tusky/pull/2339
|
||||
return mentions.firstOrNull { it.url == span.url }?.let {
|
||||
getCustomSpanForMentionUrl(span.url, it.id, listener)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getCustomSpanForMentionUrl(url: String, mentionId: String, listener: LinkListener): ClickableSpan {
|
||||
private fun getCustomSpanForMentionUrl(
|
||||
url: String,
|
||||
mentionId: String,
|
||||
listener: LinkListener
|
||||
): ClickableSpan {
|
||||
return object : MentionSpan(url) {
|
||||
override fun onClick(view: View) = listener.onViewAccount(mentionId)
|
||||
}
|
||||
|
|
@ -264,7 +294,9 @@ fun createClickableText(text: String, link: String): CharSequence {
|
|||
*/
|
||||
fun Context.openLink(url: String) {
|
||||
val uri = url.toUri().normalizeScheme()
|
||||
val useCustomTabs = PreferenceManager.getDefaultSharedPreferences(this).getBoolean("customTabs", false)
|
||||
val useCustomTabs = PreferenceManager.getDefaultSharedPreferences(
|
||||
this
|
||||
).getBoolean("customTabs", false)
|
||||
|
||||
if (useCustomTabs) {
|
||||
openLinkInCustomTab(uri, this)
|
||||
|
|
@ -296,9 +328,21 @@ private fun openLinkInBrowser(uri: Uri?, context: Context) {
|
|||
* @param context context
|
||||
*/
|
||||
fun openLinkInCustomTab(uri: Uri, context: Context) {
|
||||
val toolbarColor = MaterialColors.getColor(context, com.google.android.material.R.attr.colorSurface, Color.BLACK)
|
||||
val navigationbarColor = MaterialColors.getColor(context, android.R.attr.navigationBarColor, Color.BLACK)
|
||||
val navigationbarDividerColor = MaterialColors.getColor(context, R.attr.dividerColor, Color.BLACK)
|
||||
val toolbarColor = MaterialColors.getColor(
|
||||
context,
|
||||
com.google.android.material.R.attr.colorSurface,
|
||||
Color.BLACK
|
||||
)
|
||||
val navigationbarColor = MaterialColors.getColor(
|
||||
context,
|
||||
android.R.attr.navigationBarColor,
|
||||
Color.BLACK
|
||||
)
|
||||
val navigationbarDividerColor = MaterialColors.getColor(
|
||||
context,
|
||||
R.attr.dividerColor,
|
||||
Color.BLACK
|
||||
)
|
||||
val colorSchemeParams = CustomTabColorSchemeParams.Builder()
|
||||
.setToolbarColor(toolbarColor)
|
||||
.setNavigationBarColor(navigationbarColor)
|
||||
|
|
|
|||
|
|
@ -94,11 +94,7 @@ class ListStatusAccessibilityDelegate(
|
|||
}
|
||||
}
|
||||
|
||||
override fun performAccessibilityAction(
|
||||
host: View,
|
||||
action: Int,
|
||||
args: Bundle?
|
||||
): Boolean {
|
||||
override fun performAccessibilityAction(host: View, action: Int, args: Bundle?): Boolean {
|
||||
val pos = recyclerView.getChildAdapterPosition(host)
|
||||
when (action) {
|
||||
R.id.action_reply -> {
|
||||
|
|
@ -114,7 +110,11 @@ class ListStatusAccessibilityDelegate(
|
|||
R.id.action_open_profile -> {
|
||||
interrupt()
|
||||
statusActionListener.onViewAccount(
|
||||
(statusProvider.getStatus(pos) as StatusViewData.Concrete).actionable.account.id
|
||||
(
|
||||
statusProvider.getStatus(
|
||||
pos
|
||||
) as StatusViewData.Concrete
|
||||
).actionable.account.id
|
||||
)
|
||||
}
|
||||
R.id.action_open_media_1 -> {
|
||||
|
|
|
|||
|
|
@ -59,7 +59,10 @@ private fun ensureLanguagesAreFirst(locales: MutableList<Locale>, languages: Lis
|
|||
}
|
||||
}
|
||||
|
||||
fun getInitialLanguages(language: String? = null, activeAccount: AccountEntity? = null): List<String> {
|
||||
fun getInitialLanguages(
|
||||
language: String? = null,
|
||||
activeAccount: AccountEntity? = null
|
||||
): List<String> {
|
||||
val selected = listOfNotNull(language, activeAccount?.defaultPostLanguage)
|
||||
val system = AppCompatDelegate.getApplicationLocales().toList() +
|
||||
LocaleListCompat.getDefault().toList()
|
||||
|
|
|
|||
|
|
@ -165,7 +165,10 @@ fun getImageOrientation(uri: Uri, contentResolver: ContentResolver): Int {
|
|||
inputStream.closeQuietly()
|
||||
return ExifInterface.ORIENTATION_UNDEFINED
|
||||
}
|
||||
val orientation = exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL)
|
||||
val orientation = exifInterface.getAttributeInt(
|
||||
ExifInterface.TAG_ORIENTATION,
|
||||
ExifInterface.ORIENTATION_NORMAL
|
||||
)
|
||||
inputStream.closeQuietly()
|
||||
return orientation
|
||||
}
|
||||
|
|
@ -196,5 +199,8 @@ fun deleteStaleCachedMedia(mediaDirectory: File?) {
|
|||
}
|
||||
|
||||
fun getTemporaryMediaFilename(extension: String): String {
|
||||
return "${MEDIA_TEMP_PREFIX}_${SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(Date())}.$extension"
|
||||
return "${MEDIA_TEMP_PREFIX}_${SimpleDateFormat(
|
||||
"yyyyMMdd_HHmmss",
|
||||
Locale.US
|
||||
).format(Date())}.$extension"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ import androidx.arch.core.util.Function
|
|||
* supplementary one.
|
||||
* @constructor
|
||||
*/
|
||||
class PairedList<T, V> (private val mapper: Function<T, out V>) : AbstractMutableList<T>() {
|
||||
class PairedList<T, V>(private val mapper: Function<T, out V>) : AbstractMutableList<T>() {
|
||||
private val main: MutableList<T> = ArrayList()
|
||||
private val synced: MutableList<V> = ArrayList()
|
||||
|
||||
|
|
|
|||
|
|
@ -2,11 +2,11 @@ package com.keylesspalace.tusky.util
|
|||
|
||||
sealed class Resource<T>(open val data: T?)
|
||||
|
||||
class Loading<T> (override val data: T? = null) : Resource<T>(data)
|
||||
class Loading<T>(override val data: T? = null) : Resource<T>(data)
|
||||
|
||||
class Success<T> (override val data: T? = null) : Resource<T>(data)
|
||||
class Success<T>(override val data: T? = null) : Resource<T>(data)
|
||||
|
||||
class Error<T> (
|
||||
class Error<T>(
|
||||
override val data: T? = null,
|
||||
val errorMessage: String? = null,
|
||||
var consumed: Boolean = false,
|
||||
|
|
|
|||
|
|
@ -57,7 +57,12 @@ fun updateShortcut(context: Context, account: AccountEntity) {
|
|||
val outBmp = Bitmap.createBitmap(outerSize, outerSize, Bitmap.Config.ARGB_8888)
|
||||
|
||||
val canvas = Canvas(outBmp)
|
||||
canvas.drawBitmap(bmp, (outerSize - innerSize).toFloat() / 2f, (outerSize - innerSize).toFloat() / 2f, null)
|
||||
canvas.drawBitmap(
|
||||
bmp,
|
||||
(outerSize - innerSize).toFloat() / 2f,
|
||||
(outerSize - innerSize).toFloat() / 2f,
|
||||
null
|
||||
)
|
||||
|
||||
val icon = IconCompat.createWithAdaptiveBitmap(outBmp)
|
||||
|
||||
|
|
|
|||
|
|
@ -55,7 +55,14 @@ fun shouldTrimStatus(message: Spanned): Boolean {
|
|||
*/
|
||||
object SmartLengthInputFilter : InputFilter {
|
||||
/** {@inheritDoc} */
|
||||
override fun filter(source: CharSequence, start: Int, end: Int, dest: Spanned, dstart: Int, dend: Int): CharSequence? {
|
||||
override fun filter(
|
||||
source: CharSequence,
|
||||
start: Int,
|
||||
end: Int,
|
||||
dest: Spanned,
|
||||
dstart: Int,
|
||||
dend: Int
|
||||
): CharSequence? {
|
||||
// Code originally imported from InputFilter.LengthFilter but heavily customized and converted to Kotlin.
|
||||
// https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/text/InputFilter.java#175
|
||||
|
||||
|
|
|
|||
|
|
@ -35,14 +35,17 @@ private const val HTTPS_URL_REGEX = "(?:(^|\\b)https://[^\\s]+)"
|
|||
/**
|
||||
* Dump of android.util.Patterns.WEB_URL
|
||||
*/
|
||||
private val STRICT_WEB_URL_PATTERN = Pattern.compile("(((?:(?i:http|https|rtsp)://(?:(?:[a-zA-Z0-9\\\$\\-\\_\\.\\+\\!\\*\\'\\(\\)\\,\\;\\?\\&\\=]|(?:\\%[a-fA-F0-9]{2})){1,64}(?:\\:(?:[a-zA-Z0-9\\\$\\-\\_\\.\\+\\!\\*\\'\\(\\)\\,\\;\\?\\&\\=]|(?:\\%[a-fA-F0-9]{2})){1,25})?\\@)?)?(?:(([a-zA-Z0-9[ -\uD7FF豈-\uFDCFﷰ-\uFFEF\uD800\uDC00-\uD83F\uDFFD\uD840\uDC00-\uD87F\uDFFD\uD880\uDC00-\uD8BF\uDFFD\uD8C0\uDC00-\uD8FF\uDFFD\uD900\uDC00-\uD93F\uDFFD\uD940\uDC00-\uD97F\uDFFD\uD980\uDC00-\uD9BF\uDFFD\uD9C0\uDC00-\uD9FF\uDFFD\uDA00\uDC00-\uDA3F\uDFFD\uDA40\uDC00-\uDA7F\uDFFD\uDA80\uDC00-\uDABF\uDFFD\uDAC0\uDC00-\uDAFF\uDFFD\uDB00\uDC00-\uDB3F\uDFFD\uDB44\uDC00-\uDB7F\uDFFD&&[^ [ - ]\u2028\u2029 ]]](?:[a-zA-Z0-9[ -\uD7FF豈-\uFDCFﷰ-\uFFEF\uD800\uDC00-\uD83F\uDFFD\uD840\uDC00-\uD87F\uDFFD\uD880\uDC00-\uD8BF\uDFFD\uD8C0\uDC00-\uD8FF\uDFFD\uD900\uDC00-\uD93F\uDFFD\uD940\uDC00-\uD97F\uDFFD\uD980\uDC00-\uD9BF\uDFFD\uD9C0\uDC00-\uD9FF\uDFFD\uDA00\uDC00-\uDA3F\uDFFD\uDA40\uDC00-\uDA7F\uDFFD\uDA80\uDC00-\uDABF\uDFFD\uDAC0\uDC00-\uDAFF\uDFFD\uDB00\uDC00-\uDB3F\uDFFD\uDB44\uDC00-\uDB7F\uDFFD&&[^ [ - ]\u2028\u2029 ]]_\\-]{0,61}[a-zA-Z0-9[ -\uD7FF豈-\uFDCFﷰ-\uFFEF\uD800\uDC00-\uD83F\uDFFD\uD840\uDC00-\uD87F\uDFFD\uD880\uDC00-\uD8BF\uDFFD\uD8C0\uDC00-\uD8FF\uDFFD\uD900\uDC00-\uD93F\uDFFD\uD940\uDC00-\uD97F\uDFFD\uD980\uDC00-\uD9BF\uDFFD\uD9C0\uDC00-\uD9FF\uDFFD\uDA00\uDC00-\uDA3F\uDFFD\uDA40\uDC00-\uDA7F\uDFFD\uDA80\uDC00-\uDABF\uDFFD\uDAC0\uDC00-\uDAFF\uDFFD\uDB00\uDC00-\uDB3F\uDFFD\uDB44\uDC00-\uDB7F\uDFFD&&[^ [ - ]\u2028\u2029 ]]]){0,1}\\.)+(xn\\-\\-[\\w\\-]{0,58}\\w|[a-zA-Z[ -\uD7FF豈-\uFDCFﷰ-\uFFEF\uD800\uDC00-\uD83F\uDFFD\uD840\uDC00-\uD87F\uDFFD\uD880\uDC00-\uD8BF\uDFFD\uD8C0\uDC00-\uD8FF\uDFFD\uD900\uDC00-\uD93F\uDFFD\uD940\uDC00-\uD97F\uDFFD\uD980\uDC00-\uD9BF\uDFFD\uD9C0\uDC00-\uD9FF\uDFFD\uDA00\uDC00-\uDA3F\uDFFD\uDA40\uDC00-\uDA7F\uDFFD\uDA80\uDC00-\uDABF\uDFFD\uDAC0\uDC00-\uDAFF\uDFFD\uDB00\uDC00-\uDB3F\uDFFD\uDB44\uDC00-\uDB7F\uDFFD&&[^ [ - ]\u2028\u2029 ]]]{2,63})|((25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9])\\.(25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[0-9]))))(?:\\:\\d{1,5})?)([/\\?](?:(?:[a-zA-Z0-9[ -\uD7FF豈-\uFDCFﷰ-\uFFEF\uD800\uDC00-\uD83F\uDFFD\uD840\uDC00-\uD87F\uDFFD\uD880\uDC00-\uD8BF\uDFFD\uD8C0\uDC00-\uD8FF\uDFFD\uD900\uDC00-\uD93F\uDFFD\uD940\uDC00-\uD97F\uDFFD\uD980\uDC00-\uD9BF\uDFFD\uD9C0\uDC00-\uD9FF\uDFFD\uDA00\uDC00-\uDA3F\uDFFD\uDA40\uDC00-\uDA7F\uDFFD\uDA80\uDC00-\uDABF\uDFFD\uDAC0\uDC00-\uDAFF\uDFFD\uDB00\uDC00-\uDB3F\uDFFD\uDB44\uDC00-\uDB7F\uDFFD&&[^ [ - ]\u2028\u2029 ]];/\\?:@&=#~\\-\\.\\+!\\*'\\(\\),_\\\$])|(?:%[a-fA-F0-9]{2}))*)?(?:\\b|\$|^))")
|
||||
private val STRICT_WEB_URL_PATTERN = Pattern.compile(
|
||||
"(((?:(?i:http|https|rtsp)://(?:(?:[a-zA-Z0-9\\\$\\-\\_\\.\\+\\!\\*\\'\\(\\)\\,\\;\\?\\&\\=]|(?:\\%[a-fA-F0-9]{2})){1,64}(?:\\:(?:[a-zA-Z0-9\\\$\\-\\_\\.\\+\\!\\*\\'\\(\\)\\,\\;\\?\\&\\=]|(?:\\%[a-fA-F0-9]{2})){1,25})?\\@)?)?(?:(([a-zA-Z0-9[ -\uD7FF豈-\uFDCFﷰ-\uFFEF\uD800\uDC00-\uD83F\uDFFD\uD840\uDC00-\uD87F\uDFFD\uD880\uDC00-\uD8BF\uDFFD\uD8C0\uDC00-\uD8FF\uDFFD\uD900\uDC00-\uD93F\uDFFD\uD940\uDC00-\uD97F\uDFFD\uD980\uDC00-\uD9BF\uDFFD\uD9C0\uDC00-\uD9FF\uDFFD\uDA00\uDC00-\uDA3F\uDFFD\uDA40\uDC00-\uDA7F\uDFFD\uDA80\uDC00-\uDABF\uDFFD\uDAC0\uDC00-\uDAFF\uDFFD\uDB00\uDC00-\uDB3F\uDFFD\uDB44\uDC00-\uDB7F\uDFFD&&[^ [ - ]\u2028\u2029 ]]](?:[a-zA-Z0-9[ -\uD7FF豈-\uFDCFﷰ-\uFFEF\uD800\uDC00-\uD83F\uDFFD\uD840\uDC00-\uD87F\uDFFD\uD880\uDC00-\uD8BF\uDFFD\uD8C0\uDC00-\uD8FF\uDFFD\uD900\uDC00-\uD93F\uDFFD\uD940\uDC00-\uD97F\uDFFD\uD980\uDC00-\uD9BF\uDFFD\uD9C0\uDC00-\uD9FF\uDFFD\uDA00\uDC00-\uDA3F\uDFFD\uDA40\uDC00-\uDA7F\uDFFD\uDA80\uDC00-\uDABF\uDFFD\uDAC0\uDC00-\uDAFF\uDFFD\uDB00\uDC00-\uDB3F\uDFFD\uDB44\uDC00-\uDB7F\uDFFD&&[^ [ - ]\u2028\u2029 ]]_\\-]{0,61}[a-zA-Z0-9[ -\uD7FF豈-\uFDCFﷰ-\uFFEF\uD800\uDC00-\uD83F\uDFFD\uD840\uDC00-\uD87F\uDFFD\uD880\uDC00-\uD8BF\uDFFD\uD8C0\uDC00-\uD8FF\uDFFD\uD900\uDC00-\uD93F\uDFFD\uD940\uDC00-\uD97F\uDFFD\uD980\uDC00-\uD9BF\uDFFD\uD9C0\uDC00-\uD9FF\uDFFD\uDA00\uDC00-\uDA3F\uDFFD\uDA40\uDC00-\uDA7F\uDFFD\uDA80\uDC00-\uDABF\uDFFD\uDAC0\uDC00-\uDAFF\uDFFD\uDB00\uDC00-\uDB3F\uDFFD\uDB44\uDC00-\uDB7F\uDFFD&&[^ [ - ]\u2028\u2029 ]]]){0,1}\\.)+(xn\\-\\-[\\w\\-]{0,58}\\w|[a-zA-Z[ -\uD7FF豈-\uFDCFﷰ-\uFFEF\uD800\uDC00-\uD83F\uDFFD\uD840\uDC00-\uD87F\uDFFD\uD880\uDC00-\uD8BF\uDFFD\uD8C0\uDC00-\uD8FF\uDFFD\uD900\uDC00-\uD93F\uDFFD\uD940\uDC00-\uD97F\uDFFD\uD980\uDC00-\uD9BF\uDFFD\uD9C0\uDC00-\uD9FF\uDFFD\uDA00\uDC00-\uDA3F\uDFFD\uDA40\uDC00-\uDA7F\uDFFD\uDA80\uDC00-\uDABF\uDFFD\uDAC0\uDC00-\uDAFF\uDFFD\uDB00\uDC00-\uDB3F\uDFFD\uDB44\uDC00-\uDB7F\uDFFD&&[^ [ - ]\u2028\u2029 ]]]{2,63})|((25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9])\\.(25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[0-9]))))(?:\\:\\d{1,5})?)([/\\?](?:(?:[a-zA-Z0-9[ -\uD7FF豈-\uFDCFﷰ-\uFFEF\uD800\uDC00-\uD83F\uDFFD\uD840\uDC00-\uD87F\uDFFD\uD880\uDC00-\uD8BF\uDFFD\uD8C0\uDC00-\uD8FF\uDFFD\uD900\uDC00-\uD93F\uDFFD\uD940\uDC00-\uD97F\uDFFD\uD980\uDC00-\uD9BF\uDFFD\uD9C0\uDC00-\uD9FF\uDFFD\uDA00\uDC00-\uDA3F\uDFFD\uDA40\uDC00-\uDA7F\uDFFD\uDA80\uDC00-\uDABF\uDFFD\uDAC0\uDC00-\uDAFF\uDFFD\uDB00\uDC00-\uDB3F\uDFFD\uDB44\uDC00-\uDB7F\uDFFD&&[^ [ - ]\u2028\u2029 ]];/\\?:@&=#~\\-\\.\\+!\\*'\\(\\),_\\\$])|(?:%[a-fA-F0-9]{2}))*)?(?:\\b|\$|^))"
|
||||
)
|
||||
|
||||
private val spanClasses = listOf(ForegroundColorSpan::class.java, URLSpan::class.java)
|
||||
private val finders = mapOf(
|
||||
FoundMatchType.HTTP_URL to PatternFinder(':', HTTP_URL_REGEX, 5, Character::isWhitespace),
|
||||
FoundMatchType.HTTPS_URL to PatternFinder(':', HTTPS_URL_REGEX, 6, Character::isWhitespace),
|
||||
FoundMatchType.TAG to PatternFinder('#', TAG_REGEX, 1, ::isValidForTagPrefix),
|
||||
FoundMatchType.MENTION to PatternFinder('@', MENTION_REGEX, 1, Character::isWhitespace) // TODO: We also need a proper validator for mentions
|
||||
// TODO: We also need a proper validator for mentions
|
||||
FoundMatchType.MENTION to PatternFinder('@', MENTION_REGEX, 1, Character::isWhitespace)
|
||||
)
|
||||
|
||||
private enum class FoundMatchType {
|
||||
|
|
@ -87,7 +90,12 @@ fun highlightSpans(text: Spannable, colour: Int) {
|
|||
start = found.start
|
||||
end = found.end
|
||||
if (start in 0 until end) {
|
||||
text.setSpan(getSpan(found.matchType, string, colour, start, end), start, end, Spanned.SPAN_INCLUSIVE_EXCLUSIVE)
|
||||
text.setSpan(
|
||||
getSpan(found.matchType, string, colour, start, end),
|
||||
start,
|
||||
end,
|
||||
Spanned.SPAN_INCLUSIVE_EXCLUSIVE
|
||||
)
|
||||
start += finders[found.matchType]!!.searchPrefixWidth
|
||||
}
|
||||
}
|
||||
|
|
@ -181,7 +189,13 @@ private fun findEndOfPattern(string: String, result: FindCharsResult, pattern: P
|
|||
}
|
||||
}
|
||||
|
||||
private fun getSpan(matchType: FoundMatchType, string: String, colour: Int, start: Int, end: Int): CharacterStyle {
|
||||
private fun getSpan(
|
||||
matchType: FoundMatchType,
|
||||
string: String,
|
||||
colour: Int,
|
||||
start: Int,
|
||||
end: Int
|
||||
): CharacterStyle {
|
||||
return when (matchType) {
|
||||
FoundMatchType.HTTP_URL -> NoUnderlineURLSpan(string.substring(start, end))
|
||||
FoundMatchType.HTTPS_URL -> NoUnderlineURLSpan(string.substring(start, end))
|
||||
|
|
|
|||
|
|
@ -53,11 +53,7 @@ data class StatusDisplayOptions(
|
|||
/**
|
||||
* @return a new StatusDisplayOptions adapted to whichever preference changed.
|
||||
*/
|
||||
fun make(
|
||||
preferences: SharedPreferences,
|
||||
key: String,
|
||||
account: AccountEntity
|
||||
) = when (key) {
|
||||
fun make(preferences: SharedPreferences, key: String, account: AccountEntity) = when (key) {
|
||||
PrefKeys.ANIMATE_GIF_AVATARS -> copy(
|
||||
animateAvatars = preferences.getBoolean(key, false)
|
||||
)
|
||||
|
|
@ -91,7 +87,9 @@ data class StatusDisplayOptions(
|
|||
PrefKeys.ALWAYS_OPEN_SPOILER -> copy(
|
||||
openSpoiler = account.alwaysOpenSpoiler
|
||||
)
|
||||
else -> { this }
|
||||
else -> {
|
||||
this
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
|
|
|||
|
|
@ -68,7 +68,9 @@ class StatusViewHelper(private val itemView: View) {
|
|||
itemView.findViewById(R.id.status_media_overlay_3)
|
||||
)
|
||||
|
||||
val sensitiveMediaWarning = itemView.findViewById<TextView>(R.id.status_sensitive_media_warning)
|
||||
val sensitiveMediaWarning = itemView.findViewById<TextView>(
|
||||
R.id.status_sensitive_media_warning
|
||||
)
|
||||
val sensitiveMediaShow = itemView.findViewById<View>(R.id.status_sensitive_media_button)
|
||||
val mediaLabel = itemView.findViewById<TextView>(R.id.status_media_label)
|
||||
if (statusDisplayOptions.mediaPreviewEnabled) {
|
||||
|
|
@ -86,7 +88,10 @@ class StatusViewHelper(private val itemView: View) {
|
|||
return
|
||||
}
|
||||
|
||||
val mediaPreviewUnloaded = ColorDrawable(MaterialColors.getColor(context, R.attr.colorBackgroundAccent, Color.BLACK))
|
||||
val mediaPreviewUnloaded =
|
||||
ColorDrawable(
|
||||
MaterialColors.getColor(context, R.attr.colorBackgroundAccent, Color.BLACK)
|
||||
)
|
||||
|
||||
val n = min(attachments.size, Status.MAX_MEDIA_ATTACHMENTS)
|
||||
|
||||
|
|
@ -246,7 +251,9 @@ class StatusViewHelper(private val itemView: View) {
|
|||
private fun getLabelTypeText(context: Context, type: Attachment.Type): String {
|
||||
return when (type) {
|
||||
Attachment.Type.IMAGE -> context.getString(R.string.post_media_images)
|
||||
Attachment.Type.GIFV, Attachment.Type.VIDEO -> context.getString(R.string.post_media_video)
|
||||
Attachment.Type.GIFV, Attachment.Type.VIDEO -> context.getString(
|
||||
R.string.post_media_video
|
||||
)
|
||||
Attachment.Type.AUDIO -> context.getString(R.string.post_media_audio)
|
||||
else -> context.getString(R.string.post_media_attachments)
|
||||
}
|
||||
|
|
@ -262,7 +269,11 @@ class StatusViewHelper(private val itemView: View) {
|
|||
}
|
||||
}
|
||||
|
||||
fun setupPollReadonly(poll: PollViewData?, emojis: List<Emoji>, statusDisplayOptions: StatusDisplayOptions) {
|
||||
fun setupPollReadonly(
|
||||
poll: PollViewData?,
|
||||
emojis: List<Emoji>,
|
||||
statusDisplayOptions: StatusDisplayOptions
|
||||
) {
|
||||
val pollResults = listOf<TextView>(
|
||||
itemView.findViewById(R.id.status_poll_option_result_0),
|
||||
itemView.findViewById(R.id.status_poll_option_result_1),
|
||||
|
|
@ -287,7 +298,12 @@ class StatusViewHelper(private val itemView: View) {
|
|||
}
|
||||
}
|
||||
|
||||
private fun getPollInfoText(timestamp: Long, poll: PollViewData, pollDescription: TextView, useAbsoluteTime: Boolean): CharSequence {
|
||||
private fun getPollInfoText(
|
||||
timestamp: Long,
|
||||
poll: PollViewData,
|
||||
pollDescription: TextView,
|
||||
useAbsoluteTime: Boolean
|
||||
): CharSequence {
|
||||
val context = pollDescription.context
|
||||
|
||||
val votesText = if (poll.votersCount == null) {
|
||||
|
|
@ -301,7 +317,10 @@ class StatusViewHelper(private val itemView: View) {
|
|||
context.getString(R.string.poll_info_closed)
|
||||
} else {
|
||||
if (useAbsoluteTime) {
|
||||
context.getString(R.string.poll_info_time_absolute, absoluteTimeFormatter.format(poll.expiresAt, false))
|
||||
context.getString(
|
||||
R.string.poll_info_time_absolute,
|
||||
absoluteTimeFormatter.format(poll.expiresAt, false)
|
||||
)
|
||||
} else {
|
||||
formatPollDuration(context, poll.expiresAt!!.time, timestamp)
|
||||
}
|
||||
|
|
@ -310,14 +329,26 @@ class StatusViewHelper(private val itemView: View) {
|
|||
return context.getString(R.string.poll_info_format, votesText, pollDurationInfo)
|
||||
}
|
||||
|
||||
private fun setupPollResult(poll: PollViewData, emojis: List<Emoji>, pollResults: List<TextView>, animateEmojis: Boolean) {
|
||||
private fun setupPollResult(
|
||||
poll: PollViewData,
|
||||
emojis: List<Emoji>,
|
||||
pollResults: List<TextView>,
|
||||
animateEmojis: Boolean
|
||||
) {
|
||||
val options = poll.options
|
||||
|
||||
for (i in 0 until Status.MAX_POLL_OPTIONS) {
|
||||
if (i < options.size) {
|
||||
val percent = calculatePercent(options[i].votesCount, poll.votersCount, poll.votesCount)
|
||||
val percent =
|
||||
calculatePercent(options[i].votesCount, poll.votersCount, poll.votesCount)
|
||||
|
||||
val pollOptionText = buildDescription(options[i].title, percent, options[i].voted, pollResults[i].context)
|
||||
val pollOptionText =
|
||||
buildDescription(
|
||||
options[i].title,
|
||||
percent,
|
||||
options[i].voted,
|
||||
pollResults[i].context
|
||||
)
|
||||
pollResults[i].text = pollOptionText.emojify(emojis, pollResults[i], animateEmojis)
|
||||
pollResults[i].visibility = View.VISIBLE
|
||||
|
||||
|
|
|
|||
|
|
@ -2,10 +2,10 @@ package com.keylesspalace.tusky.util
|
|||
|
||||
import android.content.Context
|
||||
import com.keylesspalace.tusky.R
|
||||
import java.io.IOException
|
||||
import org.json.JSONException
|
||||
import org.json.JSONObject
|
||||
import retrofit2.HttpException
|
||||
import java.io.IOException
|
||||
|
||||
/**
|
||||
* checks if this throwable indicates an error causes by a 4xx/5xx server response and
|
||||
|
|
|
|||
|
|
@ -42,11 +42,15 @@ class FragmentViewBindingDelegate<T : ViewBinding>(
|
|||
}
|
||||
|
||||
override fun onCreate(owner: LifecycleOwner) {
|
||||
fragment.viewLifecycleOwnerLiveData.observeForever(viewLifecycleOwnerLiveDataObserver)
|
||||
fragment.viewLifecycleOwnerLiveData.observeForever(
|
||||
viewLifecycleOwnerLiveDataObserver
|
||||
)
|
||||
}
|
||||
|
||||
override fun onDestroy(owner: LifecycleOwner) {
|
||||
fragment.viewLifecycleOwnerLiveData.removeObserver(viewLifecycleOwnerLiveDataObserver)
|
||||
fragment.viewLifecycleOwnerLiveData.removeObserver(
|
||||
viewLifecycleOwnerLiveDataObserver
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
@ -59,7 +63,9 @@ class FragmentViewBindingDelegate<T : ViewBinding>(
|
|||
|
||||
val lifecycle = fragment.viewLifecycleOwner.lifecycle
|
||||
if (!lifecycle.currentState.isAtLeast(Lifecycle.State.INITIALIZED)) {
|
||||
throw IllegalStateException("Should not attempt to get bindings when Fragment views are destroyed.")
|
||||
throw IllegalStateException(
|
||||
"Should not attempt to get bindings when Fragment views are destroyed."
|
||||
)
|
||||
}
|
||||
|
||||
return viewBindingFactory(thisRef.requireView()).also { this.binding = it }
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@
|
|||
*
|
||||
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
package com.keylesspalace.tusky.util
|
||||
|
||||
import androidx.paging.CombinedLoadStates
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue