package jp.juggler.subwaytooter.table import android.content.ContentValues import android.content.Context import android.database.sqlite.SQLiteDatabase import jp.juggler.subwaytooter.App1 import jp.juggler.subwaytooter.util.LogCategory import android.support.v4.util.LruCache import android.text.Spannable import android.text.SpannableStringBuilder import android.text.style.BackgroundColorSpan import android.text.style.ForegroundColorSpan import jp.juggler.subwaytooter.util.sanitizeBDI import java.util.Locale class AcctColor { var acct : String var color_fg : Int = 0 var color_bg : Int = 0 var nickname : String? = null var notification_sound : String? = null constructor( acct : String, nickname : String, color_fg : Int, color_bg : Int, notification_sound : String? ) { this.acct = acct this.nickname = nickname this.color_fg = color_fg this.color_bg = color_bg this.notification_sound = notification_sound } private constructor(acct : String) { this.acct = acct } fun save(now : Long) { acct = acct.toLowerCase(Locale.ENGLISH) try { val cv = ContentValues() cv.put(COL_TIME_SAVE, now) cv.put(COL_ACCT, acct) cv.put(COL_COLOR_FG, color_fg) cv.put(COL_COLOR_BG, color_bg) cv.put(COL_NICKNAME, if(nickname == null) "" else nickname) cv.put( COL_NOTIFICATION_SOUND, if(notification_sound == null) "" else notification_sound ) App1.database.replace(table, null, cv) mMemoryCache.remove(acct) } catch(ex : Throwable) { log.trace(ex) log.e(ex, "save failed.") } } companion object { private val log = LogCategory("AcctColor") const val table = "acct_color" private const val COL_TIME_SAVE = "time_save" private const val COL_ACCT = "ac" //@who@host ascii文字の大文字小文字は(sqliteにより)同一視される private const val COL_COLOR_FG = "cf" // 未設定なら0、それ以外は色 private const val COL_COLOR_BG = "cb" // 未設定なら0、それ以外は色 private const val COL_NICKNAME = "nick" // 未設定ならnullか空文字列 private const val COL_NOTIFICATION_SOUND = "notification_sound" // 未設定ならnullか空文字列 private const val CHAR_REPLACE : Char = 0x328A.toChar() private const val load_where = COL_ACCT + "=?" private val load_where_arg = object : ThreadLocal>() { override fun initialValue() : Array { return arrayOfNulls(1) } } private val mMemoryCache = LruCache(2048) fun onDBCreate(db : SQLiteDatabase) { log.d("onDBCreate!") db.execSQL( "create table if not exists " + table + "(_id INTEGER PRIMARY KEY" + "," + COL_TIME_SAVE + " integer not null" + "," + COL_ACCT + " text not null" + "," + COL_COLOR_FG + " integer" + "," + COL_COLOR_BG + " integer" + "," + COL_NICKNAME + " text " + "," + COL_NOTIFICATION_SOUND + " text default ''" + ")" ) db.execSQL( "create unique index if not exists " + table + "_acct on " + table + "(" + COL_ACCT + ")" ) db.execSQL( "create index if not exists " + table + "_time on " + table + "(" + COL_TIME_SAVE + ")" ) } fun onDBUpgrade(db : SQLiteDatabase, oldVersion : Int, newVersion : Int) { if(oldVersion < 9 && newVersion >= 9) { onDBCreate(db) return } if(oldVersion < 17 && newVersion >= 17) { try { db.execSQL("alter table $table add column $COL_NOTIFICATION_SOUND text default ''") } catch(ex : Throwable) { log.trace(ex) } } } fun load(acctArg : String) : AcctColor { val acct = acctArg.toLowerCase(Locale.ENGLISH) val cached : AcctColor? = mMemoryCache.get(acct) if(cached != null) return cached try { val where_arg = load_where_arg.get() where_arg[0] = acct App1.database.query(table, null, load_where, where_arg, null, null, null) .use { cursor -> if(cursor.moveToNext()) { var idx : Int val ac = AcctColor(acct) idx = cursor.getColumnIndex(COL_COLOR_FG) ac.color_fg = if(cursor.isNull(idx)) 0 else cursor.getInt(idx) idx = cursor.getColumnIndex(COL_COLOR_BG) ac.color_bg = if(cursor.isNull(idx)) 0 else cursor.getInt(idx) idx = cursor.getColumnIndex(COL_NICKNAME) ac.nickname = if(cursor.isNull(idx)) null else cursor.getString(idx) idx = cursor.getColumnIndex(COL_NOTIFICATION_SOUND) ac.notification_sound = if(cursor.isNull(idx)) null else cursor.getString(idx) mMemoryCache.put(acct, ac) return ac } } } catch(ex : Throwable) { log.trace(ex) log.e(ex, "load failed.") } log.d( "lruCache size=%s,hit=%s,miss=%s", mMemoryCache.size(), mMemoryCache.hitCount(), mMemoryCache.missCount() ) val ac = AcctColor(acct) mMemoryCache.put(acct, ac) return ac } fun getNickname(acct : String) : String { val ac = load(acct) val nickname = ac.nickname return if(nickname != null && nickname.isNotEmpty()) nickname.sanitizeBDI() else acct } fun getNotificationSound(acct : String) : String? { val ac = load(acct) val notification_sound = ac.notification_sound return if(notification_sound != null && notification_sound.isNotEmpty()) notification_sound else null } fun hasNickname(ac : AcctColor?) : Boolean { val nickname = ac?.nickname return nickname != null && nickname.isNotEmpty() } fun hasColorForeground(ac : AcctColor?) : Boolean { return ac != null && ac.color_fg != 0 } fun hasColorBackground(ac : AcctColor?) : Boolean { return ac != null && ac.color_bg != 0 } fun clearMemoryCache() { mMemoryCache.evictAll() } fun getStringWithNickname( context : Context, string_id : Int, acct : String ) : CharSequence { val ac = load(acct) val nickname = ac.nickname val name = if(nickname == null || nickname.isEmpty()) acct else nickname.sanitizeBDI() val sb = SpannableStringBuilder( context.getString( string_id, String(charArrayOf(CHAR_REPLACE)) ) ) for(i in sb.length - 1 downTo 0) { val c = sb[i] if(c != CHAR_REPLACE) continue sb.replace(i, i + 1, name) if(ac.color_fg != 0) { sb.setSpan( ForegroundColorSpan(ac.color_fg), i, i + name.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE ) } if(ac.color_bg != 0) { sb.setSpan( BackgroundColorSpan(ac.color_bg), i, i + name.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE ) } } return sb } } }