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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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<SavedAccount>? = 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<SavedAccount>? = null,
dismiss_callback: DialogInterfaceCallback? = null,
extra_callback: (LinearLayout, Int, Int) -> Unit = { _, _, _ -> },
callback: SavedAccountCallback
) {
var removeMastodon = 0
val account_list : MutableList<SavedAccount> = 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<TextView>(R.id.tvMessage)
tv.visibility = View.VISIBLE
tv.text = message
}
viewRoot.findViewById<View>(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<SavedAccount> = 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<TextView>(R.id.tvMessage)
tv.visibility = View.VISIBLE
tv.text = message
}
viewRoot.findViewById<View>(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()
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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<SpannableStringBuilder>().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 && CharacterGroup.isWhitespace(this[firstNonSpace].toInt())) ++firstNonSpace
while (firstNonSpace <end && CharacterGroup.isWhitespace(this[firstNonSpace].code)) ++firstNonSpace
var i = 0
while (i < end) {
var lineStart = i
val lineStart = i
while (i < end && this[i] != '\n') ++i
val lineEnd = if (i >= end) end else i + 1
++i
@ -758,7 +758,7 @@ object HTMLDecoder {
val dst = HashMap<String, String>()
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
}

View File

@ -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 {
"=+-*/%~^&|><!?".forEach { put(it.toInt(), true) }
"=+-*/%~^&|><!?".forEach { put(it.code, true) }
}
// 文字列リテラルの開始文字のマップ
private val stringStart = SparseBooleanArray().apply {
"\"'`".forEach { put(it.toInt(), true) }
"\"'`".forEach { put(it.code, true) }
}
private class Token(
@ -536,7 +536,7 @@ object MisskeySyntaxHighlighter {
// string
{
val beginChar = source[pos]
if (!stringStart[beginChar.toInt()]) return@arrayOf null
if (!stringStart[beginChar.code]) return@arrayOf null
var i = pos + 1
while (i < end) {
val char = source[i++]
@ -653,7 +653,7 @@ object MisskeySyntaxHighlighter {
{
val c = source[pos]
when {
symbolMap.get(c.toInt(), false) ->
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("@", {

View File

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

View File

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

View File

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

View File

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

View File

@ -182,7 +182,7 @@ private val mimeTypeExMap : HashMap<String, String> 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)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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