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:
Konrad Pozniak 2024-01-04 17:00:55 +01:00 committed by GitHub
commit 5192fb08a5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
215 changed files with 2813 additions and 1177 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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