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