「強調表示する単語/編集/読み上げを有効にする」を追加。どのキーワードに反応したのかカラムを辿らなくても分かる。

This commit is contained in:
tateisu 2019-11-15 15:13:59 +09:00
parent 096a8770db
commit 73360e2269
18 changed files with 303 additions and 161 deletions

View File

@ -49,6 +49,8 @@ class ActHighlightWordEdit
private lateinit var tvName : TextView private lateinit var tvName : TextView
private lateinit var swSound : Switch private lateinit var swSound : Switch
private lateinit var swSpeech : Switch
private var bBusy = false private var bBusy = false
@ -60,7 +62,6 @@ class ActHighlightWordEdit
} catch(ex : JSONException) { } catch(ex : JSONException) {
throw RuntimeException(ex) throw RuntimeException(ex)
} }
} }
override fun onBackPressed() { override fun onBackPressed() {
@ -110,12 +111,19 @@ class ActHighlightWordEdit
swSound = findViewById(R.id.swSound) swSound = findViewById(R.id.swSound)
swSound.setOnCheckedChangeListener(this) swSound.setOnCheckedChangeListener(this)
findViewById<View>(R.id.btnTextColorEdit).setOnClickListener(this) swSpeech = findViewById(R.id.swSpeech)
findViewById<View>(R.id.btnTextColorReset).setOnClickListener(this) swSpeech.setOnCheckedChangeListener(this)
findViewById<View>(R.id.btnBackgroundColorEdit).setOnClickListener(this)
findViewById<View>(R.id.btnBackgroundColorReset).setOnClickListener(this) intArrayOf(
findViewById<View>(R.id.btnNotificationSoundEdit).setOnClickListener(this) R.id.btnTextColorEdit,
findViewById<View>(R.id.btnNotificationSoundReset).setOnClickListener(this) R.id.btnTextColorReset,
R.id.btnBackgroundColorEdit,
R.id.btnBackgroundColorReset,
R.id.btnNotificationSoundEdit,
R.id.btnNotificationSoundReset
).forEach {
findViewById<View>(it)?.setOnClickListener(this)
}
} }
private fun showSampleText() { private fun showSampleText() {
@ -124,6 +132,8 @@ class ActHighlightWordEdit
swSound.isChecked = item.sound_type != HighlightWord.SOUND_TYPE_NONE swSound.isChecked = item.sound_type != HighlightWord.SOUND_TYPE_NONE
swSpeech.isChecked = item.speech != 0
tvName.text = item.name tvName.text = item.name
tvName.setBackgroundColor(item.color_bg) // may 0 tvName.setBackgroundColor(item.color_bg) // may 0
tvName.textColor = item.color_fg.notZero() tvName.textColor = item.color_fg.notZero()
@ -169,12 +179,21 @@ class ActHighlightWordEdit
override fun onCheckedChanged(buttonView : CompoundButton, isChecked : Boolean) { override fun onCheckedChanged(buttonView : CompoundButton, isChecked : Boolean) {
if(bBusy) return if(bBusy) return
if(! isChecked) { when(buttonView.id){
item.sound_type = HighlightWord.SOUND_TYPE_NONE R.id.swSound ->{
} else { if(! isChecked) {
item.sound_type = HighlightWord.SOUND_TYPE_NONE
item.sound_type = } else {
if(item.sound_uri?.isEmpty() != false) HighlightWord.SOUND_TYPE_DEFAULT else HighlightWord.SOUND_TYPE_CUSTOM 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
}
}
} }
} }

View File

@ -11,6 +11,7 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ImageButton
import android.widget.TextView import android.widget.TextView
import com.woxthebox.draglistview.DragItem import com.woxthebox.draglistview.DragItem
import com.woxthebox.draglistview.DragItemAdapter import com.woxthebox.draglistview.DragItemAdapter
@ -95,7 +96,7 @@ class ActHighlightWordList : AppCompatActivity(), View.OnClickListener {
if(swipedDirection == ListSwipeItem.SwipeDirection.LEFT) { if(swipedDirection == ListSwipeItem.SwipeDirection.LEFT) {
val o = item.tag val o = item.tag
if(o is HighlightWord) { if(o is HighlightWord) {
o.delete() o.delete(this@ActHighlightWordList)
listAdapter.removeItem(listAdapter.getPositionForItem(o)) listAdapter.removeItem(listAdapter.getPositionForItem(o))
} }
} }
@ -135,11 +136,13 @@ class ActHighlightWordList : AppCompatActivity(), View.OnClickListener {
val tvName : TextView val tvName : TextView
private val btnSound : View private val btnSound : View
val ivSpeech: ImageButton
init { init {
tvName = viewRoot.findViewById(R.id.tvName) tvName = viewRoot.findViewById(R.id.tvName)
btnSound = viewRoot.findViewById(R.id.btnSound) btnSound = viewRoot.findViewById(R.id.btnSound)
ivSpeech = viewRoot.findViewById(R.id.ivSpeech)
// リスト要素のビューが ListSwipeItem だった場合、Swipe操作を制御できる // リスト要素のビューが ListSwipeItem だった場合、Swipe操作を制御できる
if(viewRoot is ListSwipeItem) { if(viewRoot is ListSwipeItem) {
@ -163,6 +166,10 @@ class ActHighlightWordList : AppCompatActivity(), View.OnClickListener {
vg(btnSound, item.sound_type != HighlightWord.SOUND_TYPE_NONE) vg(btnSound, item.sound_type != HighlightWord.SOUND_TYPE_NONE)
btnSound.setOnClickListener(this) btnSound.setOnClickListener(this)
btnSound.tag = item btnSound.tag = item
vg(ivSpeech,item.speech != 0 )
ivSpeech.setOnClickListener(this)
ivSpeech.tag = item
} }
// @Override // @Override
@ -180,9 +187,15 @@ class ActHighlightWordList : AppCompatActivity(), View.OnClickListener {
override fun onClick(v : View) { override fun onClick(v : View) {
val o = v.tag val o = v.tag
if(o is HighlightWord) { 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) { DragItem(context, layoutId) {
override fun onBindDragView(clickedView : View, dragView : View) { override fun onBindDragView(clickedView : View, dragView : View) {
dragView.findViewById<TextView>(R.id.tvName).text = dragView.findViewById<TextView>(R.id.tvName).text =
clickedView.findViewById<TextView>(R.id.tvName).text clickedView.findViewById<TextView>(R.id.tvName).text
dragView.findViewById<View>(R.id.btnSound).visibility = dragView.findViewById<View>(R.id.btnSound).visibility =
clickedView.findViewById<View>(R.id.btnSound).visibility clickedView.findViewById<View>(R.id.btnSound).visibility
dragView.findViewById<View>(R.id.ivSpeech).visibility=
clickedView.findViewById<View>(R.id.ivSpeech).visibility
dragView.findViewById<View>(R.id.item_layout).setBackgroundColor( dragView.findViewById<View>(R.id.item_layout).setBackgroundColor(
getAttributeColor( getAttributeColor(
this@ActHighlightWordList, this@ActHighlightWordList,
@ -238,7 +257,7 @@ class ActHighlightWordList : AppCompatActivity(), View.OnClickListener {
var item = HighlightWord.load(text) var item = HighlightWord.load(text)
if(item == null) { if(item == null) {
item = HighlightWord(text) item = HighlightWord(text)
item.save() item.save(this@ActHighlightWordList)
loadData() loadData()
} }
edit(item) edit(item)
@ -260,7 +279,7 @@ class ActHighlightWordList : AppCompatActivity(), View.OnClickListener {
try { try {
val sv = data.getStringExtra(ActHighlightWordEdit.EXTRA_ITEM) ?: return val sv = data.getStringExtra(ActHighlightWordEdit.EXTRA_ITEM) ?: return
val item = HighlightWord(sv.toJsonObject()) val item = HighlightWord(sv.toJsonObject())
item.save() item.save(this@ActHighlightWordList)
loadData() loadData()
} catch(ex : Throwable) { } catch(ex : Throwable) {
throw RuntimeException("can't load data", ex) throw RuntimeException("can't load data", ex)

View File

@ -127,7 +127,9 @@ class App1 : Application() {
// 2019/10/22 39 => 40 NotificationTracking テーブルに項目追加。 // 2019/10/22 39 => 40 NotificationTracking テーブルに項目追加。
// 2019/10/22 40 => 41 NotificationCache テーブルに項目追加。 // 2019/10/22 40 => 41 NotificationCache テーブルに項目追加。
// 2019/10/23 41=> 42 SavedAccount テーブルに項目追加。 // 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( private val tableList = arrayOf(
LogData, LogData,

View File

@ -142,7 +142,7 @@ class AppState(internal val context : Context, internal val pref : SharedPrefere
this.handler = Handler() this.handler = Handler()
this.density = context.resources.displayMetrics.density this.density = context.resources.displayMetrics.density
this.stream_reader = StreamReader(context, handler, pref) this.stream_reader = StreamReader(context, handler, pref)
this.networkTracker = NetworkStateTracker(context){ this.networkTracker = NetworkStateTracker(context) {
App1.custom_emoji_cache.onNetworkChanged() App1.custom_emoji_cache.onNetworkChanged()
App1.custom_emoji_lister.onNetworkChanged() App1.custom_emoji_lister.onNetworkChanged()
} }
@ -153,16 +153,7 @@ class AppState(internal val context : Context, internal val pref : SharedPrefere
// TextToSpeech // TextToSpeech
private val isTextToSpeechRequired : Boolean private val isTextToSpeechRequired : Boolean
get() { get() = column_list.any { it.enable_speech } || HighlightWord.hasTextToSpeechHighlightWord()
var b = false
for(c in column_list) {
if(c.enable_speech) {
b = true
break
}
}
return b
}
private val tts_receiver = object : BroadcastReceiver() { private val tts_receiver = object : BroadcastReceiver() {
override fun onReceive(context : Context, intent : Intent?) { 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) map_busy_bookmark.remove(key)
} }
fun isBusyBoost(account : SavedAccount, status : TootStatus) : Boolean { fun isBusyBoost(account : SavedAccount, status : TootStatus) : Boolean {
val key = account.acct + ":" + status.busyKey val key = account.acct + ":" + status.busyKey
return map_busy_boost.contains(key) return map_busy_boost.contains(key)
@ -363,7 +352,7 @@ class AppState(internal val context : Context, internal val pref : SharedPrefere
} }
@SuppressLint("StaticFieldLeak") @SuppressLint("StaticFieldLeak")
private fun enableSpeech() { fun enableSpeech() {
this.willSpeechEnabled = isTextToSpeechRequired this.willSpeechEnabled = isTextToSpeechRequired
if(willSpeechEnabled && tts == null && tts_status == TTS_STATUS_NONE) { 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()) addSpeech(sb.toString())
} }
private fun addSpeech(text : String) { internal fun addSpeech(text : String,allowRepeat:Boolean = false) {
if(tts == null) return if(tts == null) return
val sv = reSpaces.matcher(text).replaceAll(" ").trim { it <= ' ' } val sv = reSpaces.matcher(text).replaceAll(" ").trim { it <= ' ' }
if(sv.isEmpty()) return if(sv.isEmpty()) return
for(check in duplication_check) { if(!allowRepeat) {
if(check == sv) return for(check in duplication_check) {
} if(check == sv) return
duplication_check.addLast(sv) }
if(duplication_check.size >= 60) { duplication_check.addLast(sv)
duplication_check.removeFirst() if(duplication_check.size >= 60) {
duplication_check.removeFirst()
}
} }
tts_queue.add(sv) tts_queue.add(sv)

View File

@ -1411,7 +1411,7 @@ class Column(
if(! (with_attachment || with_highlight)) return false if(! (with_attachment || with_highlight)) return false
val matchMedia = with_attachment && status.reblog?.hasMedia() ?: status.hasMedia() 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) // どれかの条件を満たすならフィルタしない(false)、どれも満たさないならフィルタする(true)
return ! (matchMedia || matchHighlight) return ! (matchMedia || matchHighlight)
@ -2690,16 +2690,18 @@ class Column(
val added = list_new.size // may 0 val added = list_new.size // may 0
loop@ for(o in list_new) { var doneSound = false
when(o) { for(o in list_new) {
if( o is TootStatus ){
is TootStatus -> { o.highlightSound?.let{
val highlight_sound = o.highlight_sound if(!doneSound){
if(highlight_sound != null) { doneSound = true
App1.sound(highlight_sound) App1.sound(it)
break@loop
} }
} }
o.highlightSpeech?.let{
App1.getAppState(context).addSpeech(it.name,allowRepeat = true)
}
} }
} }

View File

@ -134,12 +134,17 @@ class ColumnTask_Refresh(
column.list_data.removeAt(0) column.list_data.removeAt(0)
} }
var doneSound = false
for(o in list_new) { for(o in list_new) {
if(o is TootStatus) { if( o is TootStatus ){
val highlight_sound = o.highlight_sound o.highlightSound?.let{
if(highlight_sound != null) { if(!doneSound){
App1.sound(highlight_sound) doneSound = true
break App1.sound(it)
}
}
o.highlightSpeech?.let{
App1.getAppState(context).addSpeech(it.name,allowRepeat = true)
} }
} }
} }

View File

@ -25,36 +25,36 @@ class DownloadReceiver : BroadcastReceiver() {
try { try {
val id = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, 0L) val id = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, 0L)
val query = DownloadManager.Query().setFilterById(id) val query = DownloadManager.Query().setFilterById(id)
downloadManager.query(query)?.use { cursor -> downloadManager.query(query)?.use { cursor ->
if(! cursor.moveToFirst()) { if(! cursor.moveToFirst()) {
log.e("cursor.moveToFirst() failed.") log.e("cursor.moveToFirst() failed.")
} else { 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.
重複を回避する方法はなさそうだ
*/
} }
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) { } catch(ex : Throwable) {
log.e(ex, "downloadManager.query() failed.") log.e(ex, "downloadManager.query() failed.")

View File

@ -181,9 +181,9 @@ class TootStatus(parser : TootParser, src : JSONObject) : TimelineItem() {
// 会話の流れビューで後から追加する // 会話の流れビューで後から追加する
var card : TootCard? = null var card : TootCard? = null
var highlight_sound : HighlightWord? = null var highlightSound: HighlightWord? = null
var highlightSpeech: HighlightWord? = null
var hasHighlight : Boolean = false var highlightAny: HighlightWord? = null
val time_created_at : Long val time_created_at : Long
@ -295,10 +295,9 @@ class TootStatus(parser : TootParser, src : JSONObject) : TimelineItem() {
) )
this.decoded_content = options.decodeHTML(content) this.decoded_content = options.decodeHTML(content)
this.hasHighlight = this.hasHighlight || options.hasHighlight if(this.highlightSound==null) this.highlightSound = options.highlightSound
if(options.highlight_sound != null && this.highlight_sound == null) { if(this.highlightSpeech==null) this.highlightSpeech = options.highlightSpeech
this.highlight_sound = options.highlight_sound if(this.highlightAny==null) this.highlightAny = options.highlightAny
}
// Markdownのデコード結果からmentionsを読むのだった // Markdownのデコード結果からmentionsを読むのだった
val mentions1 = val mentions1 =
@ -325,10 +324,9 @@ class TootStatus(parser : TootParser, src : JSONObject) : TimelineItem() {
) )
this.decoded_spoiler_text = options.decodeHTML(spoiler_text) this.decoded_spoiler_text = options.decodeHTML(spoiler_text)
this.hasHighlight = this.hasHighlight || options.hasHighlight if(this.highlightSound==null) this.highlightSound = options.highlightSound
if(options.highlight_sound != null && this.highlight_sound == null) { if(this.highlightSpeech==null) this.highlightSpeech = options.highlightSpeech
this.highlight_sound = options.highlight_sound if(this.highlightAny==null) this.highlightAny = options.highlightAny
}
val mentions2 = val mentions2 =
(decoded_spoiler_text as? MisskeyMarkdownDecoder.SpannableStringBuilderEx)?.mentions (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.decoded_content = options.decodeHTML(content)
this.hasHighlight = this.hasHighlight || options.hasHighlight if(this.highlightSound==null) this.highlightSound = options.highlightSound
if(options.highlight_sound != null && this.highlight_sound == null) { if(this.highlightSpeech==null) this.highlightSpeech = options.highlightSpeech
this.highlight_sound = options.highlight_sound if(this.highlightAny==null) this.highlightAny = options.highlightAny
}
var sv = (src.parseString("spoiler_text") ?: "").cleanCW() var sv = (src.parseString("spoiler_text") ?: "").cleanCW()
this.spoiler_text = when { this.spoiler_text = when {
@ -517,10 +514,9 @@ class TootStatus(parser : TootParser, src : JSONObject) : TimelineItem() {
this.decoded_spoiler_text = options.decodeEmoji(spoiler_text) this.decoded_spoiler_text = options.decodeEmoji(spoiler_text)
this.hasHighlight = this.hasHighlight || options.hasHighlight if(this.highlightSound==null) this.highlightSound = options.highlightSound
if(options.highlight_sound != null && this.highlight_sound == null) { if(this.highlightSpeech==null) this.highlightSpeech = options.highlightSpeech
this.highlight_sound = options.highlight_sound if(this.highlightAny==null) this.highlightAny = options.highlightAny
}
this.enquete = try { this.enquete = try {
sv = src.parseString("enquete") ?: "" sv = src.parseString("enquete") ?: ""

View File

@ -1,17 +1,20 @@
package jp.juggler.subwaytooter.table package jp.juggler.subwaytooter.table
import android.content.ContentValues import android.content.ContentValues
import android.content.Context
import android.database.Cursor import android.database.Cursor
import android.database.sqlite.SQLiteDatabase import android.database.sqlite.SQLiteDatabase
import android.provider.BaseColumns
import org.json.JSONObject import org.json.JSONObject
import jp.juggler.subwaytooter.App1 import jp.juggler.subwaytooter.App1
import jp.juggler.util.* import jp.juggler.util.*
import java.util.concurrent.atomic.AtomicReference
class HighlightWord { class HighlightWord {
companion object :TableCompanion{ companion object : TableCompanion {
private val log = LogCategory("HighlightWord") private val log = LogCategory("HighlightWord")
@ -20,34 +23,37 @@ class HighlightWord {
const val SOUND_TYPE_CUSTOM = 2 const val SOUND_TYPE_CUSTOM = 2
const val table = "highlight_word" const val table = "highlight_word"
const val COL_ID = "_id" const val COL_ID = BaseColumns._ID
const val COL_NAME = "name" const val COL_NAME = "name"
private const val COL_TIME_SAVE = "time_save" private const val COL_TIME_SAVE = "time_save"
private const val COL_COLOR_BG = "color_bg" private const val COL_COLOR_BG = "color_bg"
private const val COL_COLOR_FG = "color_fg" private const val COL_COLOR_FG = "color_fg"
private const val COL_SOUND_TYPE = "sound_type" private const val COL_SOUND_TYPE = "sound_type"
private const val COL_SOUND_URI = "sound_uri" 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_name = "$COL_NAME=?"
private const val selection_id = COL_ID + "=?" private const val selection_speech = "$COL_SPEECH<>0"
private const val selection_id = "$COL_ID=?"
private val columns_name = arrayOf(COL_NAME) private val columns_name = arrayOf(COL_NAME)
override fun onDBCreate(db : SQLiteDatabase) { override fun onDBCreate(db : SQLiteDatabase) {
log.d("onDBCreate!") log.d("onDBCreate!")
db.execSQL( db.execSQL(
"create table if not exists " + table """create table if not exists $table
+ "(_id INTEGER PRIMARY KEY" ($COL_ID INTEGER PRIMARY KEY
+ ",name text not null" ,$COL_NAME text not null
+ ",time_save integer not null" ,$COL_TIME_SAVE integer not null
+ ",color_bg integer not null default 0" ,$COL_COLOR_BG integer not null default 0
+ ",color_fg integer not null default 0" ,$COL_COLOR_FG integer not null default 0
+ ",sound_type integer not null default 1" ,$COL_SOUND_TYPE integer not null default 1
+ ",sound_uri text default null" ,$COL_SOUND_URI text default null
+ ")" ,$COL_SPEECH integer default 0
)"""
) )
db.execSQL( 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) { if(oldVersion < 21 && newVersion >= 21) {
onDBCreate(db) 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? { fun load(name : String) : HighlightWord? {
@ -73,7 +86,7 @@ class HighlightWord {
} }
fun createCursor() : Cursor { 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? val nameSet : WordTrieTree?
@ -95,14 +108,46 @@ class HighlightWord {
return if(dst.isEmpty) null else dst return if(dst.isEmpty) null else dst
} }
private val hasTextToSpeechHighlightWordCache = AtomicReference<Boolean>(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 id = - 1L
var name : String var name : String
var color_bg : Int = 0 var color_bg = 0
var color_fg : Int = 0 var color_fg = 0
var sound_type : Int = 0 var sound_type = 0
var sound_uri : String? = null var sound_uri : String? = null
var speech = 0
fun encodeJson() : JSONObject { fun encodeJson() : JSONObject {
val dst = JSONObject() val dst = JSONObject()
@ -111,17 +156,19 @@ class HighlightWord {
dst.put(COL_COLOR_BG, color_bg) dst.put(COL_COLOR_BG, color_bg)
dst.put(COL_COLOR_FG, color_fg) dst.put(COL_COLOR_FG, color_fg)
dst.put(COL_SOUND_TYPE, sound_type) dst.put(COL_SOUND_TYPE, sound_type)
dst.put(COL_SPEECH, speech)
if(sound_uri != null) dst.put(COL_SOUND_URI, sound_uri) if(sound_uri != null) dst.put(COL_SOUND_URI, sound_uri)
return dst return dst
} }
constructor(src : JSONObject) { constructor(src : JSONObject) {
this.id = src.parseLong( COL_ID) ?: -1L this.id = src.parseLong(COL_ID) ?: - 1L
this.name = src.notEmptyOrThrow(COL_NAME) this.name = src.notEmptyOrThrow(COL_NAME)
this.color_bg = src.optInt(COL_COLOR_BG) this.color_bg = src.optInt(COL_COLOR_BG)
this.color_fg = src.optInt(COL_COLOR_FG) this.color_fg = src.optInt(COL_COLOR_FG)
this.sound_type = src.optInt(COL_SOUND_TYPE) 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) { constructor(name : String) {
@ -137,9 +184,10 @@ class HighlightWord {
this.color_fg = cursor.getInt(COL_COLOR_FG) this.color_fg = cursor.getInt(COL_COLOR_FG)
this.sound_type = cursor.getInt(COL_SOUND_TYPE) this.sound_type = cursor.getInt(COL_SOUND_TYPE)
this.sound_uri = cursor.getStringOrNull(COL_SOUND_URI) 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") if(name.isEmpty()) throw RuntimeException("HighlightWord.save(): name is empty")
try { try {
@ -149,12 +197,15 @@ class HighlightWord {
cv.put(COL_COLOR_BG, color_bg) cv.put(COL_COLOR_BG, color_bg)
cv.put(COL_COLOR_FG, color_fg) cv.put(COL_COLOR_FG, color_fg)
cv.put(COL_SOUND_TYPE, sound_type) cv.put(COL_SOUND_TYPE, sound_type)
val sound_uri = this.sound_uri val sound_uri = this.sound_uri
if(sound_uri?.isEmpty() != false) { if(sound_uri?.isEmpty() != false) {
cv.putNull(COL_SOUND_URI) cv.putNull(COL_SOUND_URI)
} else { } else {
cv.put(COL_SOUND_URI, sound_uri) cv.put(COL_SOUND_URI, sound_uri)
} }
cv.put(COL_SPEECH, speech)
if(id == - 1L) { if(id == - 1L) {
id = App1.database.replace(table, null, cv) id = App1.database.replace(table, null, cv)
} else { } else {
@ -164,14 +215,22 @@ class HighlightWord {
log.e(ex, "save failed.") log.e(ex, "save failed.")
} }
synchronized(Companion) {
hasTextToSpeechHighlightWordCache.set(null)
}
App1.getAppState(context).enableSpeech()
} }
fun delete() { fun delete(context : Context) {
try { try {
App1.database.delete(table, selection_id, arrayOf(id.toString())) App1.database.delete(table, selection_id, arrayOf(id.toString()))
} catch(ex : Throwable) { } catch(ex : Throwable) {
log.e(ex, "delete failed.") log.e(ex, "delete failed.")
} }
synchronized(Companion) {
hasTextToSpeechHighlightWordCache.set(null)
}
App1.getAppState(context).enableSpeech()
} }
} }

View File

@ -36,11 +36,10 @@ class DecodeOptions(
return false return false
} }
// OUTPUT: true if found highlight // OUTPUT
var hasHighlight : Boolean = false var highlightSound: HighlightWord? = null
var highlightSpeech: HighlightWord? = null
// OUTPUT: found highlight with sound var highlightAny: HighlightWord? = null
var highlight_sound : HighlightWord? = null
//////////////////////// ////////////////////////
// decoder // decoder

View File

@ -107,19 +107,23 @@ object EmojiDecoder {
private fun applyHighlight(start : Int, end : Int) { private fun applyHighlight(start : Int, end : Int) {
val list = options.highlightTrie?.matchList(sb, start, end) ?: return val list = options.highlightTrie?.matchList(sb, start, end) ?: return
for(range in list) { for(range in list) {
val word = HighlightWord.load(range.word) val word = HighlightWord.load(range.word) ?: continue
if(word != null) { sb.setSpan(
options.hasHighlight = true HighlightSpan(word.color_fg, word.color_bg),
sb.setSpan( range.start,
HighlightSpan(word.color_fg, word.color_bg), range.end,
range.start, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
range.end, )
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
) if(word.sound_type != HighlightWord.SOUND_TYPE_NONE) {
if(word.sound_type != HighlightWord.SOUND_TYPE_NONE) { if(options.highlightSound == null) options.highlightSound = word
options.highlight_sound = word
}
} }
if( word.speech != 0 ) {
if(options.highlightSpeech == null) options.highlightSpeech = word
}
if(options.highlightAny == null) options.highlightAny = word
} }
} }

View File

@ -408,19 +408,23 @@ object HTMLDecoder {
val list = options.highlightTrie?.matchList(sb, start, end) val list = options.highlightTrie?.matchList(sb, start, end)
if(list != null) { if(list != null) {
for(range in list) { for(range in list) {
val word = HighlightWord.load(range.word) val word = HighlightWord.load(range.word) ?: continue
if(word != null) { sb.setSpan(
options.hasHighlight = true HighlightSpan(word.color_fg, word.color_bg),
sb.setSpan( range.start,
HighlightSpan(word.color_fg, word.color_bg), range.end,
range.start, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
range.end, )
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
) if(word.sound_type != HighlightWord.SOUND_TYPE_NONE) {
if(word.sound_type != HighlightWord.SOUND_TYPE_NONE) { if(options.highlightSound == null) options.highlightSound = word
options.highlight_sound = word
}
} }
if( word.speech != 0 ) {
if(options.highlightSpeech == null) options.highlightSpeech = word
}
if(options.highlightAny == null) options.highlightAny = word
} }
} }
} }

View File

@ -732,18 +732,22 @@ object MisskeyMarkdownDecoder {
val list = options.highlightTrie?.matchList(sb, start, end) val list = options.highlightTrie?.matchList(sb, start, end)
if(list != null) { if(list != null) {
for(range in list) { for(range in list) {
val word = HighlightWord.load(range.word) val word = HighlightWord.load(range.word) ?: continue
if(word != null) { spanList.addLast(
options.hasHighlight = true range.start,
spanList.addLast( range.end,
range.start, HighlightSpan(word.color_fg, word.color_bg)
range.end, )
HighlightSpan(word.color_fg, word.color_bg)
) if(word.sound_type != HighlightWord.SOUND_TYPE_NONE) {
if(word.sound_type != HighlightWord.SOUND_TYPE_NONE) { if(options.highlightSound == null) options.highlightSound = word
options.highlight_sound = word
}
} }
if( word.speech != 0 ) {
if(options.highlightSpeech == null) options.highlightSpeech = word
}
if(options.highlightAny == null) options.highlightAny = word
} }
} }
} }

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M21.99,4c0,-1.1 -0.89,-2 -1.99,-2L4,2c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h14l4,4 -0.01,-18zM18,14L6,14v-2h12v2zM18,11L6,11L6,9h12v2zM18,8L6,8L6,6h12v2z"/>
</vector>

View File

@ -123,6 +123,23 @@
</LinearLayout> </LinearLayout>
<View style="@style/setting_divider"/>
<TextView
style="@style/setting_row_label"
android:text="@string/enable_speech"
/>
<LinearLayout style="@style/setting_row_form">
<Switch
android:id="@+id/swSpeech"
style="@style/setting_horizontal_stretch"
android:gravity="center"
/>
</LinearLayout>
</LinearLayout> </LinearLayout>
</ScrollView> </ScrollView>

View File

@ -66,6 +66,15 @@
android:gravity="center_vertical|start" android:gravity="center_vertical|start"
android:paddingStart="12dp" android:paddingStart="12dp"
android:paddingEnd="12dp" android:paddingEnd="12dp"
android:layout_marginEnd="4dp"
/>
<ImageButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/ivSpeech"
android:src="@drawable/ic_comment"
android:tint="?attr/colorVectorDrawable"
android:layout_marginEnd="4dp"
/> />
<ImageButton <ImageButton
android:layout_width="wrap_content" android:layout_width="wrap_content"
@ -74,6 +83,7 @@
android:src="@drawable/ic_volume_up" android:src="@drawable/ic_volume_up"
android:contentDescription="@string/check_sound" android:contentDescription="@string/check_sound"
android:tint="?attr/colorVectorDrawable" android:tint="?attr/colorVectorDrawable"
android:layout_marginEnd="4dp"
/> />
</LinearLayout> </LinearLayout>

View File

@ -970,5 +970,6 @@
<string name="always_expand_context_menu_sub_items" >コンテキストメニューの副項目を常に展開する</string> <string name="always_expand_context_menu_sub_items" >コンテキストメニューの副項目を常に展開する</string>
<string name="release_notes_latest">更新履歴 (v%1$s)</string> <string name="release_notes_latest">更新履歴 (v%1$s)</string>
<string name="release_notes_updated">更新履歴 (v%1$s→v%2$s)</string> <string name="release_notes_updated">更新履歴 (v%1$s→v%2$s)</string>
<string name="test">Test</string>
</resources> </resources>

View File

@ -966,4 +966,5 @@
<string name="always_expand_context_menu_sub_items" >Always expand sub-items of context menu</string> <string name="always_expand_context_menu_sub_items" >Always expand sub-items of context menu</string>
<string name="release_notes_latest">Release notes (v%1$s)</string> <string name="release_notes_latest">Release notes (v%1$s)</string>
<string name="release_notes_updated">Release notes (v%1$s→v%2$s)</string> <string name="release_notes_updated">Release notes (v%1$s→v%2$s)</string>
<string name="test">Test</string>
</resources> </resources>