diff --git a/.idea/dictionaries/tateisu.xml b/.idea/dictionaries/tateisu.xml index f276a652..185aed18 100644 --- a/.idea/dictionaries/tateisu.xml +++ b/.idea/dictionaries/tateisu.xml @@ -13,6 +13,7 @@ atmark autofill basi + bindable blockee blockquote blurhash diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index a434d20b..f3bcd749 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -168,13 +168,6 @@ - diff --git a/app/src/main/java/jp/juggler/subwaytooter/ActAccountSetting.kt b/app/src/main/java/jp/juggler/subwaytooter/ActAccountSetting.kt index 56fcb129..8dc82dc1 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ActAccountSetting.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/ActAccountSetting.kt @@ -261,7 +261,7 @@ class ActAccountSetting Styler.fixHorizontalPadding(root) - ActAppSettingChild.setSwitchColor(this, pref, root) + ActAppSetting.setSwitchColor(this, pref, root) tvInstance = findViewById(R.id.tvInstance) tvUser = findViewById(R.id.tvUser) diff --git a/app/src/main/java/jp/juggler/subwaytooter/ActAppSetting.kt b/app/src/main/java/jp/juggler/subwaytooter/ActAppSetting.kt index 54fff61b..b1c42f2e 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ActAppSetting.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/ActAppSetting.kt @@ -1,29 +1,51 @@ package jp.juggler.subwaytooter import android.annotation.SuppressLint +import android.app.PendingIntent import android.content.Intent +import android.content.SharedPreferences +import android.graphics.Color +import android.graphics.Typeface import android.net.Uri import android.os.AsyncTask +import android.os.Build import android.os.Bundle +import android.os.Handler +import android.text.Editable +import android.text.TextWatcher import android.util.JsonWriter import android.view.View import android.view.ViewGroup -import android.widget.AdapterView -import android.widget.BaseAdapter -import android.widget.ListView -import android.widget.TextView +import android.view.Window +import android.widget.* +import androidx.annotation.ColorInt import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity +import androidx.core.content.ContextCompat import androidx.core.content.FileProvider +import com.jrummyapps.android.colorpicker.ColorPickerDialog +import com.jrummyapps.android.colorpicker.ColorPickerDialogListener +import jp.juggler.subwaytooter.action.CustomShare +import jp.juggler.subwaytooter.action.CustomShareTarget import jp.juggler.subwaytooter.dialog.ProgressDialogEx +import jp.juggler.subwaytooter.table.AcctColor +import jp.juggler.subwaytooter.table.SavedAccount import jp.juggler.util.* +import org.apache.commons.io.IOUtils import java.io.File import java.io.FileOutputStream +import java.io.InputStream import java.io.OutputStreamWriter +import java.text.NumberFormat +import java.util.* +import java.util.concurrent.TimeUnit import java.util.zip.ZipEntry import java.util.zip.ZipOutputStream +import kotlin.Comparator +import kotlin.collections.ArrayList +import kotlin.math.abs -class ActAppSetting : AppCompatActivity() { +class ActAppSetting : AppCompatActivity(), ColorPickerDialogListener, View.OnClickListener { companion object { internal val log = LogCategory("ActAppSetting") @@ -35,16 +57,49 @@ class ActAppSetting : AppCompatActivity() { ) } + private const val COLOR_DIALOG_ID = 1 + + private const val STATE_CHOOSE_INTENT_TARGET = "customShareTarget" + // 他の設定子画面と重複しない値にすること private const val REQUEST_CODE_OTHER = 0 private const val REQUEST_CODE_APP_DATA_IMPORT = 1 + + internal const val REQUEST_CODE_TIMELINE_FONT = 1 + internal const val REQUEST_CODE_TIMELINE_FONT_BOLD = 2 + + val reLinefeed = Regex("[\\x0d\\x0a]+") } + private var customShareTarget : CustomShareTarget? = null + + lateinit var pref : SharedPreferences + lateinit var handler : Handler private lateinit var lvList : ListView + private lateinit var adapter : MyAdapter + private lateinit var etSearch : EditText override fun onCreate(savedInstanceState : Bundle?) { super.onCreate(savedInstanceState) - App1.setActivityTheme(this) + requestWindowFeature(Window.FEATURE_NO_TITLE) + App1.setActivityTheme(this, noActionBar = true) + + this.handler = App1.getAppState(this).handler + this.pref = Pref.pref(this) + + // 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 { + val sv = savedInstanceState.getString(STATE_CHOOSE_INTENT_TARGET) + customShareTarget = CustomShareTarget.values().firstOrNull { it.name == sv } + } catch(ex : Throwable) { + log.e(ex, "can't restore customShareTarget.") + } + } setContentView(R.layout.act_app_setting) App1.initEdgeToEdge(this) @@ -52,131 +107,655 @@ class ActAppSetting : AppCompatActivity() { Styler.fixHorizontalPadding0(findViewById(R.id.llContent)) lvList = findViewById(R.id.lvList) - val adapter = MyAdapter() + adapter = MyAdapter() lvList.adapter = adapter - lvList.onItemClickListener = adapter + load(null, null) + etSearch = findViewById(R.id.etSearch).apply { + addTextChangedListener(object : TextWatcher { + override fun afterTextChanged(p0 : Editable?) { + pendingQuery = p0?.toString() + this@ActAppSetting.handler.removeCallbacks(procQuery) + this@ActAppSetting.handler.postDelayed(procQuery, 166L) + } + + override fun beforeTextChanged( + p0 : CharSequence?, + p1 : Int, + p2 : Int, + p3 : Int + ) { + } + + override fun onTextChanged( + p0 : CharSequence?, + p1 : Int, + p2 : Int, + p3 : Int + ) { + } + }) + } + + findViewById(R.id.btnSearchReset).apply { + setOnClickListener(this@ActAppSetting) + } + } + + override fun onSaveInstanceState(outState : Bundle) { + super.onSaveInstanceState(outState) + val sv = customShareTarget?.name + if(sv != null) outState.putString(STATE_CHOOSE_INTENT_TARGET, sv) + } + + override fun onResume() { + super.onResume() + onCustomShareSelected() + } + + override fun onPause() { + super.onPause() + + // Pull通知チェック間隔を変更したかもしれないのでジョブを再設定する + try { + PollingWorker.scheduleJob(this, PollingWorker.JOB_POLLING) + } catch(ex : Throwable) { + log.trace(ex, "PollingWorker.scheduleJob failed.") + } } override fun onActivityResult(requestCode : Int, resultCode : Int, data : Intent?) { if(resultCode == RESULT_OK && data != null && requestCode == REQUEST_CODE_APP_DATA_IMPORT) { data.handleGetContentResult(contentResolver).firstOrNull()?.uri?.let { - importAppData(false, it) + importAppData2(false, it) } + } else if(resultCode == RESULT_OK && data != null && requestCode == REQUEST_CODE_TIMELINE_FONT) { + handleFontResult(AppSettingItem.TIMELINE_FONT, data, "TimelineFont") + } else if(resultCode == RESULT_OK && data != null && requestCode == REQUEST_CODE_TIMELINE_FONT_BOLD) { + handleFontResult(AppSettingItem.TIMELINE_FONT_BOLD, data, "TimelineFontBold") } + super.onActivityResult(requestCode, resultCode, data) } + + override fun onBackPressed() { + when { + lastQuery != null -> load(lastSection, null) + lastSection != null -> load(null, null) + else -> super.onBackPressed() + } + } + + override fun onClick(v : View) { + when(v.id) { + R.id.btnSearchReset -> { + handler.removeCallbacks(procQuery) + etSearch.setText("") + etSearch.hideKeyboard() + load(lastSection, null) + } + } + } + + /////////////////////////////////////////////////////////////// + private var pendingQuery : String? = null + + private val procQuery : Runnable = Runnable { + if(pendingQuery != null) load(null, pendingQuery) + } + /////////////////////////////////////////////////////////////// - class Item( - val titleId : Int, - val descId : Int? = null, - val block : (Item) -> Unit - ) + private val divider = Any() + private val list = ArrayList() - inner class MyAdapter : BaseAdapter(), AdapterView.OnItemClickListener { + private var lastSection : AppSettingGroup? = null + private var lastQuery : String? = null + + private fun load(section : AppSettingGroup?, query : String?) { + list.clear() - private val items = arrayOf( + var lastPath : String? = null + fun addParentPath(item : AppSettingItem) { + list.add(divider) - Item(R.string.notifications) { - openChild(it, R.layout.act_app_setting_notifications) - }, - - Item(R.string.behavior) { - openChild(it, R.layout.act_app_setting_behavior) - }, - - Item(R.string.post) { - openChild(it, R.layout.act_app_setting_post) - }, - - Item(R.string.tablet_mode) { - openChild(it, R.layout.act_app_setting_tablet) - }, - - Item(R.string.media_attachment) { - openChild(it, R.layout.act_app_setting_media_attachment) - }, - - Item(R.string.animation) { - openChild(it, R.layout.act_app_setting_animation) - }, - - Item(R.string.appearance) { - openChild(it, R.layout.act_app_setting_appearance) - }, - - Item(R.string.color) { - openChild(it, R.layout.act_app_setting_color) - }, - - Item(R.string.performance) { - openChild(it, R.layout.act_app_setting_performance) - }, - - Item(R.string.developer_options) { - openChild(it, R.layout.act_app_setting_developer) - }, - - Item(R.string.app_data_export) { - exportAppData() - }, - Item(R.string.app_data_import, R.string.app_data_import_desc) { - importAppData() + val pathList = ArrayList() + 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 + list.add(path) + list.add(divider) + } + } - override fun getCount() : Int = items.size + if(query?.isNotEmpty() == true) { + lastQuery = query + fun scanGroup(level : Int, item : AppSettingItem) { + if(item.caption == 0) return + if(item.type != SettingType.Section) { + var match = getString(item.caption).contains(query, ignoreCase = true) + if(item.type == SettingType.Group && item is AppSettingGroup) { + for(child in item.items) { + if(child.caption == 0) continue + if(getString(item.caption).contains(query, ignoreCase = true)) { + match = true + break + } + } + if(match) { + // put entire group + addParentPath(item) + list.add(item) + for(child in item.items) { + list.add(child) + } + } + return + } else if(match) { + addParentPath(item) + list.add(item) + } + } + if(item is AppSettingGroup) { + for(child in item.items) { + scanGroup(level + 1, child) + } + } + } + scanGroup(0, appSettingRoot) + if(list.isNotEmpty()) list.add(divider) + } else if(section == null) { + // show root page + val root = appSettingRoot + lastQuery = null + lastSection = null + for(child in root.items) { + list.add(divider) + list.add(child) + } + list.add(divider) + } else { + // show section page + lastSection = section + lastQuery = null + fun scanGroup(level : Int, parent : AppSettingGroup?) { + parent ?: return + for(item in parent.items) { + list.add(divider) + list.add(item) + if(item is AppSettingGroup) { + if(item.type == SettingType.Group) { + for(child in item.items) { + list.add(child) + } + } else { + scanGroup(level + 1, item) + } + } + } + } + scanGroup(0, section.cast()) + if(list.isNotEmpty()) list.add(divider) + } + adapter.notifyDataSetChanged() + lvList.setSelectionFromTop(0, 0) + } + + inner class MyAdapter : BaseAdapter() { - override fun getItemId(position : Int) : Long = items[position].titleId.toLong() + override fun getCount() : Int = list.size + override fun getItemId(position : Int) : Long = 0 + override fun getItem(position : Int) : Any = list[position] + override fun getViewTypeCount() : Int = SettingType.values().maxBy { it.id } !!.id + 1 - override fun getItem(position : Int) : Any = items[position] + override fun getItemViewType(position : Int) : Int = + when(val item = list[position]) { + is AppSettingItem -> item.type.id + is String -> SettingType.Path.id + divider -> SettingType.Divider.id + else -> error("can't generate view for type ${item}") + } - override fun onItemClick( + override fun getView(position : Int, convertView : View?, parent : ViewGroup?) : View = + when(val item = list[position]) { + is AppSettingItem -> + getViewSettingItem(item, convertView, parent) + is String -> getViewPath(item, convertView) + divider -> getViewDivider(convertView) + else -> error("can't generate view for type ${item}") + } + } + + private fun dip(dp : Float) : Int = + (resources.displayMetrics.density * dp + 0.5f).toInt() + + private fun dip(dp : Int) : Int = dip(dp.toFloat()) + + private fun getViewDivider(convertView : View?) : View = + convertView ?: FrameLayout(this@ActAppSetting).apply { + layoutParams = AbsListView.LayoutParams( + AbsListView.LayoutParams.MATCH_PARENT, + AbsListView.LayoutParams.WRAP_CONTENT + ) + addView(View(this@ActAppSetting).apply { + layoutParams = FrameLayout.LayoutParams( + AbsListView.LayoutParams.MATCH_PARENT, + dip(1) + ).apply { + val margin_lr = 0 + val margin_tb = dip(6) + setMargins(margin_lr, margin_tb, margin_lr, margin_tb) + } + setBackgroundColor(getAttributeColor(context, R.attr.colorSettingDivider)) + }) + } + + private fun getViewPath(path : String, convertView : View?) : View { + val tv : TextView = convertView.cast() ?: TextView(this@ActAppSetting).apply { + layoutParams = AbsListView.LayoutParams( + AbsListView.LayoutParams.MATCH_PARENT, + AbsListView.LayoutParams.WRAP_CONTENT + ) + val pad_lr = 0 + val pad_tb = dip(3) + setTypeface(typeface, Typeface.BOLD) + setPaddingRelative(pad_lr, pad_tb, pad_lr, pad_tb) + } + tv.text = path + return tv + } + + private fun getViewSettingItem( + item : AppSettingItem, + convertView : View?, + parent : ViewGroup? + ) : View { + val view : View + val holder : ViewHolderSettingItem + if(convertView != null) { + view = convertView + holder = convertView.tag.cast() !! + } else { + view = layoutInflater.inflate(R.layout.lv_setting_item, parent, false) + holder = ViewHolderSettingItem(view) + view.tag = holder + } + holder.bind(item) + return view + } + + private var colorTarget : AppSettingItem? = null + + override fun onDialogDismissed(dialogId : Int) { + } + + override fun onColorSelected(dialogId : Int, @ColorInt colorSelected : 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 -> colorSelected.notZero() ?: 0x01000000 + else -> colorSelected or Color.BLACK + } + pref.edit().put(ip, c).apply() + findItemViewHolder(colorTarget)?.showColor() + colorTarget.changed(this) + } + + inner class ViewHolderSettingItem(viewRoot : View) : + TextWatcher, + AdapterView.OnItemSelectedListener, + CompoundButton.OnCheckedChangeListener { + + private val tvCaption : TextView = viewRoot.findViewById(R.id.tvCaption) + private val btnAction : Button = viewRoot.findViewById(R.id.btnAction) + + private val checkBox : CheckBox = viewRoot.findViewById(R.id.checkBox) + .also { it.setOnCheckedChangeListener(this) } + + private val swSwitch : Switch = viewRoot.findViewById(R.id.swSwitch) + .also { it.setOnCheckedChangeListener(this) } + + val llExtra : LinearLayout = viewRoot.findViewById(R.id.llExtra) + + val textView1 : TextView = viewRoot.findViewById(R.id.textView1) + + private val llButtonBar : LinearLayout = viewRoot.findViewById(R.id.llButtonBar) + private val vColor : View = viewRoot.findViewById(R.id.vColor) + private val btnEdit : Button = viewRoot.findViewById(R.id.btnEdit) + private val btnReset : Button = viewRoot.findViewById(R.id.btnReset) + + private val spSpinner : Spinner = viewRoot.findViewById(R.id.spSpinner) + .also { it.onItemSelectedListener = this } + + private val etEditText : EditText = viewRoot.findViewById(R.id.etEditText) + .also { it.addTextChangedListener(this) } + + private val tvDesc : TextView = viewRoot.findViewById(R.id.tvDesc) + private val tvError : TextView = viewRoot.findViewById(R.id.tvError) + + val activity : ActAppSetting + get() = this@ActAppSetting + + var item : AppSettingItem? = null + + private var bindingBusy = false + + fun bind(item : AppSettingItem) { + bindingBusy = true + try { + this.item = item + + tvCaption.vg(false) + btnAction.vg(false) + checkBox.vg(false) + swSwitch.vg(false) + llExtra.vg(false) + textView1.vg(false) + llButtonBar.vg(false) + vColor.vg(false) + spSpinner.vg(false) + etEditText.vg(false) + tvDesc.vg(false) + tvError.vg(false) + + val name = if(item.caption == 0) "" else getString(item.caption) + + if(item.desc != 0) { + tvDesc.vg(true) + tvDesc.text = getString(item.desc) + if(item.descClickSet) { + tvDesc.background = ContextCompat.getDrawable( + activity, + R.drawable.btn_bg_transparent_round6dp + ) + tvDesc.setOnClickListener { item.descClick.invoke(activity) } + } else { + tvDesc.background = null + tvDesc.setOnClickListener(null) + tvDesc.isClickable = false + } + } + + when(item.type) { + + SettingType.Section -> { + btnAction.vg(true) + btnAction.text = name + btnAction.isEnabled = item.enabled + btnAction.setOnClickListener { + load(item.cast() !!, null) + } + } + + SettingType.Action -> { + btnAction.vg(true) + btnAction.text = name + btnAction.isEnabled = item.enabled + btnAction.setOnClickListener { + item.action(activity) + } + } + + SettingType.CheckBox -> { + val bp = item.pref.cast() ?: error("$name has no boolean pref") + if(bp(pref) == bp.defVal) pref.edit().remove(bp).apply() + checkBox.vg(false) // skip animation + checkBox.text = name + checkBox.isEnabled = item.enabled + checkBox.isChecked = bp(pref) + checkBox.vg(true) + } + + SettingType.Switch -> { + val bp = item.pref.cast() ?: error("$name has no boolean pref") + if(bp(pref) == bp.defVal) pref.edit().remove(bp).apply() + showCaption(name) + swSwitch.vg(false) // skip animation + swSwitch.isEnabled = item.enabled + swSwitch.isChecked = bp(pref) + + App1.setSwitchColor1(activity, pref, swSwitch) + swSwitch.vg(true) + } + + SettingType.Group -> { + showCaption(name) + } + + SettingType.Sample -> { + llExtra.vg(true) + llExtra.removeAllViews() + layoutInflater.inflate(item.sampleLayoutId, llExtra, true) + item.sampleUpdate(activity, llExtra) + } + + SettingType.ColorAlpha, SettingType.ColorOpaque -> { + val ip = item.pref.cast() ?: error("$name has no int pref") + showCaption(name) + llButtonBar.vg(true) + vColor.vg(true) + vColor.setBackgroundColor(ip(pref)) + btnEdit.isEnabled = item.enabled + btnReset.isEnabled = item.enabled + btnEdit.setOnClickListener { + colorTarget = item + val color = ip(pref) + 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) + builder.show(activity) + + } + btnReset.setOnClickListener { + pref.edit().remove(ip).apply() + item.changed.invoke(activity) + } + } + + SettingType.Spinner -> { + showCaption(name) + + spSpinner.vg(true) + spSpinner.isEnabled = item.enabled + + val pi = item.pref + if(pi is IntPref) { + // 整数型の設定のSpinnerは全て選択肢を単純に覚える + val argsInt = item.spinnerArgs + if(argsInt != null) { + initSpinner(spSpinner, argsInt.map { getString(it) }) + } else { + initSpinner(spSpinner, item.spinnerArgsProc(activity)) + } + spSpinner.setSelection(pi.invoke(pref)) + } else { + item.spinnerInitializer.invoke(activity, spSpinner) + } + } + + SettingType.EditText -> { + showCaption(name) + etEditText.vg(true) + + ?: error("EditText must have preference.") + etEditText.inputType = item.inputType + val text = when(val pi = item.pref) { + is FloatPref -> { + item.fromFloat.invoke(activity, pi(pref)) + } + + is StringPref -> { + pi(pref) + } + + else -> error("EditText han incorrect pref $pi") + } + etEditText.setText(text) + etEditText.setSelection(0, text.length) + + item.hint?.let { etEditText.hint = it } + + updateErrorView() + } + + SettingType.TextWithSelector -> { + showCaption(name) + llButtonBar.vg(true) + vColor.vg(false) + textView1.vg(true) + + item.showTextView.invoke(activity, textView1) + + btnEdit.setOnClickListener { + item.onClickEdit.invoke(activity) + } + btnReset.setOnClickListener { + item.onClickReset.invoke(activity) + } + } + + else -> error("unknown type ${item.type}") + } + } finally { + bindingBusy = false + } + } + + private fun showCaption(caption : String) { + if(caption.isNotEmpty()) { + tvCaption.vg(true) + tvCaption.text = caption + updateCaption() + } + } + + fun updateCaption() { + val item = item ?: return + val key = item.pref?.key ?: return + + val sample : TextView = tvCaption + var defaultExtra = defaultLineSpacingExtra[key] + if(defaultExtra == null) { + defaultExtra = sample.lineSpacingExtra + defaultLineSpacingExtra[key] = defaultExtra + } + var defaultMultiplier = defaultLineSpacingMultiplier[key] + if(defaultMultiplier == null) { + defaultMultiplier = sample.lineSpacingMultiplier + defaultLineSpacingMultiplier[key] = defaultMultiplier + } + + val size = item.captionFontSize.invoke(activity) + if(size != null) sample.textSize = size + + val spacing = item.captionSpacing.invoke(activity) + if(spacing == null || ! spacing.isFinite()) { + sample.setLineSpacing(defaultExtra, defaultMultiplier) + } else { + sample.setLineSpacing(0f, spacing) + } + + } + + private fun updateErrorView() { + val item = item ?: return + val sv = etEditText.text.toString() + val error = item.getError.invoke(activity, sv) + tvError.vg(error != null)?.text = error + } + + fun showColor() { + val item = item ?: return + val ip = item.pref.cast() ?: return + val c = ip(pref) + vColor.setBackgroundColor(c) + } + + 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) { + + is StringPref -> { + pref.edit().put(pi, sv).apply() + } + + is FloatPref -> { + val fv = item.toFloat.invoke(activity, sv) + if(fv.isFinite()) { + pref.edit().put(pi, fv).apply() + } else { + pref.edit().remove(pi.key).apply() + } + } + + else -> { + error("not FloatPref or StringPref") + } + } + + item.changed.invoke(activity) + updateErrorView() + } + + override fun onNothingSelected(v : AdapterView<*>?) = Unit + + override fun onItemSelected( parent : AdapterView<*>?, view : View?, position : Int, id : Long ) { - items[position].let { it.block(it) } - } - - override fun getView(position : Int, convertView : View?, parent : ViewGroup?) : View { - val view : View - val holder : ViewHolder - if(convertView == null) { - view = layoutInflater.inflate(R.layout.lv_app_setting, parent, false) - holder = ViewHolder(view) - view.tag = holder - } else { - view = convertView - holder = view.tag as ViewHolder + if(bindingBusy) return + val item = item ?: return + when(val pi = item.pref) { + is IntPref -> pref.edit().put(pi, spSpinner.selectedItemPosition).apply() + else -> item.spinnerOnSelected.invoke(activity, spSpinner, position) } - holder.bind(items[position]) - return view + item.changed.invoke(activity) } - } - - inner class ViewHolder(viewRoot : View) { - private val tvTitle : TextView = viewRoot.findViewById(R.id.tvTitle) - private val tvDesc : TextView = viewRoot.findViewById(R.id.tvDesc) - fun bind(item : Item) { - tvTitle.setText(item.titleId) - val descId = item.descId ?: 0 - tvDesc.vg(descId != 0)?.setText(descId) + override fun onCheckedChanged(v : CompoundButton?, isChecked : Boolean) { + if(bindingBusy) return + val item = item ?: return + when(val pi = item.pref) { + is BooleanPref -> pref.edit().putOrRemove(pi, isChecked).apply() + else -> error("CompoundButton has no booleanPref $pi") + } + item.changed.invoke(activity) + } + } + + private fun initSpinner(spinner : Spinner, captions : List) { + spinner.adapter = ArrayAdapter( + this, + android.R.layout.simple_spinner_item, + captions.toTypedArray() + ).apply { + setDropDownViewResource(R.layout.lv_spinner_dropdown) } } /////////////////////////////////////////////////////////////// - private fun openChild(item : Item, layoutId : Int) { - ActAppSettingChild.open(this, REQUEST_CODE_OTHER, layoutId, item.titleId) - } - - /////////////////////////////////////////////////////////////// - - private fun exportAppData() { + fun exportAppData() { @Suppress("DEPRECATION") val progress = ProgressDialogEx(this) @@ -268,17 +847,18 @@ class ActAppSetting : AppCompatActivity() { task.executeOnExecutor(App1.task_executor) } - private fun importAppData() { + // open data picker + fun importAppData1() { try { val intent = intentOpenDocument("*/*") startActivityForResult(intent, REQUEST_CODE_APP_DATA_IMPORT) } catch(ex : Throwable) { showToast(this, ex, "importAppData(1) failed.") } - } - private fun importAppData(bConfirm : Boolean, uri : Uri) { + // after data picked + private fun importAppData2(bConfirm : Boolean, uri : Uri) { val type = contentResolver.getType(uri) log.d("importAppData type=%s", type) @@ -287,7 +867,7 @@ class ActAppSetting : AppCompatActivity() { AlertDialog.Builder(this) .setMessage(getString(R.string.app_data_import_confirm)) .setNegativeButton(R.string.cancel, null) - .setPositiveButton(R.string.ok) { _, _ -> importAppData(true, uri) } + .setPositiveButton(R.string.ok) { _, _ -> importAppData2(true, uri) } .show() return } @@ -296,6 +876,389 @@ class ActAppSetting : AppCompatActivity() { data.data = uri setResult(ActMain.RESULT_APP_DATA_IMPORT, data) finish() + } + + fun findItemViewHolder(item : AppSettingItem?) : ViewHolderSettingItem? { + if(item != null) { + for(i in 0 until lvList.childCount) { + val view = lvList.getChildAt(i) + val holder : ViewHolderSettingItem? = view?.tag?.cast() + if(holder?.item == item) return holder + } + } + return null + } + + fun showSample(item : AppSettingItem?) { + item ?: error("showSample: missing item…") + findItemViewHolder(item)?.let { + item.sampleUpdate.invoke(this, it.llExtra) + } + } + + fun setSwitchColor() { + App1.setSwitchColor(this@ActAppSetting, pref, lvList) + } + + ////////////////////////////////////////////////////// + + fun formatFontSize(fv : Float) : String = + when { + fv.isFinite() -> String.format(Locale.getDefault(), "%.1f", fv) + else -> "" + } + + fun parseFontSize(src : String) : Float { + try { + if(src.isNotEmpty()) { + val f = NumberFormat.getInstance(Locale.getDefault()).parse(src)?.toFloat() + return when { + f == null -> Float.NaN + f.isNaN() -> Float.NaN + f < 0f -> 0f + f > 999f -> 999f + else -> f + } + } + } catch(ex : Throwable) { + log.trace(ex) + } + + return Float.NaN + } + + private val defaultLineSpacingExtra = HashMap() + private val defaultLineSpacingMultiplier = HashMap() + + private fun handleFontResult(item : AppSettingItem?, data : Intent, file_name : String) { + item ?: error("handleFontResult : setting item is null") + data.handleGetContentResult(contentResolver).firstOrNull()?.uri?.let { + val file = saveTimelineFont(it, file_name) + if(file != null) { + pref.edit().put(item.pref.cast() !!, file.absolutePath).apply() + showTimelineFont(item) + } + } + } + + fun showTimelineFont(item : AppSettingItem?) { + item ?: return + val holder = findItemViewHolder(item) ?: return + item.showTextView.invoke(this, holder.textView1) + } + + fun showTimelineFont(item : AppSettingItem, tv : TextView) { + val font_url = item.pref.cast() !!.invoke(this) + try { + if(font_url.isNotEmpty()) { + tv.typeface = Typeface.DEFAULT + val face = Typeface.createFromFile(font_url) + tv.typeface = face + tv.text = font_url + return + } + } catch(ex : Throwable) { + log.trace(ex) + } + + // fallback + tv.text = getString(R.string.not_selected) + tv.typeface = Typeface.DEFAULT + } + + private fun saveTimelineFont(uri : Uri?, file_name : String) : File? { + try { + if(uri == null) { + showToast(this, false, "missing uri.") + return null + } + + contentResolver.takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION) + + val dir = filesDir + + dir.mkdir() + + val tmp_file = File(dir, "$file_name.tmp") + + val source : InputStream? = contentResolver.openInputStream(uri) + if(source == null) { + showToast(this, false, "openInputStream returns null. uri=%s", uri) + return null + } else { + source.use { inStream -> + FileOutputStream(tmp_file).use { outStream -> + IOUtils.copy(inStream, outStream) + } + } + } + + val face = Typeface.createFromFile(tmp_file) + if(face == null) { + showToast(this, false, "Typeface.createFromFile() failed.") + return null + } + + val file = File(dir, file_name) + if(! tmp_file.renameTo(file)) { + showToast(this, false, "File operation failed.") + return null + } + + return file + } catch(ex : Throwable) { + log.trace(ex) + showToast(this, ex, "saveTimelineFont failed.") + return null + } } -} + + ////////////////////////////////////////////////////// + + inner class AccountAdapter internal constructor() : BaseAdapter() { + + internal val list = java.util.ArrayList() + + init { + for(a in SavedAccount.loadAccountList(this@ActAppSetting)) { + if(a.isPseudo) continue + list.add(a) + } + SavedAccount.sort(list) + } + + override fun getCount() : Int { + return 1 + list.size + } + + override fun getItem(position : Int) : Any? { + return if(position == 0) null else list[position - 1] + } + + 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 + ) + view.findViewById(android.R.id.text1).text = + if(position == 0) + getString(R.string.ask_always) + else + AcctColor.getNickname(list[position - 1].acct) + return view + } + + override fun getDropDownView(position : Int, viewOld : View?, parent : ViewGroup) : View { + val view = + viewOld ?: layoutInflater.inflate(R.layout.lv_spinner_dropdown, parent, false) + view.findViewById(android.R.id.text1).text = + if(position == 0) + getString(R.string.ask_always) + else + AcctColor.getNickname(list[position - 1].acct) + return view + } + + internal fun getIndexFromId(db_id : Long) : Int { + var i = 0 + val ie = list.size + while(i < ie) { + if(list[i].db_id == db_id) return i + 1 + ++ i + } + return 0 + } + + internal fun getIdFromIndex(position : Int) : Long { + return if(position > 0) list[position - 1].db_id else - 1L + } + } + + private class Item( + val id : String, + val caption : String, + val offset : Int + ) + + inner class TimeZoneAdapter internal constructor() : BaseAdapter() { + + private val list = ArrayList() + + 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) { + 0L -> String.format("(UTC\u00B100:00) %s %s", tz.id, tz.displayName) + + else -> { + + val format = if(offset > 0) + "(UTC+%02d:%02d) %s %s" + else + "(UTC-%02d:%02d) %s %s" + + 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) + } + } + if(null == list.find { it.caption == caption }) { + list.add(Item(id, caption, tz.rawOffset)) + } + } + + list.sortWith(Comparator { 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(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(android.R.id.text1).text = item.caption + return view + } + + internal fun getIndexFromId(tz_id : String) : Int { + val index = list.indexOfFirst { it.id == tz_id } + return if(index == - 1) 0 else index + } + + internal fun getIdFromIndex(position : Int) : String { + return list[position].id + } + } + + fun openCustomShareChooser(target : CustomShareTarget) { + + val intent = Intent() + intent.action = Intent.ACTION_SEND + intent.type = "text/plain" + intent.putExtra(Intent.EXTRA_TEXT, getString(R.string.content_sample)) + + // このifはwhenにしてはならない。APIバージョン関連の警告が出てしまう + @Suppress("CascadeIf") + if(intent.resolveActivity(packageManager) == null) { + // ACTION_SENDを受け取れるアプリがインストールされてない + showToast(this, true, getString(R.string.missing_app_can_receive_action_send)) + } else if(Build.VERSION.SDK_INT <= 21) { + // createChooserにIntentSenderを指定できるのはAndroid 22以降 + showToast( + this, + true, + getString(R.string.app_chooser_works_android_5_1) + ) + } else try { + customShareTarget = target + ChooseReceiver.lastComponentName = null + ChooseReceiver.setCallback { onCustomShareSelected() } + + val receiver = Intent(this, ChooseReceiver::class.java) + val pendingIntent = PendingIntent.getBroadcast( + this, + 1, + receiver, + PendingIntent.FLAG_UPDATE_CURRENT + ) + startActivity( + Intent.createChooser( + intent, + getString(R.string.select_destination_app_and_back), + pendingIntent.intentSender + ) + ) + + } catch(ex : Throwable) { + log.trace(ex) + showToast(this, ex, "openCustomShareChooser failed.") + } + } + + private fun onCustomShareSelected() { + if(isDestroyed) return + + val cn = ChooseReceiver.lastComponentName + if(cn != null) { + ChooseReceiver.lastComponentName = null + setCustomShare(customShareTarget, "${cn.packageName}/${cn.className}") + } + } + + fun setCustomShare(target : CustomShareTarget?, value : String) { + + target ?: return + + val item = when(target) { + CustomShareTarget.Translate -> AppSettingItem.CUSTOM_TRANSLATE + CustomShareTarget.CustomShare1 -> AppSettingItem.CUSTOM_SHARE_1 + CustomShareTarget.CustomShare2 -> AppSettingItem.CUSTOM_SHARE_2 + CustomShareTarget.CustomShare3 -> AppSettingItem.CUSTOM_SHARE_3 + } + ?: error("setCustomShare $target has no setting item.") + + val sp : StringPref = item.pref.cast() ?: error("$target: not StringPref") + pref.edit().put(sp, value).apply() + + showCustomShareIcon(findItemViewHolder(item)?.textView1, target) + } + + fun showCustomShareIcon(tv : TextView?, target : CustomShareTarget) { + tv ?: return + val cn = CustomShare.getCustomShareComponentName(pref, target) + val (label, icon) = CustomShare.getInfo(packageManager, cn) + tv.text = label ?: getString(R.string.not_selected) + tv.setCompoundDrawablesRelativeWithIntrinsicBounds(icon, null, null, null) + } + +} \ No newline at end of file diff --git a/app/src/main/java/jp/juggler/subwaytooter/ActAppSettingChild.kt b/app/src/main/java/jp/juggler/subwaytooter/ActAppSettingChild.kt deleted file mode 100644 index 435c60da..00000000 --- a/app/src/main/java/jp/juggler/subwaytooter/ActAppSettingChild.kt +++ /dev/null @@ -1,2063 +0,0 @@ -package jp.juggler.subwaytooter - -import android.app.PendingIntent -import android.content.Intent -import android.content.SharedPreferences -import android.content.res.ColorStateList -import android.graphics.Color -import android.graphics.PorterDuff -import android.graphics.Typeface -import android.net.Uri -import android.os.Build -import android.os.Bundle -import android.text.Editable -import android.text.TextWatcher -import android.util.SparseArray -import android.view.View -import android.view.ViewGroup -import android.widget.* -import androidx.annotation.ColorInt -import androidx.annotation.IdRes -import androidx.appcompat.app.AppCompatActivity -import com.jrummyapps.android.colorpicker.ColorPickerDialog -import com.jrummyapps.android.colorpicker.ColorPickerDialogListener -import jp.juggler.subwaytooter.action.CustomShare -import jp.juggler.subwaytooter.action.CustomShareTarget -import jp.juggler.subwaytooter.table.AcctColor -import jp.juggler.subwaytooter.table.SavedAccount -import jp.juggler.util.* -import org.apache.commons.io.IOUtils -import org.jetbrains.anko.backgroundColor -import org.jetbrains.anko.backgroundDrawable -import org.jetbrains.anko.textColor -import java.io.File -import java.io.FileOutputStream -import java.io.InputStream -import java.text.NumberFormat -import java.util.* -import java.util.concurrent.TimeUnit -import kotlin.math.abs - -class ActAppSettingChild : AppCompatActivity() - , CompoundButton.OnCheckedChangeListener - , AdapterView.OnItemSelectedListener - , View.OnClickListener - , ColorPickerDialogListener - , TextWatcher { - - companion object { - internal val log = LogCategory("ActAppSettingChild") - - private const val EXTRA_LAYOUT_ID = "layoutId" - private const val EXTRA_TITLE_ID = "titleId" - - fun open(activity : AppCompatActivity, requestCode : Int, layoutId : Int, titleId : Int) { - activity.startActivityForResult( - Intent(activity, ActAppSettingChild::class.java).apply { - putExtra(EXTRA_LAYOUT_ID, layoutId) - putExtra(EXTRA_TITLE_ID, titleId) - }, - requestCode - ) - } - - fun setSwitchColor(activity : AppCompatActivity, pref : SharedPreferences, root : View?) { - - fun mixColor(col1 : Int, col2 : Int) : Int = Color.rgb( - (Color.red(col1) + Color.red(col2)) ushr 1, - (Color.green(col1) + Color.green(col2)) ushr 1, - (Color.blue(col1) + Color.blue(col2)) ushr 1 - ) - - val colorBg = getAttributeColor(activity, R.attr.colorWindowBackground) - - val colorOn = Pref.ipSwitchOnColor(pref) - - val colorOff = /* Pref.ipSwitchOffColor(pref).notZero() ?: */ - getAttributeColor(activity, android.R.attr.colorPrimary) - - val colorDisabled = mixColor(colorBg, colorOff) - - val colorTrackDisabled = mixColor(colorBg, colorDisabled) - val colorTrackOn = mixColor(colorBg, colorOn) - val colorTrackOff = mixColor(colorBg, colorOff) - - // set Switch Color - // https://stackoverflow.com/a/25635526/9134243 - val thumbStates = ColorStateList( - arrayOf( - intArrayOf(- android.R.attr.state_enabled), - intArrayOf(android.R.attr.state_checked), - intArrayOf() - ), - intArrayOf( - colorDisabled, - colorOn, - colorOff - ) - ) - - val trackStates = ColorStateList( - arrayOf( - intArrayOf(- android.R.attr.state_enabled), - intArrayOf(android.R.attr.state_checked), - intArrayOf() - ), - intArrayOf( - colorTrackDisabled, - colorTrackOn, - colorTrackOff - ) - ) - - root?.scan { - (it as? Switch)?.apply { - if(Build.VERSION.SDK_INT < 23) { - // android 5 - thumbDrawable?.setTintList(thumbStates) - trackDrawable?.setTintList(thumbStates) // not trackState - } else { - // android 6 - thumbTintList = thumbStates - if(Build.VERSION.SDK_INT >= 24) { - // android 7 - trackTintList = trackStates - trackTintMode = PorterDuff.Mode.SRC_OVER - } - } - } - } - } - - internal const val COLOR_DIALOG_ID_FOOTER_BUTTON_BG = 1 - internal const val COLOR_DIALOG_ID_FOOTER_BUTTON_FG = 2 - internal const val COLOR_DIALOG_ID_FOOTER_TAB_BG = 3 - internal const val COLOR_DIALOG_ID_FOOTER_TAB_DIVIDER = 4 - internal const val COLOR_DIALOG_ID_FOOTER_TAB_INDICATOR = 5 - internal const val COLOR_DIALOG_ID_LIST_DIVIDER = 6 - - internal const val COLOR_DIALOG_ID_TOOT_BG_UNLISTED = 7 - internal const val COLOR_DIALOG_ID_TOOT_BG_FOLLOWER = 8 - internal const val COLOR_DIALOG_ID_TOOT_BG_DIRECT_USER = 9 - internal const val COLOR_DIALOG_ID_TOOT_BG_DIRECT_ME = 10 - internal const val COLOR_DIALOG_ID_LINK = 11 - - internal const val COLOR_DIALOG_ID_COLUMN_HEADER_BG = 12 - internal const val COLOR_DIALOG_ID_COLUMN_HEADER_FG = 13 - internal const val COLOR_DIALOG_ID_COLUMN_BG = 14 - internal const val COLOR_DIALOG_ID_COLUMN_ACCT = 15 - internal const val COLOR_DIALOG_ID_COLUMN_TEXT = 16 - - internal const val COLOR_DIALOG_ID_EVENT_BG_BOOST = 17 - internal const val COLOR_DIALOG_ID_EVENT_BG_FAVOURITE = 18 - internal const val COLOR_DIALOG_ID_EVENT_BG_FOLLOW = 19 - internal const val COLOR_DIALOG_ID_EVENT_BG_MENTION = 20 - internal const val COLOR_DIALOG_ID_EVENT_BG_UNFOLLOW = 21 - internal const val COLOR_DIALOG_ID_EVENT_BG_REACTION = 22 - internal const val COLOR_DIALOG_ID_EVENT_BG_QUOTE = 23 - internal const val COLOR_DIALOG_ID_EVENT_BG_VOTE = 24 - internal const val COLOR_DIALOG_ID_EVENT_BG_FOLLOWREQUEST = 25 - - internal const val COLOR_DIALOG_ID_SWITCH_BUTTON = 26 - internal const val COLOR_DIALOG_ID_STATUS_BAR = 27 - internal const val COLOR_DIALOG_ID_NAVIGATION_BAR = 28 - - internal const val REQUEST_CODE_TIMELINE_FONT = 1 - internal const val REQUEST_CODE_TIMELINE_FONT_BOLD = 2 - - private val reLinefeed = Regex("[\\x0d\\x0a]+") - - internal const val STATE_CHOOSE_INTENT_TARGET = "customShareTarget" - - } - - internal lateinit var pref : SharedPreferences - - class BooleanViewInfo( - val info : BooleanPref, - val view : CompoundButton - ) - - private val booleanViewList = ArrayList() - - private lateinit var svContent : View - - private var spBackButtonAction : Spinner? = null - private var spUITheme : Spinner? = null - private var spResizeImage : Spinner? = null - private var spRefreshAfterToot : Spinner? = null - private var spAdditionalButtonsPosition : Spinner? = null - private var spDefaultAccount : Spinner? = null - private var spRepliesCount : Spinner? = null - private var spVisibilityStyle : Spinner? = null - private var spBoostButtonJustify : Spinner? = null - private var spTimeZone : Spinner? = null - private var spJustifyWindowContentPortrait : Spinner? = null - - private var footer_button_bg_color : Int = 0 - private var footer_button_fg_color : Int = 0 - private var footer_tab_bg_color : Int = 0 - private var footer_tab_divider_color : Int = 0 - private var footer_tab_indicator_color : Int = 0 - private var list_divider_color : Int = 0 - private var toot_color_unlisted : Int = 0 - private var toot_color_follower : Int = 0 - private var toot_color_direct_user : Int = 0 - private var toot_color_direct_me : Int = 0 - private var link_color : Int = 0 - - private var event_bg_color_boost : Int = 0 - private var event_bg_color_favourite : Int = 0 - private var event_bg_color_follow : Int = 0 - private var event_bg_color_mention : Int = 0 - private var event_bg_color_unfollow : Int = 0 - private var event_bg_color_reaction : Int = 0 - private var event_bg_color_quote : Int = 0 - private var event_bg_color_vote : Int = 0 - private var event_bg_color_follow_request : Int = 0 - private var switch_button_color = 0 - private var status_bar_color = 0 - private var navigation_bar_color = 0 - - private var color_column_header_bg : Int = 0 - private var color_column_header_fg : Int = 0 - private var color_column_bg : Int = 0 - private var color_column_acct : Int = 0 - private var color_column_text : Int = 0 - - private var ivFooterToot : ImageView? = null - private var ivFooterMenu : ImageView? = null - private var llFooterBG : View? = null - private var vFooterDivider1 : View? = null - private var vFooterDivider2 : View? = null - private var vIndicator : View? = null - - private var etColumnWidth : EditText? = null - private var etMediaThumbHeight : EditText? = null - private var etClientName : EditText? = null - private var etUserAgent : EditText? = null - private var etQuoteNameFormat : EditText? = null - private var etAutoCWLines : EditText? = null - private var etCardDescriptionLength : EditText? = null - private var etMediaSizeMax : EditText? = null - private var etMovieSizeMax : EditText? = null - private var etMediaSizeMaxPixelfed : EditText? = null - private var etRoundRatio : EditText? = null - private var etBoostAlpha : EditText? = null - private var etMediaReadTimeout : EditText? = null - private var tvTranslateAppComponent : TextView? = null - private var tvCustomShare1 : TextView? = null - private var tvCustomShare2 : TextView? = null - private var tvCustomShare3 : TextView? = null - - private var tvTimelineFontUrl : TextView? = null - private var timeline_font : String? = null - private var tvTimelineFontBoldUrl : TextView? = null - private var timeline_font_bold : String? = null - - private var etTimelineFontSize : EditText? = null - private var etAcctFontSize : EditText? = null - private var tvTimelineFontSize : TextView? = null - private var tvAcctFontSize : TextView? = null - private var etTimelineSpacing : EditText? = null - - private var etHeaderTextSize : EditText? = null - private var tvHeaderTextSize : TextView? = null - - private var etAvatarIconSize : EditText? = null - - private var etPullNotificationCheckInterval : EditText? = null - - private var etNotificationTlFontSize : EditText? = null - private var tvNotificationTlFontSize : TextView? = null - private var etNotificationTlIconSize : EditText? = null - - private var etBoostButtonSize : EditText? = null - private var etReplyIconSize : EditText? = null - private var etHeaderIconSize : EditText? = null - private var etStripIconSize : EditText? = null - private var tvUserAgentError : TextView? = null - - private var llColumnHeader : View? = null - private var ivColumnHeader : ImageView? = null - private var tvColumnName : TextView? = null - private var flColumnBackground : View? = null - private var tvSampleAcct : TextView? = null - private var tvSampleContent : TextView? = null - - private var load_busy : Boolean = false - - private var hasTimelineFontUi = false - private var hasFooterColorUi = false - private var hasListDividerColorUi = false - private var hasTootBackgroundColorUi = false - private var hasLinkColorUi = false - private var hasColumnColorDefaultUi = false - - private var customShareTarget : CustomShareTarget? = null - - override fun onResume() { - super.onResume() - - onCustomShareSelected() - } - - override fun onPause() { - super.onPause() - - // DefaultAccount の Spinnerの値を復元するため、このタイミングでも保存することになった - saveUIToData() - - // Pull通知チェック間隔を変更したかもしれないのでジョブを再設定する - try { - PollingWorker.scheduleJob(this, PollingWorker.JOB_POLLING) - } catch(ex : Throwable) { - log.trace(ex, "PollingWorker.scheduleJob failed.") - } - } - - override fun onSaveInstanceState(outState : Bundle) { - super.onSaveInstanceState(outState) - - val sv = customShareTarget?.name - if(sv != null) outState.putString(STATE_CHOOSE_INTENT_TARGET, sv) - - } - - override fun onCreate(savedInstanceState : Bundle?) { - super.onCreate(savedInstanceState) - - App1.setActivityTheme(this) - - pref = Pref.pref(this) - - 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 { - val sv = savedInstanceState.getString(STATE_CHOOSE_INTENT_TARGET) - customShareTarget = CustomShareTarget.values().firstOrNull { it.name == sv } - } catch(ex : Throwable) { - log.e(ex, "can't restore customShareTarget.") - } - } - - setContentView(layoutId) - App1.initEdgeToEdge(this) - - initUI() - - loadUIFromData() - } - - private fun initUI() { - - svContent = findViewById(R.id.svContent) - - setSwitchColor(this, pref, svContent) - - Styler.fixHorizontalPadding(svContent) - - // initialize Switch and CheckBox - for(info in Pref.map.values) { - if(info is BooleanPref && info.id != 0) { - val view = findViewById(info.id) - if(view != null) { - view.setOnCheckedChangeListener(this) - booleanViewList.add(BooleanViewInfo(info, view)) - } - } - } - - val bBefore8 = Build.VERSION.SDK_INT < 26 - for(si in booleanViewList) { - when(si.info) { - Pref.bpNotificationLED, - Pref.bpNotificationVibration, - Pref.bpNotificationSound -> si.view.isEnabled = bBefore8 - } - } - - spBackButtonAction = initSpinner( - R.id.spBackButtonAction - , getString(R.string.ask_always) - , getString(R.string.close_column) - , getString(R.string.open_column_list) - , getString(R.string.app_exit) - ) - - spRepliesCount = initSpinner( - R.id.spRepliesCount - , getString(R.string.replies_count_simple) - , getString(R.string.replies_count_actual) - , getString(R.string.replies_count_none) - ) - - spVisibilityStyle = initSpinner( - R.id.spVisibilityStyle - , getString(R.string.visibility_style_by_account) - , getString(R.string.mastodon) - , getString(R.string.misskey) - ) - spBoostButtonJustify = initSpinner( - R.id.spBoostButtonJustify - , getString(R.string.start) - , getString(R.string.center) - , getString(R.string.end) - ) - spUITheme = initSpinner( - R.id.spUITheme - , getString(R.string.theme_light) - , getString(R.string.theme_dark) - ) - spJustifyWindowContentPortrait = initSpinner( - R.id.spJustifyWindowContentPortrait - , getString(R.string.default_) - , getString(R.string.start) - , getString(R.string.end) - ) - - val resizeList = ActPost.resizeConfigList.map { - when(it.type) { - ResizeType.None -> getString(R.string.dont_resize) - ResizeType.LongSide -> getString(R.string.long_side_pixel, it.size) - ResizeType.SquarePixel -> getString( - R.string.resize_square_pixels, - it.size * it.size, - it.size - ) - } - }.toTypedArray() - - spResizeImage = initSpinner(R.id.spResizeImage, *resizeList) - - spRefreshAfterToot = initSpinner( - R.id.spRefreshAfterToot - , getString(R.string.refresh_scroll_to_toot) - , getString(R.string.refresh_no_scroll) - , getString(R.string.dont_refresh) - ) - - spAdditionalButtonsPosition = initSpinner( - R.id.spAdditionalButtonsPosition - , getString(R.string.top) - , getString(R.string.bottom) - , getString(R.string.start) - , getString(R.string.end) - ) - - spDefaultAccount = findViewById(R.id.spDefaultAccount)?.also { - it.adapter = AccountAdapter() - it.onItemSelectedListener = this@ActAppSettingChild - } - - spTimeZone = findViewById(R.id.spTimeZone)?.also { - it.adapter = TimeZoneAdapter() - it.onItemSelectedListener = this@ActAppSettingChild - } - - - - - intArrayOf( - R.id.btnFooterBackgroundEdit - , R.id.btnFooterBackgroundReset - , R.id.btnFooterForegroundColorEdit - , R.id.btnFooterForegroundColorReset - , R.id.btnTabBackgroundColorEdit - , R.id.btnTabBackgroundColorReset - , R.id.btnTabDividerColorEdit - , R.id.btnTabDividerColorReset - , R.id.btnTabIndicatorColorEdit - , R.id.btnTabIndicatorColorReset - , R.id.btnListDividerColorEdit - , R.id.btnListDividerColorReset - , R.id.btnBackgroundColorUnlistedEdit - , R.id.btnBackgroundColorUnlistedReset - , R.id.btnBackgroundColorFollowerEdit - , R.id.btnBackgroundColorFollowerReset - , R.id.btnBackgroundColorDirectWithUserEdit - , R.id.btnBackgroundColorDirectWithUserReset - , R.id.btnBackgroundColorDirectNoUserEdit - , R.id.btnBackgroundColorDirectNoUserReset - , R.id.btnLinkColorEdit - , R.id.btnLinkColorReset - , R.id.btnTimelineFontEdit - , R.id.btnTimelineFontReset - , R.id.btnTimelineFontBoldEdit - , R.id.btnTimelineFontBoldReset - , R.id.btnCcdHeaderBackgroundEdit - , R.id.btnCcdHeaderBackgroundReset - , R.id.btnCcdHeaderForegroundEdit - , R.id.btnCcdHeaderForegroundReset - , R.id.btnCcdContentBackgroundEdit - , R.id.btnCcdContentBackgroundReset - , R.id.btnCcdContentAcctEdit - , R.id.btnCcdContentAcctReset - , R.id.btnCcdContentTextEdit - , R.id.btnCcdContentTextReset - , R.id.btnInstanceTickerCopyright - , R.id.btnBackgroundColorBoostEdit - , R.id.btnBackgroundColorBoostReset - , R.id.btnBackgroundColorFavouritesEdit - , R.id.btnBackgroundColorFavouritesReset - , R.id.btnBackgroundColorFollowEdit - , R.id.btnBackgroundColorFollowReset - , R.id.btnBackgroundColorMentionEdit - , R.id.btnBackgroundColorMentionReset - , R.id.btnBackgroundColorUnfollowEdit - , R.id.btnBackgroundColorUnfollowReset - , R.id.btnBackgroundColorReactionEdit - , R.id.btnBackgroundColorReactionReset - , R.id.btnBackgroundColorQuotedEdit - , R.id.btnBackgroundColorQuotedReset - , R.id.btnBackgroundColorVotedEdit - , R.id.btnBackgroundColorVotedReset - , R.id.btnBackgroundColorFollowRequestedEdit - , R.id.btnBackgroundColorFollowRequestedReset - , R.id.btnTranslateAppComponentEdit - , R.id.btnTranslateAppComponentReset - , R.id.btnCustomShare1Edit - , R.id.btnCustomShare1Reset - , R.id.btnCustomShare2Edit - , R.id.btnCustomShare2Reset - , R.id.btnCustomShare3Edit - , R.id.btnCustomShare3Reset - , R.id.btnSwitchButtonColorEdit - , R.id.btnSwitchButtonColorReset - , R.id.btnStatusBarColorEdit - , R.id.btnStatusBarColorReset - , R.id.btnNavigationBarColorEdit - , R.id.btnNavigationBarColorReset - ,R.id.btnDrawableList - - ).forEach { - findViewById(it)?.setOnClickListener(this) - } - - hasTimelineFontUi = null != findViewById(R.id.btnTimelineFontEdit) - hasFooterColorUi = null != findViewById(R.id.btnTabDividerColorEdit) - hasListDividerColorUi = null != findViewById(R.id.btnListDividerColorEdit) - hasTootBackgroundColorUi = null != findViewById(R.id.btnBackgroundColorUnlistedEdit) - hasLinkColorUi = null != findViewById(R.id.btnLinkColorEdit) - hasColumnColorDefaultUi = null != findViewById(R.id.btnCcdHeaderBackgroundEdit) - - ivFooterToot = findViewById(R.id.ivFooterToot) - ivFooterMenu = findViewById(R.id.ivFooterMenu) - llFooterBG = findViewById(R.id.llFooterBG) - vFooterDivider1 = findViewById(R.id.vFooterDivider1) - vFooterDivider2 = findViewById(R.id.vFooterDivider2) - vIndicator = findViewById(R.id.vIndicator) - - etColumnWidth = findViewById(R.id.etColumnWidth) - etColumnWidth?.addTextChangedListener(this) - - etMediaThumbHeight = findViewById(R.id.etMediaThumbHeight) - etMediaThumbHeight?.addTextChangedListener(this) - - etClientName = findViewById(R.id.etClientName) - etClientName?.addTextChangedListener(this) - - etUserAgent = findViewById(R.id.etUserAgent) - etUserAgent?.addTextChangedListener(this) - - etQuoteNameFormat = findViewById(R.id.etQuoteNameFormat) - etQuoteNameFormat?.addTextChangedListener(this) - - etAutoCWLines = findViewById(R.id.etAutoCWLines) - etAutoCWLines?.addTextChangedListener(this) - - etCardDescriptionLength = findViewById(R.id.etCardDescriptionLength) - etCardDescriptionLength?.addTextChangedListener(this) - - etMediaSizeMax = findViewById(R.id.etMediaSizeMax) - etMediaSizeMax?.addTextChangedListener(this) - - etMovieSizeMax = findViewById(R.id.etMovieSizeMax) - etMovieSizeMax?.addTextChangedListener(this) - - etMediaSizeMaxPixelfed = findViewById(R.id.etMediaSizeMaxPixelfed) - etMediaSizeMaxPixelfed?.addTextChangedListener(this) - - etRoundRatio = findViewById(R.id.etRoundRatio) - etRoundRatio?.addTextChangedListener(this) - - etBoostAlpha = findViewById(R.id.etBoostAlpha) - etBoostAlpha?.addTextChangedListener(this) - - etMediaReadTimeout = findViewById(R.id.etMediaReadTimeout) - etMediaReadTimeout?.addTextChangedListener(this) - - tvTranslateAppComponent = findViewById(R.id.tvTranslateAppComponent) - tvCustomShare1 = findViewById(R.id.tvCustomShare1) - tvCustomShare2 = findViewById(R.id.tvCustomShare2) - tvCustomShare3 = findViewById(R.id.tvCustomShare3) - - tvTimelineFontSize = findViewById(R.id.tvTimelineFontSize) - tvAcctFontSize = findViewById(R.id.tvAcctFontSize) - tvNotificationTlFontSize = findViewById(R.id.tvNotificationTlFontSize) - - etTimelineSpacing = findViewById(R.id.etTimelineSpacing) - etTimelineFontSize = findViewById(R.id.etTimelineFontSize) - - etTimelineFontSize?.addTextChangedListener( - SizeCheckTextWatcher( - tvTimelineFontSize !!, - etTimelineFontSize !!, - Pref.default_timeline_font_size, - etSpacing = etTimelineSpacing !! - ) - ) - - - etAcctFontSize = findViewById(R.id.etAcctFontSize) - etAcctFontSize?.addTextChangedListener( - SizeCheckTextWatcher( - tvAcctFontSize !!, - etAcctFontSize !!, - Pref.default_acct_font_size - ) - ) - - tvHeaderTextSize = findViewById(R.id.tvHeaderTextSize) - etHeaderTextSize = findViewById(R.id.etHeaderTextSize) - etHeaderTextSize?.addTextChangedListener( - SizeCheckTextWatcher( - tvHeaderTextSize !!, - etHeaderTextSize !!, - Pref.default_header_font_size - ) - ) - - - etNotificationTlFontSize = findViewById(R.id.etNotificationTlFontSize) - etNotificationTlFontSize?.addTextChangedListener( - SizeCheckTextWatcher( - tvNotificationTlFontSize !!, - etNotificationTlFontSize !!, - Pref.default_notification_tl_font_size, - etSpacing = etTimelineSpacing !! - ) - ) - - etTimelineSpacing?.addTextChangedListener( - SizeCheckTextWatcher( - tvTimelineFontSize !!, - etTimelineFontSize !!, - Pref.default_timeline_font_size, - etSpacing = etTimelineSpacing !! - ) - ) - - etTimelineSpacing?.addTextChangedListener( - SizeCheckTextWatcher( - tvNotificationTlFontSize !!, - etNotificationTlFontSize !!, - Pref.default_notification_tl_font_size, - etSpacing = etTimelineSpacing !! - ) - ) - - - etAvatarIconSize = findViewById(R.id.etAvatarIconSize) - etNotificationTlIconSize = findViewById(R.id.etNotificationTlIconSize) - etPullNotificationCheckInterval = findViewById(R.id.etPullNotificationCheckInterval) - - etBoostButtonSize = findViewById(R.id.etBoostButtonSize) - etReplyIconSize = findViewById(R.id.etReplyIconSize) - etHeaderIconSize = findViewById(R.id.etHeaderIconSize) - etStripIconSize = findViewById(R.id.etStripIconSize) - tvTimelineFontUrl = findViewById(R.id.tvTimelineFontUrl) - tvTimelineFontBoldUrl = findViewById(R.id.tvTimelineFontBoldUrl) - - - tvUserAgentError = findViewById(R.id.tvUserAgentError) - - llColumnHeader = findViewById(R.id.llColumnHeader) - ivColumnHeader = findViewById(R.id.ivColumnHeader) - tvColumnName = findViewById(R.id.tvColumnName) - flColumnBackground = findViewById(R.id.flColumnBackground) - tvSampleAcct = findViewById(R.id.tvSampleAcct) - tvSampleContent = findViewById(R.id.tvSampleContent) - - if(Build.VERSION.SDK_INT < 26) { - findViewById(R.id.swSeparateReplyNotificationGroup)?.isEnabled = false - } - } - - private fun initSpinner(@IdRes viewId : Int, vararg captions : String) : Spinner? = - findViewById(viewId)?.apply { - adapter = ArrayAdapter( - this@ActAppSettingChild, - android.R.layout.simple_spinner_item, - arrayOf(*captions) - ).apply { - setDropDownViewResource(R.layout.lv_spinner_dropdown) - } - onItemSelectedListener = this@ActAppSettingChild - } - - private fun loadUIFromData() { - load_busy = true - - pref.edit().also{ e-> - for(si in booleanViewList) { - val curVal = si.info(pref) - si.view.isChecked = curVal - if( curVal == si.info.defVal) si.info.remove(e) - } - }.apply() - - spBackButtonAction?.setSelection(Pref.ipBackButtonAction(pref)) - spRepliesCount?.setSelection(Pref.ipRepliesCount(pref)) - spVisibilityStyle?.setSelection(Pref.ipVisibilityStyle(pref)) - spBoostButtonJustify?.setSelection(Pref.ipBoostButtonJustify(pref)) - spJustifyWindowContentPortrait?.setSelection(Pref.ipJustifyWindowContentPortrait(pref)) - - - spUITheme?.setSelection(Pref.ipUiTheme(pref)) - spResizeImage?.setSelection(Pref.ipResizeImage(pref)) - spRefreshAfterToot?.setSelection(Pref.ipRefreshAfterToot(pref)) - spAdditionalButtonsPosition?.setSelection(Pref.ipAdditionalButtonsPosition(pref)) - - spDefaultAccount?.setSelection( - (spDefaultAccount?.adapter as? AccountAdapter) - ?.getIndexFromId(Pref.lpTabletTootDefaultAccount(pref)) - ?: 0 - ) - - spTimeZone?.setSelection( - (spTimeZone?.adapter as? TimeZoneAdapter) - ?.getIndexFromId(Pref.spTimeZone(pref)) - ?: 0 - ) - - footer_button_bg_color = Pref.ipFooterButtonBgColor(pref) - footer_button_fg_color = Pref.ipFooterButtonFgColor(pref) - footer_tab_bg_color = Pref.ipFooterTabBgColor(pref) - footer_tab_divider_color = Pref.ipFooterTabDividerColor(pref) - footer_tab_indicator_color = Pref.ipFooterTabIndicatorColor(pref) - list_divider_color = Pref.ipListDividerColor(pref) - toot_color_unlisted = Pref.ipTootColorUnlisted(pref) - toot_color_follower = Pref.ipTootColorFollower(pref) - toot_color_direct_user = Pref.ipTootColorDirectUser(pref) - toot_color_direct_me = Pref.ipTootColorDirectMe(pref) - link_color = Pref.ipLinkColor(pref) - - event_bg_color_boost = Pref.ipEventBgColorBoost(pref) - event_bg_color_favourite = Pref.ipEventBgColorFavourite(pref) - event_bg_color_follow = Pref.ipEventBgColorFollow(pref) - event_bg_color_mention = Pref.ipEventBgColorMention(pref) - event_bg_color_unfollow = Pref.ipEventBgColorUnfollow(pref) - event_bg_color_reaction = Pref.ipEventBgColorReaction(pref) - event_bg_color_quote = Pref.ipEventBgColorQuote(pref) - event_bg_color_vote = Pref.ipEventBgColorVote(pref) - event_bg_color_follow_request = Pref.ipEventBgColorFollowRequest(pref) - switch_button_color = Pref.ipSwitchOnColor(pref) - status_bar_color = Pref.ipStatusBarColor(pref) - navigation_bar_color = Pref.ipNavigationBarColor(pref) - - color_column_header_bg = Pref.ipCcdHeaderBg(pref) - color_column_header_fg = Pref.ipCcdHeaderFg(pref) - color_column_bg = Pref.ipCcdContentBg(pref) - color_column_acct = Pref.ipCcdContentAcct(pref) - color_column_text = Pref.ipCcdContentText(pref) - - etColumnWidth?.setText(Pref.spColumnWidth(pref)) - etMediaThumbHeight?.setText(Pref.spMediaThumbHeight(pref)) - etClientName?.setText(Pref.spClientName(pref)) - etUserAgent?.setText(Pref.spUserAgent(pref)) - etQuoteNameFormat?.setText(Pref.spQuoteNameFormat(pref)) - etAutoCWLines?.setText(Pref.spAutoCWLines(pref)) - etCardDescriptionLength?.setText(Pref.spCardDescriptionLength(pref)) - etAvatarIconSize?.setText(Pref.spAvatarIconSize(pref)) - etNotificationTlIconSize?.setText(Pref.spNotificationTlIconSize(pref)) - etBoostButtonSize?.setText(Pref.spBoostButtonSize(pref)) - etReplyIconSize?.setText(Pref.spReplyIconSize(pref)) - etHeaderIconSize?.setText(Pref.spHeaderIconSize(pref)) - etStripIconSize?.setText(Pref.spStripIconSize(pref)) - etPullNotificationCheckInterval?.setText(Pref.spPullNotificationCheckInterval(pref)) - - etMediaSizeMax?.setText(Pref.spMediaSizeMax(pref)) - etMovieSizeMax?.setText(Pref.spMovieSizeMax(pref)) - etMediaSizeMaxPixelfed?.setText(Pref.spMediaSizeMaxPixelfed(pref)) - etRoundRatio?.setText(Pref.spRoundRatio(pref)) - etBoostAlpha?.setText(Pref.spBoostAlpha(pref)) - - etMediaReadTimeout?.setText(Pref.spMediaReadTimeout(pref)) - - showCustomShareIcon(tvTranslateAppComponent, CustomShareTarget.Translate) - showCustomShareIcon(tvCustomShare1, CustomShareTarget.CustomShare1) - showCustomShareIcon(tvCustomShare2, CustomShareTarget.CustomShare2) - showCustomShareIcon(tvCustomShare3, CustomShareTarget.CustomShare3) - - timeline_font = Pref.spTimelineFont(pref) - timeline_font_bold = Pref.spTimelineFontBold(pref) - - etTimelineFontSize?.setText(formatFontSize(Pref.fpTimelineFontSize(pref))) - etAcctFontSize?.setText(formatFontSize(Pref.fpAcctFontSize(pref))) - etNotificationTlFontSize?.setText(formatFontSize(Pref.fpNotificationTlFontSize(pref))) - etHeaderTextSize?.setText(formatFontSize(Pref.fpHeaderTextSize(pref))) - etTimelineSpacing?.setText(Pref.spTimelineSpacing(pref)) - - etUserAgent?.hint = App1.userAgentDefault - - load_busy = false - - showFooterColor() - showTimelineFont(tvTimelineFontUrl, timeline_font) - showTimelineFont(tvTimelineFontBoldUrl, timeline_font_bold) - - showFontSize( - tvTimelineFontSize, - etTimelineFontSize, - Pref.default_timeline_font_size, - etSpacing = etTimelineSpacing - ) - showFontSize(tvAcctFontSize, etAcctFontSize, Pref.default_acct_font_size) - showFontSize( - tvNotificationTlFontSize, - etNotificationTlFontSize, - Pref.default_notification_tl_font_size, - etSpacing = etTimelineSpacing - ) - showFontSize(tvHeaderTextSize, etHeaderTextSize, Pref.default_header_font_size) - - showUserAgentError() - showColumnSample() - showColumnHeaderSample() - } - - private fun saveUIToData() { - if(load_busy) return - - val e = pref.edit() - - for(si in booleanViewList) { - si.info.putOrRemove(e,si.view.isChecked) - } - - spDefaultAccount?.let { - e.put( - Pref.lpTabletTootDefaultAccount, - (it.adapter as AccountAdapter).getIdFromIndex(it.selectedItemPosition) - ) - } - - spTimeZone?.let { - e.put( - Pref.spTimeZone, - (it.adapter as TimeZoneAdapter).getIdFromIndex(it.selectedItemPosition) - ) - } - - - - fun putFontSize(fp : FloatPref, et : EditText?) { - et ?: return - e.put(fp, parseFontSize(et.text.toString().trim())) - } - putFontSize(Pref.fpTimelineFontSize, etTimelineFontSize) - putFontSize(Pref.fpAcctFontSize, etAcctFontSize) - putFontSize(Pref.fpNotificationTlFontSize, etNotificationTlFontSize) - putFontSize(Pref.fpNotificationTlFontSize, etNotificationTlFontSize) - putFontSize(Pref.fpHeaderTextSize, etHeaderTextSize) - - fun putText(sp : StringPref, tv : TextView?, filter : (String) -> String = { it.trim() }) { - tv ?: return - e.put(sp, filter(tv.text.toString())) - } - - putText(Pref.spColumnWidth, etColumnWidth) - putText(Pref.spMediaThumbHeight, etMediaThumbHeight) - putText(Pref.spClientName, etClientName) - putText(Pref.spUserAgent, etUserAgent) { it.replace(reLinefeed, " ").trim() } - putText(Pref.spQuoteNameFormat, etQuoteNameFormat) { it } // don't trim - putText(Pref.spAutoCWLines, etAutoCWLines) - putText(Pref.spCardDescriptionLength, etCardDescriptionLength) - putText(Pref.spAvatarIconSize, etAvatarIconSize) - putText(Pref.spNotificationTlIconSize, etNotificationTlIconSize) - putText(Pref.spBoostButtonSize, etBoostButtonSize) - putText(Pref.spReplyIconSize, etReplyIconSize) - putText(Pref.spHeaderIconSize, etHeaderIconSize) - putText(Pref.spStripIconSize, etStripIconSize) - putText(Pref.spPullNotificationCheckInterval, etPullNotificationCheckInterval) - putText(Pref.spMediaSizeMax, etMediaSizeMax) - putText(Pref.spMovieSizeMax, etMovieSizeMax) - putText(Pref.spMediaSizeMaxPixelfed, etMediaSizeMaxPixelfed) - putText(Pref.spRoundRatio, etRoundRatio) - putText(Pref.spBoostAlpha, etBoostAlpha) - putText(Pref.spMediaReadTimeout, etMediaReadTimeout) - putText(Pref.spTimelineSpacing, etTimelineSpacing) - - fun putIf(hasUi : Boolean, sp : StringPref, value : String) { - if(! hasUi) return - e.put(sp, value) - } - putIf(hasTimelineFontUi, Pref.spTimelineFont, timeline_font ?: "") - putIf(hasTimelineFontUi, Pref.spTimelineFontBold, timeline_font_bold ?: "") - - fun putSpinner(ip : IntPref, sp : Spinner?) { - sp ?: return - e.put(ip, sp.selectedItemPosition) - } - - putSpinner(Pref.ipBackButtonAction, spBackButtonAction) - putSpinner(Pref.ipRepliesCount, spRepliesCount) - putSpinner(Pref.ipVisibilityStyle, spVisibilityStyle) - putSpinner(Pref.ipBoostButtonJustify, spBoostButtonJustify) - putSpinner(Pref.ipJustifyWindowContentPortrait, spJustifyWindowContentPortrait) - - putSpinner(Pref.ipUiTheme, spUITheme) - putSpinner(Pref.ipResizeImage, spResizeImage) - putSpinner(Pref.ipRefreshAfterToot, spRefreshAfterToot) - putSpinner(Pref.ipAdditionalButtonsPosition, spAdditionalButtonsPosition) - - fun putIf(hasUi : Boolean, sp : IntPref, value : Int) { - if(! hasUi) return - e.put(sp, value) - } - - fun put(sp : IntPref, value : Int) = e.put(sp, value) - - putIf(hasFooterColorUi, Pref.ipFooterButtonBgColor, footer_button_bg_color) - putIf(hasFooterColorUi, Pref.ipFooterButtonFgColor, footer_button_fg_color) - putIf(hasFooterColorUi, Pref.ipFooterTabBgColor, footer_tab_bg_color) - putIf(hasFooterColorUi, Pref.ipFooterTabDividerColor, footer_tab_divider_color) - putIf(hasFooterColorUi, Pref.ipFooterTabIndicatorColor, footer_tab_indicator_color) - - putIf(hasListDividerColorUi, Pref.ipListDividerColor, list_divider_color) - - if(hasTootBackgroundColorUi) { - put(Pref.ipTootColorUnlisted, toot_color_unlisted) - put(Pref.ipTootColorFollower, toot_color_follower) - put(Pref.ipTootColorDirectUser, toot_color_direct_user) - put(Pref.ipTootColorDirectMe, toot_color_direct_me) - - put(Pref.ipEventBgColorBoost, event_bg_color_boost) - put(Pref.ipEventBgColorFavourite, event_bg_color_favourite) - put(Pref.ipEventBgColorFollow, event_bg_color_follow) - put(Pref.ipEventBgColorMention, event_bg_color_mention) - put(Pref.ipEventBgColorUnfollow, event_bg_color_unfollow) - put(Pref.ipEventBgColorReaction, event_bg_color_reaction) - put(Pref.ipEventBgColorQuote, event_bg_color_quote) - put(Pref.ipEventBgColorVote, event_bg_color_vote) - put(Pref.ipEventBgColorFollowRequest, event_bg_color_follow_request) - put(Pref.ipSwitchOnColor, switch_button_color) - put(Pref.ipStatusBarColor, status_bar_color) - put(Pref.ipNavigationBarColor, navigation_bar_color) - } - - - - putIf(hasLinkColorUi, Pref.ipLinkColor, link_color) - - putIf(hasColumnColorDefaultUi, Pref.ipCcdHeaderBg, color_column_header_bg) - putIf(hasColumnColorDefaultUi, Pref.ipCcdHeaderFg, color_column_header_fg) - putIf(hasColumnColorDefaultUi, Pref.ipCcdContentBg, color_column_bg) - putIf(hasColumnColorDefaultUi, Pref.ipCcdContentAcct, color_column_acct) - putIf(hasColumnColorDefaultUi, Pref.ipCcdContentText, color_column_text) - - e.apply() - showUserAgentError() - } - - private fun showUserAgentError() { - etUserAgent?.let { et -> - val m = App1.reNotAllowedInUserAgent.matcher(et.text.toString()) - tvUserAgentError !!.text = when(m.find()) { - true -> getString(R.string.user_agent_error, m.group()) - else -> "" - } - } - } - - private fun showColumnHeaderSample() { - llColumnHeader ?: return - - val header_bg = when { - color_column_header_bg != 0 -> color_column_header_bg - else -> getAttributeColor(this, R.attr.color_column_header) - } - - val header_fg = when { - color_column_header_fg != 0 -> color_column_header_fg - else -> getAttributeColor(this, R.attr.colorColumnHeaderName) - } - - llColumnHeader?.backgroundDrawable = getAdaptiveRippleDrawable(header_bg, header_fg) - - tvColumnName?.textColor = header_fg - ivColumnHeader?.setImageResource(R.drawable.ic_bike) - ivColumnHeader?.imageTintList = ColorStateList.valueOf(header_fg) - } - - private fun showColumnSample() { - flColumnBackground ?: return - - flColumnBackground?.backgroundColor = color_column_bg // may 0 - - tvSampleAcct?.textColor = color_column_acct.notZero() - ?: getAttributeColor(this, R.attr.colorTimeSmall) - - tvSampleContent?.textColor = color_column_text.notZero() - ?: getAttributeColor(this, R.attr.colorContentText) - - } - - override fun onCheckedChanged(buttonView : CompoundButton, isChecked : Boolean) { - saveUIToData() - } - - override fun onItemSelected(parent : AdapterView<*>, view : View?, position : Int, id : Long) { - // view may null. - saveUIToData() - } - - override fun onNothingSelected(parent : AdapterView<*>) {} - - override fun onClick(v : View) { - when(v.id) { - - R.id.btnFooterBackgroundEdit -> openColorPicker( - COLOR_DIALOG_ID_FOOTER_BUTTON_BG, - footer_button_bg_color, - false - ) - - R.id.btnFooterBackgroundReset -> { - footer_button_bg_color = 0 - saveUIToData() - showFooterColor() - } - - R.id.btnFooterForegroundColorEdit -> openColorPicker( - COLOR_DIALOG_ID_FOOTER_BUTTON_FG, - footer_button_fg_color, - false - ) - - R.id.btnFooterForegroundColorReset -> { - footer_button_fg_color = 0 - saveUIToData() - showFooterColor() - } - - R.id.btnTabBackgroundColorEdit -> openColorPicker( - COLOR_DIALOG_ID_FOOTER_TAB_BG, - footer_tab_bg_color, - false - ) - - R.id.btnTabBackgroundColorReset -> { - footer_tab_bg_color = 0 - saveUIToData() - showFooterColor() - } - - R.id.btnTabDividerColorEdit -> openColorPicker( - COLOR_DIALOG_ID_FOOTER_TAB_DIVIDER, - footer_tab_divider_color, - false - ) - - R.id.btnTabDividerColorReset -> { - footer_tab_divider_color = 0 - saveUIToData() - showFooterColor() - } - - R.id.btnTabIndicatorColorEdit -> openColorPicker( - COLOR_DIALOG_ID_FOOTER_TAB_INDICATOR, - footer_tab_indicator_color, - true - ) - - R.id.btnTabIndicatorColorReset -> { - footer_tab_indicator_color = 0 - saveUIToData() - showFooterColor() - } - - R.id.btnListDividerColorEdit -> openColorPicker( - COLOR_DIALOG_ID_LIST_DIVIDER, - list_divider_color, - true - ) - - R.id.btnListDividerColorReset -> { - list_divider_color = 0 - saveUIToData() - } - - R.id.btnBackgroundColorUnlistedEdit -> openColorPicker( - COLOR_DIALOG_ID_TOOT_BG_UNLISTED, - toot_color_unlisted, - true - ) - - R.id.btnBackgroundColorUnlistedReset -> { - toot_color_unlisted = 0 - saveUIToData() - } - - R.id.btnBackgroundColorFollowerEdit -> openColorPicker( - COLOR_DIALOG_ID_TOOT_BG_FOLLOWER, - toot_color_follower, - true - ) - - R.id.btnBackgroundColorFollowerReset -> { - toot_color_follower = 0 - saveUIToData() - } - - R.id.btnBackgroundColorDirectWithUserEdit -> openColorPicker( - COLOR_DIALOG_ID_TOOT_BG_DIRECT_USER, - toot_color_direct_user, - true - ) - - R.id.btnBackgroundColorDirectWithUserReset -> { - toot_color_direct_user = 0 - saveUIToData() - } - - R.id.btnBackgroundColorDirectNoUserEdit -> openColorPicker( - COLOR_DIALOG_ID_TOOT_BG_DIRECT_ME, - toot_color_direct_me, - true - ) - - R.id.btnBackgroundColorDirectNoUserReset -> { - toot_color_direct_me = 0 - saveUIToData() - } - - R.id.btnLinkColorEdit -> openColorPicker( - COLOR_DIALOG_ID_LINK, - link_color, - true - ) - - R.id.btnLinkColorReset -> { - link_color = 0 - saveUIToData() - } - - R.id.btnCcdHeaderBackgroundEdit -> openColorPicker( - COLOR_DIALOG_ID_COLUMN_HEADER_BG, - color_column_header_bg, - false - ) - - R.id.btnCcdHeaderBackgroundReset -> { - color_column_header_bg = 0 - saveUIToData() - showColumnHeaderSample() - } - - R.id.btnCcdHeaderForegroundEdit -> openColorPicker( - COLOR_DIALOG_ID_COLUMN_HEADER_FG, - color_column_header_fg, - false - ) - - R.id.btnCcdHeaderForegroundReset -> { - color_column_header_fg = 0 - saveUIToData() - showColumnHeaderSample() - } - - R.id.btnCcdContentBackgroundEdit -> openColorPicker( - COLOR_DIALOG_ID_COLUMN_BG, - color_column_bg, - false - ) - - R.id.btnCcdContentBackgroundReset -> { - color_column_bg = 0 - saveUIToData() - showColumnSample() - } - - R.id.btnCcdContentAcctEdit -> openColorPicker( - COLOR_DIALOG_ID_COLUMN_ACCT, - color_column_acct, - true - ) - - R.id.btnCcdContentAcctReset -> { - color_column_acct = 0 - saveUIToData() - showColumnSample() - } - - R.id.btnCcdContentTextEdit -> openColorPicker( - COLOR_DIALOG_ID_COLUMN_TEXT, - color_column_text, - true - ) - - R.id.btnCcdContentTextReset -> { - color_column_text = 0 - saveUIToData() - showColumnSample() - } - - R.id.btnTimelineFontReset -> { - timeline_font = "" - saveUIToData() - showTimelineFont(tvTimelineFontUrl, timeline_font) - } - - R.id.btnTimelineFontEdit -> try { - val intent = intentOpenDocument("*/*") - startActivityForResult(intent, REQUEST_CODE_TIMELINE_FONT) - } catch(ex : Throwable) { - showToast(this, ex, "could not open picker for font") - } - - R.id.btnTimelineFontBoldReset -> { - timeline_font_bold = "" - saveUIToData() - showTimelineFont(tvTimelineFontBoldUrl, timeline_font_bold) - } - - R.id.btnTimelineFontBoldEdit -> try { - val intent = intentOpenDocument("*/*") - startActivityForResult(intent, REQUEST_CODE_TIMELINE_FONT_BOLD) - } catch(ex : Throwable) { - showToast(this, ex, "could not open picker for font") - } - - R.id.btnInstanceTickerCopyright -> App1.openBrowser( - this@ActAppSettingChild, - "https://github.com/MiyonMiyon/InstanceTicker_List" - ) - - R.id.btnBackgroundColorBoostEdit -> openColorPicker( - COLOR_DIALOG_ID_EVENT_BG_BOOST, - event_bg_color_boost, - true - ) - - R.id.btnBackgroundColorBoostReset -> { - event_bg_color_boost = 0 - saveUIToData() - } - - R.id.btnBackgroundColorFavouritesEdit -> openColorPicker( - COLOR_DIALOG_ID_EVENT_BG_FAVOURITE, - event_bg_color_favourite, - true - ) - - R.id.btnBackgroundColorFavouritesReset -> { - event_bg_color_favourite = 0 - saveUIToData() - } - - R.id.btnBackgroundColorFollowEdit -> openColorPicker( - COLOR_DIALOG_ID_EVENT_BG_FOLLOW, - event_bg_color_follow, - true - ) - - R.id.btnBackgroundColorFollowReset -> { - event_bg_color_follow = 0 - saveUIToData() - } - - R.id.btnBackgroundColorMentionEdit -> openColorPicker( - COLOR_DIALOG_ID_EVENT_BG_MENTION, - event_bg_color_mention, - true - ) - - R.id.btnBackgroundColorMentionReset -> { - event_bg_color_mention = 0 - saveUIToData() - } - - R.id.btnBackgroundColorUnfollowEdit -> openColorPicker( - COLOR_DIALOG_ID_EVENT_BG_UNFOLLOW, - event_bg_color_unfollow, - true - ) - - R.id.btnBackgroundColorUnfollowReset -> { - event_bg_color_unfollow = 0 - saveUIToData() - } - - R.id.btnBackgroundColorReactionEdit -> openColorPicker( - COLOR_DIALOG_ID_EVENT_BG_REACTION, - event_bg_color_reaction, - true - ) - - R.id.btnBackgroundColorReactionReset -> { - event_bg_color_reaction = 0 - saveUIToData() - } - - R.id.btnBackgroundColorQuotedEdit -> openColorPicker( - COLOR_DIALOG_ID_EVENT_BG_QUOTE, - event_bg_color_quote, - true - ) - - R.id.btnBackgroundColorQuotedReset -> { - event_bg_color_quote = 0 - saveUIToData() - } - - R.id.btnBackgroundColorVotedEdit -> openColorPicker( - COLOR_DIALOG_ID_EVENT_BG_VOTE, - event_bg_color_vote, - true - ) - - R.id.btnBackgroundColorVotedReset -> { - event_bg_color_vote = 0 - saveUIToData() - } - - R.id.btnBackgroundColorFollowRequestedEdit -> openColorPicker( - COLOR_DIALOG_ID_EVENT_BG_FOLLOWREQUEST, - event_bg_color_follow_request, - true - ) - - R.id.btnBackgroundColorFollowRequestedReset -> { - event_bg_color_follow_request = 0 - saveUIToData() - } - - R.id.btnSwitchButtonColorEdit -> openColorPicker( - COLOR_DIALOG_ID_SWITCH_BUTTON, - switch_button_color, - true - ) - - R.id.btnSwitchButtonColorReset -> { - switch_button_color = Pref.ipSwitchOnColor.defVal - saveUIToData() - setSwitchColor(this, pref, svContent) - - } - - R.id.btnStatusBarColorEdit -> openColorPicker( - COLOR_DIALOG_ID_STATUS_BAR, - status_bar_color, - false - ) - - R.id.btnStatusBarColorReset -> { - status_bar_color = Pref.ipStatusBarColor.defVal - saveUIToData() - App1.setStatusBarColor(this) - - } - - R.id.btnNavigationBarColorEdit -> openColorPicker( - COLOR_DIALOG_ID_NAVIGATION_BAR, - navigation_bar_color, - false - ) - - R.id.btnNavigationBarColorReset -> { - navigation_bar_color = Pref.ipNavigationBarColor.defVal - saveUIToData() - App1.setStatusBarColor(this) - } - - R.id.btnTranslateAppComponentEdit -> openCustomShareChooser(CustomShareTarget.Translate) - R.id.btnCustomShare1Edit -> openCustomShareChooser(CustomShareTarget.CustomShare1) - R.id.btnCustomShare2Edit -> openCustomShareChooser(CustomShareTarget.CustomShare2) - R.id.btnCustomShare3Edit -> openCustomShareChooser(CustomShareTarget.CustomShare3) - - R.id.btnTranslateAppComponentReset -> setCustomShare(CustomShareTarget.Translate, "") - R.id.btnCustomShare1Reset -> setCustomShare(CustomShareTarget.CustomShare1, "") - R.id.btnCustomShare2Reset -> setCustomShare(CustomShareTarget.CustomShare2, "") - R.id.btnCustomShare3Reset -> setCustomShare(CustomShareTarget.CustomShare3, "") - - R.id.btnDrawableList -> startActivity(Intent(this,ActDrawableList::class.java)) - } - } - - override fun onActivityResult(requestCode : Int, resultCode : Int, data : Intent?) { - if(resultCode == RESULT_OK && data != null && requestCode == REQUEST_CODE_TIMELINE_FONT) { - data.handleGetContentResult(contentResolver).firstOrNull()?.uri?.let { - val file = saveTimelineFont(it, "TimelineFont") - if(file != null) { - timeline_font = file.absolutePath - saveUIToData() - showTimelineFont(tvTimelineFontUrl, timeline_font) - } - } - } else if(resultCode == RESULT_OK && data != null && requestCode == REQUEST_CODE_TIMELINE_FONT_BOLD) { - data.handleGetContentResult(contentResolver).firstOrNull()?.uri?.let { - val file = saveTimelineFont(it, "TimelineFontBold") - if(file != null) { - timeline_font_bold = file.absolutePath - saveUIToData() - showTimelineFont(tvTimelineFontBoldUrl, timeline_font_bold) - } - } - } - super.onActivityResult(requestCode, resultCode, data) - } - - private fun openColorPicker(id : Int, color : Int, bShowAlphaSlider : Boolean) { - val builder = ColorPickerDialog.newBuilder() - .setDialogType(ColorPickerDialog.TYPE_CUSTOM) - .setAllowPresets(true) - .setShowAlphaSlider(bShowAlphaSlider) - .setDialogId(id) - if(color != 0) builder.setColor(color) - builder.show(this) - } - - override fun onColorSelected(dialogId : Int, @ColorInt colorSelected : Int) { - - val colorOpaque = colorSelected or Color.BLACK - - val colorAlpha = colorSelected.notZero() ?: 0x01000000 - - when(dialogId) { - - COLOR_DIALOG_ID_FOOTER_BUTTON_BG -> { - footer_button_bg_color = colorOpaque - saveUIToData() - showFooterColor() - } - - COLOR_DIALOG_ID_FOOTER_BUTTON_FG -> { - footer_button_fg_color = colorOpaque - saveUIToData() - showFooterColor() - } - - COLOR_DIALOG_ID_FOOTER_TAB_BG -> { - footer_tab_bg_color = colorOpaque - saveUIToData() - showFooterColor() - } - - COLOR_DIALOG_ID_FOOTER_TAB_DIVIDER -> { - footer_tab_divider_color = colorOpaque - saveUIToData() - showFooterColor() - } - - COLOR_DIALOG_ID_FOOTER_TAB_INDICATOR -> { - footer_tab_indicator_color = colorAlpha - saveUIToData() - showFooterColor() - } - - COLOR_DIALOG_ID_LIST_DIVIDER -> { - list_divider_color = colorAlpha - saveUIToData() - } - - COLOR_DIALOG_ID_TOOT_BG_UNLISTED -> { - toot_color_unlisted = colorAlpha - saveUIToData() - } - - COLOR_DIALOG_ID_TOOT_BG_FOLLOWER -> { - toot_color_follower = colorAlpha - saveUIToData() - } - - COLOR_DIALOG_ID_TOOT_BG_DIRECT_USER -> { - toot_color_direct_user = colorAlpha - saveUIToData() - } - - COLOR_DIALOG_ID_TOOT_BG_DIRECT_ME -> { - toot_color_direct_me = colorAlpha - saveUIToData() - } - - COLOR_DIALOG_ID_LINK -> { - link_color = colorAlpha - saveUIToData() - } - - COLOR_DIALOG_ID_COLUMN_HEADER_BG -> { - color_column_header_bg = colorOpaque - saveUIToData() - showColumnHeaderSample() - } - - COLOR_DIALOG_ID_COLUMN_HEADER_FG -> { - color_column_header_fg = colorOpaque - saveUIToData() - showColumnHeaderSample() - } - - COLOR_DIALOG_ID_COLUMN_BG -> { - color_column_bg = colorOpaque - saveUIToData() - showColumnSample() - } - - COLOR_DIALOG_ID_COLUMN_ACCT -> { - color_column_acct = colorAlpha - saveUIToData() - showColumnSample() - } - - COLOR_DIALOG_ID_COLUMN_TEXT -> { - color_column_text = colorAlpha - saveUIToData() - showColumnSample() - } - - COLOR_DIALOG_ID_EVENT_BG_BOOST -> { - event_bg_color_boost = colorAlpha - saveUIToData() - } - - COLOR_DIALOG_ID_EVENT_BG_FAVOURITE -> { - event_bg_color_favourite = colorAlpha - saveUIToData() - } - - COLOR_DIALOG_ID_EVENT_BG_FOLLOW -> { - event_bg_color_follow = colorAlpha - saveUIToData() - } - - COLOR_DIALOG_ID_EVENT_BG_MENTION -> { - event_bg_color_mention = colorAlpha - saveUIToData() - } - - COLOR_DIALOG_ID_EVENT_BG_UNFOLLOW -> { - event_bg_color_unfollow = colorAlpha - saveUIToData() - } - - COLOR_DIALOG_ID_EVENT_BG_REACTION -> { - event_bg_color_reaction = colorAlpha - saveUIToData() - } - - COLOR_DIALOG_ID_EVENT_BG_QUOTE -> { - event_bg_color_quote = colorAlpha - saveUIToData() - } - - COLOR_DIALOG_ID_EVENT_BG_VOTE -> { - event_bg_color_vote = colorAlpha - saveUIToData() - } - - COLOR_DIALOG_ID_EVENT_BG_FOLLOWREQUEST -> { - event_bg_color_follow_request = colorAlpha - saveUIToData() - } - - COLOR_DIALOG_ID_SWITCH_BUTTON -> { - switch_button_color = colorOpaque - saveUIToData() - setSwitchColor(this, pref, svContent) - } - - COLOR_DIALOG_ID_STATUS_BAR -> { - status_bar_color = colorOpaque - saveUIToData() - App1.setStatusBarColor(this) - } - - COLOR_DIALOG_ID_NAVIGATION_BAR -> { - navigation_bar_color = colorOpaque - saveUIToData() - App1.setStatusBarColor(this) - } - } - } - - override fun onDialogDismissed(dialogId : Int) {} - - private fun showFooterColor() { - ivFooterToot ?: return - - val colorBg = footer_button_bg_color.notZero() ?: getAttributeColor( - this, - R.attr.colorStatusButtonsPopupBg - ) - val colorRipple = - footer_button_fg_color.notZero() ?: getAttributeColor(this, R.attr.colorRippleEffect) - ivFooterToot?.backgroundDrawable = getAdaptiveRippleDrawable(colorBg, colorRipple) - ivFooterMenu?.backgroundDrawable = getAdaptiveRippleDrawable(colorBg, colorRipple) - - val csl = ColorStateList.valueOf( - footer_button_fg_color.notZero() - ?: getAttributeColor(this, R.attr.colorVectorDrawable) - ) - ivFooterToot?.imageTintList = csl - ivFooterMenu?.imageTintList = csl - - llFooterBG?.setBackgroundColor( - footer_tab_bg_color.notZero() - ?: getAttributeColor(this, R.attr.colorColumnStripBackground) - ) - - val c = - footer_tab_divider_color.notZero() ?: getAttributeColor(this, R.attr.colorImageButton) - vFooterDivider1?.setBackgroundColor(c) - vFooterDivider2?.setBackgroundColor(c) - - vIndicator?.setBackgroundColor( - footer_tab_indicator_color.notZero() - ?: getAttributeColor(this, R.attr.colorAccent) - ) - } - - override fun beforeTextChanged(s : CharSequence, start : Int, count : Int, after : Int) { - - } - - override fun onTextChanged(s : CharSequence, start : Int, before : Int, count : Int) { - - } - - override fun afterTextChanged(s : Editable) { - saveUIToData() - } - - private inner class SizeCheckTextWatcher internal constructor( - internal val sample : TextView, - internal val et : EditText, - internal val default_size_sp : Float, - internal val etSpacing : EditText? = null - ) : TextWatcher { - - override fun beforeTextChanged(s : CharSequence, start : Int, count : Int, after : Int) { - - } - - override fun onTextChanged(s : CharSequence, start : Int, before : Int, count : Int) { - - } - - override fun afterTextChanged(s : Editable) { - saveUIToData() - showFontSize(sample, et, default_size_sp, etSpacing) - } - } - - private fun formatFontSize(fv : Float) : String { - return if(fv.isNaN()) { - "" - } else { - String.format(Locale.getDefault(), "%.1f", fv) - } - } - - private fun parseFontSize(src : String) : Float { - try { - if(src.isNotEmpty()) { - val f = NumberFormat.getInstance(Locale.getDefault()).parse(src)?.toFloat() - return when { - f == null -> Float.NaN - f.isNaN() -> Float.NaN - f < 0f -> 0f - f > 999f -> 999f - else -> f - } - } - } catch(ex : Throwable) { - log.trace(ex) - } - - return Float.NaN - } - - private val defaultLineSpacingExtra = SparseArray() - private val defaultLineSpacingMultiplier = SparseArray() - - - private fun showFontSize( - sample : TextView?, - et : EditText?, - default_sp : Float, - etSpacing : EditText? =null - ) { - sample ?: return - et ?: return - var fv = parseFontSize(et.text.toString().trim()) - if(fv.isNaN()) { - sample.textSize = default_sp - } else { - if(fv < 1f) fv = 1f - sample.textSize = fv - } - if( etSpacing != null){ - var defaultExtra = defaultLineSpacingExtra[sample.id] - if(defaultExtra == null){ - defaultExtra = sample.lineSpacingExtra - defaultLineSpacingExtra.put(sample.id, defaultExtra) - } - var defaultMultiplier = defaultLineSpacingMultiplier[sample.id] - if(defaultMultiplier == null){ - defaultMultiplier = sample.lineSpacingMultiplier - defaultLineSpacingMultiplier.put(sample.id, defaultMultiplier) - } - - val spacing = etSpacing.text.toString().toFloatOrNull() - if( spacing ==null || !spacing.isFinite()){ - sample.setLineSpacing(defaultExtra,defaultMultiplier) - }else{ - sample.setLineSpacing(0f,spacing) - } - } - } - - private fun showTimelineFont( - tvFontUrl : TextView?, - font_url : String? - ) { - tvFontUrl ?: return - - try { - if(font_url?.isNotEmpty() == true) { - - tvFontUrl.typeface = Typeface.DEFAULT - val face = Typeface.createFromFile(font_url) - tvFontUrl.typeface = face - tvFontUrl.text = font_url - return - } - } catch(ex : Throwable) { - log.trace(ex) - } - - // fallback - tvFontUrl.text = getString(R.string.not_selected) - tvFontUrl.typeface = Typeface.DEFAULT - } - - private fun saveTimelineFont(uri : Uri?, file_name : String) : File? { - try { - if(uri == null) { - showToast(this, false, "missing uri.") - return null - } - - contentResolver.takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION) - - val dir = filesDir - - dir.mkdir() - - val tmp_file = File(dir, "$file_name.tmp") - - val source : InputStream? = contentResolver.openInputStream(uri) - if(source == null) { - showToast(this, false, "openInputStream returns null. uri=%s", uri) - return null - } else { - source.use { inStream -> - FileOutputStream(tmp_file).use { outStream -> - IOUtils.copy(inStream, outStream) - } - } - } - - val face = Typeface.createFromFile(tmp_file) - if(face == null) { - showToast(this, false, "Typeface.createFromFile() failed.") - return null - } - - val file = File(dir, file_name) - if(! tmp_file.renameTo(file)) { - showToast(this, false, "File operation failed.") - return null - } - - return file - } catch(ex : Throwable) { - log.trace(ex) - showToast(this, ex, "saveTimelineFont failed.") - return null - } - - } - - private inner class AccountAdapter internal constructor() : BaseAdapter() { - - internal val list = ArrayList() - - init { - for(a in SavedAccount.loadAccountList(this@ActAppSettingChild)) { - if(a.isPseudo) continue - list.add(a) - } - SavedAccount.sort(list) - } - - override fun getCount() : Int { - return 1 + list.size - } - - override fun getItem(position : Int) : Any? { - return if(position == 0) null else list[position - 1] - } - - 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 - ) - view.findViewById(android.R.id.text1).text = - if(position == 0) - getString(R.string.ask_always) - else - AcctColor.getNickname(list[position - 1].acct) - return view - } - - override fun getDropDownView(position : Int, viewOld : View?, parent : ViewGroup) : View { - val view = - viewOld ?: layoutInflater.inflate(R.layout.lv_spinner_dropdown, parent, false) - view.findViewById(android.R.id.text1).text = - if(position == 0) - getString(R.string.ask_always) - else - AcctColor.getNickname(list[position - 1].acct) - return view - } - - internal fun getIndexFromId(db_id : Long) : Int { - var i = 0 - val ie = list.size - while(i < ie) { - if(list[i].db_id == db_id) return i + 1 - ++ i - } - return 0 - } - - internal fun getIdFromIndex(position : Int) : Long { - return if(position > 0) list[position - 1].db_id else - 1L - } - } - - private class Item( - val id : String, - val caption : String, - val offset : Int - ) - - private inner class TimeZoneAdapter internal constructor() : BaseAdapter() { - - internal val list = ArrayList() - - 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) { - 0L -> String.format("(UTC\u00B100:00) %s %s", tz.id, tz.displayName) - - else -> { - - val format = if(offset > 0) - "(UTC+%02d:%02d) %s %s" - else - "(UTC-%02d:%02d) %s %s" - - 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) - } - } - if(null == list.find { it.caption == caption }) { - list.add(Item(id, caption, tz.rawOffset)) - } - } - - list.sortWith(Comparator { 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(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(android.R.id.text1).text = item.caption - return view - } - - internal fun getIndexFromId(tz_id : String) : Int { - val index = list.indexOfFirst { it.id == tz_id } - return if(index == - 1) 0 else index - } - - internal fun getIdFromIndex(position : Int) : String { - return list[position].id - } - } - - private fun openCustomShareChooser(target : CustomShareTarget) { - - val intent = Intent() - intent.action = Intent.ACTION_SEND - intent.type = "text/plain" - intent.putExtra(Intent.EXTRA_TEXT, getString(R.string.content_sample)) - - // このifはwhenにしてはならない。APIバージョン関連の警告が出てしまう - @Suppress("CascadeIf") - if(intent.resolveActivity(packageManager) == null) { - // ACTION_SENDを受け取れるアプリがインストールされてない - showToast(this, true, getString(R.string.missing_app_can_receive_action_send)) - } else if(Build.VERSION.SDK_INT <= 21) { - // createChooserにIntentSenderを指定できるのはAndroid 22以降 - showToast( - this, - true, - getString(R.string.app_chooser_works_android_5_1) - ) - } else try { - customShareTarget = target - ChooseReceiver.lastComponentName = null - ChooseReceiver.setCallback { onCustomShareSelected() } - - val receiver = Intent(this, ChooseReceiver::class.java) - val pendingIntent = PendingIntent.getBroadcast( - this, - 1, - receiver, - PendingIntent.FLAG_UPDATE_CURRENT - ) - startActivity( - Intent.createChooser( - intent, - getString(R.string.select_destination_app_and_back), - pendingIntent.intentSender - ) - ) - - } catch(ex : Throwable) { - log.trace(ex) - showToast(this, ex, "openCustomShareChooser failed.") - } - } - - private fun onCustomShareSelected() { - if(isDestroyed) return - - val cn = ChooseReceiver.lastComponentName - if(cn != null) { - ChooseReceiver.lastComponentName = null - setCustomShare(customShareTarget, "${cn.packageName}/${cn.className}") - } - } - - private fun setCustomShare(target : CustomShareTarget?, value : String) { - - target ?: return - - val sp : StringPref - val tv : TextView? - - when(target) { - - CustomShareTarget.Translate -> { - sp = Pref.spTranslateAppComponent - tv = tvTranslateAppComponent - } - - CustomShareTarget.CustomShare1 -> { - sp = Pref.spCustomShare1 - tv = tvCustomShare1 - } - - CustomShareTarget.CustomShare2 -> { - sp = Pref.spCustomShare2 - tv = tvCustomShare2 - } - - CustomShareTarget.CustomShare3 -> { - sp = Pref.spCustomShare3 - tv = tvCustomShare3 - } - } - pref.edit().put(sp, value).apply() - showCustomShareIcon(tv, target) - } - - private fun showCustomShareIcon(tv : TextView?, target : CustomShareTarget) { - - tv ?: return - - val cn = CustomShare.getCustomShareComponentName(pref, target) - val (label, icon) = CustomShare.getInfo(packageManager, cn) - - tv.text = label ?: getString(R.string.not_selected) - - tv.setCompoundDrawablesRelativeWithIntrinsicBounds(icon, null, null, null) - } - -} diff --git a/app/src/main/java/jp/juggler/subwaytooter/App1.kt b/app/src/main/java/jp/juggler/subwaytooter/App1.kt index d0764774..62aecdeb 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/App1.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/App1.kt @@ -7,14 +7,18 @@ import android.content.ComponentName import android.content.Context import android.content.Intent import android.content.SharedPreferences +import android.content.res.ColorStateList import android.database.sqlite.SQLiteDatabase import android.database.sqlite.SQLiteOpenHelper import android.graphics.Color +import android.graphics.PorterDuff import android.net.Uri import android.os.Build import android.util.Log import android.view.View import android.view.WindowManager +import android.widget.Switch +import androidx.appcompat.app.AppCompatActivity import androidx.browser.customtabs.CustomTabsIntent import com.bumptech.glide.Glide import com.bumptech.glide.GlideBuilder @@ -741,5 +745,143 @@ class App1 : Application() { } // else: need restart app. } } + + + fun setSwitchColor1( + activity : AppCompatActivity, + pref : SharedPreferences, + view : Switch? + ) { + fun mixColor(col1 : Int, col2 : Int) : Int = Color.rgb( + (Color.red(col1) + Color.red(col2)) ushr 1, + (Color.green(col1) + Color.green(col2)) ushr 1, + (Color.blue(col1) + Color.blue(col2)) ushr 1 + ) + + val colorBg = getAttributeColor(activity, R.attr.colorWindowBackground) + + val colorOn = Pref.ipSwitchOnColor(pref) + + val colorOff = /* Pref.ipSwitchOffColor(pref).notZero() ?: */ + getAttributeColor(activity, android.R.attr.colorPrimary) + + val colorDisabled = mixColor(colorBg, colorOff) + + val colorTrackDisabled = mixColor(colorBg, colorDisabled) + val colorTrackOn = mixColor(colorBg, colorOn) + val colorTrackOff = mixColor(colorBg, colorOff) + + // set Switch Color + // https://stackoverflow.com/a/25635526/9134243 + val thumbStates = ColorStateList( + arrayOf( + intArrayOf(- android.R.attr.state_enabled), + intArrayOf(android.R.attr.state_checked), + intArrayOf() + ), + intArrayOf( + colorDisabled, + colorOn, + colorOff + ) + ) + + val trackStates = ColorStateList( + arrayOf( + intArrayOf(- android.R.attr.state_enabled), + intArrayOf(android.R.attr.state_checked), + intArrayOf() + ), + intArrayOf( + colorTrackDisabled, + colorTrackOn, + colorTrackOff + ) + ) + + view?.apply { + if(Build.VERSION.SDK_INT < 23) { + // android 5 + thumbDrawable?.setTintList(thumbStates) + trackDrawable?.setTintList(thumbStates) // not trackState + } else { + // android 6 + thumbTintList = thumbStates + if(Build.VERSION.SDK_INT >= 24) { + // android 7 + trackTintList = trackStates + trackTintMode = PorterDuff.Mode.SRC_OVER + } + } + } + } + + fun setSwitchColor(activity : AppCompatActivity, pref : SharedPreferences, root : View?) { + + fun mixColor(col1 : Int, col2 : Int) : Int = Color.rgb( + (Color.red(col1) + Color.red(col2)) ushr 1, + (Color.green(col1) + Color.green(col2)) ushr 1, + (Color.blue(col1) + Color.blue(col2)) ushr 1 + ) + + val colorBg = getAttributeColor(activity, R.attr.colorWindowBackground) + + val colorOn = Pref.ipSwitchOnColor(pref) + + val colorOff = /* Pref.ipSwitchOffColor(pref).notZero() ?: */ + getAttributeColor(activity, android.R.attr.colorPrimary) + + val colorDisabled = mixColor(colorBg, colorOff) + + val colorTrackDisabled = mixColor(colorBg, colorDisabled) + val colorTrackOn = mixColor(colorBg, colorOn) + val colorTrackOff = mixColor(colorBg, colorOff) + + // set Switch Color + // https://stackoverflow.com/a/25635526/9134243 + val thumbStates = ColorStateList( + arrayOf( + intArrayOf(- android.R.attr.state_enabled), + intArrayOf(android.R.attr.state_checked), + intArrayOf() + ), + intArrayOf( + colorDisabled, + colorOn, + colorOff + ) + ) + + val trackStates = ColorStateList( + arrayOf( + intArrayOf(- android.R.attr.state_enabled), + intArrayOf(android.R.attr.state_checked), + intArrayOf() + ), + intArrayOf( + colorTrackDisabled, + colorTrackOn, + colorTrackOff + ) + ) + + root?.scan { + (it as? Switch)?.apply { + if(Build.VERSION.SDK_INT < 23) { + // android 5 + thumbDrawable?.setTintList(thumbStates) + trackDrawable?.setTintList(thumbStates) // not trackState + } else { + // android 6 + thumbTintList = thumbStates + if(Build.VERSION.SDK_INT >= 24) { + // android 7 + trackTintList = trackStates + trackTintMode = PorterDuff.Mode.SRC_OVER + } + } + } + } + } } } diff --git a/app/src/main/java/jp/juggler/subwaytooter/AppSettingItem.kt b/app/src/main/java/jp/juggler/subwaytooter/AppSettingItem.kt new file mode 100644 index 00000000..39dcb9fe --- /dev/null +++ b/app/src/main/java/jp/juggler/subwaytooter/AppSettingItem.kt @@ -0,0 +1,923 @@ +package jp.juggler.subwaytooter + +import android.content.Intent +import android.content.res.ColorStateList +import android.os.Build +import android.view.View +import android.widget.ImageView +import android.widget.Spinner +import android.widget.TextView +import androidx.annotation.StringRes +import androidx.appcompat.widget.AppCompatImageView +import jp.juggler.subwaytooter.action.CustomShareTarget +import jp.juggler.util.* + +enum class SettingType(val id : Int) { + Path(0), + Divider(1), + Switch(2), + EditText(3), + Spinner(4), + ColorOpaque(5), + ColorAlpha(6), + Action(7), + Sample(8), + Group(9), + TextWithSelector(10), + CheckBox(11), + Section(12) +} + +open class AppSettingItem( + val parent : AppSettingItem?, + val type : SettingType, + val pref : BasePref<*>?, + @StringRes val caption : Int +) { + + @StringRes + var desc : Int = 0 + var descClickSet = false + var descClick : ActAppSetting.() -> Unit = {} + set(value) { + field = value + descClickSet = true + } + + var getError : ActAppSetting.(String) -> String? = { null } + + // may be open exportAppData() or importAppData() + var action : ActAppSetting.() -> Unit = {} + + var changed : ActAppSetting.() -> Unit = {} + + // used for EditText + var inputType = InputTypeEx.text + + var sampleLayoutId : Int = 0 + var sampleUpdate : (ActAppSetting, View) -> Unit = { _, _ -> } + + var spinnerArgs : IntArray? = null + var spinnerArgsProc : (ActAppSetting) -> List = { _ -> emptyList() } + var spinnerInitializer : ActAppSetting.(Spinner) -> Unit = {} + var spinnerOnSelected : ActAppSetting.(Spinner, Int) -> Unit = { _, _ -> } + + var enabled : Boolean = true + + var onClickEdit : ActAppSetting.() -> Unit = {} + var onClickReset : ActAppSetting.() -> Unit = {} + var showTextView : ActAppSetting.(TextView) -> Unit = {} + + // for EditText + var hint : String? = null + var filter : (String) -> String = { it.trim() } + var captionFontSize : ActAppSetting.() -> Float? = { null } + var captionSpacing : ActAppSetting.() -> Float? = { null } + + // cast before save + var toFloat : ActAppSetting.(String) -> Float = { 0f } + var fromFloat : ActAppSetting.(Float) -> String = { it.toString() } + + companion object { + + var SAMPLE_CCD_HEADER : AppSettingItem? = null + var SAMPLE_CCD_BODY : AppSettingItem? = null + var SAMPLE_FOOTER : AppSettingItem? = null + + var CUSTOM_TRANSLATE : AppSettingItem? = null + var CUSTOM_SHARE_1 : AppSettingItem? = null + var CUSTOM_SHARE_2 : AppSettingItem? = null + var CUSTOM_SHARE_3 : AppSettingItem? = null + + var TIMELINE_FONT : AppSettingItem? = null + var TIMELINE_FONT_BOLD : AppSettingItem? = null + + var FONT_SIZE_TIMELINE : AppSettingItem? = null + var FONT_SIZE_NOTIFICATION_TL : AppSettingItem? = null + + } +} + +class AppSettingGroup( + parent : AppSettingGroup?, + type : SettingType, + @StringRes caption : Int +) : AppSettingItem( + parent = parent, + type = type, + pref = null, + caption = caption +) { + + val items = ArrayList() + + fun section( + @StringRes caption : Int, + initializer : AppSettingGroup.() -> Unit = {} + ) { + items.add(AppSettingGroup(this, SettingType.Section, caption).apply { initializer() }) + } + + fun group( + @StringRes caption : Int, + initializer : AppSettingGroup.() -> Unit = {} + ) { + items.add(AppSettingGroup(this, SettingType.Group, caption).apply { initializer() }) + } + + fun item( + type : SettingType, + pref : BasePref<*>?, + @StringRes caption : Int, + initializer : AppSettingItem.() -> Unit = {} + ) : AppSettingItem { + val item = AppSettingItem(this, type, pref, caption).apply { initializer() } + items.add(item) + return item + } + + fun spinner( + pref : IntPref, + @StringRes caption : Int, + vararg args : Int + ) = item(SettingType.Spinner, pref, caption) { + spinnerArgs = args + } + + fun spinner( + pref : IntPref, + @StringRes caption : Int, + argsProc : (ActAppSetting) -> List + ) = item(SettingType.Spinner, pref, caption) { + spinnerArgsProc = argsProc + } + + fun sw( + pref : BooleanPref, + @StringRes caption : Int, + initializer : AppSettingItem.() -> Unit = {} + ) = item(SettingType.Switch, pref, caption, initializer) + + fun checkbox( + pref : BooleanPref, + @StringRes caption : Int, + initializer : AppSettingItem.() -> Unit = {} + ) = item(SettingType.CheckBox, pref, caption, initializer) + + fun action( + @StringRes caption : Int, + initializer : AppSettingItem.() -> Unit = {} + ) = item(SettingType.Action, null, caption, initializer) + + fun colorOpaque( + pref : IntPref, + @StringRes caption : Int, + initializer : AppSettingItem.() -> Unit = {} + ) = item(SettingType.ColorOpaque, pref, caption, initializer) + + fun colorAlpha( + pref : IntPref, + @StringRes caption : Int, + initializer : AppSettingItem.() -> Unit = {} + ) = item(SettingType.ColorAlpha, pref, caption, initializer) + + fun text( + pref : StringPref, + @StringRes caption : Int, + inputType : Int, + initializer : AppSettingItem.() -> Unit = {} + ) = item(SettingType.EditText, pref, caption) { + this.inputType = inputType + this.initializer() + } + + fun textX( + pref : BasePref<*>, + @StringRes caption : Int, + inputType : Int, + initializer : AppSettingItem.() -> Unit = {} + ) = item(SettingType.EditText, pref, caption) { + this.inputType = inputType + this.initializer() + } + + fun sample( + sampleLayoutId : Int = 0, + sampleUpdate : (ActAppSetting, View) -> Unit = { _, _ -> } + // ,initializer : AppSettingItem.() -> Unit = {} + ) = item(SettingType.Sample, pref, caption) { + this.sampleLayoutId = sampleLayoutId + this.sampleUpdate = sampleUpdate + } + +} + +val appSettingRoot = AppSettingGroup(null, SettingType.Section, R.string.app_setting).apply { + + // TODO項目一覧に漏れがないか確認すること + + section(R.string.notifications) { + + group(R.string.notification_style_before_oreo) { + + checkbox(Pref.bpNotificationSound, R.string.sound) { + enabled = Build.VERSION.SDK_INT < 26 + } + + checkbox(Pref.bpNotificationVibration, R.string.vibration) { + enabled = Build.VERSION.SDK_INT < 26 + } + + checkbox(Pref.bpNotificationLED, R.string.led) { + enabled = Build.VERSION.SDK_INT < 26 + } + + sample(R.layout.setting_sample_notification_desc) + } + + text( + Pref.spPullNotificationCheckInterval, + R.string.pull_notification_check_interval, + InputTypeEx.number + ) + + sw(Pref.bpShowAcctInSystemNotification, R.string.show_acct_in_system_notification) + + sw(Pref.bpSeparateReplyNotificationGroup, R.string.separate_notification_group_for_reply) { + enabled = Build.VERSION.SDK_INT >= 26 + } + + sw(Pref.bpDivideNotification, R.string.divide_notification) + } + + section(R.string.behavior) { + + sw(Pref.bpDontConfirmBeforeCloseColumn, R.string.dont_confirm_before_close_column) + + spinner( + Pref.ipBackButtonAction, + R.string.back_button_action, + R.string.ask_always, + R.string.close_column, + R.string.open_column_list, + R.string.app_exit + ) + + sw(Pref.bpExitAppWhenCloseProtectedColumn, R.string.exit_app_when_close_protected_column) + sw(Pref.bpScrollTopFromColumnStrip, R.string.scroll_top_from_column_strip) + sw(Pref.bpDontScreenOff, R.string.dont_screen_off) + sw(Pref.bpDontUseCustomTabs, R.string.dont_use_custom_tabs) + sw(Pref.bpPriorChrome, R.string.prior_chrome_custom_tabs) + sw(Pref.bpAllowColumnDuplication, R.string.allow_column_duplication) + sw(Pref.bpForceGap, R.string.force_gap_when_refresh) + + text(Pref.spClientName, R.string.client_name, InputTypeEx.text) + + text(Pref.spUserAgent, R.string.user_agent, InputTypeEx.textMultiLine) { + hint = App1.userAgentDefault + filter = { it.replace(ActAppSetting.reLinefeed, " ").trim() } + getError = { + val m = App1.reNotAllowedInUserAgent.matcher(it) + when(m.find()) { + true -> getString(R.string.user_agent_error, m.group()) + else -> null + } + } + } + + sw(Pref.bpDontRemoveDeletedToot, R.string.dont_remove_deleted_toot_from_timeline) + sw(Pref.bpCustomEmojiSeparatorZwsp, R.string.custom_emoji_separator_zwsp) + sw(Pref.bpShowTranslateButton, R.string.show_translate_button) + + AppSettingItem.CUSTOM_TRANSLATE = item( + SettingType.TextWithSelector, + Pref.spTranslateAppComponent, + R.string.translation_app + ) { + val target = CustomShareTarget.Translate + onClickEdit = { openCustomShareChooser(target) } + onClickReset = { setCustomShare(target, "") } + showTextView = { showCustomShareIcon(it, target) } + } + + AppSettingItem.CUSTOM_SHARE_1 = item( + SettingType.TextWithSelector, + Pref.spCustomShare1, + R.string.custom_share_button_1 + ) { + val target = CustomShareTarget.CustomShare1 + onClickEdit = { openCustomShareChooser(target) } + onClickReset = { setCustomShare(target, "") } + showTextView = { showCustomShareIcon(it, target) } + } + AppSettingItem.CUSTOM_SHARE_2 = item( + SettingType.TextWithSelector, + Pref.spCustomShare2, + R.string.custom_share_button_2 + ) { + val target = CustomShareTarget.CustomShare2 + onClickEdit = { openCustomShareChooser(target) } + onClickReset = { setCustomShare(target, "") } + showTextView = { showCustomShareIcon(it, target) } + } + AppSettingItem.CUSTOM_SHARE_3 = item( + SettingType.TextWithSelector, + Pref.spCustomShare3, + R.string.custom_share_button_3 + ) { + val target = CustomShareTarget.CustomShare3 + onClickEdit = { openCustomShareChooser(target) } + onClickReset = { setCustomShare(target, "") } + showTextView = { showCustomShareIcon(it, target) } + } + + spinner( + Pref.ipAdditionalButtonsPosition, + R.string.additional_buttons_position, + R.string.top, + R.string.bottom, + R.string.start, + R.string.end + ) + + sw(Pref.bpEnablePixelfed, R.string.enable_connect_to_pixelfed_server) + sw(Pref.bpShowFilteredWord, R.string.show_filtered_word) + sw(Pref.bpEnableDomainTimeline, R.string.enable_domain_timeline) + } + + section(R.string.post) { + + spinner(Pref.ipResizeImage, R.string.resize_image) { activity -> + ActPost.resizeConfigList.map { + when(it.type) { + ResizeType.None -> activity.getString(R.string.dont_resize) + ResizeType.LongSide -> activity.getString(R.string.long_side_pixel, it.size) + ResizeType.SquarePixel -> activity.getString( + R.string.resize_square_pixels, + it.size * it.size, + it.size + ) + } + } + } + + text(Pref.spMediaSizeMax, R.string.media_attachment_max_byte_size, InputTypeEx.number) + text(Pref.spMovieSizeMax, R.string.media_attachment_max_byte_size_movie, InputTypeEx.number) + text( + Pref.spMediaSizeMaxPixelfed, + R.string.media_attachment_max_byte_size_pixelfed, + InputTypeEx.number + ) + + spinner( + Pref.ipRefreshAfterToot, + R.string.refresh_after_toot, + R.string.refresh_scroll_to_toot, + R.string.refresh_no_scroll, + R.string.dont_refresh + ) + + sw(Pref.bpPostButtonBarTop, R.string.show_post_button_bar_top) + + sw( + Pref.bpDontDuplicationCheck, + R.string.dont_add_duplication_check_header + ) + + sw(Pref.bpQuickTootBar, R.string.show_quick_toot_bar) + + sw( + Pref.bpDontUseActionButtonWithQuickTootBar, + R.string.dont_use_action_button_with_quick_toot_bar + ) + + text(Pref.spQuoteNameFormat, R.string.format_of_quote_name, InputTypeEx.text) { + filter = { it } // don't trim + } + + sw( + Pref.bpAppendAttachmentUrlToContent, + R.string.append_attachment_url_to_content + ) + + sw( + Pref.bpWarnHashtagAsciiAndNonAscii, + R.string.warn_hashtag_ascii_and_non_ascii + ) + } + + section(R.string.tablet_mode) { + + sw(Pref.bpDisableTabletMode, R.string.disable_tablet_mode) + + text(Pref.spColumnWidth, R.string.minimum_column_width, InputTypeEx.number) + + item( + SettingType.Spinner, + Pref.lpTabletTootDefaultAccount, + R.string.toot_button_default_account + ) { + val lp = pref.cast() !! + spinnerInitializer = { spinner -> + val adapter = AccountAdapter() + spinner.adapter = adapter + spinner.setSelection(adapter.getIndexFromId(lp(pref))) + } + spinnerOnSelected = { spinner, index -> + val adapter = spinner.adapter.cast() + ?: error("spinnerOnSelected: missing AccountAdapter") + pref.edit().put(lp, adapter.getIdFromIndex(index)).apply() + } + } + + sw( + Pref.bpQuickTootOmitAccountSelection, + R.string.quick_toot_omit_account_selection + ) + + spinner( + Pref.ipJustifyWindowContentPortrait, + R.string.justify_window_content_portrait, + R.string.default_, + R.string.start, + R.string.end + ) + } + + section(R.string.media_attachment) { + sw(Pref.bpUseInternalMediaViewer, R.string.use_internal_media_viewer) + sw(Pref.bpPriorLocalURL, R.string.prior_local_url_when_open_attachment) + text(Pref.spMediaThumbHeight, R.string.media_thumbnail_height, InputTypeEx.number) + sw(Pref.bpDontCropMediaThumb, R.string.dont_crop_media_thumbnail) + sw(Pref.bpVerticalArrangeThumbnails, R.string.thumbnails_arrange_vertically) + } + + section(R.string.animation) { + sw(Pref.bpEnableGifAnimation, R.string.enable_gif_animation) + sw(Pref.bpDisableEmojiAnimation, R.string.disable_custom_emoji_animation) + } + + section(R.string.appearance) { + sw(Pref.bpSimpleList, R.string.simple_list) + sw(Pref.bpShowFollowButtonInButtonBar, R.string.show_follow_button_in_button_bar) + sw(Pref.bpDontShowPreviewCard, R.string.dont_show_preview_card) + sw(Pref.bpShortAcctLocalUser, R.string.short_acct_local_user) + sw(Pref.bpMentionFullAcct, R.string.mention_full_acct) + sw(Pref.bpRelativeTimestamp, R.string.relative_timestamp) + + item( + SettingType.Spinner, + Pref.spTimeZone, + R.string.timezone + ) { + val sp : StringPref = pref.cast() !! + spinnerInitializer = { spinner -> + val adapter = TimeZoneAdapter() + spinner.adapter = adapter + spinner.setSelection(adapter.getIndexFromId(sp(pref))) + } + spinnerOnSelected = { spinner, index -> + val adapter = spinner.adapter.cast() + ?: error("spinnerOnSelected: missing TimeZoneAdapter") + pref.edit().put(sp, adapter.getIdFromIndex(index)).apply() + } + } + + sw(Pref.bpShowAppName, R.string.always_show_application) + sw(Pref.bpShowLanguage, R.string.always_show_language) + text(Pref.spAutoCWLines, R.string.auto_cw, InputTypeEx.number) + text(Pref.spCardDescriptionLength, R.string.card_description_length, InputTypeEx.number) + + spinner( + Pref.ipRepliesCount, + R.string.display_replies_count, + R.string.replies_count_simple, + R.string.replies_count_actual, + R.string.replies_count_none + ) + + spinner( + Pref.ipVisibilityStyle, + R.string.visibility_style, + R.string.visibility_style_by_account, + R.string.mastodon, + R.string.misskey + ) + + AppSettingItem.TIMELINE_FONT = item( + SettingType.TextWithSelector, + Pref.spTimelineFont, + R.string.timeline_font + ) { + val item = this + onClickEdit = { + try { + val intent = intentOpenDocument("*/*") + startActivityForResult(intent, ActAppSetting.REQUEST_CODE_TIMELINE_FONT) + } catch(ex : Throwable) { + showToast(this, ex, "could not open picker for font") + } + } + onClickReset = { + pref.edit().remove(item.pref?.key).apply() + showTimelineFont(item) + } + showTextView = { showTimelineFont(item, it) } + } + + + AppSettingItem.TIMELINE_FONT_BOLD = item( + SettingType.TextWithSelector, + Pref.spTimelineFontBold, + R.string.timeline_font_bold + ) { + val item = this + onClickEdit = { + try { + val intent = intentOpenDocument("*/*") + startActivityForResult(intent, ActAppSetting.REQUEST_CODE_TIMELINE_FONT_BOLD) + } catch(ex : Throwable) { + showToast(this, ex, "could not open picker for font") + } + } + onClickReset = { + pref.edit().remove(item.pref?.key).apply() + showTimelineFont(AppSettingItem.TIMELINE_FONT_BOLD) + } + showTextView = { showTimelineFont(item, it) } + } + + AppSettingItem.FONT_SIZE_TIMELINE = textX( + Pref.fpTimelineFontSize, + R.string.timeline_font_size, + InputTypeEx.numberDecimal + ) { + + val item = this + val fp : FloatPref = item.pref.cast() !! + + toFloat = { parseFontSize(it) } + fromFloat = { formatFontSize(it) } + + captionFontSize = { + val fv = fp(pref) + when { + ! fv.isFinite() -> Pref.default_timeline_font_size + fv < 1f -> 1f + else -> fv + } + } + captionSpacing = { + Pref.spTimelineSpacing(pref).toFloatOrNull() + } + changed = { + findItemViewHolder(item)?.updateCaption() + } + } + + textX(Pref.fpAcctFontSize, R.string.acct_font_size, InputTypeEx.numberDecimal) { + val item = this + val fp : FloatPref = item.pref.cast() !! + + toFloat = { parseFontSize(it) } + fromFloat = { formatFontSize(it) } + + captionFontSize = { + val fv = fp(pref) + when { + ! fv.isFinite() -> Pref.default_acct_font_size + fv < 1f -> 1f + else -> fv + } + } + + changed = { findItemViewHolder(item)?.updateCaption() } + } + + AppSettingItem.FONT_SIZE_NOTIFICATION_TL = textX( + Pref.fpNotificationTlFontSize, + R.string.notification_tl_font_size, + InputTypeEx.numberDecimal + ) { + val item = this + val fp : FloatPref = item.pref.cast() !! + + toFloat = { parseFontSize(it) } + fromFloat = { formatFontSize(it) } + + captionFontSize = { + val fv = fp(pref) + when { + ! fv.isFinite() -> Pref.default_notification_tl_font_size + fv < 1f -> 1f + else -> fv + } + } + captionSpacing = { + Pref.spTimelineSpacing(pref).toFloatOrNull() + } + changed = { + findItemViewHolder(item)?.updateCaption() + } + } + + text( + Pref.spNotificationTlIconSize, + R.string.notification_tl_icon_size, + InputTypeEx.numberDecimal + ) + + text(Pref.spTimelineSpacing, R.string.timeline_line_spacing, InputTypeEx.numberDecimal) { + changed = { + findItemViewHolder(AppSettingItem.FONT_SIZE_TIMELINE)?.updateCaption() + findItemViewHolder(AppSettingItem.FONT_SIZE_NOTIFICATION_TL)?.updateCaption() + } + } + + text(Pref.spBoostButtonSize, R.string.boost_button_size, InputTypeEx.numberDecimal) + + spinner( + Pref.ipBoostButtonJustify, + R.string.boost_button_alignment, + R.string.start, + R.string.center, + R.string.end + ) + + text(Pref.spAvatarIconSize, R.string.avatar_icon_size, InputTypeEx.numberDecimal) + text(Pref.spRoundRatio, R.string.avatar_icon_round_ratio, InputTypeEx.numberDecimal) + sw(Pref.bpDontRound, R.string.avatar_icon_dont_round) + text(Pref.spReplyIconSize, R.string.reply_icon_size, InputTypeEx.numberDecimal) + text(Pref.spHeaderIconSize, R.string.header_icon_size, InputTypeEx.numberDecimal) + textX(Pref.fpHeaderTextSize, R.string.header_text_size, InputTypeEx.numberDecimal) { + val item = this + val fp : FloatPref = item.pref.cast() !! + + toFloat = { parseFontSize(it) } + fromFloat = { formatFontSize(it) } + + captionFontSize = { + val fv = fp(pref) + when { + ! fv.isFinite() -> Pref.default_header_font_size + fv < 1f -> 1f + else -> fv + } + } + + changed = { + findItemViewHolder(item)?.updateCaption() + } + } + + text(Pref.spStripIconSize, R.string.strip_icon_size, InputTypeEx.numberDecimal) + + sw(Pref.bpInstanceTicker, R.string.show_instance_ticker) { + desc = R.string.instance_ticker_copyright + descClick = { + App1.openBrowser( + this, + "https://github.com/MiyonMiyon/InstanceTicker_List" + ) + } + } + + sw(Pref.bpLinksInContextMenu, R.string.show_links_in_context_menu) + sw(Pref.bpShowLinkUnderline, R.string.show_link_underline) + sw( + Pref.bpMoveNotificationsQuickFilter, + R.string.move_notifications_quick_filter_to_column_setting + ) + sw(Pref.bpShowSearchClear, R.string.show_clear_button_in_search_bar) + sw( + Pref.bpDontShowColumnBackgroundImage, + R.string.dont_show_column_background_image + ) + + group(R.string.show_in_directory) { + checkbox(Pref.bpDirectoryLastActive, R.string.last_active) + checkbox(Pref.bpDirectoryFollowers, R.string.followers) + checkbox(Pref.bpDirectoryTootCount, R.string.toot_count) + checkbox(Pref.bpDirectoryNote, R.string.note) + } + + sw( + Pref.bpAlwaysExpandContextMenuItems, + R.string.always_expand_context_menu_sub_items + ) + sw(Pref.bpShowBookmarkButton, R.string.show_bookmark_button) + sw(Pref.bpHideFollowCount, R.string.hide_followers_count) + sw(Pref.bpEmojioneShortcode, R.string.emojione_shortcode_support) { + desc = R.string.emojione_shortcode_support_desc + } + } + + section(R.string.color) { + + spinner( + Pref.ipUiTheme, + R.string.ui_theme, + R.string.theme_light, + R.string.theme_dark + ) + + colorAlpha(Pref.ipListDividerColor, R.string.list_divider_color) + colorAlpha(Pref.ipLinkColor, R.string.link_color) + + group(R.string.toot_background_color) { + colorAlpha(Pref.ipTootColorUnlisted, R.string.unlisted_visibility) + colorAlpha(Pref.ipTootColorFollower, R.string.followers_visibility) + colorAlpha(Pref.ipTootColorDirectUser, R.string.direct_with_user_visibility) + colorAlpha(Pref.ipTootColorDirectMe, R.string.direct_only_me_visibility) + } + + group(R.string.event_background_color) { + colorAlpha(Pref.ipEventBgColorBoost, R.string.boost) + colorAlpha(Pref.ipEventBgColorFavourite, R.string.favourites) + colorAlpha(Pref.ipEventBgColorMention, R.string.reply) + colorAlpha(Pref.ipEventBgColorFollow, R.string.follow) + colorAlpha(Pref.ipEventBgColorUnfollow, R.string.unfollow_misskey) + colorAlpha(Pref.ipEventBgColorFollowRequest, R.string.follow_request) + colorAlpha(Pref.ipEventBgColorReaction, R.string.reaction) + colorAlpha(Pref.ipEventBgColorQuote, R.string.quote_renote) + colorAlpha(Pref.ipEventBgColorVote, R.string.vote_polls) + } + + group(R.string.column_color_default) { + AppSettingItem.SAMPLE_CCD_HEADER = + sample(R.layout.setting_sample_column_header) { activity, viewRoot -> + + val llColumnHeader : View = viewRoot.findViewById(R.id.llColumnHeader) + val ivColumnHeader : ImageView = viewRoot.findViewById(R.id.ivColumnHeader) + val tvColumnName : TextView = viewRoot.findViewById(R.id.tvColumnName) + + val color_column_header_bg = Pref.ipCcdHeaderBg(activity.pref) + val color_column_header_fg = Pref.ipCcdHeaderFg(activity.pref) + + val header_bg = when { + color_column_header_bg != 0 -> color_column_header_bg + else -> getAttributeColor(activity, R.attr.color_column_header) + } + + val header_fg = when { + color_column_header_fg != 0 -> color_column_header_fg + else -> getAttributeColor(activity, R.attr.colorColumnHeaderName) + } + + llColumnHeader.background = getAdaptiveRippleDrawable(header_bg, header_fg) + + tvColumnName.setTextColor(header_fg) + ivColumnHeader.setImageResource(R.drawable.ic_bike) + ivColumnHeader.imageTintList = ColorStateList.valueOf(header_fg) + } + + colorOpaque(Pref.ipCcdHeaderBg, R.string.header_background_color) { + changed = { showSample(AppSettingItem.SAMPLE_CCD_HEADER) } + } + colorOpaque(Pref.ipCcdHeaderFg, R.string.header_foreground_color) { + changed = { showSample(AppSettingItem.SAMPLE_CCD_HEADER) } + } + + AppSettingItem.SAMPLE_CCD_BODY = + sample(R.layout.setting_sample_column_body) { activity, viewRoot -> + val flColumnBackground : View = viewRoot.findViewById(R.id.flColumnBackground) + val tvSampleAcct : TextView = viewRoot.findViewById(R.id.tvSampleAcct) + val tvSampleContent : TextView = viewRoot.findViewById(R.id.tvSampleContent) + + val color_column_bg = Pref.ipCcdContentBg(activity.pref) + val color_column_acct = Pref.ipCcdContentAcct(activity.pref) + val color_column_text = Pref.ipCcdContentText(activity.pref) + + flColumnBackground.setBackgroundColor(color_column_bg) // may 0 + + tvSampleAcct.setTextColor( + color_column_acct.notZero() + ?: getAttributeColor(activity, R.attr.colorTimeSmall) + ) + + tvSampleContent.setTextColor( + color_column_text.notZero() + ?: getAttributeColor(activity, R.attr.colorContentText) + ) + } + + colorOpaque(Pref.ipCcdContentBg, R.string.content_background_color) { + changed = { showSample(AppSettingItem.SAMPLE_CCD_BODY) } + } + colorAlpha(Pref.ipCcdContentAcct, R.string.content_acct_color) { + changed = { showSample(AppSettingItem.SAMPLE_CCD_BODY) } + } + colorAlpha(Pref.ipCcdContentText, R.string.content_text_color) { + changed = { showSample(AppSettingItem.SAMPLE_CCD_BODY) } + } + } + + text(Pref.spBoostAlpha, R.string.boost_button_alpha, InputTypeEx.numberDecimal) + + group(R.string.footer_color) { + AppSettingItem.SAMPLE_FOOTER = + sample(R.layout.setting_sample_footer) { activity, viewRoot -> + val pref = activity.pref + val footer_button_bg_color = Pref.ipFooterButtonBgColor(pref) + val footer_button_fg_color = Pref.ipFooterButtonFgColor(pref) + val footer_tab_bg_color = Pref.ipFooterTabBgColor(pref) + val footer_tab_divider_color = Pref.ipFooterTabDividerColor(pref) + val footer_tab_indicator_color = Pref.ipFooterTabIndicatorColor(pref) + + val ivFooterToot : AppCompatImageView = viewRoot.findViewById(R.id.ivFooterToot) + val ivFooterMenu : AppCompatImageView = viewRoot.findViewById(R.id.ivFooterMenu) + val llFooterBG : View = viewRoot.findViewById(R.id.llFooterBG) + val vFooterDivider1 : View = viewRoot.findViewById(R.id.vFooterDivider1) + val vFooterDivider2 : View = viewRoot.findViewById(R.id.vFooterDivider2) + val vIndicator : View = viewRoot.findViewById(R.id.vIndicator) + + val colorBg = footer_button_bg_color.notZero() ?: getAttributeColor( + activity, + R.attr.colorStatusButtonsPopupBg + ) + val colorRipple = + footer_button_fg_color.notZero() ?: getAttributeColor( + activity, + R.attr.colorRippleEffect + ) + ivFooterToot.background = getAdaptiveRippleDrawable(colorBg, colorRipple) + ivFooterMenu.background = getAdaptiveRippleDrawable(colorBg, colorRipple) + + val csl = ColorStateList.valueOf( + footer_button_fg_color.notZero() + ?: getAttributeColor(activity, R.attr.colorVectorDrawable) + ) + ivFooterToot.imageTintList = csl + ivFooterMenu.imageTintList = csl + + llFooterBG.setBackgroundColor( + footer_tab_bg_color.notZero() + ?: getAttributeColor(activity, R.attr.colorColumnStripBackground) + ) + + val c = + footer_tab_divider_color.notZero() ?: getAttributeColor( + activity, + R.attr.colorImageButton + ) + vFooterDivider1.setBackgroundColor(c) + vFooterDivider2.setBackgroundColor(c) + + vIndicator.setBackgroundColor( + footer_tab_indicator_color.notZero() + ?: getAttributeColor(activity, R.attr.colorAccent) + ) + } + + colorOpaque(Pref.ipFooterButtonBgColor, R.string.button_background_color) { + changed = { showSample(AppSettingItem.SAMPLE_FOOTER) } + } + colorOpaque(Pref.ipFooterButtonFgColor, R.string.button_foreground_color) { + changed = { showSample(AppSettingItem.SAMPLE_FOOTER) } + } + colorOpaque(Pref.ipFooterTabBgColor, R.string.quick_toot_bar_background_color) { + changed = { showSample(AppSettingItem.SAMPLE_FOOTER) } + } + colorOpaque(Pref.ipFooterTabDividerColor, R.string.tab_divider_color) { + changed = { showSample(AppSettingItem.SAMPLE_FOOTER) } + } + colorAlpha(Pref.ipFooterTabIndicatorColor, R.string.tab_indicator_color) { + changed = { showSample(AppSettingItem.SAMPLE_FOOTER) } + } + } + + colorOpaque(Pref.ipSwitchOnColor, R.string.switch_button_color) { + changed = { setSwitchColor() } + } + + colorOpaque(Pref.ipStatusBarColor, R.string.status_bar_color) { + changed = { App1.setStatusBarColor(this) } + } + + colorOpaque(Pref.ipNavigationBarColor, R.string.navigation_bar_color) { + changed = { App1.setStatusBarColor(this) } + } + } + + section(R.string.performance) { + sw(Pref.bpShareViewPool, R.string.share_view_pool) + sw(Pref.bpDontUseStreaming, R.string.dont_use_streaming_api) + sw(Pref.bpDontRefreshOnResume, R.string.dont_refresh_on_activity_resume) + text(Pref.spMediaReadTimeout, R.string.timeout_for_embed_media_viewer, InputTypeEx.number) + } + + section(R.string.developer_options) { + action(R.string.drawable_list) { + action = { startActivity(Intent(this, ActDrawableList::class.java)) } + } + } + + action(R.string.app_data_export) { + action = { exportAppData() } + } + + action(R.string.app_data_import) { + action = { importAppData1() } + desc = R.string.app_data_import_desc + } +} diff --git a/app/src/main/java/jp/juggler/subwaytooter/Pref.kt b/app/src/main/java/jp/juggler/subwaytooter/Pref.kt index 66f3b97c..20926092 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/Pref.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/Pref.kt @@ -116,6 +116,10 @@ fun SharedPreferences.Editor.put(item : BooleanPref, v : Boolean) : SharedPrefer item.put(this, v) return this } +fun SharedPreferences.Editor.putOrRemove(item : BooleanPref, v : Boolean) : SharedPreferences.Editor { + item.putOrRemove(this, v) + return this +} fun SharedPreferences.Editor.put(item : StringPref, v : String) : SharedPreferences.Editor { item.put(this, v) diff --git a/app/src/main/java/jp/juggler/util/InputTypeEx.kt b/app/src/main/java/jp/juggler/util/InputTypeEx.kt new file mode 100644 index 00000000..f7d89982 --- /dev/null +++ b/app/src/main/java/jp/juggler/util/InputTypeEx.kt @@ -0,0 +1,124 @@ +package jp.juggler.util + +import android.text.InputType + +@Suppress("unused") +object InputTypeEx { + + // For entering a date. + const val date = InputType.TYPE_CLASS_DATETIME or InputType.TYPE_DATETIME_VARIATION_DATE + + // For entering a date and time. + const val datetime = InputType.TYPE_CLASS_DATETIME or InputType.TYPE_DATETIME_VARIATION_NORMAL + + // There is no content type. The text is not editable. + const val none = 0 + + // A numeric only field. + const val number = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_VARIATION_NORMAL + + // Can be combined with number and its other options to allow a decimal (fractional) number. + const val numberDecimal = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_FLAG_DECIMAL + + // A numeric password field. . + const val numberPassword = + InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_VARIATION_PASSWORD + + // Can be combined with number and its other options to allow a signed number. + const val numberSigned = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_FLAG_SIGNED + + // For entering a phone number. + const val phone = InputType.TYPE_CLASS_PHONE + + // Just plain old text. + const val text = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_NORMAL + + // Can be combined with text and its variations to + // specify that this field will be doing its own auto-completion + // and talking with the input method appropriately. + const val textAutoComplete = text or InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE + + // Can be combined with text and its variations to + // request auto-correction of text being input. + const val textAutoCorrect = text or InputType.TYPE_TEXT_FLAG_AUTO_CORRECT + + // Can be combined with text and its variations to + // request capitalization of all characters. + const val textCapCharacters = text or InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS + + // Can be combined with text and its variations to + // request capitalization of the first character of every sentence. + const val textCapSentences = text or InputType.TYPE_TEXT_FLAG_CAP_SENTENCES + + // Can be combined with text and its variations to + // request capitalization of the first character of every word. + const val textCapWords = text or InputType.TYPE_TEXT_FLAG_CAP_WORDS + + // Text that will be used as an e-mail address. + const val textEmailAddress = + InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS + + // Text that is being supplied as the subject of an e-mail. + const val textEmailSubject = + InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_EMAIL_SUBJECT + + // Text that is filtering some other data. + const val textFilter = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_FILTER + + // Can be combined with text and its variations to indicate that though + // the regular text view should not be multiple lines, + // the IME should provide multiple lines if it can. + const val textImeMultiLine = text or InputType.TYPE_TEXT_FLAG_IME_MULTI_LINE + + // Text that is the content of a long message. + const val textLongMessage = + InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_LONG_MESSAGE + + // Can be combined with text and its variations to allow multiple lines of text in the field. + // If this flag is not set, the text field will be constrained to a single line. + const val textMultiLine = text or InputType.TYPE_TEXT_FLAG_MULTI_LINE + + // Can be combined with text and its variations to indicate that + // the IME should not show any dictionary-based word suggestions. + const val textNoSuggestions = text or InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS + + // Text that is a password. + const val textPassword = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD + + // Text that is the name of a person. + const val textPersonName = + InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PERSON_NAME + + // Text that is for phonetic pronunciation, such as a phonetic name field in a contact entry. + const val textPhonetic = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PHONETIC + + // Text that is being supplied as a postal mailing address. + const val textPostalAddress = + InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_POSTAL_ADDRESS + + // Text that is the content of a short message. + const val textShortMessage = + InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE + + // Text that will be used as a URI. + const val textUri = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_URI + + // Text that is a password that should be visible. + const val textVisiblePassword = + InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD + + // Text that is being supplied as text in a web form. + const val textWebEditText = + InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT + + // Text that will be used as an e-mail address on a web form. + const val textWebEmailAddress = + InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS + + // Text that will be used as a password on a web form. + const val textWebPassword = + InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD + + // For entering a time. + const val time = InputType.TYPE_CLASS_DATETIME or InputType.TYPE_DATETIME_VARIATION_TIME +} \ No newline at end of file diff --git a/app/src/main/res/layout/act_app_setting.xml b/app/src/main/res/layout/act_app_setting.xml index e2a28a2b..819aa50d 100644 --- a/app/src/main/res/layout/act_app_setting.xml +++ b/app/src/main/res/layout/act_app_setting.xml @@ -1,139 +1,54 @@ - + android:orientation="vertical"> + + + + + + + + + android:paddingEnd="12dp" + android:paddingBottom="128dp" + android:scrollbarStyle="outsideOverlay" /> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/lv_setting_item.xml b/app/src/main/res/layout/lv_setting_item.xml new file mode 100644 index 00000000..0c30cd4f --- /dev/null +++ b/app/src/main/res/layout/lv_setting_item.xml @@ -0,0 +1,100 @@ + + + + + +