From e8222fa2d832be14f86b34ed01cf4d38aa4b43b9 Mon Sep 17 00:00:00 2001 From: tateisu Date: Sat, 8 May 2021 14:56:34 +0900 Subject: [PATCH] - update translators list in ActAbout - Android gradle plugin 4.2.0 - Gradle 6.9 - kotlin 1.5.0 - kotlinx.coroutines 1.4.3 - junit 4.13.2 - firebase-messaging:21.1.0 - androidx.annotation:1.2.0 - androidx.recyclerview:1.2.0 - exoplayer:2.13.3 --- .../main/java/jp/juggler/apng/ApngPalette.kt | 3 +- .../main/java/jp/juggler/apng/GifDecoder.kt | 15 +- .../main/java/jp/juggler/apng/IdatDecoder.kt | 1080 +++++++++-------- .../jp/juggler/apng/util/ByteSequenceQueue.kt | 5 +- .../main/java/jp/juggler/apng/ApngFrames.kt | 23 +- app/build.gradle | 11 +- .../juggler/subwaytooter/WordTrieTreeTest.kt | 24 +- app/src/main/AndroidManifest.xml | 1 + .../java/jp/juggler/subwaytooter/ActAbout.kt | 261 ++-- .../juggler/subwaytooter/ActAccountSetting.kt | 4 +- .../java/jp/juggler/subwaytooter/ActPost.kt | 4 +- .../juggler/subwaytooter/action/Action_App.kt | 1 - .../subwaytooter/action/Action_Filter.kt | 1 - .../subwaytooter/action/Action_Follow.kt | 1 - .../subwaytooter/action/Action_ListMember.kt | 1 - .../juggler/subwaytooter/api/entity/Acct.kt | 2 +- .../subwaytooter/api/entity/TootInstance.kt | 2 +- .../subwaytooter/api/entity/TootPolls.kt | 2 +- .../subwaytooter/api/entity/TootTag.kt | 2 +- .../subwaytooter/dialog/AccountPicker.kt | 304 +++-- .../subwaytooter/dialog/EmojiPicker.kt | 2 +- .../juggler/subwaytooter/dialog/LoginForm.kt | 4 +- .../notification/PollingForegrounder.kt | 2 +- .../subwaytooter/notification/TaskRunner.kt | 6 +- .../juggler/subwaytooter/table/AcctColor.kt | 4 +- .../table/NotificationTracking.kt | 1 - .../juggler/subwaytooter/util/EmojiDecoder.kt | 24 +- .../juggler/subwaytooter/util/HTMLDecoder.kt | 10 +- .../util/MisskeyMarkdownDecoder.kt | 27 +- .../juggler/subwaytooter/util/PostHelper.kt | 4 +- .../juggler/subwaytooter/view/BlurhashView.kt | 8 +- .../java/jp/juggler/util/CharacterGroup.kt | 14 +- app/src/main/java/jp/juggler/util/Json.kt | 4 +- .../main/java/jp/juggler/util/StorageUtils.kt | 2 +- .../main/java/jp/juggler/util/StringUtils.kt | 4 +- .../main/java/jp/juggler/util/WordTrieTree.kt | 4 +- .../juggler/subwaytooter/TestKotlinFeature.kt | 6 +- build.gradle | 11 +- .../main/java/jp/juggler/emoji/EmojiMap.kt | 2 +- .../main/java/jp/juggler/emoji/EmojiTrie.kt | 6 +- gradle/wrapper/gradle-wrapper.properties | 2 +- sample_apng/build.gradle | 3 + .../java/jp/juggler/apng/sample/ActList.kt | 400 +++--- .../java/jp/juggler/apng/sample/ApngView.kt | 11 +- sample_apng/src/main/res/layout/act_list.xml | 1 - 45 files changed, 1170 insertions(+), 1139 deletions(-) diff --git a/apng/src/main/java/jp/juggler/apng/ApngPalette.kt b/apng/src/main/java/jp/juggler/apng/ApngPalette.kt index b5454436..f7fe3af2 100644 --- a/apng/src/main/java/jp/juggler/apng/ApngPalette.kt +++ b/apng/src/main/java/jp/juggler/apng/ApngPalette.kt @@ -3,6 +3,7 @@ package jp.juggler.apng import jp.juggler.apng.util.getUInt8 +import kotlin.math.min class ApngPalette( src : ByteArray // repeat of R,G,B @@ -35,7 +36,7 @@ class ApngPalette( // update alpha value from tRNS chunk data fun parseTRNS(ba : ByteArray) { hasAlpha = true - for(i in 0 until Math.min(list.size, ba.size)) { + for(i in 0 until min(list.size, ba.size)) { list[i] = (list[i] and 0xffffff) or (ba.getUInt8(i) shl 24) } } diff --git a/apng/src/main/java/jp/juggler/apng/GifDecoder.kt b/apng/src/main/java/jp/juggler/apng/GifDecoder.kt index 13189a6a..86d29855 100644 --- a/apng/src/main/java/jp/juggler/apng/GifDecoder.kt +++ b/apng/src/main/java/jp/juggler/apng/GifDecoder.kt @@ -1,6 +1,7 @@ package jp.juggler.apng import java.io.InputStream +import java.lang.StringBuilder import kotlin.math.min // https://raw.githubusercontent.com/rtyley/animated-gif-lib-for-java/master/src/main/java/com/madgag/gif/fmsware/GifDecoder.java @@ -44,9 +45,11 @@ class GifDecoder(val callback : GifDecoderCallback) { // Reads specified bytes and compose it to ascii string fun string(n : Int) : String { - val ba = ByteArray(n) - array(ba) - return ba.map { it.toChar() }.joinToString(separator = "") + return StringBuilder(n).apply{ + ByteArray(n) + .also{ array(it)} + .forEach { append( Char( it.toInt() and 255)) } + }.toString() } // Reads next variable length block @@ -478,11 +481,11 @@ class GifDecoder(val callback : GifDecoderCallback) { // application extension 0xff -> { val block = reader.block() - var app = "" + val app = StringBuilder(12) for(i in 0 until 11) { - app += block[i].toChar() + app.append( Char( block[i].toInt() and 255 )) } - if(app == "NETSCAPE2.0") { + if(app.toString() == "NETSCAPE2.0") { readNetscapeExt(reader) } else { reader.skipBlock() // don't care diff --git a/apng/src/main/java/jp/juggler/apng/IdatDecoder.kt b/apng/src/main/java/jp/juggler/apng/IdatDecoder.kt index a9103277..ded1d9a7 100644 --- a/apng/src/main/java/jp/juggler/apng/IdatDecoder.kt +++ b/apng/src/main/java/jp/juggler/apng/IdatDecoder.kt @@ -6,547 +6,551 @@ import jp.juggler.apng.util.* import java.io.InputStream import java.util.zip.CRC32 import java.util.zip.Inflater +import kotlin.math.abs +import kotlin.math.min internal class IdatDecoder( - apng : Apng, - private val bitmap : ApngBitmap, - private val inflateBufferPool : BufferPool, - private val callback : ApngDecoderCallback, - private val onCompleted : () -> Unit + apng: Apng, + private val bitmap: ApngBitmap, + private val inflateBufferPool: BufferPool, + private val callback: ApngDecoderCallback, + private val onCompleted: () -> Unit ) { - - private class PassInfo(val xStep : Int, val xStart : Int, val yStep : Int, val yStart : Int) - - companion object { - - private val passInfoList = listOf( - PassInfo(8, 0, 8, 0), // [0] Adam7 pass 1 - PassInfo(8, 4, 8, 0), // [1] Adam7 pass 2 - PassInfo(4, 0, 8, 4), // [2] Adam7 pass 3 - PassInfo(4, 2, 4, 0), // [3] Adam7 pass 4 - PassInfo(2, 0, 4, 2), // [4] Adam7 pass 5 - PassInfo(2, 1, 2, 0), // [5] Adam7 pass 6 - PassInfo(1, 0, 2, 1), // [6] Adam7 pass 7 - PassInfo(1, 0, 1, 0) // [7] no interlacing - ) - - private val dummyPaletteData = IntArray(0) - - // a = left, b = above, c = upper left - private fun paeth(a : Int, b : Int, c : Int) : Int { - val p = a + b - c - val pa = Math.abs(p - a) - val pb = Math.abs(p - b) - val pc = Math.abs(p - c) - return when { - (pa <= pb && pa <= pc) -> a - (pb <= pc) -> b - else -> c - } - } - - private inline fun scanLine1(baLine : ByteArray, pass_w : Int, block : (v : Int) -> Unit) { - var pos = 1 - var remain = pass_w - while(remain >= 8) { - remain -= 8 - val v = baLine[pos ++].toInt() - block((v shr 7) and 1) - block((v shr 6) and 1) - block((v shr 5) and 1) - block((v shr 4) and 1) - block((v shr 3) and 1) - block((v shr 2) and 1) - block((v shr 1) and 1) - block(v and 1) - } - if(remain > 0) { - val v = baLine[pos].toInt() - block((v shr 7) and 1) - if(remain > 1) block((v shr 6) and 1) - if(remain > 2) block((v shr 5) and 1) - if(remain > 3) block((v shr 4) and 1) - if(remain > 4) block((v shr 3) and 1) - if(remain > 5) block((v shr 2) and 1) - if(remain > 6) block((v shr 1) and 1) - } - } - - private inline fun scanLine2(baLine : ByteArray, pass_w : Int, block : (v : Int) -> Unit) { - var pos = 1 - var remain = pass_w - while(remain >= 4) { - remain -= 4 - val v = baLine[pos ++].toInt() - block((v shr 6) and 3) - block((v shr 4) and 3) - block((v shr 2) and 3) - block(v and 3) - } - if(remain > 0) { - val v = baLine[pos].toInt() - block((v shr 6) and 3) - if(remain > 1) block((v shr 4) and 3) - if(remain > 2) block((v shr 2) and 3) - } - } - - private inline fun scanLine4(baLine : ByteArray, pass_w : Int, block : (v : Int) -> Unit) { - var pos = 1 - var remain = pass_w - while(remain >= 2) { - remain -= 2 - val v = baLine[pos ++].toInt() - block((v shr 4) and 15) - block(v and 15) - } - if(remain > 0) { - val v = baLine[pos].toInt() - block((v shr 4) and 15) - } - } - - private inline fun scanLine8(baLine : ByteArray, pass_w : Int, block : (v : Int) -> Unit) { - var pos = 1 - var remain = pass_w - while(remain -- > 0) { - block(baLine.getUInt8(pos ++)) - } - } - - } - - private val inflater = Inflater() - private val inflateBufferQueue = ByteSequenceQueue { inflateBufferPool.recycle(it.array) } - private val colorType : ColorType - private val bitDepth : Int - private val paletteData : IntArray - private val sampleBits : Int - private val sampleBytes : Int - private val scanLineBytesMax : Int - private val scanLinePool : BufferPool - private val transparentCheckerGrey : (v : Int) -> Int - private val transparentCheckerRGB : (r : Int, g : Int, b : Int) -> Int - private val renderScanLineFunc : (baLine : ByteArray) -> Unit - private val bitmapPointer = bitmap.pointer() - - private var pass : Int - private lateinit var passInfo : PassInfo - private var passWidth : Int = 0 - private var passHeight : Int = 0 - private var passY : Int = 0 - private var scanLineBytes : Int = 0 - private var baPreviousLine : ByteArray? = null - private var isCompleted = false - - init { - val header = requireNotNull(apng.header) - colorType = header.colorType - bitDepth = header.bitDepth - - sampleBits = when(colorType) { - ColorType.GREY, ColorType.INDEX -> bitDepth - ColorType.GREY_ALPHA -> bitDepth * 2 - ColorType.RGB -> bitDepth * 3 - ColorType.RGBA -> bitDepth * 4 - } - - sampleBytes = (sampleBits + 7) / 8 - scanLineBytesMax = 1 + (bitmap.width * sampleBits + 7) / 8 - scanLinePool = BufferPool(scanLineBytesMax) - - paletteData = if(colorType == ColorType.INDEX) { - apng.palette?.list - ?: throw ApngParseError("missing ApngPalette for index color") - } else { - dummyPaletteData - } - - val transparentColor = apng.transparentColor - - transparentCheckerGrey = if(transparentColor != null) { - { v : Int -> if(transparentColor.match(v)) 0 else 255 } - } else { - { _ : Int -> 255 } - } - - transparentCheckerRGB = if(transparentColor != null) { - { r : Int, g : Int, b : Int -> if(transparentColor.match(r, g, b)) 0 else 255 } - } else { - { _ : Int, _ : Int, _ : Int -> 255 } - } - - renderScanLineFunc = selectRenderFunc() - - pass = when(header.interlaceMethod) { - InterlaceMethod.Standard -> 0 - InterlaceMethod.None -> 7 - } - - initializePass() - } - - private fun renderIndex1(baLine : ByteArray) { - scanLine1(baLine, passWidth) { v -> - bitmapPointer.setPixel(paletteData[v]).next() - } - } - - private fun renderIndex2(baLine : ByteArray) { - scanLine2(baLine, passWidth) { v -> - bitmapPointer.setPixel(paletteData[v]).next() - } - } - - private fun renderIndex4(baLine : ByteArray) { - scanLine4(baLine, passWidth) { v -> - bitmapPointer.setPixel(paletteData[v]).next() - } - } - - private fun renderIndex8(baLine : ByteArray) { - scanLine8(baLine, passWidth) { v -> - bitmapPointer.setPixel(paletteData[v]).next() - } - } - - private fun renderGrey1(baLine : ByteArray) { - scanLine1(baLine, passWidth) { v -> - val g8 = if(v == 0) 0 else 255 - val a8 = transparentCheckerGrey(v) - bitmapPointer.setPixel(a8, g8, g8, g8).next() - } - } - - private fun renderGrey2(baLine : ByteArray) { - scanLine2(baLine, passWidth) { v -> - val g8 = v or (v shl 2) or (v shl 4) or (v shl 6) - val a8 = transparentCheckerGrey(v) - bitmapPointer.setPixel(a8, g8, g8, g8).next() - } - } - - private fun renderGrey4(baLine : ByteArray) { - scanLine4(baLine, passWidth) { v -> - val g8 = v or (v shl 4) - val a8 = transparentCheckerGrey(v) - bitmapPointer.setPixel(a8, g8, g8, g8).next() - } - } - - private fun renderGrey8(baLine : ByteArray) { - scanLine8(baLine, passWidth) { v -> - val a8 = transparentCheckerGrey(v) - bitmapPointer.setPixel(a8, v, v, v).next() - } - } - - private fun renderGrey16(baLine : ByteArray) { - var pos = 1 - var remain = passWidth - while(remain -- > 0) { - val v = baLine.getUInt16(pos) - pos += 2 - val g8 = v shr 8 - val a8 = transparentCheckerGrey(v) - bitmapPointer.setPixel(a8, g8, g8, g8).next() - } - } - - private fun renderGA8(baLine : ByteArray) { - var pos = 1 - var remain = passWidth - while(remain -- > 0) { - val g = baLine.getUInt8(pos) - val a = baLine.getUInt8(pos + 1) - pos += 2 - bitmapPointer.setPixel(a, g, g, g).next() - } - } - - private fun renderGA16(baLine : ByteArray) { - var pos = 1 - var remain = passWidth - while(remain -- > 0) { - val g8 = baLine.getUInt16(pos) shr 8 - val a8 = baLine.getUInt16(pos + 2) shr 8 - pos += 4 - bitmapPointer.setPixel(a8, g8, g8, g8).next() - } - } - - private fun renderRGB8(baLine : ByteArray) { - var pos = 1 - var remain = passWidth - while(remain -- > 0) { - val r = baLine.getUInt8(pos) - val g = baLine.getUInt8(pos + 1) - val b = baLine.getUInt8(pos + 2) - pos += 3 - val a8 = transparentCheckerRGB(r, g, b) - bitmapPointer.setPixel(a8, r, g, b).next() - } - } - - private fun renderRGB16(baLine : ByteArray) { - var pos = 1 - var remain = passWidth - while(remain -- > 0) { - val r = baLine.getUInt16(pos) - val g = baLine.getUInt16(pos + 2) - val b = baLine.getUInt16(pos + 4) - pos += 6 - val a8 = transparentCheckerRGB(r, g, b) - bitmapPointer.setPixel(a8, r shr 8, g shr 8, b shr 8).next() - } - } - - private fun renderRGBA8(baLine : ByteArray) { - var pos = 1 - var remain = passWidth - while(remain -- > 0) { - val r = baLine.getUInt8(pos) - val g = baLine.getUInt8(pos + 1) - val b = baLine.getUInt8(pos + 2) - val a = baLine.getUInt8(pos + 3) - pos += 4 - bitmapPointer.setPixel(a, r, g, b).next() - } - } - - private fun renderRGBA16(baLine : ByteArray) { - var pos = 1 - var remain = passWidth - while(remain -- > 0) { - val r = baLine.getUInt16(pos) - val g = baLine.getUInt16(pos + 2) - val b = baLine.getUInt16(pos + 4) - val a = baLine.getUInt16(pos + 6) - pos += 8 - bitmapPointer.setPixel(a shr 8, r shr 8, g shr 8, b shr 8).next() - } - } - - private fun colorBitsNotSupported() : Nothing { - throw ApngParseError("bitDepth $bitDepth is not supported for $colorType") - } - - private fun selectRenderFunc() = when(colorType) { - ColorType.GREY -> when(bitDepth) { - 1 -> ::renderGrey1 - 2 -> ::renderGrey2 - 4 -> ::renderGrey4 - 8 -> ::renderGrey8 - 16 -> ::renderGrey16 - else -> colorBitsNotSupported() - } - ColorType.RGB -> when(bitDepth) { - 8 -> ::renderRGB8 - 16 -> ::renderRGB16 - else -> colorBitsNotSupported() - } - ColorType.INDEX -> when(bitDepth) { - 1 -> ::renderIndex1 - 2 -> ::renderIndex2 - 4 -> ::renderIndex4 - 8 -> ::renderIndex8 - else -> colorBitsNotSupported() - } - ColorType.GREY_ALPHA -> when(bitDepth) { - 8 -> ::renderGA8 - 16 -> ::renderGA16 - else -> colorBitsNotSupported() - } - ColorType.RGBA -> when(bitDepth) { - 8 -> ::renderRGBA8 - 16 -> ::renderRGBA16 - else -> colorBitsNotSupported() - } - } - - private fun initializePass() { - passInfo = passInfoList[pass] - passWidth = (bitmap.width + passInfo.xStep - passInfo.xStart - 1) / passInfo.xStep - passHeight = (bitmap.height + passInfo.yStep - passInfo.yStart - 1) / passInfo.yStep - passY = 0 - scanLineBytes = 1 + (passWidth * sampleBits + 7) / 8 - - scanLinePool.recycle(baPreviousLine) - baPreviousLine = null - - if(passWidth <= 0 || passHeight <= 0) { - if(callback.canApngDebug()) callback.onApngDebug("pass $pass is empty. size=${passWidth}x$passHeight ") - incrementPassOrComplete() - } - } - - private fun incrementPassOrComplete() { - if(pass < 6) { - ++ pass - initializePass() - } else if(! isCompleted) { - isCompleted = true - onCompleted() - } - } - - // スキャンラインを読む。行を処理したらtrueを返す - private fun readScanLine() : Boolean { - - if(inflateBufferQueue.remain < scanLineBytes) { - // not yet enough data to process scanline - return false - } - - val baLine = scanLinePool.obtain() - inflateBufferQueue.readBytes(baLine, 0, scanLineBytes) - - val filterNum = baLine.getUInt8(0) - val filterType = FilterType.values().first { it.num == filterNum } - + + private class PassInfo(val xStep: Int, val xStart: Int, val yStep: Int, val yStart: Int) + + companion object { + + private val passInfoList = listOf( + PassInfo(8, 0, 8, 0), // [0] Adam7 pass 1 + PassInfo(8, 4, 8, 0), // [1] Adam7 pass 2 + PassInfo(4, 0, 8, 4), // [2] Adam7 pass 3 + PassInfo(4, 2, 4, 0), // [3] Adam7 pass 4 + PassInfo(2, 0, 4, 2), // [4] Adam7 pass 5 + PassInfo(2, 1, 2, 0), // [5] Adam7 pass 6 + PassInfo(1, 0, 2, 1), // [6] Adam7 pass 7 + PassInfo(1, 0, 1, 0) // [7] no interlacing + ) + + private val dummyPaletteData = IntArray(0) + + // a = left, b = above, c = upper left + private fun paeth(a: Int, b: Int, c: Int): Int { + val p = a + b - c + val pa = abs(p - a) + val pb = abs(p - b) + val pc = abs(p - c) + return when { + (pa <= pb && pa <= pc) -> a + (pb <= pc) -> b + else -> c + } + } + + private inline fun scanLine1(baLine: ByteArray, pass_w: Int, block: (v: Int) -> Unit) { + var pos = 1 + var remain = pass_w + while (remain >= 8) { + remain -= 8 + val v = baLine[pos++].toInt() + block((v shr 7) and 1) + block((v shr 6) and 1) + block((v shr 5) and 1) + block((v shr 4) and 1) + block((v shr 3) and 1) + block((v shr 2) and 1) + block((v shr 1) and 1) + block(v and 1) + } + if (remain > 0) { + val v = baLine[pos].toInt() + block((v shr 7) and 1) + if (remain > 1) block((v shr 6) and 1) + if (remain > 2) block((v shr 5) and 1) + if (remain > 3) block((v shr 4) and 1) + if (remain > 4) block((v shr 3) and 1) + if (remain > 5) block((v shr 2) and 1) + if (remain > 6) block((v shr 1) and 1) + } + } + + private inline fun scanLine2(baLine: ByteArray, pass_w: Int, block: (v: Int) -> Unit) { + var pos = 1 + var remain = pass_w + while (remain >= 4) { + remain -= 4 + val v = baLine[pos++].toInt() + block((v shr 6) and 3) + block((v shr 4) and 3) + block((v shr 2) and 3) + block(v and 3) + } + if (remain > 0) { + val v = baLine[pos].toInt() + block((v shr 6) and 3) + if (remain > 1) block((v shr 4) and 3) + if (remain > 2) block((v shr 2) and 3) + } + } + + private inline fun scanLine4(baLine: ByteArray, pass_w: Int, block: (v: Int) -> Unit) { + var pos = 1 + var remain = pass_w + while (remain >= 2) { + remain -= 2 + val v = baLine[pos++].toInt() + block((v shr 4) and 15) + block(v and 15) + } + if (remain > 0) { + val v = baLine[pos].toInt() + block((v shr 4) and 15) + } + } + + private inline fun scanLine8(baLine: ByteArray, pass_w: Int, block: (v: Int) -> Unit) { + var pos = 1 + var remain = pass_w + while (remain-- > 0) { + block(baLine.getUInt8(pos++)) + } + } + + } + + private val inflater = Inflater() + private val inflateBufferQueue = ByteSequenceQueue { inflateBufferPool.recycle(it.array) } + private val colorType: ColorType + private val bitDepth: Int + private val paletteData: IntArray + private val sampleBits: Int + private val sampleBytes: Int + private val scanLineBytesMax: Int + private val scanLinePool: BufferPool + private val transparentCheckerGrey: (v: Int) -> Int + private val transparentCheckerRGB: (r: Int, g: Int, b: Int) -> Int + private val renderScanLineFunc: (baLine: ByteArray) -> Unit + private val bitmapPointer = bitmap.pointer() + + private var pass: Int + private lateinit var passInfo: PassInfo + private var passWidth: Int = 0 + private var passHeight: Int = 0 + private var passY: Int = 0 + private var scanLineBytes: Int = 0 + private var baPreviousLine: ByteArray? = null + private var isCompleted = false + + init { + val header = requireNotNull(apng.header) + colorType = header.colorType + bitDepth = header.bitDepth + + sampleBits = when (colorType) { + ColorType.GREY, ColorType.INDEX -> bitDepth + ColorType.GREY_ALPHA -> bitDepth * 2 + ColorType.RGB -> bitDepth * 3 + ColorType.RGBA -> bitDepth * 4 + } + + sampleBytes = (sampleBits + 7) / 8 + scanLineBytesMax = 1 + (bitmap.width * sampleBits + 7) / 8 + scanLinePool = BufferPool(scanLineBytesMax) + + paletteData = if (colorType == ColorType.INDEX) { + apng.palette?.list + ?: throw ApngParseError("missing ApngPalette for index color") + } else { + dummyPaletteData + } + + val transparentColor = apng.transparentColor + + transparentCheckerGrey = if (transparentColor != null) { + { v: Int -> if (transparentColor.match(v)) 0 else 255 } + } else { + { 255 } + } + + transparentCheckerRGB = if (transparentColor != null) { + { r, g, b -> if (transparentColor.match(r, g, b)) 0 else 255 } + } else { + { _, _, _ -> 255 } + } + + renderScanLineFunc = selectRenderFunc() + + pass = when (header.interlaceMethod) { + InterlaceMethod.Standard -> 0 + InterlaceMethod.None -> 7 + } + + initializePass() + } + + private fun renderIndex1(baLine: ByteArray) { + scanLine1(baLine, passWidth) { v -> + bitmapPointer.setPixel(paletteData[v]).next() + } + } + + private fun renderIndex2(baLine: ByteArray) { + scanLine2(baLine, passWidth) { v -> + bitmapPointer.setPixel(paletteData[v]).next() + } + } + + private fun renderIndex4(baLine: ByteArray) { + scanLine4(baLine, passWidth) { v -> + bitmapPointer.setPixel(paletteData[v]).next() + } + } + + private fun renderIndex8(baLine: ByteArray) { + scanLine8(baLine, passWidth) { v -> + bitmapPointer.setPixel(paletteData[v]).next() + } + } + + private fun renderGrey1(baLine: ByteArray) { + scanLine1(baLine, passWidth) { v -> + val g8 = if (v == 0) 0 else 255 + val a8 = transparentCheckerGrey(v) + bitmapPointer.setPixel(a8, g8, g8, g8).next() + } + } + + private fun renderGrey2(baLine: ByteArray) { + scanLine2(baLine, passWidth) { v -> + val g8 = v or (v shl 2) or (v shl 4) or (v shl 6) + val a8 = transparentCheckerGrey(v) + bitmapPointer.setPixel(a8, g8, g8, g8).next() + } + } + + private fun renderGrey4(baLine: ByteArray) { + scanLine4(baLine, passWidth) { v -> + val g8 = v or (v shl 4) + val a8 = transparentCheckerGrey(v) + bitmapPointer.setPixel(a8, g8, g8, g8).next() + } + } + + private fun renderGrey8(baLine: ByteArray) { + scanLine8(baLine, passWidth) { v -> + val a8 = transparentCheckerGrey(v) + bitmapPointer.setPixel(a8, v, v, v).next() + } + } + + private fun renderGrey16(baLine: ByteArray) { + var pos = 1 + var remain = passWidth + while (remain-- > 0) { + val v = baLine.getUInt16(pos) + pos += 2 + val g8 = v shr 8 + val a8 = transparentCheckerGrey(v) + bitmapPointer.setPixel(a8, g8, g8, g8).next() + } + } + + private fun renderGA8(baLine: ByteArray) { + var pos = 1 + var remain = passWidth + while (remain-- > 0) { + val g = baLine.getUInt8(pos) + val a = baLine.getUInt8(pos + 1) + pos += 2 + bitmapPointer.setPixel(a, g, g, g).next() + } + } + + private fun renderGA16(baLine: ByteArray) { + var pos = 1 + var remain = passWidth + while (remain-- > 0) { + val g8 = baLine.getUInt16(pos) shr 8 + val a8 = baLine.getUInt16(pos + 2) shr 8 + pos += 4 + bitmapPointer.setPixel(a8, g8, g8, g8).next() + } + } + + private fun renderRGB8(baLine: ByteArray) { + var pos = 1 + var remain = passWidth + while (remain-- > 0) { + val r = baLine.getUInt8(pos) + val g = baLine.getUInt8(pos + 1) + val b = baLine.getUInt8(pos + 2) + pos += 3 + val a8 = transparentCheckerRGB(r, g, b) + bitmapPointer.setPixel(a8, r, g, b).next() + } + } + + private fun renderRGB16(baLine: ByteArray) { + var pos = 1 + var remain = passWidth + while (remain-- > 0) { + val r = baLine.getUInt16(pos) + val g = baLine.getUInt16(pos + 2) + val b = baLine.getUInt16(pos + 4) + pos += 6 + val a8 = transparentCheckerRGB(r, g, b) + bitmapPointer.setPixel(a8, r shr 8, g shr 8, b shr 8).next() + } + } + + private fun renderRGBA8(baLine: ByteArray) { + var pos = 1 + var remain = passWidth + while (remain-- > 0) { + val r = baLine.getUInt8(pos) + val g = baLine.getUInt8(pos + 1) + val b = baLine.getUInt8(pos + 2) + val a = baLine.getUInt8(pos + 3) + pos += 4 + bitmapPointer.setPixel(a, r, g, b).next() + } + } + + private fun renderRGBA16(baLine: ByteArray) { + var pos = 1 + var remain = passWidth + while (remain-- > 0) { + val r = baLine.getUInt16(pos) + val g = baLine.getUInt16(pos + 2) + val b = baLine.getUInt16(pos + 4) + val a = baLine.getUInt16(pos + 6) + pos += 8 + bitmapPointer.setPixel(a shr 8, r shr 8, g shr 8, b shr 8).next() + } + } + + private fun colorBitsNotSupported(): Nothing { + throw ApngParseError("bitDepth $bitDepth is not supported for $colorType") + } + + private fun selectRenderFunc() = when (colorType) { + ColorType.GREY -> when (bitDepth) { + 1 -> ::renderGrey1 + 2 -> ::renderGrey2 + 4 -> ::renderGrey4 + 8 -> ::renderGrey8 + 16 -> ::renderGrey16 + else -> colorBitsNotSupported() + } + ColorType.RGB -> when (bitDepth) { + 8 -> ::renderRGB8 + 16 -> ::renderRGB16 + else -> colorBitsNotSupported() + } + ColorType.INDEX -> when (bitDepth) { + 1 -> ::renderIndex1 + 2 -> ::renderIndex2 + 4 -> ::renderIndex4 + 8 -> ::renderIndex8 + else -> colorBitsNotSupported() + } + ColorType.GREY_ALPHA -> when (bitDepth) { + 8 -> ::renderGA8 + 16 -> ::renderGA16 + else -> colorBitsNotSupported() + } + ColorType.RGBA -> when (bitDepth) { + 8 -> ::renderRGBA8 + 16 -> ::renderRGBA16 + else -> colorBitsNotSupported() + } + } + + private fun initializePass() { + passInfo = passInfoList[pass] + passWidth = (bitmap.width + passInfo.xStep - passInfo.xStart - 1) / passInfo.xStep + passHeight = (bitmap.height + passInfo.yStep - passInfo.yStart - 1) / passInfo.yStep + passY = 0 + scanLineBytes = 1 + (passWidth * sampleBits + 7) / 8 + + scanLinePool.recycle(baPreviousLine) + baPreviousLine = null + + if (passWidth <= 0 || passHeight <= 0) { + if (callback.canApngDebug()) callback.onApngDebug("pass $pass is empty. size=${passWidth}x$passHeight ") + incrementPassOrComplete() + } + } + + private fun incrementPassOrComplete() { + if (pass < 6) { + ++pass + initializePass() + } else if (!isCompleted) { + isCompleted = true + onCompleted() + } + } + + // スキャンラインを読む。行を処理したらtrueを返す + private fun readScanLine(): Boolean { + + if (inflateBufferQueue.remain < scanLineBytes) { + // not yet enough data to process scanline + return false + } + + val baLine = scanLinePool.obtain() + inflateBufferQueue.readBytes(baLine, 0, scanLineBytes) + + val filterNum = baLine.getUInt8(0) + // if( callback.canApngDebug() ) callback.onApngDebug("y=$passY/${passHeight},filterType=$filterType") - - when(filterType) { - FilterType.None -> { - } - - FilterType.Sub -> { - for(pos in 1 until scanLineBytes) { - val vCur = baLine.getUInt8(pos) - val leftPos = pos - sampleBytes - val vLeft = if(leftPos <= 0) 0 else baLine.getUInt8(leftPos) - - baLine[pos] = (vCur + vLeft).toByte() - - // if( callback.canApngDebug() ){ - // val x = passInfo.xStart + passInfo.xStep * ((pos-1)/sampleBytes) - // val y = passInfo.yStart + passInfo.yStep * passY - // callback.onApngDebug("sub pos=$pos,x=$x,y=$y,left=$vLeft,cur=$vCur,after=${baLine[pos].toInt() and 255}") - // } - - } - } - - FilterType.Up -> { - val baPreviousLine = this.baPreviousLine - for(pos in 1 until scanLineBytes) { - val vCur = baLine.getUInt8(pos) - val vUp = baPreviousLine?.getUInt8(pos) ?: 0 - baLine[pos] = (vCur + vUp).toByte() - } - } - - FilterType.Average -> { - val baPreviousLine = this.baPreviousLine - for(pos in 1 until scanLineBytes) { - val vCur = baLine.getUInt8(pos) - val leftPos = pos - sampleBytes - val vLeft = if(leftPos <= 0) 0 else baLine.getUInt8(leftPos) - val vUp = baPreviousLine?.getUInt8(pos) ?: 0 - baLine[pos] = (vCur + ((vLeft + vUp) shr 1)).toByte() - } - } - - FilterType.Paeth -> { - val baPreviousLine = this.baPreviousLine - for(pos in 1 until scanLineBytes) { - val vCur = baLine.getUInt8(pos) - val leftPos = pos - sampleBytes - val vLeft = if(leftPos <= 0) 0 else baLine.getUInt8(leftPos) - val vUp = baPreviousLine?.getUInt8(pos) ?: 0 - val vUpperLeft = if(leftPos <= 0) 0 else baPreviousLine?.getUInt8(leftPos) ?: 0 - - baLine[pos] = (vCur + paeth(vLeft, vUp, vUpperLeft)).toByte() - - // if( callback.canApngDebug() ){ - // val x = passInfo.xStart + passInfo.xStep * ((pos-1)/sampleBytes) - // val y = passInfo.yStart + passInfo.yStep * passY - // callback.onApngDebug("paeth pos=$pos,x=$x,y=$y,left=$vLeft,up=$vUp,ul=$vUpperLeft,cur=$vCur,paeth=${paeth(vLeft, vUp, vUpperLeft)}") - // } - - } - } - } - - // render scanline - bitmapPointer.setXY( - x = passInfo.xStart, - y = passInfo.yStart + passInfo.yStep * passY, - step = passInfo.xStep - ) - renderScanLineFunc(baLine) - - // save previous line - scanLinePool.recycle(baPreviousLine) - baPreviousLine = baLine - - if(++ passY >= passHeight) { - // pass completed - incrementPassOrComplete() - } - - return true - } - - // - 複数のIDATチャンクを順に読む - // - deflate圧縮をデコード(複数のチャンクの場合連続したものとして扱う) - // - interlace passに合わせてスキャンライン単位に分割 - // - filterをデコードして - // - ビットマップにレンダリング - fun addData( - inStream : InputStream, - size : Int, - inBuffer : ByteArray, - crc32 : CRC32 - ) { - var foundEnd = false - var inRemain = size - while(inRemain > 0 && ! foundEnd) { - - // inBufferのサイズに合わせて読み込む - var nRead = 0 - val nReadMax = Math.min(inBuffer.size, inRemain) - while(true) { - - val remain = nReadMax - nRead - if(remain <= 0) break - - val delta = inStream.read(inBuffer, nRead, remain) - if(delta < 0) { - foundEnd = true - break - } - nRead += delta - } - - if(nRead <= 0) continue - - inRemain -= nRead - - // 読んだらCRC計算する - crc32.update(inBuffer, 0, nRead) - - // チャンク末尾に余計なデータがあった場合はinflateせずに終端まで読む - if(isCompleted) continue - - // zlibのdeflateをデコードする - inflater.setInput(inBuffer, 0, nRead) - while(! inflater.needsInput()) { - val buffer = inflateBufferPool.obtain() - val nInflated = inflater.inflate(buffer) - if(nInflated <= 0) { - inflateBufferPool.recycle(buffer) - } else { - inflateBufferQueue.add(ByteSequence(buffer, 0, nInflated)) - // キューに追加したデータをScanLine単位で消費する - while(! isCompleted && readScanLine()) { + + when (FilterType.values().first { it.num == filterNum }) { + FilterType.None -> { + } + + FilterType.Sub -> { + for (pos in 1 until scanLineBytes) { + val vCur = baLine.getUInt8(pos) + val leftPos = pos - sampleBytes + val vLeft = if (leftPos <= 0) 0 else baLine.getUInt8(leftPos) + + baLine[pos] = (vCur + vLeft).toByte() + + // if( callback.canApngDebug() ){ + // val x = passInfo.xStart + passInfo.xStep * ((pos-1)/sampleBytes) + // val y = passInfo.yStart + passInfo.yStep * passY + // callback.onApngDebug("sub pos=$pos,x=$x,y=$y,left=$vLeft,cur=$vCur,after=${baLine[pos].toInt() and 255}") + // } + + } + } + + FilterType.Up -> { + val baPreviousLine = this.baPreviousLine + for (pos in 1 until scanLineBytes) { + val vCur = baLine.getUInt8(pos) + val vUp = baPreviousLine?.getUInt8(pos) ?: 0 + baLine[pos] = (vCur + vUp).toByte() + } + } + + FilterType.Average -> { + val baPreviousLine = this.baPreviousLine + for (pos in 1 until scanLineBytes) { + val vCur = baLine.getUInt8(pos) + val leftPos = pos - sampleBytes + val vLeft = if (leftPos <= 0) 0 else baLine.getUInt8(leftPos) + val vUp = baPreviousLine?.getUInt8(pos) ?: 0 + baLine[pos] = (vCur + ((vLeft + vUp) shr 1)).toByte() + } + } + + FilterType.Paeth -> { + val baPreviousLine = this.baPreviousLine + for (pos in 1 until scanLineBytes) { + val vCur = baLine.getUInt8(pos) + val leftPos = pos - sampleBytes + val vLeft = if (leftPos <= 0) 0 else baLine.getUInt8(leftPos) + val vUp = baPreviousLine?.getUInt8(pos) ?: 0 + val vUpperLeft = if (leftPos <= 0) 0 else baPreviousLine?.getUInt8(leftPos) ?: 0 + + baLine[pos] = (vCur + paeth(vLeft, vUp, vUpperLeft)).toByte() + + // if( callback.canApngDebug() ){ + // val x = passInfo.xStart + passInfo.xStep * ((pos-1)/sampleBytes) + // val y = passInfo.yStart + passInfo.yStep * passY + // callback.onApngDebug("paeth pos=$pos,x=$x,y=$y,left=$vLeft,up=$vUp,ul=$vUpperLeft,cur=$vCur,paeth=${paeth(vLeft, vUp, vUpperLeft)}") + // } + + } + } + } + + // render scanline + bitmapPointer.setXY( + x = passInfo.xStart, + y = passInfo.yStart + passInfo.yStep * passY, + step = passInfo.xStep + ) + renderScanLineFunc(baLine) + + // save previous line + scanLinePool.recycle(baPreviousLine) + baPreviousLine = baLine + + if (++passY >= passHeight) { + // pass completed + incrementPassOrComplete() + } + + return true + } + + // - 複数のIDATチャンクを順に読む + // - deflate圧縮をデコード(複数のチャンクの場合連続したものとして扱う) + // - interlace passに合わせてスキャンライン単位に分割 + // - filterをデコードして + // - ビットマップにレンダリング + fun addData( + inStream: InputStream, + size: Int, + inBuffer: ByteArray, + crc32: CRC32 + ) { + var foundEnd = false + var inRemain = size + while (inRemain > 0 && !foundEnd) { + + // inBufferのサイズに合わせて読み込む + var nRead = 0 + val nReadMax = min(inBuffer.size, inRemain) + while (true) { + + val remain = nReadMax - nRead + if (remain <= 0) break + + val delta = inStream.read(inBuffer, nRead, remain) + if (delta < 0) { + foundEnd = true + break + } + nRead += delta + } + + if (nRead <= 0) continue + + inRemain -= nRead + + // 読んだらCRC計算する + crc32.update(inBuffer, 0, nRead) + + // チャンク末尾に余計なデータがあった場合はinflateせずに終端まで読む + if (isCompleted) continue + + // zlibのdeflateをデコードする + inflater.setInput(inBuffer, 0, nRead) + while (!inflater.needsInput()) { + val buffer = inflateBufferPool.obtain() + val nInflated = inflater.inflate(buffer) + if (nInflated <= 0) { + inflateBufferPool.recycle(buffer) + } else { + inflateBufferQueue.add(ByteSequence(buffer, 0, nInflated)) + + // キューに追加したデータをScanLine単位で消費する + @Suppress("ControlFlowWithEmptyBody") + while (!isCompleted && readScanLine()){ } - if(isCompleted) { - inflateBufferQueue.clear() - break - } - } - } - } - } + + if (isCompleted) { + inflateBufferQueue.clear() + break + } + } + } + } + } } diff --git a/apng/src/main/java/jp/juggler/apng/util/ByteSequenceQueue.kt b/apng/src/main/java/jp/juggler/apng/util/ByteSequenceQueue.kt index a5d678fc..10447d1e 100644 --- a/apng/src/main/java/jp/juggler/apng/util/ByteSequenceQueue.kt +++ b/apng/src/main/java/jp/juggler/apng/util/ByteSequenceQueue.kt @@ -1,13 +1,14 @@ package jp.juggler.apng.util import java.util.* +import kotlin.math.min internal class ByteSequenceQueue(private val bufferRecycler : (ByteSequence) -> Unit) { private val list = LinkedList() val remain : Int - get() = list.sumBy { it.length } + get() = list.sumOf { it.length } fun add(range : ByteSequence) =list.add(range) @@ -22,7 +23,7 @@ internal class ByteSequenceQueue(private val bufferRecycler : (ByteSequence) -> bufferRecycler(item) list.removeFirst() } else { - val delta = Math.min(item.length, dstRemain) + val delta = min(item.length, dstRemain) System.arraycopy(item.array, item.offset, dst, dstOffset, delta) dstOffset += delta dstRemain -= delta diff --git a/apng_android/src/main/java/jp/juggler/apng/ApngFrames.kt b/apng_android/src/main/java/jp/juggler/apng/ApngFrames.kt index a7de7efd..35ee0821 100644 --- a/apng_android/src/main/java/jp/juggler/apng/ApngFrames.kt +++ b/apng_android/src/main/java/jp/juggler/apng/ApngFrames.kt @@ -11,6 +11,7 @@ import android.util.Log import java.io.InputStream import java.util.ArrayList import kotlin.math.max +import kotlin.math.min class ApngFrames private constructor( private val pixelSizeMax : Int = 0, @@ -125,8 +126,17 @@ class ApngFrames private constructor( throw ex } } + + private val apngHeadKey = byteArrayOf(0x89.toByte(),0x50) + private val gifHeadKey = "GIF".toByteArray(Charsets.UTF_8) + + private fun matchBytes(ba1:ByteArray,ba2:ByteArray,length:Int=min(ba1.size,ba2.size)):Boolean{ + for( i in 0 until length){ + if( ba1[i] != ba2[i] ) return false + } + return true + } - @Suppress("unused") fun parse( pixelSizeMax : Int, debug : Boolean = false, @@ -136,18 +146,11 @@ class ApngFrames private constructor( val buf = ByteArray(8) { 0.toByte() } opener()?.use { it.read(buf, 0, buf.size) } - if(buf.size >= 8 - && (buf[0].toInt() and 0xff) == 0x89 - && (buf[1].toInt() and 0xff) == 0x50 - ) { + if(buf.size >= 8 && matchBytes(buf, apngHeadKey) ) { return opener()?.use { parseApng(it, pixelSizeMax, debug) } } - if(buf.size >= 6 - && buf[0].toChar() == 'G' - && buf[1].toChar() == 'I' - && buf[2].toChar() == 'F' - ) { + if(buf.size >= 6 && matchBytes(buf, gifHeadKey) ) { return opener()?.use { parseGif(it, pixelSizeMax, debug) } } diff --git a/app/build.gradle b/app/build.gradle index cf41cc9a..10c3a9b0 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -97,6 +97,9 @@ dependencies { implementation "androidx.appcompat:appcompat:$appcompat_version" + //noinspection KtxExtensionAvailable + implementation "androidx.lifecycle:lifecycle-viewmodel:$lifecycle_version" + // DrawerLayout implementation "androidx.drawerlayout:drawerlayout:1.1.1" @@ -113,12 +116,12 @@ dependencies { implementation "androidx.browser:browser:1.3.0" // Recyclerview - implementation "androidx.recyclerview:recyclerview:1.1.0" + implementation "androidx.recyclerview:recyclerview:1.2.0" - kapt 'androidx.annotation:annotation:1.1.0' + kapt 'androidx.annotation:annotation:1.2.0' // https://firebase.google.com/support/release-notes/android - implementation "com.google.firebase:firebase-messaging:21.0.1" + implementation "com.google.firebase:firebase-messaging:21.1.0" implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" @@ -174,7 +177,7 @@ dependencies { implementation 'com.astuetz:pagerslidingtabstrip:1.0.1' - implementation 'com.google.android.exoplayer:exoplayer:2.12.0' + implementation 'com.google.android.exoplayer:exoplayer:2.13.3' /* WARNING: [Processor] Library '…\exoplayer-ui-2.12.0.aar' contains references to both AndroidX and old support library. This seems like the library is partially migrated. Jetifier will try to rewrite the library anyway. Example of androidX reference: 'androidx/core/app/NotificationCompat$Builder' diff --git a/app/src/androidTest/java/jp/juggler/subwaytooter/WordTrieTreeTest.kt b/app/src/androidTest/java/jp/juggler/subwaytooter/WordTrieTreeTest.kt index f9d6464d..852007d2 100644 --- a/app/src/androidTest/java/jp/juggler/subwaytooter/WordTrieTreeTest.kt +++ b/app/src/androidTest/java/jp/juggler/subwaytooter/WordTrieTreeTest.kt @@ -48,15 +48,15 @@ class WordTrieTreeTest { val tokenizer = CharacterGroup.Tokenizer().reset(strTest, 0, strTest.length) // id = tokenizer.next() - assertEquals('A'.toInt(), id) + assertEquals('A'.code, id) assertEquals((whitespace_len + 1), tokenizer.offset) // offset は Aの次の位置になる // id = tokenizer.next() - assertEquals('B'.toInt(), id) + assertEquals('B'.code, id) assertEquals((whitespace_len + 2).toLong(), tokenizer.offset.toLong()) // id = tokenizer.next() - assertEquals('C'.toInt(), id) + assertEquals('C'.code, id) assertEquals((whitespace_len + 3).toLong(), tokenizer.offset.toLong()) // id = tokenizer.next() @@ -71,15 +71,15 @@ class WordTrieTreeTest { // id = tokenizer.next() assertEquals((whitespace_len + 1).toLong(), tokenizer.offset.toLong()) - assertEquals('A'.toInt(), id) + assertEquals('A'.code, id) // id = tokenizer.next() assertEquals((whitespace_len + 2).toLong(), tokenizer.offset.toLong()) - assertEquals('B'.toInt(), id) + assertEquals('B'.code, id) // id = tokenizer.next() assertEquals((whitespace_len + 3).toLong(), tokenizer.offset.toLong()) - assertEquals('C'.toInt(), id) + assertEquals('C'.code, id) // id = tokenizer.next() assertEquals((whitespace_len + 3).toLong(), tokenizer.offset.toLong()) @@ -93,15 +93,15 @@ class WordTrieTreeTest { // id = tokenizer.next() assertEquals((whitespace_len + 1).toLong(), tokenizer.offset.toLong()) - assertEquals('A'.toInt(), id) + assertEquals('A'.code, id) // id = tokenizer.next() assertEquals((whitespace_len + 2).toLong(), tokenizer.offset.toLong()) - assertEquals('B'.toInt(), id) + assertEquals('B'.code, id) // id = tokenizer.next() assertEquals((whitespace_len + 3).toLong(), tokenizer.offset.toLong()) - assertEquals('C'.toInt(), id) + assertEquals('C'.code, id) // id = tokenizer.next() assertEquals((whitespace_len + 3).toLong(), tokenizer.offset.toLong()) @@ -115,15 +115,15 @@ class WordTrieTreeTest { // id = tokenizer.next() assertEquals((whitespace_len + 1).toLong(), tokenizer.offset.toLong()) - assertEquals('A'.toInt(), id) + assertEquals('A'.code, id) // id = tokenizer.next() assertEquals((whitespace_len + 2).toLong(), tokenizer.offset.toLong()) - assertEquals('B'.toInt(), id) + assertEquals('B'.code, id) // id = tokenizer.next() assertEquals((whitespace_len + 3).toLong(), tokenizer.offset.toLong()) - assertEquals('C'.toInt(), id) + assertEquals('C'.code, id) // id = tokenizer.next() assertEquals((whitespace_len + 3).toLong(), tokenizer.offset.toLong()) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 34484157..de07b892 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -7,6 +7,7 @@ + diff --git a/app/src/main/java/jp/juggler/subwaytooter/ActAbout.kt b/app/src/main/java/jp/juggler/subwaytooter/ActAbout.kt index 2cc3e1cf..ee3a605e 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ActAbout.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/ActAbout.kt @@ -13,126 +13,143 @@ import jp.juggler.subwaytooter.util.openBrowser import jp.juggler.util.LogCategory class ActAbout : AppCompatActivity() { - - class Translators( - val name : String, - val acct : String?, - val lang : String - ) - - companion object { - - val log = LogCategory("ActAbout") - - const val EXTRA_SEARCH = "search" - - const val developer_acct = "tateisu@mastodon.juggler.jp" - const val official_acct = "SubwayTooter@mastodon.juggler.jp" - - const val url_release = "https://github.com/tateisu/SubwayTooter/releases" - - const val url_weblate = "https://hosted.weblate.org/projects/subway-tooter/" - - // git log --pretty=format:"%an %s" |grep "Translated using Weblate"|sort|uniq - val translators = arrayOf( - Translators("Allan Nordhøy", null, "English & Norwegian Bokmål"), - Translators("ButterflyOfFire", "@ButterflyOfFire@mstdn.fr", "Arabic & French"), - Translators("Ch", null, "Korean"), - Translators("Elizabeth Sherrock", null, "Chinese (Simplified)"), - Translators("Gennady Archangorodsky", null, "Hebrew"), - Translators("inqbs Siina", null, "Korean"), - Translators("Jeong Arm", "@jarm@qdon.space", "Korean"), - Translators("Joan Pujolar", "@jpujolar@mastodont.cat", "Catalan"), - Translators("Kai Zhang", "@bearzk@mastodon.social", "Chinese (Simplified)"), - Translators("lptprjh", null, "Korean"), - Translators("mynameismonkey", null, "Welsh"), - Translators("Nathan", null, "French"), - Translators("Owain Rhys Lewis", null, "Welsh"), - Translators("Swann Martinet", null, "French"), - Translators("takubunn", null, "Chinese (Simplified)"), - Translators("배태길", null, "Korea") - ) - } - - override fun onCreate(savedInstanceState : Bundle?) { - super.onCreate(savedInstanceState) - App1.setActivityTheme(this) - setContentView(R.layout.act_about) - App1.initEdgeToEdge(this) - - Styler.fixHorizontalPadding(findViewById(R.id.svContent)) - - - try { - val pInfo = packageManager.getPackageInfo(packageName, 0) - val tv = findViewById(R.id.tvVersion) - tv.text = getString(R.string.version_is, pInfo.versionName) - } catch(ex : PackageManager.NameNotFoundException) { - log.trace(ex, "getPackageInfo failed.") - } - - fun setButton(btnId : Int, caption : String, onClick : () -> Unit) { - val b : Button = findViewById(btnId) - b.text = caption - b.setOnClickListener { onClick() } - } - - fun searchAcct(acct : String) { - setResult(Activity.RESULT_OK, Intent().apply { putExtra(EXTRA_SEARCH, acct) }) - finish() - } - - - setButton( - R.id.btnDeveloper, - getString(R.string.search_for, developer_acct) - ) { searchAcct(developer_acct) } - - setButton( - R.id.btnOfficialAccount, - getString(R.string.search_for, official_acct) - ) { searchAcct(official_acct) } - - setButton(R.id.btnReleaseNote, url_release) - { openBrowser(url_release) } - - // setButton(R.id.btnIconDesign, url_futaba) - // { openUrl(url_futaba) } - - setButton(R.id.btnWeblate, getString(R.string.please_help_translation)) - { openBrowser(url_weblate) } - - val ll = findViewById(R.id.llContributors) - val density = resources.displayMetrics.density - val margin_top = (0.5f + density * 8).toInt() - val padding = (0.5f + density * 8).toInt() - - for(who in translators) { - ll.addView(Button(this).apply { - // - layoutParams = LinearLayout.LayoutParams( - LinearLayout.LayoutParams.WRAP_CONTENT, - LinearLayout.LayoutParams.WRAP_CONTENT - ).apply { - if(ll.childCount != 0) topMargin = margin_top - } - // - setBackgroundResource(R.drawable.btn_bg_transparent_round6dp) - setPadding(padding, padding, padding, padding) - isAllCaps = false - - // - val acct = who.acct ?: "@?@?" - text = "${who.name}\n$acct\n${getString(R.string.thanks_for, who.lang)}" - gravity = Gravity.START or Gravity.CENTER_VERTICAL - - setOnClickListener { - val data = Intent() - data.putExtra(EXTRA_SEARCH, who.acct ?: who.name) - setResult(Activity.RESULT_OK, data) - finish() - } - }) - } - } + + class Translators( + val name: String, + val acct: String?, + val lang: String + ) + + companion object { + + val log = LogCategory("ActAbout") + + const val EXTRA_SEARCH = "search" + + const val developer_acct = "tateisu@mastodon.juggler.jp" + const val official_acct = "SubwayTooter@mastodon.juggler.jp" + + const val url_release = "https://github.com/tateisu/SubwayTooter/releases" + + const val url_weblate = "https://hosted.weblate.org/projects/subway-tooter/" + + // git log --pretty=format:"%an %s" |grep "Translated using Weblate"|sort|uniq + val translators = arrayOf( + Translators("Allan Nordhøy", null, "English, Norwegian Bokmål"), + Translators("ayiniho", null, "French"), + Translators("ButterflyOfFire", "@ButterflyOfFire@mstdn.fr", "Arabic, French, Kabyle"), + Translators("Ch", null, "Korean"), + Translators("chinnux", "@chinnux@neko.ci", "Chinese (Simplified)"), + Translators("Dyxang", null, "Chinese (Simplified)"), + Translators("Elizabeth Sherrock", null, "Chinese (Simplified)"), + Translators("Gennady Archangorodsky", null, "Hebrew"), + Translators("inqbs Siina", null, "Korean"), + Translators("J. Lavoie", null, "French, German"), + Translators("Jeong Arm", "@jarm@qdon.space", "Korean"), + Translators("Joan Pujolar", "@jpujolar@mastodont.cat", "Catalan"), + Translators("Kai Zhang", "@bearzk@mastodon.social", "Chinese (Simplified)"), + Translators("koyu", null, "German"), + Translators("Liaizon Wakest", null, "English"), + Translators("lingcas", null, "Chinese (Traditional)"), + Translators("Love Xu", null, "Chinese (Simplified)"), + Translators("lptprjh", null, "Korean"), + Translators("mv87", null, "German"), + Translators("mynameismonkey", null, "Welsh"), + Translators("Nathan", null, "French"), + Translators("Niek Visser", null, "Dutch"), + Translators("Owain Rhys Lewis", null, "Welsh"), + Translators("Remi Rampin", null, "French"), + Translators("Sachin", null, "Kannada"), + Translators("Swann Martinet", null, "French"), + Translators("takubunn", null, "Chinese (Simplified)"), + Translators("Whod", null, "Bulgarian"), + Translators("yucj", null, "Chinese (Traditional)"), + Translators("邓志诚", null, "Chinese (Simplified)"), + Translators("배태길", null, "Korea"), + + + ) + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + App1.setActivityTheme(this) + setContentView(R.layout.act_about) + App1.initEdgeToEdge(this) + + Styler.fixHorizontalPadding(findViewById(R.id.svContent)) + + + try { + val pInfo = packageManager.getPackageInfo(packageName, 0) + val tv = findViewById(R.id.tvVersion) + tv.text = getString(R.string.version_is, pInfo.versionName) + } catch (ex: PackageManager.NameNotFoundException) { + log.trace(ex, "getPackageInfo failed.") + } + + fun setButton(btnId: Int, caption: String, onClick: () -> Unit) { + val b: Button = findViewById(btnId) + b.text = caption + b.setOnClickListener { onClick() } + } + + fun searchAcct(acct: String) { + setResult(Activity.RESULT_OK, Intent().apply { putExtra(EXTRA_SEARCH, acct) }) + finish() + } + + + setButton( + R.id.btnDeveloper, + getString(R.string.search_for, developer_acct) + ) { searchAcct(developer_acct) } + + setButton( + R.id.btnOfficialAccount, + getString(R.string.search_for, official_acct) + ) { searchAcct(official_acct) } + + setButton(R.id.btnReleaseNote, url_release) + { openBrowser(url_release) } + + // setButton(R.id.btnIconDesign, url_futaba) + // { openUrl(url_futaba) } + + setButton(R.id.btnWeblate, getString(R.string.please_help_translation)) + { openBrowser(url_weblate) } + + val ll = findViewById(R.id.llContributors) + val density = resources.displayMetrics.density + val margin_top = (0.5f + density * 8).toInt() + val padding = (0.5f + density * 8).toInt() + + for (who in translators) { + ll.addView(Button(this).apply { + // + layoutParams = LinearLayout.LayoutParams( + LinearLayout.LayoutParams.WRAP_CONTENT, + LinearLayout.LayoutParams.WRAP_CONTENT + ).apply { + if (ll.childCount != 0) topMargin = margin_top + } + // + setBackgroundResource(R.drawable.btn_bg_transparent_round6dp) + setPadding(padding, padding, padding, padding) + isAllCaps = false + + // + val acct = who.acct ?: "@?@?" + text = "${who.name}\n$acct\n${getString(R.string.thanks_for, who.lang)}" + gravity = Gravity.START or Gravity.CENTER_VERTICAL + + setOnClickListener { + val data = Intent() + data.putExtra(EXTRA_SEARCH, who.acct ?: who.name) + setResult(Activity.RESULT_OK, data) + finish() + } + }) + } + } } diff --git a/app/src/main/java/jp/juggler/subwaytooter/ActAccountSetting.kt b/app/src/main/java/jp/juggler/subwaytooter/ActAccountSetting.kt index c6da94cc..f7d15046 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ActAccountSetting.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/ActAccountSetting.kt @@ -338,7 +338,7 @@ class ActAccountSetting : AsyncActivity(), View.OnClickListener, spResizeImage = findViewById(R.id.spResizeImage) imageResizeItems = SavedAccount.resizeConfigList.map { - var caption = when (it.type) { + val caption = when (it.type) { ResizeType.None -> getString(R.string.dont_resize) ResizeType.LongSide -> getString(R.string.long_side_pixel, it.size) ResizeType.SquarePixel -> if (it.extraStringId != 0) { @@ -436,7 +436,7 @@ class ActAccountSetting : AsyncActivity(), View.OnClickListener, btnNotificationStyleEditReply.setOnClickListener(this) - spResizeImage.setOnItemSelectedListener(this) + spResizeImage.onItemSelectedListener = this btnNotificationStyleEditReply.vg(Pref.bpSeparateReplyNotificationGroup(pref)) diff --git a/app/src/main/java/jp/juggler/subwaytooter/ActPost.kt b/app/src/main/java/jp/juggler/subwaytooter/ActPost.kt index 0d4eadad..ed793ab4 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ActPost.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/ActPost.kt @@ -1125,7 +1125,7 @@ class ActPost : AsyncActivity(), } } else { if (editable.isNotEmpty() - && !CharacterGroup.isWhitespace(editable[editable.length - 1].toInt()) + && !CharacterGroup.isWhitespace(editable[editable.length - 1].code) ) { editable.append(' ') } @@ -2571,7 +2571,7 @@ class ActPost : AsyncActivity(), val e = etContent.editableText val len = e.length val last_char = if (len <= 0) ' ' else e[len - 1] - if (!CharacterGroup.isWhitespace(last_char.toInt())) { + if (!CharacterGroup.isWhitespace(last_char.code)) { e.append(" ").append(a.text_url) } else { e.append(a.text_url) diff --git a/app/src/main/java/jp/juggler/subwaytooter/action/Action_App.kt b/app/src/main/java/jp/juggler/subwaytooter/action/Action_App.kt index 1aeb7cc2..6fffe94d 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/action/Action_App.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/action/Action_App.kt @@ -3,7 +3,6 @@ package jp.juggler.subwaytooter.action import android.app.AlertDialog import jp.juggler.subwaytooter.ActColumnList import jp.juggler.subwaytooter.ActMain -import jp.juggler.subwaytooter.App1 import jp.juggler.subwaytooter.R import jp.juggler.subwaytooter.api.entity.TootApplication import jp.juggler.subwaytooter.table.MutedApp diff --git a/app/src/main/java/jp/juggler/subwaytooter/action/Action_Filter.kt b/app/src/main/java/jp/juggler/subwaytooter/action/Action_Filter.kt index 73949007..24198fc1 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/action/Action_Filter.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/action/Action_Filter.kt @@ -1,7 +1,6 @@ package jp.juggler.subwaytooter.action import jp.juggler.subwaytooter.ActMain -import jp.juggler.subwaytooter.App1 import jp.juggler.subwaytooter.R import jp.juggler.subwaytooter.api.TootApiClient import jp.juggler.subwaytooter.api.TootApiResult diff --git a/app/src/main/java/jp/juggler/subwaytooter/action/Action_Follow.kt b/app/src/main/java/jp/juggler/subwaytooter/action/Action_Follow.kt index b012b244..6e76044f 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/action/Action_Follow.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/action/Action_Follow.kt @@ -2,7 +2,6 @@ package jp.juggler.subwaytooter.action import androidx.appcompat.app.AlertDialog import jp.juggler.subwaytooter.ActMain -import jp.juggler.subwaytooter.App1 import jp.juggler.subwaytooter.ColumnType import jp.juggler.subwaytooter.R import jp.juggler.subwaytooter.api.* diff --git a/app/src/main/java/jp/juggler/subwaytooter/action/Action_ListMember.kt b/app/src/main/java/jp/juggler/subwaytooter/action/Action_ListMember.kt index da436518..4a20de26 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/action/Action_ListMember.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/action/Action_ListMember.kt @@ -2,7 +2,6 @@ package jp.juggler.subwaytooter.action import android.app.AlertDialog import jp.juggler.subwaytooter.ActMain -import jp.juggler.subwaytooter.App1 import jp.juggler.subwaytooter.R import jp.juggler.subwaytooter.api.* import jp.juggler.subwaytooter.api.entity.* diff --git a/app/src/main/java/jp/juggler/subwaytooter/api/entity/Acct.kt b/app/src/main/java/jp/juggler/subwaytooter/api/entity/Acct.kt index b4f1e884..a01d6ed9 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/api/entity/Acct.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/api/entity/Acct.kt @@ -47,7 +47,7 @@ class Host private constructor( val cached = hostSet[srcArg] if(cached != null) return cached val src = srcArg.removeUrlSchema() - val ascii = IDN.toASCII(src, IDN.ALLOW_UNASSIGNED).toLowerCase(Locale.JAPAN) + val ascii = IDN.toASCII(src, IDN.ALLOW_UNASSIGNED).lowercase() val pretty = IDN.toUnicode(src, IDN.ALLOW_UNASSIGNED) val host = if(ascii == pretty) Host(ascii) else Host(ascii, pretty) hostSet[src] = host diff --git a/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootInstance.kt b/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootInstance.kt index a7e04cf8..ecced2e7 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootInstance.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootInstance.kt @@ -427,7 +427,7 @@ class TootInstance(parser: TootParser, src: JsonObject) { private fun Host.getCacheEntry(): CacheEntry = synchronized(_hostCache) { - val hostLower = ascii.toLowerCase(Locale.JAPAN) + val hostLower = ascii.lowercase() var item = _hostCache[hostLower] if (item == null) { item = CacheEntry() diff --git a/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootPolls.kt b/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootPolls.kt index e5e1efc3..6b04a483 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootPolls.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootPolls.kt @@ -240,7 +240,7 @@ class TootPolls ( TootStatus.parseTime(src.string("endTime")).notZero() ?: Long.MAX_VALUE this.expired = expired_at >= System.currentTimeMillis() this.multiple = src.containsKey("anyOf") - this.votes_count = items?.sumBy{ it.votes?: 0 }?.notZero() + this.votes_count = items?.sumOf{ it.votes ?: 0 }?.notZero() this.ownVoted = false diff --git a/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootTag.kt b/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootTag.kt index 730a91ac..ec7c9ce9 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootTag.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootTag.kt @@ -26,7 +26,7 @@ open class TootTag constructor( init { countDaily = history?.first()?.uses ?: 0 - countWeekly = history?.sumBy { it.uses } ?: 0 + countWeekly = history?.sumOf{ it.uses } ?: 0 accountDaily = history?.first()?.accounts ?: 0 accountWeekly = history?.map { it.accounts }?.maxOrNull() ?: accountDaily diff --git a/app/src/main/java/jp/juggler/subwaytooter/dialog/AccountPicker.kt b/app/src/main/java/jp/juggler/subwaytooter/dialog/AccountPicker.kt index 67b62421..dd0b58c8 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/dialog/AccountPicker.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/dialog/AccountPicker.kt @@ -24,158 +24,156 @@ import java.util.* import java.util.concurrent.atomic.AtomicBoolean object AccountPicker { - - @SuppressLint("InflateParams") - fun pick( - activity : AppCompatActivity, - bAllowPseudo : Boolean = false, - bAllowMisskey : Boolean = true, - bAllowMastodon : Boolean = true, - bAuto : Boolean = false, - message : String? = null, - accountListArg : ArrayList? = null, - dismiss_callback : DialogInterfaceCallback? = null, - extra_callback : (LinearLayout, Int, Int) -> Unit = { _, _, _ -> }, - callback : SavedAccountCallback - ) { - var removedMisskey = 0 - var removedPseudo = 0 + + @SuppressLint("InflateParams") + fun pick( + activity: AppCompatActivity, + bAllowPseudo: Boolean = false, + bAllowMisskey: Boolean = true, + bAllowMastodon: Boolean = true, + bAuto: Boolean = false, + message: String? = null, + accountListArg: ArrayList? = null, + dismiss_callback: DialogInterfaceCallback? = null, + extra_callback: (LinearLayout, Int, Int) -> Unit = { _, _, _ -> }, + callback: SavedAccountCallback + ) { var removeMastodon = 0 - val account_list : MutableList = accountListArg ?: { - val l = SavedAccount.loadAccountList(activity).filter { a -> - var bOk = true - - if(! bAllowMastodon && ! a.isMisskey) { - ++ removeMastodon - bOk = false - } - - if(! bAllowMisskey && a.isMisskey) { - ++ removedMisskey - bOk = false - } - - if(! bAllowPseudo && a.isPseudo) { - ++ removedPseudo - bOk = false - } - - bOk - }.toMutableList() - SavedAccount.sort(l) - l - }() - - if(account_list.isEmpty()) { - - val sb = StringBuilder() - - if(removedPseudo > 0) { - sb.append(activity.getString(R.string.not_available_for_pseudo_account)) - } - - if(removedMisskey > 0) { - if(sb.isNotEmpty()) sb.append('\n') - sb.append(activity.getString(R.string.not_available_for_misskey_account)) - } - if(removeMastodon > 0) { - if(sb.isNotEmpty()) sb.append('\n') - sb.append(activity.getString(R.string.not_available_for_mastodon_account)) - } - - if(sb.isEmpty()) { - sb.append(activity.getString(R.string.account_empty)) - } - - activity.showToast(false, sb.toString()) - return - } - - if(bAuto && account_list.size == 1) { - callback(account_list[0]) - return - } - - val viewRoot = activity.layoutInflater.inflate(R.layout.dlg_account_picker, null, false) - - val dialog = Dialog(activity) - val isDialogClosed = AtomicBoolean(false) - - dialog.setOnDismissListener { - if(dismiss_callback != null) dismiss_callback(it) - } - - dialog.setContentView(viewRoot) - if(message != null && message.isNotEmpty()) { - val tv = viewRoot.findViewById(R.id.tvMessage) - tv.visibility = View.VISIBLE - tv.text = message - } - viewRoot.findViewById(R.id.btnCancel).setOnClickListener { - isDialogClosed.set(true) - dialog.cancel() - } - dialog.setCancelable(true) - dialog.setCanceledOnTouchOutside(true) - dialog.setOnCancelListener { isDialogClosed.set(true) } - - val density = activity.resources.displayMetrics.density - - val llAccounts : LinearLayout = viewRoot.findViewById(R.id.llAccounts) - val pad_se = (0.5f + 12f * density).toInt() - val pad_tb = (0.5f + 6f * density).toInt() - - for(a in account_list) { - val lp = LinearLayout.LayoutParams( - LinearLayout.LayoutParams.MATCH_PARENT, - LinearLayout.LayoutParams.WRAP_CONTENT - ) - - val ac = AcctColor.load(a) - - val b = Button(activity) - - if(AcctColor.hasColorBackground(ac)) { - b.background = getAdaptiveRippleDrawableRound(activity, ac.color_bg, ac.color_fg) - } else { - b.setBackgroundResource(R.drawable.btn_bg_transparent_round6dp) - } - if(AcctColor.hasColorForeground(ac)) { - b.textColor = ac.color_fg - } - - b.setPaddingRelative(pad_se, pad_tb, pad_se, pad_tb) - b.gravity = Gravity.START or Gravity.CENTER_VERTICAL - b.isAllCaps = false - b.layoutParams = lp - b.minHeight = (0.5f + 32f * density).toInt() - - val sb = SpannableStringBuilder(ac.nickname) - if(a.last_notification_error?.isNotEmpty() == true) { - sb.append("\n") - val start = sb.length - sb.append(a.last_notification_error) - val end = sb.length - sb.setSpan(RelativeSizeSpan(0.7f), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) - } else if(a.last_subscription_error?.isNotEmpty() == true) { - sb.append("\n") - val start = sb.length - sb.append(a.last_subscription_error) - val end = sb.length - sb.setSpan(RelativeSizeSpan(0.7f), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) - } - b.text = sb - - b.setOnClickListener { - isDialogClosed.set(true) - callback(a) - dialog.dismissSafe() - } - llAccounts.addView(b) - } - - extra_callback(llAccounts, pad_se, pad_tb) - - dialog.show() - } + var removedMisskey = 0 + var removedPseudo = 0 + + fun SavedAccount.checkMastodon() = when { + !bAllowMastodon && !isMisskey -> ++removeMastodon + else -> 0 + } + + fun SavedAccount.checkMisskey() = when { + !bAllowMisskey && isMisskey -> ++removedMisskey + else -> 0 + } + + fun SavedAccount.checkPseudo() = when { + !bAllowPseudo && isPseudo -> ++removedPseudo + else -> 0 + } + + + val account_list: MutableList = accountListArg + ?: SavedAccount.loadAccountList(activity) + .filter { 0 == it.checkMastodon() + it.checkMisskey() + it.checkPseudo() } + .toMutableList() + .also { SavedAccount.sort(it) } + + if (account_list.isEmpty()) { + + val sb = StringBuilder() + + if (removedPseudo > 0) { + sb.append(activity.getString(R.string.not_available_for_pseudo_account)) + } + + if (removedMisskey > 0) { + if (sb.isNotEmpty()) sb.append('\n') + sb.append(activity.getString(R.string.not_available_for_misskey_account)) + } + if (removeMastodon > 0) { + if (sb.isNotEmpty()) sb.append('\n') + sb.append(activity.getString(R.string.not_available_for_mastodon_account)) + } + + if (sb.isEmpty()) { + sb.append(activity.getString(R.string.account_empty)) + } + + activity.showToast(false, sb.toString()) + return + } + + if (bAuto && account_list.size == 1) { + callback(account_list[0]) + return + } + + val viewRoot = activity.layoutInflater.inflate(R.layout.dlg_account_picker, null, false) + + val dialog = Dialog(activity) + val isDialogClosed = AtomicBoolean(false) + + dialog.setOnDismissListener { + if (dismiss_callback != null) dismiss_callback(it) + } + + dialog.setContentView(viewRoot) + if (message != null && message.isNotEmpty()) { + val tv = viewRoot.findViewById(R.id.tvMessage) + tv.visibility = View.VISIBLE + tv.text = message + } + viewRoot.findViewById(R.id.btnCancel).setOnClickListener { + isDialogClosed.set(true) + dialog.cancel() + } + dialog.setCancelable(true) + dialog.setCanceledOnTouchOutside(true) + dialog.setOnCancelListener { isDialogClosed.set(true) } + + val density = activity.resources.displayMetrics.density + + val llAccounts: LinearLayout = viewRoot.findViewById(R.id.llAccounts) + val pad_se = (0.5f + 12f * density).toInt() + val pad_tb = (0.5f + 6f * density).toInt() + + for (a in account_list) { + val lp = LinearLayout.LayoutParams( + LinearLayout.LayoutParams.MATCH_PARENT, + LinearLayout.LayoutParams.WRAP_CONTENT + ) + + val ac = AcctColor.load(a) + + val b = Button(activity) + + if (AcctColor.hasColorBackground(ac)) { + b.background = getAdaptiveRippleDrawableRound(activity, ac.color_bg, ac.color_fg) + } else { + b.setBackgroundResource(R.drawable.btn_bg_transparent_round6dp) + } + if (AcctColor.hasColorForeground(ac)) { + b.textColor = ac.color_fg + } + + b.setPaddingRelative(pad_se, pad_tb, pad_se, pad_tb) + b.gravity = Gravity.START or Gravity.CENTER_VERTICAL + b.isAllCaps = false + b.layoutParams = lp + b.minHeight = (0.5f + 32f * density).toInt() + + val sb = SpannableStringBuilder(ac.nickname) + if (a.last_notification_error?.isNotEmpty() == true) { + sb.append("\n") + val start = sb.length + sb.append(a.last_notification_error) + val end = sb.length + sb.setSpan(RelativeSizeSpan(0.7f), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) + } else if (a.last_subscription_error?.isNotEmpty() == true) { + sb.append("\n") + val start = sb.length + sb.append(a.last_subscription_error) + val end = sb.length + sb.setSpan(RelativeSizeSpan(0.7f), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) + } + b.text = sb + + b.setOnClickListener { + isDialogClosed.set(true) + callback(a) + dialog.dismissSafe() + } + llAccounts.addView(b) + } + + extra_callback(llAccounts, pad_se, pad_tb) + + dialog.show() + } } diff --git a/app/src/main/java/jp/juggler/subwaytooter/dialog/EmojiPicker.kt b/app/src/main/java/jp/juggler/subwaytooter/dialog/EmojiPicker.kt index 10003cc5..e164c55f 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/dialog/EmojiPicker.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/dialog/EmojiPicker.kt @@ -267,7 +267,7 @@ class EmojiPicker( val entries = newList.entries custom_list.clear() custom_categories.clear() - custom_list.ensureCapacity(entries.sumBy { it.value.size }) + custom_list.ensureCapacity(entries.sumOf { it.value.size }) custom_categories.ensureCapacity(entries.size) entries.forEach { val rangeStart = custom_list.size diff --git a/app/src/main/java/jp/juggler/subwaytooter/dialog/LoginForm.kt b/app/src/main/java/jp/juggler/subwaytooter/dialog/LoginForm.kt index 3b43e39e..6d2a2fa3 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/dialog/LoginForm.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/dialog/LoginForm.kt @@ -131,7 +131,7 @@ object LoginForm { val br = BufferedReader(InputStreamReader(inStream, "UTF-8")) while(true) { val s : String = - br.readLine()?.trim { it <= ' ' }?.toLowerCase(Locale.JAPAN) ?: break + br.readLine()?.trim { it <= ' ' }?.lowercase() ?: break if(s.isEmpty()) continue add(s) add(IDN.toASCII(s, IDN.ALLOW_UNASSIGNED)) @@ -155,7 +155,7 @@ object LoginForm { override fun performFiltering(constraint : CharSequence?) : FilterResults = FilterResults().also { result -> if(constraint?.isNotEmpty() == true) { - val key = constraint.toString().toLowerCase(Locale.JAPAN) + val key = constraint.toString().lowercase() // suggestions リストは毎回生成する必要がある。publishResultsと同時にアクセスされる場合がある val suggestions = StringArray() for(s in instance_list) { diff --git a/app/src/main/java/jp/juggler/subwaytooter/notification/PollingForegrounder.kt b/app/src/main/java/jp/juggler/subwaytooter/notification/PollingForegrounder.kt index 42d93bbd..1b9280ff 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/notification/PollingForegrounder.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/notification/PollingForegrounder.kt @@ -59,7 +59,7 @@ class PollingForegrounder : IntentService("PollingForegrounder") { intent_click.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) val pi_click = PendingIntent.getActivity( context, 2, intent_click, - PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE + PendingIntent.FLAG_UPDATE_CURRENT or (if(Build.VERSION.SDK_INT>=23) PendingIntent.FLAG_IMMUTABLE else 0) ) val builder = if (Build.VERSION.SDK_INT >= 26) { diff --git a/app/src/main/java/jp/juggler/subwaytooter/notification/TaskRunner.kt b/app/src/main/java/jp/juggler/subwaytooter/notification/TaskRunner.kt index 0df8021b..b77dcb2b 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/notification/TaskRunner.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/notification/TaskRunner.kt @@ -692,7 +692,7 @@ class TaskRunner( // FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY を付与してはいけない addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) }, - PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE + PendingIntent.FLAG_UPDATE_CURRENT or (if(Build.VERSION.SDK_INT>=23) PendingIntent.FLAG_IMMUTABLE else 0) ) ) @@ -705,7 +705,7 @@ class TaskRunner( data = "subwaytooter://notification_delete/?$params".toUri() }, - PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE + PendingIntent.FLAG_UPDATE_CURRENT or (if(Build.VERSION.SDK_INT>=23) PendingIntent.FLAG_IMMUTABLE else 0) ) ) @@ -748,7 +748,7 @@ class TaskRunner( context, 3, intent_click, - PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE + PendingIntent.FLAG_UPDATE_CURRENT or (if(Build.VERSION.SDK_INT>=23) PendingIntent.FLAG_IMMUTABLE else 0) ) val builder = if (Build.VERSION.SDK_INT >= 26) { diff --git a/app/src/main/java/jp/juggler/subwaytooter/table/AcctColor.kt b/app/src/main/java/jp/juggler/subwaytooter/table/AcctColor.kt index e7b97797..d2023b80 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/table/AcctColor.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/table/AcctColor.kt @@ -50,7 +50,7 @@ class AcctColor { fun save(now : Long) { - val key = acctAscii.toLowerCase(Locale.ENGLISH) + val key = acctAscii.lowercase() try { val cv = ContentValues() @@ -137,7 +137,7 @@ class AcctColor { fun load(acct:Acct) =load(acct.ascii,acct.pretty) fun load(acctAscii: String,acctPretty : String) : AcctColor { - val key = acctAscii.toLowerCase(Locale.ENGLISH) + val key = acctAscii.lowercase() val cached : AcctColor? = mMemoryCache.get(key) if(cached != null) return cached diff --git a/app/src/main/java/jp/juggler/subwaytooter/table/NotificationTracking.kt b/app/src/main/java/jp/juggler/subwaytooter/table/NotificationTracking.kt index ba8b10c6..c1d959cc 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/table/NotificationTracking.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/table/NotificationTracking.kt @@ -1,7 +1,6 @@ package jp.juggler.subwaytooter.table import android.content.ContentValues -import android.content.Context import android.database.sqlite.SQLiteDatabase import android.provider.BaseColumns import jp.juggler.subwaytooter.App1 diff --git a/app/src/main/java/jp/juggler/subwaytooter/util/EmojiDecoder.kt b/app/src/main/java/jp/juggler/subwaytooter/util/EmojiDecoder.kt index a8498b60..72737bc8 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/util/EmojiDecoder.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/util/EmojiDecoder.kt @@ -28,9 +28,9 @@ object EmojiDecoder { // private val log = LogCategory("EmojiDecoder") - private const val cpColon = ':'.toInt() + private const val cpColon = ':'.code - private const val cpZwsp = '\u200B'.toInt() + private const val cpZwsp = '\u200B'.code var handleUnicodeEmoji = true @@ -223,7 +223,7 @@ object EmojiDecoder { continue } - val nextChar = if (result.endPos >= end) null else s[result.endPos].toInt() + val nextChar = if (result.endPos >= end) null else s[result.endPos].code // 絵文字バリエーション・シーケンス(EVS)のU+FE0E(VS-15)が直後にある場合 // その文字を絵文字化しない @@ -232,7 +232,7 @@ object EmojiDecoder { continue } - val emoji = if (nextChar == 0xFE0F && s[result.endPos - 1].toInt() != 0xFE0F) { + val emoji = if (nextChar == 0xFE0F && s[result.endPos - 1].code != 0xFE0F) { // 絵文字の最後が 0xFE0F でない // 直後にU+0xFE0F (絵文字バリエーション・シーケンスEVSのVS-16)がある // 直後のそれまで含めて絵文字として表示する @@ -246,16 +246,16 @@ object EmojiDecoder { } } - private const val codepointColon = ':'.toInt() + private const val codepointColon = ':'.code // private const val codepointAtmark = '@'.toInt() private val shortCodeCharacterSet = SparseBooleanArray().apply { - for (c in 'A'..'Z') put(c.toInt(), true) - for (c in 'a'..'z') put(c.toInt(), true) - for (c in '0'..'9') put(c.toInt(), true) - for (c in "+-_@:") put(c.toInt(), true) - for (c in ".") put(c.toInt(), true) + for (c in 'A'..'Z') put(c.code, true) + for (c in 'a'..'z') put(c.code, true) + for (c in '0'..'9') put(c.code, true) + for (c in "+-_@:") put(c.code, true) + for (c in ".") put(c.code, true) } private interface ShortCodeSplitterCallback { @@ -392,7 +392,7 @@ object EmojiDecoder { else -> { // EmojiOneのショートコード val emoji = if (useEmojioneShortcode) { - EmojiMap.shortNameMap[name.toLowerCase(Locale.JAPAN).replace('-', '_')] + EmojiMap.shortNameMap[name.lowercase().replace('-', '_')] } else { null } @@ -437,7 +437,7 @@ object EmojiDecoder { // カスタム絵文字ではなく通常の絵文字のショートコードなら絵文字に変換する val emoji = if (decodeEmojioneShortcode) { - EmojiMap.shortNameMap[name.toLowerCase(Locale.JAPAN).replace('-', '_')] + EmojiMap.shortNameMap[name.lowercase().replace('-', '_')] } else { null } diff --git a/app/src/main/java/jp/juggler/subwaytooter/util/HTMLDecoder.kt b/app/src/main/java/jp/juggler/subwaytooter/util/HTMLDecoder.kt index ce120489..f63d5c6a 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/util/HTMLDecoder.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/util/HTMLDecoder.kt @@ -258,7 +258,7 @@ object HTMLDecoder { val m = reTag.matcher(text) if (m.find()) { val is_close = m.groupEx(1)!!.isNotEmpty() - tag = m.groupEx(2)!!.toLowerCase(Locale.JAPAN) + tag = m.groupEx(2)!!.lowercase() val m2 = reTagEnd.matcher(text) val is_openclose = when { @@ -369,15 +369,15 @@ object HTMLDecoder { ArrayList().also { dst -> // 入力の末尾のtrim var end = this.length - while (end > 0 && CharacterGroup.isWhitespace(this[end - 1].toInt())) --end + while (end > 0 && CharacterGroup.isWhitespace(this[end - 1].code)) --end // 入力の最初の非空白文字の位置を調べておく var firstNonSpace = 0 - while (firstNonSpace = end) end else i + 1 ++i @@ -758,7 +758,7 @@ object HTMLDecoder { val dst = HashMap() val m = reAttribute.matcher(text) while (m.find()) { - val name = m.groupEx(1)!!.toLowerCase(Locale.JAPAN) + val name = m.groupEx(1)!!.lowercase() val value = decodeEntity(m.groupEx(3)) dst[name] = value } diff --git a/app/src/main/java/jp/juggler/subwaytooter/util/MisskeyMarkdownDecoder.kt b/app/src/main/java/jp/juggler/subwaytooter/util/MisskeyMarkdownDecoder.kt index 3c385d1f..eb22fd2d 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/util/MisskeyMarkdownDecoder.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/util/MisskeyMarkdownDecoder.kt @@ -367,10 +367,10 @@ object MisskeySyntaxHighlighter { addAll(_keywords) // UPPER - addAll(_keywords.map { k -> k.toUpperCase(Locale.JAPAN) }) + addAll(_keywords.map { it.uppercase() }) // Snake - addAll(_keywords.map { k -> k[0].toUpperCase() + k.substring(1) }) + addAll(_keywords.map { k -> k[0].uppercase() + k.substring(1) }) add("NaN") @@ -378,12 +378,12 @@ object MisskeySyntaxHighlighter { } private val symbolMap = SparseBooleanArray().apply { - "=+-*/%~^&|> + symbolMap.get(c.code, false) -> Token(length = 1, color = 0x42b983) c == '-' -> Token(length = 1, color = 0x42b983) @@ -1348,7 +1348,7 @@ object MisskeyMarkdownDecoder { var i = lastEnd //スキャン中の位置 while (i < end) { // 注目位置の文字に関連するパーサー - val lastParsers = nodeParserMap[text[i].toInt()] + val lastParsers = nodeParserMap[text[i].code] if (lastParsers == null) { ++i continue @@ -1538,8 +1538,11 @@ object MisskeyMarkdownDecoder { } // \} \]はムダなエスケープに見えるが、androidでは必要なので削ってはいけない + @Suppress("RegExpRedundantEscape") private val reLatexRemove = """\\(?:quad|Huge|atop|sf|scriptsize|bf|small|tiny|underline|large|(?:color)\{[^}]*\})""".toRegex() + @Suppress("RegExpRedundantEscape") private val reLatex1 = """\\(?:(?:url)|(?:textcolor|colorbox)\{[^}]*\}|(?:fcolorbox|raisebox)\{[^}]*\}\{[^}]*\}|includegraphics\[[^]]*\])\{([^}]*)\}""".toRegex() + @Suppress("RegExpRedundantEscape") private val reLatex2reversed = """\\(?:overset|href)\{([^}]+)\}\{([^}]+)\}""".toRegex() private fun String.removeLatex(): String { @@ -1586,7 +1589,7 @@ object MisskeyMarkdownDecoder { vararg nodeParsers: NodeParseEnv.() -> NodeDetected? ) { for (s in firstChars) { - put(s.toInt(), nodeParsers) + put(s.code, nodeParsers) } } @@ -1808,15 +1811,15 @@ object MisskeyMarkdownDecoder { // メールアドレスの@の手前に使える文字なら真 val mailChars = SparseBooleanArray().apply { for (it in '0'..'9') { - put(it.toInt(), true) + put(it.code, true) } for (it in 'A'..'Z') { - put(it.toInt(), true) + put(it.code, true) } for (it in 'a'..'z') { - put(it.toInt(), true) + put(it.code, true) } - """${'$'}!#%&'`"*+-/=?^_{|}~""".forEach { put(it.toInt(), true) } + """${'$'}!#%&'`"*+-/=?^_{|}~""".forEach { put(it.code, true) } } addParser("@", { diff --git a/app/src/main/java/jp/juggler/subwaytooter/util/PostHelper.kt b/app/src/main/java/jp/juggler/subwaytooter/util/PostHelper.kt index 40b9597e..d1cef760 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/util/PostHelper.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/util/PostHelper.kt @@ -760,7 +760,7 @@ class PostHelper( val cp = src.codePointBefore(i) i -= Character.charCount(cp) - if (cp == '@'.toInt()) { + if (cp == '@'.code) { start = i if (++count_atMark >= 2) break else continue } else if (count_atMark == 1) { @@ -865,7 +865,7 @@ class PostHelper( val remain = limit - code_list.size if (remain > 0) { val s = - src.substring(last_colon + 1, end).toLowerCase(Locale.JAPAN).replace('-', '_') + src.substring(last_colon + 1, end).lowercase().replace('-', '_') val matches = EmojiDecoder.searchShortCode(activity, s, remain) log.d("checkEmoji: search for %s, result=%d", s, matches.size) code_list.addAll(matches) diff --git a/app/src/main/java/jp/juggler/subwaytooter/view/BlurhashView.kt b/app/src/main/java/jp/juggler/subwaytooter/view/BlurhashView.kt index edc6b984..8704d358 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/view/BlurhashView.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/view/BlurhashView.kt @@ -18,7 +18,7 @@ class Blurhash(blurhash : String, punch : Float = 1f) { private val base83Map = SparseIntArray().apply { "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz#$%*+,-.:;=?@[]^_{|}~" .forEachIndexed { index, c -> - put(c.toInt(), index) + put(c.code, index) } } @@ -26,11 +26,9 @@ class Blurhash(blurhash : String, punch : Float = 1f) { private fun String.decodeBase83(start : Int, length : Int) : Int { var v = 0 for(i in start until start + length) { - val ci = this[i].toInt() + val ci = this[i].code val idx = base83Map.get(ci, - 1) - if(idx == - 1) { - error("decodeBase83: incorrect char code $ci") - } + if(idx == - 1) error("decodeBase83: incorrect char code $ci") v = v * 83 + idx } return v diff --git a/app/src/main/java/jp/juggler/util/CharacterGroup.kt b/app/src/main/java/jp/juggler/util/CharacterGroup.kt index 6ad52e7f..6a54a488 100644 --- a/app/src/main/java/jp/juggler/util/CharacterGroup.kt +++ b/app/src/main/java/jp/juggler/util/CharacterGroup.kt @@ -109,7 +109,7 @@ object CharacterGroup { var id = Integer.MAX_VALUE for(s in list) { if(s.length == 1) { - val c = s[0].toInt() + val c = s[0].code if(c < id) id = c } } @@ -130,7 +130,7 @@ object CharacterGroup { // ユニコード文字を正規化する。 // 簡易版なので全ての文字には対応していない fun getUnifiedCharacter(c : Char) : Char { - val v1 = map1[c.toInt()] + val v1 = map1[c.code] return if(v1 != 0) v1.toChar() else c } @@ -145,13 +145,13 @@ object CharacterGroup { val map : SparseIntArray val key : Int - val v1 = s[0].toInt() + val v1 = s[0].code if(s.length == 1) { map = map1 key = v1 } else { map = map2 - val v2 = s[1].toInt() + val v2 = s[1].code key = v1 or (v2 shl 16) } @@ -183,7 +183,7 @@ object CharacterGroup { var pos = offset // 空白を読み飛ばす - while(pos < end && isWhitespace(text[pos].toInt())) ++ pos + while(pos < end && isWhitespace(text[pos].code)) ++ pos // 終端までの文字数 val remain = end - pos @@ -193,7 +193,7 @@ object CharacterGroup { return END } - val v1 = text[pos].toInt() + val v1 = text[pos].code // グループに登録された文字を長い順にチェック var check_len = if(remain > 2) 2 else remain @@ -201,7 +201,7 @@ object CharacterGroup { val group_id = if(check_len == 1) map1.get(v1) else - map2.get(v1 or (text[pos + 1].toInt() shl 16)) + map2.get(v1 or (text[pos + 1].code shl 16)) if(group_id != 0) { this.offset = pos + check_len return group_id diff --git a/app/src/main/java/jp/juggler/util/Json.kt b/app/src/main/java/jp/juggler/util/Json.kt index 7a2dc6be..2e078583 100644 --- a/app/src/main/java/jp/juggler/util/Json.kt +++ b/app/src/main/java/jp/juggler/util/Json.kt @@ -853,7 +853,7 @@ private fun Writer.writeQuote(string: String): Writer { in '\u0080' until '\u00a0', in '\u2000' until '\u2100' -> { write("\\u") - val hexCode: String = Integer.toHexString(c.toInt()) + val hexCode: String = Integer.toHexString(c.code) write("0000", 0, 4 - hexCode.length) write(hexCode) } @@ -1046,7 +1046,7 @@ fun Writer.writeJsonValue( } } - value is Char -> writeJsonValue(indentFactor, indent, value.toInt(), sort = sort) + value is Char -> writeJsonValue(indentFactor, indent, value.code, sort = sort) value is String -> writeQuote(value) value is Enum<*> -> writeQuote(value.name) diff --git a/app/src/main/java/jp/juggler/util/StorageUtils.kt b/app/src/main/java/jp/juggler/util/StorageUtils.kt index bc64cff2..b98c28ff 100644 --- a/app/src/main/java/jp/juggler/util/StorageUtils.kt +++ b/app/src/main/java/jp/juggler/util/StorageUtils.kt @@ -182,7 +182,7 @@ private val mimeTypeExMap : HashMap by lazy { fun getMimeType(log : LogCategory?, src : String) : String { var ext = MimeTypeMap.getFileExtensionFromUrl(src) if(ext != null && ext.isNotEmpty()) { - ext = ext.toLowerCase(Locale.US) + ext = ext.lowercase() // var mime_type : String? = MimeTypeMap.getSingleton().getMimeTypeFromExtension(ext) diff --git a/app/src/main/java/jp/juggler/util/StringUtils.kt b/app/src/main/java/jp/juggler/util/StringUtils.kt index 5e0c0a24..b6fd02fe 100644 --- a/app/src/main/java/jp/juggler/util/StringUtils.kt +++ b/app/src/main/java/jp/juggler/util/StringUtils.kt @@ -81,7 +81,7 @@ fun IntArray.toByteArray(): ByteArray { fun CharArray.toLowerByteArray(): ByteArray { val dst = ByteArray(this.size) for (i in this.indices) { - dst[i] = this[i].toByte() + dst[i] = this[i].code.toByte() } return dst } @@ -124,7 +124,7 @@ fun CharSequence.codePointBefore(index: Int): Int { val c1 = this[index - 2] if (Character.isHighSurrogate(c1)) return Character.toCodePoint(c1, c2) } - return c2.toInt() + return c2.code } else { return -1 } diff --git a/app/src/main/java/jp/juggler/util/WordTrieTree.kt b/app/src/main/java/jp/juggler/util/WordTrieTree.kt index 3e8580ab..bc2f9083 100644 --- a/app/src/main/java/jp/juggler/util/WordTrieTree.kt +++ b/app/src/main/java/jp/juggler/util/WordTrieTree.kt @@ -152,7 +152,7 @@ class WordTrieTree { val t = CharacterGroup.Tokenizer() for(i in start until end) { - if(! CharacterGroup.isWhitespace(src[i].toInt())) { + if(! CharacterGroup.isWhitespace(src[i].code)) { val item = match(true, t.reset(src, i, end)) if(item != null) return item } @@ -174,7 +174,7 @@ class WordTrieTree { var i = start while(i < end) { - if(! CharacterGroup.isWhitespace(src[i].toInt())) { + if(! CharacterGroup.isWhitespace(src[i].code)) { val item = match(false, t.reset(src, i, end)) if(item != null) { if(dst == null) dst = ArrayList() diff --git a/app/src/test/java/jp/juggler/subwaytooter/TestKotlinFeature.kt b/app/src/test/java/jp/juggler/subwaytooter/TestKotlinFeature.kt index b2a66f87..c9dde98f 100644 --- a/app/src/test/java/jp/juggler/subwaytooter/TestKotlinFeature.kt +++ b/app/src/test/java/jp/juggler/subwaytooter/TestKotlinFeature.kt @@ -131,12 +131,10 @@ class TestKotlinFeature { // 型が分からないようにするとビルドできるが、intの10とlongの10は異なると判断される // Int.equals も同じ結果 assertEquals(false, int10 as Any == long10 as Any) - assertEquals(false, int10.equals(long10)) - + // Long.equals でも同じ結果 assertEquals(false, long10 as Any == int10 as Any) - assertEquals(false, long10.equals(int10)) - + // 同じ型に変換すると数値が同じだと判定できる assertEquals(true, int10.toLong() == long10) assertEquals(true, int10.toLong() == long10) diff --git a/build.gradle b/build.gradle index ed2ec319..0eab5a9a 100644 --- a/build.gradle +++ b/build.gradle @@ -3,13 +3,15 @@ buildscript { ext.min_sdk_version = 21 ext.target_sdk_version = 30 ext.compile_sdk_version = 30 - ext.appcompat_version='1.2.0' - ext.kotlin_version = '1.4.32' - ext.kotlinx_coroutines_version = '1.4.2' + ext.appcompat_version='1.2.0' + ext.lifecycle_version='2.3.1' + + ext.kotlin_version = '1.5.0' + ext.kotlinx_coroutines_version = '1.4.3' ext.anko_version='0.10.8' - ext.junit_version='4.13.1' + ext.junit_version='4.13.2' repositories { google() @@ -20,6 +22,7 @@ buildscript { classpath 'com.android.tools.build:gradle:4.2.0' classpath 'com.google.gms:google-services:4.3.5' + //noinspection DifferentKotlinGradleVersion classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "com.github.bjoernq:unmockplugin:0.7.6" diff --git a/emoji/src/main/java/jp/juggler/emoji/EmojiMap.kt b/emoji/src/main/java/jp/juggler/emoji/EmojiMap.kt index d6c19391..537a77f4 100644 --- a/emoji/src/main/java/jp/juggler/emoji/EmojiMap.kt +++ b/emoji/src/main/java/jp/juggler/emoji/EmojiMap.kt @@ -34,7 +34,7 @@ object EmojiMap { // 素の数字とcopyright,registered, trademark は絵文字にしない fun isIgnored(code: String): Boolean { - val c = code[0].toInt() + val c = code[0].code return code.length == 1 && c <= 0xae } diff --git a/emoji/src/main/java/jp/juggler/emoji/EmojiTrie.kt b/emoji/src/main/java/jp/juggler/emoji/EmojiTrie.kt index ccdbecd6..968d2136 100644 --- a/emoji/src/main/java/jp/juggler/emoji/EmojiTrie.kt +++ b/emoji/src/main/java/jp/juggler/emoji/EmojiTrie.kt @@ -15,17 +15,17 @@ class EmojiTrie { this.data = data return } - val c = src[offset].toInt() + val c = src[offset].code val next = map[c] ?: EmojiTrie().also { map.put(c, it) } next.append(src, offset + 1, data) } - fun hasNext(c: Char) = map.containsKey(c.toInt()) + fun hasNext(c: Char) = map.containsKey(c.code) fun get(src: String, offset: Int, end: Int): Result? { // 長い方を優先するので、先に子を調べる if (offset < end) - map[src[offset].toInt()]?.get(src, offset + 1, end) + map[src[offset].code]?.get(src, offset + 1, end) ?.let { return it } return this.data?.let { Result(it, offset) } } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 4a2989d4..d26fa4d3 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ #Wed May 05 20:20:56 JST 2021 distributionBase=GRADLE_USER_HOME -distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.9-bin.zip distributionPath=wrapper/dists zipStorePath=wrapper/dists zipStoreBase=GRADLE_USER_HOME diff --git a/sample_apng/build.gradle b/sample_apng/build.gradle index 7d6e9ab4..e5dcc411 100644 --- a/sample_apng/build.gradle +++ b/sample_apng/build.gradle @@ -40,6 +40,9 @@ dependencies { implementation project(':apng_android') implementation "androidx.appcompat:appcompat:$appcompat_version" + //noinspection KtxExtensionAvailable + implementation "androidx.lifecycle:lifecycle-viewmodel:$lifecycle_version" + testImplementation "junit:junit:$junit_version" androidTestImplementation 'androidx.test:runner:1.3.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' diff --git a/sample_apng/src/main/java/jp/juggler/apng/sample/ActList.kt b/sample_apng/src/main/java/jp/juggler/apng/sample/ActList.kt index f19db7a1..929147aa 100644 --- a/sample_apng/src/main/java/jp/juggler/apng/sample/ActList.kt +++ b/sample_apng/src/main/java/jp/juggler/apng/sample/ActList.kt @@ -5,9 +5,6 @@ import android.content.pm.PackageManager import android.os.Build import android.os.Bundle import android.os.SystemClock -import androidx.core.app.ActivityCompat -import androidx.core.content.ContextCompat -import androidx.appcompat.app.AppCompatActivity import android.util.Log import android.view.View import android.view.ViewGroup @@ -15,206 +12,209 @@ import android.widget.AdapterView import android.widget.BaseAdapter import android.widget.ListView import android.widget.TextView +import androidx.appcompat.app.AppCompatActivity +import androidx.core.app.ActivityCompat +import androidx.core.content.ContextCompat import jp.juggler.apng.ApngFrames import kotlinx.coroutines.* import kotlin.coroutines.CoroutineContext class ActList : AppCompatActivity(), CoroutineScope { - - companion object { - const val TAG = "ActList" - - const val PERMISSION_REQUEST_CODE_STORAGE = 1 - } - - class ListItem(val id : Int, val caption : String) - - private lateinit var listView : ListView - private lateinit var listAdapter : MyAdapter - private var timeAnimationStart : Long = 0L - - private lateinit var activityJob : Job - - override val coroutineContext : CoroutineContext - get() = Dispatchers.Main + activityJob - - override fun onCreate(savedInstanceState : Bundle?) { - - activityJob = Job() - - super.onCreate(savedInstanceState) - setContentView(R.layout.act_list) - this.listView = findViewById(R.id.listView) - listAdapter = MyAdapter() - listView.adapter = listAdapter - listView.onItemClickListener = listAdapter - timeAnimationStart = SystemClock.elapsedRealtime() - - if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - // Assume thisActivity is the current activity - if(PackageManager.PERMISSION_GRANTED != ContextCompat.checkSelfPermission( - this, - Manifest.permission.WRITE_EXTERNAL_STORAGE - )) { - ActivityCompat.requestPermissions( - this, - arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), - - PERMISSION_REQUEST_CODE_STORAGE - ) - } - } - load() - } - - override fun onDestroy() { - super.onDestroy() - activityJob.cancel() - } - - override fun onRequestPermissionsResult( - requestCode : Int, - permissions : Array, - grantResults : IntArray - ) { - when(requestCode) { - PERMISSION_REQUEST_CODE_STORAGE -> { - // If request is cancelled, the result arrays are empty. - if(grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { - // permission was granted, yay! Do the - // contacts-related task you need to do. - } else { - // permission denied, boo! Disable the - // functionality that depends on this permission. - } - return - } - // other 'case' lines to check for other - // permissions this app might request - } - } - - private fun load() = launch { - val list = async(Dispatchers.IO) { - // RawリソースのIDと名前の一覧 - R.raw::class.java.fields - .mapNotNull { it.get(null) as? Int } - .map { id -> - ListItem( - id, - resources.getResourceName(id) - .replaceFirst(""".+/""".toRegex(), "") - ) - } - .toMutableList() - .apply { sortBy { it.caption } } - - }.await() - - listAdapter.list.addAll(list) - listAdapter.notifyDataSetChanged() - } - - inner class MyAdapter : BaseAdapter(), AdapterView.OnItemClickListener { - - val list = ArrayList() - - override fun getCount() : Int { - return list.size - } - - override fun getItem(position : Int) : Any { - return list[position] - } - - override fun getItemId(position : Int) : Long { - return list[position].id.toLong() - } - - override fun getView( - position : Int, - viewArg : View?, - parent : ViewGroup? - ) : View { - val view : View - val holder : MyViewHolder - if(viewArg == null) { - view = layoutInflater.inflate(R.layout.lv_item, parent, false) - holder = MyViewHolder(view, this@ActList) - view.tag = holder - } else { - view = viewArg - holder = view.tag as MyViewHolder - } - holder.bind(list[position]) - return view - } - - override fun onItemClick( - parent : AdapterView<*>?, - view : View?, - position : Int, - id : Long - ) { - val item = list[position] - ActViewer.open(this@ActList, item.id, item.caption) - } - - } - - inner class MyViewHolder( - viewRoot : View, - _activity : ActList - ) { - - private val tvCaption : TextView = viewRoot.findViewById(R.id.tvCaption) - private val apngView : ApngView = viewRoot.findViewById(R.id.apngView) - - init { - apngView.timeAnimationStart = _activity.timeAnimationStart - } - - private var lastId : Int = 0 - private var lastJob : Job? = null - - fun bind(listItem : ListItem) { - tvCaption.text = listItem.caption - - val resId = listItem.id - if(lastId != resId) { - lastId = resId - apngView.apngFrames?.dispose() - apngView.apngFrames = null - launch { - var apngFrames : ApngFrames? = null - try { - lastJob?.cancelAndJoin() - - val job = async(Dispatchers.IO) { - try { - ApngFrames.parse(128){resources?.openRawResource(resId)} - } catch(ex : Throwable) { - ex.printStackTrace() - null - } - } - - lastJob = job - apngFrames = job.await() - - if(apngFrames != null && lastId == resId) { - apngView.apngFrames = apngFrames - apngFrames = null - } - - } catch(ex : Throwable) { - ex.printStackTrace() - Log.e(TAG, "load error: ${ex.javaClass.simpleName} ${ex.message}") - } finally { - apngFrames?.dispose() - } - } - } - } - } + + companion object { + const val TAG = "ActList" + + const val PERMISSION_REQUEST_CODE_STORAGE = 1 + } + + class ListItem(val id: Int, val caption: String) + + private lateinit var listView: ListView + private lateinit var listAdapter: MyAdapter + private var timeAnimationStart: Long = 0L + + private lateinit var activityJob: Job + + override val coroutineContext: CoroutineContext + get() = Dispatchers.Main + activityJob + + override fun onCreate(savedInstanceState: Bundle?) { + + activityJob = Job() + + super.onCreate(savedInstanceState) + setContentView(R.layout.act_list) + this.listView = findViewById(R.id.listView) + listAdapter = MyAdapter() + listView.adapter = listAdapter + listView.onItemClickListener = listAdapter + timeAnimationStart = SystemClock.elapsedRealtime() + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + // Assume thisActivity is the current activity + if (PackageManager.PERMISSION_GRANTED != ContextCompat.checkSelfPermission( + this, + Manifest.permission.WRITE_EXTERNAL_STORAGE + ) + ) { + ActivityCompat.requestPermissions( + this, + arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), + + PERMISSION_REQUEST_CODE_STORAGE + ) + } + } + load() + } + + override fun onDestroy() { + super.onDestroy() + activityJob.cancel() + } + + override fun onRequestPermissionsResult( + requestCode: Int, + permissions: Array, + grantResults: IntArray + ) { + when (requestCode) { + PERMISSION_REQUEST_CODE_STORAGE -> { + // If request is cancelled, the result arrays are empty. + if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + // permission was granted, yay! Do the + // contacts-related task you need to do. + } else { + // permission denied, boo! Disable the + // functionality that depends on this permission. + } + return + } + // other 'case' lines to check for other + // permissions this app might request + } + } + + private fun load() = launch { + val list = withContext(Dispatchers.IO) { + // RawリソースのIDと名前の一覧 + R.raw::class.java.fields + .mapNotNull { it.get(null) as? Int } + .map { id -> + ListItem( + id, + resources.getResourceName(id) + .replaceFirst(""".+/""".toRegex(), "") + ) + } + .toMutableList() + .apply { sortBy { it.caption } } + } + + listAdapter.list.addAll(list) + listAdapter.notifyDataSetChanged() + } + + inner class MyAdapter : BaseAdapter(), AdapterView.OnItemClickListener { + + val list = ArrayList() + + override fun getCount(): Int { + return list.size + } + + override fun getItem(position: Int): Any { + return list[position] + } + + override fun getItemId(position: Int): Long { + return list[position].id.toLong() + } + + override fun getView( + position: Int, + viewArg: View?, + parent: ViewGroup? + ): View { + val view: View + val holder: MyViewHolder + if (viewArg == null) { + view = layoutInflater.inflate(R.layout.lv_item, parent, false) + holder = MyViewHolder(view, this@ActList) + view.tag = holder + } else { + view = viewArg + holder = view.tag as MyViewHolder + } + holder.bind(list[position]) + return view + } + + override fun onItemClick( + parent: AdapterView<*>?, + view: View?, + position: Int, + id: Long + ) { + val item = list[position] + ActViewer.open(this@ActList, item.id, item.caption) + } + + } + + inner class MyViewHolder( + viewRoot: View, + _activity: ActList + ) { + + private val tvCaption: TextView = viewRoot.findViewById(R.id.tvCaption) + private val apngView: ApngView = viewRoot.findViewById(R.id.apngView) + + init { + apngView.timeAnimationStart = _activity.timeAnimationStart + } + + private var lastId: Int = 0 + private var lastJob: Job? = null + + fun bind(listItem: ListItem) { + tvCaption.text = listItem.caption + + val resId = listItem.id + if (lastId != resId) { + lastId = resId + apngView.apngFrames?.dispose() + apngView.apngFrames = null + launch { + var apngFrames: ApngFrames? = null + try { + lastJob?.cancelAndJoin() + + val job = async(Dispatchers.IO) { + try { + ApngFrames.parse(128) { resources?.openRawResource(resId) } + } catch (ex: Throwable) { + ex.printStackTrace() + null + } + } + + lastJob = job + apngFrames = job.await() + + if (apngFrames != null && lastId == resId) { + apngView.apngFrames = apngFrames + apngFrames = null + } + + } catch (ex: Throwable) { + ex.printStackTrace() + Log.e(TAG, "load error: ${ex.javaClass.simpleName} ${ex.message}") + } finally { + apngFrames?.dispose() + } + } + } + } + } } diff --git a/sample_apng/src/main/java/jp/juggler/apng/sample/ApngView.kt b/sample_apng/src/main/java/jp/juggler/apng/sample/ApngView.kt index 8135e2c9..885135ef 100644 --- a/sample_apng/src/main/java/jp/juggler/apng/sample/ApngView.kt +++ b/sample_apng/src/main/java/jp/juggler/apng/sample/ApngView.kt @@ -8,6 +8,7 @@ import android.os.SystemClock import android.util.AttributeSet import android.view.View import jp.juggler.apng.ApngFrames +import kotlin.math.max class ApngView : View{ @@ -64,8 +65,8 @@ class ApngView : View{ override fun onSizeChanged(w : Int, h : Int, oldw : Int, oldh : Int) { super.onSizeChanged(w, h, oldw, oldh) - wView = Math.max(1, w).toFloat() - hView = Math.max(1, h).toFloat() + wView = max(1, w).toFloat() + hView = max(1, h).toFloat() aspectView = wView / hView initializeScale() @@ -74,8 +75,8 @@ class ApngView : View{ private fun initializeScale(){ val apngFrames = this.apngFrames if( apngFrames != null) { - wImage =Math.max(1, apngFrames.width).toFloat() - hImage =Math.max(1, apngFrames.height).toFloat() + wImage = max(1, apngFrames.width).toFloat() + hImage = max(1, apngFrames.height).toFloat() aspectImage = wImage / hImage currentScale = if(aspectView > aspectImage) { @@ -118,7 +119,7 @@ class ApngView : View{ canvas.drawBitmap(bitmap, drawMatrix, paint) if( delay != Long.MAX_VALUE){ - postInvalidateDelayed(Math.max(1L,delay)) + postInvalidateDelayed(max(1L,delay)) } } } diff --git a/sample_apng/src/main/res/layout/act_list.xml b/sample_apng/src/main/res/layout/act_list.xml index 68bc45b3..6331dc7e 100644 --- a/sample_apng/src/main/res/layout/act_list.xml +++ b/sample_apng/src/main/res/layout/act_list.xml @@ -5,7 +5,6 @@ android:layout_height="match_parent" >