package jp.juggler.subwaytooter import android.content.Intent import android.content.res.ColorStateList import android.graphics.Bitmap import android.graphics.Color import android.net.Uri import android.os.Bundle import android.text.Editable import android.text.TextWatcher import android.view.View import android.view.inputmethod.EditorInfo import android.widget.ImageView import android.widget.SeekBar import androidx.appcompat.app.AppCompatActivity import androidx.core.view.ViewCompat import com.jrummyapps.android.colorpicker.dialogColorPicker import jp.juggler.subwaytooter.api.TootApiResult import jp.juggler.subwaytooter.api.runApiTask import jp.juggler.subwaytooter.column.Column import jp.juggler.subwaytooter.column.getAcctColor import jp.juggler.subwaytooter.column.getBackgroundImageDir import jp.juggler.subwaytooter.column.getColumnName import jp.juggler.subwaytooter.column.getContentColor import jp.juggler.subwaytooter.column.getHeaderBackgroundColor import jp.juggler.subwaytooter.column.getHeaderNameColor import jp.juggler.subwaytooter.column.getIconId import jp.juggler.subwaytooter.column.setHeaderBackground import jp.juggler.subwaytooter.databinding.ActColumnCustomizeBinding import jp.juggler.util.backPressed import jp.juggler.util.coroutine.launchAndShowError import jp.juggler.util.coroutine.launchMain import jp.juggler.util.data.checkMimeTypeAndGrant import jp.juggler.util.data.defaultLocale import jp.juggler.util.data.intentGetContent import jp.juggler.util.data.mayUri import jp.juggler.util.data.notZero import jp.juggler.util.int import jp.juggler.util.log.LogCategory import jp.juggler.util.log.showToast import jp.juggler.util.log.withCaption import jp.juggler.util.media.createResizedBitmap import jp.juggler.util.ui.ActivityResultHandler import jp.juggler.util.ui.hideKeyboard import jp.juggler.util.ui.isNotOk import jp.juggler.util.ui.setNavigationBack import jp.juggler.util.ui.vg import org.jetbrains.anko.textColor import java.io.File import java.io.FileOutputStream import java.text.NumberFormat import kotlin.math.max class ActColumnCustomize : AppCompatActivity(), View.OnClickListener { companion object { internal val log = LogCategory("ActColumnCustomize") internal const val EXTRA_COLUMN_INDEX = "column_index" internal const val PROGRESS_MAX = 65536 fun createIntent(activity: ActMain, idx: Int) = Intent(activity, ActColumnCustomize::class.java).apply { putExtra(EXTRA_COLUMN_INDEX, idx) } } private var columnIndex: Int = 0 internal lateinit var column: Column internal lateinit var appState: AppState internal var density: Float = 0f private val views by lazy { ActColumnCustomizeBinding.inflate(layoutInflater) } internal var loadingBusy: Boolean = false private var lastImageUri: String? = null private var lastImageBitmap: Bitmap? = null private val arColumnBackgroundImage = ActivityResultHandler(log) { r -> if (r.isNotOk) return@ActivityResultHandler r.data?.checkMimeTypeAndGrant(contentResolver) ?.firstOrNull()?.uri?.let { updateBackground(it) } } private fun makeResult() { val data = Intent() data.putExtra(EXTRA_COLUMN_INDEX, columnIndex) setResult(RESULT_OK, data) } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) backPressed { makeResult() finish() } arColumnBackgroundImage.register(this) App1.setActivityTheme(this) initUI() appState = App1.getAppState(this) density = appState.density columnIndex = intent.int(EXTRA_COLUMN_INDEX) ?: 0 column = appState.column(columnIndex)!! show() } override fun onDestroy() { closeBitmaps() super.onDestroy() } override fun onClick(v: View) { when (v.id) { R.id.btnHeaderBackgroundEdit -> launchAndShowError { column.headerBgColor = Color.BLACK or dialogColorPicker( colorInitial = column.getHeaderBackgroundColor(), alphaEnabled = false, ) } R.id.btnHeaderBackgroundReset -> { column.headerBgColor = 0 show() } R.id.btnHeaderTextEdit -> launchAndShowError { column.headerFgColor = Color.BLACK or dialogColorPicker( colorInitial = column.getHeaderNameColor(), alphaEnabled = false, ) } R.id.btnHeaderTextReset -> { column.headerFgColor = 0 show() } R.id.btnColumnBackgroundColor -> launchAndShowError { column.columnBgColor = Color.BLACK or dialogColorPicker( colorInitial = column.columnBgColor.notZero(), alphaEnabled = false, ) } R.id.btnColumnBackgroundColorReset -> { column.columnBgColor = 0 show() } R.id.btnAcctColor -> launchAndShowError { column.acctColor = dialogColorPicker( colorInitial = column.getAcctColor(), alphaEnabled = true, ).notZero() ?: 1 } R.id.btnAcctColorReset -> { column.acctColor = 0 show() } R.id.btnContentColor -> launchAndShowError { column.contentColor = dialogColorPicker( colorInitial = column.getContentColor(), alphaEnabled = true, ).notZero() ?: 1 } R.id.btnContentColorReset -> { column.contentColor = 0 show() } R.id.btnColumnBackgroundImage -> { val intent = intentGetContent( false, getString(R.string.pick_image), arrayOf("image/*") ) arColumnBackgroundImage.launch(intent) } R.id.btnColumnBackgroundImageReset -> { column.columnBgImage = "" show() } } } private fun updateBackground(uriArg: Uri) { launchMain { var resultUri: String? = null runApiTask { client -> try { val backgroundDir = getBackgroundImageDir(this@ActColumnCustomize) val file = File(backgroundDir, "${column.columnId}:${System.currentTimeMillis()}") val fileUri = Uri.fromFile(file) client.publishApiProgress("loading image from $uriArg") contentResolver.openInputStream(uriArg)?.use { inStream -> FileOutputStream(file).use { outStream -> inStream.copyTo(outStream) } } // リサイズや回転が必要ならする client.publishApiProgress("check resize/rotation…") val size = (max( resources.displayMetrics.widthPixels, resources.displayMetrics.heightPixels ) * 1.5f).toInt() val bitmap = createResizedBitmap( this, fileUri, size, skipIfNoNeedToResizeAndRotate = true, ) if (bitmap != null) { try { client.publishApiProgress("save resized(${bitmap.width}x${bitmap.height}) image to $file") FileOutputStream(file).use { os -> bitmap.compress(Bitmap.CompressFormat.PNG, 100, os) } } finally { bitmap.recycle() } } resultUri = fileUri.toString() TootApiResult() } catch (ex: Throwable) { log.e(ex, "can't update background image.") TootApiResult(ex.withCaption("can't update background image.")) } }?.let { result -> when (val bgUri = resultUri) { null -> showToast(true, result.error ?: "?") else -> { column.columnBgImage = bgUri show() } } } } } private fun initUI() { setContentView(views.root) setSupportActionBar(views.toolbar) setNavigationBack(views.toolbar) fixHorizontalMargin(views.svContent) arrayOf( views.btnHeaderBackgroundEdit, views.btnHeaderBackgroundReset, views.btnHeaderTextEdit, views.btnHeaderTextReset, views.btnColumnBackgroundColor, views.btnColumnBackgroundColorReset, views.btnColumnBackgroundImage, views.btnColumnBackgroundImageReset, views.btnAcctColor, views.btnAcctColorReset, views.btnContentColor, views.btnContentColorReset, ).forEach { it.setOnClickListener(this) } views.sbColumnBackgroundAlpha.max = PROGRESS_MAX views.sbColumnBackgroundAlpha.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener { override fun onStartTrackingTouch(seekBar: SeekBar) {} override fun onStopTrackingTouch(seekBar: SeekBar) {} override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) { if (loadingBusy) return if (!fromUser) return column.columnBgImageAlpha = progress / PROGRESS_MAX.toFloat() showAlpha(updateText = true, updateSeek = false) } }) views.etAlpha.addTextChangedListener(object : 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) { if (loadingBusy) return try { var f = NumberFormat.getInstance(defaultLocale(this@ActColumnCustomize)) .parse(views.etAlpha.text.toString())?.toFloat() if (f != null && !f.isNaN()) { if (f < 0f) f = 0f if (f > 1f) f = 1f column.columnBgImageAlpha = f showAlpha(updateText = false, updateSeek = true) } } catch (ex: Throwable) { log.e(ex, "alpha parse failed.") } } }) views.etAlpha.setOnEditorActionListener { _, actionId, _ -> when (actionId) { EditorInfo.IME_ACTION_DONE -> { views.etAlpha.hideKeyboard() true } else -> false } } } private fun show() { try { loadingBusy = true column.setHeaderBackground(views.llColumnHeader) val c = column.getHeaderNameColor() views.tvColumnName.textColor = c views.ivColumnHeader.setImageResource(column.getIconId()) views.ivColumnHeader.imageTintList = ColorStateList.valueOf(c) views.tvColumnName.text = column.getColumnName(false) if (column.columnBgColor != 0) { views.flColumnBackground.setBackgroundColor(column.columnBgColor) } else { ViewCompat.setBackground(views.flColumnBackground, null) } showAlpha(updateText = true, updateSeek = true) loadImage(views.ivColumnBackground, column.columnBgImage) views.tvSampleAcct.setTextColor(column.getAcctColor()) views.tvSampleContent.setTextColor(column.getContentColor()) } finally { loadingBusy = false } } private fun showAlpha(updateText: Boolean, updateSeek: Boolean) { var alpha = column.columnBgImageAlpha if (alpha.isNaN()) { alpha = 1f column.columnBgImageAlpha = alpha } views.ivColumnBackground.alpha = alpha val hasAlphaWarning = alpha < 0.3 && column.columnBgImage.isNotEmpty() views.tvBackgroundError.vg(hasAlphaWarning)?.text = getString(R.string.image_alpha_too_low) if (updateText) { views.etAlpha.setText("%.4f".format(column.columnBgImageAlpha)) } if (updateSeek) { views.sbColumnBackgroundAlpha.progress = (0.5f + alpha * PROGRESS_MAX).toInt() } } private fun closeBitmaps() { try { views.ivColumnBackground.setImageDrawable(null) lastImageUri = null lastImageBitmap?.recycle() lastImageBitmap = null } catch (ex: Throwable) { log.e(ex, "closeBitmaps failed.") } } private fun loadImage(ivColumnBackground: ImageView, url: String) { try { if (url.isEmpty()) { closeBitmaps() return } else if (url == lastImageUri) { // 今表示してるのと同じ return } // 直前のBitmapを掃除する closeBitmaps() val uri = url.mayUri() ?: return // 画像をロードして、成功したら表示してURLを覚える val resizeMax = (0.5f + 64f * density).toInt() lastImageBitmap = createResizedBitmap(this, uri, resizeMax) if (lastImageBitmap != null) { ivColumnBackground.setImageBitmap(lastImageBitmap) lastImageUri = url } } catch (ex: Throwable) { log.e(ex, "loadImage failed.") } } }