owenclave / app/src/main/java/io/nekohasekai/sagernet/ui/profile/ConfigEditActivity.kt

github - dev - back to tree - upstream
/****************************************************************************** * * * Copyright (C) 2021 by nekohasekai * * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 3 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * * * ******************************************************************************/ package io.nekohasekai.sagernet.ui.profile import android.annotation.SuppressLint import android.graphics.Color import android.os.Bundle import android.view.Menu import android.view.MenuItem import androidx.activity.OnBackPressedCallback import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat import androidx.core.view.updatePadding import com.blacksquircle.ui.editorkit.listener.OnChangeListener import com.blacksquircle.ui.editorkit.model.ColorScheme import com.blacksquircle.ui.language.base.model.SyntaxScheme import com.blacksquircle.ui.language.json.JsonLanguage import io.nekohasekai.sagernet.R import io.nekohasekai.sagernet.database.DataStore import io.nekohasekai.sagernet.databinding.LayoutEditConfigBinding import io.nekohasekai.sagernet.ktx.getColorAttr import io.nekohasekai.sagernet.ktx.onMainDispatcher import io.nekohasekai.sagernet.ktx.runOnDefaultDispatcher import io.nekohasekai.sagernet.ui.ThemedActivity import io.nekohasekai.sagernet.utils.Theme import androidx.core.graphics.toColorInt import com.google.android.material.dialog.MaterialAlertDialogBuilder import io.nekohasekai.sagernet.SagerNet import io.nekohasekai.sagernet.ktx.parseJson import io.nekohasekai.sagernet.ktx.readableMessage class ConfigEditActivity : ThemedActivity() { companion object { private const val KEY_CONFIG = "config" private const val KEY_DIRTY = "dirty" } private lateinit var binding: LayoutEditConfigBinding var config = "" var dirty = false override val onBackPressedCallback = object : OnBackPressedCallback(enabled = false) { override fun handleOnBackPressed() { if (ViewCompat.getRootWindowInsets(binding.editor) ?.isVisible(WindowInsetsCompat.Type.ime()) == true /* this also works on Android Emulator Android 5.0 anyway */ ) { this@ConfigEditActivity.currentFocus?.windowToken?.let { SagerNet.ime.hideSoftInputFromWindow(it, 0) } } else { MaterialAlertDialogBuilder(this@ConfigEditActivity) .setTitle(R.string.unsaved_changes_prompt) .setPositiveButton(android.R.string.ok) { _, _ -> saveAndExit() } .setNegativeButton(android.R.string.cancel) { _, _ -> finish() } .show() } } } @SuppressLint("InlinedApi") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = LayoutEditConfigBinding.inflate(layoutInflater) setContentView(binding.root) ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.layout_editor)) { v, insets -> val bars = insets.getInsets( WindowInsetsCompat.Type.systemBars() or WindowInsetsCompat.Type.displayCutout() ) val ime = insets.getInsets(WindowInsetsCompat.Type.ime()) v.updatePadding( left = bars.left, bottom = ime.bottom, ) insets } ViewCompat.setOnApplyWindowInsetsListener(binding.editor) { v, insets -> val bars = insets.getInsets( WindowInsetsCompat.Type.systemBars() or WindowInsetsCompat.Type.displayCutout() ) val ime = insets.getInsets(WindowInsetsCompat.Type.ime()) v.updatePadding( right = bars.right, bottom = if (ime.bottom > bars.bottom) 0 else bars.bottom - ime.bottom, ) insets } setSupportActionBar(findViewById(R.id.toolbar)) supportActionBar?.apply { setTitle(R.string.config_settings) setDisplayHomeAsUpEnabled(true) setHomeAsUpIndicator(R.drawable.ic_navigation_close) } binding.editor.colorScheme = mkTheme() binding.editor.language = JsonLanguage() binding.editor.onChangeListener = OnChangeListener { config = binding.editor.text.toString() dirty = true onBackPressedCallback.isEnabled = true } runOnDefaultDispatcher { config = DataStore.serverConfig savedInstanceState?.getString(KEY_CONFIG)?.takeIf { it.isNotEmpty() }?.let { config = it } onMainDispatcher { binding.editor.setTextContent(config) } } savedInstanceState?.getBoolean(KEY_DIRTY)?.let { dirty = it onBackPressedCallback.isEnabled = it } } override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) outState.putString(KEY_CONFIG, config) outState.putBoolean(KEY_DIRTY, dirty) } @SuppressLint("CheckResult") fun saveAndExit() { try { parseJson(config, lenient = true).asJsonObject } catch (e: Exception) { MaterialAlertDialogBuilder(this).setTitle(R.string.error_title) .setMessage(e.readableMessage).show() return } DataStore.serverConfig = config finish() } override fun onSupportNavigateUp(): Boolean { if (!super.onSupportNavigateUp()) finish() return true } override fun onCreateOptionsMenu(menu: Menu): Boolean { menuInflater.inflate(R.menu.profile_apply_menu, menu) return true } override fun onOptionsItemSelected(item: MenuItem): Boolean { when (item.itemId) { R.id.action_apply -> { saveAndExit() return true } } return super.onOptionsItemSelected(item) } fun mkTheme(): ColorScheme { val colorPrimary = getColorAttr(androidx.appcompat.R.attr.colorPrimary) val colorPrimaryDark = getColorAttr(androidx.appcompat.R.attr.colorPrimaryDark) return ColorScheme( textColor = when (Theme.getTheme()) { R.style.Theme_SagerNet_Black -> Color.WHITE R.style.Theme_SagerNet_LightBlack -> Color.BLACK else -> colorPrimary }, backgroundColor = when (Theme.getTheme()) { R.style.Theme_SagerNet_Black -> Color.BLACK R.style.Theme_SagerNet_LightBlack -> Color.WHITE else -> if (Theme.usingNightMode()) Color.BLACK else Color.WHITE }, gutterColor = colorPrimary, gutterDividerColor = if (Theme.usingNightMode()) Color.BLACK else Color.WHITE, gutterCurrentLineNumberColor = when (Theme.getTheme()) { R.style.Theme_SagerNet_LightBlack -> Color.BLACK else -> Color.WHITE }, gutterTextColor = when (Theme.getTheme()) { R.style.Theme_SagerNet_LightBlack -> Color.BLACK else -> Color.WHITE }, selectedLineColor = if (Theme.usingNightMode()) "#2C2C2C".toColorInt() else "#D3D3D3".toColorInt(), selectionColor = when (Theme.getTheme()) { R.style.Theme_SagerNet_Black -> "#4C4C4C".toColorInt() R.style.Theme_SagerNet_LightBlack -> "#B3B3B3".toColorInt() else -> colorPrimary }, suggestionQueryColor = "#7CE0F3".toColorInt(), findResultBackgroundColor = "#5F5E5A".toColorInt(), delimiterBackgroundColor = "#5F5E5A".toColorInt(), syntaxScheme = SyntaxScheme( numberColor = "#BB8FF8".toColorInt(), operatorColor = if (Theme.usingNightMode()) Color.WHITE else Color.BLACK, keywordColor = "#EB347E".toColorInt(), typeColor = "#7FD0E4".toColorInt(), langConstColor = "#EB347E".toColorInt(), preprocessorColor = "#EB347E".toColorInt(), variableColor = "#7FD0E4".toColorInt(), methodColor = "#B6E951".toColorInt(), stringColor = when (Theme.getTheme()) { R.style.Theme_SagerNet_Black -> Color.WHITE R.style.Theme_SagerNet_LightBlack -> Color.BLACK else -> colorPrimaryDark }, commentColor = "#89826D".toColorInt(), tagColor = "#F8F8F8".toColorInt(), tagNameColor = "#EB347E".toColorInt(), attrNameColor = "#B6E951".toColorInt(), attrValueColor = "#EBE48C".toColorInt(), entityRefColor = "#BB8FF8".toColorInt() ) ) } }