- 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
This commit is contained in:
tateisu 2021-05-08 14:56:34 +09:00
parent 5a3dcd8704
commit e8222fa2d8
45 changed files with 1170 additions and 1139 deletions

View File

@ -3,6 +3,7 @@
package jp.juggler.apng package jp.juggler.apng
import jp.juggler.apng.util.getUInt8 import jp.juggler.apng.util.getUInt8
import kotlin.math.min
class ApngPalette( class ApngPalette(
src : ByteArray // repeat of R,G,B src : ByteArray // repeat of R,G,B
@ -35,7 +36,7 @@ class ApngPalette(
// update alpha value from tRNS chunk data // update alpha value from tRNS chunk data
fun parseTRNS(ba : ByteArray) { fun parseTRNS(ba : ByteArray) {
hasAlpha = true 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) list[i] = (list[i] and 0xffffff) or (ba.getUInt8(i) shl 24)
} }
} }

View File

@ -1,6 +1,7 @@
package jp.juggler.apng package jp.juggler.apng
import java.io.InputStream import java.io.InputStream
import java.lang.StringBuilder
import kotlin.math.min import kotlin.math.min
// https://raw.githubusercontent.com/rtyley/animated-gif-lib-for-java/master/src/main/java/com/madgag/gif/fmsware/GifDecoder.java // 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 // Reads specified bytes and compose it to ascii string
fun string(n : Int) : String { fun string(n : Int) : String {
val ba = ByteArray(n) return StringBuilder(n).apply{
array(ba) ByteArray(n)
return ba.map { it.toChar() }.joinToString(separator = "") .also{ array(it)}
.forEach { append( Char( it.toInt() and 255)) }
}.toString()
} }
// Reads next variable length block // Reads next variable length block
@ -478,11 +481,11 @@ class GifDecoder(val callback : GifDecoderCallback) {
// application extension // application extension
0xff -> { 0xff -> {
val block = reader.block() val block = reader.block()
var app = "" val app = StringBuilder(12)
for(i in 0 until 11) { 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) readNetscapeExt(reader)
} else { } else {
reader.skipBlock() // don't care reader.skipBlock() // don't care

File diff suppressed because it is too large Load Diff

View File

@ -1,13 +1,14 @@
package jp.juggler.apng.util package jp.juggler.apng.util
import java.util.* import java.util.*
import kotlin.math.min
internal class ByteSequenceQueue(private val bufferRecycler : (ByteSequence) -> Unit) { internal class ByteSequenceQueue(private val bufferRecycler : (ByteSequence) -> Unit) {
private val list = LinkedList<ByteSequence>() private val list = LinkedList<ByteSequence>()
val remain : Int val remain : Int
get() = list.sumBy { it.length } get() = list.sumOf { it.length }
fun add(range : ByteSequence) =list.add(range) fun add(range : ByteSequence) =list.add(range)
@ -22,7 +23,7 @@ internal class ByteSequenceQueue(private val bufferRecycler : (ByteSequence) ->
bufferRecycler(item) bufferRecycler(item)
list.removeFirst() list.removeFirst()
} else { } else {
val delta = Math.min(item.length, dstRemain) val delta = min(item.length, dstRemain)
System.arraycopy(item.array, item.offset, dst, dstOffset, delta) System.arraycopy(item.array, item.offset, dst, dstOffset, delta)
dstOffset += delta dstOffset += delta
dstRemain -= delta dstRemain -= delta

View File

@ -11,6 +11,7 @@ import android.util.Log
import java.io.InputStream import java.io.InputStream
import java.util.ArrayList import java.util.ArrayList
import kotlin.math.max import kotlin.math.max
import kotlin.math.min
class ApngFrames private constructor( class ApngFrames private constructor(
private val pixelSizeMax : Int = 0, private val pixelSizeMax : Int = 0,
@ -125,8 +126,17 @@ class ApngFrames private constructor(
throw ex 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( fun parse(
pixelSizeMax : Int, pixelSizeMax : Int,
debug : Boolean = false, debug : Boolean = false,
@ -136,18 +146,11 @@ class ApngFrames private constructor(
val buf = ByteArray(8) { 0.toByte() } val buf = ByteArray(8) { 0.toByte() }
opener()?.use { it.read(buf, 0, buf.size) } opener()?.use { it.read(buf, 0, buf.size) }
if(buf.size >= 8 if(buf.size >= 8 && matchBytes(buf, apngHeadKey) ) {
&& (buf[0].toInt() and 0xff) == 0x89
&& (buf[1].toInt() and 0xff) == 0x50
) {
return opener()?.use { parseApng(it, pixelSizeMax, debug) } return opener()?.use { parseApng(it, pixelSizeMax, debug) }
} }
if(buf.size >= 6 if(buf.size >= 6 && matchBytes(buf, gifHeadKey) ) {
&& buf[0].toChar() == 'G'
&& buf[1].toChar() == 'I'
&& buf[2].toChar() == 'F'
) {
return opener()?.use { parseGif(it, pixelSizeMax, debug) } return opener()?.use { parseGif(it, pixelSizeMax, debug) }
} }

View File

@ -97,6 +97,9 @@ dependencies {
implementation "androidx.appcompat:appcompat:$appcompat_version" implementation "androidx.appcompat:appcompat:$appcompat_version"
//noinspection KtxExtensionAvailable
implementation "androidx.lifecycle:lifecycle-viewmodel:$lifecycle_version"
// DrawerLayout // DrawerLayout
implementation "androidx.drawerlayout:drawerlayout:1.1.1" implementation "androidx.drawerlayout:drawerlayout:1.1.1"
@ -113,12 +116,12 @@ dependencies {
implementation "androidx.browser:browser:1.3.0" implementation "androidx.browser:browser:1.3.0"
// Recyclerview // 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 // 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-stdlib:$kotlin_version"
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
@ -174,7 +177,7 @@ dependencies {
implementation 'com.astuetz:pagerslidingtabstrip:1.0.1' 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. 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' Example of androidX reference: 'androidx/core/app/NotificationCompat$Builder'

View File

@ -48,15 +48,15 @@ class WordTrieTreeTest {
val tokenizer = CharacterGroup.Tokenizer().reset(strTest, 0, strTest.length) val tokenizer = CharacterGroup.Tokenizer().reset(strTest, 0, strTest.length)
// //
id = tokenizer.next() id = tokenizer.next()
assertEquals('A'.toInt(), id) assertEquals('A'.code, id)
assertEquals((whitespace_len + 1), tokenizer.offset) // offset は Aの次の位置になる assertEquals((whitespace_len + 1), tokenizer.offset) // offset は Aの次の位置になる
// //
id = tokenizer.next() id = tokenizer.next()
assertEquals('B'.toInt(), id) assertEquals('B'.code, id)
assertEquals((whitespace_len + 2).toLong(), tokenizer.offset.toLong()) assertEquals((whitespace_len + 2).toLong(), tokenizer.offset.toLong())
// //
id = tokenizer.next() id = tokenizer.next()
assertEquals('C'.toInt(), id) assertEquals('C'.code, id)
assertEquals((whitespace_len + 3).toLong(), tokenizer.offset.toLong()) assertEquals((whitespace_len + 3).toLong(), tokenizer.offset.toLong())
// //
id = tokenizer.next() id = tokenizer.next()
@ -71,15 +71,15 @@ class WordTrieTreeTest {
// //
id = tokenizer.next() id = tokenizer.next()
assertEquals((whitespace_len + 1).toLong(), tokenizer.offset.toLong()) assertEquals((whitespace_len + 1).toLong(), tokenizer.offset.toLong())
assertEquals('A'.toInt(), id) assertEquals('A'.code, id)
// //
id = tokenizer.next() id = tokenizer.next()
assertEquals((whitespace_len + 2).toLong(), tokenizer.offset.toLong()) assertEquals((whitespace_len + 2).toLong(), tokenizer.offset.toLong())
assertEquals('B'.toInt(), id) assertEquals('B'.code, id)
// //
id = tokenizer.next() id = tokenizer.next()
assertEquals((whitespace_len + 3).toLong(), tokenizer.offset.toLong()) assertEquals((whitespace_len + 3).toLong(), tokenizer.offset.toLong())
assertEquals('C'.toInt(), id) assertEquals('C'.code, id)
// //
id = tokenizer.next() id = tokenizer.next()
assertEquals((whitespace_len + 3).toLong(), tokenizer.offset.toLong()) assertEquals((whitespace_len + 3).toLong(), tokenizer.offset.toLong())
@ -93,15 +93,15 @@ class WordTrieTreeTest {
// //
id = tokenizer.next() id = tokenizer.next()
assertEquals((whitespace_len + 1).toLong(), tokenizer.offset.toLong()) assertEquals((whitespace_len + 1).toLong(), tokenizer.offset.toLong())
assertEquals('A'.toInt(), id) assertEquals('A'.code, id)
// //
id = tokenizer.next() id = tokenizer.next()
assertEquals((whitespace_len + 2).toLong(), tokenizer.offset.toLong()) assertEquals((whitespace_len + 2).toLong(), tokenizer.offset.toLong())
assertEquals('B'.toInt(), id) assertEquals('B'.code, id)
// //
id = tokenizer.next() id = tokenizer.next()
assertEquals((whitespace_len + 3).toLong(), tokenizer.offset.toLong()) assertEquals((whitespace_len + 3).toLong(), tokenizer.offset.toLong())
assertEquals('C'.toInt(), id) assertEquals('C'.code, id)
// //
id = tokenizer.next() id = tokenizer.next()
assertEquals((whitespace_len + 3).toLong(), tokenizer.offset.toLong()) assertEquals((whitespace_len + 3).toLong(), tokenizer.offset.toLong())
@ -115,15 +115,15 @@ class WordTrieTreeTest {
// //
id = tokenizer.next() id = tokenizer.next()
assertEquals((whitespace_len + 1).toLong(), tokenizer.offset.toLong()) assertEquals((whitespace_len + 1).toLong(), tokenizer.offset.toLong())
assertEquals('A'.toInt(), id) assertEquals('A'.code, id)
// //
id = tokenizer.next() id = tokenizer.next()
assertEquals((whitespace_len + 2).toLong(), tokenizer.offset.toLong()) assertEquals((whitespace_len + 2).toLong(), tokenizer.offset.toLong())
assertEquals('B'.toInt(), id) assertEquals('B'.code, id)
// //
id = tokenizer.next() id = tokenizer.next()
assertEquals((whitespace_len + 3).toLong(), tokenizer.offset.toLong()) assertEquals((whitespace_len + 3).toLong(), tokenizer.offset.toLong())
assertEquals('C'.toInt(), id) assertEquals('C'.code, id)
// //
id = tokenizer.next() id = tokenizer.next()
assertEquals((whitespace_len + 3).toLong(), tokenizer.offset.toLong()) assertEquals((whitespace_len + 3).toLong(), tokenizer.offset.toLong())

View File

@ -7,6 +7,7 @@
<uses-permission android:name="android.permission.WAKE_LOCK" /> <uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<!-- CAMERAパーミッションをつけるとPlayストアにプライバシーポリシーを記載する必要がある --> <!-- CAMERAパーミッションをつけるとPlayストアにプライバシーポリシーを記載する必要がある -->

View File

@ -13,126 +13,143 @@ import jp.juggler.subwaytooter.util.openBrowser
import jp.juggler.util.LogCategory import jp.juggler.util.LogCategory
class ActAbout : AppCompatActivity() { class ActAbout : AppCompatActivity() {
class Translators( class Translators(
val name : String, val name: String,
val acct : String?, val acct: String?,
val lang : String val lang: String
) )
companion object { companion object {
val log = LogCategory("ActAbout") val log = LogCategory("ActAbout")
const val EXTRA_SEARCH = "search" const val EXTRA_SEARCH = "search"
const val developer_acct = "tateisu@mastodon.juggler.jp" const val developer_acct = "tateisu@mastodon.juggler.jp"
const val official_acct = "SubwayTooter@mastodon.juggler.jp" const val official_acct = "SubwayTooter@mastodon.juggler.jp"
const val url_release = "https://github.com/tateisu/SubwayTooter/releases" const val url_release = "https://github.com/tateisu/SubwayTooter/releases"
const val url_weblate = "https://hosted.weblate.org/projects/subway-tooter/" const val url_weblate = "https://hosted.weblate.org/projects/subway-tooter/"
// git log --pretty=format:"%an %s" |grep "Translated using Weblate"|sort|uniq // git log --pretty=format:"%an %s" |grep "Translated using Weblate"|sort|uniq
val translators = arrayOf( val translators = arrayOf(
Translators("Allan Nordhøy", null, "English & Norwegian Bokmål"), Translators("Allan Nordhøy", null, "English, Norwegian Bokmål"),
Translators("ButterflyOfFire", "@ButterflyOfFire@mstdn.fr", "Arabic & French"), Translators("ayiniho", null, "French"),
Translators("Ch", null, "Korean"), Translators("ButterflyOfFire", "@ButterflyOfFire@mstdn.fr", "Arabic, French, Kabyle"),
Translators("Elizabeth Sherrock", null, "Chinese (Simplified)"), Translators("Ch", null, "Korean"),
Translators("Gennady Archangorodsky", null, "Hebrew"), Translators("chinnux", "@chinnux@neko.ci", "Chinese (Simplified)"),
Translators("inqbs Siina", null, "Korean"), Translators("Dyxang", null, "Chinese (Simplified)"),
Translators("Jeong Arm", "@jarm@qdon.space", "Korean"), Translators("Elizabeth Sherrock", null, "Chinese (Simplified)"),
Translators("Joan Pujolar", "@jpujolar@mastodont.cat", "Catalan"), Translators("Gennady Archangorodsky", null, "Hebrew"),
Translators("Kai Zhang", "@bearzk@mastodon.social", "Chinese (Simplified)"), Translators("inqbs Siina", null, "Korean"),
Translators("lptprjh", null, "Korean"), Translators("J. Lavoie", null, "French, German"),
Translators("mynameismonkey", null, "Welsh"), Translators("Jeong Arm", "@jarm@qdon.space", "Korean"),
Translators("Nathan", null, "French"), Translators("Joan Pujolar", "@jpujolar@mastodont.cat", "Catalan"),
Translators("Owain Rhys Lewis", null, "Welsh"), Translators("Kai Zhang", "@bearzk@mastodon.social", "Chinese (Simplified)"),
Translators("Swann Martinet", null, "French"), Translators("koyu", null, "German"),
Translators("takubunn", null, "Chinese (Simplified)"), Translators("Liaizon Wakest", null, "English"),
Translators("배태길", null, "Korea") Translators("lingcas", null, "Chinese (Traditional)"),
) Translators("Love Xu", null, "Chinese (Simplified)"),
} Translators("lptprjh", null, "Korean"),
Translators("mv87", null, "German"),
override fun onCreate(savedInstanceState : Bundle?) { Translators("mynameismonkey", null, "Welsh"),
super.onCreate(savedInstanceState) Translators("Nathan", null, "French"),
App1.setActivityTheme(this) Translators("Niek Visser", null, "Dutch"),
setContentView(R.layout.act_about) Translators("Owain Rhys Lewis", null, "Welsh"),
App1.initEdgeToEdge(this) Translators("Remi Rampin", null, "French"),
Translators("Sachin", null, "Kannada"),
Styler.fixHorizontalPadding(findViewById(R.id.svContent)) Translators("Swann Martinet", null, "French"),
Translators("takubunn", null, "Chinese (Simplified)"),
Translators("Whod", null, "Bulgarian"),
try { Translators("yucj", null, "Chinese (Traditional)"),
val pInfo = packageManager.getPackageInfo(packageName, 0) Translators("邓志诚", null, "Chinese (Simplified)"),
val tv = findViewById<TextView>(R.id.tvVersion) Translators("배태길", null, "Korea"),
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) { override fun onCreate(savedInstanceState: Bundle?) {
val b : Button = findViewById(btnId) super.onCreate(savedInstanceState)
b.text = caption App1.setActivityTheme(this)
b.setOnClickListener { onClick() } setContentView(R.layout.act_about)
} App1.initEdgeToEdge(this)
fun searchAcct(acct : String) { Styler.fixHorizontalPadding(findViewById(R.id.svContent))
setResult(Activity.RESULT_OK, Intent().apply { putExtra(EXTRA_SEARCH, acct) })
finish()
} try {
val pInfo = packageManager.getPackageInfo(packageName, 0)
val tv = findViewById<TextView>(R.id.tvVersion)
setButton( tv.text = getString(R.string.version_is, pInfo.versionName)
R.id.btnDeveloper, } catch (ex: PackageManager.NameNotFoundException) {
getString(R.string.search_for, developer_acct) log.trace(ex, "getPackageInfo failed.")
) { searchAcct(developer_acct) } }
setButton( fun setButton(btnId: Int, caption: String, onClick: () -> Unit) {
R.id.btnOfficialAccount, val b: Button = findViewById(btnId)
getString(R.string.search_for, official_acct) b.text = caption
) { searchAcct(official_acct) } b.setOnClickListener { onClick() }
}
setButton(R.id.btnReleaseNote, url_release)
{ openBrowser(url_release) } fun searchAcct(acct: String) {
setResult(Activity.RESULT_OK, Intent().apply { putExtra(EXTRA_SEARCH, acct) })
// setButton(R.id.btnIconDesign, url_futaba) finish()
// { openUrl(url_futaba) } }
setButton(R.id.btnWeblate, getString(R.string.please_help_translation))
{ openBrowser(url_weblate) } setButton(
R.id.btnDeveloper,
val ll = findViewById<LinearLayout>(R.id.llContributors) getString(R.string.search_for, developer_acct)
val density = resources.displayMetrics.density ) { searchAcct(developer_acct) }
val margin_top = (0.5f + density * 8).toInt()
val padding = (0.5f + density * 8).toInt() setButton(
R.id.btnOfficialAccount,
for(who in translators) { getString(R.string.search_for, official_acct)
ll.addView(Button(this).apply { ) { searchAcct(official_acct) }
//
layoutParams = LinearLayout.LayoutParams( setButton(R.id.btnReleaseNote, url_release)
LinearLayout.LayoutParams.WRAP_CONTENT, { openBrowser(url_release) }
LinearLayout.LayoutParams.WRAP_CONTENT
).apply { // setButton(R.id.btnIconDesign, url_futaba)
if(ll.childCount != 0) topMargin = margin_top // { openUrl(url_futaba) }
}
// setButton(R.id.btnWeblate, getString(R.string.please_help_translation))
setBackgroundResource(R.drawable.btn_bg_transparent_round6dp) { openBrowser(url_weblate) }
setPadding(padding, padding, padding, padding)
isAllCaps = false val ll = findViewById<LinearLayout>(R.id.llContributors)
val density = resources.displayMetrics.density
// val margin_top = (0.5f + density * 8).toInt()
val acct = who.acct ?: "@?@?" val padding = (0.5f + density * 8).toInt()
text = "${who.name}\n$acct\n${getString(R.string.thanks_for, who.lang)}"
gravity = Gravity.START or Gravity.CENTER_VERTICAL for (who in translators) {
ll.addView(Button(this).apply {
setOnClickListener { //
val data = Intent() layoutParams = LinearLayout.LayoutParams(
data.putExtra(EXTRA_SEARCH, who.acct ?: who.name) LinearLayout.LayoutParams.WRAP_CONTENT,
setResult(Activity.RESULT_OK, data) LinearLayout.LayoutParams.WRAP_CONTENT
finish() ).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()
}
})
}
}
} }

View File

@ -338,7 +338,7 @@ class ActAccountSetting : AsyncActivity(), View.OnClickListener,
spResizeImage = findViewById(R.id.spResizeImage) spResizeImage = findViewById(R.id.spResizeImage)
imageResizeItems = SavedAccount.resizeConfigList.map { imageResizeItems = SavedAccount.resizeConfigList.map {
var caption = when (it.type) { val caption = when (it.type) {
ResizeType.None -> getString(R.string.dont_resize) ResizeType.None -> getString(R.string.dont_resize)
ResizeType.LongSide -> getString(R.string.long_side_pixel, it.size) ResizeType.LongSide -> getString(R.string.long_side_pixel, it.size)
ResizeType.SquarePixel -> if (it.extraStringId != 0) { ResizeType.SquarePixel -> if (it.extraStringId != 0) {
@ -436,7 +436,7 @@ class ActAccountSetting : AsyncActivity(), View.OnClickListener,
btnNotificationStyleEditReply.setOnClickListener(this) btnNotificationStyleEditReply.setOnClickListener(this)
spResizeImage.setOnItemSelectedListener(this) spResizeImage.onItemSelectedListener = this
btnNotificationStyleEditReply.vg(Pref.bpSeparateReplyNotificationGroup(pref)) btnNotificationStyleEditReply.vg(Pref.bpSeparateReplyNotificationGroup(pref))

View File

@ -1125,7 +1125,7 @@ class ActPost : AsyncActivity(),
} }
} else { } else {
if (editable.isNotEmpty() if (editable.isNotEmpty()
&& !CharacterGroup.isWhitespace(editable[editable.length - 1].toInt()) && !CharacterGroup.isWhitespace(editable[editable.length - 1].code)
) { ) {
editable.append(' ') editable.append(' ')
} }
@ -2571,7 +2571,7 @@ class ActPost : AsyncActivity(),
val e = etContent.editableText val e = etContent.editableText
val len = e.length val len = e.length
val last_char = if (len <= 0) ' ' else e[len - 1] 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) e.append(" ").append(a.text_url)
} else { } else {
e.append(a.text_url) e.append(a.text_url)

View File

@ -3,7 +3,6 @@ package jp.juggler.subwaytooter.action
import android.app.AlertDialog import android.app.AlertDialog
import jp.juggler.subwaytooter.ActColumnList import jp.juggler.subwaytooter.ActColumnList
import jp.juggler.subwaytooter.ActMain import jp.juggler.subwaytooter.ActMain
import jp.juggler.subwaytooter.App1
import jp.juggler.subwaytooter.R import jp.juggler.subwaytooter.R
import jp.juggler.subwaytooter.api.entity.TootApplication import jp.juggler.subwaytooter.api.entity.TootApplication
import jp.juggler.subwaytooter.table.MutedApp import jp.juggler.subwaytooter.table.MutedApp

View File

@ -1,7 +1,6 @@
package jp.juggler.subwaytooter.action package jp.juggler.subwaytooter.action
import jp.juggler.subwaytooter.ActMain import jp.juggler.subwaytooter.ActMain
import jp.juggler.subwaytooter.App1
import jp.juggler.subwaytooter.R import jp.juggler.subwaytooter.R
import jp.juggler.subwaytooter.api.TootApiClient import jp.juggler.subwaytooter.api.TootApiClient
import jp.juggler.subwaytooter.api.TootApiResult import jp.juggler.subwaytooter.api.TootApiResult

View File

@ -2,7 +2,6 @@ package jp.juggler.subwaytooter.action
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import jp.juggler.subwaytooter.ActMain import jp.juggler.subwaytooter.ActMain
import jp.juggler.subwaytooter.App1
import jp.juggler.subwaytooter.ColumnType import jp.juggler.subwaytooter.ColumnType
import jp.juggler.subwaytooter.R import jp.juggler.subwaytooter.R
import jp.juggler.subwaytooter.api.* import jp.juggler.subwaytooter.api.*

View File

@ -2,7 +2,6 @@ package jp.juggler.subwaytooter.action
import android.app.AlertDialog import android.app.AlertDialog
import jp.juggler.subwaytooter.ActMain import jp.juggler.subwaytooter.ActMain
import jp.juggler.subwaytooter.App1
import jp.juggler.subwaytooter.R import jp.juggler.subwaytooter.R
import jp.juggler.subwaytooter.api.* import jp.juggler.subwaytooter.api.*
import jp.juggler.subwaytooter.api.entity.* import jp.juggler.subwaytooter.api.entity.*

View File

@ -47,7 +47,7 @@ class Host private constructor(
val cached = hostSet[srcArg] val cached = hostSet[srcArg]
if(cached != null) return cached if(cached != null) return cached
val src = srcArg.removeUrlSchema() 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 pretty = IDN.toUnicode(src, IDN.ALLOW_UNASSIGNED)
val host = if(ascii == pretty) Host(ascii) else Host(ascii, pretty) val host = if(ascii == pretty) Host(ascii) else Host(ascii, pretty)
hostSet[src] = host hostSet[src] = host

View File

@ -427,7 +427,7 @@ class TootInstance(parser: TootParser, src: JsonObject) {
private fun Host.getCacheEntry(): CacheEntry = private fun Host.getCacheEntry(): CacheEntry =
synchronized(_hostCache) { synchronized(_hostCache) {
val hostLower = ascii.toLowerCase(Locale.JAPAN) val hostLower = ascii.lowercase()
var item = _hostCache[hostLower] var item = _hostCache[hostLower]
if (item == null) { if (item == null) {
item = CacheEntry() item = CacheEntry()

View File

@ -240,7 +240,7 @@ class TootPolls (
TootStatus.parseTime(src.string("endTime")).notZero() ?: Long.MAX_VALUE TootStatus.parseTime(src.string("endTime")).notZero() ?: Long.MAX_VALUE
this.expired = expired_at >= System.currentTimeMillis() this.expired = expired_at >= System.currentTimeMillis()
this.multiple = src.containsKey("anyOf") 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 this.ownVoted = false

View File

@ -26,7 +26,7 @@ open class TootTag constructor(
init { init {
countDaily = history?.first()?.uses ?: 0 countDaily = history?.first()?.uses ?: 0
countWeekly = history?.sumBy { it.uses } ?: 0 countWeekly = history?.sumOf{ it.uses } ?: 0
accountDaily = history?.first()?.accounts ?: 0 accountDaily = history?.first()?.accounts ?: 0
accountWeekly = history?.map { it.accounts }?.maxOrNull() ?: accountDaily accountWeekly = history?.map { it.accounts }?.maxOrNull() ?: accountDaily

View File

@ -24,158 +24,156 @@ import java.util.*
import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicBoolean
object AccountPicker { object AccountPicker {
@SuppressLint("InflateParams") @SuppressLint("InflateParams")
fun pick( fun pick(
activity : AppCompatActivity, activity: AppCompatActivity,
bAllowPseudo : Boolean = false, bAllowPseudo: Boolean = false,
bAllowMisskey : Boolean = true, bAllowMisskey: Boolean = true,
bAllowMastodon : Boolean = true, bAllowMastodon: Boolean = true,
bAuto : Boolean = false, bAuto: Boolean = false,
message : String? = null, message: String? = null,
accountListArg : ArrayList<SavedAccount>? = null, accountListArg: ArrayList<SavedAccount>? = null,
dismiss_callback : DialogInterfaceCallback? = null, dismiss_callback: DialogInterfaceCallback? = null,
extra_callback : (LinearLayout, Int, Int) -> Unit = { _, _, _ -> }, extra_callback: (LinearLayout, Int, Int) -> Unit = { _, _, _ -> },
callback : SavedAccountCallback callback: SavedAccountCallback
) { ) {
var removedMisskey = 0
var removedPseudo = 0
var removeMastodon = 0 var removeMastodon = 0
val account_list : MutableList<SavedAccount> = accountListArg ?: { var removedMisskey = 0
val l = SavedAccount.loadAccountList(activity).filter { a -> var removedPseudo = 0
var bOk = true
fun SavedAccount.checkMastodon() = when {
if(! bAllowMastodon && ! a.isMisskey) { !bAllowMastodon && !isMisskey -> ++removeMastodon
++ removeMastodon else -> 0
bOk = false }
}
fun SavedAccount.checkMisskey() = when {
if(! bAllowMisskey && a.isMisskey) { !bAllowMisskey && isMisskey -> ++removedMisskey
++ removedMisskey else -> 0
bOk = false }
}
fun SavedAccount.checkPseudo() = when {
if(! bAllowPseudo && a.isPseudo) { !bAllowPseudo && isPseudo -> ++removedPseudo
++ removedPseudo else -> 0
bOk = false }
}
bOk val account_list: MutableList<SavedAccount> = accountListArg
}.toMutableList() ?: SavedAccount.loadAccountList(activity)
SavedAccount.sort(l) .filter { 0 == it.checkMastodon() + it.checkMisskey() + it.checkPseudo() }
l .toMutableList()
}() .also { SavedAccount.sort(it) }
if(account_list.isEmpty()) { if (account_list.isEmpty()) {
val sb = StringBuilder() val sb = StringBuilder()
if(removedPseudo > 0) { if (removedPseudo > 0) {
sb.append(activity.getString(R.string.not_available_for_pseudo_account)) sb.append(activity.getString(R.string.not_available_for_pseudo_account))
} }
if(removedMisskey > 0) { if (removedMisskey > 0) {
if(sb.isNotEmpty()) sb.append('\n') if (sb.isNotEmpty()) sb.append('\n')
sb.append(activity.getString(R.string.not_available_for_misskey_account)) sb.append(activity.getString(R.string.not_available_for_misskey_account))
} }
if(removeMastodon > 0) { if (removeMastodon > 0) {
if(sb.isNotEmpty()) sb.append('\n') if (sb.isNotEmpty()) sb.append('\n')
sb.append(activity.getString(R.string.not_available_for_mastodon_account)) sb.append(activity.getString(R.string.not_available_for_mastodon_account))
} }
if(sb.isEmpty()) { if (sb.isEmpty()) {
sb.append(activity.getString(R.string.account_empty)) sb.append(activity.getString(R.string.account_empty))
} }
activity.showToast(false, sb.toString()) activity.showToast(false, sb.toString())
return return
} }
if(bAuto && account_list.size == 1) { if (bAuto && account_list.size == 1) {
callback(account_list[0]) callback(account_list[0])
return return
} }
val viewRoot = activity.layoutInflater.inflate(R.layout.dlg_account_picker, null, false) val viewRoot = activity.layoutInflater.inflate(R.layout.dlg_account_picker, null, false)
val dialog = Dialog(activity) val dialog = Dialog(activity)
val isDialogClosed = AtomicBoolean(false) val isDialogClosed = AtomicBoolean(false)
dialog.setOnDismissListener { dialog.setOnDismissListener {
if(dismiss_callback != null) dismiss_callback(it) if (dismiss_callback != null) dismiss_callback(it)
} }
dialog.setContentView(viewRoot) dialog.setContentView(viewRoot)
if(message != null && message.isNotEmpty()) { if (message != null && message.isNotEmpty()) {
val tv = viewRoot.findViewById<TextView>(R.id.tvMessage) val tv = viewRoot.findViewById<TextView>(R.id.tvMessage)
tv.visibility = View.VISIBLE tv.visibility = View.VISIBLE
tv.text = message tv.text = message
} }
viewRoot.findViewById<View>(R.id.btnCancel).setOnClickListener { viewRoot.findViewById<View>(R.id.btnCancel).setOnClickListener {
isDialogClosed.set(true) isDialogClosed.set(true)
dialog.cancel() dialog.cancel()
} }
dialog.setCancelable(true) dialog.setCancelable(true)
dialog.setCanceledOnTouchOutside(true) dialog.setCanceledOnTouchOutside(true)
dialog.setOnCancelListener { isDialogClosed.set(true) } dialog.setOnCancelListener { isDialogClosed.set(true) }
val density = activity.resources.displayMetrics.density val density = activity.resources.displayMetrics.density
val llAccounts : LinearLayout = viewRoot.findViewById(R.id.llAccounts) val llAccounts: LinearLayout = viewRoot.findViewById(R.id.llAccounts)
val pad_se = (0.5f + 12f * density).toInt() val pad_se = (0.5f + 12f * density).toInt()
val pad_tb = (0.5f + 6f * density).toInt() val pad_tb = (0.5f + 6f * density).toInt()
for(a in account_list) { for (a in account_list) {
val lp = LinearLayout.LayoutParams( val lp = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT LinearLayout.LayoutParams.WRAP_CONTENT
) )
val ac = AcctColor.load(a) val ac = AcctColor.load(a)
val b = Button(activity) val b = Button(activity)
if(AcctColor.hasColorBackground(ac)) { if (AcctColor.hasColorBackground(ac)) {
b.background = getAdaptiveRippleDrawableRound(activity, ac.color_bg, ac.color_fg) b.background = getAdaptiveRippleDrawableRound(activity, ac.color_bg, ac.color_fg)
} else { } else {
b.setBackgroundResource(R.drawable.btn_bg_transparent_round6dp) b.setBackgroundResource(R.drawable.btn_bg_transparent_round6dp)
} }
if(AcctColor.hasColorForeground(ac)) { if (AcctColor.hasColorForeground(ac)) {
b.textColor = ac.color_fg b.textColor = ac.color_fg
} }
b.setPaddingRelative(pad_se, pad_tb, pad_se, pad_tb) b.setPaddingRelative(pad_se, pad_tb, pad_se, pad_tb)
b.gravity = Gravity.START or Gravity.CENTER_VERTICAL b.gravity = Gravity.START or Gravity.CENTER_VERTICAL
b.isAllCaps = false b.isAllCaps = false
b.layoutParams = lp b.layoutParams = lp
b.minHeight = (0.5f + 32f * density).toInt() b.minHeight = (0.5f + 32f * density).toInt()
val sb = SpannableStringBuilder(ac.nickname) val sb = SpannableStringBuilder(ac.nickname)
if(a.last_notification_error?.isNotEmpty() == true) { if (a.last_notification_error?.isNotEmpty() == true) {
sb.append("\n") sb.append("\n")
val start = sb.length val start = sb.length
sb.append(a.last_notification_error) sb.append(a.last_notification_error)
val end = sb.length val end = sb.length
sb.setSpan(RelativeSizeSpan(0.7f), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) sb.setSpan(RelativeSizeSpan(0.7f), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
} else if(a.last_subscription_error?.isNotEmpty() == true) { } else if (a.last_subscription_error?.isNotEmpty() == true) {
sb.append("\n") sb.append("\n")
val start = sb.length val start = sb.length
sb.append(a.last_subscription_error) sb.append(a.last_subscription_error)
val end = sb.length val end = sb.length
sb.setSpan(RelativeSizeSpan(0.7f), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) sb.setSpan(RelativeSizeSpan(0.7f), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
} }
b.text = sb b.text = sb
b.setOnClickListener { b.setOnClickListener {
isDialogClosed.set(true) isDialogClosed.set(true)
callback(a) callback(a)
dialog.dismissSafe() dialog.dismissSafe()
} }
llAccounts.addView(b) llAccounts.addView(b)
} }
extra_callback(llAccounts, pad_se, pad_tb) extra_callback(llAccounts, pad_se, pad_tb)
dialog.show() dialog.show()
} }
} }

View File

@ -267,7 +267,7 @@ class EmojiPicker(
val entries = newList.entries val entries = newList.entries
custom_list.clear() custom_list.clear()
custom_categories.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) custom_categories.ensureCapacity(entries.size)
entries.forEach { entries.forEach {
val rangeStart = custom_list.size val rangeStart = custom_list.size

View File

@ -131,7 +131,7 @@ object LoginForm {
val br = BufferedReader(InputStreamReader(inStream, "UTF-8")) val br = BufferedReader(InputStreamReader(inStream, "UTF-8"))
while(true) { while(true) {
val s : String = val s : String =
br.readLine()?.trim { it <= ' ' }?.toLowerCase(Locale.JAPAN) ?: break br.readLine()?.trim { it <= ' ' }?.lowercase() ?: break
if(s.isEmpty()) continue if(s.isEmpty()) continue
add(s) add(s)
add(IDN.toASCII(s, IDN.ALLOW_UNASSIGNED)) add(IDN.toASCII(s, IDN.ALLOW_UNASSIGNED))
@ -155,7 +155,7 @@ object LoginForm {
override fun performFiltering(constraint : CharSequence?) : FilterResults = override fun performFiltering(constraint : CharSequence?) : FilterResults =
FilterResults().also { result -> FilterResults().also { result ->
if(constraint?.isNotEmpty() == true) { if(constraint?.isNotEmpty() == true) {
val key = constraint.toString().toLowerCase(Locale.JAPAN) val key = constraint.toString().lowercase()
// suggestions リストは毎回生成する必要がある。publishResultsと同時にアクセスされる場合がある // suggestions リストは毎回生成する必要がある。publishResultsと同時にアクセスされる場合がある
val suggestions = StringArray() val suggestions = StringArray()
for(s in instance_list) { for(s in instance_list) {

View File

@ -59,7 +59,7 @@ class PollingForegrounder : IntentService("PollingForegrounder") {
intent_click.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) intent_click.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
val pi_click = PendingIntent.getActivity( val pi_click = PendingIntent.getActivity(
context, 2, intent_click, 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) { val builder = if (Build.VERSION.SDK_INT >= 26) {

View File

@ -692,7 +692,7 @@ class TaskRunner(
// FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY を付与してはいけない // FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY を付与してはいけない
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) 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 = data =
"subwaytooter://notification_delete/?$params".toUri() "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, context,
3, 3,
intent_click, 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) { val builder = if (Build.VERSION.SDK_INT >= 26) {

View File

@ -50,7 +50,7 @@ class AcctColor {
fun save(now : Long) { fun save(now : Long) {
val key = acctAscii.toLowerCase(Locale.ENGLISH) val key = acctAscii.lowercase()
try { try {
val cv = ContentValues() val cv = ContentValues()
@ -137,7 +137,7 @@ class AcctColor {
fun load(acct:Acct) =load(acct.ascii,acct.pretty) fun load(acct:Acct) =load(acct.ascii,acct.pretty)
fun load(acctAscii: String,acctPretty : String) : AcctColor { fun load(acctAscii: String,acctPretty : String) : AcctColor {
val key = acctAscii.toLowerCase(Locale.ENGLISH) val key = acctAscii.lowercase()
val cached : AcctColor? = mMemoryCache.get(key) val cached : AcctColor? = mMemoryCache.get(key)
if(cached != null) return cached if(cached != null) return cached

View File

@ -1,7 +1,6 @@
package jp.juggler.subwaytooter.table package jp.juggler.subwaytooter.table
import android.content.ContentValues import android.content.ContentValues
import android.content.Context
import android.database.sqlite.SQLiteDatabase import android.database.sqlite.SQLiteDatabase
import android.provider.BaseColumns import android.provider.BaseColumns
import jp.juggler.subwaytooter.App1 import jp.juggler.subwaytooter.App1

View File

@ -28,9 +28,9 @@ object EmojiDecoder {
// private val log = LogCategory("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 var handleUnicodeEmoji = true
@ -223,7 +223,7 @@ object EmojiDecoder {
continue 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+FE0EVS-15が直後にある場合 // 絵文字バリエーション・シーケンスEVSのU+FE0EVS-15が直後にある場合
// その文字を絵文字化しない // その文字を絵文字化しない
@ -232,7 +232,7 @@ object EmojiDecoder {
continue continue
} }
val emoji = if (nextChar == 0xFE0F && s[result.endPos - 1].toInt() != 0xFE0F) { val emoji = if (nextChar == 0xFE0F && s[result.endPos - 1].code != 0xFE0F) {
// 絵文字の最後が 0xFE0F でない // 絵文字の最後が 0xFE0F でない
// 直後にU+0xFE0F (絵文字バリエーション・シーケンスEVSのVS-16がある // 直後に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 const val codepointAtmark = '@'.toInt()
private val shortCodeCharacterSet = private val shortCodeCharacterSet =
SparseBooleanArray().apply { SparseBooleanArray().apply {
for (c in 'A'..'Z') put(c.toInt(), true) for (c in 'A'..'Z') put(c.code, true)
for (c in 'a'..'z') put(c.toInt(), true) for (c in 'a'..'z') put(c.code, true)
for (c in '0'..'9') put(c.toInt(), true) for (c in '0'..'9') put(c.code, true)
for (c in "+-_@:") put(c.toInt(), true) for (c in "+-_@:") put(c.code, true)
for (c in ".") put(c.toInt(), true) for (c in ".") put(c.code, true)
} }
private interface ShortCodeSplitterCallback { private interface ShortCodeSplitterCallback {
@ -392,7 +392,7 @@ object EmojiDecoder {
else -> { else -> {
// EmojiOneのショートコード // EmojiOneのショートコード
val emoji = if (useEmojioneShortcode) { val emoji = if (useEmojioneShortcode) {
EmojiMap.shortNameMap[name.toLowerCase(Locale.JAPAN).replace('-', '_')] EmojiMap.shortNameMap[name.lowercase().replace('-', '_')]
} else { } else {
null null
} }
@ -437,7 +437,7 @@ object EmojiDecoder {
// カスタム絵文字ではなく通常の絵文字のショートコードなら絵文字に変換する // カスタム絵文字ではなく通常の絵文字のショートコードなら絵文字に変換する
val emoji = if (decodeEmojioneShortcode) { val emoji = if (decodeEmojioneShortcode) {
EmojiMap.shortNameMap[name.toLowerCase(Locale.JAPAN).replace('-', '_')] EmojiMap.shortNameMap[name.lowercase().replace('-', '_')]
} else { } else {
null null
} }

View File

@ -258,7 +258,7 @@ object HTMLDecoder {
val m = reTag.matcher(text) val m = reTag.matcher(text)
if (m.find()) { if (m.find()) {
val is_close = m.groupEx(1)!!.isNotEmpty() 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 m2 = reTagEnd.matcher(text)
val is_openclose = when { val is_openclose = when {
@ -369,15 +369,15 @@ object HTMLDecoder {
ArrayList<SpannableStringBuilder>().also { dst -> ArrayList<SpannableStringBuilder>().also { dst ->
// 入力の末尾のtrim // 入力の末尾のtrim
var end = this.length 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 var firstNonSpace = 0
while (firstNonSpace <end && CharacterGroup.isWhitespace(this[firstNonSpace].toInt())) ++firstNonSpace while (firstNonSpace <end && CharacterGroup.isWhitespace(this[firstNonSpace].code)) ++firstNonSpace
var i = 0 var i = 0
while (i < end) { while (i < end) {
var lineStart = i val lineStart = i
while (i < end && this[i] != '\n') ++i while (i < end && this[i] != '\n') ++i
val lineEnd = if (i >= end) end else i + 1 val lineEnd = if (i >= end) end else i + 1
++i ++i
@ -758,7 +758,7 @@ object HTMLDecoder {
val dst = HashMap<String, String>() val dst = HashMap<String, String>()
val m = reAttribute.matcher(text) val m = reAttribute.matcher(text)
while (m.find()) { while (m.find()) {
val name = m.groupEx(1)!!.toLowerCase(Locale.JAPAN) val name = m.groupEx(1)!!.lowercase()
val value = decodeEntity(m.groupEx(3)) val value = decodeEntity(m.groupEx(3))
dst[name] = value dst[name] = value
} }

View File

@ -367,10 +367,10 @@ object MisskeySyntaxHighlighter {
addAll(_keywords) addAll(_keywords)
// UPPER // UPPER
addAll(_keywords.map { k -> k.toUpperCase(Locale.JAPAN) }) addAll(_keywords.map { it.uppercase() })
// Snake // Snake
addAll(_keywords.map { k -> k[0].toUpperCase() + k.substring(1) }) addAll(_keywords.map { k -> k[0].uppercase() + k.substring(1) })
add("NaN") add("NaN")
@ -378,12 +378,12 @@ object MisskeySyntaxHighlighter {
} }
private val symbolMap = SparseBooleanArray().apply { private val symbolMap = SparseBooleanArray().apply {
"=+-*/%~^&|><!?".forEach { put(it.toInt(), true) } "=+-*/%~^&|><!?".forEach { put(it.code, true) }
} }
// 文字列リテラルの開始文字のマップ // 文字列リテラルの開始文字のマップ
private val stringStart = SparseBooleanArray().apply { private val stringStart = SparseBooleanArray().apply {
"\"'`".forEach { put(it.toInt(), true) } "\"'`".forEach { put(it.code, true) }
} }
private class Token( private class Token(
@ -536,7 +536,7 @@ object MisskeySyntaxHighlighter {
// string // string
{ {
val beginChar = source[pos] val beginChar = source[pos]
if (!stringStart[beginChar.toInt()]) return@arrayOf null if (!stringStart[beginChar.code]) return@arrayOf null
var i = pos + 1 var i = pos + 1
while (i < end) { while (i < end) {
val char = source[i++] val char = source[i++]
@ -653,7 +653,7 @@ object MisskeySyntaxHighlighter {
{ {
val c = source[pos] val c = source[pos]
when { when {
symbolMap.get(c.toInt(), false) -> symbolMap.get(c.code, false) ->
Token(length = 1, color = 0x42b983) Token(length = 1, color = 0x42b983)
c == '-' -> c == '-' ->
Token(length = 1, color = 0x42b983) Token(length = 1, color = 0x42b983)
@ -1348,7 +1348,7 @@ object MisskeyMarkdownDecoder {
var i = lastEnd //スキャン中の位置 var i = lastEnd //スキャン中の位置
while (i < end) { while (i < end) {
// 注目位置の文字に関連するパーサー // 注目位置の文字に関連するパーサー
val lastParsers = nodeParserMap[text[i].toInt()] val lastParsers = nodeParserMap[text[i].code]
if (lastParsers == null) { if (lastParsers == null) {
++i ++i
continue continue
@ -1538,8 +1538,11 @@ object MisskeyMarkdownDecoder {
} }
// \} \]はムダなエスケープに見えるが、androidでは必要なので削ってはいけない // \} \]はムダなエスケープに見えるが、androidでは必要なので削ってはいけない
@Suppress("RegExpRedundantEscape")
private val reLatexRemove = """\\(?:quad|Huge|atop|sf|scriptsize|bf|small|tiny|underline|large|(?:color)\{[^}]*\})""".toRegex() 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() private val reLatex1 = """\\(?:(?:url)|(?:textcolor|colorbox)\{[^}]*\}|(?:fcolorbox|raisebox)\{[^}]*\}\{[^}]*\}|includegraphics\[[^]]*\])\{([^}]*)\}""".toRegex()
@Suppress("RegExpRedundantEscape")
private val reLatex2reversed = """\\(?:overset|href)\{([^}]+)\}\{([^}]+)\}""".toRegex() private val reLatex2reversed = """\\(?:overset|href)\{([^}]+)\}\{([^}]+)\}""".toRegex()
private fun String.removeLatex(): String { private fun String.removeLatex(): String {
@ -1586,7 +1589,7 @@ object MisskeyMarkdownDecoder {
vararg nodeParsers: NodeParseEnv.() -> NodeDetected? vararg nodeParsers: NodeParseEnv.() -> NodeDetected?
) { ) {
for (s in firstChars) { for (s in firstChars) {
put(s.toInt(), nodeParsers) put(s.code, nodeParsers)
} }
} }
@ -1808,15 +1811,15 @@ object MisskeyMarkdownDecoder {
// メールアドレスの@の手前に使える文字なら真 // メールアドレスの@の手前に使える文字なら真
val mailChars = SparseBooleanArray().apply { val mailChars = SparseBooleanArray().apply {
for (it in '0'..'9') { for (it in '0'..'9') {
put(it.toInt(), true) put(it.code, true)
} }
for (it in 'A'..'Z') { for (it in 'A'..'Z') {
put(it.toInt(), true) put(it.code, true)
} }
for (it in 'a'..'z') { for (it in 'a'..'z') {
put(it.toInt(), true) put(it.code, true)
} }
"""${'$'}!#%&'`"*+-/=?^_{|}~""".forEach { put(it.toInt(), true) } """${'$'}!#%&'`"*+-/=?^_{|}~""".forEach { put(it.code, true) }
} }
addParser("@", { addParser("@", {

View File

@ -760,7 +760,7 @@ class PostHelper(
val cp = src.codePointBefore(i) val cp = src.codePointBefore(i)
i -= Character.charCount(cp) i -= Character.charCount(cp)
if (cp == '@'.toInt()) { if (cp == '@'.code) {
start = i start = i
if (++count_atMark >= 2) break else continue if (++count_atMark >= 2) break else continue
} else if (count_atMark == 1) { } else if (count_atMark == 1) {
@ -865,7 +865,7 @@ class PostHelper(
val remain = limit - code_list.size val remain = limit - code_list.size
if (remain > 0) { if (remain > 0) {
val s = 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) val matches = EmojiDecoder.searchShortCode(activity, s, remain)
log.d("checkEmoji: search for %s, result=%d", s, matches.size) log.d("checkEmoji: search for %s, result=%d", s, matches.size)
code_list.addAll(matches) code_list.addAll(matches)

View File

@ -18,7 +18,7 @@ class Blurhash(blurhash : String, punch : Float = 1f) {
private val base83Map = SparseIntArray().apply { private val base83Map = SparseIntArray().apply {
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz#$%*+,-.:;=?@[]^_{|}~" "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz#$%*+,-.:;=?@[]^_{|}~"
.forEachIndexed { index, c -> .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 { private fun String.decodeBase83(start : Int, length : Int) : Int {
var v = 0 var v = 0
for(i in start until start + length) { for(i in start until start + length) {
val ci = this[i].toInt() val ci = this[i].code
val idx = base83Map.get(ci, - 1) val idx = base83Map.get(ci, - 1)
if(idx == - 1) { if(idx == - 1) error("decodeBase83: incorrect char code $ci")
error("decodeBase83: incorrect char code $ci")
}
v = v * 83 + idx v = v * 83 + idx
} }
return v return v

View File

@ -109,7 +109,7 @@ object CharacterGroup {
var id = Integer.MAX_VALUE var id = Integer.MAX_VALUE
for(s in list) { for(s in list) {
if(s.length == 1) { if(s.length == 1) {
val c = s[0].toInt() val c = s[0].code
if(c < id) id = c if(c < id) id = c
} }
} }
@ -130,7 +130,7 @@ object CharacterGroup {
// ユニコード文字を正規化する。 // ユニコード文字を正規化する。
// 簡易版なので全ての文字には対応していない // 簡易版なので全ての文字には対応していない
fun getUnifiedCharacter(c : Char) : Char { fun getUnifiedCharacter(c : Char) : Char {
val v1 = map1[c.toInt()] val v1 = map1[c.code]
return if(v1 != 0) v1.toChar() else c return if(v1 != 0) v1.toChar() else c
} }
@ -145,13 +145,13 @@ object CharacterGroup {
val map : SparseIntArray val map : SparseIntArray
val key : Int val key : Int
val v1 = s[0].toInt() val v1 = s[0].code
if(s.length == 1) { if(s.length == 1) {
map = map1 map = map1
key = v1 key = v1
} else { } else {
map = map2 map = map2
val v2 = s[1].toInt() val v2 = s[1].code
key = v1 or (v2 shl 16) key = v1 or (v2 shl 16)
} }
@ -183,7 +183,7 @@ object CharacterGroup {
var pos = offset var pos = offset
// 空白を読み飛ばす // 空白を読み飛ばす
while(pos < end && isWhitespace(text[pos].toInt())) ++ pos while(pos < end && isWhitespace(text[pos].code)) ++ pos
// 終端までの文字数 // 終端までの文字数
val remain = end - pos val remain = end - pos
@ -193,7 +193,7 @@ object CharacterGroup {
return END return END
} }
val v1 = text[pos].toInt() val v1 = text[pos].code
// グループに登録された文字を長い順にチェック // グループに登録された文字を長い順にチェック
var check_len = if(remain > 2) 2 else remain var check_len = if(remain > 2) 2 else remain
@ -201,7 +201,7 @@ object CharacterGroup {
val group_id = if(check_len == 1) val group_id = if(check_len == 1)
map1.get(v1) map1.get(v1)
else 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) { if(group_id != 0) {
this.offset = pos + check_len this.offset = pos + check_len
return group_id return group_id

View File

@ -853,7 +853,7 @@ private fun Writer.writeQuote(string: String): Writer {
in '\u0080' until '\u00a0', in '\u0080' until '\u00a0',
in '\u2000' until '\u2100' -> { in '\u2000' until '\u2100' -> {
write("\\u") write("\\u")
val hexCode: String = Integer.toHexString(c.toInt()) val hexCode: String = Integer.toHexString(c.code)
write("0000", 0, 4 - hexCode.length) write("0000", 0, 4 - hexCode.length)
write(hexCode) 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 String -> writeQuote(value)
value is Enum<*> -> writeQuote(value.name) value is Enum<*> -> writeQuote(value.name)

View File

@ -182,7 +182,7 @@ private val mimeTypeExMap : HashMap<String, String> by lazy {
fun getMimeType(log : LogCategory?, src : String) : String { fun getMimeType(log : LogCategory?, src : String) : String {
var ext = MimeTypeMap.getFileExtensionFromUrl(src) var ext = MimeTypeMap.getFileExtensionFromUrl(src)
if(ext != null && ext.isNotEmpty()) { if(ext != null && ext.isNotEmpty()) {
ext = ext.toLowerCase(Locale.US) ext = ext.lowercase()
// //
var mime_type : String? = MimeTypeMap.getSingleton().getMimeTypeFromExtension(ext) var mime_type : String? = MimeTypeMap.getSingleton().getMimeTypeFromExtension(ext)

View File

@ -81,7 +81,7 @@ fun IntArray.toByteArray(): ByteArray {
fun CharArray.toLowerByteArray(): ByteArray { fun CharArray.toLowerByteArray(): ByteArray {
val dst = ByteArray(this.size) val dst = ByteArray(this.size)
for (i in this.indices) { for (i in this.indices) {
dst[i] = this[i].toByte() dst[i] = this[i].code.toByte()
} }
return dst return dst
} }
@ -124,7 +124,7 @@ fun CharSequence.codePointBefore(index: Int): Int {
val c1 = this[index - 2] val c1 = this[index - 2]
if (Character.isHighSurrogate(c1)) return Character.toCodePoint(c1, c2) if (Character.isHighSurrogate(c1)) return Character.toCodePoint(c1, c2)
} }
return c2.toInt() return c2.code
} else { } else {
return -1 return -1
} }

View File

@ -152,7 +152,7 @@ class WordTrieTree {
val t = CharacterGroup.Tokenizer() val t = CharacterGroup.Tokenizer()
for(i in start until end) { 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)) val item = match(true, t.reset(src, i, end))
if(item != null) return item if(item != null) return item
} }
@ -174,7 +174,7 @@ class WordTrieTree {
var i = start var i = start
while(i < end) { while(i < end) {
if(! CharacterGroup.isWhitespace(src[i].toInt())) { if(! CharacterGroup.isWhitespace(src[i].code)) {
val item = match(false, t.reset(src, i, end)) val item = match(false, t.reset(src, i, end))
if(item != null) { if(item != null) {
if(dst == null) dst = ArrayList() if(dst == null) dst = ArrayList()

View File

@ -131,12 +131,10 @@ class TestKotlinFeature {
// 型が分からないようにするとビルドできるが、intの10とlongの10は異なると判断される // 型が分からないようにするとビルドできるが、intの10とlongの10は異なると判断される
// Int.equals も同じ結果 // Int.equals も同じ結果
assertEquals(false, int10 as Any == long10 as Any) assertEquals(false, int10 as Any == long10 as Any)
assertEquals(false, int10.equals(long10))
// Long.equals でも同じ結果 // Long.equals でも同じ結果
assertEquals(false, long10 as Any == int10 as Any) assertEquals(false, long10 as Any == int10 as Any)
assertEquals(false, long10.equals(int10))
// 同じ型に変換すると数値が同じだと判定できる // 同じ型に変換すると数値が同じだと判定できる
assertEquals(true, int10.toLong() == long10) assertEquals(true, int10.toLong() == long10)
assertEquals(true, int10.toLong() == long10) assertEquals(true, int10.toLong() == long10)

View File

@ -3,13 +3,15 @@ buildscript {
ext.min_sdk_version = 21 ext.min_sdk_version = 21
ext.target_sdk_version = 30 ext.target_sdk_version = 30
ext.compile_sdk_version = 30 ext.compile_sdk_version = 30
ext.appcompat_version='1.2.0'
ext.kotlin_version = '1.4.32' ext.appcompat_version='1.2.0'
ext.kotlinx_coroutines_version = '1.4.2' 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.anko_version='0.10.8'
ext.junit_version='4.13.1' ext.junit_version='4.13.2'
repositories { repositories {
google() google()
@ -20,6 +22,7 @@ buildscript {
classpath 'com.android.tools.build:gradle:4.2.0' classpath 'com.android.tools.build:gradle:4.2.0'
classpath 'com.google.gms:google-services:4.3.5' classpath 'com.google.gms:google-services:4.3.5'
//noinspection DifferentKotlinGradleVersion
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "com.github.bjoernq:unmockplugin:0.7.6" classpath "com.github.bjoernq:unmockplugin:0.7.6"

View File

@ -34,7 +34,7 @@ object EmojiMap {
// 素の数字とcopyright,registered, trademark は絵文字にしない // 素の数字とcopyright,registered, trademark は絵文字にしない
fun isIgnored(code: String): Boolean { fun isIgnored(code: String): Boolean {
val c = code[0].toInt() val c = code[0].code
return code.length == 1 && c <= 0xae return code.length == 1 && c <= 0xae
} }

View File

@ -15,17 +15,17 @@ class EmojiTrie<T> {
this.data = data this.data = data
return return
} }
val c = src[offset].toInt() val c = src[offset].code
val next = map[c] ?: EmojiTrie<T>().also { map.put(c, it) } val next = map[c] ?: EmojiTrie<T>().also { map.put(c, it) }
next.append(src, offset + 1, data) 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<T>? { fun get(src: String, offset: Int, end: Int): Result<T>? {
// 長い方を優先するので、先に子を調べる // 長い方を優先するので、先に子を調べる
if (offset < end) if (offset < end)
map[src[offset].toInt()]?.get(src, offset + 1, end) map[src[offset].code]?.get(src, offset + 1, end)
?.let { return it } ?.let { return it }
return this.data?.let { Result(it, offset) } return this.data?.let { Result(it, offset) }
} }

View File

@ -1,6 +1,6 @@
#Wed May 05 20:20:56 JST 2021 #Wed May 05 20:20:56 JST 2021
distributionBase=GRADLE_USER_HOME 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 distributionPath=wrapper/dists
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME

View File

@ -40,6 +40,9 @@ dependencies {
implementation project(':apng_android') implementation project(':apng_android')
implementation "androidx.appcompat:appcompat:$appcompat_version" implementation "androidx.appcompat:appcompat:$appcompat_version"
//noinspection KtxExtensionAvailable
implementation "androidx.lifecycle:lifecycle-viewmodel:$lifecycle_version"
testImplementation "junit:junit:$junit_version" testImplementation "junit:junit:$junit_version"
androidTestImplementation 'androidx.test:runner:1.3.0' androidTestImplementation 'androidx.test:runner:1.3.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'

View File

@ -5,9 +5,6 @@ import android.content.pm.PackageManager
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.os.SystemClock 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.util.Log
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
@ -15,206 +12,209 @@ import android.widget.AdapterView
import android.widget.BaseAdapter import android.widget.BaseAdapter
import android.widget.ListView import android.widget.ListView
import android.widget.TextView 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 jp.juggler.apng.ApngFrames
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
class ActList : AppCompatActivity(), CoroutineScope { class ActList : AppCompatActivity(), CoroutineScope {
companion object { companion object {
const val TAG = "ActList" const val TAG = "ActList"
const val PERMISSION_REQUEST_CODE_STORAGE = 1 const val PERMISSION_REQUEST_CODE_STORAGE = 1
} }
class ListItem(val id : Int, val caption : String) class ListItem(val id: Int, val caption: String)
private lateinit var listView : ListView private lateinit var listView: ListView
private lateinit var listAdapter : MyAdapter private lateinit var listAdapter: MyAdapter
private var timeAnimationStart : Long = 0L private var timeAnimationStart: Long = 0L
private lateinit var activityJob : Job private lateinit var activityJob: Job
override val coroutineContext : CoroutineContext override val coroutineContext: CoroutineContext
get() = Dispatchers.Main + activityJob get() = Dispatchers.Main + activityJob
override fun onCreate(savedInstanceState : Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
activityJob = Job() activityJob = Job()
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.act_list) setContentView(R.layout.act_list)
this.listView = findViewById(R.id.listView) this.listView = findViewById(R.id.listView)
listAdapter = MyAdapter() listAdapter = MyAdapter()
listView.adapter = listAdapter listView.adapter = listAdapter
listView.onItemClickListener = listAdapter listView.onItemClickListener = listAdapter
timeAnimationStart = SystemClock.elapsedRealtime() timeAnimationStart = SystemClock.elapsedRealtime()
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
// Assume thisActivity is the current activity // Assume thisActivity is the current activity
if(PackageManager.PERMISSION_GRANTED != ContextCompat.checkSelfPermission( if (PackageManager.PERMISSION_GRANTED != ContextCompat.checkSelfPermission(
this, this,
Manifest.permission.WRITE_EXTERNAL_STORAGE Manifest.permission.WRITE_EXTERNAL_STORAGE
)) { )
ActivityCompat.requestPermissions( ) {
this, ActivityCompat.requestPermissions(
arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), this,
arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE),
PERMISSION_REQUEST_CODE_STORAGE
) PERMISSION_REQUEST_CODE_STORAGE
} )
} }
load() }
} load()
}
override fun onDestroy() {
super.onDestroy() override fun onDestroy() {
activityJob.cancel() super.onDestroy()
} activityJob.cancel()
}
override fun onRequestPermissionsResult(
requestCode : Int, override fun onRequestPermissionsResult(
permissions : Array<String>, requestCode: Int,
grantResults : IntArray permissions: Array<String>,
) { grantResults: IntArray
when(requestCode) { ) {
PERMISSION_REQUEST_CODE_STORAGE -> { when (requestCode) {
// If request is cancelled, the result arrays are empty. PERMISSION_REQUEST_CODE_STORAGE -> {
if(grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { // If request is cancelled, the result arrays are empty.
// permission was granted, yay! Do the if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// contacts-related task you need to do. // permission was granted, yay! Do the
} else { // contacts-related task you need to do.
// permission denied, boo! Disable the } else {
// functionality that depends on this permission. // permission denied, boo! Disable the
} // functionality that depends on this permission.
return }
} return
// other 'case' lines to check for other }
// permissions this app might request // other 'case' lines to check for other
} // permissions this app might request
} }
}
private fun load() = launch {
val list = async(Dispatchers.IO) { private fun load() = launch {
// RawリソースのIDと名前の一覧 val list = withContext(Dispatchers.IO) {
R.raw::class.java.fields // RawリソースのIDと名前の一覧
.mapNotNull { it.get(null) as? Int } R.raw::class.java.fields
.map { id -> .mapNotNull { it.get(null) as? Int }
ListItem( .map { id ->
id, ListItem(
resources.getResourceName(id) id,
.replaceFirst(""".+/""".toRegex(), "") resources.getResourceName(id)
) .replaceFirst(""".+/""".toRegex(), "")
} )
.toMutableList() }
.apply { sortBy { it.caption } } .toMutableList()
.apply { sortBy { it.caption } }
}.await() }
listAdapter.list.addAll(list) listAdapter.list.addAll(list)
listAdapter.notifyDataSetChanged() listAdapter.notifyDataSetChanged()
} }
inner class MyAdapter : BaseAdapter(), AdapterView.OnItemClickListener { inner class MyAdapter : BaseAdapter(), AdapterView.OnItemClickListener {
val list = ArrayList<ListItem>() val list = ArrayList<ListItem>()
override fun getCount() : Int { override fun getCount(): Int {
return list.size return list.size
} }
override fun getItem(position : Int) : Any { override fun getItem(position: Int): Any {
return list[position] return list[position]
} }
override fun getItemId(position : Int) : Long { override fun getItemId(position: Int): Long {
return list[position].id.toLong() return list[position].id.toLong()
} }
override fun getView( override fun getView(
position : Int, position: Int,
viewArg : View?, viewArg: View?,
parent : ViewGroup? parent: ViewGroup?
) : View { ): View {
val view : View val view: View
val holder : MyViewHolder val holder: MyViewHolder
if(viewArg == null) { if (viewArg == null) {
view = layoutInflater.inflate(R.layout.lv_item, parent, false) view = layoutInflater.inflate(R.layout.lv_item, parent, false)
holder = MyViewHolder(view, this@ActList) holder = MyViewHolder(view, this@ActList)
view.tag = holder view.tag = holder
} else { } else {
view = viewArg view = viewArg
holder = view.tag as MyViewHolder holder = view.tag as MyViewHolder
} }
holder.bind(list[position]) holder.bind(list[position])
return view return view
} }
override fun onItemClick( override fun onItemClick(
parent : AdapterView<*>?, parent: AdapterView<*>?,
view : View?, view: View?,
position : Int, position: Int,
id : Long id: Long
) { ) {
val item = list[position] val item = list[position]
ActViewer.open(this@ActList, item.id, item.caption) ActViewer.open(this@ActList, item.id, item.caption)
} }
} }
inner class MyViewHolder( inner class MyViewHolder(
viewRoot : View, viewRoot: View,
_activity : ActList _activity: ActList
) { ) {
private val tvCaption : TextView = viewRoot.findViewById(R.id.tvCaption) private val tvCaption: TextView = viewRoot.findViewById(R.id.tvCaption)
private val apngView : ApngView = viewRoot.findViewById(R.id.apngView) private val apngView: ApngView = viewRoot.findViewById(R.id.apngView)
init { init {
apngView.timeAnimationStart = _activity.timeAnimationStart apngView.timeAnimationStart = _activity.timeAnimationStart
} }
private var lastId : Int = 0 private var lastId: Int = 0
private var lastJob : Job? = null private var lastJob: Job? = null
fun bind(listItem : ListItem) { fun bind(listItem: ListItem) {
tvCaption.text = listItem.caption tvCaption.text = listItem.caption
val resId = listItem.id val resId = listItem.id
if(lastId != resId) { if (lastId != resId) {
lastId = resId lastId = resId
apngView.apngFrames?.dispose() apngView.apngFrames?.dispose()
apngView.apngFrames = null apngView.apngFrames = null
launch { launch {
var apngFrames : ApngFrames? = null var apngFrames: ApngFrames? = null
try { try {
lastJob?.cancelAndJoin() lastJob?.cancelAndJoin()
val job = async(Dispatchers.IO) { val job = async(Dispatchers.IO) {
try { try {
ApngFrames.parse(128){resources?.openRawResource(resId)} ApngFrames.parse(128) { resources?.openRawResource(resId) }
} catch(ex : Throwable) { } catch (ex: Throwable) {
ex.printStackTrace() ex.printStackTrace()
null null
} }
} }
lastJob = job lastJob = job
apngFrames = job.await() apngFrames = job.await()
if(apngFrames != null && lastId == resId) { if (apngFrames != null && lastId == resId) {
apngView.apngFrames = apngFrames apngView.apngFrames = apngFrames
apngFrames = null apngFrames = null
} }
} catch(ex : Throwable) { } catch (ex: Throwable) {
ex.printStackTrace() ex.printStackTrace()
Log.e(TAG, "load error: ${ex.javaClass.simpleName} ${ex.message}") Log.e(TAG, "load error: ${ex.javaClass.simpleName} ${ex.message}")
} finally { } finally {
apngFrames?.dispose() apngFrames?.dispose()
} }
} }
} }
} }
} }
} }

View File

@ -8,6 +8,7 @@ import android.os.SystemClock
import android.util.AttributeSet import android.util.AttributeSet
import android.view.View import android.view.View
import jp.juggler.apng.ApngFrames import jp.juggler.apng.ApngFrames
import kotlin.math.max
class ApngView : View{ class ApngView : View{
@ -64,8 +65,8 @@ class ApngView : View{
override fun onSizeChanged(w : Int, h : Int, oldw : Int, oldh : Int) { override fun onSizeChanged(w : Int, h : Int, oldw : Int, oldh : Int) {
super.onSizeChanged(w, h, oldw, oldh) super.onSizeChanged(w, h, oldw, oldh)
wView = Math.max(1, w).toFloat() wView = max(1, w).toFloat()
hView = Math.max(1, h).toFloat() hView = max(1, h).toFloat()
aspectView = wView / hView aspectView = wView / hView
initializeScale() initializeScale()
@ -74,8 +75,8 @@ class ApngView : View{
private fun initializeScale(){ private fun initializeScale(){
val apngFrames = this.apngFrames val apngFrames = this.apngFrames
if( apngFrames != null) { if( apngFrames != null) {
wImage =Math.max(1, apngFrames.width).toFloat() wImage = max(1, apngFrames.width).toFloat()
hImage =Math.max(1, apngFrames.height).toFloat() hImage = max(1, apngFrames.height).toFloat()
aspectImage = wImage / hImage aspectImage = wImage / hImage
currentScale = if(aspectView > aspectImage) { currentScale = if(aspectView > aspectImage) {
@ -118,7 +119,7 @@ class ApngView : View{
canvas.drawBitmap(bitmap, drawMatrix, paint) canvas.drawBitmap(bitmap, drawMatrix, paint)
if( delay != Long.MAX_VALUE){ if( delay != Long.MAX_VALUE){
postInvalidateDelayed(Math.max(1L,delay)) postInvalidateDelayed(max(1L,delay))
} }
} }
} }

View File

@ -5,7 +5,6 @@
android:layout_height="match_parent" android:layout_height="match_parent"
> >
<ListView <ListView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:id="@+id/listView" android:id="@+id/listView"