2018-01-04 19:52:25 +01:00
|
|
|
|
package jp.juggler.subwaytooter
|
|
|
|
|
|
2023-02-11 10:25:17 +01:00
|
|
|
|
import android.content.Context
|
2018-01-04 19:52:25 +01:00
|
|
|
|
import android.content.Intent
|
2023-02-11 10:25:17 +01:00
|
|
|
|
import android.content.pm.PackageManager
|
2020-12-19 07:46:42 +01:00
|
|
|
|
import android.content.pm.ResolveInfo
|
2020-01-31 08:46:30 +01:00
|
|
|
|
import android.graphics.Color
|
|
|
|
|
import android.graphics.Typeface
|
2018-01-04 19:52:25 +01:00
|
|
|
|
import android.net.Uri
|
2023-02-11 10:25:17 +01:00
|
|
|
|
import android.os.Build
|
2018-01-04 19:52:25 +01:00
|
|
|
|
import android.os.Bundle
|
2020-01-31 08:46:30 +01:00
|
|
|
|
import android.os.Handler
|
|
|
|
|
import android.text.Editable
|
|
|
|
|
import android.text.TextWatcher
|
2018-01-04 19:52:25 +01:00
|
|
|
|
import android.util.JsonWriter
|
2023-01-16 06:58:23 +01:00
|
|
|
|
import android.view.KeyEvent
|
2018-01-04 19:52:25 +01:00
|
|
|
|
import android.view.View
|
2023-01-16 06:58:23 +01:00
|
|
|
|
import android.view.View.FOCUS_FORWARD
|
2018-01-04 19:52:25 +01:00
|
|
|
|
import android.view.ViewGroup
|
2020-01-31 08:46:30 +01:00
|
|
|
|
import android.view.Window
|
2023-01-16 06:58:23 +01:00
|
|
|
|
import android.view.inputmethod.EditorInfo
|
2020-01-31 08:46:30 +01:00
|
|
|
|
import android.widget.*
|
2023-01-16 06:58:23 +01:00
|
|
|
|
import android.widget.TextView.OnEditorActionListener
|
2020-01-31 08:46:30 +01:00
|
|
|
|
import androidx.annotation.ColorInt
|
2023-05-03 18:20:11 +02:00
|
|
|
|
import androidx.annotation.WorkerThread
|
2019-08-25 10:28:16 +02:00
|
|
|
|
import androidx.appcompat.app.AlertDialog
|
2021-05-27 04:15:59 +02:00
|
|
|
|
import androidx.appcompat.app.AppCompatActivity
|
2020-01-31 08:46:30 +01:00
|
|
|
|
import androidx.core.content.ContextCompat
|
2019-08-25 10:28:16 +02:00
|
|
|
|
import androidx.core.content.FileProvider
|
2023-01-12 22:17:34 +01:00
|
|
|
|
import androidx.recyclerview.widget.DiffUtil
|
|
|
|
|
import androidx.recyclerview.widget.LinearLayoutManager
|
|
|
|
|
import androidx.recyclerview.widget.RecyclerView
|
2020-01-31 08:46:30 +01:00
|
|
|
|
import com.jrummyapps.android.colorpicker.ColorPickerDialog
|
|
|
|
|
import com.jrummyapps.android.colorpicker.ColorPickerDialogListener
|
2021-06-28 09:09:00 +02:00
|
|
|
|
import jp.juggler.subwaytooter.appsetting.AppDataExporter
|
|
|
|
|
import jp.juggler.subwaytooter.appsetting.AppSettingItem
|
|
|
|
|
import jp.juggler.subwaytooter.appsetting.SettingType
|
|
|
|
|
import jp.juggler.subwaytooter.appsetting.appSettingRoot
|
2023-01-12 22:17:34 +01:00
|
|
|
|
import jp.juggler.subwaytooter.databinding.ActAppSettingBinding
|
|
|
|
|
import jp.juggler.subwaytooter.databinding.LvSettingItemBinding
|
2020-01-31 12:49:54 +01:00
|
|
|
|
import jp.juggler.subwaytooter.dialog.DlgAppPicker
|
2022-06-13 19:23:46 +02:00
|
|
|
|
import jp.juggler.subwaytooter.notification.restartAllWorker
|
2023-02-08 20:50:08 +01:00
|
|
|
|
import jp.juggler.subwaytooter.pref.FILE_PROVIDER_AUTHORITY
|
2021-11-06 04:00:29 +01:00
|
|
|
|
import jp.juggler.subwaytooter.pref.impl.BooleanPref
|
|
|
|
|
import jp.juggler.subwaytooter.pref.impl.FloatPref
|
|
|
|
|
import jp.juggler.subwaytooter.pref.impl.IntPref
|
|
|
|
|
import jp.juggler.subwaytooter.pref.impl.StringPref
|
2023-02-04 21:52:26 +01:00
|
|
|
|
import jp.juggler.subwaytooter.pref.lazyPref
|
2020-01-31 08:46:30 +01:00
|
|
|
|
import jp.juggler.subwaytooter.table.SavedAccount
|
2023-02-04 21:52:26 +01:00
|
|
|
|
import jp.juggler.subwaytooter.table.daoAcctColor
|
2023-02-11 10:25:17 +01:00
|
|
|
|
import jp.juggler.subwaytooter.table.daoLogData
|
2020-12-19 07:46:42 +01:00
|
|
|
|
import jp.juggler.subwaytooter.util.CustomShare
|
|
|
|
|
import jp.juggler.subwaytooter.util.CustomShareTarget
|
|
|
|
|
import jp.juggler.subwaytooter.util.cn
|
2021-11-08 12:05:03 +01:00
|
|
|
|
import jp.juggler.subwaytooter.view.MyTextView
|
2018-12-01 00:02:18 +01:00
|
|
|
|
import jp.juggler.util.*
|
2023-02-11 10:25:17 +01:00
|
|
|
|
import jp.juggler.util.coroutine.launchAndShowError
|
2023-01-13 13:22:25 +01:00
|
|
|
|
import jp.juggler.util.coroutine.launchProgress
|
|
|
|
|
import jp.juggler.util.data.*
|
|
|
|
|
import jp.juggler.util.log.LogCategory
|
2023-05-03 18:20:11 +02:00
|
|
|
|
import jp.juggler.util.log.dialogOrToast
|
2023-01-13 13:22:25 +01:00
|
|
|
|
import jp.juggler.util.log.showToast
|
2023-05-03 18:20:11 +02:00
|
|
|
|
import jp.juggler.util.log.withCaption
|
2023-01-13 13:22:25 +01:00
|
|
|
|
import jp.juggler.util.ui.*
|
2018-01-04 19:52:25 +01:00
|
|
|
|
import java.io.File
|
2023-05-03 18:20:11 +02:00
|
|
|
|
import java.io.FileInputStream
|
2018-01-04 19:52:25 +01:00
|
|
|
|
import java.io.FileOutputStream
|
2020-01-31 08:46:30 +01:00
|
|
|
|
import java.io.InputStream
|
2018-09-20 18:41:04 +02:00
|
|
|
|
import java.io.OutputStreamWriter
|
2020-01-31 08:46:30 +01:00
|
|
|
|
import java.text.NumberFormat
|
|
|
|
|
import java.util.*
|
2023-01-12 22:17:34 +01:00
|
|
|
|
import java.util.concurrent.ConcurrentHashMap
|
2020-01-31 08:46:30 +01:00
|
|
|
|
import java.util.concurrent.TimeUnit
|
2018-09-20 18:41:04 +02:00
|
|
|
|
import java.util.zip.ZipEntry
|
|
|
|
|
import java.util.zip.ZipOutputStream
|
2020-01-31 08:46:30 +01:00
|
|
|
|
import kotlin.math.abs
|
2018-01-04 19:52:25 +01:00
|
|
|
|
|
2021-05-27 04:15:59 +02:00
|
|
|
|
class ActAppSetting : AppCompatActivity(), ColorPickerDialogListener, View.OnClickListener {
|
2021-05-22 00:03:16 +02:00
|
|
|
|
|
|
|
|
|
companion object {
|
|
|
|
|
|
|
|
|
|
internal val log = LogCategory("ActAppSetting")
|
|
|
|
|
|
|
|
|
|
fun createIntent(activity: ActMain) =
|
|
|
|
|
Intent(activity, ActAppSetting::class.java)
|
|
|
|
|
|
|
|
|
|
private const val COLOR_DIALOG_ID = 1
|
|
|
|
|
|
|
|
|
|
private const val STATE_CHOOSE_INTENT_TARGET = "customShareTarget"
|
|
|
|
|
|
|
|
|
|
// 他の設定子画面と重複しない値にすること
|
|
|
|
|
// const val REQUEST_CODE_OTHER = 0
|
|
|
|
|
// const val REQUEST_CODE_APP_DATA_IMPORT = 1
|
|
|
|
|
// const val REQUEST_CODE_TIMELINE_FONT = 2
|
|
|
|
|
|
|
|
|
|
val reLinefeed = Regex("[\\x0d\\x0a]+")
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-03 18:20:11 +02:00
|
|
|
|
// states
|
2021-05-22 00:03:16 +02:00
|
|
|
|
private var customShareTarget: CustomShareTarget? = null
|
|
|
|
|
|
|
|
|
|
lateinit var handler: Handler
|
2023-01-12 22:17:34 +01:00
|
|
|
|
|
|
|
|
|
val views by lazy {
|
|
|
|
|
ActAppSettingBinding.inflate(layoutInflater)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private val adapter by lazy {
|
|
|
|
|
MyAdapter()
|
|
|
|
|
}
|
2021-05-22 00:03:16 +02:00
|
|
|
|
|
2022-08-06 00:52:31 +02:00
|
|
|
|
private val arNoop = ActivityResultHandler(log) { }
|
2021-05-22 00:03:16 +02:00
|
|
|
|
|
2022-08-06 00:52:31 +02:00
|
|
|
|
private val arImportAppData = ActivityResultHandler(log) { r ->
|
|
|
|
|
if (r.isNotOk) return@ActivityResultHandler
|
|
|
|
|
r.data?.handleGetContentResult(contentResolver)
|
|
|
|
|
?.firstOrNull()
|
|
|
|
|
?.uri?.let { importAppData2(false, it) }
|
2021-05-22 00:03:16 +02:00
|
|
|
|
}
|
|
|
|
|
|
2022-08-06 00:52:31 +02:00
|
|
|
|
val arTimelineFont = ActivityResultHandler(log) { r ->
|
|
|
|
|
if (r.isNotOk) return@ActivityResultHandler
|
|
|
|
|
r.data?.let { handleFontResult(AppSettingItem.TIMELINE_FONT, it, "TimelineFont") }
|
2021-05-22 00:03:16 +02:00
|
|
|
|
}
|
|
|
|
|
|
2022-08-06 00:52:31 +02:00
|
|
|
|
val arTimelineFontBold = ActivityResultHandler(log) { r ->
|
|
|
|
|
if (r.isNotOk) return@ActivityResultHandler
|
|
|
|
|
r.data?.let {
|
|
|
|
|
handleFontResult(
|
|
|
|
|
AppSettingItem.TIMELINE_FONT_BOLD,
|
|
|
|
|
it,
|
|
|
|
|
"TimelineFontBold"
|
|
|
|
|
)
|
2021-06-20 15:12:25 +02:00
|
|
|
|
}
|
2021-05-22 00:03:16 +02:00
|
|
|
|
}
|
|
|
|
|
|
2023-01-12 22:17:34 +01:00
|
|
|
|
private var pendingQuery: String? = null
|
|
|
|
|
|
|
|
|
|
private val procQuery: Runnable = Runnable {
|
|
|
|
|
if (pendingQuery != null) load(null, pendingQuery)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private val divider = Any()
|
|
|
|
|
|
|
|
|
|
private var lastSection: AppSettingItem? = null
|
|
|
|
|
private var lastQuery: String? = null
|
|
|
|
|
private var colorTarget: AppSettingItem? = null
|
|
|
|
|
|
2021-05-22 00:03:16 +02:00
|
|
|
|
override fun onCreate(savedInstanceState: Bundle?) {
|
2023-01-13 13:22:25 +01:00
|
|
|
|
supportRequestWindowFeature(Window.FEATURE_NO_TITLE)
|
2021-05-22 00:03:16 +02:00
|
|
|
|
super.onCreate(savedInstanceState)
|
2022-09-10 23:09:26 +02:00
|
|
|
|
backPressed {
|
|
|
|
|
when {
|
|
|
|
|
lastQuery != null -> load(lastSection, null)
|
|
|
|
|
lastSection != null -> load(null, null)
|
|
|
|
|
else -> finish()
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-05-22 00:03:16 +02:00
|
|
|
|
|
2022-08-06 00:52:31 +02:00
|
|
|
|
arNoop.register(this)
|
|
|
|
|
arImportAppData.register(this)
|
|
|
|
|
arTimelineFont.register(this)
|
|
|
|
|
arTimelineFontBold.register(this)
|
2023-05-03 18:20:11 +02:00
|
|
|
|
arSaveAppData.register(this)
|
2021-05-22 00:03:16 +02:00
|
|
|
|
|
2023-01-14 21:37:23 +01:00
|
|
|
|
App1.setActivityTheme(this)
|
2021-05-22 00:03:16 +02:00
|
|
|
|
|
|
|
|
|
this.handler = App1.getAppState(this).handler
|
|
|
|
|
|
|
|
|
|
// val intent = this.intent
|
|
|
|
|
// val layoutId = intent.getIntExtra(EXTRA_LAYOUT_ID, 0)
|
|
|
|
|
// val titleId = intent.getIntExtra(EXTRA_TITLE_ID, 0)
|
|
|
|
|
// this.title = getString(titleId)
|
|
|
|
|
|
|
|
|
|
if (savedInstanceState != null) {
|
|
|
|
|
try {
|
2023-05-03 18:20:11 +02:00
|
|
|
|
savedInstanceState.getString(STATE_CHOOSE_INTENT_TARGET)?.let { target ->
|
|
|
|
|
customShareTarget = CustomShareTarget.values().firstOrNull { it.name == target }
|
|
|
|
|
}
|
2021-05-22 00:03:16 +02:00
|
|
|
|
} catch (ex: Throwable) {
|
|
|
|
|
log.e(ex, "can't restore customShareTarget.")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
initUi()
|
|
|
|
|
|
|
|
|
|
removeDefaultPref()
|
|
|
|
|
|
|
|
|
|
load(null, null)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun initUi() {
|
2023-01-12 22:17:34 +01:00
|
|
|
|
setContentView(views.root)
|
2021-05-22 00:03:16 +02:00
|
|
|
|
|
2023-02-17 16:42:14 +01:00
|
|
|
|
fixHorizontalPadding(views.llContent, 0f)
|
2021-05-22 00:03:16 +02:00
|
|
|
|
|
2023-01-12 22:17:34 +01:00
|
|
|
|
views.lvList.layoutManager = LinearLayoutManager(this)
|
|
|
|
|
views.lvList.adapter = adapter
|
2021-05-22 00:03:16 +02:00
|
|
|
|
|
2023-01-12 22:17:34 +01:00
|
|
|
|
views.etSearch.addTextChangedListener(object : TextWatcher {
|
|
|
|
|
override fun afterTextChanged(p0: Editable?) {
|
|
|
|
|
pendingQuery = p0?.toString()
|
|
|
|
|
this@ActAppSetting.handler.removeCallbacks(procQuery)
|
|
|
|
|
this@ActAppSetting.handler.postDelayed(procQuery, 166L)
|
|
|
|
|
}
|
2021-05-22 00:03:16 +02:00
|
|
|
|
|
2023-01-12 22:17:34 +01:00
|
|
|
|
override fun beforeTextChanged(
|
|
|
|
|
p0: CharSequence?,
|
|
|
|
|
p1: Int,
|
|
|
|
|
p2: Int,
|
|
|
|
|
p3: Int,
|
|
|
|
|
) {
|
|
|
|
|
}
|
2021-05-22 00:03:16 +02:00
|
|
|
|
|
2023-01-12 22:17:34 +01:00
|
|
|
|
override fun onTextChanged(
|
|
|
|
|
p0: CharSequence?,
|
|
|
|
|
p1: Int,
|
|
|
|
|
p2: Int,
|
|
|
|
|
p3: Int,
|
|
|
|
|
) {
|
|
|
|
|
}
|
|
|
|
|
})
|
2021-05-22 00:03:16 +02:00
|
|
|
|
|
2023-01-12 22:17:34 +01:00
|
|
|
|
views.btnSearchReset.setOnClickListener(this)
|
2021-05-22 00:03:16 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun removeDefaultPref() {
|
2023-02-04 21:52:26 +01:00
|
|
|
|
val e = lazyPref.edit()
|
2021-05-22 00:03:16 +02:00
|
|
|
|
var changed = false
|
|
|
|
|
appSettingRoot.scan {
|
2023-01-16 06:58:23 +01:00
|
|
|
|
when {
|
|
|
|
|
(it.pref as? IntPref)?.noRemove == true -> Unit
|
2023-02-04 21:52:26 +01:00
|
|
|
|
it.pref?.removeDefault(lazyPref, e) == true -> changed = true
|
2023-01-16 06:58:23 +01:00
|
|
|
|
}
|
2021-05-22 00:03:16 +02:00
|
|
|
|
}
|
|
|
|
|
if (changed) e.apply()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override fun onSaveInstanceState(outState: Bundle) {
|
|
|
|
|
super.onSaveInstanceState(outState)
|
2023-05-03 18:20:11 +02:00
|
|
|
|
customShareTarget?.name?.let {
|
|
|
|
|
outState.putString(STATE_CHOOSE_INTENT_TARGET, it)
|
|
|
|
|
}
|
2021-05-22 00:03:16 +02:00
|
|
|
|
}
|
|
|
|
|
|
2023-01-16 06:58:23 +01:00
|
|
|
|
override fun dispatchKeyEvent(event: KeyEvent) = try {
|
|
|
|
|
super.dispatchKeyEvent(event)
|
|
|
|
|
} catch (ex: Throwable) {
|
|
|
|
|
log.e(ex, "dispatchKeyEvent error")
|
|
|
|
|
false
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-22 00:03:16 +02:00
|
|
|
|
override fun onStop() {
|
|
|
|
|
super.onStop()
|
|
|
|
|
|
|
|
|
|
// Pull通知チェック間隔を変更したかもしれないのでジョブを再設定する
|
2022-06-13 19:23:46 +02:00
|
|
|
|
restartAllWorker(context = this)
|
2021-05-22 00:03:16 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override fun onClick(v: View) {
|
|
|
|
|
when (v.id) {
|
|
|
|
|
R.id.btnSearchReset -> {
|
|
|
|
|
handler.removeCallbacks(procQuery)
|
2023-01-12 22:17:34 +01:00
|
|
|
|
views.etSearch.setText("")
|
|
|
|
|
views.etSearch.hideKeyboard()
|
2021-05-22 00:03:16 +02:00
|
|
|
|
load(lastSection, null)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun load(section: AppSettingItem?, query: String?) {
|
2023-01-12 22:17:34 +01:00
|
|
|
|
adapter.items = buildList {
|
|
|
|
|
var lastPath: String? = null
|
|
|
|
|
fun addParentPath(item: AppSettingItem) {
|
|
|
|
|
add(divider)
|
|
|
|
|
|
|
|
|
|
val pathList = ArrayList<String>()
|
|
|
|
|
var parent = item.parent
|
|
|
|
|
while (parent != null) {
|
|
|
|
|
if (parent.caption != 0) pathList.add(0, getString(parent.caption))
|
|
|
|
|
parent = parent.parent
|
|
|
|
|
}
|
|
|
|
|
val path = pathList.joinToString("/")
|
|
|
|
|
if (path != lastPath) {
|
|
|
|
|
lastPath = path
|
|
|
|
|
add(path)
|
|
|
|
|
add(divider)
|
|
|
|
|
}
|
2021-05-22 00:03:16 +02:00
|
|
|
|
}
|
|
|
|
|
|
2023-01-12 22:17:34 +01:00
|
|
|
|
when {
|
|
|
|
|
// 検索キーワードあり
|
|
|
|
|
query?.isNotBlank() == true -> {
|
|
|
|
|
lastQuery = query
|
|
|
|
|
fun scanGroup(level: Int, item: AppSettingItem) {
|
|
|
|
|
if (item.caption == 0) return
|
|
|
|
|
if (item.type != SettingType.Section) {
|
2023-02-10 20:33:51 +01:00
|
|
|
|
when (item.type) {
|
|
|
|
|
SettingType.Group -> {
|
2023-02-11 10:25:17 +01:00
|
|
|
|
var caption = getString(item.caption)
|
2023-02-10 20:33:51 +01:00
|
|
|
|
var match = caption.contains(query, ignoreCase = true)
|
|
|
|
|
// log.d("group match=$match caption=$caption")
|
|
|
|
|
for (child in item.items) {
|
|
|
|
|
if (child.caption == 0) continue
|
|
|
|
|
caption = getString(child.caption)
|
2023-02-11 10:25:17 +01:00
|
|
|
|
match = caption.contains(query, ignoreCase = true)
|
2023-02-10 20:33:51 +01:00
|
|
|
|
// log.d("group.item match=$match caption=$caption")
|
|
|
|
|
if (match) break
|
2023-01-12 22:17:34 +01:00
|
|
|
|
}
|
2023-02-10 20:33:51 +01:00
|
|
|
|
if (match) {
|
|
|
|
|
// put entire group
|
|
|
|
|
addParentPath(item)
|
|
|
|
|
add(item)
|
|
|
|
|
addAll(item.items)
|
|
|
|
|
}
|
|
|
|
|
return
|
2023-01-12 22:17:34 +01:00
|
|
|
|
}
|
2023-05-03 18:20:11 +02:00
|
|
|
|
|
2023-02-10 20:33:51 +01:00
|
|
|
|
else -> {
|
2023-02-11 10:25:17 +01:00
|
|
|
|
val caption = getString(item.caption)
|
2023-02-10 20:33:51 +01:00
|
|
|
|
val match = caption.contains(query, ignoreCase = true)
|
|
|
|
|
// log.d("item match=$match caption=$caption")
|
|
|
|
|
if (match) {
|
|
|
|
|
addParentPath(item)
|
|
|
|
|
add(item)
|
|
|
|
|
}
|
2023-01-12 22:17:34 +01:00
|
|
|
|
}
|
2021-05-22 00:03:16 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
2023-01-12 22:17:34 +01:00
|
|
|
|
for (child in item.items) {
|
|
|
|
|
scanGroup(level + 1, child)
|
|
|
|
|
}
|
2021-05-22 00:03:16 +02:00
|
|
|
|
}
|
2023-01-12 22:17:34 +01:00
|
|
|
|
scanGroup(0, appSettingRoot)
|
2021-05-22 00:03:16 +02:00
|
|
|
|
}
|
2023-01-12 22:17:34 +01:00
|
|
|
|
// show root page
|
|
|
|
|
section == null -> {
|
|
|
|
|
val root = appSettingRoot
|
|
|
|
|
lastQuery = null
|
|
|
|
|
lastSection = null
|
|
|
|
|
for (child in root.items) {
|
|
|
|
|
add(divider)
|
|
|
|
|
add(child)
|
|
|
|
|
}
|
2021-05-22 00:03:16 +02:00
|
|
|
|
}
|
2023-01-12 22:17:34 +01:00
|
|
|
|
// show section page
|
|
|
|
|
else -> {
|
|
|
|
|
lastSection = section
|
|
|
|
|
lastQuery = null
|
|
|
|
|
fun scanGroup(level: Int, parent: AppSettingItem?) {
|
|
|
|
|
parent ?: return
|
|
|
|
|
for (item in parent.items) {
|
|
|
|
|
add(divider)
|
|
|
|
|
add(item)
|
|
|
|
|
if (item.items.isNotEmpty()) {
|
|
|
|
|
if (item.type == SettingType.Group) {
|
|
|
|
|
addAll(item.items)
|
|
|
|
|
} else {
|
|
|
|
|
scanGroup(level + 1, item)
|
|
|
|
|
}
|
2021-05-22 00:03:16 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-01-12 22:17:34 +01:00
|
|
|
|
scanGroup(0, section.cast())
|
2021-05-22 00:03:16 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
2023-01-12 22:17:34 +01:00
|
|
|
|
if (isNotEmpty()) add(divider)
|
2021-05-22 00:03:16 +02:00
|
|
|
|
}
|
2023-01-12 22:17:34 +01:00
|
|
|
|
views.lvList.scrollToPosition(0)
|
2021-05-22 00:03:16 +02:00
|
|
|
|
}
|
|
|
|
|
|
2023-01-12 22:17:34 +01:00
|
|
|
|
private fun dip(dp: Float): Int =
|
|
|
|
|
(resources.displayMetrics.density * dp + 0.5f).toInt()
|
2021-05-22 00:03:16 +02:00
|
|
|
|
|
2023-01-12 22:17:34 +01:00
|
|
|
|
private fun dip(dp: Int): Int = dip(dp.toFloat())
|
2021-05-22 00:03:16 +02:00
|
|
|
|
|
2023-01-12 22:17:34 +01:00
|
|
|
|
override fun onDialogDismissed(dialogId: Int) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override fun onColorSelected(dialogId: Int, @ColorInt newColor: Int) {
|
|
|
|
|
val colorTarget = this.colorTarget ?: return
|
|
|
|
|
val ip: IntPref = colorTarget.pref.cast() ?: error("$colorTarget has no in pref")
|
|
|
|
|
val c = when (colorTarget.type) {
|
|
|
|
|
SettingType.ColorAlpha -> newColor.notZero() ?: 1
|
|
|
|
|
else -> newColor or Color.BLACK
|
|
|
|
|
}
|
2023-02-04 21:52:26 +01:00
|
|
|
|
ip.value = c
|
2023-02-10 13:19:55 +01:00
|
|
|
|
val vh = findItemViewHolder(colorTarget)
|
|
|
|
|
vh?.showColor()
|
2023-01-12 22:17:34 +01:00
|
|
|
|
colorTarget.changed(this)
|
|
|
|
|
}
|
|
|
|
|
|
2023-02-10 13:19:55 +01:00
|
|
|
|
private val settingHolderList =
|
|
|
|
|
ConcurrentHashMap<Int, VhSettingItem>()
|
|
|
|
|
|
2023-01-12 22:17:34 +01:00
|
|
|
|
inner class MyAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
|
|
|
|
var items: List<Any> = emptyList()
|
|
|
|
|
set(newItems) {
|
|
|
|
|
val oldItems = field
|
|
|
|
|
field = newItems
|
|
|
|
|
DiffUtil.calculateDiff(object : DiffUtil.Callback() {
|
|
|
|
|
override fun getOldListSize() = oldItems.size
|
|
|
|
|
override fun getNewListSize() = newItems.size
|
|
|
|
|
override fun areItemsTheSame(
|
|
|
|
|
oldItemPosition: Int,
|
|
|
|
|
newItemPosition: Int,
|
|
|
|
|
) = oldItems.elementAtOrNull(oldItemPosition) == newItems.elementAtOrNull(
|
2023-01-13 13:22:25 +01:00
|
|
|
|
newItemPosition
|
|
|
|
|
)
|
2023-01-12 22:17:34 +01:00
|
|
|
|
|
|
|
|
|
override fun areContentsTheSame(
|
|
|
|
|
oldItemPosition: Int,
|
|
|
|
|
newItemPosition: Int,
|
|
|
|
|
) = oldItems.elementAtOrNull(oldItemPosition) == newItems.elementAtOrNull(
|
2023-01-13 13:22:25 +01:00
|
|
|
|
newItemPosition
|
|
|
|
|
)
|
2023-01-12 22:17:34 +01:00
|
|
|
|
}, true).dispatchUpdatesTo(this)
|
2021-05-22 00:03:16 +02:00
|
|
|
|
}
|
|
|
|
|
|
2023-01-12 22:17:34 +01:00
|
|
|
|
override fun getItemCount() = items.size
|
2021-05-22 00:03:16 +02:00
|
|
|
|
|
2023-01-12 22:17:34 +01:00
|
|
|
|
override fun getItemViewType(position: Int) =
|
|
|
|
|
when (val item = items.elementAtOrNull(position)) {
|
|
|
|
|
divider -> SettingType.Divider.id
|
|
|
|
|
is String -> SettingType.Path.id
|
|
|
|
|
is AppSettingItem -> item.type.id
|
2021-06-20 15:12:25 +02:00
|
|
|
|
else -> error("can't generate view for type $item")
|
2021-05-22 00:03:16 +02:00
|
|
|
|
}
|
|
|
|
|
|
2023-01-12 22:17:34 +01:00
|
|
|
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
|
|
|
|
|
when (SettingType.map[viewType]) {
|
|
|
|
|
SettingType.Divider -> VhDivider()
|
|
|
|
|
SettingType.Path -> VhPath(parent)
|
|
|
|
|
else -> VhSettingItem(this@ActAppSetting, parent)
|
|
|
|
|
}
|
2021-05-22 00:03:16 +02:00
|
|
|
|
|
2023-02-10 13:19:55 +01:00
|
|
|
|
override fun onViewRecycled(holder: RecyclerView.ViewHolder) {
|
|
|
|
|
super.onViewRecycled(holder)
|
|
|
|
|
// 古い紐付けを削除
|
|
|
|
|
settingHolderList.entries.filter {
|
|
|
|
|
when (it.value) {
|
|
|
|
|
holder -> true
|
|
|
|
|
else -> false
|
|
|
|
|
}
|
|
|
|
|
}.forEach { settingHolderList.remove(it.key) }
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-12 22:17:34 +01:00
|
|
|
|
override fun onBindViewHolder(viewHolder: RecyclerView.ViewHolder, position: Int) {
|
|
|
|
|
when (val item = items.elementAtOrNull(position)) {
|
|
|
|
|
divider -> viewHolder.cast<VhDivider>()
|
|
|
|
|
is String -> viewHolder.cast<VhPath>()?.bind(item)
|
|
|
|
|
is AppSettingItem -> if (viewHolder is VhSettingItem) {
|
|
|
|
|
viewHolder.bind(item)
|
|
|
|
|
// 古い紐付けを削除
|
|
|
|
|
settingHolderList.entries.filter {
|
2023-02-10 13:19:55 +01:00
|
|
|
|
when (it.value) {
|
|
|
|
|
viewHolder -> true
|
2023-01-12 22:17:34 +01:00
|
|
|
|
else -> false
|
|
|
|
|
}
|
|
|
|
|
}.forEach { settingHolderList.remove(it.key) }
|
|
|
|
|
// 新しい紐付けを覚える
|
2023-02-10 13:19:55 +01:00
|
|
|
|
settingHolderList[item.id] = viewHolder
|
2023-01-12 22:17:34 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-02-10 13:19:55 +01:00
|
|
|
|
fun findVhSetting(item: AppSettingItem) =
|
|
|
|
|
settingHolderList[item.id]
|
2023-01-12 22:17:34 +01:00
|
|
|
|
}
|
2021-05-22 00:03:16 +02:00
|
|
|
|
|
2023-01-12 22:17:34 +01:00
|
|
|
|
private inner class VhDivider(
|
|
|
|
|
viewRoot: FrameLayout = FrameLayout(this@ActAppSetting).apply {
|
|
|
|
|
layoutParams = RecyclerView.LayoutParams(
|
|
|
|
|
RecyclerView.LayoutParams.MATCH_PARENT,
|
|
|
|
|
RecyclerView.LayoutParams.WRAP_CONTENT
|
2021-05-22 00:03:16 +02:00
|
|
|
|
)
|
|
|
|
|
addView(View(this@ActAppSetting).apply {
|
|
|
|
|
layoutParams = FrameLayout.LayoutParams(
|
2023-01-12 22:17:34 +01:00
|
|
|
|
FrameLayout.LayoutParams.MATCH_PARENT,
|
2021-05-22 00:03:16 +02:00
|
|
|
|
dip(1)
|
|
|
|
|
).apply {
|
2021-06-20 15:12:25 +02:00
|
|
|
|
val marginX = 0
|
|
|
|
|
val marginY = dip(6)
|
|
|
|
|
setMargins(marginX, marginY, marginX, marginY)
|
2021-05-22 00:03:16 +02:00
|
|
|
|
}
|
|
|
|
|
setBackgroundColor(context.attrColor(R.attr.colorSettingDivider))
|
|
|
|
|
})
|
2023-01-12 22:17:34 +01:00
|
|
|
|
},
|
|
|
|
|
) : RecyclerView.ViewHolder(viewRoot)
|
|
|
|
|
|
|
|
|
|
private inner class VhPath(
|
|
|
|
|
val parent: ViewGroup,
|
|
|
|
|
val viewRoot: MyTextView = MyTextView(this@ActAppSetting).apply {
|
|
|
|
|
layoutParams = RecyclerView.LayoutParams(
|
|
|
|
|
RecyclerView.LayoutParams.MATCH_PARENT,
|
|
|
|
|
RecyclerView.LayoutParams.WRAP_CONTENT
|
|
|
|
|
)
|
|
|
|
|
val padX = 0
|
|
|
|
|
val padY = dip(3)
|
|
|
|
|
setTypeface(typeface, Typeface.BOLD)
|
|
|
|
|
setPaddingRelative(padX, padY, padX, padY)
|
|
|
|
|
},
|
|
|
|
|
) : RecyclerView.ViewHolder(viewRoot) {
|
|
|
|
|
fun bind(path: String) {
|
|
|
|
|
viewRoot.text = path
|
2021-05-22 00:03:16 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-12 22:17:34 +01:00
|
|
|
|
// not private
|
|
|
|
|
class VhSettingItem(
|
2023-01-12 22:38:14 +01:00
|
|
|
|
private val actAppSetting: ActAppSetting,
|
2023-01-12 22:17:34 +01:00
|
|
|
|
parent: ViewGroup,
|
2023-01-12 22:38:14 +01:00
|
|
|
|
val views: LvSettingItemBinding = LvSettingItemBinding
|
|
|
|
|
.inflate(actAppSetting.layoutInflater, parent, false),
|
2023-01-12 22:17:34 +01:00
|
|
|
|
) : RecyclerView.ViewHolder(views.root),
|
2021-05-22 00:03:16 +02:00
|
|
|
|
TextWatcher,
|
|
|
|
|
AdapterView.OnItemSelectedListener,
|
|
|
|
|
CompoundButton.OnCheckedChangeListener {
|
|
|
|
|
|
2023-01-12 22:38:14 +01:00
|
|
|
|
init {
|
|
|
|
|
views.checkBox.setOnCheckedChangeListener(this)
|
|
|
|
|
views.swSwitch.setOnCheckedChangeListener(this)
|
|
|
|
|
views.spSpinner.onItemSelectedListener = this
|
|
|
|
|
views.etEditText.addTextChangedListener(this)
|
2023-01-16 06:58:23 +01:00
|
|
|
|
|
|
|
|
|
// https://stackoverflow.com/questions/13614101/fatal-crash-focus-search-returned-a-view-that-wasnt-able-to-take-focus
|
2023-01-17 13:42:47 +01:00
|
|
|
|
views.etEditText.setOnEditorActionListener(OnEditorActionListener { textView, actionId, _ ->
|
2023-01-16 06:58:23 +01:00
|
|
|
|
if (actionId == EditorInfo.IME_ACTION_NEXT) {
|
|
|
|
|
@Suppress("WrongConstant")
|
|
|
|
|
textView.focusSearch(FOCUS_FORWARD)?.requestFocus(FOCUS_FORWARD)
|
|
|
|
|
// 結果に関わらずこのアクションを処理したとみなす
|
|
|
|
|
return@OnEditorActionListener true
|
|
|
|
|
}
|
|
|
|
|
false
|
|
|
|
|
})
|
2023-01-12 22:38:14 +01:00
|
|
|
|
}
|
2021-05-22 00:03:16 +02:00
|
|
|
|
|
2023-01-12 22:17:34 +01:00
|
|
|
|
private val tvDesc = views.tvDesc
|
|
|
|
|
private val tvError = views.tvError
|
2021-05-22 00:03:16 +02:00
|
|
|
|
|
|
|
|
|
var item: AppSettingItem? = null
|
|
|
|
|
|
|
|
|
|
private var bindingBusy = false
|
|
|
|
|
|
|
|
|
|
fun bind(item: AppSettingItem) {
|
|
|
|
|
bindingBusy = true
|
|
|
|
|
try {
|
|
|
|
|
this.item = item
|
|
|
|
|
|
2023-01-12 22:17:34 +01:00
|
|
|
|
views.tvCaption.vg(false)
|
2023-01-12 22:38:14 +01:00
|
|
|
|
views.btnAction.vg(false)
|
|
|
|
|
views.checkBox.vg(false)
|
|
|
|
|
views.swSwitch.vg(false)
|
|
|
|
|
views.llExtra.vg(false)
|
|
|
|
|
views.textView1.vg(false)
|
|
|
|
|
views.llButtonBar.vg(false)
|
|
|
|
|
views.vColor.vg(false)
|
|
|
|
|
views.spSpinner.vg(false)
|
|
|
|
|
views.etEditText.vg(false)
|
|
|
|
|
views.tvDesc.vg(false)
|
|
|
|
|
views.tvError.vg(false)
|
|
|
|
|
|
|
|
|
|
val name = if (item.caption == 0) "" else actAppSetting.getString(item.caption)
|
|
|
|
|
|
|
|
|
|
tvDesc.vg(item.desc != 0)?.run {
|
|
|
|
|
text = context.getString(item.desc)
|
2021-05-22 00:03:16 +02:00
|
|
|
|
if (item.descClickSet) {
|
2023-01-12 22:38:14 +01:00
|
|
|
|
background = ContextCompat.getDrawable(
|
|
|
|
|
context,
|
2021-05-22 00:03:16 +02:00
|
|
|
|
R.drawable.btn_bg_transparent_round6dp
|
|
|
|
|
)
|
2023-01-12 22:38:14 +01:00
|
|
|
|
setOnClickListener { item.descClick.invoke(actAppSetting) }
|
2021-05-22 00:03:16 +02:00
|
|
|
|
} else {
|
2023-01-12 22:38:14 +01:00
|
|
|
|
background = null
|
|
|
|
|
setOnClickListener(null)
|
|
|
|
|
isClickable = false
|
2021-05-22 00:03:16 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
when (item.type) {
|
|
|
|
|
|
2023-01-12 22:38:14 +01:00
|
|
|
|
SettingType.Section -> views.btnAction.vg(true)?.run {
|
|
|
|
|
text = name
|
|
|
|
|
isEnabledAlpha = item.enabled
|
|
|
|
|
setOnClickListener {
|
|
|
|
|
actAppSetting.load(item.cast()!!, null)
|
2021-05-22 00:03:16 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-12 22:38:14 +01:00
|
|
|
|
SettingType.Action -> views.btnAction.vg(true)?.run {
|
|
|
|
|
text = name
|
|
|
|
|
isEnabledAlpha = item.enabled
|
|
|
|
|
setOnClickListener { item.action(actAppSetting) }
|
2021-05-22 00:03:16 +02:00
|
|
|
|
}
|
|
|
|
|
|
2023-01-12 22:38:14 +01:00
|
|
|
|
SettingType.CheckBox -> views.checkBox.run {
|
|
|
|
|
val bp: BooleanPref = item.pref.cast()
|
|
|
|
|
?: error("$name has no boolean pref")
|
|
|
|
|
vg(false) // skip animation
|
|
|
|
|
text = name
|
|
|
|
|
isEnabledAlpha = item.enabled
|
2023-02-04 21:52:26 +01:00
|
|
|
|
isChecked = bp.value
|
2023-01-12 22:38:14 +01:00
|
|
|
|
vg(true)
|
2021-05-22 00:03:16 +02:00
|
|
|
|
}
|
|
|
|
|
|
2023-01-12 22:38:14 +01:00
|
|
|
|
SettingType.Switch -> views.swSwitch.run {
|
|
|
|
|
val bp: BooleanPref = item.pref.cast()
|
|
|
|
|
?: error("$name has no boolean pref")
|
2021-05-22 00:03:16 +02:00
|
|
|
|
showCaption(name)
|
2023-01-12 22:38:14 +01:00
|
|
|
|
vg(false) // skip animation
|
|
|
|
|
actAppSetting.setSwitchColor(views.swSwitch)
|
|
|
|
|
isEnabledAlpha = item.enabled
|
2023-02-04 21:52:26 +01:00
|
|
|
|
isChecked = bp.value
|
2023-01-12 22:38:14 +01:00
|
|
|
|
vg(true)
|
2021-05-22 00:03:16 +02:00
|
|
|
|
}
|
|
|
|
|
|
2023-01-12 22:38:14 +01:00
|
|
|
|
SettingType.Group -> showCaption(name)
|
2021-05-22 00:03:16 +02:00
|
|
|
|
|
2023-01-12 22:38:14 +01:00
|
|
|
|
SettingType.Sample -> views.llExtra.run {
|
|
|
|
|
vg(true)
|
|
|
|
|
removeAllViews()
|
|
|
|
|
actAppSetting.layoutInflater.inflate(
|
|
|
|
|
item.sampleLayoutId,
|
|
|
|
|
views.llExtra,
|
|
|
|
|
true
|
|
|
|
|
)
|
|
|
|
|
item.sampleUpdate(actAppSetting, this)
|
2021-05-22 00:03:16 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SettingType.ColorAlpha, SettingType.ColorOpaque -> {
|
|
|
|
|
val ip = item.pref.cast<IntPref>() ?: error("$name has no int pref")
|
|
|
|
|
showCaption(name)
|
2023-01-12 22:38:14 +01:00
|
|
|
|
views.llButtonBar.vg(true)
|
|
|
|
|
views.vColor.vg(true)
|
2023-02-04 21:52:26 +01:00
|
|
|
|
views.vColor.setBackgroundColor(ip.value)
|
2023-01-12 22:38:14 +01:00
|
|
|
|
views.btnEdit.isEnabledAlpha = item.enabled
|
|
|
|
|
views.btnReset.isEnabledAlpha = item.enabled
|
|
|
|
|
views.btnEdit.setOnClickListener {
|
|
|
|
|
actAppSetting.colorTarget = item
|
2023-02-04 21:52:26 +01:00
|
|
|
|
val color = ip.value
|
2021-05-22 00:03:16 +02:00
|
|
|
|
val builder = ColorPickerDialog.newBuilder()
|
|
|
|
|
.setDialogType(ColorPickerDialog.TYPE_CUSTOM)
|
|
|
|
|
.setAllowPresets(true)
|
|
|
|
|
.setShowAlphaSlider(item.type == SettingType.ColorAlpha)
|
|
|
|
|
.setDialogId(COLOR_DIALOG_ID)
|
|
|
|
|
if (color != 0) builder.setColor(color)
|
2023-01-12 22:38:14 +01:00
|
|
|
|
builder.show(actAppSetting)
|
2021-05-22 00:03:16 +02:00
|
|
|
|
}
|
2023-01-12 22:38:14 +01:00
|
|
|
|
views.btnReset.setOnClickListener {
|
2023-02-04 21:52:26 +01:00
|
|
|
|
ip.removeValue()
|
2021-05-22 00:03:16 +02:00
|
|
|
|
showColor()
|
2023-01-12 22:38:14 +01:00
|
|
|
|
item.changed.invoke(actAppSetting)
|
2021-05-22 00:03:16 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SettingType.Spinner -> {
|
|
|
|
|
showCaption(name)
|
|
|
|
|
|
2023-01-12 22:38:14 +01:00
|
|
|
|
views.spSpinner.vg(true)
|
|
|
|
|
views.spSpinner.isEnabledAlpha = item.enabled
|
2021-05-22 00:03:16 +02:00
|
|
|
|
|
|
|
|
|
val pi = item.pref
|
|
|
|
|
if (pi is IntPref) {
|
|
|
|
|
// 整数型の設定のSpinnerは全て選択肢を単純に覚える
|
|
|
|
|
val argsInt = item.spinnerArgs
|
2023-01-12 22:38:14 +01:00
|
|
|
|
actAppSetting.initSpinner(
|
|
|
|
|
views.spSpinner,
|
|
|
|
|
argsInt?.map { actAppSetting.getString(it) }
|
|
|
|
|
?: item.spinnerArgsProc(actAppSetting)
|
|
|
|
|
)
|
2023-02-04 21:52:26 +01:00
|
|
|
|
views.spSpinner.setSelection(pi.value)
|
2021-05-22 00:03:16 +02:00
|
|
|
|
} else {
|
2023-01-12 22:38:14 +01:00
|
|
|
|
item.spinnerInitializer.invoke(actAppSetting, views.spSpinner)
|
2021-05-22 00:03:16 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SettingType.EditText -> {
|
|
|
|
|
showCaption(name)
|
2023-01-16 06:58:23 +01:00
|
|
|
|
views.etEditText.vg(true)?.let { etEditText ->
|
|
|
|
|
val text = when (val pi = item.pref) {
|
|
|
|
|
is FloatPref ->
|
2023-02-04 21:52:26 +01:00
|
|
|
|
item.fromFloat.invoke(actAppSetting, pi.value)
|
2023-05-03 18:20:11 +02:00
|
|
|
|
|
2023-01-16 06:58:23 +01:00
|
|
|
|
is StringPref ->
|
2023-02-04 21:52:26 +01:00
|
|
|
|
pi.value
|
2023-05-03 18:20:11 +02:00
|
|
|
|
|
2023-01-16 06:58:23 +01:00
|
|
|
|
else -> error("EditText has incorrect pref $pi")
|
|
|
|
|
}
|
2021-05-22 00:03:16 +02:00
|
|
|
|
|
2023-01-16 06:58:23 +01:00
|
|
|
|
etEditText.hint = item.hint ?: ""
|
|
|
|
|
etEditText.inputType = item.inputType
|
|
|
|
|
etEditText.setText(text)
|
|
|
|
|
etEditText.setSelection(0, text.length)
|
|
|
|
|
}
|
2021-05-22 00:03:16 +02:00
|
|
|
|
updateErrorView()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SettingType.TextWithSelector -> {
|
|
|
|
|
showCaption(name)
|
2023-01-12 22:38:14 +01:00
|
|
|
|
views.llButtonBar.vg(true)
|
|
|
|
|
views.vColor.vg(false)
|
|
|
|
|
views.textView1.vg(true)
|
2021-05-22 00:03:16 +02:00
|
|
|
|
|
2023-01-12 22:38:14 +01:00
|
|
|
|
item.showTextView.invoke(actAppSetting, views.textView1)
|
2021-05-22 00:03:16 +02:00
|
|
|
|
|
2023-01-12 22:38:14 +01:00
|
|
|
|
views.btnEdit.setOnClickListener {
|
|
|
|
|
item.onClickEdit.invoke(actAppSetting)
|
2021-05-22 00:03:16 +02:00
|
|
|
|
}
|
2023-01-12 22:38:14 +01:00
|
|
|
|
views.btnReset.setOnClickListener {
|
|
|
|
|
item.onClickReset.invoke(actAppSetting)
|
2021-05-22 00:03:16 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
else -> error("unknown type ${item.type}")
|
|
|
|
|
}
|
|
|
|
|
} finally {
|
|
|
|
|
bindingBusy = false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun showCaption(caption: String) {
|
|
|
|
|
if (caption.isNotEmpty()) {
|
2023-01-12 22:17:34 +01:00
|
|
|
|
views.tvCaption.vg(true)?.text = caption
|
2021-05-22 00:03:16 +02:00
|
|
|
|
updateCaption()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fun updateCaption() {
|
|
|
|
|
val item = item ?: return
|
|
|
|
|
val key = item.pref?.key ?: return
|
|
|
|
|
|
2023-01-12 22:17:34 +01:00
|
|
|
|
val sample = views.tvCaption
|
2023-01-12 22:38:14 +01:00
|
|
|
|
var defaultExtra = actAppSetting.defaultLineSpacingExtra[key]
|
2021-05-22 00:03:16 +02:00
|
|
|
|
if (defaultExtra == null) {
|
|
|
|
|
defaultExtra = sample.lineSpacingExtra
|
2023-01-12 22:38:14 +01:00
|
|
|
|
actAppSetting.defaultLineSpacingExtra[key] = defaultExtra
|
2021-05-22 00:03:16 +02:00
|
|
|
|
}
|
2023-01-12 22:38:14 +01:00
|
|
|
|
var defaultMultiplier = actAppSetting.defaultLineSpacingMultiplier[key]
|
2021-05-22 00:03:16 +02:00
|
|
|
|
if (defaultMultiplier == null) {
|
|
|
|
|
defaultMultiplier = sample.lineSpacingMultiplier
|
2023-01-12 22:38:14 +01:00
|
|
|
|
actAppSetting.defaultLineSpacingMultiplier[key] = defaultMultiplier
|
2021-05-22 00:03:16 +02:00
|
|
|
|
}
|
|
|
|
|
|
2023-01-12 22:38:14 +01:00
|
|
|
|
val size = item.captionFontSize.invoke(actAppSetting)
|
2021-05-22 00:03:16 +02:00
|
|
|
|
if (size != null) sample.textSize = size
|
|
|
|
|
|
2023-01-12 22:38:14 +01:00
|
|
|
|
val spacing = item.captionSpacing.invoke(actAppSetting)
|
2021-05-22 00:03:16 +02:00
|
|
|
|
if (spacing == null || !spacing.isFinite()) {
|
|
|
|
|
sample.setLineSpacing(defaultExtra, defaultMultiplier)
|
|
|
|
|
} else {
|
|
|
|
|
sample.setLineSpacing(0f, spacing)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun updateErrorView() {
|
|
|
|
|
val item = item ?: return
|
2023-01-12 22:38:14 +01:00
|
|
|
|
val sv = views.etEditText.text.toString()
|
|
|
|
|
val error = item.getError.invoke(actAppSetting, sv)
|
2021-05-22 00:03:16 +02:00
|
|
|
|
tvError.vg(error != null)?.text = error
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fun showColor() {
|
|
|
|
|
val item = item ?: return
|
|
|
|
|
val ip = item.pref.cast<IntPref>() ?: return
|
2023-02-04 21:52:26 +01:00
|
|
|
|
val c = ip.value
|
2023-01-12 22:38:14 +01:00
|
|
|
|
views.vColor.setBackgroundColor(c)
|
2021-05-22 00:03:16 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override fun afterTextChanged(p0: Editable?) {
|
|
|
|
|
if (bindingBusy) return
|
|
|
|
|
val item = item ?: return
|
|
|
|
|
|
|
|
|
|
val sv = item.filter.invoke(p0?.toString() ?: "")
|
|
|
|
|
|
|
|
|
|
when (val pi = item.pref) {
|
2023-02-04 21:52:26 +01:00
|
|
|
|
is StringPref -> pi.value = sv
|
2021-05-22 00:03:16 +02:00
|
|
|
|
|
|
|
|
|
is FloatPref -> {
|
2023-01-12 22:38:14 +01:00
|
|
|
|
val fv = item.toFloat.invoke(actAppSetting, sv)
|
2021-05-22 00:03:16 +02:00
|
|
|
|
if (fv.isFinite()) {
|
2023-02-04 21:52:26 +01:00
|
|
|
|
pi.value = fv
|
2021-05-22 00:03:16 +02:00
|
|
|
|
} else {
|
2023-02-04 21:52:26 +01:00
|
|
|
|
pi.removeValue()
|
2021-05-22 00:03:16 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
else -> {
|
|
|
|
|
error("not FloatPref or StringPref")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-12 22:38:14 +01:00
|
|
|
|
item.changed.invoke(actAppSetting)
|
2021-05-22 00:03:16 +02:00
|
|
|
|
updateErrorView()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override fun onNothingSelected(v: AdapterView<*>?) = Unit
|
|
|
|
|
|
|
|
|
|
override fun onItemSelected(
|
|
|
|
|
parent: AdapterView<*>?,
|
|
|
|
|
view: View?,
|
|
|
|
|
position: Int,
|
2021-06-20 15:12:25 +02:00
|
|
|
|
id: Long,
|
2021-05-22 00:03:16 +02:00
|
|
|
|
) {
|
|
|
|
|
if (bindingBusy) return
|
|
|
|
|
val item = item ?: return
|
|
|
|
|
when (val pi = item.pref) {
|
2023-02-04 21:52:26 +01:00
|
|
|
|
is IntPref -> pi.value = views.spSpinner.selectedItemPosition
|
2023-01-12 22:38:14 +01:00
|
|
|
|
else -> item.spinnerOnSelected.invoke(actAppSetting, views.spSpinner, position)
|
2021-05-22 00:03:16 +02:00
|
|
|
|
}
|
2023-01-12 22:38:14 +01:00
|
|
|
|
item.changed.invoke(actAppSetting)
|
2021-05-22 00:03:16 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override fun onCheckedChanged(v: CompoundButton?, isChecked: Boolean) {
|
|
|
|
|
if (bindingBusy) return
|
|
|
|
|
val item = item ?: return
|
|
|
|
|
when (val pi = item.pref) {
|
2023-02-04 21:52:26 +01:00
|
|
|
|
is BooleanPref -> pi.value = isChecked
|
2021-05-22 00:03:16 +02:00
|
|
|
|
else -> error("CompoundButton has no booleanPref $pi")
|
|
|
|
|
}
|
2023-01-12 22:38:14 +01:00
|
|
|
|
item.changed.invoke(actAppSetting)
|
2021-05-22 00:03:16 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun initSpinner(spinner: Spinner, captions: List<String>) {
|
|
|
|
|
spinner.adapter = ArrayAdapter(
|
|
|
|
|
this,
|
|
|
|
|
android.R.layout.simple_spinner_item,
|
|
|
|
|
captions.toTypedArray()
|
|
|
|
|
).apply {
|
|
|
|
|
setDropDownViewResource(R.layout.lv_spinner_dropdown)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
|
|
@Suppress("BlockingMethodInNonBlockingContext")
|
2023-05-03 18:20:11 +02:00
|
|
|
|
fun sendAppData() {
|
2021-05-27 04:15:59 +02:00
|
|
|
|
val activity = this
|
2022-07-20 06:27:19 +02:00
|
|
|
|
launchProgress(
|
|
|
|
|
"export app data",
|
2023-05-03 18:20:11 +02:00
|
|
|
|
doInBackground = { encodeAppData() },
|
|
|
|
|
afterProc = {
|
|
|
|
|
try {
|
|
|
|
|
val uri =
|
|
|
|
|
FileProvider.getUriForFile(activity, FILE_PROVIDER_AUTHORITY, it)
|
|
|
|
|
Intent(Intent.ACTION_SEND).apply {
|
|
|
|
|
type = contentResolver.getType(uri)
|
|
|
|
|
putExtra(Intent.EXTRA_SUBJECT, "SubwayTooter app data")
|
|
|
|
|
putExtra(Intent.EXTRA_STREAM, uri)
|
|
|
|
|
addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION or Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
|
|
|
|
}.launch(arNoop)
|
|
|
|
|
} catch (ex: Throwable) {
|
|
|
|
|
log.e(ex, "exportAppData failed.")
|
|
|
|
|
dialogOrToast(ex.withCaption(getString(R.string.missing_app_can_receive_action_send)))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
}
|
2022-07-20 06:27:19 +02:00
|
|
|
|
|
2023-05-03 18:20:11 +02:00
|
|
|
|
fun saveAppData() {
|
|
|
|
|
try {
|
|
|
|
|
Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
|
|
|
|
|
addCategory(Intent.CATEGORY_OPENABLE)
|
|
|
|
|
type = "application/zip"
|
|
|
|
|
putExtra(Intent.EXTRA_TITLE, "SubwayTooter app data.zip")
|
|
|
|
|
}.launch(arSaveAppData)
|
|
|
|
|
} catch (ex: Throwable) {
|
|
|
|
|
log.e(ex, "can't find app that can handle ACTION_CREATE_DOCUMENT.")
|
|
|
|
|
dialogOrToast(ex.withCaption("can't find app that can handle ACTION_CREATE_DOCUMENT."))
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-07-20 06:27:19 +02:00
|
|
|
|
|
2023-05-03 18:20:11 +02:00
|
|
|
|
private val arSaveAppData = ActivityResultHandler(log) { r ->
|
|
|
|
|
launchAndShowError {
|
|
|
|
|
if (r.resultCode != RESULT_OK) return@launchAndShowError
|
|
|
|
|
val outUri = r.data?.data ?: error("missing result.data.data")
|
|
|
|
|
launchProgress(
|
|
|
|
|
"save app data",
|
|
|
|
|
doInBackground = {
|
|
|
|
|
val tempFile = encodeAppData()
|
2022-07-20 06:27:19 +02:00
|
|
|
|
try {
|
2023-05-03 18:20:11 +02:00
|
|
|
|
FileInputStream(tempFile).use { inStream ->
|
|
|
|
|
(contentResolver.openOutputStream(outUri)
|
|
|
|
|
?: error("contentResolver.openOutputStream returns null : $outUri"))
|
|
|
|
|
.use { inStream.copyTo(it) }
|
|
|
|
|
}
|
2022-07-20 06:27:19 +02:00
|
|
|
|
} finally {
|
2023-05-03 18:20:11 +02:00
|
|
|
|
tempFile.delete()
|
2022-07-20 06:27:19 +02:00
|
|
|
|
}
|
2021-05-22 00:03:16 +02:00
|
|
|
|
}
|
2023-05-03 18:20:11 +02:00
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-07-20 06:27:19 +02:00
|
|
|
|
|
2023-05-03 18:20:11 +02:00
|
|
|
|
/**
|
|
|
|
|
* アプリデータを一時ファイルに保存する
|
|
|
|
|
* -
|
|
|
|
|
*/
|
|
|
|
|
@WorkerThread
|
|
|
|
|
private fun encodeAppData(): File {
|
|
|
|
|
val activity = this
|
|
|
|
|
|
|
|
|
|
val cacheDir = externalCacheDir ?: cacheDir ?: error("missing cache directory")
|
|
|
|
|
cacheDir.mkdirs()
|
|
|
|
|
|
|
|
|
|
val name = "SubwayTooter.${android.os.Process.myPid()}.${android.os.Process.myTid()}.zip"
|
|
|
|
|
val file = File(cacheDir, name)
|
|
|
|
|
|
|
|
|
|
ZipOutputStream(FileOutputStream(file)).use { zipStream ->
|
|
|
|
|
zipStream.putNextEntry(ZipEntry("AppData.json"))
|
|
|
|
|
try {
|
|
|
|
|
val jw = JsonWriter(OutputStreamWriter(zipStream, "UTF-8"))
|
|
|
|
|
AppDataExporter.encodeAppData(activity, jw)
|
|
|
|
|
jw.flush()
|
|
|
|
|
} finally {
|
|
|
|
|
zipStream.closeEntry()
|
2022-07-20 06:27:19 +02:00
|
|
|
|
}
|
2023-05-03 18:20:11 +02:00
|
|
|
|
// カラム背景画像
|
|
|
|
|
val appState = App1.getAppState(activity)
|
|
|
|
|
for (column in appState.columnList) {
|
|
|
|
|
AppDataExporter.saveBackgroundImage(activity, zipStream, column)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return file
|
2021-05-22 00:03:16 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// open data picker
|
|
|
|
|
fun importAppData1() {
|
|
|
|
|
try {
|
|
|
|
|
val intent = intentOpenDocument("*/*")
|
|
|
|
|
arImportAppData.launch(intent)
|
|
|
|
|
} catch (ex: Throwable) {
|
|
|
|
|
showToast(ex, "importAppData(1) failed.")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// after data picked
|
|
|
|
|
private fun importAppData2(bConfirm: Boolean, uri: Uri) {
|
|
|
|
|
|
|
|
|
|
val type = contentResolver.getType(uri)
|
2021-06-13 13:48:48 +02:00
|
|
|
|
log.d("importAppData type=$type")
|
2021-05-22 00:03:16 +02:00
|
|
|
|
|
|
|
|
|
if (!bConfirm) {
|
|
|
|
|
AlertDialog.Builder(this)
|
|
|
|
|
.setMessage(getString(R.string.app_data_import_confirm))
|
|
|
|
|
.setNegativeButton(R.string.cancel, null)
|
|
|
|
|
.setPositiveButton(R.string.ok) { _, _ -> importAppData2(true, uri) }
|
|
|
|
|
.show()
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
val data = Intent()
|
|
|
|
|
data.data = uri
|
|
|
|
|
setResult(ActMain.RESULT_APP_DATA_IMPORT, data)
|
|
|
|
|
finish()
|
|
|
|
|
}
|
|
|
|
|
|
2023-02-10 13:19:55 +01:00
|
|
|
|
fun findItemViewHolder(item: AppSettingItem?) =
|
|
|
|
|
item?.let { adapter.findVhSetting(it) }
|
2021-05-22 00:03:16 +02:00
|
|
|
|
|
|
|
|
|
fun showSample(item: AppSettingItem?) {
|
|
|
|
|
item ?: error("showSample: missing item…")
|
|
|
|
|
findItemViewHolder(item)?.let {
|
2023-01-12 22:38:14 +01:00
|
|
|
|
item.sampleUpdate.invoke(this, it.views.llExtra)
|
2021-05-22 00:03:16 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-11-21 08:12:10 +01:00
|
|
|
|
// リスト内部のSwitchCompat全ての色を更新する
|
2023-01-12 22:17:34 +01:00
|
|
|
|
fun setSwitchColor() = setSwitchColor(views.lvList)
|
2021-05-22 00:03:16 +02:00
|
|
|
|
|
|
|
|
|
//////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
|
|
fun formatFontSize(fv: Float): String =
|
|
|
|
|
when {
|
|
|
|
|
fv.isFinite() -> String.format(defaultLocale(this), "%.1f", fv)
|
|
|
|
|
else -> ""
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fun parseFontSize(src: String): Float {
|
|
|
|
|
try {
|
|
|
|
|
if (src.isNotEmpty()) {
|
|
|
|
|
val f = NumberFormat.getInstance(defaultLocale(this)).parse(src)?.toFloat()
|
|
|
|
|
return when {
|
|
|
|
|
f == null -> Float.NaN
|
|
|
|
|
f.isNaN() -> Float.NaN
|
|
|
|
|
f < 0f -> 0f
|
|
|
|
|
f > 999f -> 999f
|
|
|
|
|
else -> f
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} catch (ex: Throwable) {
|
2022-12-27 03:54:52 +01:00
|
|
|
|
log.e(ex, "parseFontSize failed.")
|
2021-05-22 00:03:16 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return Float.NaN
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-12 22:17:34 +01:00
|
|
|
|
val defaultLineSpacingExtra = HashMap<String, Float>()
|
|
|
|
|
val defaultLineSpacingMultiplier = HashMap<String, Float>()
|
2021-05-22 00:03:16 +02:00
|
|
|
|
|
2021-05-27 04:15:59 +02:00
|
|
|
|
private fun handleFontResult(item: AppSettingItem?, data: Intent, fileName: String) {
|
2021-05-22 00:03:16 +02:00
|
|
|
|
item ?: error("handleFontResult : setting item is null")
|
|
|
|
|
data.handleGetContentResult(contentResolver).firstOrNull()?.uri?.let {
|
|
|
|
|
val file = saveTimelineFont(it, fileName)
|
|
|
|
|
if (file != null) {
|
2023-02-04 21:52:26 +01:00
|
|
|
|
(item.pref as? StringPref)?.value = file.absolutePath
|
2021-05-22 00:03:16 +02:00
|
|
|
|
showTimelineFont(item)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fun showTimelineFont(item: AppSettingItem?) {
|
|
|
|
|
item ?: return
|
|
|
|
|
val holder = findItemViewHolder(item) ?: return
|
2023-01-12 22:38:14 +01:00
|
|
|
|
item.showTextView.invoke(this, holder.views.textView1)
|
2021-05-22 00:03:16 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fun showTimelineFont(item: AppSettingItem, tv: TextView) {
|
|
|
|
|
try {
|
2023-02-04 21:52:26 +01:00
|
|
|
|
item.pref.cast<StringPref>()?.value.notEmpty()?.let { url ->
|
2021-05-22 00:03:16 +02:00
|
|
|
|
tv.typeface = Typeface.DEFAULT
|
2023-02-04 21:52:26 +01:00
|
|
|
|
val face = Typeface.createFromFile(url)
|
2021-05-22 00:03:16 +02:00
|
|
|
|
tv.typeface = face
|
2023-02-04 21:52:26 +01:00
|
|
|
|
tv.text = url
|
2021-05-22 00:03:16 +02:00
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
} catch (ex: Throwable) {
|
2022-12-27 03:54:52 +01:00
|
|
|
|
log.e(ex, "showTimelineFont failed.")
|
2021-05-22 00:03:16 +02:00
|
|
|
|
}
|
|
|
|
|
// fallback
|
|
|
|
|
tv.text = getString(R.string.not_selected)
|
|
|
|
|
tv.typeface = Typeface.DEFAULT
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-20 15:12:25 +02:00
|
|
|
|
private fun saveTimelineFont(uri: Uri?, fileName: String): File? {
|
2021-05-22 00:03:16 +02:00
|
|
|
|
try {
|
|
|
|
|
if (uri == null) {
|
|
|
|
|
showToast(false, "missing uri.")
|
|
|
|
|
return null
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
contentResolver.takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
|
|
|
|
|
|
|
|
|
val dir = filesDir
|
|
|
|
|
|
|
|
|
|
dir.mkdir()
|
|
|
|
|
|
2021-06-20 15:12:25 +02:00
|
|
|
|
val tmpFile = File(dir, "$fileName.tmp")
|
2021-05-22 00:03:16 +02:00
|
|
|
|
|
|
|
|
|
val source: InputStream? = contentResolver.openInputStream(uri)
|
|
|
|
|
if (source == null) {
|
2021-06-20 15:46:07 +02:00
|
|
|
|
showToast(false, "openInputStream returns null. uri=$uri")
|
2021-05-22 00:03:16 +02:00
|
|
|
|
return null
|
|
|
|
|
} else {
|
|
|
|
|
source.use { inStream ->
|
2021-06-20 15:12:25 +02:00
|
|
|
|
FileOutputStream(tmpFile).use { outStream ->
|
2022-03-13 13:05:54 +01:00
|
|
|
|
inStream.copyTo(outStream)
|
2021-05-22 00:03:16 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-20 15:12:25 +02:00
|
|
|
|
val face = Typeface.createFromFile(tmpFile)
|
2021-05-22 00:03:16 +02:00
|
|
|
|
if (face == null) {
|
|
|
|
|
showToast(false, "Typeface.createFromFile() failed.")
|
|
|
|
|
return null
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-20 15:12:25 +02:00
|
|
|
|
val file = File(dir, fileName)
|
|
|
|
|
if (!tmpFile.renameTo(file)) {
|
2021-05-22 00:03:16 +02:00
|
|
|
|
showToast(false, "File operation failed.")
|
|
|
|
|
return null
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return file
|
|
|
|
|
} catch (ex: Throwable) {
|
2022-12-27 03:54:52 +01:00
|
|
|
|
log.e(ex, "saveTimelineFont failed.")
|
2021-05-22 00:03:16 +02:00
|
|
|
|
showToast(ex, "saveTimelineFont failed.")
|
|
|
|
|
return null
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//////////////////////////////////////////////////////
|
|
|
|
|
|
2023-02-04 21:52:26 +01:00
|
|
|
|
inner class AccountAdapter(val list: List<SavedAccount>) : BaseAdapter() {
|
2021-05-22 00:03:16 +02:00
|
|
|
|
|
2023-02-10 11:50:20 +01:00
|
|
|
|
override fun getCount() = 1 + list.size
|
|
|
|
|
override fun getItemId(position: Int) = 0L
|
|
|
|
|
override fun getItem(position: Int) = if (position == 0) null else list[position - 1]
|
2021-05-22 00:03:16 +02:00
|
|
|
|
|
|
|
|
|
override fun getView(position: Int, viewOld: View?, parent: ViewGroup): View {
|
|
|
|
|
val view = viewOld ?: layoutInflater.inflate(
|
2023-02-10 11:50:20 +01:00
|
|
|
|
R.layout.lv_spinner_wrap_text,
|
2021-05-22 00:03:16 +02:00
|
|
|
|
parent,
|
|
|
|
|
false
|
|
|
|
|
)
|
2021-06-20 15:12:25 +02:00
|
|
|
|
view.findViewById<TextView>(android.R.id.text1).text = when (position) {
|
2023-02-10 11:50:20 +01:00
|
|
|
|
0 -> getString(R.string.default_post_account_default_action)
|
2023-02-04 21:52:26 +01:00
|
|
|
|
else -> daoAcctColor.getNickname(list[position - 1])
|
2021-06-20 15:12:25 +02:00
|
|
|
|
}
|
2021-05-22 00:03:16 +02:00
|
|
|
|
return view
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override fun getDropDownView(position: Int, viewOld: View?, parent: ViewGroup): View {
|
2021-11-08 10:47:01 +01:00
|
|
|
|
val view =
|
|
|
|
|
viewOld ?: layoutInflater.inflate(R.layout.lv_spinner_dropdown, parent, false)
|
2021-06-20 15:12:25 +02:00
|
|
|
|
view.findViewById<TextView>(android.R.id.text1).text = when (position) {
|
2023-02-10 11:50:20 +01:00
|
|
|
|
0 -> getString(R.string.default_post_account_default_action)
|
2023-02-04 21:52:26 +01:00
|
|
|
|
else -> daoAcctColor.getNickname(list[position - 1])
|
2021-06-20 15:12:25 +02:00
|
|
|
|
}
|
2021-05-22 00:03:16 +02:00
|
|
|
|
return view
|
|
|
|
|
}
|
|
|
|
|
|
2023-02-10 11:50:20 +01:00
|
|
|
|
/**
|
|
|
|
|
* 設定に保存したdbId から アダプターのインデクス値に変換
|
|
|
|
|
*/
|
|
|
|
|
|
2021-06-20 15:12:25 +02:00
|
|
|
|
// 見つからなければ0,見つかったら1以上
|
|
|
|
|
internal fun getIndexFromId(dbId: Long): Int =
|
|
|
|
|
1 + list.indexOfFirst { it.db_id == dbId }
|
2021-05-22 00:03:16 +02:00
|
|
|
|
|
2023-02-10 11:50:20 +01:00
|
|
|
|
/**
|
|
|
|
|
* アダプターのインデクス値から設定に保存するdbIdに変換
|
|
|
|
|
* - -1L : タブレットモードなら毎回尋ねる。スマホモードなら現在開いているカラム。
|
|
|
|
|
*/
|
2021-06-20 15:12:25 +02:00
|
|
|
|
internal fun getIdFromIndex(position: Int): Long =
|
|
|
|
|
if (position > 0) list[position - 1].db_id else -1L
|
2021-05-22 00:03:16 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private class Item(
|
|
|
|
|
val id: String,
|
|
|
|
|
val caption: String,
|
2021-06-20 15:12:25 +02:00
|
|
|
|
val offset: Int,
|
2021-05-22 00:03:16 +02:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
inner class TimeZoneAdapter internal constructor() : BaseAdapter() {
|
|
|
|
|
|
|
|
|
|
private val list = ArrayList<Item>()
|
|
|
|
|
|
|
|
|
|
init {
|
|
|
|
|
|
|
|
|
|
for (id in TimeZone.getAvailableIDs()) {
|
|
|
|
|
val tz = TimeZone.getTimeZone(id)
|
|
|
|
|
|
|
|
|
|
// GMT数字を指定するタイプのタイムゾーンは無視する。ただしGMT-12:00の1項目だけは残す
|
|
|
|
|
// 3文字のIDは曖昧な場合があるので非推奨
|
|
|
|
|
// '/' を含まないIDは列挙しない
|
|
|
|
|
if (!when {
|
|
|
|
|
!tz.id.contains('/') -> false
|
|
|
|
|
tz.id == "Etc/GMT+12" -> true
|
|
|
|
|
tz.id.startsWith("Etc/") -> false
|
|
|
|
|
else -> true
|
|
|
|
|
}
|
|
|
|
|
) continue
|
|
|
|
|
|
|
|
|
|
var offset = tz.rawOffset.toLong()
|
|
|
|
|
val caption = when (offset) {
|
2021-06-20 15:12:25 +02:00
|
|
|
|
0L -> "(UTC\u00B100:00) ${tz.id} ${tz.displayName}"
|
2021-05-22 00:03:16 +02:00
|
|
|
|
|
|
|
|
|
else -> {
|
|
|
|
|
|
2021-06-20 15:12:25 +02:00
|
|
|
|
val format = when {
|
|
|
|
|
offset > 0 -> "(UTC+%02d:%02d) %s %s"
|
|
|
|
|
else -> "(UTC-%02d:%02d) %s %s"
|
|
|
|
|
}
|
2021-05-22 00:03:16 +02:00
|
|
|
|
|
|
|
|
|
offset = abs(offset)
|
|
|
|
|
|
|
|
|
|
val hours = TimeUnit.MILLISECONDS.toHours(offset)
|
|
|
|
|
val minutes =
|
|
|
|
|
TimeUnit.MILLISECONDS.toMinutes(offset) - TimeUnit.HOURS.toMinutes(hours)
|
|
|
|
|
|
|
|
|
|
String.format(format, hours, minutes, tz.id, tz.displayName)
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-07-20 06:27:19 +02:00
|
|
|
|
if (list.none { it.caption == caption }) {
|
2021-05-22 00:03:16 +02:00
|
|
|
|
list.add(Item(id, caption, tz.rawOffset))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
list.sortWith { a, b ->
|
|
|
|
|
(a.offset - b.offset).notZero() ?: a.caption.compareTo(b.caption)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
list.add(0, Item("", getString(R.string.device_timezone), 0))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override fun getCount(): Int {
|
|
|
|
|
return list.size
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override fun getItem(position: Int): Any {
|
|
|
|
|
return list[position]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override fun getItemId(position: Int): Long {
|
|
|
|
|
return 0
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override fun getView(position: Int, viewOld: View?, parent: ViewGroup): View {
|
|
|
|
|
val view = viewOld ?: layoutInflater.inflate(
|
|
|
|
|
android.R.layout.simple_spinner_item,
|
|
|
|
|
parent,
|
|
|
|
|
false
|
|
|
|
|
)
|
|
|
|
|
val item = list[position]
|
|
|
|
|
view.findViewById<TextView>(android.R.id.text1).text = item.caption
|
|
|
|
|
return view
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override fun getDropDownView(position: Int, viewOld: View?, parent: ViewGroup): View {
|
|
|
|
|
val view =
|
|
|
|
|
viewOld ?: layoutInflater.inflate(R.layout.lv_spinner_dropdown, parent, false)
|
|
|
|
|
val item = list[position]
|
|
|
|
|
view.findViewById<TextView>(android.R.id.text1).text = item.caption
|
|
|
|
|
return view
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-20 15:12:25 +02:00
|
|
|
|
internal fun getIndexFromId(tzId: String): Int {
|
|
|
|
|
val index = list.indexOfFirst { it.id == tzId }
|
2021-05-22 00:03:16 +02:00
|
|
|
|
return if (index == -1) 0 else index
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
internal fun getIdFromIndex(position: Int): String {
|
|
|
|
|
return list[position].id
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fun openCustomShareChooser(appSettingItem: AppSettingItem, target: CustomShareTarget) {
|
|
|
|
|
try {
|
|
|
|
|
val rv = DlgAppPicker(
|
|
|
|
|
this,
|
|
|
|
|
intent = Intent().apply {
|
|
|
|
|
action = Intent.ACTION_SEND
|
|
|
|
|
type = "text/plain"
|
|
|
|
|
putExtra(Intent.EXTRA_TEXT, getString(R.string.content_sample))
|
|
|
|
|
},
|
|
|
|
|
addCopyAction = true
|
|
|
|
|
) { setCustomShare(appSettingItem, target, it) }
|
|
|
|
|
.show()
|
|
|
|
|
if (!rv) showToast(true, "share target app is not installed.")
|
|
|
|
|
} catch (ex: Throwable) {
|
2022-12-27 03:54:52 +01:00
|
|
|
|
log.e(ex, "openCustomShareChooser failed.")
|
2021-05-22 00:03:16 +02:00
|
|
|
|
showToast(ex, "openCustomShareChooser failed.")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fun setCustomShare(appSettingItem: AppSettingItem, target: CustomShareTarget, value: String) {
|
|
|
|
|
val sp: StringPref = appSettingItem.pref.cast() ?: error("$target: not StringPref")
|
2023-02-04 21:52:26 +01:00
|
|
|
|
sp.value = value
|
2021-05-22 00:03:16 +02:00
|
|
|
|
|
2023-01-12 22:38:14 +01:00
|
|
|
|
showCustomShareIcon(findItemViewHolder(appSettingItem)?.views?.textView1, target)
|
2021-05-22 00:03:16 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fun showCustomShareIcon(tv: TextView?, target: CustomShareTarget) {
|
|
|
|
|
tv ?: return
|
2023-05-03 19:21:36 +02:00
|
|
|
|
val cn = target.customShareComponentName
|
2021-05-22 00:03:16 +02:00
|
|
|
|
val (label, icon) = CustomShare.getInfo(this, cn)
|
|
|
|
|
tv.text = label ?: getString(R.string.not_selected)
|
|
|
|
|
tv.setCompoundDrawablesRelativeWithIntrinsicBounds(icon, null, null, null)
|
|
|
|
|
tv.compoundDrawablePadding = (resources.displayMetrics.density * 4f + 0.5f).toInt()
|
|
|
|
|
}
|
|
|
|
|
|
2021-11-08 10:47:01 +01:00
|
|
|
|
fun openWebBrowserChooser(
|
|
|
|
|
appSettingItem: AppSettingItem,
|
|
|
|
|
intent: Intent,
|
2022-03-13 13:05:54 +01:00
|
|
|
|
filter: (ResolveInfo) -> Boolean,
|
2021-11-08 10:47:01 +01:00
|
|
|
|
) {
|
2021-05-22 00:03:16 +02:00
|
|
|
|
try {
|
|
|
|
|
val rv = DlgAppPicker(
|
|
|
|
|
this,
|
|
|
|
|
intent = intent,
|
|
|
|
|
filter = filter,
|
|
|
|
|
addCopyAction = false
|
|
|
|
|
) { setWebBrowser(appSettingItem, it) }
|
|
|
|
|
.show()
|
|
|
|
|
if (!rv) showToast(true, "share target app is not installed.")
|
|
|
|
|
} catch (ex: Throwable) {
|
2022-12-27 03:54:52 +01:00
|
|
|
|
log.e(ex, "openCustomShareChooser failed.")
|
2021-05-22 00:03:16 +02:00
|
|
|
|
showToast(ex, "openCustomShareChooser failed.")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-11-20 13:16:56 +01:00
|
|
|
|
private fun setWebBrowser(appSettingItem: AppSettingItem, value: String) {
|
2021-11-08 10:47:01 +01:00
|
|
|
|
val sp: StringPref = appSettingItem.pref.cast()
|
|
|
|
|
?: error("${getString(appSettingItem.caption)}: not StringPref")
|
2023-02-04 21:52:26 +01:00
|
|
|
|
sp.value = value
|
2021-05-22 00:03:16 +02:00
|
|
|
|
|
2023-01-12 22:38:14 +01:00
|
|
|
|
showWebBrowser(findItemViewHolder(appSettingItem)?.views?.textView1, value)
|
2021-05-22 00:03:16 +02:00
|
|
|
|
}
|
|
|
|
|
|
2021-11-20 13:16:56 +01:00
|
|
|
|
private fun showWebBrowser(tv: TextView?, prefValue: String) {
|
2021-05-22 00:03:16 +02:00
|
|
|
|
tv ?: return
|
|
|
|
|
val cn = prefValue.cn()
|
|
|
|
|
val (label, icon) = CustomShare.getInfo(this, cn)
|
|
|
|
|
tv.text = label ?: getString(R.string.not_selected)
|
|
|
|
|
tv.setCompoundDrawablesRelativeWithIntrinsicBounds(icon, null, null, null)
|
|
|
|
|
tv.compoundDrawablePadding = (resources.displayMetrics.density * 4f + 0.5f).toInt()
|
|
|
|
|
}
|
2023-02-11 10:25:17 +01:00
|
|
|
|
|
|
|
|
|
fun exportLog() {
|
|
|
|
|
val context = this
|
|
|
|
|
launchAndShowError {
|
|
|
|
|
val logZipFile = daoLogData.createLogFile(context)
|
|
|
|
|
val uri = FileProvider.getUriForFile(context, FILE_PROVIDER_AUTHORITY, logZipFile)
|
|
|
|
|
|
|
|
|
|
val intent = Intent(Intent.ACTION_SEND_MULTIPLE).apply {
|
|
|
|
|
type = "text/plain"
|
|
|
|
|
putExtra(Intent.EXTRA_EMAIL, arrayOf("tateisu@gmail.com"))
|
|
|
|
|
putExtra(Intent.EXTRA_SUBJECT, "SubwayTooter bug report")
|
|
|
|
|
val soc = if (Build.VERSION.SDK_INT >= 31) {
|
|
|
|
|
"manufacturer=${Build.SOC_MANUFACTURER} product=${Build.SOC_MODEL}"
|
|
|
|
|
} else {
|
|
|
|
|
"(no information)"
|
|
|
|
|
}
|
|
|
|
|
val text = """
|
|
|
|
|
|Please write about the problem.
|
|
|
|
|
|…
|
|
|
|
|
|…
|
|
|
|
|
|…
|
|
|
|
|
|…
|
|
|
|
|
|
|
|
|
|
|
|Don't rewrite below lines.
|
|
|
|
|
|SubwayTooter version: $currentVersion $packageName
|
|
|
|
|
|Android version: ${Build.VERSION.RELEASE}
|
|
|
|
|
|Device: manufacturer=${Build.MANUFACTURER} product=${Build.PRODUCT} model=${Build.MODEL} device=${Build.DEVICE}
|
|
|
|
|
|$soc
|
|
|
|
|
""".trimMargin("|")
|
|
|
|
|
|
|
|
|
|
// ログに警告がでるが偽陽性だった。
|
|
|
|
|
// extras!!.putCharSequenceArrayList に変えると警告は出なくなるが、Gmailに本文が表示されない。
|
|
|
|
|
putExtra(Intent.EXTRA_TEXT, text)
|
|
|
|
|
putParcelableArrayListExtra(Intent.EXTRA_STREAM, arrayListOf(uri))
|
|
|
|
|
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
|
|
|
|
}
|
|
|
|
|
// 送る前にgrantしておく
|
|
|
|
|
val chooserIntent = Intent.createChooser(intent, null)
|
|
|
|
|
grantFileProviderUri(intent, uri)
|
|
|
|
|
grantFileProviderUri(chooserIntent, uri)
|
|
|
|
|
startActivity(chooserIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private val currentVersion: String
|
|
|
|
|
get() = try {
|
|
|
|
|
packageManager.getPackageInfoCompat(packageName)!!.versionName
|
|
|
|
|
} catch (ignored: Throwable) {
|
|
|
|
|
"??"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun Context.grantFileProviderUri(
|
|
|
|
|
intent: Intent,
|
|
|
|
|
uri: Uri,
|
|
|
|
|
permission: Int = Intent.FLAG_GRANT_READ_URI_PERMISSION,
|
|
|
|
|
) {
|
|
|
|
|
try {
|
|
|
|
|
intent.addFlags(permission)
|
|
|
|
|
packageManager.queryIntentActivitiesCompat(
|
|
|
|
|
intent,
|
|
|
|
|
PackageManager.MATCH_DEFAULT_ONLY
|
|
|
|
|
).forEach {
|
|
|
|
|
grantUriPermission(
|
|
|
|
|
it.activityInfo.packageName,
|
|
|
|
|
uri,
|
|
|
|
|
permission
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
} catch (ex: Throwable) {
|
|
|
|
|
log.e(ex, "grantFileProviderUri failed.")
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-06-20 15:12:25 +02:00
|
|
|
|
}
|