diff --git a/app/src/main/java/jp/juggler/subwaytooter/ActHighlightWordEdit.kt b/app/src/main/java/jp/juggler/subwaytooter/ActHighlightWordEdit.kt index d4950387..d1bb70ac 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ActHighlightWordEdit.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/ActHighlightWordEdit.kt @@ -49,6 +49,8 @@ class ActHighlightWordEdit private lateinit var tvName : TextView private lateinit var swSound : Switch + private lateinit var swSpeech : Switch + private var bBusy = false @@ -60,7 +62,6 @@ class ActHighlightWordEdit } catch(ex : JSONException) { throw RuntimeException(ex) } - } override fun onBackPressed() { @@ -110,12 +111,19 @@ class ActHighlightWordEdit swSound = findViewById(R.id.swSound) swSound.setOnCheckedChangeListener(this) - findViewById(R.id.btnTextColorEdit).setOnClickListener(this) - findViewById(R.id.btnTextColorReset).setOnClickListener(this) - findViewById(R.id.btnBackgroundColorEdit).setOnClickListener(this) - findViewById(R.id.btnBackgroundColorReset).setOnClickListener(this) - findViewById(R.id.btnNotificationSoundEdit).setOnClickListener(this) - findViewById(R.id.btnNotificationSoundReset).setOnClickListener(this) + swSpeech = findViewById(R.id.swSpeech) + swSpeech.setOnCheckedChangeListener(this) + + intArrayOf( + R.id.btnTextColorEdit, + R.id.btnTextColorReset, + R.id.btnBackgroundColorEdit, + R.id.btnBackgroundColorReset, + R.id.btnNotificationSoundEdit, + R.id.btnNotificationSoundReset + ).forEach { + findViewById(it)?.setOnClickListener(this) + } } private fun showSampleText() { @@ -124,6 +132,8 @@ class ActHighlightWordEdit swSound.isChecked = item.sound_type != HighlightWord.SOUND_TYPE_NONE + swSpeech.isChecked = item.speech != 0 + tvName.text = item.name tvName.setBackgroundColor(item.color_bg) // may 0 tvName.textColor = item.color_fg.notZero() @@ -169,12 +179,21 @@ class ActHighlightWordEdit override fun onCheckedChanged(buttonView : CompoundButton, isChecked : Boolean) { if(bBusy) return - if(! isChecked) { - item.sound_type = HighlightWord.SOUND_TYPE_NONE - } else { - - item.sound_type = - if(item.sound_uri?.isEmpty() != false) HighlightWord.SOUND_TYPE_DEFAULT else HighlightWord.SOUND_TYPE_CUSTOM + when(buttonView.id){ + R.id.swSound ->{ + if(! isChecked) { + item.sound_type = HighlightWord.SOUND_TYPE_NONE + } else { + item.sound_type = + if(item.sound_uri?.isEmpty() != false) HighlightWord.SOUND_TYPE_DEFAULT else HighlightWord.SOUND_TYPE_CUSTOM + } + } + R.id.swSpeech->{ + item.speech = when(isChecked){ + false->0 + else->1 + } + } } } diff --git a/app/src/main/java/jp/juggler/subwaytooter/ActHighlightWordList.kt b/app/src/main/java/jp/juggler/subwaytooter/ActHighlightWordList.kt index bf3b8681..88b7ef85 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ActHighlightWordList.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/ActHighlightWordList.kt @@ -11,6 +11,7 @@ import androidx.appcompat.app.AppCompatActivity import androidx.recyclerview.widget.LinearLayoutManager import android.view.View import android.view.ViewGroup +import android.widget.ImageButton import android.widget.TextView import com.woxthebox.draglistview.DragItem import com.woxthebox.draglistview.DragItemAdapter @@ -95,7 +96,7 @@ class ActHighlightWordList : AppCompatActivity(), View.OnClickListener { if(swipedDirection == ListSwipeItem.SwipeDirection.LEFT) { val o = item.tag if(o is HighlightWord) { - o.delete() + o.delete(this@ActHighlightWordList) listAdapter.removeItem(listAdapter.getPositionForItem(o)) } } @@ -135,11 +136,13 @@ class ActHighlightWordList : AppCompatActivity(), View.OnClickListener { val tvName : TextView private val btnSound : View + val ivSpeech: ImageButton init { tvName = viewRoot.findViewById(R.id.tvName) btnSound = viewRoot.findViewById(R.id.btnSound) + ivSpeech = viewRoot.findViewById(R.id.ivSpeech) // リスト要素のビューが ListSwipeItem だった場合、Swipe操作を制御できる if(viewRoot is ListSwipeItem) { @@ -163,6 +166,10 @@ class ActHighlightWordList : AppCompatActivity(), View.OnClickListener { vg(btnSound, item.sound_type != HighlightWord.SOUND_TYPE_NONE) btnSound.setOnClickListener(this) btnSound.tag = item + + vg(ivSpeech,item.speech != 0 ) + ivSpeech.setOnClickListener(this) + ivSpeech.tag = item } // @Override @@ -180,9 +187,15 @@ class ActHighlightWordList : AppCompatActivity(), View.OnClickListener { override fun onClick(v : View) { val o = v.tag if(o is HighlightWord) { - sound(o) + when(v.id){ + R.id.btnSound->{ + sound(o) + } + R.id.ivSpeech->{ + App1.getAppState(this@ActHighlightWordList).addSpeech(o.name,allowRepeat = true) + } + } } - } } @@ -191,10 +204,16 @@ class ActHighlightWordList : AppCompatActivity(), View.OnClickListener { DragItem(context, layoutId) { override fun onBindDragView(clickedView : View, dragView : View) { + dragView.findViewById(R.id.tvName).text = clickedView.findViewById(R.id.tvName).text + dragView.findViewById(R.id.btnSound).visibility = clickedView.findViewById(R.id.btnSound).visibility + + dragView.findViewById(R.id.ivSpeech).visibility= + clickedView.findViewById(R.id.ivSpeech).visibility + dragView.findViewById(R.id.item_layout).setBackgroundColor( getAttributeColor( this@ActHighlightWordList, @@ -238,7 +257,7 @@ class ActHighlightWordList : AppCompatActivity(), View.OnClickListener { var item = HighlightWord.load(text) if(item == null) { item = HighlightWord(text) - item.save() + item.save(this@ActHighlightWordList) loadData() } edit(item) @@ -260,7 +279,7 @@ class ActHighlightWordList : AppCompatActivity(), View.OnClickListener { try { val sv = data.getStringExtra(ActHighlightWordEdit.EXTRA_ITEM) ?: return val item = HighlightWord(sv.toJsonObject()) - item.save() + item.save(this@ActHighlightWordList) loadData() } catch(ex : Throwable) { throw RuntimeException("can't load data", ex) diff --git a/app/src/main/java/jp/juggler/subwaytooter/App1.kt b/app/src/main/java/jp/juggler/subwaytooter/App1.kt index a7398ea0..44f02efc 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/App1.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/App1.kt @@ -127,7 +127,9 @@ class App1 : Application() { // 2019/10/22 39 => 40 NotificationTracking テーブルに項目追加。 // 2019/10/22 40 => 41 NotificationCache テーブルに項目追加。 // 2019/10/23 41=> 42 SavedAccount テーブルに項目追加。 - internal const val DB_VERSION = 42 + // 2019/11/15 42=> 43 HighlightWord テーブルに項目追加。 + + internal const val DB_VERSION = 43 private val tableList = arrayOf( LogData, diff --git a/app/src/main/java/jp/juggler/subwaytooter/AppState.kt b/app/src/main/java/jp/juggler/subwaytooter/AppState.kt index 89249370..0045fce7 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/AppState.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/AppState.kt @@ -142,7 +142,7 @@ class AppState(internal val context : Context, internal val pref : SharedPrefere this.handler = Handler() this.density = context.resources.displayMetrics.density this.stream_reader = StreamReader(context, handler, pref) - this.networkTracker = NetworkStateTracker(context){ + this.networkTracker = NetworkStateTracker(context) { App1.custom_emoji_cache.onNetworkChanged() App1.custom_emoji_lister.onNetworkChanged() } @@ -153,16 +153,7 @@ class AppState(internal val context : Context, internal val pref : SharedPrefere // TextToSpeech private val isTextToSpeechRequired : Boolean - get() { - var b = false - for(c in column_list) { - if(c.enable_speech) { - b = true - break - } - } - return b - } + get() = column_list.any { it.enable_speech } || HighlightWord.hasTextToSpeechHighlightWord() private val tts_receiver = object : BroadcastReceiver() { override fun onReceive(context : Context, intent : Intent?) { @@ -345,8 +336,6 @@ class AppState(internal val context : Context, internal val pref : SharedPrefere map_busy_bookmark.remove(key) } - - fun isBusyBoost(account : SavedAccount, status : TootStatus) : Boolean { val key = account.acct + ":" + status.busyKey return map_busy_boost.contains(key) @@ -363,7 +352,7 @@ class AppState(internal val context : Context, internal val pref : SharedPrefere } @SuppressLint("StaticFieldLeak") - private fun enableSpeech() { + fun enableSpeech() { this.willSpeechEnabled = isTextToSpeechRequired if(willSpeechEnabled && tts == null && tts_status == TTS_STATUS_NONE) { @@ -526,19 +515,21 @@ class AppState(internal val context : Context, internal val pref : SharedPrefere addSpeech(sb.toString()) } - private fun addSpeech(text : String) { + internal fun addSpeech(text : String,allowRepeat:Boolean = false) { if(tts == null) return val sv = reSpaces.matcher(text).replaceAll(" ").trim { it <= ' ' } if(sv.isEmpty()) return - for(check in duplication_check) { - if(check == sv) return - } - duplication_check.addLast(sv) - if(duplication_check.size >= 60) { - duplication_check.removeFirst() + if(!allowRepeat) { + for(check in duplication_check) { + if(check == sv) return + } + duplication_check.addLast(sv) + if(duplication_check.size >= 60) { + duplication_check.removeFirst() + } } tts_queue.add(sv) diff --git a/app/src/main/java/jp/juggler/subwaytooter/Column.kt b/app/src/main/java/jp/juggler/subwaytooter/Column.kt index a28be736..7f4c56e8 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/Column.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/Column.kt @@ -1411,7 +1411,7 @@ class Column( if(! (with_attachment || with_highlight)) return false val matchMedia = with_attachment && status.reblog?.hasMedia() ?: status.hasMedia() - val matchHighlight = with_highlight && status.reblog?.hasHighlight ?: status.hasHighlight + val matchHighlight = with_highlight && null != (status.reblog?.highlightAny ?: status.highlightAny) // どれかの条件を満たすならフィルタしない(false)、どれも満たさないならフィルタする(true) return ! (matchMedia || matchHighlight) @@ -2690,16 +2690,18 @@ class Column( val added = list_new.size // may 0 - loop@ for(o in list_new) { - when(o) { - - is TootStatus -> { - val highlight_sound = o.highlight_sound - if(highlight_sound != null) { - App1.sound(highlight_sound) - break@loop + var doneSound = false + for(o in list_new) { + if( o is TootStatus ){ + o.highlightSound?.let{ + if(!doneSound){ + doneSound = true + App1.sound(it) } } + o.highlightSpeech?.let{ + App1.getAppState(context).addSpeech(it.name,allowRepeat = true) + } } } diff --git a/app/src/main/java/jp/juggler/subwaytooter/ColumnTask_Refresh.kt b/app/src/main/java/jp/juggler/subwaytooter/ColumnTask_Refresh.kt index 58b60dfa..4b7bb5ca 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ColumnTask_Refresh.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/ColumnTask_Refresh.kt @@ -134,12 +134,17 @@ class ColumnTask_Refresh( column.list_data.removeAt(0) } + var doneSound = false for(o in list_new) { - if(o is TootStatus) { - val highlight_sound = o.highlight_sound - if(highlight_sound != null) { - App1.sound(highlight_sound) - break + if( o is TootStatus ){ + o.highlightSound?.let{ + if(!doneSound){ + doneSound = true + App1.sound(it) + } + } + o.highlightSpeech?.let{ + App1.getAppState(context).addSpeech(it.name,allowRepeat = true) } } } diff --git a/app/src/main/java/jp/juggler/subwaytooter/DownloadReceiver.kt b/app/src/main/java/jp/juggler/subwaytooter/DownloadReceiver.kt index 9ff08ebe..00279c09 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/DownloadReceiver.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/DownloadReceiver.kt @@ -25,36 +25,36 @@ class DownloadReceiver : BroadcastReceiver() { try { val id = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, 0L) val query = DownloadManager.Query().setFilterById(id) - + downloadManager.query(query)?.use { cursor -> if(! cursor.moveToFirst()) { log.e("cursor.moveToFirst() failed.") - } else { - val title = cursor.getStringOrNull(DownloadManager.COLUMN_TITLE) - val status = cursor.getIntOrNull(DownloadManager.COLUMN_STATUS) - showToast( - context, - false, - if(status == DownloadManager.STATUS_SUCCESSFUL) { - context.getString(R.string.download_complete, title) - } else { - context.getString(R.string.download_failed, title) - } - ) - /* - ダウンロード完了通知がシステムからのものと重複することがある - - - (Aubee elm. Android 5.1) don't shows toast. - - (Samsung Galaxy S8+ Android 7.0) don't show toast. - - (Kyocera AndroidOne Android 8.0 S2) don't show toast. - - (LGE LGL24 Android 5.0.2) SHOWS toast. - - (LGE LGV32 Android 6.0) SHOWS toast. - - maybe it depends on customization by device maker. not depends on OS version. - - 重複を回避する方法はなさそうだ… - */ + return } + val title = cursor.getStringOrNull(DownloadManager.COLUMN_TITLE) + val status = cursor.getIntOrNull(DownloadManager.COLUMN_STATUS) + showToast( + context, + false, + if(status == DownloadManager.STATUS_SUCCESSFUL) { + context.getString(R.string.download_complete, title) + } else { + context.getString(R.string.download_failed, title) + } + ) + /* + ダウンロード完了通知がシステムからのものと重複することがある + + - (Aubee elm. Android 5.1) don't shows toast. + - (Samsung Galaxy S8+ Android 7.0) don't show toast. + - (Kyocera AndroidOne Android 8.0 S2) don't show toast. + - (LGE LGL24 Android 5.0.2) SHOWS toast. + - (LGE LGV32 Android 6.0) SHOWS toast. + + maybe it depends on customization by device maker. not depends on OS version. + + 重複を回避する方法はなさそうだ… + */ } } catch(ex : Throwable) { log.e(ex, "downloadManager.query() failed.") diff --git a/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootStatus.kt b/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootStatus.kt index d867d8a3..1122ce32 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootStatus.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootStatus.kt @@ -181,9 +181,9 @@ class TootStatus(parser : TootParser, src : JSONObject) : TimelineItem() { // 会話の流れビューで後から追加する var card : TootCard? = null - var highlight_sound : HighlightWord? = null - - var hasHighlight : Boolean = false + var highlightSound: HighlightWord? = null + var highlightSpeech: HighlightWord? = null + var highlightAny: HighlightWord? = null val time_created_at : Long @@ -295,10 +295,9 @@ class TootStatus(parser : TootParser, src : JSONObject) : TimelineItem() { ) this.decoded_content = options.decodeHTML(content) - this.hasHighlight = this.hasHighlight || options.hasHighlight - if(options.highlight_sound != null && this.highlight_sound == null) { - this.highlight_sound = options.highlight_sound - } + if(this.highlightSound==null) this.highlightSound = options.highlightSound + if(this.highlightSpeech==null) this.highlightSpeech = options.highlightSpeech + if(this.highlightAny==null) this.highlightAny = options.highlightAny // Markdownのデコード結果からmentionsを読むのだった val mentions1 = @@ -325,10 +324,9 @@ class TootStatus(parser : TootParser, src : JSONObject) : TimelineItem() { ) this.decoded_spoiler_text = options.decodeHTML(spoiler_text) - this.hasHighlight = this.hasHighlight || options.hasHighlight - if(options.highlight_sound != null && this.highlight_sound == null) { - this.highlight_sound = options.highlight_sound - } + if(this.highlightSound==null) this.highlightSound = options.highlightSound + if(this.highlightSpeech==null) this.highlightSpeech = options.highlightSpeech + if(this.highlightAny==null) this.highlightAny = options.highlightAny val mentions2 = (decoded_spoiler_text as? MisskeyMarkdownDecoder.SpannableStringBuilderEx)?.mentions @@ -494,10 +492,9 @@ class TootStatus(parser : TootParser, src : JSONObject) : TimelineItem() { ) this.decoded_content = options.decodeHTML(content) - this.hasHighlight = this.hasHighlight || options.hasHighlight - if(options.highlight_sound != null && this.highlight_sound == null) { - this.highlight_sound = options.highlight_sound - } + if(this.highlightSound==null) this.highlightSound = options.highlightSound + if(this.highlightSpeech==null) this.highlightSpeech = options.highlightSpeech + if(this.highlightAny==null) this.highlightAny = options.highlightAny var sv = (src.parseString("spoiler_text") ?: "").cleanCW() this.spoiler_text = when { @@ -517,10 +514,9 @@ class TootStatus(parser : TootParser, src : JSONObject) : TimelineItem() { this.decoded_spoiler_text = options.decodeEmoji(spoiler_text) - this.hasHighlight = this.hasHighlight || options.hasHighlight - if(options.highlight_sound != null && this.highlight_sound == null) { - this.highlight_sound = options.highlight_sound - } + if(this.highlightSound==null) this.highlightSound = options.highlightSound + if(this.highlightSpeech==null) this.highlightSpeech = options.highlightSpeech + if(this.highlightAny==null) this.highlightAny = options.highlightAny this.enquete = try { sv = src.parseString("enquete") ?: "" diff --git a/app/src/main/java/jp/juggler/subwaytooter/table/HighlightWord.kt b/app/src/main/java/jp/juggler/subwaytooter/table/HighlightWord.kt index 9c230912..119f2ab7 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/table/HighlightWord.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/table/HighlightWord.kt @@ -1,17 +1,20 @@ package jp.juggler.subwaytooter.table import android.content.ContentValues +import android.content.Context import android.database.Cursor import android.database.sqlite.SQLiteDatabase +import android.provider.BaseColumns import org.json.JSONObject import jp.juggler.subwaytooter.App1 import jp.juggler.util.* +import java.util.concurrent.atomic.AtomicReference class HighlightWord { - companion object :TableCompanion{ + companion object : TableCompanion { private val log = LogCategory("HighlightWord") @@ -20,34 +23,37 @@ class HighlightWord { const val SOUND_TYPE_CUSTOM = 2 const val table = "highlight_word" - const val COL_ID = "_id" + const val COL_ID = BaseColumns._ID const val COL_NAME = "name" private const val COL_TIME_SAVE = "time_save" private const val COL_COLOR_BG = "color_bg" private const val COL_COLOR_FG = "color_fg" private const val COL_SOUND_TYPE = "sound_type" private const val COL_SOUND_URI = "sound_uri" + private const val COL_SPEECH = "speech" - private const val selection_name = COL_NAME + "=?" - private const val selection_id = COL_ID + "=?" + private const val selection_name = "$COL_NAME=?" + private const val selection_speech = "$COL_SPEECH<>0" + private const val selection_id = "$COL_ID=?" private val columns_name = arrayOf(COL_NAME) override fun onDBCreate(db : SQLiteDatabase) { log.d("onDBCreate!") db.execSQL( - "create table if not exists " + table - + "(_id INTEGER PRIMARY KEY" - + ",name text not null" - + ",time_save integer not null" - + ",color_bg integer not null default 0" - + ",color_fg integer not null default 0" - + ",sound_type integer not null default 1" - + ",sound_uri text default null" - + ")" + """create table if not exists $table + ($COL_ID INTEGER PRIMARY KEY + ,$COL_NAME text not null + ,$COL_TIME_SAVE integer not null + ,$COL_COLOR_BG integer not null default 0 + ,$COL_COLOR_FG integer not null default 0 + ,$COL_SOUND_TYPE integer not null default 1 + ,$COL_SOUND_URI text default null + ,$COL_SPEECH integer default 0 + )""" ) db.execSQL( - "create unique index if not exists " + table + "_name on " + table + "(name)" + "create unique index if not exists ${table}_name on $table(name)" ) } @@ -55,6 +61,13 @@ class HighlightWord { if(oldVersion < 21 && newVersion >= 21) { onDBCreate(db) } + if(oldVersion < 43 && newVersion >= 43) { + try { + db.execSQL("alter table $table add column $COL_SPEECH integer default 0") + } catch(ex : Throwable) { + log.trace(ex) + } + } } fun load(name : String) : HighlightWord? { @@ -73,7 +86,7 @@ class HighlightWord { } fun createCursor() : Cursor { - return App1.database.query(table, null, null, null, null, null, COL_NAME + " asc") + return App1.database.query(table, null, null, null, null, null, "$COL_NAME asc") } val nameSet : WordTrieTree? @@ -95,14 +108,46 @@ class HighlightWord { return if(dst.isEmpty) null else dst } + + private val hasTextToSpeechHighlightWordCache = AtomicReference(null) + + fun hasTextToSpeechHighlightWord() : Boolean { + synchronized(this) { + var cached = hasTextToSpeechHighlightWordCache.get() + if(cached == null) { + cached = false + try { + App1.database.query( + table, + columns_name, + selection_speech, + null, + null, + null, + null + ) + .use { cursor -> + while(cursor.moveToNext()) { + cached = true + } + } + } catch(ex : Throwable) { + log.trace(ex) + } + hasTextToSpeechHighlightWordCache.set(cached) + } + return cached + } + } } var id = - 1L var name : String - var color_bg : Int = 0 - var color_fg : Int = 0 - var sound_type : Int = 0 + var color_bg = 0 + var color_fg = 0 + var sound_type = 0 var sound_uri : String? = null + var speech = 0 fun encodeJson() : JSONObject { val dst = JSONObject() @@ -111,17 +156,19 @@ class HighlightWord { dst.put(COL_COLOR_BG, color_bg) dst.put(COL_COLOR_FG, color_fg) dst.put(COL_SOUND_TYPE, sound_type) + dst.put(COL_SPEECH, speech) if(sound_uri != null) dst.put(COL_SOUND_URI, sound_uri) return dst } constructor(src : JSONObject) { - this.id = src.parseLong( COL_ID) ?: -1L + this.id = src.parseLong(COL_ID) ?: - 1L this.name = src.notEmptyOrThrow(COL_NAME) this.color_bg = src.optInt(COL_COLOR_BG) this.color_fg = src.optInt(COL_COLOR_FG) this.sound_type = src.optInt(COL_SOUND_TYPE) - this.sound_uri = src.parseString( COL_SOUND_URI) + this.sound_uri = src.parseString(COL_SOUND_URI) + this.speech = src.optInt(COL_SPEECH) } constructor(name : String) { @@ -137,9 +184,10 @@ class HighlightWord { this.color_fg = cursor.getInt(COL_COLOR_FG) this.sound_type = cursor.getInt(COL_SOUND_TYPE) this.sound_uri = cursor.getStringOrNull(COL_SOUND_URI) + this.speech = cursor.getInt(COL_SPEECH) } - fun save() { + fun save(context : Context) { if(name.isEmpty()) throw RuntimeException("HighlightWord.save(): name is empty") try { @@ -149,12 +197,15 @@ class HighlightWord { cv.put(COL_COLOR_BG, color_bg) cv.put(COL_COLOR_FG, color_fg) cv.put(COL_SOUND_TYPE, sound_type) + val sound_uri = this.sound_uri if(sound_uri?.isEmpty() != false) { cv.putNull(COL_SOUND_URI) } else { cv.put(COL_SOUND_URI, sound_uri) } + cv.put(COL_SPEECH, speech) + if(id == - 1L) { id = App1.database.replace(table, null, cv) } else { @@ -164,14 +215,22 @@ class HighlightWord { log.e(ex, "save failed.") } + synchronized(Companion) { + hasTextToSpeechHighlightWordCache.set(null) + } + App1.getAppState(context).enableSpeech() } - fun delete() { + fun delete(context : Context) { try { App1.database.delete(table, selection_id, arrayOf(id.toString())) } catch(ex : Throwable) { log.e(ex, "delete failed.") } + synchronized(Companion) { + hasTextToSpeechHighlightWordCache.set(null) + } + App1.getAppState(context).enableSpeech() } } diff --git a/app/src/main/java/jp/juggler/subwaytooter/util/DecodeOptions.kt b/app/src/main/java/jp/juggler/subwaytooter/util/DecodeOptions.kt index 0c15eefd..9c043634 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/util/DecodeOptions.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/util/DecodeOptions.kt @@ -36,11 +36,10 @@ class DecodeOptions( return false } - // OUTPUT: true if found highlight - var hasHighlight : Boolean = false - - // OUTPUT: found highlight with sound - var highlight_sound : HighlightWord? = null + // OUTPUT + var highlightSound: HighlightWord? = null + var highlightSpeech: HighlightWord? = null + var highlightAny: HighlightWord? = null //////////////////////// // decoder diff --git a/app/src/main/java/jp/juggler/subwaytooter/util/EmojiDecoder.kt b/app/src/main/java/jp/juggler/subwaytooter/util/EmojiDecoder.kt index dcda16f1..ab793e20 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/util/EmojiDecoder.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/util/EmojiDecoder.kt @@ -107,19 +107,23 @@ object EmojiDecoder { private fun applyHighlight(start : Int, end : Int) { val list = options.highlightTrie?.matchList(sb, start, end) ?: return for(range in list) { - val word = HighlightWord.load(range.word) - if(word != null) { - options.hasHighlight = true - sb.setSpan( - HighlightSpan(word.color_fg, word.color_bg), - range.start, - range.end, - Spanned.SPAN_EXCLUSIVE_EXCLUSIVE - ) - if(word.sound_type != HighlightWord.SOUND_TYPE_NONE) { - options.highlight_sound = word - } + val word = HighlightWord.load(range.word) ?: continue + sb.setSpan( + HighlightSpan(word.color_fg, word.color_bg), + range.start, + range.end, + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE + ) + + if(word.sound_type != HighlightWord.SOUND_TYPE_NONE) { + if(options.highlightSound == null) options.highlightSound = word } + + if( word.speech != 0 ) { + if(options.highlightSpeech == null) options.highlightSpeech = word + } + + if(options.highlightAny == null) options.highlightAny = word } } diff --git a/app/src/main/java/jp/juggler/subwaytooter/util/HTMLDecoder.kt b/app/src/main/java/jp/juggler/subwaytooter/util/HTMLDecoder.kt index a23aa0de..46a25b1b 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/util/HTMLDecoder.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/util/HTMLDecoder.kt @@ -408,19 +408,23 @@ object HTMLDecoder { val list = options.highlightTrie?.matchList(sb, start, end) if(list != null) { for(range in list) { - val word = HighlightWord.load(range.word) - if(word != null) { - options.hasHighlight = true - sb.setSpan( - HighlightSpan(word.color_fg, word.color_bg), - range.start, - range.end, - Spanned.SPAN_EXCLUSIVE_EXCLUSIVE - ) - if(word.sound_type != HighlightWord.SOUND_TYPE_NONE) { - options.highlight_sound = word - } + val word = HighlightWord.load(range.word) ?: continue + sb.setSpan( + HighlightSpan(word.color_fg, word.color_bg), + range.start, + range.end, + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE + ) + + if(word.sound_type != HighlightWord.SOUND_TYPE_NONE) { + if(options.highlightSound == null) options.highlightSound = word } + + if( word.speech != 0 ) { + if(options.highlightSpeech == null) options.highlightSpeech = word + } + + if(options.highlightAny == null) options.highlightAny = word } } } diff --git a/app/src/main/java/jp/juggler/subwaytooter/util/MisskeyMarkdownDecoder.kt b/app/src/main/java/jp/juggler/subwaytooter/util/MisskeyMarkdownDecoder.kt index cbc8e4f0..1037e2ed 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/util/MisskeyMarkdownDecoder.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/util/MisskeyMarkdownDecoder.kt @@ -732,18 +732,22 @@ object MisskeyMarkdownDecoder { val list = options.highlightTrie?.matchList(sb, start, end) if(list != null) { for(range in list) { - val word = HighlightWord.load(range.word) - if(word != null) { - options.hasHighlight = true - spanList.addLast( - range.start, - range.end, - HighlightSpan(word.color_fg, word.color_bg) - ) - if(word.sound_type != HighlightWord.SOUND_TYPE_NONE) { - options.highlight_sound = word - } + val word = HighlightWord.load(range.word) ?: continue + spanList.addLast( + range.start, + range.end, + HighlightSpan(word.color_fg, word.color_bg) + ) + + if(word.sound_type != HighlightWord.SOUND_TYPE_NONE) { + if(options.highlightSound == null) options.highlightSound = word } + + if( word.speech != 0 ) { + if(options.highlightSpeech == null) options.highlightSpeech = word + } + + if(options.highlightAny == null) options.highlightAny = word } } } diff --git a/app/src/main/res/drawable/ic_comment.xml b/app/src/main/res/drawable/ic_comment.xml new file mode 100644 index 00000000..13ed321b --- /dev/null +++ b/app/src/main/res/drawable/ic_comment.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/layout/act_highlight_edit.xml b/app/src/main/res/layout/act_highlight_edit.xml index 906f2849..209cfb13 100644 --- a/app/src/main/res/layout/act_highlight_edit.xml +++ b/app/src/main/res/layout/act_highlight_edit.xml @@ -123,6 +123,23 @@ + + + + + + + + + + diff --git a/app/src/main/res/layout/lv_highlight_word.xml b/app/src/main/res/layout/lv_highlight_word.xml index 38728c16..3fc7f589 100644 --- a/app/src/main/res/layout/lv_highlight_word.xml +++ b/app/src/main/res/layout/lv_highlight_word.xml @@ -66,6 +66,15 @@ android:gravity="center_vertical|start" android:paddingStart="12dp" android:paddingEnd="12dp" + android:layout_marginEnd="4dp" + /> + diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index c4ca11f1..5cc66eb7 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -970,5 +970,6 @@ コンテキストメニューの副項目を常に展開する 更新履歴 (v%1$s) 更新履歴 (v%1$s→v%2$s) + Test diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 76ae350b..d9dd18ea 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -966,4 +966,5 @@ Always expand sub-items of context menu Release notes (v%1$s) Release notes (v%1$s→v%2$s) + Test \ No newline at end of file