refactor
This commit is contained in:
parent
c08a5dfd8e
commit
5f7f486471
|
@ -840,7 +840,7 @@ class ActAppSetting : AppCompatActivity(), ColorPickerDialogListener, View.OnCli
|
||||||
private fun importAppData2(bConfirm: Boolean, uri: Uri) {
|
private fun importAppData2(bConfirm: Boolean, uri: Uri) {
|
||||||
|
|
||||||
val type = contentResolver.getType(uri)
|
val type = contentResolver.getType(uri)
|
||||||
log.d("importAppData type=%s", type)
|
log.d("importAppData type=$type")
|
||||||
|
|
||||||
if (!bConfirm) {
|
if (!bConfirm) {
|
||||||
AlertDialog.Builder(this)
|
AlertDialog.Builder(this)
|
||||||
|
|
|
@ -7,6 +7,7 @@ import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.core.content.FileProvider
|
import androidx.core.content.FileProvider
|
||||||
import jp.juggler.util.LogCategory
|
import jp.juggler.util.LogCategory
|
||||||
import jp.juggler.util.digestSHA256Hex
|
import jp.juggler.util.digestSHA256Hex
|
||||||
|
import okhttp3.internal.toHexString
|
||||||
import org.apache.commons.io.IOUtils
|
import org.apache.commons.io.IOUtils
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileOutputStream
|
import java.io.FileOutputStream
|
||||||
|
@ -32,7 +33,7 @@ class ActCallback : AppCompatActivity() {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState : Bundle?) {
|
override fun onCreate(savedInstanceState : Bundle?) {
|
||||||
log.d("onCreate flags=%x", intent.flags)
|
log.d("onCreate flags=0x${intent.flags.toHexString()}")
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
var intent : Intent? = intent
|
var intent : Intent? = intent
|
||||||
|
|
|
@ -141,7 +141,7 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onRepeatModeChanged(repeatMode: Int) {
|
override fun onRepeatModeChanged(repeatMode: Int) {
|
||||||
log.d("exoPlayer onRepeatModeChanged %d", repeatMode)
|
log.d("exoPlayer onRepeatModeChanged $repeatMode", )
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPlayerError(error: ExoPlaybackException) {
|
override fun onPlayerError(error: ExoPlaybackException) {
|
||||||
|
|
|
@ -135,14 +135,14 @@ object AppDataExporter {
|
||||||
Cursor.FIELD_TYPE_FLOAT -> {
|
Cursor.FIELD_TYPE_FLOAT -> {
|
||||||
val d = cursor.getDouble(i)
|
val d = cursor.getDouble(i)
|
||||||
if(d.isNaN() || d.isInfinite()) {
|
if(d.isNaN() || d.isInfinite()) {
|
||||||
log.w("column %s is nan or infinite value.", names[i])
|
log.w("column ${names[i]} is nan or infinite value.")
|
||||||
} else {
|
} else {
|
||||||
writer.name(names[i])
|
writer.name(names[i])
|
||||||
writer.value(d)
|
writer.value(d)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Cursor.FIELD_TYPE_BLOB -> log.w("column %s is blob.", names[i])
|
Cursor.FIELD_TYPE_BLOB -> log.w("column ${names[i]} is blob." )
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,8 +20,6 @@ import jp.juggler.subwaytooter.table.SavedAccount
|
||||||
import jp.juggler.subwaytooter.util.NetworkStateTracker
|
import jp.juggler.subwaytooter.util.NetworkStateTracker
|
||||||
import jp.juggler.subwaytooter.util.PostAttachment
|
import jp.juggler.subwaytooter.util.PostAttachment
|
||||||
import jp.juggler.util.*
|
import jp.juggler.util.*
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import org.apache.commons.io.IOUtils
|
import org.apache.commons.io.IOUtils
|
||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
@ -37,14 +35,14 @@ enum class DedupMode {
|
||||||
}
|
}
|
||||||
|
|
||||||
class DedupItem(
|
class DedupItem(
|
||||||
val text: String,
|
val text: String,
|
||||||
var time: Long = SystemClock.elapsedRealtime()
|
var time: Long = SystemClock.elapsedRealtime()
|
||||||
)
|
)
|
||||||
|
|
||||||
class AppState(
|
class AppState(
|
||||||
internal val context: Context,
|
internal val context: Context,
|
||||||
internal val handler: Handler,
|
internal val handler: Handler,
|
||||||
internal val pref: SharedPreferences
|
internal val pref: SharedPreferences
|
||||||
) {
|
) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
@ -129,9 +127,9 @@ class AppState(
|
||||||
|
|
||||||
private val _columnList = ArrayList<Column>()
|
private val _columnList = ArrayList<Column>()
|
||||||
|
|
||||||
// make shallow copy
|
// make shallow copy
|
||||||
val columnList: List<Column>
|
val columnList: List<Column>
|
||||||
get() = synchronized(_columnList) { ArrayList(_columnList) }
|
get() = synchronized(_columnList) { ArrayList(_columnList) }
|
||||||
|
|
||||||
val columnCount: Int
|
val columnCount: Int
|
||||||
get() = synchronized(_columnList) { _columnList.size }
|
get() = synchronized(_columnList) { _columnList.size }
|
||||||
|
@ -140,7 +138,7 @@ class AppState(
|
||||||
synchronized(_columnList) { _columnList.elementAtOrNull(i) }
|
synchronized(_columnList) { _columnList.elementAtOrNull(i) }
|
||||||
|
|
||||||
fun columnIndex(column: Column?) =
|
fun columnIndex(column: Column?) =
|
||||||
synchronized(_columnList) { _columnList.indexOf(column).takeIf{ it != -1 } }
|
synchronized(_columnList) { _columnList.indexOf(column).takeIf { it != -1 } }
|
||||||
|
|
||||||
fun editColumnList(save: Boolean = true, block: (ArrayList<Column>) -> Unit) {
|
fun editColumnList(save: Boolean = true, block: (ArrayList<Column>) -> Unit) {
|
||||||
synchronized(_columnList) {
|
synchronized(_columnList) {
|
||||||
|
@ -230,10 +228,10 @@ class AppState(
|
||||||
restartTTS()
|
restartTTS()
|
||||||
} else {
|
} else {
|
||||||
log.d(
|
log.d(
|
||||||
"proc_flushSpeechQueue: tts is speaking. queue_count=%d, expire_remain=%.3f",
|
"proc_flushSpeechQueue: tts is speaking. queue_count=${queue_count}, expire_remain=${
|
||||||
queue_count,
|
String.format("%.3f",expire_remain / 1000f)
|
||||||
expire_remain / 1000f
|
}"
|
||||||
)
|
)
|
||||||
handler.postDelayed(this, expire_remain)
|
handler.postDelayed(this, expire_remain)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -241,7 +239,7 @@ class AppState(
|
||||||
}
|
}
|
||||||
|
|
||||||
val sv = tts_queue.removeFirst()
|
val sv = tts_queue.removeFirst()
|
||||||
log.d("proc_flushSpeechQueue: speak %s", sv)
|
log.d("proc_flushSpeechQueue: speak ${sv}")
|
||||||
|
|
||||||
val voice_count = voice_list.size
|
val voice_count = voice_list.size
|
||||||
if (voice_count > 0) {
|
if (voice_count > 0) {
|
||||||
|
@ -251,11 +249,11 @@ class AppState(
|
||||||
|
|
||||||
tts_speak_start = now
|
tts_speak_start = now
|
||||||
tts.speak(
|
tts.speak(
|
||||||
sv,
|
sv,
|
||||||
TextToSpeech.QUEUE_ADD,
|
TextToSpeech.QUEUE_ADD,
|
||||||
null, // Bundle params
|
null, // Bundle params
|
||||||
(++utteranceIdSeed).toString() // String utteranceId
|
(++utteranceIdSeed).toString() // String utteranceId
|
||||||
)
|
)
|
||||||
} catch (ex: Throwable) {
|
} catch (ex: Throwable) {
|
||||||
log.trace(ex)
|
log.trace(ex)
|
||||||
log.e(ex, "proc_flushSpeechQueue catch exception.")
|
log.e(ex, "proc_flushSpeechQueue catch exception.")
|
||||||
|
@ -277,7 +275,7 @@ class AppState(
|
||||||
columnList.mapIndexedNotNull { index, column ->
|
columnList.mapIndexedNotNull { index, column ->
|
||||||
try {
|
try {
|
||||||
val dst = JsonObject()
|
val dst = JsonObject()
|
||||||
ColumnEncoder.encode(column,dst, index)
|
ColumnEncoder.encode(column, dst, index)
|
||||||
dst
|
dst
|
||||||
} catch (ex: JsonException) {
|
} catch (ex: JsonException) {
|
||||||
log.trace(ex)
|
log.trace(ex)
|
||||||
|
@ -381,7 +379,7 @@ class AppState(
|
||||||
context.showToast(false, R.string.text_to_speech_initializing)
|
context.showToast(false, R.string.text_to_speech_initializing)
|
||||||
log.d("initializing TextToSpeech…")
|
log.d("initializing TextToSpeech…")
|
||||||
|
|
||||||
launchIO{
|
launchIO {
|
||||||
|
|
||||||
var tmp_tts: TextToSpeech? = null
|
var tmp_tts: TextToSpeech? = null
|
||||||
|
|
||||||
|
@ -391,11 +389,11 @@ class AppState(
|
||||||
val tts = tmp_tts
|
val tts = tmp_tts
|
||||||
if (tts == null || TextToSpeech.SUCCESS != status) {
|
if (tts == null || TextToSpeech.SUCCESS != status) {
|
||||||
context.showToast(
|
context.showToast(
|
||||||
false,
|
false,
|
||||||
R.string.text_to_speech_initialize_failed,
|
R.string.text_to_speech_initialize_failed,
|
||||||
status
|
status
|
||||||
)
|
)
|
||||||
log.d("speech initialize failed. status=%s", status)
|
log.d("speech initialize failed. status=${status}" )
|
||||||
return@OnInitListener
|
return@OnInitListener
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -423,15 +421,8 @@ class AppState(
|
||||||
} else {
|
} else {
|
||||||
val lang = defaultLocale(context).toLanguageTag()
|
val lang = defaultLocale(context).toLanguageTag()
|
||||||
for (v in voice_set) {
|
for (v in voice_set) {
|
||||||
log.d(
|
log.d( "Voice ${ v.name} ${ v.locale.toLanguageTag()} ${lang}" )
|
||||||
"Voice %s %s %s",
|
if (lang != v.locale.toLanguageTag()) continue
|
||||||
v.name,
|
|
||||||
v.locale.toLanguageTag(),
|
|
||||||
lang
|
|
||||||
)
|
|
||||||
if (lang != v.locale.toLanguageTag()) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
voice_list.add(v)
|
voice_list.add(v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -443,9 +434,9 @@ class AppState(
|
||||||
handler.post(proc_flushSpeechQueue)
|
handler.post(proc_flushSpeechQueue)
|
||||||
|
|
||||||
context.registerReceiver(
|
context.registerReceiver(
|
||||||
tts_receiver,
|
tts_receiver,
|
||||||
IntentFilter(TextToSpeech.ACTION_TTS_QUEUE_PROCESSING_COMPLETED)
|
IntentFilter(TextToSpeech.ACTION_TTS_QUEUE_PROCESSING_COMPLETED)
|
||||||
)
|
)
|
||||||
|
|
||||||
// tts.setOnUtteranceProgressListener( new UtteranceProgressListener() {
|
// tts.setOnUtteranceProgressListener( new UtteranceProgressListener() {
|
||||||
// @Override public void onStart( String utteranceId ){
|
// @Override public void onStart( String utteranceId ){
|
||||||
|
@ -603,7 +594,8 @@ class AppState(
|
||||||
}
|
}
|
||||||
|
|
||||||
if (item.sound_type == HighlightWord.SOUND_TYPE_CUSTOM && item.sound_uri.mayUri()
|
if (item.sound_type == HighlightWord.SOUND_TYPE_CUSTOM && item.sound_uri.mayUri()
|
||||||
.tryRingtone()) return
|
.tryRingtone()
|
||||||
|
) return
|
||||||
|
|
||||||
RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION).tryRingtone()
|
RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION).tryRingtone()
|
||||||
}
|
}
|
||||||
|
|
|
@ -329,7 +329,7 @@ fun Column.isFiltered(item: TootNotification): Boolean {
|
||||||
TootNotification.TYPE_FOLLOW_REQUEST_ACCEPTED_MISSKEY -> {
|
TootNotification.TYPE_FOLLOW_REQUEST_ACCEPTED_MISSKEY -> {
|
||||||
val who = item.account
|
val who = item.account
|
||||||
if (who != null && favMuteSet?.contains(access_info.getFullAcct(who)) == true) {
|
if (who != null && favMuteSet?.contains(access_info.getFullAcct(who)) == true) {
|
||||||
Column.log.d("%s is in favMuteSet.", access_info.getFullAcct(who))
|
Column.log.d("${access_info.getFullAcct(who)} is in favMuteSet.")
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -404,21 +404,21 @@ fun Column.checkFiltersForListData(trees: FilterTrees?) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun reloadFilter(context: Context, access_info: SavedAccount) {
|
fun reloadFilter(context: Context, access_info: SavedAccount) {
|
||||||
launchMain{
|
launchMain {
|
||||||
var resultList: ArrayList<TootFilter>? = null
|
var resultList: ArrayList<TootFilter>? = null
|
||||||
|
|
||||||
context.runApiTask(
|
context.runApiTask(
|
||||||
access_info,
|
access_info,
|
||||||
progressStyle = ApiTask.PROGRESS_NONE
|
progressStyle = ApiTask.PROGRESS_NONE
|
||||||
){client->
|
) { client ->
|
||||||
client.request(ApiPath.PATH_FILTERS)?.also{ result->
|
client.request(ApiPath.PATH_FILTERS)?.also { result ->
|
||||||
result.jsonArray?.let{
|
result.jsonArray?.let {
|
||||||
resultList = TootFilter.parseList(it)
|
resultList = TootFilter.parseList(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
resultList?.let{
|
resultList?.let {
|
||||||
Column.log.d("update filters for ${access_info.acct.pretty}")
|
Column.log.d("update filters for ${access_info.acct.pretty}")
|
||||||
for (column in App1.getAppState(context).columnList) {
|
for (column in App1.getAppState(context).columnList) {
|
||||||
if (column.access_info == access_info) {
|
if (column.access_info == access_info) {
|
||||||
|
|
|
@ -258,6 +258,7 @@ class ColumnTask_Loading(
|
||||||
column.saveRange(bBottom = true, bTop = true, result = result, list = src)
|
column.saveRange(bBottom = true, bTop = true, result = result, list = src)
|
||||||
} else {
|
} else {
|
||||||
column.saveRangeTop(result, src)
|
column.saveRangeTop(result, src)
|
||||||
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
return when {
|
return when {
|
||||||
|
@ -416,6 +417,7 @@ class ColumnTask_Loading(
|
||||||
column.saveRange(bBottom = true, bTop = true, result = result, list = src)
|
column.saveRange(bBottom = true, bTop = true, result = result, list = src)
|
||||||
} else {
|
} else {
|
||||||
column.saveRangeTop(result, src)
|
column.saveRangeTop(result, src)
|
||||||
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
return when {
|
return when {
|
||||||
|
@ -998,7 +1000,7 @@ class ColumnTask_Loading(
|
||||||
target_status.conversation_main = true
|
target_status.conversation_main = true
|
||||||
|
|
||||||
// 祖先
|
// 祖先
|
||||||
val list_asc = java.util.ArrayList<TootStatus>()
|
val list_asc = ArrayList<TootStatus>()
|
||||||
while (true) {
|
while (true) {
|
||||||
if (client.isApiCancelled) return null
|
if (client.isApiCancelled) return null
|
||||||
queryParams["offset"] = list_asc.size
|
queryParams["offset"] = list_asc.size
|
||||||
|
@ -1012,7 +1014,7 @@ class ColumnTask_Loading(
|
||||||
}
|
}
|
||||||
|
|
||||||
// 直接の子リプライ。(子孫をたどることまではしない)
|
// 直接の子リプライ。(子孫をたどることまではしない)
|
||||||
val list_desc = java.util.ArrayList<TootStatus>()
|
val list_desc = ArrayList<TootStatus>()
|
||||||
val idSet = HashSet<EntityId>()
|
val idSet = HashSet<EntityId>()
|
||||||
var untilId: EntityId? = null
|
var untilId: EntityId? = null
|
||||||
|
|
||||||
|
@ -1048,7 +1050,7 @@ class ColumnTask_Loading(
|
||||||
}
|
}
|
||||||
|
|
||||||
// 一つのリストにまとめる
|
// 一つのリストにまとめる
|
||||||
this.list_tmp = java.util.ArrayList<TimelineItem>(
|
this.list_tmp = ArrayList<TimelineItem>(
|
||||||
list_asc.size + list_desc.size + 2
|
list_asc.size + list_desc.size + 2
|
||||||
).apply {
|
).apply {
|
||||||
addAll(list_asc.sortedBy { it.time_created_at })
|
addAll(list_asc.sortedBy { it.time_created_at })
|
||||||
|
@ -1084,7 +1086,7 @@ class ColumnTask_Loading(
|
||||||
target_status.conversation_main = true
|
target_status.conversation_main = true
|
||||||
if (conversation_context != null) {
|
if (conversation_context != null) {
|
||||||
|
|
||||||
this.list_tmp = java.util.ArrayList(
|
this.list_tmp = ArrayList(
|
||||||
1
|
1
|
||||||
+ (conversation_context.ancestors?.size ?: 0)
|
+ (conversation_context.ancestors?.size ?: 0)
|
||||||
+ (conversation_context.descendants?.size ?: 0)
|
+ (conversation_context.descendants?.size ?: 0)
|
||||||
|
|
|
@ -103,8 +103,6 @@ class ColumnTask_Refresh(
|
||||||
sp = holder.scrollPosition
|
sp = holder.scrollPosition
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if (bBottom) {
|
if (bBottom) {
|
||||||
val changeList = listOf(
|
val changeList = listOf(
|
||||||
AdapterChange(
|
AdapterChange(
|
||||||
|
|
|
@ -237,21 +237,17 @@ class ColumnViewHolder(
|
||||||
|
|
||||||
val column = this@ColumnViewHolder.column
|
val column = this@ColumnViewHolder.column
|
||||||
if (column == null) {
|
if (column == null) {
|
||||||
log.d("restoreScrollPosition [%d], column==null", page_idx)
|
log.d("restoreScrollPosition [${page_idx}], column==null")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (column.is_dispose.get()) {
|
if (column.is_dispose.get()) {
|
||||||
log.d("restoreScrollPosition [%d], column is disposed", page_idx)
|
log.d("restoreScrollPosition [${page_idx}], column is disposed")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (column.hasMultipleViewHolder()) {
|
if (column.hasMultipleViewHolder()) {
|
||||||
log.d(
|
log.d("restoreScrollPosition [${page_idx}] ${column.getColumnName(true)}, column has multiple view holder. retry later.")
|
||||||
"restoreScrollPosition [%d] %s , column has multiple view holder. retry later.",
|
|
||||||
page_idx,
|
|
||||||
column.getColumnName(true)
|
|
||||||
)
|
|
||||||
|
|
||||||
// タブレットモードでカラムを追加/削除した際に発生する。
|
// タブレットモードでカラムを追加/削除した際に発生する。
|
||||||
// このタイミングでスクロール位置を復元してもうまくいかないので延期する
|
// このタイミングでスクロール位置を復元してもうまくいかないので延期する
|
||||||
|
@ -277,30 +273,16 @@ class ColumnViewHolder(
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
log.d(
|
log.d("restoreScrollPosition [$page_idx] ${column.getColumnName(true)} , column has no saved scroll position.")
|
||||||
"restoreScrollPosition [$page_idx] %s , column has no saved scroll position.",
|
|
||||||
column.getColumnName(true)
|
|
||||||
)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
column.scroll_save = null
|
column.scroll_save = null
|
||||||
|
|
||||||
if (listView.visibility != View.VISIBLE) {
|
if (listView.visibility != View.VISIBLE) {
|
||||||
log.d(
|
log.d("restoreScrollPosition [$page_idx] ${column.getColumnName(true)} , listView is not visible. saved position ${sp.adapterIndex},${sp.offset} is dropped.")
|
||||||
"restoreScrollPosition [$page_idx] %s , listView is not visible. saved position %s,%s is dropped.",
|
|
||||||
column.getColumnName(true),
|
|
||||||
sp.adapterIndex,
|
|
||||||
sp.offset
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
log.d(
|
log.d("restoreScrollPosition [${page_idx}] ${column.getColumnName(true)} , listView is visible. resume ${sp.adapterIndex},${sp.offset}")
|
||||||
"restoreScrollPosition [%d] %s , listView is visible. resume %s,%s",
|
|
||||||
page_idx,
|
|
||||||
column.getColumnName(true),
|
|
||||||
sp.adapterIndex,
|
|
||||||
sp.offset
|
|
||||||
)
|
|
||||||
sp.restore(this@ColumnViewHolder)
|
sp.restore(this@ColumnViewHolder)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -418,7 +400,7 @@ class ColumnViewHolder(
|
||||||
llColumnHeader,
|
llColumnHeader,
|
||||||
llRefreshError,
|
llRefreshError,
|
||||||
|
|
||||||
).forEach { it.setOnClickListener(this) }
|
).forEach { it.setOnClickListener(this) }
|
||||||
|
|
||||||
|
|
||||||
btnColumnClose.setOnLongClickListener(this)
|
btnColumnClose.setOnLongClickListener(this)
|
||||||
|
|
|
@ -53,7 +53,7 @@ fun ColumnViewHolder.loadBackgroundImage(iv: ImageView, url: String?) {
|
||||||
val screen_h = iv.resources.displayMetrics.heightPixels
|
val screen_h = iv.resources.displayMetrics.heightPixels
|
||||||
|
|
||||||
// 非同期処理を開始
|
// 非同期処理を開始
|
||||||
last_image_task = launchMain{
|
last_image_task = launchMain {
|
||||||
val bitmap = try {
|
val bitmap = try {
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
|
@ -93,7 +93,7 @@ fun ColumnViewHolder.onPageDestroy(page_idx: Int) {
|
||||||
// タブレットモードの場合、onPageCreateより前に呼ばれる
|
// タブレットモードの場合、onPageCreateより前に呼ばれる
|
||||||
val column = this.column
|
val column = this.column
|
||||||
if (column != null) {
|
if (column != null) {
|
||||||
ColumnViewHolder.log.d("onPageDestroy [%d] %s", page_idx, tvColumnName.text)
|
ColumnViewHolder.log.d("onPageDestroy [${page_idx}] ${tvColumnName.text}")
|
||||||
saveScrollPosition()
|
saveScrollPosition()
|
||||||
listView.adapter = null
|
listView.adapter = null
|
||||||
column.removeColumnViewHolder(this)
|
column.removeColumnViewHolder(this)
|
||||||
|
@ -111,7 +111,7 @@ fun ColumnViewHolder.onPageCreate(column: Column, page_idx: Int, page_count: Int
|
||||||
this.column = column
|
this.column = column
|
||||||
this.page_idx = page_idx
|
this.page_idx = page_idx
|
||||||
|
|
||||||
ColumnViewHolder.log.d("onPageCreate [%d] %s", page_idx, column.getColumnName(true))
|
ColumnViewHolder.log.d("onPageCreate [${page_idx}] ${column.getColumnName(true)}")
|
||||||
|
|
||||||
val bSimpleList =
|
val bSimpleList =
|
||||||
column.type != ColumnType.CONVERSATION && Pref.bpSimpleList(activity.pref)
|
column.type != ColumnType.CONVERSATION && Pref.bpSimpleList(activity.pref)
|
||||||
|
|
|
@ -209,22 +209,17 @@ fun ColumnViewHolder.scrollToTop2() {
|
||||||
fun ColumnViewHolder.saveScrollPosition(): Boolean {
|
fun ColumnViewHolder.saveScrollPosition(): Boolean {
|
||||||
val column = this.column
|
val column = this.column
|
||||||
when {
|
when {
|
||||||
column == null -> ColumnViewHolder.log.d("saveScrollPosition [%d] , column==null", page_idx)
|
column == null ->
|
||||||
|
ColumnViewHolder.log.d("saveScrollPosition [${page_idx}] , column==null")
|
||||||
|
|
||||||
column.is_dispose.get() -> ColumnViewHolder.log.d(
|
column.is_dispose.get() ->
|
||||||
"saveScrollPosition [%d] , column is disposed",
|
ColumnViewHolder.log.d("saveScrollPosition [${page_idx}] , column is disposed")
|
||||||
page_idx
|
|
||||||
)
|
|
||||||
|
|
||||||
listView.visibility != View.VISIBLE -> {
|
listView.visibility != View.VISIBLE -> {
|
||||||
val scroll_save = ScrollPosition()
|
val scroll_save = ScrollPosition()
|
||||||
column.scroll_save = scroll_save
|
column.scroll_save = scroll_save
|
||||||
ColumnViewHolder.log.d(
|
ColumnViewHolder.log.d(
|
||||||
"saveScrollPosition [%d] %s , listView is not visible, save %s,%s",
|
"saveScrollPosition [${page_idx}] ${column.getColumnName(true)} , listView is not visible, save ${scroll_save.adapterIndex},${scroll_save.offset}"
|
||||||
page_idx,
|
|
||||||
column.getColumnName(true),
|
|
||||||
scroll_save.adapterIndex,
|
|
||||||
scroll_save.offset
|
|
||||||
)
|
)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -233,11 +228,7 @@ fun ColumnViewHolder.saveScrollPosition(): Boolean {
|
||||||
val scroll_save = ScrollPosition(this)
|
val scroll_save = ScrollPosition(this)
|
||||||
column.scroll_save = scroll_save
|
column.scroll_save = scroll_save
|
||||||
ColumnViewHolder.log.d(
|
ColumnViewHolder.log.d(
|
||||||
"saveScrollPosition [%d] %s , listView is visible, save %s,%s",
|
"saveScrollPosition [${page_idx}] ${column.getColumnName(true)} , listView is visible, save ${scroll_save.adapterIndex},${scroll_save.offset}"
|
||||||
page_idx,
|
|
||||||
column.getColumnName(true),
|
|
||||||
scroll_save.adapterIndex,
|
|
||||||
scroll_save.offset
|
|
||||||
)
|
)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -262,7 +253,7 @@ fun ColumnViewHolder.setScrollPosition(sp: ScrollPosition, deltaDp: Float = 0f)
|
||||||
listLayoutManager.scrollVerticallyBy(dy, recycler, state)
|
listLayoutManager.scrollVerticallyBy(dy, recycler, state)
|
||||||
} catch (ex: Throwable) {
|
} catch (ex: Throwable) {
|
||||||
ColumnViewHolder.log.trace(ex)
|
ColumnViewHolder.log.trace(ex)
|
||||||
ColumnViewHolder.log.e("can't access field in class %s", RecyclerView::class.java.simpleName)
|
ColumnViewHolder.log.e("can't access field in class ${RecyclerView::class.java.simpleName}")
|
||||||
}
|
}
|
||||||
}, 20L)
|
}, 20L)
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,7 +26,7 @@ class EventReceiver : BroadcastReceiver() {
|
||||||
ACTION_NOTIFICATION_DELETE ->
|
ACTION_NOTIFICATION_DELETE ->
|
||||||
PollingWorker.queueNotificationDeleted( context,intent.data)
|
PollingWorker.queueNotificationDeleted( context,intent.data)
|
||||||
|
|
||||||
else -> log.e("onReceive: unsupported action %s", action)
|
else -> log.e("onReceive: unsupported action ${action}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,44 +11,44 @@ import jp.juggler.subwaytooter.notification.PollingWorker
|
||||||
import jp.juggler.util.LogCategory
|
import jp.juggler.util.LogCategory
|
||||||
|
|
||||||
class MyFirebaseMessagingService : FirebaseMessagingService() {
|
class MyFirebaseMessagingService : FirebaseMessagingService() {
|
||||||
|
|
||||||
companion object {
|
|
||||||
internal val log = LogCategory("MyFirebaseMessagingService")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onMessageReceived(remoteMessage : RemoteMessage) {
|
|
||||||
|
|
||||||
super.onMessageReceived(remoteMessage)
|
|
||||||
|
|
||||||
var tag : String? = null
|
|
||||||
val data = remoteMessage.data
|
|
||||||
for((key, value) in data) {
|
|
||||||
log.d("onMessageReceived: %s=%s", key, value)
|
|
||||||
when(key){
|
|
||||||
"notification_tag" -> tag = value
|
|
||||||
"acct" -> tag= "acct<>$value"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val context = applicationContext
|
|
||||||
val intent = Intent(context, PollingForegrounder::class.java)
|
|
||||||
if(tag != null) intent.putExtra(PollingWorker.EXTRA_TAG, tag)
|
|
||||||
if(Build.VERSION.SDK_INT >= 26) {
|
|
||||||
context.startForegroundService(intent)
|
|
||||||
} else {
|
|
||||||
context.startService(intent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onNewToken(token : String) {
|
companion object {
|
||||||
try {
|
internal val log = LogCategory("MyFirebaseMessagingService")
|
||||||
log.d("onTokenRefresh: token=%s", token)
|
}
|
||||||
PrefDevice.prefDevice(this).edit().putString(PrefDevice.KEY_DEVICE_TOKEN, token).apply()
|
|
||||||
|
|
||||||
PollingWorker.queueFCMTokenUpdated(this)
|
override fun onMessageReceived(remoteMessage: RemoteMessage) {
|
||||||
|
|
||||||
} catch(ex : Throwable) {
|
super.onMessageReceived(remoteMessage)
|
||||||
log.trace(ex)
|
|
||||||
}
|
var tag: String? = null
|
||||||
}
|
val data = remoteMessage.data
|
||||||
|
for ((key, value) in data) {
|
||||||
|
log.d("onMessageReceived: ${key}=${value}")
|
||||||
|
when (key) {
|
||||||
|
"notification_tag" -> tag = value
|
||||||
|
"acct" -> tag = "acct<>$value"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val context = applicationContext
|
||||||
|
val intent = Intent(context, PollingForegrounder::class.java)
|
||||||
|
if (tag != null) intent.putExtra(PollingWorker.EXTRA_TAG, tag)
|
||||||
|
if (Build.VERSION.SDK_INT >= 26) {
|
||||||
|
context.startForegroundService(intent)
|
||||||
|
} else {
|
||||||
|
context.startService(intent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onNewToken(token: String) {
|
||||||
|
try {
|
||||||
|
log.d("onTokenRefresh: token=${token}")
|
||||||
|
PrefDevice.prefDevice(this).edit().putString(PrefDevice.KEY_DEVICE_TOKEN, token).apply()
|
||||||
|
|
||||||
|
PollingWorker.queueFCMTokenUpdated(this)
|
||||||
|
|
||||||
|
} catch (ex: Throwable) {
|
||||||
|
log.trace(ex)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,33 +5,33 @@ import androidx.recyclerview.widget.RecyclerView
|
||||||
import jp.juggler.util.LogCategory
|
import jp.juggler.util.LogCategory
|
||||||
|
|
||||||
internal class TabletColumnViewHolder(
|
internal class TabletColumnViewHolder(
|
||||||
activity : ActMain,
|
activity: ActMain,
|
||||||
parent: ViewGroup,
|
parent: ViewGroup,
|
||||||
val columnViewHolder : ColumnViewHolder = ColumnViewHolder(activity,parent)
|
val columnViewHolder: ColumnViewHolder = ColumnViewHolder(activity, parent)
|
||||||
) : RecyclerView.ViewHolder(columnViewHolder.viewRoot) {
|
) : RecyclerView.ViewHolder(columnViewHolder.viewRoot) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val log = LogCategory("TabletColumnViewHolder")
|
val log = LogCategory("TabletColumnViewHolder")
|
||||||
}
|
}
|
||||||
|
|
||||||
private var pageIndex = - 1
|
private var pageIndex = -1
|
||||||
|
|
||||||
fun bind(column : Column, pageIndex : Int, column_count : Int) {
|
fun bind(column: Column, pageIndex: Int, column_count: Int) {
|
||||||
log.d("bind. %d => %d ", this.pageIndex, pageIndex)
|
log.d("bind. ${this.pageIndex} => ${pageIndex}")
|
||||||
|
|
||||||
columnViewHolder.onPageDestroy(this.pageIndex)
|
columnViewHolder.onPageDestroy(this.pageIndex)
|
||||||
|
|
||||||
this.pageIndex = pageIndex
|
this.pageIndex = pageIndex
|
||||||
|
|
||||||
columnViewHolder.onPageCreate(column, pageIndex, column_count)
|
columnViewHolder.onPageCreate(column, pageIndex, column_count)
|
||||||
|
|
||||||
if(! column.bFirstInitialized) {
|
if (!column.bFirstInitialized) {
|
||||||
column.startLoading()
|
column.startLoading()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onViewRecycled() {
|
fun onViewRecycled() {
|
||||||
log.d("onViewRecycled %d", pageIndex)
|
log.d("onViewRecycled ${pageIndex}")
|
||||||
columnViewHolder.onPageDestroy(pageIndex)
|
columnViewHolder.onPageDestroy(pageIndex)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -63,7 +63,7 @@ class UpdateRelationEnv(val column: Column) {
|
||||||
UserRelation.saveListMisskey(now, column.access_info.db_id, who_list, start, step)
|
UserRelation.saveListMisskey(now, column.access_info.db_id, who_list, start, step)
|
||||||
start += step
|
start += step
|
||||||
}
|
}
|
||||||
Column.log.d("updateRelation: update %d relations.", end)
|
Column.log.d("updateRelation: update ${end} relations.")
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2018/11/1 Misskeyにもリレーション取得APIができた
|
// 2018/11/1 Misskeyにもリレーション取得APIができた
|
||||||
|
@ -108,7 +108,7 @@ class UpdateRelationEnv(val column: Column) {
|
||||||
UserRelation.saveList2(now, column.access_info.db_id, list)
|
UserRelation.saveList2(now, column.access_info.db_id, list)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Column.log.d("updateRelation: update %d relations.", n)
|
Column.log.d("updateRelation: update ${n} relations.")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -139,7 +139,7 @@ class UpdateRelationEnv(val column: Column) {
|
||||||
list
|
list
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Column.log.d("updateRelation: update %d relations.", n)
|
Column.log.d("updateRelation: update ${n} relations.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -158,7 +158,7 @@ class UpdateRelationEnv(val column: Column) {
|
||||||
AcctSet.saveList(now, acct_list, n, length)
|
AcctSet.saveList(now, acct_list, n, length)
|
||||||
n += length
|
n += length
|
||||||
}
|
}
|
||||||
Column.log.d("updateRelation: update %d acct.", n)
|
Column.log.d("updateRelation: update ${n} acct.")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -177,7 +177,7 @@ class UpdateRelationEnv(val column: Column) {
|
||||||
TagSet.saveList(now, tag_list, n, length)
|
TagSet.saveList(now, tag_list, n, length)
|
||||||
n += length
|
n += length
|
||||||
}
|
}
|
||||||
Column.log.d("updateRelation: update %d tag.", n)
|
Column.log.d("updateRelation: update ${n} tag.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -231,7 +231,7 @@ private fun appServerUnregister(context: Context, account: SavedAccount) {
|
||||||
|
|
||||||
val response = call.await()
|
val response = call.await()
|
||||||
|
|
||||||
log.e("appServerUnregister: %s", response)
|
log.e("appServerUnregister: ${response}")
|
||||||
} catch (ex: Throwable) {
|
} catch (ex: Throwable) {
|
||||||
log.trace(ex, "appServerUnregister failed.")
|
log.trace(ex, "appServerUnregister failed.")
|
||||||
}
|
}
|
||||||
|
|
|
@ -132,7 +132,7 @@ private fun ActMain.conversationRemote(
|
||||||
val (result, status) = client.syncStatus(access_info, remote_status_url)
|
val (result, status) = client.syncStatus(access_info, remote_status_url)
|
||||||
if (status != null) {
|
if (status != null) {
|
||||||
local_status_id = status.id
|
local_status_id = status.id
|
||||||
log.d("status id conversion %s => %s", remote_status_url, status.id)
|
log.d("status id conversion ${remote_status_url}=>${status.id}")
|
||||||
}
|
}
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,120 +8,123 @@ import jp.juggler.util.asciiPattern
|
||||||
import jp.juggler.util.groupEx
|
import jp.juggler.util.groupEx
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
class TootList(parser:TootParser,src : JsonObject): TimelineItem(), Comparable<TootList> {
|
class TootList(parser: TootParser, src: JsonObject) : TimelineItem(), Comparable<TootList> {
|
||||||
|
|
||||||
val id : EntityId
|
val id: EntityId
|
||||||
|
|
||||||
val title : String?
|
val title: String?
|
||||||
|
|
||||||
// タイトルの数字列部分は数字の大小でソートされるようにしたい
|
// タイトルの数字列部分は数字の大小でソートされるようにしたい
|
||||||
private val title_for_sort : ArrayList<Any>?
|
private val title_for_sort: ArrayList<Any>?
|
||||||
|
|
||||||
|
|
||||||
// 内部で使用する
|
// 内部で使用する
|
||||||
var isRegistered : Boolean = false
|
var isRegistered: Boolean = false
|
||||||
|
|
||||||
var userIds :ArrayList<EntityId>? = null
|
var userIds: ArrayList<EntityId>? = null
|
||||||
|
|
||||||
init {
|
init {
|
||||||
if( parser.serviceType == ServiceType.MISSKEY){
|
if (parser.serviceType == ServiceType.MISSKEY) {
|
||||||
id = EntityId.mayDefault(src.string("id") )
|
id = EntityId.mayDefault(src.string("id"))
|
||||||
title = src.string("name") ?: src.string("title") // v11,v10
|
title = src.string("name") ?: src.string("title") // v11,v10
|
||||||
this.title_for_sort = makeTitleForSort(this.title)
|
this.title_for_sort = makeTitleForSort(this.title)
|
||||||
val user_list = ArrayList<EntityId>()
|
val user_list = ArrayList<EntityId>()
|
||||||
userIds = user_list
|
userIds = user_list
|
||||||
src.jsonArray("userIds")?.forEach {
|
src.jsonArray("userIds")?.forEach {
|
||||||
val id = EntityId.mayNull( it as? String )
|
val id = EntityId.mayNull(it as? String)
|
||||||
if(id != null ) user_list.add(id )
|
if (id != null) user_list.add(id)
|
||||||
}
|
}
|
||||||
}else {
|
} else {
|
||||||
id = EntityId.mayDefault(src.string("id") )
|
id = EntityId.mayDefault(src.string("id"))
|
||||||
title = src.string("title")
|
title = src.string("title")
|
||||||
this.title_for_sort = makeTitleForSort(this.title)
|
this.title_for_sort = makeTitleForSort(this.title)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getOrderId() = id
|
override fun getOrderId() = id
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private var log = LogCategory("TootList")
|
private var log = LogCategory("TootList")
|
||||||
|
|
||||||
private val reNumber = """(\d+)""".asciiPattern()
|
private val reNumber = """(\d+)""".asciiPattern()
|
||||||
|
|
||||||
private fun makeTitleForSort(title : String?) : ArrayList<Any> {
|
private fun makeTitleForSort(title: String?): ArrayList<Any> {
|
||||||
val list = ArrayList<Any>()
|
val list = ArrayList<Any>()
|
||||||
if(title != null) {
|
if (title != null) {
|
||||||
val m = reNumber.matcher(title)
|
val m = reNumber.matcher(title)
|
||||||
var last_end = 0
|
var last_end = 0
|
||||||
while(m.find()) {
|
while (m.find()) {
|
||||||
val match_start = m.start()
|
val match_start = m.start()
|
||||||
val match_end = m.end()
|
val match_end = m.end()
|
||||||
if(match_start > last_end) {
|
if (match_start > last_end) {
|
||||||
list.add(title.substring(last_end, match_start))
|
list.add(title.substring(last_end, match_start))
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
list.add(m.groupEx(1)!!.toLong())
|
list.add(m.groupEx(1)!!.toLong())
|
||||||
} catch(ex : Throwable) {
|
} catch (ex: Throwable) {
|
||||||
list.clear()
|
list.clear()
|
||||||
list.add(title)
|
list.add(title)
|
||||||
return list
|
return list
|
||||||
}
|
}
|
||||||
|
|
||||||
last_end = match_end
|
last_end = match_end
|
||||||
}
|
}
|
||||||
val end = title.length
|
val end = title.length
|
||||||
if(end > last_end) {
|
if (end > last_end) {
|
||||||
list.add(title.substring(last_end, end))
|
list.add(title.substring(last_end, end))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return list
|
return list
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun compareLong(a : Long, b : Long) : Int {
|
private fun compareLong(a: Long, b: Long): Int {
|
||||||
return a.compareTo(b)
|
return a.compareTo(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun compareString(a : String, b : String) : Int {
|
private fun compareString(a: String, b: String): Int {
|
||||||
return a.compareTo(b)
|
return a.compareTo(b)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun compareTo(other : TootList) : Int {
|
override fun compareTo(other: TootList): Int {
|
||||||
val la = this.title_for_sort
|
val la = this.title_for_sort
|
||||||
val lb = other.title_for_sort
|
val lb = other.title_for_sort
|
||||||
|
|
||||||
if(la == null) {
|
if (la == null) {
|
||||||
return if(lb == null) 0 else - 1
|
return if (lb == null) 0 else -1
|
||||||
} else if(lb == null) {
|
} else if (lb == null) {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
val sa = la.size
|
val sa = la.size
|
||||||
val sb = lb.size
|
val sb = lb.size
|
||||||
|
|
||||||
var i = 0
|
var i = 0
|
||||||
while(true) {
|
while (true) {
|
||||||
val oa = if(i >= sa) null else la[i]
|
val oa = if (i >= sa) null else la[i]
|
||||||
val ob = if(i >= sb) null else lb[i]
|
val ob = if (i >= sb) null else lb[i]
|
||||||
|
|
||||||
if(oa == null && ob == null) return 0
|
if (oa == null && ob == null) return 0
|
||||||
|
|
||||||
val delta = when {
|
val delta = when {
|
||||||
oa == null -> - 1
|
oa == null -> -1
|
||||||
ob == null -> 1
|
ob == null -> 1
|
||||||
oa is Long && ob is Long -> compareLong(oa, ob)
|
oa is Long && ob is Long -> compareLong(oa, ob)
|
||||||
oa is String && ob is String -> compareString(oa, ob)
|
oa is String && ob is String -> compareString(oa, ob)
|
||||||
else -> (ob is Long).b2i() - (oa is Long).b2i()
|
else -> (ob is Long).b2i() - (oa is Long).b2i()
|
||||||
}
|
}
|
||||||
log.d(
|
log.d(
|
||||||
"%s %s %s"
|
"${oa} ${
|
||||||
, oa
|
when {
|
||||||
, if(delta < 0) "<" else if(delta > 0) ">" else "="
|
delta < 0 -> "<"
|
||||||
, ob
|
delta > 0 -> ">"
|
||||||
)
|
else -> "="
|
||||||
if(delta != 0) return delta
|
}
|
||||||
++ i
|
} ${ob}"
|
||||||
}
|
|
||||||
}
|
)
|
||||||
|
if (delta != 0) return delta
|
||||||
|
++i
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,88 +4,88 @@ import jp.juggler.subwaytooter.api.TootParser
|
||||||
import jp.juggler.util.*
|
import jp.juggler.util.*
|
||||||
|
|
||||||
object TootPayload {
|
object TootPayload {
|
||||||
|
|
||||||
val log = LogCategory("TootPayload")
|
|
||||||
|
|
||||||
private val reNumber = "([-]?\\d+)".asciiPattern()
|
val log = LogCategory("TootPayload")
|
||||||
|
|
||||||
// ストリーミングAPIのペイロード部分をTootStatus,TootNotification,整数IDのどれかに解釈する
|
|
||||||
fun parsePayload(
|
|
||||||
parser : TootParser,
|
|
||||||
event : String,
|
|
||||||
parent : JsonObject,
|
|
||||||
parent_text : String
|
|
||||||
) : Any? {
|
|
||||||
try {
|
|
||||||
val payload = parent["payload"] ?: return null
|
|
||||||
|
|
||||||
if(payload is JsonObject) {
|
|
||||||
return when(event) {
|
|
||||||
|
|
||||||
// ここを通るケースはまだ確認できていない
|
|
||||||
"update" -> parser.status(payload)
|
|
||||||
|
|
||||||
// ここを通るケースはまだ確認できていない
|
|
||||||
"notification" -> parser.notification(payload)
|
|
||||||
|
|
||||||
// ここを通るケースはまだ確認できていない
|
|
||||||
else -> {
|
|
||||||
log.e("unknown payload(1). message=%s", parent_text)
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if(payload is JsonArray) {
|
|
||||||
log.e("unknown payload(1b). message=%s", parent_text)
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
if(payload is Number) {
|
|
||||||
// 2017/8/24 18:37 mastodon.juggler.jpでここを通った
|
|
||||||
return payload.toLong()
|
|
||||||
}
|
|
||||||
|
|
||||||
if(payload is String) {
|
|
||||||
|
|
||||||
if(payload[0] == '{') {
|
|
||||||
val src = payload.decodeJsonObject()
|
|
||||||
return when(event) {
|
|
||||||
// 2017/8/24 18:37 mastodon.juggler.jpでここを通った
|
|
||||||
"update" -> parser.status(src)
|
|
||||||
|
|
||||||
// 2017/8/24 18:37 mastodon.juggler.jpでここを通った
|
|
||||||
"notification" -> parser.notification(src)
|
|
||||||
|
|
||||||
"conversation" -> parseItem(::TootConversationSummary, parser, src)
|
|
||||||
|
|
||||||
"announcement" -> parseItem(::TootAnnouncement, parser, src)
|
|
||||||
|
|
||||||
"emoji_reaction",
|
private val reNumber = "([-]?\\d+)".asciiPattern()
|
||||||
"announcement.reaction" -> parseItem(TootReaction::parseFedibird, src)
|
|
||||||
|
|
||||||
else -> {
|
// ストリーミングAPIのペイロード部分をTootStatus,TootNotification,整数IDのどれかに解釈する
|
||||||
log.e("unknown payload(2). message=%s", parent_text)
|
fun parsePayload(
|
||||||
// ここを通るケースはまだ確認できていない
|
parser: TootParser,
|
||||||
}
|
event: String,
|
||||||
}
|
parent: JsonObject,
|
||||||
} else if(payload[0] == '[') {
|
parent_text: String
|
||||||
log.e("unknown payload(2b). message=%s", parent_text)
|
): Any? {
|
||||||
return null
|
try {
|
||||||
}
|
val payload = parent["payload"] ?: return null
|
||||||
|
|
||||||
// 2017/8/24 18:37 mdx.ggtea.org でここを通った
|
if (payload is JsonObject) {
|
||||||
val m = reNumber.matcher(payload)
|
return when (event) {
|
||||||
if(m.find()) {
|
|
||||||
return m.groupEx(1) !!.toLong(10)
|
// ここを通るケースはまだ確認できていない
|
||||||
}
|
"update" -> parser.status(payload)
|
||||||
}
|
|
||||||
|
// ここを通るケースはまだ確認できていない
|
||||||
// ここを通るケースはまだ確認できていない
|
"notification" -> parser.notification(payload)
|
||||||
log.e("unknown payload(3). message=%s", parent_text)
|
|
||||||
|
// ここを通るケースはまだ確認できていない
|
||||||
} catch(ex : Throwable) {
|
else -> {
|
||||||
log.trace(ex)
|
log.e("unknown payload(1). message=${parent_text}")
|
||||||
}
|
null
|
||||||
|
}
|
||||||
return null
|
}
|
||||||
}
|
} else if (payload is JsonArray) {
|
||||||
|
log.e("unknown payload(1b). message=${parent_text}")
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (payload is Number) {
|
||||||
|
// 2017/8/24 18:37 mastodon.juggler.jpでここを通った
|
||||||
|
return payload.toLong()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (payload is String) {
|
||||||
|
|
||||||
|
if (payload[0] == '{') {
|
||||||
|
val src = payload.decodeJsonObject()
|
||||||
|
return when (event) {
|
||||||
|
// 2017/8/24 18:37 mastodon.juggler.jpでここを通った
|
||||||
|
"update" -> parser.status(src)
|
||||||
|
|
||||||
|
// 2017/8/24 18:37 mastodon.juggler.jpでここを通った
|
||||||
|
"notification" -> parser.notification(src)
|
||||||
|
|
||||||
|
"conversation" -> parseItem(::TootConversationSummary, parser, src)
|
||||||
|
|
||||||
|
"announcement" -> parseItem(::TootAnnouncement, parser, src)
|
||||||
|
|
||||||
|
"emoji_reaction",
|
||||||
|
"announcement.reaction" -> parseItem(TootReaction::parseFedibird, src)
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
log.e("unknown payload(2). message=${parent_text}")
|
||||||
|
// ここを通るケースはまだ確認できていない
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (payload[0] == '[') {
|
||||||
|
log.e("unknown payload(2b). message=${parent_text}")
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2017/8/24 18:37 mdx.ggtea.org でここを通った
|
||||||
|
val m = reNumber.matcher(payload)
|
||||||
|
if (m.find()) {
|
||||||
|
return m.groupEx(1)!!.toLong(10)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ここを通るケースはまだ確認できていない
|
||||||
|
log.e("unknown payload(3). message=${parent_text}")
|
||||||
|
|
||||||
|
} catch (ex: Throwable) {
|
||||||
|
log.trace(ex)
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -951,24 +951,24 @@ class TootStatus(parser: TootParser, src: JsonObject) : TimelineItem() {
|
||||||
return list
|
return list
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateReactionMastodon( newReactionSet: TootReactionSet ) {
|
fun updateReactionMastodon(newReactionSet: TootReactionSet) {
|
||||||
synchronized(this) {
|
synchronized(this) {
|
||||||
this.reactionSet = newReactionSet
|
this.reactionSet = newReactionSet
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateReactionMastodonByEvent( newReaction: TootReaction ) {
|
fun updateReactionMastodonByEvent(newReaction: TootReaction) {
|
||||||
synchronized(this) {
|
synchronized(this) {
|
||||||
var reactionSet = this.reactionSet
|
var reactionSet = this.reactionSet
|
||||||
if( newReaction.count <= 0 ){
|
if (newReaction.count <= 0) {
|
||||||
reactionSet?.get(newReaction.name)?.let{ reactionSet?.remove(it) }
|
reactionSet?.get(newReaction.name)?.let { reactionSet?.remove(it) }
|
||||||
}else{
|
} else {
|
||||||
if (reactionSet == null) {
|
if (reactionSet == null) {
|
||||||
reactionSet = TootReactionSet(isMisskey = false)
|
reactionSet = TootReactionSet(isMisskey = false)
|
||||||
this.reactionSet = reactionSet
|
this.reactionSet = reactionSet
|
||||||
}
|
}
|
||||||
|
|
||||||
when(val old = reactionSet[newReaction.name]) {
|
when (val old = reactionSet[newReaction.name]) {
|
||||||
null -> reactionSet.add(newReaction)
|
null -> reactionSet.add(newReaction)
|
||||||
|
|
||||||
// 同一オブジェクトならマージは不要
|
// 同一オブジェクトならマージは不要
|
||||||
|
@ -1007,7 +1007,7 @@ class TootStatus(parser: TootParser, src: JsonObject) : TimelineItem() {
|
||||||
if (byMe) {
|
if (byMe) {
|
||||||
// 自分でリアクションしたらUIで更新した後にストリーミングイベントが届くことがある
|
// 自分でリアクションしたらUIで更新した後にストリーミングイベントが届くことがある
|
||||||
// その場合はカウントを変更しない
|
// その場合はカウントを変更しない
|
||||||
if(reactionSet.any{ it.me && it.name == code}) return false
|
if (reactionSet.any { it.me && it.name == code }) return false
|
||||||
}
|
}
|
||||||
|
|
||||||
log.d("increaseReaction noteId=$id byMe=$byMe caller=$caller")
|
log.d("increaseReaction noteId=$id byMe=$byMe caller=$caller")
|
||||||
|
@ -1017,7 +1017,7 @@ class TootStatus(parser: TootParser, src: JsonObject) : TimelineItem() {
|
||||||
reactionSet[code]?.also { it.count = max(0, it.count + 1L) }
|
reactionSet[code]?.also { it.count = max(0, it.count + 1L) }
|
||||||
?: TootReaction(name = code, count = 1L).also { reactionSet.add(it) }
|
?: TootReaction(name = code, count = 1L).also { reactionSet.add(it) }
|
||||||
|
|
||||||
if(byMe) reaction.me = true
|
if (byMe) reaction.me = true
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -1037,7 +1037,7 @@ class TootStatus(parser: TootParser, src: JsonObject) : TimelineItem() {
|
||||||
if (byMe) {
|
if (byMe) {
|
||||||
// 自分でリアクションしたらUIで更新した後にストリーミングイベントが届くことがある
|
// 自分でリアクションしたらUIで更新した後にストリーミングイベントが届くことがある
|
||||||
// その場合はカウントを変更しない
|
// その場合はカウントを変更しない
|
||||||
if(reactionSet.any{ !it.me && it.name == code}) return false
|
if (reactionSet.any { !it.me && it.name == code }) return false
|
||||||
}
|
}
|
||||||
|
|
||||||
log.d("decreaseReaction noteId=$id byMe=$byMe caller=$caller")
|
log.d("decreaseReaction noteId=$id byMe=$byMe caller=$caller")
|
||||||
|
@ -1046,7 +1046,7 @@ class TootStatus(parser: TootParser, src: JsonObject) : TimelineItem() {
|
||||||
val reaction = reactionSet[code]
|
val reaction = reactionSet[code]
|
||||||
?.also { it.count = max(0L, it.count - 1L) }
|
?.also { it.count = max(0L, it.count - 1L) }
|
||||||
|
|
||||||
if(byMe) reaction?.me = false
|
if (byMe) reaction?.me = false
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -1210,10 +1210,10 @@ class TootStatus(parser: TootParser, src: JsonObject) : TimelineItem() {
|
||||||
m = reDate.matcher(strTime)
|
m = reDate.matcher(strTime)
|
||||||
if (m.find()) return parseTime("${strTime}T00:00:00.000Z")
|
if (m.find()) return parseTime("${strTime}T00:00:00.000Z")
|
||||||
|
|
||||||
log.w("invalid time format: %s", strTime)
|
log.w("invalid time format: ${strTime}")
|
||||||
} catch (ex: Throwable) { // ParseException, ArrayIndexOutOfBoundsException
|
} catch (ex: Throwable) { // ParseException, ArrayIndexOutOfBoundsException
|
||||||
log.trace(ex)
|
log.trace(ex)
|
||||||
log.e(ex, "TootStatus.parseTime failed. src=%s", strTime)
|
log.e(ex, "TootStatus.parseTime failed. src=$strTime")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return 0L
|
return 0L
|
||||||
|
@ -1224,7 +1224,7 @@ class TootStatus(parser: TootParser, src: JsonObject) : TimelineItem() {
|
||||||
try {
|
try {
|
||||||
val m = reMSPTime.matcher(strTime)
|
val m = reMSPTime.matcher(strTime)
|
||||||
if (!m.find()) {
|
if (!m.find()) {
|
||||||
log.d("invalid time format: %s", strTime)
|
log.d("invalid time format: $strTime")
|
||||||
} else {
|
} else {
|
||||||
val g = GregorianCalendar(tz_utc)
|
val g = GregorianCalendar(tz_utc)
|
||||||
g.set(
|
g.set(
|
||||||
|
@ -1240,7 +1240,7 @@ class TootStatus(parser: TootParser, src: JsonObject) : TimelineItem() {
|
||||||
}
|
}
|
||||||
} catch (ex: Throwable) { // ParseException, ArrayIndexOutOfBoundsException
|
} catch (ex: Throwable) { // ParseException, ArrayIndexOutOfBoundsException
|
||||||
log.trace(ex)
|
log.trace(ex)
|
||||||
log.e(ex, "parseTimeMSP failed. src=%s", strTime)
|
log.e(ex, "parseTimeMSP failed. src=${strTime}" )
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -103,7 +103,7 @@ class PollingForegrounder : IntentService("PollingForegrounder") {
|
||||||
if (sv.isEmpty() || sv == last_status) return@handleFCMMessage
|
if (sv.isEmpty() || sv == last_status) return@handleFCMMessage
|
||||||
// 状況が変化したらログと通知領域に出力する
|
// 状況が変化したらログと通知領域に出力する
|
||||||
last_status = sv
|
last_status = sv
|
||||||
log.d("onStatus %s", sv)
|
log.d("onStatus $sv")
|
||||||
startForeground(NOTIFICATION_ID_FOREGROUNDER, createNotification(context, sv))
|
startForeground(NOTIFICATION_ID_FOREGROUNDER, createNotification(context, sv))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -357,10 +357,7 @@ class PollingWorker private constructor(contextArg: Context) {
|
||||||
// ジョブが完了した?
|
// ジョブが完了した?
|
||||||
val now = SystemClock.elapsedRealtime()
|
val now = SystemClock.elapsedRealtime()
|
||||||
if (!pw.hasJob(JobId.Push)) {
|
if (!pw.hasJob(JobId.Push)) {
|
||||||
log.d(
|
log.d("handleFCMMessage: JOB_FCM completed. time=${String.format("%.2f", (now - time_start) / 1000f)}")
|
||||||
"handleFCMMessage: JOB_FCM completed. time=%.2f",
|
|
||||||
(now - time_start) / 1000f
|
|
||||||
)
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -398,7 +395,7 @@ class PollingWorker private constructor(contextArg: Context) {
|
||||||
private val workerNotifier = Channel<Unit>(capacity = Channel.CONFLATED)
|
private val workerNotifier = Channel<Unit>(capacity = Channel.CONFLATED)
|
||||||
|
|
||||||
fun notifyWorker() =
|
fun notifyWorker() =
|
||||||
workerNotifier.trySend(Unit)
|
workerNotifier.trySend(Unit)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
log.d("init")
|
log.d("init")
|
||||||
|
|
|
@ -77,7 +77,7 @@ object NotestockHelper {
|
||||||
parseList(parser, data)
|
parseList(parser, data)
|
||||||
.also {
|
.also {
|
||||||
if (it.isEmpty())
|
if (it.isEmpty())
|
||||||
log.d("search result is empty. %s", result.bodyString)
|
log.d("search result is empty. ${result.bodyString}")
|
||||||
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
|
@ -72,7 +72,7 @@ object TootsearchHelper {
|
||||||
parseList(parser, root)
|
parseList(parser, root)
|
||||||
.also {
|
.also {
|
||||||
if (it.isEmpty())
|
if (it.isEmpty())
|
||||||
log.d("search result is empty. %s", result.bodyString)
|
log.d("search result is empty. ${result.bodyString}")
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -164,12 +164,7 @@ class AcctColor {
|
||||||
log.e(ex, "load failed.")
|
log.e(ex, "load failed.")
|
||||||
}
|
}
|
||||||
|
|
||||||
log.d(
|
log.d("lruCache size=${mMemoryCache.size()},hit=${mMemoryCache.hitCount()},miss=${mMemoryCache.missCount()}")
|
||||||
"lruCache size=%s,hit=%s,miss=%s",
|
|
||||||
mMemoryCache.size(),
|
|
||||||
mMemoryCache.hitCount(),
|
|
||||||
mMemoryCache.missCount()
|
|
||||||
)
|
|
||||||
val ac = AcctColor(key, acctPretty)
|
val ac = AcctColor(key, acctPretty)
|
||||||
mMemoryCache.put(key, ac)
|
mMemoryCache.put(key, ac)
|
||||||
return ac
|
return ac
|
||||||
|
|
|
@ -69,14 +69,7 @@ class NotificationTracking {
|
||||||
post_id.putTo(cv, COL_POST_ID)
|
post_id.putTo(cv, COL_POST_ID)
|
||||||
cv.put(COL_POST_TIME, post_time)
|
cv.put(COL_POST_TIME, post_time)
|
||||||
val rows = App1.database.update(table, cv, WHERE_AID, arrayOf(account_db_id.toString(),notificationType))
|
val rows = App1.database.update(table, cv, WHERE_AID, arrayOf(account_db_id.toString(),notificationType))
|
||||||
log.d(
|
log.d("updatePost account_db_id=${account_db_id}, nt=${notificationType}, post=${post_id},${post_time} update_rows=${rows}")
|
||||||
"updatePost account_db_id=%s, nt=%s, post=%s,%s update_rows=%s"
|
|
||||||
, account_db_id
|
|
||||||
, notificationType
|
|
||||||
, post_id
|
|
||||||
, post_time
|
|
||||||
, rows
|
|
||||||
)
|
|
||||||
dirty=false
|
dirty=false
|
||||||
clearCache(account_db_id,notificationType)
|
clearCache(account_db_id,notificationType)
|
||||||
} catch(ex : Throwable) {
|
} catch(ex : Throwable) {
|
||||||
|
|
|
@ -11,166 +11,166 @@ import jp.juggler.util.digestSHA256Hex
|
||||||
import jp.juggler.util.decodeJsonObject
|
import jp.juggler.util.decodeJsonObject
|
||||||
|
|
||||||
class PostDraft {
|
class PostDraft {
|
||||||
|
|
||||||
var id : Long = 0
|
var id: Long = 0
|
||||||
var time_save : Long = 0
|
var time_save: Long = 0
|
||||||
var json : JsonObject? = null
|
var json: JsonObject? = null
|
||||||
var hash : String? = null
|
var hash: String? = null
|
||||||
|
|
||||||
class ColIdx(cursor : Cursor) {
|
class ColIdx(cursor: Cursor) {
|
||||||
internal val idx_id : Int
|
internal val idx_id: Int
|
||||||
internal val idx_time_save : Int
|
internal val idx_time_save: Int
|
||||||
internal val idx_json : Int
|
internal val idx_json: Int
|
||||||
internal val idx_hash : Int
|
internal val idx_hash: Int
|
||||||
|
|
||||||
init {
|
init {
|
||||||
idx_id = cursor.getColumnIndex(COL_ID)
|
idx_id = cursor.getColumnIndex(COL_ID)
|
||||||
idx_time_save = cursor.getColumnIndex(COL_TIME_SAVE)
|
idx_time_save = cursor.getColumnIndex(COL_TIME_SAVE)
|
||||||
idx_json = cursor.getColumnIndex(COL_JSON)
|
idx_json = cursor.getColumnIndex(COL_JSON)
|
||||||
idx_hash = cursor.getColumnIndex(COL_HASH)
|
idx_hash = cursor.getColumnIndex(COL_HASH)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun delete() {
|
fun delete() {
|
||||||
try {
|
try {
|
||||||
App1.database.delete(table, "$COL_ID=?", arrayOf(id.toString()))
|
App1.database.delete(table, "$COL_ID=?", arrayOf(id.toString()))
|
||||||
} catch(ex : Throwable) {
|
} catch (ex: Throwable) {
|
||||||
log.e(ex, "delete failed.")
|
log.e(ex, "delete failed.")
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object : TableCompanion {
|
companion object : TableCompanion {
|
||||||
|
|
||||||
private val log = LogCategory("PostDraft")
|
private val log = LogCategory("PostDraft")
|
||||||
|
|
||||||
private const val table = "post_draft"
|
private const val table = "post_draft"
|
||||||
private const val COL_ID = BaseColumns._ID
|
private const val COL_ID = BaseColumns._ID
|
||||||
private const val COL_TIME_SAVE = "time_save"
|
private const val COL_TIME_SAVE = "time_save"
|
||||||
private const val COL_JSON = "json"
|
private const val COL_JSON = "json"
|
||||||
private const val COL_HASH = "hash"
|
private const val COL_HASH = "hash"
|
||||||
|
|
||||||
override fun onDBCreate(db : SQLiteDatabase) {
|
override fun onDBCreate(db: SQLiteDatabase) {
|
||||||
log.d("onDBCreate!")
|
log.d("onDBCreate!")
|
||||||
db.execSQL(
|
db.execSQL(
|
||||||
"create table if not exists " + table
|
"create table if not exists " + table
|
||||||
+ "(" + COL_ID + " INTEGER PRIMARY KEY"
|
+ "(" + COL_ID + " INTEGER PRIMARY KEY"
|
||||||
+ "," + COL_TIME_SAVE + " integer not null"
|
+ "," + COL_TIME_SAVE + " integer not null"
|
||||||
+ "," + COL_JSON + " text not null"
|
+ "," + COL_JSON + " text not null"
|
||||||
+ "," + COL_HASH + " text not null"
|
+ "," + COL_HASH + " text not null"
|
||||||
+ ")"
|
+ ")"
|
||||||
)
|
)
|
||||||
db.execSQL(
|
db.execSQL(
|
||||||
"create unique index if not exists " + table + "_hash on " + table + "(" + COL_HASH + ")"
|
"create unique index if not exists " + table + "_hash on " + table + "(" + COL_HASH + ")"
|
||||||
)
|
)
|
||||||
db.execSQL(
|
db.execSQL(
|
||||||
"create index if not exists " + table + "_time on " + table + "(" + COL_TIME_SAVE + ")"
|
"create index if not exists " + table + "_time on " + table + "(" + COL_TIME_SAVE + ")"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDBUpgrade(db : SQLiteDatabase, oldVersion : Int, newVersion : Int) {
|
override fun onDBUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
|
||||||
if(oldVersion < 12 && newVersion >= 12) {
|
if (oldVersion < 12 && newVersion >= 12) {
|
||||||
onDBCreate(db)
|
onDBCreate(db)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun deleteOld(now : Long) {
|
private fun deleteOld(now: Long) {
|
||||||
try {
|
try {
|
||||||
// 古いデータを掃除する
|
// 古いデータを掃除する
|
||||||
val expire = now - 86400000L * 30
|
val expire = now - 86400000L * 30
|
||||||
App1.database.delete(table, "$COL_TIME_SAVE<?", arrayOf(expire.toString()))
|
App1.database.delete(table, "$COL_TIME_SAVE<?", arrayOf(expire.toString()))
|
||||||
} catch(ex : Throwable) {
|
} catch (ex: Throwable) {
|
||||||
log.e(ex, "deleteOld failed.")
|
log.e(ex, "deleteOld failed.")
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun save(now : Long, json : JsonObject) {
|
fun save(now: Long, json: JsonObject) {
|
||||||
|
|
||||||
deleteOld(now)
|
deleteOld(now)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// make hash
|
// make hash
|
||||||
val sb = StringBuilder()
|
val sb = StringBuilder()
|
||||||
json.keys.toMutableList().apply { sort() }.forEach { k ->
|
json.keys.toMutableList().apply { sort() }.forEach { k ->
|
||||||
val v = json[k]?.toString() ?: "(null)"
|
val v = json[k]?.toString() ?: "(null)"
|
||||||
sb.append("&")
|
sb.append("&")
|
||||||
sb.append(k)
|
sb.append(k)
|
||||||
sb.append("=")
|
sb.append("=")
|
||||||
sb.append(v)
|
sb.append(v)
|
||||||
}
|
}
|
||||||
val hash = sb.toString().digestSHA256Hex()
|
val hash = sb.toString().digestSHA256Hex()
|
||||||
|
|
||||||
// save to db
|
// save to db
|
||||||
App1.database.replace(table, null, ContentValues().apply {
|
App1.database.replace(table, null, ContentValues().apply {
|
||||||
put(COL_TIME_SAVE, now)
|
put(COL_TIME_SAVE, now)
|
||||||
put(COL_JSON, json.toString())
|
put(COL_JSON, json.toString())
|
||||||
put(COL_HASH, hash)
|
put(COL_HASH, hash)
|
||||||
})
|
})
|
||||||
} catch(ex : Throwable) {
|
} catch (ex: Throwable) {
|
||||||
log.e(ex, "save failed.")
|
log.e(ex, "save failed.")
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun hasDraft() : Boolean {
|
fun hasDraft(): Boolean {
|
||||||
try {
|
try {
|
||||||
App1.database.query(table, arrayOf("count(*)"), null, null, null, null, null)
|
App1.database.query(table, arrayOf("count(*)"), null, null, null, null, null)
|
||||||
.use { cursor ->
|
.use { cursor ->
|
||||||
if(cursor.moveToNext()) {
|
if (cursor.moveToNext()) {
|
||||||
val count = cursor.getInt(0)
|
val count = cursor.getInt(0)
|
||||||
return count > 0
|
return count > 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch(ex : Throwable) {
|
} catch (ex: Throwable) {
|
||||||
log.trace(ex)
|
log.trace(ex)
|
||||||
log.e(ex, "hasDraft failed.")
|
log.e(ex, "hasDraft failed.")
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
fun createCursor() : Cursor? {
|
fun createCursor(): Cursor? {
|
||||||
try {
|
try {
|
||||||
return App1.database.query(
|
return App1.database.query(
|
||||||
table,
|
table,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
"$COL_TIME_SAVE desc"
|
"$COL_TIME_SAVE desc"
|
||||||
)
|
)
|
||||||
} catch(ex : Throwable) {
|
} catch (ex: Throwable) {
|
||||||
log.trace(ex)
|
log.trace(ex)
|
||||||
log.e(ex, "createCursor failed.")
|
log.e(ex, "createCursor failed.")
|
||||||
}
|
}
|
||||||
|
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
fun loadFromCursor(cursor : Cursor, colIdxArg : ColIdx?, position : Int) : PostDraft? {
|
fun loadFromCursor(cursor: Cursor, colIdxArg: ColIdx?, position: Int): PostDraft? {
|
||||||
val colIdx = colIdxArg ?: ColIdx(cursor)
|
val colIdx = colIdxArg ?: ColIdx(cursor)
|
||||||
|
|
||||||
if(! cursor.moveToPosition(position)) {
|
if (!cursor.moveToPosition(position)) {
|
||||||
log.d("loadFromCursor: move failed. position=%s", position)
|
log.d("loadFromCursor: move failed. position=${position}")
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
val dst = PostDraft()
|
val dst = PostDraft()
|
||||||
dst.id = cursor.getLong(colIdx.idx_id)
|
dst.id = cursor.getLong(colIdx.idx_id)
|
||||||
dst.time_save = cursor.getLong(colIdx.idx_time_save)
|
dst.time_save = cursor.getLong(colIdx.idx_time_save)
|
||||||
try {
|
try {
|
||||||
dst.json = cursor.getString(colIdx.idx_json).decodeJsonObject()
|
dst.json = cursor.getString(colIdx.idx_json).decodeJsonObject()
|
||||||
} catch(ex : Throwable) {
|
} catch (ex: Throwable) {
|
||||||
log.trace(ex)
|
log.trace(ex)
|
||||||
dst.json = JsonObject()
|
dst.json = JsonObject()
|
||||||
}
|
}
|
||||||
|
|
||||||
dst.hash = cursor.getString(colIdx.idx_hash)
|
dst.hash = cursor.getString(colIdx.idx_hash)
|
||||||
return dst
|
return dst
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,8 +26,8 @@ import java.util.concurrent.TimeUnit
|
||||||
import kotlin.math.ceil
|
import kotlin.math.ceil
|
||||||
|
|
||||||
class CustomEmojiCache(
|
class CustomEmojiCache(
|
||||||
val context: Context,
|
val context: Context,
|
||||||
private val handler: Handler
|
private val handler: Handler
|
||||||
) {
|
) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
@ -48,10 +48,10 @@ class CustomEmojiCache(
|
||||||
}
|
}
|
||||||
|
|
||||||
private class DbCache(
|
private class DbCache(
|
||||||
val id: Long,
|
val id: Long,
|
||||||
val timeUsed: Long,
|
val timeUsed: Long,
|
||||||
val data: ByteArray
|
val data: ByteArray
|
||||||
) {
|
) {
|
||||||
|
|
||||||
companion object : TableCompanion {
|
companion object : TableCompanion {
|
||||||
|
|
||||||
|
@ -65,48 +65,48 @@ class CustomEmojiCache(
|
||||||
|
|
||||||
override fun onDBCreate(db: SQLiteDatabase) {
|
override fun onDBCreate(db: SQLiteDatabase) {
|
||||||
db.execSQL(
|
db.execSQL(
|
||||||
"""create table if not exists $table
|
"""create table if not exists $table
|
||||||
($COL_ID INTEGER PRIMARY KEY
|
($COL_ID INTEGER PRIMARY KEY
|
||||||
,$COL_TIME_SAVE integer not null
|
,$COL_TIME_SAVE integer not null
|
||||||
,$COL_TIME_USED integer not null
|
,$COL_TIME_USED integer not null
|
||||||
,$COL_URL text not null
|
,$COL_URL text not null
|
||||||
,$COL_DATA blob not null
|
,$COL_DATA blob not null
|
||||||
)""".trimIndent()
|
)""".trimIndent()
|
||||||
)
|
)
|
||||||
db.execSQL("create unique index if not exists ${table}_url on ${table}($COL_URL)")
|
db.execSQL("create unique index if not exists ${table}_url on ${table}($COL_URL)")
|
||||||
db.execSQL("create index if not exists ${table}_old on ${table}($COL_TIME_USED)")
|
db.execSQL("create index if not exists ${table}_old on ${table}($COL_TIME_USED)")
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDBUpgrade(
|
override fun onDBUpgrade(
|
||||||
db: SQLiteDatabase,
|
db: SQLiteDatabase,
|
||||||
oldVersion: Int,
|
oldVersion: Int,
|
||||||
newVersion: Int
|
newVersion: Int
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun load(db: SQLiteDatabase, url: String, now: Long) =
|
fun load(db: SQLiteDatabase, url: String, now: Long) =
|
||||||
db.rawQuery(
|
db.rawQuery(
|
||||||
"select $COL_ID,$COL_TIME_USED,$COL_DATA from $table where $COL_URL=?",
|
"select $COL_ID,$COL_TIME_USED,$COL_DATA from $table where $COL_URL=?",
|
||||||
arrayOf(url)
|
arrayOf(url)
|
||||||
)?.use { cursor ->
|
)?.use { cursor ->
|
||||||
if (cursor.count == 0)
|
if (cursor.count == 0)
|
||||||
null
|
null
|
||||||
else {
|
else {
|
||||||
cursor.moveToNext()
|
cursor.moveToNext()
|
||||||
DbCache(
|
DbCache(
|
||||||
id = cursor.getLong(cursor.getColumnIndex(COL_ID)),
|
id = cursor.getLong(cursor.getColumnIndex(COL_ID)),
|
||||||
timeUsed = cursor.getLong(cursor.getColumnIndex(COL_TIME_USED)),
|
timeUsed = cursor.getLong(cursor.getColumnIndex(COL_TIME_USED)),
|
||||||
data = cursor.getBlob(cursor.getColumnIndex(COL_DATA))
|
data = cursor.getBlob(cursor.getColumnIndex(COL_DATA))
|
||||||
).apply {
|
).apply {
|
||||||
if (now - timeUsed >= 5 * 3600000L) {
|
if (now - timeUsed >= 5 * 3600000L) {
|
||||||
db.update(
|
db.update(
|
||||||
table,
|
table,
|
||||||
ContentValues().apply {
|
ContentValues().apply {
|
||||||
put(COL_TIME_USED, now)
|
put(COL_TIME_USED, now)
|
||||||
},
|
},
|
||||||
"$COL_ID=?",
|
"$COL_ID=?",
|
||||||
arrayOf(id.toString())
|
arrayOf(id.toString())
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -115,23 +115,23 @@ class CustomEmojiCache(
|
||||||
fun sweep(db: SQLiteDatabase, now: Long) {
|
fun sweep(db: SQLiteDatabase, now: Long) {
|
||||||
val expire = now - TimeUnit.DAYS.toMillis(30)
|
val expire = now - TimeUnit.DAYS.toMillis(30)
|
||||||
db.delete(
|
db.delete(
|
||||||
table,
|
table,
|
||||||
"$COL_TIME_USED < ?",
|
"$COL_TIME_USED < ?",
|
||||||
arrayOf(expire.toString())
|
arrayOf(expire.toString())
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun update(db: SQLiteDatabase, url: String, data: ByteArray) {
|
fun update(db: SQLiteDatabase, url: String, data: ByteArray) {
|
||||||
val now = System.currentTimeMillis()
|
val now = System.currentTimeMillis()
|
||||||
db.replace(table,
|
db.replace(table,
|
||||||
null,
|
null,
|
||||||
ContentValues().apply {
|
ContentValues().apply {
|
||||||
put(COL_URL, url)
|
put(COL_URL, url)
|
||||||
put(COL_DATA, data)
|
put(COL_DATA, data)
|
||||||
put(COL_TIME_USED, now)
|
put(COL_TIME_USED, now)
|
||||||
put(COL_TIME_SAVE, now)
|
put(COL_TIME_SAVE, now)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -166,10 +166,10 @@ class CustomEmojiCache(
|
||||||
}
|
}
|
||||||
|
|
||||||
private class Request(
|
private class Request(
|
||||||
val refTarget: WeakReference<Any>,
|
val refTarget: WeakReference<Any>,
|
||||||
val url: String,
|
val url: String,
|
||||||
val onLoadComplete: () -> Unit
|
val onLoadComplete: () -> Unit
|
||||||
)
|
)
|
||||||
|
|
||||||
// APNGデコード済のキャッシュデータ
|
// APNGデコード済のキャッシュデータ
|
||||||
private val cache = ConcurrentHashMap<String, CacheItem>()
|
private val cache = ConcurrentHashMap<String, CacheItem>()
|
||||||
|
@ -253,10 +253,10 @@ class CustomEmojiCache(
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getFrames(
|
fun getFrames(
|
||||||
refDrawTarget: WeakReference<Any>?,
|
refDrawTarget: WeakReference<Any>?,
|
||||||
url: String,
|
url: String,
|
||||||
onLoadComplete: () -> Unit
|
onLoadComplete: () -> Unit
|
||||||
): ApngFrames? {
|
): ApngFrames? {
|
||||||
try {
|
try {
|
||||||
if (refDrawTarget?.get() == null) {
|
if (refDrawTarget?.get() == null) {
|
||||||
log.e("draw: DrawTarget is null ")
|
log.e("draw: DrawTarget is null ")
|
||||||
|
@ -353,13 +353,7 @@ class CustomEmojiCache(
|
||||||
|
|
||||||
if (cache_used) continue
|
if (cache_used) continue
|
||||||
|
|
||||||
if (DEBUG)
|
if (DEBUG) log.d("start get image. queue_size=${queue_size}, cache_size=${cache_size} url=${request.url}")
|
||||||
log.d(
|
|
||||||
"start get image. queue_size=%d, cache_size=%d url=%s",
|
|
||||||
queue_size,
|
|
||||||
cache_size,
|
|
||||||
request.url
|
|
||||||
)
|
|
||||||
|
|
||||||
val now = System.currentTimeMillis()
|
val now = System.currentTimeMillis()
|
||||||
|
|
||||||
|
@ -377,8 +371,7 @@ class CustomEmojiCache(
|
||||||
data = try {
|
data = try {
|
||||||
App1.getHttpCached(request.url)
|
App1.getHttpCached(request.url)
|
||||||
} catch (ex: Throwable) {
|
} catch (ex: Throwable) {
|
||||||
log.e("get failed. url=%s", request.url)
|
log.trace(ex, "get failed. url=${request.url}")
|
||||||
log.trace(ex)
|
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
te = elapsedTime
|
te = elapsedTime
|
||||||
|
@ -473,7 +466,7 @@ class CustomEmojiCache(
|
||||||
// fall thru
|
// fall thru
|
||||||
} catch (ex: Throwable) {
|
} catch (ex: Throwable) {
|
||||||
if (DEBUG) log.trace(ex)
|
if (DEBUG) log.trace(ex)
|
||||||
log.e(ex, "PNG decode failed. %s ", url)
|
log.e(ex, "PNG decode failed. $url ")
|
||||||
}
|
}
|
||||||
|
|
||||||
// 通常のビットマップでのロードを試みる
|
// 通常のビットマップでのロードを試みる
|
||||||
|
@ -483,12 +476,12 @@ class CustomEmojiCache(
|
||||||
if (DEBUG) log.d("bitmap decoded.")
|
if (DEBUG) log.d("bitmap decoded.")
|
||||||
return ApngFrames(b)
|
return ApngFrames(b)
|
||||||
} else {
|
} else {
|
||||||
log.e("Bitmap decode returns null. %s", url)
|
log.e("Bitmap decode returns null. $url")
|
||||||
}
|
}
|
||||||
|
|
||||||
// fall thru
|
// fall thru
|
||||||
} catch (ex: Throwable) {
|
} catch (ex: Throwable) {
|
||||||
log.e(ex, "Bitmap decode failed. %s", url)
|
log.e(ex, "Bitmap decode failed. $url")
|
||||||
}
|
}
|
||||||
|
|
||||||
// SVGのロードを試みる
|
// SVGのロードを試みる
|
||||||
|
@ -501,7 +494,7 @@ class CustomEmojiCache(
|
||||||
|
|
||||||
// fall thru
|
// fall thru
|
||||||
} catch (ex: Throwable) {
|
} catch (ex: Throwable) {
|
||||||
log.e(ex, "SVG decode failed. %s", url)
|
log.e(ex, "SVG decode failed. $url")
|
||||||
}
|
}
|
||||||
|
|
||||||
return null
|
return null
|
||||||
|
@ -510,9 +503,9 @@ class CustomEmojiCache(
|
||||||
private val options = BitmapFactory.Options()
|
private val options = BitmapFactory.Options()
|
||||||
|
|
||||||
private fun decodeBitmap(
|
private fun decodeBitmap(
|
||||||
data: ByteArray,
|
data: ByteArray,
|
||||||
@Suppress("SameParameterValue") pixel_max: Int
|
@Suppress("SameParameterValue") pixel_max: Int
|
||||||
): Bitmap? {
|
): Bitmap? {
|
||||||
options.inJustDecodeBounds = true
|
options.inJustDecodeBounds = true
|
||||||
options.inScaled = false
|
options.inScaled = false
|
||||||
options.outWidth = 0
|
options.outWidth = 0
|
||||||
|
@ -536,10 +529,10 @@ class CustomEmojiCache(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun decodeSVG(
|
private fun decodeSVG(
|
||||||
url: String,
|
url: String,
|
||||||
data: ByteArray,
|
data: ByteArray,
|
||||||
@Suppress("SameParameterValue") pixelMax: Float
|
@Suppress("SameParameterValue") pixelMax: Float
|
||||||
): Bitmap? {
|
): Bitmap? {
|
||||||
try {
|
try {
|
||||||
val svg = SVG.getFromInputStream(ByteArrayInputStream(data))
|
val svg = SVG.getFromInputStream(ByteArrayInputStream(data))
|
||||||
|
|
||||||
|
@ -572,13 +565,13 @@ class CustomEmojiCache(
|
||||||
val canvas = Canvas(b)
|
val canvas = Canvas(b)
|
||||||
|
|
||||||
svg.renderToCanvas(
|
svg.renderToCanvas(
|
||||||
canvas,
|
canvas,
|
||||||
if (aspect >= 1f) {
|
if (aspect >= 1f) {
|
||||||
RectF(0f, h_ceil - dst_h, dst_w, dst_h) // 後半はw,hを指定する
|
RectF(0f, h_ceil - dst_h, dst_w, dst_h) // 後半はw,hを指定する
|
||||||
} else {
|
} else {
|
||||||
RectF(w_ceil - dst_w, 0f, dst_w, dst_h) // 後半はw,hを指定する
|
RectF(w_ceil - dst_w, 0f, dst_w, dst_h) // 後半はw,hを指定する
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
return b
|
return b
|
||||||
} catch (ex: Throwable) {
|
} catch (ex: Throwable) {
|
||||||
log.e(ex, "decodeSVG failed. $url")
|
log.e(ex, "decodeSVG failed. $url")
|
||||||
|
|
|
@ -13,302 +13,302 @@ import java.util.concurrent.ConcurrentHashMap
|
||||||
import java.util.concurrent.ConcurrentLinkedQueue
|
import java.util.concurrent.ConcurrentLinkedQueue
|
||||||
|
|
||||||
class CustomEmojiLister(
|
class CustomEmojiLister(
|
||||||
val context : Context,
|
val context: Context,
|
||||||
private val handler : Handler
|
private val handler: Handler
|
||||||
) {
|
) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
private val log = LogCategory("CustomEmojiLister")
|
private val log = LogCategory("CustomEmojiLister")
|
||||||
|
|
||||||
internal const val CACHE_MAX = 50
|
internal const val CACHE_MAX = 50
|
||||||
|
|
||||||
internal const val ERROR_EXPIRE = 60000L * 5
|
internal const val ERROR_EXPIRE = 60000L * 5
|
||||||
|
|
||||||
private val elapsedTime : Long
|
private val elapsedTime: Long
|
||||||
get() = SystemClock.elapsedRealtime()
|
get() = SystemClock.elapsedRealtime()
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class CacheItem(
|
internal class CacheItem(
|
||||||
val key : String,
|
val key: String,
|
||||||
var list : ArrayList<CustomEmoji>? = null,
|
var list: ArrayList<CustomEmoji>? = null,
|
||||||
var listWithAliases : ArrayList<CustomEmoji>? = null,
|
var listWithAliases: ArrayList<CustomEmoji>? = null,
|
||||||
// ロードした時刻
|
// ロードした時刻
|
||||||
var time_update : Long = elapsedTime,
|
var time_update: Long = elapsedTime,
|
||||||
// 参照された時刻
|
// 参照された時刻
|
||||||
var time_used : Long = time_update
|
var time_used: Long = time_update
|
||||||
)
|
)
|
||||||
|
|
||||||
internal class Request(
|
internal class Request(
|
||||||
val accessInfo : SavedAccount,
|
val accessInfo: SavedAccount,
|
||||||
val reportWithAliases : Boolean = false,
|
val reportWithAliases: Boolean = false,
|
||||||
val onListLoaded : (list : ArrayList<CustomEmoji>) -> Unit?
|
val onListLoaded: (list: ArrayList<CustomEmoji>) -> Unit?
|
||||||
)
|
)
|
||||||
|
|
||||||
// 成功キャッシュ
|
// 成功キャッシュ
|
||||||
internal val cache = ConcurrentHashMap<String, CacheItem>()
|
internal val cache = ConcurrentHashMap<String, CacheItem>()
|
||||||
|
|
||||||
// エラーキャッシュ
|
// エラーキャッシュ
|
||||||
internal val cache_error = ConcurrentHashMap<String, Long>()
|
internal val cache_error = ConcurrentHashMap<String, Long>()
|
||||||
|
|
||||||
private val cache_error_item = CacheItem("error")
|
private val cache_error_item = CacheItem("error")
|
||||||
|
|
||||||
// ロード要求
|
// ロード要求
|
||||||
internal val queue = ConcurrentLinkedQueue<Request>()
|
internal val queue = ConcurrentLinkedQueue<Request>()
|
||||||
|
|
||||||
private val worker : Worker
|
private val worker: Worker
|
||||||
|
|
||||||
init {
|
init {
|
||||||
this.worker = Worker()
|
this.worker = Worker()
|
||||||
}
|
}
|
||||||
|
|
||||||
// ネットワーク接続が変化したらエラーキャッシュをクリア
|
// ネットワーク接続が変化したらエラーキャッシュをクリア
|
||||||
fun onNetworkChanged() {
|
fun onNetworkChanged() {
|
||||||
cache_error.clear()
|
cache_error.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getCached(now : Long, accessInfo : SavedAccount) : CacheItem? {
|
private fun getCached(now: Long, accessInfo: SavedAccount): CacheItem? {
|
||||||
val host = accessInfo.apiHost.ascii
|
val host = accessInfo.apiHost.ascii
|
||||||
|
|
||||||
// 成功キャッシュ
|
// 成功キャッシュ
|
||||||
val item = cache[host]
|
val item = cache[host]
|
||||||
if(item != null && now - item.time_update <= ERROR_EXPIRE) {
|
if (item != null && now - item.time_update <= ERROR_EXPIRE) {
|
||||||
item.time_used = now
|
item.time_used = now
|
||||||
return item
|
return item
|
||||||
}
|
}
|
||||||
|
|
||||||
// エラーキャッシュ
|
// エラーキャッシュ
|
||||||
val time_error = cache_error[host]
|
val time_error = cache_error[host]
|
||||||
if(time_error != null && now < time_error + ERROR_EXPIRE) {
|
if (time_error != null && now < time_error + ERROR_EXPIRE) {
|
||||||
return cache_error_item
|
return cache_error_item
|
||||||
}
|
}
|
||||||
|
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getList(
|
fun getList(
|
||||||
accessInfo : SavedAccount,
|
accessInfo: SavedAccount,
|
||||||
onListLoaded : (list : ArrayList<CustomEmoji>) -> Unit
|
onListLoaded: (list: ArrayList<CustomEmoji>) -> Unit
|
||||||
) : ArrayList<CustomEmoji>? {
|
): ArrayList<CustomEmoji>? {
|
||||||
try {
|
try {
|
||||||
synchronized(cache) {
|
synchronized(cache) {
|
||||||
val item = getCached(elapsedTime, accessInfo)
|
val item = getCached(elapsedTime, accessInfo)
|
||||||
if(item != null) return item.list
|
if (item != null) return item.list
|
||||||
}
|
}
|
||||||
|
|
||||||
queue.add(Request(accessInfo, onListLoaded = onListLoaded))
|
queue.add(Request(accessInfo, onListLoaded = onListLoaded))
|
||||||
worker.notifyEx()
|
worker.notifyEx()
|
||||||
} catch(ex : Throwable) {
|
} catch (ex: Throwable) {
|
||||||
log.trace(ex)
|
log.trace(ex)
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getListWithAliases(
|
fun getListWithAliases(
|
||||||
accessInfo : SavedAccount,
|
accessInfo: SavedAccount,
|
||||||
onListLoaded : (list : ArrayList<CustomEmoji>) -> Unit
|
onListLoaded: (list: ArrayList<CustomEmoji>) -> Unit
|
||||||
) : ArrayList<CustomEmoji>? {
|
): ArrayList<CustomEmoji>? {
|
||||||
try {
|
try {
|
||||||
synchronized(cache) {
|
synchronized(cache) {
|
||||||
val item = getCached(elapsedTime, accessInfo)
|
val item = getCached(elapsedTime, accessInfo)
|
||||||
if(item != null) return item.listWithAliases
|
if (item != null) return item.listWithAliases
|
||||||
}
|
}
|
||||||
|
|
||||||
queue.add(
|
queue.add(
|
||||||
Request(
|
Request(
|
||||||
accessInfo,
|
accessInfo,
|
||||||
reportWithAliases = true,
|
reportWithAliases = true,
|
||||||
onListLoaded = onListLoaded
|
onListLoaded = onListLoaded
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
worker.notifyEx()
|
worker.notifyEx()
|
||||||
} catch(ex : Throwable) {
|
} catch (ex: Throwable) {
|
||||||
log.trace(ex)
|
log.trace(ex)
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getMap(accessInfo : SavedAccount) : HashMap<String, CustomEmoji>? {
|
fun getMap(accessInfo: SavedAccount): HashMap<String, CustomEmoji>? {
|
||||||
val list = getList(accessInfo) {
|
val list = getList(accessInfo) {
|
||||||
// 遅延ロード非対応
|
// 遅延ロード非対応
|
||||||
} ?: return null
|
} ?: return null
|
||||||
//
|
//
|
||||||
val dst = HashMap<String, CustomEmoji>()
|
val dst = HashMap<String, CustomEmoji>()
|
||||||
for(e in list) {
|
for (e in list) {
|
||||||
dst[e.shortcode] = e
|
dst[e.shortcode] = e
|
||||||
}
|
}
|
||||||
return dst
|
return dst
|
||||||
}
|
}
|
||||||
|
|
||||||
private inner class Worker : WorkerBase() {
|
private inner class Worker : WorkerBase() {
|
||||||
|
|
||||||
override fun cancel() {
|
override fun cancel() {
|
||||||
// このスレッドはキャンセルされない。プロセスが生きている限り動き続ける。
|
// このスレッドはキャンセルされない。プロセスが生きている限り動き続ける。
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun run() {
|
override suspend fun run() {
|
||||||
while(true) {
|
while (true) {
|
||||||
try {
|
try {
|
||||||
// リクエストを取得する
|
// リクエストを取得する
|
||||||
val request = queue.poll()
|
val request = queue.poll()
|
||||||
if(request == null) {
|
if (request == null) {
|
||||||
// なければ待機
|
// なければ待機
|
||||||
waitEx(86400000L)
|
waitEx(86400000L)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
val cached = synchronized(cache) {
|
val cached = synchronized(cache) {
|
||||||
|
|
||||||
val item = getCached(elapsedTime, request.accessInfo)
|
val item = getCached(elapsedTime, request.accessInfo)
|
||||||
return@synchronized if(item != null) {
|
return@synchronized if (item != null) {
|
||||||
val list = item.list
|
val list = item.list
|
||||||
val listWithAliases = item.listWithAliases
|
val listWithAliases = item.listWithAliases
|
||||||
if(list != null && listWithAliases != null) {
|
if (list != null && listWithAliases != null) {
|
||||||
fireCallback(request, list, listWithAliases)
|
fireCallback(request, list, listWithAliases)
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
// キャッシュにはなかった
|
// キャッシュにはなかった
|
||||||
sweep_cache()
|
sweep_cache()
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(cached) continue
|
if (cached) continue
|
||||||
|
|
||||||
val accessInfo = request.accessInfo
|
val accessInfo = request.accessInfo
|
||||||
val cacheKey = accessInfo.apiHost.ascii
|
val cacheKey = accessInfo.apiHost.ascii
|
||||||
var list : ArrayList<CustomEmoji>? = null
|
var list: ArrayList<CustomEmoji>? = null
|
||||||
var listWithAlias : ArrayList<CustomEmoji>? = null
|
var listWithAlias: ArrayList<CustomEmoji>? = null
|
||||||
try {
|
try {
|
||||||
val data = if(accessInfo.isMisskey) {
|
val data = if (accessInfo.isMisskey) {
|
||||||
App1.getHttpCachedString(
|
App1.getHttpCachedString(
|
||||||
"https://${cacheKey}/api/meta",
|
"https://${cacheKey}/api/meta",
|
||||||
accessInfo = accessInfo
|
accessInfo = accessInfo
|
||||||
) { builder ->
|
) { builder ->
|
||||||
builder.post(JsonObject().toRequestBody())
|
builder.post(JsonObject().toRequestBody())
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
App1.getHttpCachedString(
|
App1.getHttpCachedString(
|
||||||
"https://${cacheKey}/api/v1/custom_emojis",
|
"https://${cacheKey}/api/v1/custom_emojis",
|
||||||
accessInfo = accessInfo
|
accessInfo = accessInfo
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if(data != null) {
|
if (data != null) {
|
||||||
val a = decodeEmojiList(data, accessInfo)
|
val a = decodeEmojiList(data, accessInfo)
|
||||||
list = a
|
list = a
|
||||||
listWithAlias = makeListWithAlias(a)
|
listWithAlias = makeListWithAlias(a)
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch(ex : Throwable) {
|
} catch (ex: Throwable) {
|
||||||
log.trace(ex)
|
log.trace(ex)
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized(cache) {
|
synchronized(cache) {
|
||||||
val now = elapsedTime
|
val now = elapsedTime
|
||||||
if(list == null || listWithAlias == null) {
|
if (list == null || listWithAlias == null) {
|
||||||
cache_error.put(cacheKey, now)
|
cache_error.put(cacheKey, now)
|
||||||
} else {
|
} else {
|
||||||
var item : CacheItem? = cache[cacheKey]
|
var item: CacheItem? = cache[cacheKey]
|
||||||
if(item == null) {
|
if (item == null) {
|
||||||
item = CacheItem(cacheKey, list, listWithAlias)
|
item = CacheItem(cacheKey, list, listWithAlias)
|
||||||
cache[cacheKey] = item
|
cache[cacheKey] = item
|
||||||
} else {
|
} else {
|
||||||
item.list = list
|
item.list = list
|
||||||
item.listWithAliases = listWithAlias
|
item.listWithAliases = listWithAlias
|
||||||
item.time_update = now
|
item.time_update = now
|
||||||
}
|
}
|
||||||
fireCallback(request, list, listWithAlias)
|
fireCallback(request, list, listWithAlias)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch(ex : Throwable) {
|
} catch (ex: Throwable) {
|
||||||
log.trace(ex)
|
log.trace(ex)
|
||||||
waitEx(3000L)
|
waitEx(3000L)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun fireCallback(
|
private fun fireCallback(
|
||||||
request : Request,
|
request: Request,
|
||||||
list : ArrayList<CustomEmoji>,
|
list: ArrayList<CustomEmoji>,
|
||||||
listWithAliases : ArrayList<CustomEmoji>
|
listWithAliases: ArrayList<CustomEmoji>
|
||||||
) {
|
) {
|
||||||
handler.post {
|
handler.post {
|
||||||
request.onListLoaded(
|
request.onListLoaded(
|
||||||
if(request.reportWithAliases) {
|
if (request.reportWithAliases) {
|
||||||
listWithAliases
|
listWithAliases
|
||||||
} else {
|
} else {
|
||||||
list
|
list
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// キャッシュの掃除
|
// キャッシュの掃除
|
||||||
private fun sweep_cache() {
|
private fun sweep_cache() {
|
||||||
// 超過してる数
|
// 超過してる数
|
||||||
val over = cache.size - CACHE_MAX
|
val over = cache.size - CACHE_MAX
|
||||||
if(over <= 0) return
|
if (over <= 0) return
|
||||||
|
|
||||||
// 古い要素を一時リストに集める
|
// 古い要素を一時リストに集める
|
||||||
val now = elapsedTime
|
val now = elapsedTime
|
||||||
val list = ArrayList<CacheItem>(over)
|
val list = ArrayList<CacheItem>(over)
|
||||||
for(item in cache.values) {
|
for (item in cache.values) {
|
||||||
if(now - item.time_used > 1000L) list.add(item)
|
if (now - item.time_used > 1000L) list.add(item)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 昇順ソート
|
// 昇順ソート
|
||||||
list.sortBy { it.time_used }
|
list.sortBy { it.time_used }
|
||||||
|
|
||||||
// 古い物から順に捨てる
|
// 古い物から順に捨てる
|
||||||
var removed = 0
|
var removed = 0
|
||||||
for(item in list) {
|
for (item in list) {
|
||||||
cache.remove(item.key)
|
cache.remove(item.key)
|
||||||
if(++ removed >= over) break
|
if (++removed >= over) break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun decodeEmojiList(
|
private fun decodeEmojiList(
|
||||||
data : String,
|
data: String,
|
||||||
accessInfo : SavedAccount
|
accessInfo: SavedAccount
|
||||||
) : ArrayList<CustomEmoji>? {
|
): ArrayList<CustomEmoji>? {
|
||||||
return try {
|
return try {
|
||||||
val list = if(accessInfo.isMisskey) {
|
val list = if (accessInfo.isMisskey) {
|
||||||
parseList(
|
parseList(
|
||||||
CustomEmoji.decodeMisskey,
|
CustomEmoji.decodeMisskey,
|
||||||
accessInfo.apDomain,
|
accessInfo.apDomain,
|
||||||
data.decodeJsonObject().jsonArray("emojis")
|
data.decodeJsonObject().jsonArray("emojis")
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
parseList(
|
parseList(
|
||||||
CustomEmoji.decode,
|
CustomEmoji.decode,
|
||||||
accessInfo.apDomain,
|
accessInfo.apDomain,
|
||||||
data.decodeJsonArray()
|
data.decodeJsonArray()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
list.sortWith(compareBy(String.CASE_INSENSITIVE_ORDER) { it.shortcode })
|
list.sortWith(compareBy(String.CASE_INSENSITIVE_ORDER) { it.shortcode })
|
||||||
list
|
list
|
||||||
} catch(ex : Throwable) {
|
} catch (ex: Throwable) {
|
||||||
log.e(ex, "decodeEmojiList failed. instance=%s", accessInfo.apiHost.ascii)
|
log.e(ex, "decodeEmojiList failed. instance=${accessInfo.apiHost.ascii}")
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun makeListWithAlias(list : ArrayList<CustomEmoji>?) : ArrayList<CustomEmoji> {
|
private fun makeListWithAlias(list: ArrayList<CustomEmoji>?): ArrayList<CustomEmoji> {
|
||||||
val dst = ArrayList<CustomEmoji>()
|
val dst = ArrayList<CustomEmoji>()
|
||||||
if(list != null) {
|
if (list != null) {
|
||||||
dst.addAll(list)
|
dst.addAll(list)
|
||||||
for(item in list) {
|
for (item in list) {
|
||||||
val aliases = item.aliases ?: continue
|
val aliases = item.aliases ?: continue
|
||||||
for(alias in aliases) {
|
for (alias in aliases) {
|
||||||
if(alias.equals(item.shortcode, ignoreCase = true)) continue
|
if (alias.equals(item.shortcode, ignoreCase = true)) continue
|
||||||
dst.add(item.makeAlias(alias))
|
dst.add(item.makeAlias(alias))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dst.sortWith(compareBy(String.CASE_INSENSITIVE_ORDER) { it.alias ?: it.shortcode })
|
dst.sortWith(compareBy(String.CASE_INSENSITIVE_ORDER) { it.alias ?: it.shortcode })
|
||||||
}
|
}
|
||||||
return dst
|
return dst
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -350,7 +350,7 @@ class PostHelper(
|
||||||
if (visibility == checkVisibility && !checkFun(instance)) {
|
if (visibility == checkVisibility && !checkFun(instance)) {
|
||||||
val strVisibility = Styler.getVisibilityString(activity, account.isMisskey, checkVisibility)
|
val strVisibility = Styler.getVisibilityString(activity, account.isMisskey, checkVisibility)
|
||||||
return@runApiTask TootApiResult(
|
return@runApiTask TootApiResult(
|
||||||
getString(R.string.server_has_no_support_of_visibility,strVisibility)
|
getString(R.string.server_has_no_support_of_visibility, strVisibility)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -783,7 +783,7 @@ class PostHelper(
|
||||||
val limit = 100
|
val limit = 100
|
||||||
val s = src.substring(start, end)
|
val s = src.substring(start, end)
|
||||||
val acct_list = AcctSet.searchPrefix(s, limit)
|
val acct_list = AcctSet.searchPrefix(s, limit)
|
||||||
log.d("search for %s, result=%d", s, acct_list.size)
|
log.d("search for ${s}, result=${acct_list.size}")
|
||||||
if (acct_list.isEmpty()) {
|
if (acct_list.isEmpty()) {
|
||||||
closeAcctPopup()
|
closeAcctPopup()
|
||||||
} else {
|
} else {
|
||||||
|
@ -811,7 +811,7 @@ class PostHelper(
|
||||||
val limit = 100
|
val limit = 100
|
||||||
val s = src.substring(last_sharp + 1, end)
|
val s = src.substring(last_sharp + 1, end)
|
||||||
val tag_list = TagSet.searchPrefix(s, limit)
|
val tag_list = TagSet.searchPrefix(s, limit)
|
||||||
log.d("search for %s, result=%d", s, tag_list.size)
|
log.d("search for ${s}, result=${tag_list.size}")
|
||||||
if (tag_list.isEmpty()) {
|
if (tag_list.isEmpty()) {
|
||||||
closeAcctPopup()
|
closeAcctPopup()
|
||||||
} else {
|
} else {
|
||||||
|
@ -863,7 +863,7 @@ class PostHelper(
|
||||||
val s =
|
val s =
|
||||||
src.substring(last_colon + 1, end).lowercase().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=${matches.size}")
|
||||||
code_list.addAll(matches)
|
code_list.addAll(matches)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,79 +8,78 @@ import java.io.FileNotFoundException
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
class TaskList {
|
class TaskList {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
private val log = LogCategory("TaskList")
|
private val log = LogCategory("TaskList")
|
||||||
private const val FILE_TASK_LIST = "JOB_TASK_LIST"
|
private const val FILE_TASK_LIST = "JOB_TASK_LIST"
|
||||||
}
|
}
|
||||||
|
|
||||||
private lateinit var _list : LinkedList<JsonObject>
|
private lateinit var _list: LinkedList<JsonObject>
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
private fun prepareList(context : Context) : LinkedList<JsonObject> {
|
private fun prepareList(context: Context): LinkedList<JsonObject> {
|
||||||
if(! ::_list.isInitialized) {
|
if (!::_list.isInitialized) {
|
||||||
_list = LinkedList()
|
_list = LinkedList()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
context.openFileInput(FILE_TASK_LIST).use { inputStream ->
|
context.openFileInput(FILE_TASK_LIST).use { inputStream ->
|
||||||
val bao = ByteArrayOutputStream()
|
val bao = ByteArrayOutputStream()
|
||||||
IOUtils.copy(inputStream, bao)
|
IOUtils.copy(inputStream, bao)
|
||||||
bao.toByteArray().decodeUTF8().decodeJsonArray().objectList().forEach {
|
bao.toByteArray().decodeUTF8().decodeJsonArray().objectList().forEach {
|
||||||
_list.add(it)
|
_list.add(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch(ex : FileNotFoundException) {
|
} catch (ex: FileNotFoundException) {
|
||||||
log.e(ex, "prepareList: file not found.")
|
log.e(ex, "prepareList: file not found.")
|
||||||
} catch(ex : Throwable) {
|
} catch (ex: Throwable) {
|
||||||
log.trace(ex, "TaskList: prepareArray failed.")
|
log.trace(ex, "TaskList: prepareArray failed.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return _list
|
return _list
|
||||||
}
|
}
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
private fun saveArray(context : Context) {
|
private fun saveArray(context: Context) {
|
||||||
val list = prepareList(context)
|
val list = prepareList(context)
|
||||||
try {
|
try {
|
||||||
log.d("saveArray size=%s", list.size)
|
log.d("saveArray size=${list.size}")
|
||||||
val data = JsonArray(list).toString().encodeUTF8()
|
val data = JsonArray(list).toString().encodeUTF8()
|
||||||
context.openFileOutput(FILE_TASK_LIST, Context.MODE_PRIVATE)
|
context.openFileOutput(FILE_TASK_LIST, Context.MODE_PRIVATE)
|
||||||
.use { IOUtils.write(data, it) }
|
.use { IOUtils.write(data, it) }
|
||||||
} catch(ex : Throwable) {
|
} catch (ex: Throwable) {
|
||||||
log.trace(ex)
|
log.trace(ex, "TaskList: saveArray failed.size=${list.size}")
|
||||||
log.e(ex, "TaskList: saveArray failed.size=%s", list.size)
|
}
|
||||||
}
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
@Synchronized
|
||||||
@Synchronized
|
fun addLast(context: Context, removeOld: Boolean, taskData: JsonObject) {
|
||||||
fun addLast(context : Context, removeOld : Boolean, taskData : JsonObject) {
|
val list = prepareList(context)
|
||||||
val list = prepareList(context)
|
if (removeOld) {
|
||||||
if(removeOld) {
|
val it = list.iterator()
|
||||||
val it = list.iterator()
|
while (it.hasNext()) {
|
||||||
while(it.hasNext()) {
|
val item = it.next()
|
||||||
val item = it.next()
|
if (taskData == item) it.remove()
|
||||||
if(taskData == item) it.remove()
|
}
|
||||||
}
|
}
|
||||||
}
|
list.addLast(taskData)
|
||||||
list.addLast(taskData)
|
saveArray(context)
|
||||||
saveArray(context)
|
}
|
||||||
}
|
|
||||||
|
@Suppress("unused")
|
||||||
@Suppress("unused")
|
@Synchronized
|
||||||
@Synchronized
|
fun hasNext(context: Context): Boolean {
|
||||||
fun hasNext(context : Context) : Boolean {
|
return prepareList(context).isNotEmpty()
|
||||||
return prepareList(context).isNotEmpty()
|
}
|
||||||
}
|
|
||||||
|
@Synchronized
|
||||||
@Synchronized
|
fun next(context: Context): JsonObject? {
|
||||||
fun next(context : Context) : JsonObject? {
|
val list = prepareList(context)
|
||||||
val list = prepareList(context)
|
val item = if (list.isEmpty()) null else list.removeFirst()
|
||||||
val item = if(list.isEmpty()) null else list.removeFirst()
|
saveArray(context)
|
||||||
saveArray(context)
|
return item
|
||||||
return item
|
}
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,42 +11,42 @@ import jp.juggler.subwaytooter.StatusButtonsPopup
|
||||||
import jp.juggler.util.LogCategory
|
import jp.juggler.util.LogCategory
|
||||||
|
|
||||||
class MyListView : ListView {
|
class MyListView : ListView {
|
||||||
|
|
||||||
companion object {
|
|
||||||
private val log = LogCategory("MyListView")
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(context : Context) : super(context)
|
companion object {
|
||||||
constructor(context : Context, attrs : AttributeSet) : super(context, attrs)
|
private val log = LogCategory("MyListView")
|
||||||
constructor(context : Context, attrs : AttributeSet, defStyleAttr : Int) : super(context, attrs, defStyleAttr)
|
}
|
||||||
|
|
||||||
@SuppressLint("ClickableViewAccessibility")
|
constructor(context: Context) : super(context)
|
||||||
override fun onTouchEvent(ev : MotionEvent) : Boolean {
|
constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
|
||||||
|
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
|
||||||
// ポップアップを閉じた時にクリックでリストを触ったことになってしまう不具合の回避
|
|
||||||
val now = SystemClock.elapsedRealtime()
|
@SuppressLint("ClickableViewAccessibility")
|
||||||
if(now - StatusButtonsPopup.last_popup_close < 30L) {
|
override fun onTouchEvent(ev: MotionEvent): Boolean {
|
||||||
val action = ev.action
|
|
||||||
if(action == MotionEvent.ACTION_DOWN) {
|
// ポップアップを閉じた時にクリックでリストを触ったことになってしまう不具合の回避
|
||||||
// ポップアップを閉じた直後はタッチダウンを無視する
|
val now = SystemClock.elapsedRealtime()
|
||||||
return false
|
if (now - StatusButtonsPopup.last_popup_close < 30L) {
|
||||||
}
|
val action = ev.action
|
||||||
|
if (action == MotionEvent.ACTION_DOWN) {
|
||||||
val rv = super.onTouchEvent(ev)
|
// ポップアップを閉じた直後はタッチダウンを無視する
|
||||||
log.d("onTouchEvent action=%s, rv=%s", action, rv)
|
return false
|
||||||
return rv
|
}
|
||||||
}
|
|
||||||
|
val rv = super.onTouchEvent(ev)
|
||||||
return super.onTouchEvent(ev)
|
log.d("onTouchEvent action=${action}, rv=${rv}")
|
||||||
}
|
return rv
|
||||||
|
}
|
||||||
override fun layoutChildren() {
|
|
||||||
try {
|
return super.onTouchEvent(ev)
|
||||||
super.layoutChildren()
|
}
|
||||||
} catch(ex : Throwable) {
|
|
||||||
log.trace(ex)
|
override fun layoutChildren() {
|
||||||
}
|
try {
|
||||||
|
super.layoutChildren()
|
||||||
}
|
} catch (ex: Throwable) {
|
||||||
|
log.trace(ex)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -17,490 +17,491 @@ import kotlin.math.abs
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
import kotlin.math.sqrt
|
import kotlin.math.sqrt
|
||||||
|
|
||||||
class PinchBitmapView(context : Context, attrs : AttributeSet?, defStyle : Int) :
|
class PinchBitmapView(context: Context, attrs: AttributeSet?, defStyle: Int) :
|
||||||
View(context, attrs, defStyle) {
|
View(context, attrs, defStyle) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
internal val log = LogCategory("PinchImageView")
|
internal val log = LogCategory("PinchImageView")
|
||||||
|
|
||||||
// 数値を範囲内にクリップする
|
// 数値を範囲内にクリップする
|
||||||
private fun clip(min : Float, max : Float, v : Float) : Float {
|
private fun clip(min: Float, max: Float, v: Float): Float {
|
||||||
return if(v < min) min else if(v > max) max else v
|
return if (v < min) min else if (v > max) max else v
|
||||||
}
|
}
|
||||||
|
|
||||||
// ビューの幅と画像の描画サイズを元に描画位置をクリップする
|
// ビューの幅と画像の描画サイズを元に描画位置をクリップする
|
||||||
private fun clipTranslate(
|
private fun clipTranslate(
|
||||||
view_w : Float // ビューの幅
|
view_w: Float // ビューの幅
|
||||||
, bitmap_w : Float // 画像の幅
|
, bitmap_w: Float // 画像の幅
|
||||||
, current_scale : Float // 画像の拡大率
|
, current_scale: Float // 画像の拡大率
|
||||||
, trans_x : Float // タッチ操作による表示位置
|
, trans_x: Float // タッチ操作による表示位置
|
||||||
) : Float {
|
): Float {
|
||||||
|
|
||||||
// 余白(拡大率が小さい場合はプラス、拡大率が大きい場合はマイナス)
|
// 余白(拡大率が小さい場合はプラス、拡大率が大きい場合はマイナス)
|
||||||
val padding = view_w - bitmap_w * current_scale
|
val padding = view_w - bitmap_w * current_scale
|
||||||
|
|
||||||
// 余白が>=0なら画像を中心に表示する。 <0なら操作された位置をクリップする。
|
// 余白が>=0なら画像を中心に表示する。 <0なら操作された位置をクリップする。
|
||||||
return if(padding >= 0f) padding / 2f else clip(padding, 0f, trans_x)
|
return if (padding >= 0f) padding / 2f else clip(padding, 0f, trans_x)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private var callback : Callback? = null
|
private var callback: Callback? = null
|
||||||
|
|
||||||
private var bitmap : Bitmap? = null
|
private var bitmap: Bitmap? = null
|
||||||
private var bitmap_w : Float = 0.toFloat()
|
private var bitmap_w: Float = 0.toFloat()
|
||||||
private var bitmap_h : Float = 0.toFloat()
|
private var bitmap_h: Float = 0.toFloat()
|
||||||
private var bitmap_aspect : Float = 0.toFloat()
|
private var bitmap_aspect: Float = 0.toFloat()
|
||||||
|
|
||||||
// 画像を表示する位置と拡大率
|
// 画像を表示する位置と拡大率
|
||||||
private var current_trans_x : Float = 0.toFloat()
|
private var current_trans_x: Float = 0.toFloat()
|
||||||
private var current_trans_y : Float = 0.toFloat()
|
private var current_trans_y: Float = 0.toFloat()
|
||||||
private var current_scale : Float = 0.toFloat()
|
private var current_scale: Float = 0.toFloat()
|
||||||
|
|
||||||
// 画像表示に使う構造体
|
// 画像表示に使う構造体
|
||||||
private val drawMatrix = Matrix()
|
private val drawMatrix = Matrix()
|
||||||
internal val paint = Paint()
|
internal val paint = Paint()
|
||||||
|
|
||||||
// タッチ操作中に指を動かした
|
// タッチ操作中に指を動かした
|
||||||
private var bDrag : Boolean = false
|
private var bDrag: Boolean = false
|
||||||
|
|
||||||
// タッチ操作中に指の数を変えた
|
// タッチ操作中に指の数を変えた
|
||||||
private var bPointerCountChanged : Boolean = false
|
private var bPointerCountChanged: Boolean = false
|
||||||
|
|
||||||
// ページめくりに必要なスワイプ強度
|
// ページめくりに必要なスワイプ強度
|
||||||
private var swipe_velocity = 0f
|
private var swipe_velocity = 0f
|
||||||
private var swipe_velocity2 = 0f
|
private var swipe_velocity2 = 0f
|
||||||
|
|
||||||
// 指を動かしたと判断する距離
|
// 指を動かしたと判断する距離
|
||||||
private var drag_length = 0f
|
private var drag_length = 0f
|
||||||
|
|
||||||
private var time_touch_start = 0L
|
private var time_touch_start = 0L
|
||||||
|
|
||||||
// フリック操作の検出に使う
|
// フリック操作の検出に使う
|
||||||
private var velocityTracker : VelocityTracker? = null
|
private var velocityTracker: VelocityTracker? = null
|
||||||
|
|
||||||
private var click_time = 0L
|
private var click_time = 0L
|
||||||
private var click_count = 0
|
private var click_count = 0
|
||||||
|
|
||||||
// 移動後の指の位置
|
// 移動後の指の位置
|
||||||
internal val pos = PointerAvg()
|
internal val pos = PointerAvg()
|
||||||
|
|
||||||
// 移動開始時の指の位置
|
// 移動開始時の指の位置
|
||||||
private val start_pos = PointerAvg()
|
private val start_pos = PointerAvg()
|
||||||
|
|
||||||
// 移動開始時の画像の位置
|
// 移動開始時の画像の位置
|
||||||
private var start_image_trans_x : Float = 0.toFloat()
|
private var start_image_trans_x: Float = 0.toFloat()
|
||||||
private var start_image_trans_y : Float = 0.toFloat()
|
private var start_image_trans_y: Float = 0.toFloat()
|
||||||
private var start_image_scale : Float = 0.toFloat()
|
private var start_image_scale: Float = 0.toFloat()
|
||||||
|
|
||||||
private var scale_min : Float = 0.toFloat()
|
private var scale_min: Float = 0.toFloat()
|
||||||
private var scale_max : Float = 0.toFloat()
|
private var scale_max: Float = 0.toFloat()
|
||||||
|
|
||||||
private var view_w : Float = 0.toFloat()
|
private var view_w: Float = 0.toFloat()
|
||||||
private var view_h : Float = 0.toFloat()
|
private var view_h: Float = 0.toFloat()
|
||||||
private var view_aspect : Float = 0.toFloat()
|
private var view_aspect: Float = 0.toFloat()
|
||||||
|
|
||||||
private val tracking_matrix = Matrix()
|
private val tracking_matrix = Matrix()
|
||||||
private val tracking_matrix_inv = Matrix()
|
private val tracking_matrix_inv = Matrix()
|
||||||
private val avg_on_image1 = FloatArray(2)
|
private val avg_on_image1 = FloatArray(2)
|
||||||
private val avg_on_image2 = FloatArray(2)
|
private val avg_on_image2 = FloatArray(2)
|
||||||
|
|
||||||
constructor(context : Context) : this(context, null) {
|
constructor(context: Context) : this(context, null) {
|
||||||
init(context)
|
init(context)
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(context : Context, attrs : AttributeSet?) : this(context, attrs, 0) {
|
constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0) {
|
||||||
init(context)
|
init(context)
|
||||||
}
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
init(context)
|
init(context)
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun init(context : Context) {
|
internal fun init(context: Context) {
|
||||||
|
|
||||||
// 定数をdpからpxに変換
|
// 定数をdpからpxに変換
|
||||||
val density = context.resources.displayMetrics.density
|
val density = context.resources.displayMetrics.density
|
||||||
swipe_velocity = 1000f * density
|
swipe_velocity = 1000f * density
|
||||||
swipe_velocity2 = 250f * density
|
swipe_velocity2 = 250f * density
|
||||||
drag_length = 4f * density // 誤反応しがちなのでやや厳しめ
|
drag_length = 4f * density // 誤反応しがちなのでやや厳しめ
|
||||||
}
|
}
|
||||||
|
|
||||||
// ページめくり操作のコールバック
|
// ページめくり操作のコールバック
|
||||||
interface Callback {
|
interface Callback {
|
||||||
|
|
||||||
fun onSwipe(deltaX : Int, deltaY : Int)
|
fun onSwipe(deltaX: Int, deltaY: Int)
|
||||||
|
|
||||||
fun onMove(bitmap_w : Float, bitmap_h : Float, tx : Float, ty : Float, scale : Float)
|
fun onMove(bitmap_w: Float, bitmap_h: Float, tx: Float, ty: Float, scale: Float)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setCallback(callback : Callback?) {
|
fun setCallback(callback: Callback?) {
|
||||||
this.callback = callback
|
this.callback = callback
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setBitmap(b : Bitmap?) {
|
fun setBitmap(b: Bitmap?) {
|
||||||
|
|
||||||
bitmap?.recycle()
|
bitmap?.recycle()
|
||||||
|
|
||||||
this.bitmap = b
|
this.bitmap = b
|
||||||
|
|
||||||
initializeScale()
|
initializeScale()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDraw(canvas : Canvas) {
|
override fun onDraw(canvas: Canvas) {
|
||||||
super.onDraw(canvas)
|
super.onDraw(canvas)
|
||||||
|
|
||||||
val bitmap = this.bitmap
|
val bitmap = this.bitmap
|
||||||
if(bitmap != null && ! bitmap.isRecycled) {
|
if (bitmap != null && !bitmap.isRecycled) {
|
||||||
|
|
||||||
drawMatrix.reset()
|
drawMatrix.reset()
|
||||||
drawMatrix.postScale(current_scale, current_scale)
|
drawMatrix.postScale(current_scale, current_scale)
|
||||||
drawMatrix.postTranslate(current_trans_x, current_trans_y)
|
drawMatrix.postTranslate(current_trans_x, current_trans_y)
|
||||||
|
|
||||||
paint.isFilterBitmap = current_scale < 4f
|
paint.isFilterBitmap = current_scale < 4f
|
||||||
canvas.drawBitmap(bitmap, drawMatrix, paint)
|
canvas.drawBitmap(bitmap, drawMatrix, paint)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
||||||
|
|
||||||
view_w = max(1f, w.toFloat())
|
view_w = max(1f, w.toFloat())
|
||||||
view_h = max(1f, h.toFloat())
|
view_h = max(1f, h.toFloat())
|
||||||
view_aspect = view_w / view_h
|
view_aspect = view_w / view_h
|
||||||
|
|
||||||
initializeScale()
|
initializeScale()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun performClick() : Boolean {
|
override fun performClick(): Boolean {
|
||||||
super.performClick()
|
super.performClick()
|
||||||
|
|
||||||
initializeScale()
|
initializeScale()
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
private var defaultScale : Float = 1f
|
private var defaultScale: Float = 1f
|
||||||
|
|
||||||
// 表示位置の初期化
|
// 表示位置の初期化
|
||||||
// 呼ばれるのは、ビットマップを変更した時、ビューのサイズが変わった時、画像をクリックした時
|
// 呼ばれるのは、ビットマップを変更した時、ビューのサイズが変わった時、画像をクリックした時
|
||||||
private fun initializeScale() {
|
private fun initializeScale() {
|
||||||
val bitmap = this.bitmap
|
val bitmap = this.bitmap
|
||||||
if(bitmap != null && ! bitmap.isRecycled && view_w >= 1f) {
|
if (bitmap != null && !bitmap.isRecycled && view_w >= 1f) {
|
||||||
|
|
||||||
bitmap_w = max(1f, bitmap.width.toFloat())
|
bitmap_w = max(1f, bitmap.width.toFloat())
|
||||||
bitmap_h = max(1f, bitmap.height.toFloat())
|
bitmap_h = max(1f, bitmap.height.toFloat())
|
||||||
bitmap_aspect = bitmap_w / bitmap_h
|
bitmap_aspect = bitmap_w / bitmap_h
|
||||||
|
|
||||||
if(view_aspect > bitmap_aspect) {
|
if (view_aspect > bitmap_aspect) {
|
||||||
scale_min = view_h / bitmap_h / 2f
|
scale_min = view_h / bitmap_h / 2f
|
||||||
scale_max = view_w / bitmap_w * 8f
|
scale_max = view_w / bitmap_w * 8f
|
||||||
} else {
|
} else {
|
||||||
scale_min = view_w / bitmap_w / 2f
|
scale_min = view_w / bitmap_w / 2f
|
||||||
scale_max = view_h / bitmap_h * 8f
|
scale_max = view_h / bitmap_h * 8f
|
||||||
}
|
}
|
||||||
if(scale_max < scale_min) scale_max = scale_min * 16f
|
if (scale_max < scale_min) scale_max = scale_min * 16f
|
||||||
|
|
||||||
defaultScale = if(view_aspect > bitmap_aspect) {
|
defaultScale = if (view_aspect > bitmap_aspect) {
|
||||||
view_h / bitmap_h
|
view_h / bitmap_h
|
||||||
} else {
|
} else {
|
||||||
view_w / bitmap_w
|
view_w / bitmap_w
|
||||||
}
|
}
|
||||||
|
|
||||||
val draw_w = bitmap_w * defaultScale
|
val draw_w = bitmap_w * defaultScale
|
||||||
val draw_h = bitmap_h * defaultScale
|
val draw_h = bitmap_h * defaultScale
|
||||||
|
|
||||||
current_scale = defaultScale
|
current_scale = defaultScale
|
||||||
current_trans_x = (view_w - draw_w) / 2f
|
current_trans_x = (view_w - draw_w) / 2f
|
||||||
current_trans_y = (view_h - draw_h) / 2f
|
current_trans_y = (view_h - draw_h) / 2f
|
||||||
|
|
||||||
callback?.onMove(bitmap_w, bitmap_h, current_trans_x, current_trans_y, current_scale)
|
callback?.onMove(bitmap_w, bitmap_h, current_trans_x, current_trans_y, current_scale)
|
||||||
} else {
|
} else {
|
||||||
defaultScale = 1f
|
defaultScale = 1f
|
||||||
scale_min = 1f
|
scale_min = 1f
|
||||||
scale_max = 1f
|
scale_max = 1f
|
||||||
|
|
||||||
current_scale = defaultScale
|
current_scale = defaultScale
|
||||||
current_trans_y = 0f
|
current_trans_y = 0f
|
||||||
current_trans_x = 0f
|
current_trans_x = 0f
|
||||||
|
|
||||||
callback?.onMove(0f, 0f, current_trans_x, current_trans_y, current_scale)
|
callback?.onMove(0f, 0f, current_trans_x, current_trans_y, current_scale)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 画像がnullに変化した時も再描画が必要
|
// 画像がnullに変化した時も再描画が必要
|
||||||
invalidate()
|
invalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("ClickableViewAccessibility")
|
@SuppressLint("ClickableViewAccessibility")
|
||||||
override fun onTouchEvent(ev : MotionEvent) : Boolean {
|
override fun onTouchEvent(ev: MotionEvent): Boolean {
|
||||||
|
|
||||||
val bitmap = this.bitmap
|
val bitmap = this.bitmap
|
||||||
if(bitmap == null
|
if (bitmap == null
|
||||||
|| bitmap.isRecycled
|
|| bitmap.isRecycled
|
||||||
|| view_w < 1f)
|
|| view_w < 1f
|
||||||
return false
|
)
|
||||||
|
return false
|
||||||
val action = ev.action
|
|
||||||
|
val action = ev.action
|
||||||
if(action == MotionEvent.ACTION_DOWN) {
|
|
||||||
time_touch_start = SystemClock.elapsedRealtime()
|
if (action == MotionEvent.ACTION_DOWN) {
|
||||||
|
time_touch_start = SystemClock.elapsedRealtime()
|
||||||
velocityTracker?.clear()
|
|
||||||
velocityTracker = VelocityTracker.obtain()
|
velocityTracker?.clear()
|
||||||
velocityTracker?.addMovement(ev)
|
velocityTracker = VelocityTracker.obtain()
|
||||||
|
velocityTracker?.addMovement(ev)
|
||||||
bPointerCountChanged = false
|
|
||||||
bDrag = bPointerCountChanged
|
bPointerCountChanged = false
|
||||||
trackStart(ev)
|
bDrag = bPointerCountChanged
|
||||||
return true
|
trackStart(ev)
|
||||||
}
|
return true
|
||||||
|
}
|
||||||
velocityTracker?.addMovement(ev)
|
|
||||||
|
velocityTracker?.addMovement(ev)
|
||||||
when(action) {
|
|
||||||
MotionEvent.ACTION_POINTER_DOWN, MotionEvent.ACTION_POINTER_UP -> {
|
when (action) {
|
||||||
// タッチ操作中に指の数を変えた
|
MotionEvent.ACTION_POINTER_DOWN, MotionEvent.ACTION_POINTER_UP -> {
|
||||||
bPointerCountChanged = true
|
// タッチ操作中に指の数を変えた
|
||||||
bDrag = bPointerCountChanged
|
bPointerCountChanged = true
|
||||||
trackStart(ev)
|
bDrag = bPointerCountChanged
|
||||||
}
|
trackStart(ev)
|
||||||
|
}
|
||||||
MotionEvent.ACTION_MOVE -> trackNext(ev)
|
|
||||||
|
MotionEvent.ACTION_MOVE -> trackNext(ev)
|
||||||
MotionEvent.ACTION_UP -> {
|
|
||||||
trackNext(ev)
|
MotionEvent.ACTION_UP -> {
|
||||||
|
trackNext(ev)
|
||||||
checkClickOrPaging()
|
|
||||||
|
checkClickOrPaging()
|
||||||
velocityTracker?.recycle()
|
|
||||||
velocityTracker = null
|
velocityTracker?.recycle()
|
||||||
}
|
velocityTracker = null
|
||||||
}
|
}
|
||||||
return true
|
}
|
||||||
}
|
return true
|
||||||
|
}
|
||||||
private fun checkClickOrPaging() {
|
|
||||||
|
private fun checkClickOrPaging() {
|
||||||
if(! bDrag) {
|
|
||||||
// 指を動かしていないなら
|
if (!bDrag) {
|
||||||
|
// 指を動かしていないなら
|
||||||
val now = SystemClock.elapsedRealtime()
|
|
||||||
|
val now = SystemClock.elapsedRealtime()
|
||||||
if(now - time_touch_start >= 1000L) {
|
|
||||||
// ロングタップはタップカウントをリセットする
|
if (now - time_touch_start >= 1000L) {
|
||||||
log.d("click count reset by long tap")
|
// ロングタップはタップカウントをリセットする
|
||||||
click_count = 0
|
log.d("click count reset by long tap")
|
||||||
return
|
click_count = 0
|
||||||
}
|
return
|
||||||
|
}
|
||||||
val delta = now - click_time
|
|
||||||
click_time = now
|
val delta = now - click_time
|
||||||
|
click_time = now
|
||||||
if(delta > 334L) {
|
|
||||||
// 前回のタップからの時刻が長いとタップカウントをリセットする
|
if (delta > 334L) {
|
||||||
log.d("click count reset by long interval")
|
// 前回のタップからの時刻が長いとタップカウントをリセットする
|
||||||
click_count = 0
|
log.d("click count reset by long interval")
|
||||||
}
|
click_count = 0
|
||||||
|
}
|
||||||
++ click_count
|
|
||||||
|
++click_count
|
||||||
log.d("click %d %d", click_count, delta)
|
|
||||||
|
log.d("click ${click_count} ${delta}")
|
||||||
if(click_count >= 2) {
|
|
||||||
// ダブルタップでクリック操作
|
if (click_count >= 2) {
|
||||||
click_count = 0
|
// ダブルタップでクリック操作
|
||||||
performClick()
|
click_count = 0
|
||||||
}
|
performClick()
|
||||||
|
}
|
||||||
return
|
|
||||||
}
|
return
|
||||||
|
}
|
||||||
click_count = 0
|
|
||||||
|
click_count = 0
|
||||||
val velocityTracker = this.velocityTracker
|
|
||||||
if(! bPointerCountChanged && velocityTracker != null) {
|
val velocityTracker = this.velocityTracker
|
||||||
|
if (!bPointerCountChanged && velocityTracker != null) {
|
||||||
// 指の数を変えていないならページめくり操作かもしれない
|
|
||||||
|
// 指の数を変えていないならページめくり操作かもしれない
|
||||||
// 「画像を動かした」かどうかのチェック
|
|
||||||
val image_moved = max(
|
// 「画像を動かした」かどうかのチェック
|
||||||
abs(current_trans_x - start_image_trans_x),
|
val image_moved = max(
|
||||||
abs(current_trans_y - start_image_trans_y)
|
abs(current_trans_x - start_image_trans_x),
|
||||||
)
|
abs(current_trans_y - start_image_trans_y)
|
||||||
if(image_moved >= drag_length) {
|
)
|
||||||
log.d("image moved. not flick action. $image_moved")
|
if (image_moved >= drag_length) {
|
||||||
return
|
log.d("image moved. not flick action. $image_moved")
|
||||||
}
|
return
|
||||||
|
}
|
||||||
velocityTracker.computeCurrentVelocity(1000)
|
|
||||||
val vx = velocityTracker.xVelocity
|
velocityTracker.computeCurrentVelocity(1000)
|
||||||
val vy = velocityTracker.yVelocity
|
val vx = velocityTracker.xVelocity
|
||||||
val avx = abs(vx)
|
val vy = velocityTracker.yVelocity
|
||||||
val avy = abs(vy)
|
val avx = abs(vx)
|
||||||
val velocity = sqrt(vx * vx + vy * vy)
|
val avy = abs(vy)
|
||||||
val aspect = try {
|
val velocity = sqrt(vx * vx + vy * vy)
|
||||||
avx / avy
|
val aspect = try {
|
||||||
} catch(ex : Throwable) {
|
avx / avy
|
||||||
Float.MAX_VALUE
|
} catch (ex: Throwable) {
|
||||||
}
|
Float.MAX_VALUE
|
||||||
|
}
|
||||||
when {
|
|
||||||
aspect >= 0.9f -> {
|
when {
|
||||||
// 指を動かした方向が左右だった
|
aspect >= 0.9f -> {
|
||||||
|
// 指を動かした方向が左右だった
|
||||||
val vMin = when {
|
|
||||||
current_scale * bitmap_w <= view_w -> swipe_velocity2
|
val vMin = when {
|
||||||
else -> swipe_velocity
|
current_scale * bitmap_w <= view_w -> swipe_velocity2
|
||||||
}
|
else -> swipe_velocity
|
||||||
|
}
|
||||||
if(velocity < vMin) {
|
|
||||||
log.d("velocity $velocity not enough to pagingX")
|
if (velocity < vMin) {
|
||||||
return
|
log.d("velocity $velocity not enough to pagingX")
|
||||||
}
|
return
|
||||||
|
}
|
||||||
log.d("pagingX! m=$image_moved a=$aspect v=$velocity")
|
|
||||||
runOnMainLooper { callback?.onSwipe(if(vx >= 0f) - 1 else 1, 0) }
|
log.d("pagingX! m=$image_moved a=$aspect v=$velocity")
|
||||||
}
|
runOnMainLooper { callback?.onSwipe(if (vx >= 0f) -1 else 1, 0) }
|
||||||
|
}
|
||||||
aspect <= 0.333f -> {
|
|
||||||
// 指を動かした方向が上下だった
|
aspect <= 0.333f -> {
|
||||||
|
// 指を動かした方向が上下だった
|
||||||
val vMin = when {
|
|
||||||
current_scale * bitmap_h <= view_h -> swipe_velocity2
|
val vMin = when {
|
||||||
else -> swipe_velocity
|
current_scale * bitmap_h <= view_h -> swipe_velocity2
|
||||||
}
|
else -> swipe_velocity
|
||||||
|
}
|
||||||
if(velocity < vMin) {
|
|
||||||
log.d("velocity $velocity not enough to pagingY")
|
if (velocity < vMin) {
|
||||||
return
|
log.d("velocity $velocity not enough to pagingY")
|
||||||
}
|
return
|
||||||
|
}
|
||||||
log.d("pagingY! m=$image_moved a=$aspect v=$velocity")
|
|
||||||
runOnMainLooper { callback?.onSwipe(0, if(vy >= 0f) - 1 else 1) }
|
log.d("pagingY! m=$image_moved a=$aspect v=$velocity")
|
||||||
}
|
runOnMainLooper { callback?.onSwipe(0, if (vy >= 0f) -1 else 1) }
|
||||||
|
}
|
||||||
else -> log.d("flick is not horizontal/vertical. aspect=$aspect")
|
|
||||||
}
|
else -> log.d("flick is not horizontal/vertical. aspect=$aspect")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// マルチタッチの中心位置の計算
|
|
||||||
internal class PointerAvg {
|
// マルチタッチの中心位置の計算
|
||||||
|
internal class PointerAvg {
|
||||||
// タッチ位置の数
|
|
||||||
var count : Int = 0
|
// タッチ位置の数
|
||||||
|
var count: Int = 0
|
||||||
// タッチ位置の平均
|
|
||||||
val avg = FloatArray(2)
|
// タッチ位置の平均
|
||||||
|
val avg = FloatArray(2)
|
||||||
// 中心と、中心から最も離れたタッチ位置の間の距離
|
|
||||||
var max_radius : Float = 0.toFloat()
|
// 中心と、中心から最も離れたタッチ位置の間の距離
|
||||||
|
var max_radius: Float = 0.toFloat()
|
||||||
fun update(ev : MotionEvent) {
|
|
||||||
|
fun update(ev: MotionEvent) {
|
||||||
count = ev.pointerCount
|
|
||||||
if(count <= 1) {
|
count = ev.pointerCount
|
||||||
avg[0] = ev.x
|
if (count <= 1) {
|
||||||
avg[1] = ev.y
|
avg[0] = ev.x
|
||||||
max_radius = 0f
|
avg[1] = ev.y
|
||||||
|
max_radius = 0f
|
||||||
} else {
|
|
||||||
avg[0] = 0f
|
} else {
|
||||||
avg[1] = 0f
|
avg[0] = 0f
|
||||||
for(i in 0 until count) {
|
avg[1] = 0f
|
||||||
avg[0] += ev.getX(i)
|
for (i in 0 until count) {
|
||||||
avg[1] += ev.getY(i)
|
avg[0] += ev.getX(i)
|
||||||
}
|
avg[1] += ev.getY(i)
|
||||||
avg[0] /= count.toFloat()
|
}
|
||||||
avg[1] /= count.toFloat()
|
avg[0] /= count.toFloat()
|
||||||
max_radius = 0f
|
avg[1] /= count.toFloat()
|
||||||
for(i in 0 until count) {
|
max_radius = 0f
|
||||||
val dx = ev.getX(i) - avg[0]
|
for (i in 0 until count) {
|
||||||
val dy = ev.getY(i) - avg[1]
|
val dx = ev.getX(i) - avg[0]
|
||||||
val radius = dx * dx + dy * dy
|
val dy = ev.getY(i) - avg[1]
|
||||||
if(radius > max_radius) max_radius = radius
|
val radius = dx * dx + dy * dy
|
||||||
}
|
if (radius > max_radius) max_radius = radius
|
||||||
max_radius = sqrt(max_radius.toDouble()).toFloat()
|
}
|
||||||
if(max_radius < 1f) max_radius = 1f
|
max_radius = sqrt(max_radius.toDouble()).toFloat()
|
||||||
}
|
if (max_radius < 1f) max_radius = 1f
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
private fun trackStart(ev : MotionEvent) {
|
|
||||||
|
private fun trackStart(ev: MotionEvent) {
|
||||||
// 追跡開始時の指の位置
|
|
||||||
start_pos.update(ev)
|
// 追跡開始時の指の位置
|
||||||
|
start_pos.update(ev)
|
||||||
// 追跡開始時の画像の位置
|
|
||||||
start_image_trans_x = current_trans_x
|
// 追跡開始時の画像の位置
|
||||||
start_image_trans_y = current_trans_y
|
start_image_trans_x = current_trans_x
|
||||||
start_image_scale = current_scale
|
start_image_trans_y = current_trans_y
|
||||||
|
start_image_scale = current_scale
|
||||||
}
|
|
||||||
|
}
|
||||||
// 画面上の指の位置から画像中の指の位置を調べる
|
|
||||||
private fun getCoordinateOnImage(dst : FloatArray, src : FloatArray) {
|
// 画面上の指の位置から画像中の指の位置を調べる
|
||||||
tracking_matrix.reset()
|
private fun getCoordinateOnImage(dst: FloatArray, src: FloatArray) {
|
||||||
tracking_matrix.postScale(current_scale, current_scale)
|
tracking_matrix.reset()
|
||||||
tracking_matrix.postTranslate(current_trans_x, current_trans_y)
|
tracking_matrix.postScale(current_scale, current_scale)
|
||||||
tracking_matrix.invert(tracking_matrix_inv)
|
tracking_matrix.postTranslate(current_trans_x, current_trans_y)
|
||||||
tracking_matrix_inv.mapPoints(dst, src)
|
tracking_matrix.invert(tracking_matrix_inv)
|
||||||
}
|
tracking_matrix_inv.mapPoints(dst, src)
|
||||||
|
}
|
||||||
private fun trackNext(ev : MotionEvent) {
|
|
||||||
pos.update(ev)
|
private fun trackNext(ev: MotionEvent) {
|
||||||
|
pos.update(ev)
|
||||||
if(pos.count != start_pos.count) {
|
|
||||||
// タッチ操作中に指の数が変わった
|
if (pos.count != start_pos.count) {
|
||||||
log.d("nextTracking: pointer count changed")
|
// タッチ操作中に指の数が変わった
|
||||||
bPointerCountChanged = true
|
log.d("nextTracking: pointer count changed")
|
||||||
bDrag = bPointerCountChanged
|
bPointerCountChanged = true
|
||||||
trackStart(ev)
|
bDrag = bPointerCountChanged
|
||||||
return
|
trackStart(ev)
|
||||||
}
|
return
|
||||||
|
}
|
||||||
// ズーム操作
|
|
||||||
if(pos.count > 1) {
|
// ズーム操作
|
||||||
|
if (pos.count > 1) {
|
||||||
// タッチ位置にある絵柄の座標を調べる
|
|
||||||
getCoordinateOnImage(avg_on_image1, pos.avg)
|
// タッチ位置にある絵柄の座標を調べる
|
||||||
|
getCoordinateOnImage(avg_on_image1, pos.avg)
|
||||||
// ズーム率を変更する
|
|
||||||
current_scale = clip(
|
// ズーム率を変更する
|
||||||
scale_min,
|
current_scale = clip(
|
||||||
scale_max,
|
scale_min,
|
||||||
start_image_scale * pos.max_radius / start_pos.max_radius
|
scale_max,
|
||||||
)
|
start_image_scale * pos.max_radius / start_pos.max_radius
|
||||||
|
)
|
||||||
// 再び調べる
|
|
||||||
getCoordinateOnImage(avg_on_image2, pos.avg)
|
// 再び調べる
|
||||||
|
getCoordinateOnImage(avg_on_image2, pos.avg)
|
||||||
// ズーム変更の前後で位置がズレた分だけ移動させると、タッチ位置にある絵柄がズレない
|
|
||||||
start_image_trans_x += current_scale * (avg_on_image2[0] - avg_on_image1[0])
|
// ズーム変更の前後で位置がズレた分だけ移動させると、タッチ位置にある絵柄がズレない
|
||||||
start_image_trans_y += current_scale * (avg_on_image2[1] - avg_on_image1[1])
|
start_image_trans_x += current_scale * (avg_on_image2[0] - avg_on_image1[0])
|
||||||
|
start_image_trans_y += current_scale * (avg_on_image2[1] - avg_on_image1[1])
|
||||||
}
|
|
||||||
|
}
|
||||||
// 平行移動
|
|
||||||
run {
|
// 平行移動
|
||||||
// start時から指を動かした量
|
run {
|
||||||
val move_x = pos.avg[0] - start_pos.avg[0]
|
// start時から指を動かした量
|
||||||
val move_y = pos.avg[1] - start_pos.avg[1]
|
val move_x = pos.avg[0] - start_pos.avg[0]
|
||||||
|
val move_y = pos.avg[1] - start_pos.avg[1]
|
||||||
// 「指を動かした」と判断したらフラグを立てる
|
|
||||||
if(abs(move_x) >= drag_length || abs(move_y) >= drag_length) {
|
// 「指を動かした」と判断したらフラグを立てる
|
||||||
bDrag = true
|
if (abs(move_x) >= drag_length || abs(move_y) >= drag_length) {
|
||||||
}
|
bDrag = true
|
||||||
|
}
|
||||||
// 画像の表示位置を更新
|
|
||||||
current_trans_x =
|
// 画像の表示位置を更新
|
||||||
clipTranslate(view_w, bitmap_w, current_scale, start_image_trans_x + move_x)
|
current_trans_x =
|
||||||
current_trans_y =
|
clipTranslate(view_w, bitmap_w, current_scale, start_image_trans_x + move_x)
|
||||||
clipTranslate(view_h, bitmap_h, current_scale, start_image_trans_y + move_y)
|
current_trans_y =
|
||||||
}
|
clipTranslate(view_h, bitmap_h, current_scale, start_image_trans_y + move_y)
|
||||||
|
}
|
||||||
callback?.onMove(bitmap_w, bitmap_h, current_trans_x, current_trans_y, current_scale)
|
|
||||||
invalidate()
|
callback?.onMove(bitmap_w, bitmap_h, current_trans_x, current_trans_y, current_scale)
|
||||||
}
|
invalidate()
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -248,11 +248,7 @@ fun createResizedBitmap(
|
||||||
val paint = Paint()
|
val paint = Paint()
|
||||||
paint.isFilterBitmap = true
|
paint.isFilterBitmap = true
|
||||||
canvas.drawBitmap(sourceBitmap, matrix, paint)
|
canvas.drawBitmap(sourceBitmap, matrix, paint)
|
||||||
log.d(
|
log.d("createResizedBitmap: resized to ${dstSizeInt.x}x${dstSizeInt.y}")
|
||||||
"createResizedBitmap: resized to %sx%s",
|
|
||||||
dstSizeInt.x,
|
|
||||||
dstSizeInt.y
|
|
||||||
)
|
|
||||||
val tmp = dst
|
val tmp = dst
|
||||||
dst = null
|
dst = null
|
||||||
tmp
|
tmp
|
||||||
|
|
|
@ -167,66 +167,66 @@ import java.util.*
|
||||||
|
|
||||||
private const val MIME_TYPE_APPLICATION_OCTET_STREAM = "application/octet-stream"
|
private const val MIME_TYPE_APPLICATION_OCTET_STREAM = "application/octet-stream"
|
||||||
|
|
||||||
private val mimeTypeExMap : HashMap<String, String> by lazy {
|
private val mimeTypeExMap: HashMap<String, String> by lazy {
|
||||||
val map = HashMap<String, String>()
|
val map = HashMap<String, String>()
|
||||||
map["BDM"] = "application/vnd.syncml.dm+wbxml"
|
map["BDM"] = "application/vnd.syncml.dm+wbxml"
|
||||||
map["DAT"] = ""
|
map["DAT"] = ""
|
||||||
map["TID"] = ""
|
map["TID"] = ""
|
||||||
map["js"] = "text/javascript"
|
map["js"] = "text/javascript"
|
||||||
map["sh"] = "application/x-sh"
|
map["sh"] = "application/x-sh"
|
||||||
map["lua"] = "text/x-lua"
|
map["lua"] = "text/x-lua"
|
||||||
map
|
map
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
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.lowercase()
|
ext = ext.lowercase()
|
||||||
|
|
||||||
//
|
//
|
||||||
var mime_type : String? = MimeTypeMap.getSingleton().getMimeTypeFromExtension(ext)
|
var mime_type: String? = MimeTypeMap.getSingleton().getMimeTypeFromExtension(ext)
|
||||||
if(mime_type?.isNotEmpty() == true) return mime_type
|
if (mime_type?.isNotEmpty() == true) return mime_type
|
||||||
|
|
||||||
//
|
//
|
||||||
mime_type = mimeTypeExMap[ext]
|
mime_type = mimeTypeExMap[ext]
|
||||||
if(mime_type?.isNotEmpty() == true) return mime_type
|
if (mime_type?.isNotEmpty() == true) return mime_type
|
||||||
|
|
||||||
// 戻り値が空文字列の場合とnullの場合があり、空文字列の場合は既知なのでログ出力しない
|
// 戻り値が空文字列の場合とnullの場合があり、空文字列の場合は既知なのでログ出力しない
|
||||||
|
|
||||||
if(mime_type == null && log != null) {
|
if (mime_type == null && log != null) {
|
||||||
log.w("getMimeType(): unknown file extension '%s'", ext)
|
log.w("getMimeType(): unknown file extension '${ext}'")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return MIME_TYPE_APPLICATION_OCTET_STREAM
|
return MIME_TYPE_APPLICATION_OCTET_STREAM
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getDocumentName(contentResolver : ContentResolver, uri : Uri) : String {
|
fun getDocumentName(contentResolver: ContentResolver, uri: Uri): String {
|
||||||
val errorName = "no_name"
|
val errorName = "no_name"
|
||||||
return contentResolver.query(uri, null, null, null, null, null)
|
return contentResolver.query(uri, null, null, null, null, null)
|
||||||
?.use { cursor ->
|
?.use { cursor ->
|
||||||
return if(! cursor.moveToFirst()) {
|
return if (!cursor.moveToFirst()) {
|
||||||
errorName
|
errorName
|
||||||
} else {
|
} else {
|
||||||
cursor.getStringOrNull(OpenableColumns.DISPLAY_NAME) ?: errorName
|
cursor.getStringOrNull(OpenableColumns.DISPLAY_NAME) ?: errorName
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
?: errorName
|
?: errorName
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getStreamSize(bClose : Boolean, inStream : InputStream) : Long {
|
fun getStreamSize(bClose: Boolean, inStream: InputStream): Long {
|
||||||
try {
|
try {
|
||||||
var size = 0L
|
var size = 0L
|
||||||
while(true) {
|
while (true) {
|
||||||
val r = IOUtils.skip(inStream, 16384)
|
val r = IOUtils.skip(inStream, 16384)
|
||||||
if(r <= 0) break
|
if (r <= 0) break
|
||||||
size += r
|
size += r
|
||||||
}
|
}
|
||||||
return size
|
return size
|
||||||
} finally {
|
} finally {
|
||||||
@Suppress("DEPRECATION")
|
@Suppress("DEPRECATION")
|
||||||
if(bClose) IOUtils.closeQuietly(inStream)
|
if (bClose) IOUtils.closeQuietly(inStream)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//fun File.loadByteArray() : ByteArray {
|
//fun File.loadByteArray() : ByteArray {
|
||||||
|
@ -242,84 +242,84 @@ fun getStreamSize(bClose : Boolean, inStream : InputStream) : Long {
|
||||||
// }
|
// }
|
||||||
//}
|
//}
|
||||||
|
|
||||||
fun Context.loadRawResource(resId : Int) : ByteArray {
|
fun Context.loadRawResource(resId: Int): ByteArray {
|
||||||
resources.openRawResource(resId).use { inStream ->
|
resources.openRawResource(resId).use { inStream ->
|
||||||
val bao = ByteArrayOutputStream(inStream.available())
|
val bao = ByteArrayOutputStream(inStream.available())
|
||||||
IOUtils.copy(inStream, bao)
|
IOUtils.copy(inStream, bao)
|
||||||
return bao.toByteArray()
|
return bao.toByteArray()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun intentOpenDocument(mimeType : String) : Intent {
|
fun intentOpenDocument(mimeType: String): Intent {
|
||||||
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT)
|
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT)
|
||||||
intent.addCategory(Intent.CATEGORY_OPENABLE)
|
intent.addCategory(Intent.CATEGORY_OPENABLE)
|
||||||
intent.type = mimeType // "image/*"
|
intent.type = mimeType // "image/*"
|
||||||
return intent
|
return intent
|
||||||
}
|
}
|
||||||
|
|
||||||
fun intentGetContent(
|
fun intentGetContent(
|
||||||
allowMultiple : Boolean,
|
allowMultiple: Boolean,
|
||||||
caption : String,
|
caption: String,
|
||||||
mimeTypes : Array<out String>
|
mimeTypes: Array<out String>
|
||||||
) : Intent {
|
): Intent {
|
||||||
val intent = Intent(Intent.ACTION_GET_CONTENT)
|
val intent = Intent(Intent.ACTION_GET_CONTENT)
|
||||||
intent.addCategory(Intent.CATEGORY_OPENABLE)
|
intent.addCategory(Intent.CATEGORY_OPENABLE)
|
||||||
|
|
||||||
if(allowMultiple) {
|
if (allowMultiple) {
|
||||||
// EXTRA_ALLOW_MULTIPLE は API 18 (4.3)以降。ACTION_GET_CONTENT でも ACTION_OPEN_DOCUMENT でも指定できる
|
// EXTRA_ALLOW_MULTIPLE は API 18 (4.3)以降。ACTION_GET_CONTENT でも ACTION_OPEN_DOCUMENT でも指定できる
|
||||||
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
|
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// EXTRA_MIME_TYPES は API 19以降。ACTION_GET_CONTENT でも ACTION_OPEN_DOCUMENT でも指定できる
|
// EXTRA_MIME_TYPES は API 19以降。ACTION_GET_CONTENT でも ACTION_OPEN_DOCUMENT でも指定できる
|
||||||
intent.putExtra("android.intent.extra.MIME_TYPES", mimeTypes)
|
intent.putExtra("android.intent.extra.MIME_TYPES", mimeTypes)
|
||||||
|
|
||||||
intent.type = when {
|
intent.type = when {
|
||||||
mimeTypes.size == 1 -> mimeTypes[0]
|
mimeTypes.size == 1 -> mimeTypes[0]
|
||||||
|
|
||||||
// On Android 6.0 and above using "video/* image/" or "image/ video/*" type doesn't work
|
// On Android 6.0 and above using "video/* image/" or "image/ video/*" type doesn't work
|
||||||
// it only recognizes the first filter you specify.
|
// it only recognizes the first filter you specify.
|
||||||
Build.VERSION.SDK_INT >= 23 -> "*/*"
|
Build.VERSION.SDK_INT >= 23 -> "*/*"
|
||||||
|
|
||||||
else -> mimeTypes.joinToString(" ")
|
else -> mimeTypes.joinToString(" ")
|
||||||
}
|
}
|
||||||
|
|
||||||
return Intent.createChooser(intent, caption)
|
return Intent.createChooser(intent, caption)
|
||||||
}
|
}
|
||||||
|
|
||||||
data class GetContentResultEntry(
|
data class GetContentResultEntry(
|
||||||
val uri : Uri,
|
val uri: Uri,
|
||||||
val mimeType : String? = null,
|
val mimeType: String? = null,
|
||||||
var time : Long? = null
|
var time: Long? = null
|
||||||
)
|
)
|
||||||
|
|
||||||
// returns list of pair of uri and mime-type.
|
// returns list of pair of uri and mime-type.
|
||||||
fun Intent.handleGetContentResult(contentResolver : ContentResolver) : ArrayList<GetContentResultEntry> {
|
fun Intent.handleGetContentResult(contentResolver: ContentResolver): ArrayList<GetContentResultEntry> {
|
||||||
val urlList = ArrayList<GetContentResultEntry>()
|
val urlList = ArrayList<GetContentResultEntry>()
|
||||||
// 単一選択
|
// 単一選択
|
||||||
this.data?.let {
|
this.data?.let {
|
||||||
urlList.add(GetContentResultEntry(it, this.type))
|
urlList.add(GetContentResultEntry(it, this.type))
|
||||||
}
|
}
|
||||||
// 複数選択
|
// 複数選択
|
||||||
val cd = this.clipData
|
val cd = this.clipData
|
||||||
if(cd != null) {
|
if (cd != null) {
|
||||||
for(i in 0 until cd.itemCount) {
|
for (i in 0 until cd.itemCount) {
|
||||||
cd.getItemAt(i)?.uri?.let { uri ->
|
cd.getItemAt(i)?.uri?.let { uri ->
|
||||||
if(null == urlList.find { it.uri == uri }) {
|
if (null == urlList.find { it.uri == uri }) {
|
||||||
urlList.add(GetContentResultEntry(uri))
|
urlList.add(GetContentResultEntry(uri))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
urlList.forEach {
|
urlList.forEach {
|
||||||
try {
|
try {
|
||||||
contentResolver.takePersistableUriPermission(
|
contentResolver.takePersistableUriPermission(
|
||||||
it.uri,
|
it.uri,
|
||||||
Intent.FLAG_GRANT_READ_URI_PERMISSION
|
Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||||
)
|
)
|
||||||
} catch(_ : Throwable) {
|
} catch (_: Throwable) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return urlList
|
return urlList
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue