From c94ab65026ec8d75728fd47d1d3db27794f8a964 Mon Sep 17 00:00:00 2001 From: KazumaProject <59742125+KazumaProject@users.noreply.github.com> Date: Sun, 14 Jun 2026 15:15:25 -0400 Subject: [PATCH 1/8] inital reactoring for candidate view height setting --- .../setting_activity/AppPreference.kt | 162 +++++ ...CandidateHeightLandscapeSettingFragment.kt | 573 ++++++++++++++--- .../CandidateHeightPreviewUtils.kt | 81 +++ .../CandidateViewHeightSettingFragment.kt | 588 ++++++++++++++---- .../SuggestionAdapter2.kt | 1 + .../ui/setting/CommonPreferenceFragment.kt | 29 +- .../ui/setting/SettingCardEditorController.kt | 22 +- .../ui/setting/SettingHomeFragment.kt | 28 +- ...ent_candidate_height_landscape_setting.xml | 242 +++++-- ...fragment_candidate_view_height_setting.xml | 242 +++++-- app/src/main/res/values-ja/arrays.xml | 24 + app/src/main/res/values-ja/strings.xml | 13 + app/src/main/res/values/arrays.xml | 24 + app/src/main/res/values/strings.xml | 13 + 14 files changed, 1698 insertions(+), 344 deletions(-) create mode 100644 app/src/main/java/com/kazumaproject/markdownhelperkeyboard/setting_activity/ui/candidate_view_height_setting/CandidateHeightPreviewUtils.kt diff --git a/app/src/main/java/com/kazumaproject/markdownhelperkeyboard/setting_activity/AppPreference.kt b/app/src/main/java/com/kazumaproject/markdownhelperkeyboard/setting_activity/AppPreference.kt index 6b0bc6e2..be165845 100644 --- a/app/src/main/java/com/kazumaproject/markdownhelperkeyboard/setting_activity/AppPreference.kt +++ b/app/src/main/java/com/kazumaproject/markdownhelperkeyboard/setting_activity/AppPreference.kt @@ -34,6 +34,8 @@ object AppPreference { const val DEFAULT_CUSTOM_THEME_CANDIDATE_ITEM_BG_COLOR = 0x00000000 const val DEFAULT_CUSTOM_THEME_CANDIDATE_ITEM_PRESSED_BG_COLOR = 0xFFF0F0F3.toInt() + private const val MIN_CANDIDATE_VISIBLE_HEIGHT_DP = 30 + private const val MAX_CANDIDATE_VISIBLE_HEIGHT_DP = 300 private lateinit var preferences: SharedPreferences private val gson = Gson() @@ -398,6 +400,20 @@ object AppPreference { private val CANDIDATE_VIEW_HEIGHT_DP = Pair("candidate_view_height_dp_preference", 110) private val CANDIDATE_VIEW_EMPTY_HEIGHT_DP = Pair("candidate_view_empty_height_dp_preference", 110) + private val CANDIDATE_VIEW_HEIGHT_PORTRAIT_COLUMN_1_DP = + Pair("candidate_view_height_portrait_column_1_dp_preference", 110) + private val CANDIDATE_VIEW_HEIGHT_PORTRAIT_COLUMN_2_DP = + Pair("candidate_view_height_portrait_column_2_dp_preference", 165) + private val CANDIDATE_VIEW_HEIGHT_PORTRAIT_COLUMN_3_DP = + Pair("candidate_view_height_portrait_column_3_dp_preference", 230) + private val CANDIDATE_VIEW_HEIGHT_LANDSCAPE_COLUMN_1_DP = + Pair("candidate_view_height_landscape_column_1_dp_preference", 110) + private val CANDIDATE_VIEW_HEIGHT_LANDSCAPE_COLUMN_2_DP = + Pair("candidate_view_height_landscape_column_2_dp_preference", 165) + private val CANDIDATE_VIEW_HEIGHT_LANDSCAPE_COLUMN_3_DP = + Pair("candidate_view_height_landscape_column_3_dp_preference", 230) + private val CANDIDATE_HEIGHT_PER_COLUMN_MIGRATED = + Pair("candidate_height_per_column_migrated_preference", false) private val CLIP_BOARD_PREVIEW_PREFERENCE = Pair("clipboard_preview_enable_preference", true) @@ -668,6 +684,34 @@ object AppPreference { }.getOrDefault(defaultValue) } + private fun normalizeCandidateColumn(column: String): String = + if (column in setOf("1", "2", "3")) column else "1" + + private fun candidateHeightPreferenceFor( + isLandscape: Boolean, + column: String + ): Pair { + return when (normalizeCandidateColumn(column)) { + "2" -> if (isLandscape) { + CANDIDATE_VIEW_HEIGHT_LANDSCAPE_COLUMN_2_DP + } else { + CANDIDATE_VIEW_HEIGHT_PORTRAIT_COLUMN_2_DP + } + + "3" -> if (isLandscape) { + CANDIDATE_VIEW_HEIGHT_LANDSCAPE_COLUMN_3_DP + } else { + CANDIDATE_VIEW_HEIGHT_PORTRAIT_COLUMN_3_DP + } + + else -> if (isLandscape) { + CANDIDATE_VIEW_HEIGHT_LANDSCAPE_COLUMN_1_DP + } else { + CANDIDATE_VIEW_HEIGHT_PORTRAIT_COLUMN_1_DP + } + } + } + var clipboard_history_enable: Boolean? get() = preferences.getBoolean( CLIPBOARD_HISTORY_ENABLE.first, CLIPBOARD_HISTORY_ENABLE.second @@ -1639,6 +1683,124 @@ object AppPreference { it.putString(CANDIDATE_COLUMN_LANDSCAPE_PREFERENCE.first, value) } + fun migrateCandidateHeightPerColumnPreferencesIfNeeded() { + if (preferences.getBoolean( + CANDIDATE_HEIGHT_PER_COLUMN_MIGRATED.first, + CANDIDATE_HEIGHT_PER_COLUMN_MIGRATED.second + ) + ) { + return + } + + val portraitColumn = normalizeCandidateColumn(candidate_column_preference) + val landscapeColumn = normalizeCandidateColumn(candidate_column_landscape_preference) + val portraitHeight = (candidate_view_height_dp + ?: CANDIDATE_VIEW_HEIGHT_DP.second).coerceIn(MIN_CANDIDATE_VISIBLE_HEIGHT_DP, MAX_CANDIDATE_VISIBLE_HEIGHT_DP) + val landscapeHeight = (candidate_view_height_dp_landscape + ?: CANDIDATE_VIEW_HEIGHT_DP_LANDSCAPE.second).coerceIn(MIN_CANDIDATE_VISIBLE_HEIGHT_DP, MAX_CANDIDATE_VISIBLE_HEIGHT_DP) + + preferences.edit { editor -> + candidateHeightPreferenceFor(isLandscape = false, column = "1").let { editor.putInt(it.first, it.second) } + candidateHeightPreferenceFor(isLandscape = false, column = "2").let { editor.putInt(it.first, it.second) } + candidateHeightPreferenceFor(isLandscape = false, column = "3").let { editor.putInt(it.first, it.second) } + candidateHeightPreferenceFor(isLandscape = true, column = "1").let { editor.putInt(it.first, it.second) } + candidateHeightPreferenceFor(isLandscape = true, column = "2").let { editor.putInt(it.first, it.second) } + candidateHeightPreferenceFor(isLandscape = true, column = "3").let { editor.putInt(it.first, it.second) } + editor.putInt(candidateHeightPreferenceFor(false, portraitColumn).first, portraitHeight) + editor.putInt(candidateHeightPreferenceFor(true, landscapeColumn).first, landscapeHeight) + editor.putBoolean(CANDIDATE_HEIGHT_PER_COLUMN_MIGRATED.first, true) + } + } + + fun getCandidateVisibleHeightDp( + isLandscape: Boolean, + column: String + ): Int { + migrateCandidateHeightPerColumnPreferencesIfNeeded() + val preference = candidateHeightPreferenceFor(isLandscape, normalizeCandidateColumn(column)) + return readIntPreference(preference.first, preference.second) + .coerceIn(MIN_CANDIDATE_VISIBLE_HEIGHT_DP, MAX_CANDIDATE_VISIBLE_HEIGHT_DP) + } + + fun setCandidateVisibleHeightDp( + isLandscape: Boolean, + column: String, + heightDp: Int + ) { + migrateCandidateHeightPerColumnPreferencesIfNeeded() + val normalizedColumn = normalizeCandidateColumn(column) + val clampedHeight = heightDp.coerceIn( + MIN_CANDIDATE_VISIBLE_HEIGHT_DP, + MAX_CANDIDATE_VISIBLE_HEIGHT_DP + ) + val preference = candidateHeightPreferenceFor(isLandscape, normalizedColumn) + preferences.edit { editor -> + editor.putInt(preference.first, clampedHeight) + if (normalizeCandidateColumn(getCandidateColumn(isLandscape)) == normalizedColumn) { + editor.putInt( + if (isLandscape) { + CANDIDATE_VIEW_HEIGHT_DP_LANDSCAPE.first + } else { + CANDIDATE_VIEW_HEIGHT_DP.first + }, + clampedHeight + ) + } + } + } + + fun getCandidateColumn(isLandscape: Boolean): String = + normalizeCandidateColumn( + if (isLandscape) { + candidate_column_landscape_preference + } else { + candidate_column_preference + } + ) + + fun setCandidateColumnAndSyncHeight( + isLandscape: Boolean, + column: String + ) { + migrateCandidateHeightPerColumnPreferencesIfNeeded() + val normalizedColumn = normalizeCandidateColumn(column) + val heightDp = getCandidateVisibleHeightDp(isLandscape, normalizedColumn) + preferences.edit { editor -> + editor.putString( + if (isLandscape) { + CANDIDATE_COLUMN_LANDSCAPE_PREFERENCE.first + } else { + CANDIDATE_COLUMN_PREFERENCE.first + }, + normalizedColumn + ) + editor.putInt( + if (isLandscape) { + CANDIDATE_VIEW_HEIGHT_DP_LANDSCAPE.first + } else { + CANDIDATE_VIEW_HEIGHT_DP.first + }, + heightDp + ) + } + } + + fun syncActiveCandidateVisibleHeightToImePreference(isLandscape: Boolean) { + migrateCandidateHeightPerColumnPreferencesIfNeeded() + val column = getCandidateColumn(isLandscape) + val heightDp = getCandidateVisibleHeightDp(isLandscape, column) + preferences.edit { editor -> + editor.putInt( + if (isLandscape) { + CANDIDATE_VIEW_HEIGHT_DP_LANDSCAPE.first + } else { + CANDIDATE_VIEW_HEIGHT_DP.first + }, + heightDp + ) + } + } + var candidate_tab_preference: Boolean get() = preferences.getBoolean( CANDIDATE_TAB_PREFERENCE.first, CANDIDATE_TAB_PREFERENCE.second diff --git a/app/src/main/java/com/kazumaproject/markdownhelperkeyboard/setting_activity/ui/candidate_view_height_landscape_setting/CandidateHeightLandscapeSettingFragment.kt b/app/src/main/java/com/kazumaproject/markdownhelperkeyboard/setting_activity/ui/candidate_view_height_landscape_setting/CandidateHeightLandscapeSettingFragment.kt index 9cb9c568..755c874c 100644 --- a/app/src/main/java/com/kazumaproject/markdownhelperkeyboard/setting_activity/ui/candidate_view_height_landscape_setting/CandidateHeightLandscapeSettingFragment.kt +++ b/app/src/main/java/com/kazumaproject/markdownhelperkeyboard/setting_activity/ui/candidate_view_height_landscape_setting/CandidateHeightLandscapeSettingFragment.kt @@ -1,7 +1,11 @@ package com.kazumaproject.markdownhelperkeyboard.setting_activity.ui.candidate_view_height_landscape_setting import android.annotation.SuppressLint +import android.content.res.ColorStateList +import android.graphics.Color +import android.graphics.drawable.GradientDrawable import android.os.Bundle +import android.view.Gravity import android.view.LayoutInflater import android.view.Menu import android.view.MenuInflater @@ -9,21 +13,32 @@ import android.view.MenuItem import android.view.MotionEvent import android.view.View import android.view.ViewGroup +import android.widget.LinearLayout import android.widget.SeekBar -import androidx.core.view.MenuHost -import androidx.core.view.MenuProvider +import androidx.annotation.AttrRes +import androidx.appcompat.R as AppCompatR +import androidx.appcompat.widget.AppCompatImageButton +import androidx.core.content.ContextCompat +import androidx.core.view.isVisible import androidx.fragment.app.Fragment import androidx.lifecycle.Lifecycle import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.LinearLayoutManager +import com.google.android.material.R as MaterialR +import com.google.android.material.textview.MaterialTextView import com.kazumaproject.markdownhelperkeyboard.R -import com.kazumaproject.markdownhelperkeyboard.converter.candidate.Candidate import com.kazumaproject.markdownhelperkeyboard.databinding.FragmentCandidateHeightLandscapeSettingBinding -import com.kazumaproject.markdownhelperkeyboard.ime_service.adapters.GridSpacingItemDecoration +import com.kazumaproject.markdownhelperkeyboard.ime_service.state.CandidateTab +import com.kazumaproject.markdownhelperkeyboard.ime_service.state.KeyboardType import com.kazumaproject.markdownhelperkeyboard.setting_activity.AppPreference +import com.kazumaproject.markdownhelperkeyboard.setting_activity.ui.candidate_view_height_setting.CandidateHeightPreviewGridSpacingDecoration import com.kazumaproject.markdownhelperkeyboard.setting_activity.ui.candidate_view_height_setting.SuggestionAdapter2 +import com.kazumaproject.markdownhelperkeyboard.setting_activity.ui.candidate_view_height_setting.clearItemDecorations +import com.kazumaproject.markdownhelperkeyboard.setting_activity.ui.candidate_view_height_setting.createCandidateHeightPreviewCandidates +import com.kazumaproject.markdownhelperkeyboard.short_cut.ShortcutType import dagger.hilt.android.AndroidEntryPoint import timber.log.Timber +import java.util.Locale import javax.inject.Inject import kotlin.math.roundToInt @@ -34,40 +49,36 @@ class CandidateHeightLandscapeSettingFragment : Fragment() { lateinit var appPreference: AppPreference private lateinit var suggestionAdapter: SuggestionAdapter2 - private lateinit var candidateList: List private var _binding: FragmentCandidateHeightLandscapeSettingBinding? = null private val binding get() = _binding!! - private var isCandidateListVisible = false + private var isCandidateListVisible = true private var isSyncingHeightControls = false + private var isSyncingLetterSizeControls = false + private var isSyncingColumnControls = false + private var simpleKeyboardPreview: View? = null private val minHeightDp = 30 private val maxHeightDp = 300 - private val defaultHeightDp = 110 + private val minCandidateTextSize = 10f + private val maxCandidateTextSize = 40f + private val defaultCandidateTextSize = 14.0f + + private val previewCandidates = createCandidateHeightPreviewCandidates() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) suggestionAdapter = SuggestionAdapter2() - candidateList = (1..16).map { index -> - Candidate( - string = "候補 $index", - type = (index % 4).toByte(), - length = "候補 $index".length.toUByte(), - score = 100 - index, - leftId = (index * 10).toShort(), - rightId = (index * 10 + 1).toShort() - ) - } } override fun onCreateView( - inflater: LayoutInflater, container: ViewGroup?, + inflater: LayoutInflater, + container: ViewGroup?, savedInstanceState: Bundle? ): View { _binding = FragmentCandidateHeightLandscapeSettingBinding.inflate(inflater, container, false) - setupMenu() return binding.root } @@ -75,39 +86,41 @@ class CandidateHeightLandscapeSettingFragment : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + appPreference.migrateCandidateHeightPerColumnPreferencesIfNeeded() + appPreference.syncActiveCandidateVisibleHeightToImePreference(isLandscape = true) + + setupMenu() + setupAdapter() + setupColumnControls() setupResizeHandle() + setupHeightSeekBar() + setupHeightEditText() + setupCandidateLetterSizeSeekBar() + setupCandidateLetterSizeEditText() + setupKeyboardPreview() setSuggestionView() - suggestionAdapter.apply { - setUndoEnabled(false) - setPasteEnabled(false) - onListUpdated = { - applyCurrentDimensions() - } - } - binding.toggleCandidateListButton.setOnClickListener { isCandidateListVisible = !isCandidateListVisible updateCandidateListAndHeight() } - setupHeightSeekBar() - setupHeightEditText() - + applyCandidateTextSize(appPreference.candidate_letter_size ?: defaultCandidateTextSize, persist = false) updateCandidateListAndHeight() applyHeightDp(selectedHeightDp(), persist = false) } - override fun onDestroyView() { super.onDestroyView() - isCandidateListVisible = false + binding.candidateHeightSettingRecyclerview.adapter = null + suggestionAdapter.release() + simpleKeyboardPreview = null _binding = null } private fun setupMenu() { - val menuHost: MenuHost = requireActivity() - menuHost.addMenuProvider(object : MenuProvider { + val menuHost: androidx.core.view.MenuHost = requireActivity() + menuHost.addMenuProvider(object : androidx.core.view.MenuProvider { override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) { menuInflater.inflate(R.menu.fragment_reset_menu, menu) } @@ -130,34 +143,59 @@ class CandidateHeightLandscapeSettingFragment : Fragment() { }, viewLifecycleOwner, Lifecycle.State.RESUMED) } - private fun resetSettings() { - // Reset landscape-specific preferences - if (!isCandidateListVisible) { - appPreference.candidate_view_height_dp_landscape = defaultHeightDp - } else { - when (appPreference.candidate_column_preference) { - "1" -> appPreference.candidate_view_height_dp_landscape = defaultHeightDp - "2" -> appPreference.candidate_view_height_dp_landscape = 165 - "3" -> appPreference.candidate_view_height_dp_landscape = 230 + private fun setupAdapter() { + suggestionAdapter.apply { + setUndoEnabled(false) + setPasteEnabled(false) + onListUpdated = { + applyCurrentDimensions() } } - appPreference.candidate_view_empty_height_dp_landscape = defaultHeightDp + binding.candidateHeightSettingRecyclerview.adapter = suggestionAdapter + } + + private fun setupColumnControls() { + syncColumnControls() + binding.candidateColumnToggleGroup.addOnButtonCheckedListener { _, checkedId, isChecked -> + if (!isChecked || isSyncingColumnControls) return@addOnButtonCheckedListener + val column = columnForButtonId(checkedId) ?: return@addOnButtonCheckedListener + appPreference.setCandidateColumnAndSyncHeight(isLandscape = true, column = column) + setSuggestionView() + syncColumnControls() + applyHeightDp(selectedHeightDp(), persist = false) + } + } + + private fun resetSettings() { + appPreference.setCandidateVisibleHeightDp(isLandscape = true, column = "1", heightDp = 110) + appPreference.setCandidateVisibleHeightDp(isLandscape = true, column = "2", heightDp = 165) + appPreference.setCandidateVisibleHeightDp(isLandscape = true, column = "3", heightDp = 230) + appPreference.candidate_view_empty_height_dp_landscape = 110 + appPreference.syncActiveCandidateVisibleHeightToImePreference(isLandscape = true) + appPreference.candidate_letter_size = defaultCandidateTextSize + applyCandidateTextSize(defaultCandidateTextSize, persist = false) + syncColumnControls() + updateCandidateListAndHeight() applyHeightDp(selectedHeightDp(), persist = false) } private fun updateCandidateListAndHeight() { if (isCandidateListVisible) { - suggestionAdapter.suggestions = candidateList - binding.toggleCandidateListButton.text = "入力時" + suggestionAdapter.suggestions = previewCandidates + binding.toggleCandidateListButton.text = getString(R.string.candidate_preview_input_mode) } else { suggestionAdapter.suggestions = emptyList() - binding.toggleCandidateListButton.text = "未入力時" + binding.toggleCandidateListButton.text = getString(R.string.candidate_preview_empty_mode) } + updateCandidateTabPreview() + updateShortcutToolbarPreview() applyHeightDp(selectedHeightDp(), persist = false) } private fun setSuggestionView() { - when (val columnNum = appPreference.candidate_column_preference) { + val columnNum = appPreference.getCandidateColumn(isLandscape = true) + clearItemDecorations(binding.candidateHeightSettingRecyclerview) + when (columnNum) { "1" -> { binding.candidateHeightSettingRecyclerview.layoutManager = LinearLayoutManager(requireContext(), LinearLayoutManager.HORIZONTAL, false) @@ -165,24 +203,25 @@ class CandidateHeightLandscapeSettingFragment : Fragment() { "2", "3" -> { val spanCount = columnNum.toInt() - val gridLayoutManager = GridLayoutManager( - requireContext(), spanCount, GridLayoutManager.HORIZONTAL, false - ) + binding.candidateHeightSettingRecyclerview.layoutManager = + GridLayoutManager( + requireContext(), + spanCount, + GridLayoutManager.HORIZONTAL, + false + ) val spacingInPixels = resources.getDimensionPixelSize(com.kazumaproject.core.R.dimen.grid_spacing) - - binding.candidateHeightSettingRecyclerview.layoutManager = - gridLayoutManager binding.candidateHeightSettingRecyclerview.addItemDecoration( - GridSpacingItemDecoration( - spanCount, spacingInPixels, true + CandidateHeightPreviewGridSpacingDecoration( + spanCount = spanCount, + spacing = spacingInPixels, + includeEdge = true ) ) } } - binding.candidateHeightSettingRecyclerview.apply { - adapter = suggestionAdapter - } + binding.candidateHeightSettingRecyclerview.adapter = suggestionAdapter } private fun applyCurrentDimensions() { @@ -191,16 +230,23 @@ class CandidateHeightLandscapeSettingFragment : Fragment() { private fun selectedHeightDp(): Int { return if (isCandidateListVisible) { - appPreference.candidate_view_height_dp_landscape ?: defaultHeightDp + appPreference.getCandidateVisibleHeightDp( + isLandscape = true, + column = appPreference.getCandidateColumn(isLandscape = true) + ) } else { - appPreference.candidate_view_empty_height_dp_landscape ?: defaultHeightDp + appPreference.candidate_view_empty_height_dp_landscape ?: 110 } } private fun saveSelectedHeightDp(heightDp: Int) { val clamped = heightDp.coerceIn(minHeightDp, maxHeightDp) if (isCandidateListVisible) { - appPreference.candidate_view_height_dp_landscape = clamped + appPreference.setCandidateVisibleHeightDp( + isLandscape = true, + column = appPreference.getCandidateColumn(isLandscape = true), + heightDp = clamped + ) } else { appPreference.candidate_view_empty_height_dp_landscape = clamped } @@ -208,7 +254,7 @@ class CandidateHeightLandscapeSettingFragment : Fragment() { private fun applyHeightDp(heightDp: Int, persist: Boolean) { val clamped = heightDp.coerceIn(minHeightDp, maxHeightDp) - val heightPx = (clamped * resources.displayMetrics.density).toInt() + val heightPx = clamped.dpToPx() updatePreviewHeightPx(heightPx) if (persist) { saveSelectedHeightDp(clamped) @@ -217,16 +263,33 @@ class CandidateHeightLandscapeSettingFragment : Fragment() { } private fun updatePreviewHeightPx(candidateHeightPx: Int) { - binding.candidateHeightSettingRecyclerview.layoutParams = - binding.candidateHeightSettingRecyclerview.layoutParams.apply { + binding.candidatePreviewFrame.layoutParams = + binding.candidatePreviewFrame.layoutParams.apply { height = candidateHeightPx } binding.candidateHeightSettingContent.layoutParams = binding.candidateHeightSettingContent.layoutParams.apply { - height = candidateHeightPx + height = candidateHeightPx + shortcutToolbarHeightPx() + keyboardPreviewHeightPx() } } + private fun shortcutToolbarHeightPx(): Int { + return if (binding.independentShortcutToolbarPreviewContainer.isVisible) 36.dpToPx() else 0 + } + + private fun keyboardPreviewHeightPx(): Int { + val previewHeight = binding.keyboardPreviewContainer.layoutParams.height + return if (previewHeight > 0) { + previewHeight + } else { + keyboardPreviewHeightDp().dpToPx() + } + } + + private fun keyboardPreviewHeightDp(): Int { + return (appPreference.keyboard_height_landscape ?: 220).coerceIn(140, 360) + } + private fun syncHeightControls(heightDp: Int) { if (isSyncingHeightControls) return isSyncingHeightControls = true @@ -256,8 +319,7 @@ class CandidateHeightLandscapeSettingFragment : Fragment() { fromUser: Boolean ) { if (!fromUser || isSyncingHeightControls) return - val heightDp = minHeightDp + progress - applyHeightDp(heightDp, persist = true) + applyHeightDp(minHeightDp + progress, persist = true) } override fun onStartTrackingTouch(seekBar: SeekBar?) = Unit @@ -281,21 +343,92 @@ class CandidateHeightLandscapeSettingFragment : Fragment() { private fun applyHeightFromEditText() { if (isSyncingHeightControls) return - val raw = binding.candidateHeightEditText.text?.toString()?.trim() - val value = raw?.toIntOrNull() + val value = binding.candidateHeightEditText.text?.toString()?.trim()?.toIntOrNull() if (value == null) { binding.candidateHeightInputLayout.error = getString(R.string.candidate_height_invalid_value) return } - val clamped = value.coerceIn(minHeightDp, maxHeightDp) - applyHeightDp(clamped, persist = true) + applyHeightDp(value.coerceIn(minHeightDp, maxHeightDp), persist = true) + } + + private fun setupCandidateLetterSizeSeekBar() { + binding.candidateLetterSizeSeekbar.max = + ((maxCandidateTextSize - minCandidateTextSize) * 10).roundToInt() + binding.candidateLetterSizeSeekbar.setOnSeekBarChangeListener( + object : SeekBar.OnSeekBarChangeListener { + override fun onProgressChanged( + seekBar: SeekBar?, + progress: Int, + fromUser: Boolean + ) { + if (!fromUser || isSyncingLetterSizeControls) return + val newSize = minCandidateTextSize + progress / 10f + applyCandidateTextSize(newSize, persist = true) + } + + override fun onStartTrackingTouch(seekBar: SeekBar?) = Unit + + override fun onStopTrackingTouch(seekBar: SeekBar?) = Unit + } + ) + } + + private fun setupCandidateLetterSizeEditText() { + binding.candidateLetterSizeEditText.setOnEditorActionListener { _, _, _ -> + applyCandidateLetterSizeFromEditText() + false + } + binding.candidateLetterSizeEditText.setOnFocusChangeListener { _, hasFocus -> + if (!hasFocus) { + applyCandidateLetterSizeFromEditText() + } + } + } + + private fun applyCandidateLetterSizeFromEditText() { + if (isSyncingLetterSizeControls) return + val value = binding.candidateLetterSizeEditText.text?.toString()?.trim()?.toFloatOrNull() + if (value == null) { + binding.candidateLetterSizeInputLayout.error = + getString(R.string.candidate_letter_size_invalid_value) + return + } + applyCandidateTextSize(value.coerceIn(minCandidateTextSize, maxCandidateTextSize), persist = true) + } + + private fun applyCandidateTextSize(size: Float, persist: Boolean) { + val clamped = ((size.coerceIn(minCandidateTextSize, maxCandidateTextSize) * 10).roundToInt() / 10f) + suggestionAdapter.setCandidateTextSize(clamped) + if (persist) { + appPreference.candidate_letter_size = clamped + } + syncCandidateLetterSizeControls(clamped) + } + + private fun syncCandidateLetterSizeControls(size: Float) { + if (isSyncingLetterSizeControls) return + isSyncingLetterSizeControls = true + try { + val clamped = size.coerceIn(minCandidateTextSize, maxCandidateTextSize) + binding.candidateLetterSizeSeekbar.progress = + ((clamped - minCandidateTextSize) * 10).roundToInt() + val text = String.format(Locale.US, "%.1f", clamped) + if (binding.candidateLetterSizeEditText.text?.toString() != text) { + binding.candidateLetterSizeEditText.setText(text) + binding.candidateLetterSizeEditText.setSelection(text.length) + } + binding.candidateLetterSizeInputLayout.error = null + } finally { + isSyncingLetterSizeControls = false + } } @SuppressLint("ClickableViewAccessibility") private fun setupResizeHandle() { var initialY = 0f var initialHeight = 0 + val density = resources.displayMetrics.density val minHeightPx = minHeightDp * density val maxHeightPx = maxHeightDp * density @@ -304,7 +437,7 @@ class CandidateHeightLandscapeSettingFragment : Fragment() { when (event.action) { MotionEvent.ACTION_DOWN -> { initialY = event.rawY - initialHeight = binding.candidateHeightSettingRecyclerview.height + initialHeight = binding.candidatePreviewFrame.height true } @@ -312,7 +445,7 @@ class CandidateHeightLandscapeSettingFragment : Fragment() { val deltaY = event.rawY - initialY val newHeight = (initialHeight - deltaY).coerceIn(minHeightPx, maxHeightPx) updatePreviewHeightPx(newHeight.toInt()) - binding.candidateHeightSettingRecyclerview.requestLayout() + binding.candidatePreviewFrame.requestLayout() binding.candidateHeightSettingContent.requestLayout() true } @@ -330,20 +463,298 @@ class CandidateHeightLandscapeSettingFragment : Fragment() { private fun saveHeightPreference(): Int { val density = resources.displayMetrics.density - val heightPx = binding.candidateHeightSettingRecyclerview.layoutParams.height + val heightPx = binding.candidatePreviewFrame.layoutParams.height .takeIf { it > 0 } - ?: binding.candidateHeightSettingRecyclerview.height + ?: binding.candidatePreviewFrame.height val finalHeightDp = (heightPx / density).roundToInt().coerceIn(minHeightDp, maxHeightDp) saveSelectedHeightDp(finalHeightDp) - // Save to landscape-specific preferences - if (isCandidateListVisible) { - Timber.d("saveHeightPreference landscape (with candidates): $finalHeightDp dp") + Timber.d( + "saveHeightPreference landscape (%s): %d dp", + if (isCandidateListVisible) "with candidates" else "empty", + finalHeightDp + ) + return finalHeightDp + } + + private fun syncColumnControls() { + if (isSyncingColumnControls) return + isSyncingColumnControls = true + try { + binding.candidateColumnToggleGroup.check( + when (appPreference.getCandidateColumn(isLandscape = true)) { + "2" -> R.id.candidate_column_two_button + "3" -> R.id.candidate_column_three_button + else -> R.id.candidate_column_one_button + } + ) + } finally { + isSyncingColumnControls = false + } + } + + private fun columnForButtonId(id: Int): String? = + when (id) { + R.id.candidate_column_one_button -> "1" + R.id.candidate_column_two_button -> "2" + R.id.candidate_column_three_button -> "3" + else -> null + } + + private fun updateCandidateTabPreview() { + val showCandidateTab = appPreference.candidate_tab_preference && isCandidateListVisible + binding.candidateTabPreviewContainer.isVisible = showCandidateTab + if (!showCandidateTab) return + + binding.candidateTabPreviewContainer.removeAllViews() + val tabs = runCatching { appPreference.candidate_tab_order } + .getOrDefault(listOf(CandidateTab.PREDICTION, CandidateTab.CONVERSION, CandidateTab.EISUKANA)) + tabs.forEachIndexed { index, tab -> + binding.candidateTabPreviewContainer.addView( + previewTabView(label = tab.previewLabel(), selected = index == 0), + LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.MATCH_PARENT, 1f) + ) + } + } + + private fun updateShortcutToolbarPreview() { + val state = resolvePreviewShortcutToolbarState() + binding.independentShortcutToolbarPreviewContainer.isVisible = state.showIndependentToolbar + binding.integratedShortcutToolbarPreviewContainer.isVisible = state.showIntegratedShortcuts + if (state.showIndependentToolbar) { + populateShortcutToolbarPreview(binding.independentShortcutToolbarPreviewContainer) + } + if (state.showIntegratedShortcuts) { + populateShortcutToolbarPreview(binding.integratedShortcutToolbarPreviewContainer) + } + } + + private data class PreviewShortcutToolbarState( + val showIndependentToolbar: Boolean, + val showIntegratedShortcuts: Boolean + ) + + private fun resolvePreviewShortcutToolbarState(): PreviewShortcutToolbarState { + val visible = appPreference.shortcut_toolbar_visibility_preference + val integrated = appPreference.shortcut_toolbar_integrated_in_suggestion_preference + if (!visible) { + return PreviewShortcutToolbarState( + showIndependentToolbar = false, + showIntegratedShortcuts = false + ) + } + if (!integrated) { + return PreviewShortcutToolbarState( + showIndependentToolbar = true, + showIntegratedShortcuts = false + ) + } + val inputStringEmpty = !isCandidateListVisible + val tailEmpty = true + val clipboardPreviewShown = false + val selectedTextGemmaActionsShown = false + val suggestionsEmpty = !isCandidateListVisible + val customLayoutPickerShown = false + val showIntegrated = + inputStringEmpty && + tailEmpty && + !clipboardPreviewShown && + !selectedTextGemmaActionsShown && + suggestionsEmpty && + !customLayoutPickerShown + return PreviewShortcutToolbarState( + showIndependentToolbar = false, + showIntegratedShortcuts = showIntegrated + ) + } + + private fun populateShortcutToolbarPreview(container: LinearLayout) { + container.removeAllViews() + listOf( + ShortcutType.SETTINGS, + ShortcutType.EMOJI, + ShortcutType.TEMPLATE, + ShortcutType.KEYBOARD_PICKER, + ShortcutType.PASTE + ).forEach { shortcut -> + val button = AppCompatImageButton(requireContext()).apply { + setImageResource(shortcut.iconResId) + imageTintList = ColorStateList.valueOf(resolveThemeColor(MaterialR.attr.colorOnSurface)) + background = null + contentDescription = shortcut.description + isClickable = false + isFocusable = false + setPadding(8.dpToPx(), 6.dpToPx(), 8.dpToPx(), 6.dpToPx()) + } + container.addView( + button, + LinearLayout.LayoutParams(42.dpToPx(), ViewGroup.LayoutParams.MATCH_PARENT) + ) + } + } + + private fun setupKeyboardPreview() { + val heightPx = keyboardPreviewHeightPx() + binding.keyboardPreviewContainer.layoutParams = + binding.keyboardPreviewContainer.layoutParams.apply { + height = heightPx + } + binding.candidateHeightSettingTenkeyPreview.setOnTouchListener { _, _ -> true } + val previewKeyboardType = appPreference.keyboard_order.firstOrNull() ?: KeyboardType.TENKEY + if (previewKeyboardType == KeyboardType.TENKEY) { + binding.candidateHeightSettingTenkeyPreview.isVisible = true + simpleKeyboardPreview?.isVisible = false } else { - Timber.d("saveHeightPreference landscape (empty): $finalHeightDp dp") + binding.candidateHeightSettingTenkeyPreview.isVisible = false + val simplePreview = simpleKeyboardPreview ?: createSimpleKeyboardPreview(previewKeyboardType).also { + it.tag = SIMPLE_KEYBOARD_PREVIEW_TAG + binding.keyboardPreviewContainer.addView( + it, + ViewGroup.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT + ) + ) + simpleKeyboardPreview = it + } + simplePreview.isVisible = true } - return finalHeightDp + } + + private fun createSimpleKeyboardPreview(type: KeyboardType): View { + val rows = when (type) { + KeyboardType.QWERTY -> listOf( + listOf("Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P"), + listOf("A", "S", "D", "F", "G", "H", "J", "K", "L"), + listOf("Z", "X", "C", "V", "B", "N", "M") + ) + + KeyboardType.ROMAJI -> listOf( + listOf("あ", "か", "さ", "た", "な", "は", "ま", "や", "ら", "わ"), + listOf("A", "I", "U", "E", "O", "K", "S", "T", "N"), + listOf("Shift", "Space", "Enter") + ) + + KeyboardType.SUMIRE -> listOf( + listOf("あ", "い", "う", "え", "お"), + listOf("か", "き", "く", "け", "こ"), + listOf("さ", "し", "す", "せ", "そ") + ) + + KeyboardType.CUSTOM -> listOf( + listOf("Custom", "Key", "Layout"), + listOf("かな", "英数", "記号"), + listOf("Space", "Enter") + ) + + KeyboardType.TENKEY -> emptyList() + } + return LinearLayout(requireContext()).apply { + orientation = LinearLayout.VERTICAL + gravity = Gravity.CENTER + setPadding(8.dpToPx(), 8.dpToPx(), 8.dpToPx(), 8.dpToPx()) + rows.forEach { row -> + addView( + LinearLayout(requireContext()).apply { + orientation = LinearLayout.HORIZONTAL + gravity = Gravity.CENTER + row.forEach { label -> + addView( + MaterialTextView(requireContext()).apply { + text = label + gravity = Gravity.CENTER + setTextColor( + ContextCompat.getColor( + requireContext(), + com.kazumaproject.core.R.color.main_text_color + ) + ) + textSize = 13f + maxLines = 1 + background = keyPreviewBackground() + }, + LinearLayout.LayoutParams(0, 0, 1f).apply { + height = 42.dpToPx() + marginStart = 2.dpToPx() + marginEnd = 2.dpToPx() + } + ) + } + }, + LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + 0, + 1f + ).apply { + topMargin = 2.dpToPx() + bottomMargin = 2.dpToPx() + } + ) + } + } + } + + private fun previewTabView(label: String, selected: Boolean): MaterialTextView { + return MaterialTextView(requireContext()).apply { + text = label + gravity = Gravity.CENTER + textSize = 13f + setTextColor( + resolveThemeColor( + if (selected) MaterialR.attr.colorOnPrimary else MaterialR.attr.colorOnSurface + ) + ) + background = roundedBackground( + fillColor = resolveThemeColor( + if (selected) AppCompatR.attr.colorPrimary else MaterialR.attr.colorSurfaceVariant + ), + radiusDp = 6 + ) + } + } + + private fun keyPreviewBackground(): GradientDrawable = + roundedBackground( + fillColor = resolveThemeColor(MaterialR.attr.colorSurfaceVariant), + strokeColor = resolveThemeColor(MaterialR.attr.colorOutline), + radiusDp = 6 + ) + + private fun roundedBackground( + fillColor: Int, + strokeColor: Int? = null, + radiusDp: Int + ): GradientDrawable { + return GradientDrawable().apply { + shape = GradientDrawable.RECTANGLE + cornerRadius = radiusDp.dpToPx().toFloat() + setColor(fillColor) + strokeColor?.let { setStroke(1.dpToPx(), it) } + } + } + + private fun CandidateTab.previewLabel(): String = + when (this) { + CandidateTab.PREDICTION -> "予測" + CandidateTab.CONVERSION -> "変換" + CandidateTab.EISUKANA -> "英数カナ" + } + + private fun resolveThemeColor(@AttrRes attr: Int): Int { + val typedArray = requireContext().obtainStyledAttributes(intArrayOf(attr)) + return try { + typedArray.getColor(0, Color.TRANSPARENT) + } finally { + typedArray.recycle() + } + } + + private fun Int.dpToPx(): Int = + (this * resources.displayMetrics.density).roundToInt() + + companion object { + private const val SIMPLE_KEYBOARD_PREVIEW_TAG = "simple_keyboard_preview" } } diff --git a/app/src/main/java/com/kazumaproject/markdownhelperkeyboard/setting_activity/ui/candidate_view_height_setting/CandidateHeightPreviewUtils.kt b/app/src/main/java/com/kazumaproject/markdownhelperkeyboard/setting_activity/ui/candidate_view_height_setting/CandidateHeightPreviewUtils.kt new file mode 100644 index 00000000..316ed3fb --- /dev/null +++ b/app/src/main/java/com/kazumaproject/markdownhelperkeyboard/setting_activity/ui/candidate_view_height_setting/CandidateHeightPreviewUtils.kt @@ -0,0 +1,81 @@ +package com.kazumaproject.markdownhelperkeyboard.setting_activity.ui.candidate_view_height_setting + +import android.graphics.Rect +import android.view.View +import androidx.recyclerview.widget.GridLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.kazumaproject.markdownhelperkeyboard.converter.candidate.Candidate + +internal fun createCandidateHeightPreviewCandidates(): List { + return listOf( + "へんかん", + "変換", + "変換候補", + "変換する", + "日本語", + "入力", + "候補欄", + "設定", + "予測", + "学習なし", + "オフライン", + "キーボード" + ).mapIndexed { index, text -> + Candidate( + string = text, + type = 1.toByte(), + length = text.length.toUByte(), + score = 4000 - index, + leftId = 0.toShort(), + rightId = 0.toShort() + ) + } +} + +internal fun clearItemDecorations(recyclerView: RecyclerView) { + while (recyclerView.itemDecorationCount > 0) { + recyclerView.removeItemDecorationAt(0) + } +} + +internal class CandidateHeightPreviewGridSpacingDecoration( + private val spanCount: Int, + private val spacing: Int, + private val includeEdge: Boolean +) : RecyclerView.ItemDecoration() { + + override fun getItemOffsets( + outRect: Rect, + view: View, + parent: RecyclerView, + state: RecyclerView.State + ) { + val position = parent.getChildAdapterPosition(view) + if (position < 0) { + outRect.set(0, 0, 0, 0) + return + } + + val layoutManager = parent.layoutManager as? GridLayoutManager ?: return + val layoutParams = view.layoutParams as GridLayoutManager.LayoutParams + + if (layoutManager.orientation == GridLayoutManager.HORIZONTAL) { + val row = layoutParams.spanIndex + val column = position / spanCount + if (includeEdge) { + outRect.top = spacing - row * spacing / spanCount + outRect.bottom = (row + 1) * spacing / spanCount + if (column == 0) { + outRect.left = spacing + } + outRect.right = spacing + } else { + outRect.top = row * spacing / spanCount + outRect.bottom = spacing - (row + 1) * spacing / spanCount + if (column > 0) { + outRect.left = spacing + } + } + } + } +} diff --git a/app/src/main/java/com/kazumaproject/markdownhelperkeyboard/setting_activity/ui/candidate_view_height_setting/CandidateViewHeightSettingFragment.kt b/app/src/main/java/com/kazumaproject/markdownhelperkeyboard/setting_activity/ui/candidate_view_height_setting/CandidateViewHeightSettingFragment.kt index 461b5b23..9370e0d6 100644 --- a/app/src/main/java/com/kazumaproject/markdownhelperkeyboard/setting_activity/ui/candidate_view_height_setting/CandidateViewHeightSettingFragment.kt +++ b/app/src/main/java/com/kazumaproject/markdownhelperkeyboard/setting_activity/ui/candidate_view_height_setting/CandidateViewHeightSettingFragment.kt @@ -1,7 +1,11 @@ package com.kazumaproject.markdownhelperkeyboard.setting_activity.ui.candidate_view_height_setting import android.annotation.SuppressLint +import android.content.res.ColorStateList +import android.graphics.Color +import android.graphics.drawable.GradientDrawable import android.os.Bundle +import android.view.Gravity import android.view.LayoutInflater import android.view.Menu import android.view.MenuInflater @@ -9,20 +13,28 @@ import android.view.MenuItem import android.view.MotionEvent import android.view.View import android.view.ViewGroup +import android.widget.LinearLayout import android.widget.SeekBar -import androidx.core.view.MenuHost -import androidx.core.view.MenuProvider +import androidx.annotation.AttrRes +import androidx.appcompat.R as AppCompatR +import androidx.appcompat.widget.AppCompatImageButton +import androidx.core.content.ContextCompat +import androidx.core.view.isVisible import androidx.fragment.app.Fragment import androidx.lifecycle.Lifecycle import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.LinearLayoutManager +import com.google.android.material.R as MaterialR +import com.google.android.material.textview.MaterialTextView import com.kazumaproject.markdownhelperkeyboard.R -import com.kazumaproject.markdownhelperkeyboard.converter.candidate.Candidate import com.kazumaproject.markdownhelperkeyboard.databinding.FragmentCandidateViewHeightSettingBinding -import com.kazumaproject.markdownhelperkeyboard.ime_service.adapters.GridSpacingItemDecoration +import com.kazumaproject.markdownhelperkeyboard.ime_service.state.CandidateTab +import com.kazumaproject.markdownhelperkeyboard.ime_service.state.KeyboardType import com.kazumaproject.markdownhelperkeyboard.setting_activity.AppPreference +import com.kazumaproject.markdownhelperkeyboard.short_cut.ShortcutType import dagger.hilt.android.AndroidEntryPoint import timber.log.Timber +import java.util.Locale import javax.inject.Inject import kotlin.math.roundToInt @@ -33,40 +45,35 @@ class CandidateViewHeightSettingFragment : Fragment() { lateinit var appPreference: AppPreference private lateinit var suggestionAdapter: SuggestionAdapter2 - private lateinit var candidateList: List private var _binding: FragmentCandidateViewHeightSettingBinding? = null private val binding get() = _binding!! - // State to track if the candidate list is visible. Set to false for empty by default. - private var isCandidateListVisible = false + private var isCandidateListVisible = true private var isSyncingHeightControls = false + private var isSyncingLetterSizeControls = false + private var isSyncingColumnControls = false + private var simpleKeyboardPreview: View? = null private val minHeightDp = 30 private val maxHeightDp = 300 - private val defaultHeightDp = 110 + private val minCandidateTextSize = 10f + private val maxCandidateTextSize = 40f + private val defaultCandidateTextSize = 14.0f + + private val previewCandidates = createCandidateHeightPreviewCandidates() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) suggestionAdapter = SuggestionAdapter2() - candidateList = (1..16).map { index -> - Candidate( - string = "候補 $index", - type = (index % 4).toByte(), - length = "候補 $index".length.toUByte(), - score = 100 - index, - leftId = (index * 10).toShort(), - rightId = (index * 10 + 1).toShort() - ) - } } override fun onCreateView( - inflater: LayoutInflater, container: ViewGroup?, + inflater: LayoutInflater, + container: ViewGroup?, savedInstanceState: Bundle? ): View { _binding = FragmentCandidateViewHeightSettingBinding.inflate(inflater, container, false) - setupMenu() return binding.root } @@ -74,46 +81,41 @@ class CandidateViewHeightSettingFragment : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + appPreference.migrateCandidateHeightPerColumnPreferencesIfNeeded() + appPreference.syncActiveCandidateVisibleHeightToImePreference(isLandscape = false) + + setupMenu() + setupAdapter() + setupColumnControls() setupResizeHandle() + setupHeightSeekBar() + setupHeightEditText() + setupCandidateLetterSizeSeekBar() + setupCandidateLetterSizeEditText() + setupKeyboardPreview() setSuggestionView() - suggestionAdapter.apply { - setUndoEnabled(false) - setPasteEnabled(false) - - onListUpdated = { - applyCurrentDimensions() - } - } - - binding.toggleCandidateListButton.setOnClickListener { isCandidateListVisible = !isCandidateListVisible updateCandidateListAndHeight() } - binding.candidateHeightSettingTenkeyPreview.apply { - setOnTouchListener { _, _ -> - false - } - } - setupHeightSeekBar() - setupHeightEditText() - - // Set initial state + applyCandidateTextSize(appPreference.candidate_letter_size ?: defaultCandidateTextSize, persist = false) updateCandidateListAndHeight() applyHeightDp(selectedHeightDp(), persist = false) } override fun onDestroyView() { super.onDestroyView() - isCandidateListVisible = false + binding.candidateHeightSettingRecyclerview.adapter = null + suggestionAdapter.release() + simpleKeyboardPreview = null _binding = null } private fun setupMenu() { - val menuHost: MenuHost = requireActivity() - menuHost.addMenuProvider(object : MenuProvider { + val menuHost: androidx.core.view.MenuHost = requireActivity() + menuHost.addMenuProvider(object : androidx.core.view.MenuProvider { override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) { menuInflater.inflate(R.menu.fragment_reset_menu, menu) } @@ -136,42 +138,59 @@ class CandidateViewHeightSettingFragment : Fragment() { }, viewLifecycleOwner, Lifecycle.State.RESUMED) } - private fun resetSettings() { - // Reset height for when candidates are visible - if (!isCandidateListVisible) { - appPreference.candidate_view_height_dp = defaultHeightDp - } else { - when (appPreference.candidate_column_preference) { - "1" -> { - appPreference.candidate_view_height_dp = defaultHeightDp - } - - "2" -> { - appPreference.candidate_view_height_dp = 165 - } - - "3" -> { - appPreference.candidate_view_height_dp = 230 - } + private fun setupAdapter() { + suggestionAdapter.apply { + setUndoEnabled(false) + setPasteEnabled(false) + onListUpdated = { + applyCurrentDimensions() } } - appPreference.candidate_view_empty_height_dp = defaultHeightDp + binding.candidateHeightSettingRecyclerview.adapter = suggestionAdapter + } + + private fun setupColumnControls() { + syncColumnControls() + binding.candidateColumnToggleGroup.addOnButtonCheckedListener { _, checkedId, isChecked -> + if (!isChecked || isSyncingColumnControls) return@addOnButtonCheckedListener + val column = columnForButtonId(checkedId) ?: return@addOnButtonCheckedListener + appPreference.setCandidateColumnAndSyncHeight(isLandscape = false, column = column) + setSuggestionView() + syncColumnControls() + applyHeightDp(selectedHeightDp(), persist = false) + } + } + + private fun resetSettings() { + appPreference.setCandidateVisibleHeightDp(isLandscape = false, column = "1", heightDp = 110) + appPreference.setCandidateVisibleHeightDp(isLandscape = false, column = "2", heightDp = 165) + appPreference.setCandidateVisibleHeightDp(isLandscape = false, column = "3", heightDp = 230) + appPreference.candidate_view_empty_height_dp = 110 + appPreference.syncActiveCandidateVisibleHeightToImePreference(isLandscape = false) + appPreference.candidate_letter_size = defaultCandidateTextSize + applyCandidateTextSize(defaultCandidateTextSize, persist = false) + syncColumnControls() + updateCandidateListAndHeight() applyHeightDp(selectedHeightDp(), persist = false) } private fun updateCandidateListAndHeight() { if (isCandidateListVisible) { - suggestionAdapter.suggestions = candidateList - binding.toggleCandidateListButton.text = "入力時" + suggestionAdapter.suggestions = previewCandidates + binding.toggleCandidateListButton.text = getString(R.string.candidate_preview_input_mode) } else { suggestionAdapter.suggestions = emptyList() - binding.toggleCandidateListButton.text = "未入力時" + binding.toggleCandidateListButton.text = getString(R.string.candidate_preview_empty_mode) } + updateCandidateTabPreview() + updateShortcutToolbarPreview() applyHeightDp(selectedHeightDp(), persist = false) } private fun setSuggestionView() { - when (val columnNum = appPreference.candidate_column_preference) { + val columnNum = appPreference.getCandidateColumn(isLandscape = false) + clearItemDecorations(binding.candidateHeightSettingRecyclerview) + when (columnNum) { "1" -> { binding.candidateHeightSettingRecyclerview.layoutManager = LinearLayoutManager(requireContext(), LinearLayoutManager.HORIZONTAL, false) @@ -179,45 +198,50 @@ class CandidateViewHeightSettingFragment : Fragment() { "2", "3" -> { val spanCount = columnNum.toInt() - val gridLayoutManager = GridLayoutManager( - requireContext(), spanCount, GridLayoutManager.HORIZONTAL, false - ) + binding.candidateHeightSettingRecyclerview.layoutManager = + GridLayoutManager( + requireContext(), + spanCount, + GridLayoutManager.HORIZONTAL, + false + ) val spacingInPixels = resources.getDimensionPixelSize(com.kazumaproject.core.R.dimen.grid_spacing) - - binding.candidateHeightSettingRecyclerview.layoutManager = - gridLayoutManager binding.candidateHeightSettingRecyclerview.addItemDecoration( - GridSpacingItemDecoration( - spanCount, spacingInPixels, true + CandidateHeightPreviewGridSpacingDecoration( + spanCount = spanCount, + spacing = spacingInPixels, + includeEdge = true ) ) } } - binding.candidateHeightSettingRecyclerview.apply { - adapter = suggestionAdapter - } + binding.candidateHeightSettingRecyclerview.adapter = suggestionAdapter } - /** - * 保存された高さ設定をビューに適用する - */ private fun applyCurrentDimensions() { applyHeightDp(selectedHeightDp(), persist = false) } private fun selectedHeightDp(): Int { return if (isCandidateListVisible) { - appPreference.candidate_view_height_dp ?: defaultHeightDp + appPreference.getCandidateVisibleHeightDp( + isLandscape = false, + column = appPreference.getCandidateColumn(isLandscape = false) + ) } else { - appPreference.candidate_view_empty_height_dp ?: defaultHeightDp + appPreference.candidate_view_empty_height_dp ?: 110 } } private fun saveSelectedHeightDp(heightDp: Int) { val clamped = heightDp.coerceIn(minHeightDp, maxHeightDp) if (isCandidateListVisible) { - appPreference.candidate_view_height_dp = clamped + appPreference.setCandidateVisibleHeightDp( + isLandscape = false, + column = appPreference.getCandidateColumn(isLandscape = false), + heightDp = clamped + ) } else { appPreference.candidate_view_empty_height_dp = clamped } @@ -225,7 +249,7 @@ class CandidateViewHeightSettingFragment : Fragment() { private fun applyHeightDp(heightDp: Int, persist: Boolean) { val clamped = heightDp.coerceIn(minHeightDp, maxHeightDp) - val heightPx = (clamped * resources.displayMetrics.density).toInt() + val heightPx = clamped.dpToPx() updatePreviewHeightPx(heightPx) if (persist) { saveSelectedHeightDp(clamped) @@ -234,25 +258,33 @@ class CandidateViewHeightSettingFragment : Fragment() { } private fun updatePreviewHeightPx(candidateHeightPx: Int) { - binding.candidateHeightSettingRecyclerview.layoutParams = - binding.candidateHeightSettingRecyclerview.layoutParams.apply { + binding.candidatePreviewFrame.layoutParams = + binding.candidatePreviewFrame.layoutParams.apply { height = candidateHeightPx } binding.candidateHeightSettingContent.layoutParams = binding.candidateHeightSettingContent.layoutParams.apply { - height = candidateHeightPx + keyboardPreviewHeightPx() + height = candidateHeightPx + shortcutToolbarHeightPx() + keyboardPreviewHeightPx() } } + private fun shortcutToolbarHeightPx(): Int { + return if (binding.independentShortcutToolbarPreviewContainer.isVisible) 36.dpToPx() else 0 + } + private fun keyboardPreviewHeightPx(): Int { - val previewHeight = binding.candidateHeightSettingTenkeyPreview.layoutParams.height + val previewHeight = binding.keyboardPreviewContainer.layoutParams.height return if (previewHeight > 0) { previewHeight } else { - (280 * resources.displayMetrics.density).toInt() + keyboardPreviewHeightDp().dpToPx() } } + private fun keyboardPreviewHeightDp(): Int { + return (appPreference.keyboard_height ?: 280).coerceIn(180, 420) + } + private fun syncHeightControls(heightDp: Int) { if (isSyncingHeightControls) return isSyncingHeightControls = true @@ -282,8 +314,7 @@ class CandidateViewHeightSettingFragment : Fragment() { fromUser: Boolean ) { if (!fromUser || isSyncingHeightControls) return - val heightDp = minHeightDp + progress - applyHeightDp(heightDp, persist = true) + applyHeightDp(minHeightDp + progress, persist = true) } override fun onStartTrackingTouch(seekBar: SeekBar?) = Unit @@ -307,20 +338,87 @@ class CandidateViewHeightSettingFragment : Fragment() { private fun applyHeightFromEditText() { if (isSyncingHeightControls) return - val raw = binding.candidateHeightEditText.text?.toString()?.trim() - val value = raw?.toIntOrNull() + val value = binding.candidateHeightEditText.text?.toString()?.trim()?.toIntOrNull() if (value == null) { binding.candidateHeightInputLayout.error = getString(R.string.candidate_height_invalid_value) return } - val clamped = value.coerceIn(minHeightDp, maxHeightDp) - applyHeightDp(clamped, persist = true) + applyHeightDp(value.coerceIn(minHeightDp, maxHeightDp), persist = true) + } + + private fun setupCandidateLetterSizeSeekBar() { + binding.candidateLetterSizeSeekbar.max = + ((maxCandidateTextSize - minCandidateTextSize) * 10).roundToInt() + binding.candidateLetterSizeSeekbar.setOnSeekBarChangeListener( + object : SeekBar.OnSeekBarChangeListener { + override fun onProgressChanged( + seekBar: SeekBar?, + progress: Int, + fromUser: Boolean + ) { + if (!fromUser || isSyncingLetterSizeControls) return + val newSize = minCandidateTextSize + progress / 10f + applyCandidateTextSize(newSize, persist = true) + } + + override fun onStartTrackingTouch(seekBar: SeekBar?) = Unit + + override fun onStopTrackingTouch(seekBar: SeekBar?) = Unit + } + ) + } + + private fun setupCandidateLetterSizeEditText() { + binding.candidateLetterSizeEditText.setOnEditorActionListener { _, _, _ -> + applyCandidateLetterSizeFromEditText() + false + } + binding.candidateLetterSizeEditText.setOnFocusChangeListener { _, hasFocus -> + if (!hasFocus) { + applyCandidateLetterSizeFromEditText() + } + } + } + + private fun applyCandidateLetterSizeFromEditText() { + if (isSyncingLetterSizeControls) return + val value = binding.candidateLetterSizeEditText.text?.toString()?.trim()?.toFloatOrNull() + if (value == null) { + binding.candidateLetterSizeInputLayout.error = + getString(R.string.candidate_letter_size_invalid_value) + return + } + applyCandidateTextSize(value.coerceIn(minCandidateTextSize, maxCandidateTextSize), persist = true) + } + + private fun applyCandidateTextSize(size: Float, persist: Boolean) { + val clamped = ((size.coerceIn(minCandidateTextSize, maxCandidateTextSize) * 10).roundToInt() / 10f) + suggestionAdapter.setCandidateTextSize(clamped) + if (persist) { + appPreference.candidate_letter_size = clamped + } + syncCandidateLetterSizeControls(clamped) + } + + private fun syncCandidateLetterSizeControls(size: Float) { + if (isSyncingLetterSizeControls) return + isSyncingLetterSizeControls = true + try { + val clamped = size.coerceIn(minCandidateTextSize, maxCandidateTextSize) + binding.candidateLetterSizeSeekbar.progress = + ((clamped - minCandidateTextSize) * 10).roundToInt() + val text = String.format(Locale.US, "%.1f", clamped) + if (binding.candidateLetterSizeEditText.text?.toString() != text) { + binding.candidateLetterSizeEditText.setText(text) + binding.candidateLetterSizeEditText.setSelection(text.length) + } + binding.candidateLetterSizeInputLayout.error = null + } finally { + isSyncingLetterSizeControls = false + } } - /** - * 高さ変更ハンドルのタッチリスナーを設定する - */ @SuppressLint("ClickableViewAccessibility") private fun setupResizeHandle() { var initialY = 0f @@ -334,7 +432,7 @@ class CandidateViewHeightSettingFragment : Fragment() { when (event.action) { MotionEvent.ACTION_DOWN -> { initialY = event.rawY - initialHeight = binding.candidateHeightSettingRecyclerview.height + initialHeight = binding.candidatePreviewFrame.height true } @@ -342,7 +440,7 @@ class CandidateViewHeightSettingFragment : Fragment() { val deltaY = event.rawY - initialY val newHeight = (initialHeight - deltaY).coerceIn(minHeightPx, maxHeightPx) updatePreviewHeightPx(newHeight.toInt()) - binding.candidateHeightSettingRecyclerview.requestLayout() + binding.candidatePreviewFrame.requestLayout() binding.candidateHeightSettingContent.requestLayout() true } @@ -358,24 +456,300 @@ class CandidateViewHeightSettingFragment : Fragment() { } } - /** - * 現在のビューの高さを SharedPreferences に保存する - */ private fun saveHeightPreference(): Int { val density = resources.displayMetrics.density - val heightPx = binding.candidateHeightSettingRecyclerview.layoutParams.height + val heightPx = binding.candidatePreviewFrame.layoutParams.height .takeIf { it > 0 } - ?: binding.candidateHeightSettingRecyclerview.height + ?: binding.candidatePreviewFrame.height val finalHeightDp = (heightPx / density).roundToInt().coerceIn(minHeightDp, maxHeightDp) saveSelectedHeightDp(finalHeightDp) - if (isCandidateListVisible) { - Timber.d("saveHeightPreference (with candidates): $finalHeightDp dp") + Timber.d( + "saveHeightPreference portrait (%s): %d dp", + if (isCandidateListVisible) "with candidates" else "empty", + finalHeightDp + ) + return finalHeightDp + } + + private fun syncColumnControls() { + if (isSyncingColumnControls) return + isSyncingColumnControls = true + try { + binding.candidateColumnToggleGroup.check( + when (appPreference.getCandidateColumn(isLandscape = false)) { + "2" -> R.id.candidate_column_two_button + "3" -> R.id.candidate_column_three_button + else -> R.id.candidate_column_one_button + } + ) + } finally { + isSyncingColumnControls = false + } + } + + private fun columnForButtonId(id: Int): String? = + when (id) { + R.id.candidate_column_one_button -> "1" + R.id.candidate_column_two_button -> "2" + R.id.candidate_column_three_button -> "3" + else -> null + } + + private fun updateCandidateTabPreview() { + val showCandidateTab = appPreference.candidate_tab_preference && isCandidateListVisible + binding.candidateTabPreviewContainer.isVisible = showCandidateTab + if (!showCandidateTab) return + + binding.candidateTabPreviewContainer.removeAllViews() + val tabs = runCatching { appPreference.candidate_tab_order } + .getOrDefault(listOf(CandidateTab.PREDICTION, CandidateTab.CONVERSION, CandidateTab.EISUKANA)) + tabs.forEachIndexed { index, tab -> + binding.candidateTabPreviewContainer.addView( + previewTabView(label = tab.previewLabel(), selected = index == 0), + LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.MATCH_PARENT, 1f) + ) + } + } + + private fun updateShortcutToolbarPreview() { + val state = resolvePreviewShortcutToolbarState() + binding.independentShortcutToolbarPreviewContainer.isVisible = state.showIndependentToolbar + binding.integratedShortcutToolbarPreviewContainer.isVisible = state.showIntegratedShortcuts + if (state.showIndependentToolbar) { + populateShortcutToolbarPreview(binding.independentShortcutToolbarPreviewContainer) + } + if (state.showIntegratedShortcuts) { + populateShortcutToolbarPreview(binding.integratedShortcutToolbarPreviewContainer) + } + } + + private data class PreviewShortcutToolbarState( + val showIndependentToolbar: Boolean, + val showIntegratedShortcuts: Boolean + ) + + private fun resolvePreviewShortcutToolbarState(): PreviewShortcutToolbarState { + val visible = appPreference.shortcut_toolbar_visibility_preference + val integrated = appPreference.shortcut_toolbar_integrated_in_suggestion_preference + if (!visible) { + return PreviewShortcutToolbarState( + showIndependentToolbar = false, + showIntegratedShortcuts = false + ) + } + if (!integrated) { + return PreviewShortcutToolbarState( + showIndependentToolbar = true, + showIntegratedShortcuts = false + ) + } + val inputStringEmpty = !isCandidateListVisible + val tailEmpty = true + val clipboardPreviewShown = false + val selectedTextGemmaActionsShown = false + val suggestionsEmpty = !isCandidateListVisible + val customLayoutPickerShown = false + val showIntegrated = + inputStringEmpty && + tailEmpty && + !clipboardPreviewShown && + !selectedTextGemmaActionsShown && + suggestionsEmpty && + !customLayoutPickerShown + return PreviewShortcutToolbarState( + showIndependentToolbar = false, + showIntegratedShortcuts = showIntegrated + ) + } + + private fun populateShortcutToolbarPreview(container: LinearLayout) { + container.removeAllViews() + listOf( + ShortcutType.SETTINGS, + ShortcutType.EMOJI, + ShortcutType.TEMPLATE, + ShortcutType.KEYBOARD_PICKER, + ShortcutType.PASTE + ).forEach { shortcut -> + val button = AppCompatImageButton(requireContext()).apply { + setImageResource(shortcut.iconResId) + imageTintList = ColorStateList.valueOf(resolveThemeColor(MaterialR.attr.colorOnSurface)) + background = null + contentDescription = shortcut.description + isClickable = false + isFocusable = false + setPadding(8.dpToPx(), 6.dpToPx(), 8.dpToPx(), 6.dpToPx()) + } + container.addView( + button, + LinearLayout.LayoutParams(42.dpToPx(), ViewGroup.LayoutParams.MATCH_PARENT) + ) + } + } + + private fun setupKeyboardPreview() { + val heightPx = keyboardPreviewHeightPx() + binding.keyboardPreviewContainer.layoutParams = + binding.keyboardPreviewContainer.layoutParams.apply { + height = heightPx + } + binding.candidateHeightSettingTenkeyPreview.setOnTouchListener { _, _ -> true } + val previewKeyboardType = appPreference.keyboard_order.firstOrNull() ?: KeyboardType.TENKEY + if (previewKeyboardType == KeyboardType.TENKEY) { + binding.candidateHeightSettingTenkeyPreview.isVisible = true + simpleKeyboardPreview?.isVisible = false } else { - Timber.d("saveHeightPreference (empty): $finalHeightDp dp") + binding.candidateHeightSettingTenkeyPreview.isVisible = false + val simplePreview = simpleKeyboardPreview ?: createSimpleKeyboardPreview(previewKeyboardType).also { + it.tag = SIMPLE_KEYBOARD_PREVIEW_TAG + binding.keyboardPreviewContainer.addView( + it, + ViewGroup.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT + ) + ) + simpleKeyboardPreview = it + } + simplePreview.isVisible = true } - return finalHeightDp + } + + private fun createSimpleKeyboardPreview(type: KeyboardType): View { + val rows = when (type) { + KeyboardType.QWERTY -> listOf( + listOf("Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P"), + listOf("A", "S", "D", "F", "G", "H", "J", "K", "L"), + listOf("Z", "X", "C", "V", "B", "N", "M") + ) + + KeyboardType.ROMAJI -> listOf( + listOf("あ", "か", "さ", "た", "な", "は", "ま", "や", "ら", "わ"), + listOf("A", "I", "U", "E", "O", "K", "S", "T", "N"), + listOf("Shift", "Space", "Enter") + ) + + KeyboardType.SUMIRE -> listOf( + listOf("あ", "い", "う", "え", "お"), + listOf("か", "き", "く", "け", "こ"), + listOf("さ", "し", "す", "せ", "そ") + ) + + KeyboardType.CUSTOM -> listOf( + listOf("Custom", "Key", "Layout"), + listOf("かな", "英数", "記号"), + listOf("Space", "Enter") + ) + + KeyboardType.TENKEY -> emptyList() + } + return LinearLayout(requireContext()).apply { + orientation = LinearLayout.VERTICAL + gravity = Gravity.CENTER + setPadding(8.dpToPx(), 8.dpToPx(), 8.dpToPx(), 8.dpToPx()) + rows.forEach { row -> + addView( + LinearLayout(requireContext()).apply { + orientation = LinearLayout.HORIZONTAL + gravity = Gravity.CENTER + row.forEach { label -> + addView( + MaterialTextView(requireContext()).apply { + text = label + gravity = Gravity.CENTER + setTextColor( + ContextCompat.getColor( + requireContext(), + com.kazumaproject.core.R.color.main_text_color + ) + ) + textSize = 13f + maxLines = 1 + background = keyPreviewBackground() + }, + LinearLayout.LayoutParams(0, 0, 1f).apply { + height = 48.dpToPx() + marginStart = 2.dpToPx() + marginEnd = 2.dpToPx() + } + ) + } + }, + LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + 0, + 1f + ).apply { + topMargin = 2.dpToPx() + bottomMargin = 2.dpToPx() + } + ) + } + } + } + + private fun previewTabView(label: String, selected: Boolean): MaterialTextView { + return MaterialTextView(requireContext()).apply { + text = label + gravity = Gravity.CENTER + textSize = 13f + setTextColor( + resolveThemeColor( + if (selected) MaterialR.attr.colorOnPrimary else MaterialR.attr.colorOnSurface + ) + ) + background = roundedBackground( + fillColor = resolveThemeColor( + if (selected) AppCompatR.attr.colorPrimary else MaterialR.attr.colorSurfaceVariant + ), + radiusDp = 6 + ) + } + } + + private fun keyPreviewBackground(): GradientDrawable = + roundedBackground( + fillColor = resolveThemeColor(MaterialR.attr.colorSurfaceVariant), + strokeColor = resolveThemeColor(MaterialR.attr.colorOutline), + radiusDp = 6 + ) + + private fun roundedBackground( + fillColor: Int, + strokeColor: Int? = null, + radiusDp: Int + ): GradientDrawable { + return GradientDrawable().apply { + shape = GradientDrawable.RECTANGLE + cornerRadius = radiusDp.dpToPx().toFloat() + setColor(fillColor) + strokeColor?.let { setStroke(1.dpToPx(), it) } + } + } + + private fun CandidateTab.previewLabel(): String = + when (this) { + CandidateTab.PREDICTION -> "予測" + CandidateTab.CONVERSION -> "変換" + CandidateTab.EISUKANA -> "英数カナ" + } + + private fun resolveThemeColor(@AttrRes attr: Int): Int { + val typedArray = requireContext().obtainStyledAttributes(intArrayOf(attr)) + return try { + typedArray.getColor(0, Color.TRANSPARENT) + } finally { + typedArray.recycle() + } + } + + private fun Int.dpToPx(): Int = + (this * resources.displayMetrics.density).roundToInt() + + companion object { + private const val SIMPLE_KEYBOARD_PREVIEW_TAG = "simple_keyboard_preview" } } diff --git a/app/src/main/java/com/kazumaproject/markdownhelperkeyboard/setting_activity/ui/candidate_view_height_setting/SuggestionAdapter2.kt b/app/src/main/java/com/kazumaproject/markdownhelperkeyboard/setting_activity/ui/candidate_view_height_setting/SuggestionAdapter2.kt index 4ab0621a..4d168353 100644 --- a/app/src/main/java/com/kazumaproject/markdownhelperkeyboard/setting_activity/ui/candidate_view_height_setting/SuggestionAdapter2.kt +++ b/app/src/main/java/com/kazumaproject/markdownhelperkeyboard/setting_activity/ui/candidate_view_height_setting/SuggestionAdapter2.kt @@ -392,6 +392,7 @@ class SuggestionAdapter2 : RecyclerView.Adapter() { } holder.text.textSize = candidateTextSize + holder.typeText.textSize = (candidateTextSize * 0.65f).coerceIn(8f, 16f) holder.typeText.text = when (suggestion.type) { (1).toByte() -> "" diff --git a/app/src/main/java/com/kazumaproject/markdownhelperkeyboard/setting_activity/ui/setting/CommonPreferenceFragment.kt b/app/src/main/java/com/kazumaproject/markdownhelperkeyboard/setting_activity/ui/setting/CommonPreferenceFragment.kt index 3d6e55be..27764094 100644 --- a/app/src/main/java/com/kazumaproject/markdownhelperkeyboard/setting_activity/ui/setting/CommonPreferenceFragment.kt +++ b/app/src/main/java/com/kazumaproject/markdownhelperkeyboard/setting_activity/ui/setting/CommonPreferenceFragment.kt @@ -326,19 +326,22 @@ open class CommonPreferenceFragment : PreferenceFragmentCompat() { candidateColumnListPreference?.apply { setOnPreferenceChangeListener { _, newValue -> if (newValue is String) { - when (newValue) { - "1" -> { - appPreference.candidate_view_height_dp = 110 - } - - "2" -> { - appPreference.candidate_view_height_dp = 165 - } - - "3" -> { - appPreference.candidate_view_height_dp = 230 - } - } + appPreference.setCandidateColumnAndSyncHeight( + isLandscape = false, + column = newValue + ) + } + true + } + } + + findPreference("candidate_column_landscape_preference")?.apply { + setOnPreferenceChangeListener { _, newValue -> + if (newValue is String) { + appPreference.setCandidateColumnAndSyncHeight( + isLandscape = true, + column = newValue + ) } true } diff --git a/app/src/main/java/com/kazumaproject/markdownhelperkeyboard/setting_activity/ui/setting/SettingCardEditorController.kt b/app/src/main/java/com/kazumaproject/markdownhelperkeyboard/setting_activity/ui/setting/SettingCardEditorController.kt index 87210f2f..2001911c 100644 --- a/app/src/main/java/com/kazumaproject/markdownhelperkeyboard/setting_activity/ui/setting/SettingCardEditorController.kt +++ b/app/src/main/java/com/kazumaproject/markdownhelperkeyboard/setting_activity/ui/setting/SettingCardEditorController.kt @@ -336,17 +336,23 @@ class SettingCardEditorController( }.getOrDefault(defaultValue) private fun writeStringPreference(preferenceKey: String, value: String) { + if (preferenceKey == "candidate_column_preference") { + appPreference.setCandidateColumnAndSyncHeight( + isLandscape = false, + column = value + ) + return + } + if (preferenceKey == "candidate_column_landscape_preference") { + appPreference.setCandidateColumnAndSyncHeight( + isLandscape = true, + column = value + ) + return + } preferences.edit() .putString(preferenceKey, value) .apply() - if (preferenceKey == "candidate_column_preference") { - appPreference.candidate_view_height_dp = when (value) { - "1" -> 110 - "2" -> 165 - "3" -> 230 - else -> appPreference.candidate_view_height_dp ?: 110 - } - } } private fun readIntPreference(preferenceKey: String, defaultValue: Int): Int = diff --git a/app/src/main/java/com/kazumaproject/markdownhelperkeyboard/setting_activity/ui/setting/SettingHomeFragment.kt b/app/src/main/java/com/kazumaproject/markdownhelperkeyboard/setting_activity/ui/setting/SettingHomeFragment.kt index c9983088..a8f91f5c 100644 --- a/app/src/main/java/com/kazumaproject/markdownhelperkeyboard/setting_activity/ui/setting/SettingHomeFragment.kt +++ b/app/src/main/java/com/kazumaproject/markdownhelperkeyboard/setting_activity/ui/setting/SettingHomeFragment.kt @@ -394,15 +394,11 @@ class SettingHomeFragment : Fragment() { appPreference.keyboard_width_landscape ?: 100, ) - "candidate_view_height_setting_fragment_preference" -> getString( - R.string.setting_home_dp_summary, - appPreference.candidate_view_height_dp ?: 110, - ) + "candidate_view_height_setting_fragment_preference" -> + candidateHeightSummary(isLandscape = false) - "candidate_view_height_landscape_setting_fragment_preference" -> getString( - R.string.setting_home_dp_summary, - appPreference.candidate_view_height_dp_landscape ?: 110, - ) + "candidate_view_height_landscape_setting_fragment_preference" -> + candidateHeightSummary(isLandscape = true) "setting_route_keyboard_theme" -> getString( R.string.setting_home_current_value, @@ -447,6 +443,22 @@ class SettingHomeFragment : Fragment() { } } + private fun candidateHeightSummary(isLandscape: Boolean): String { + appPreference.migrateCandidateHeightPerColumnPreferencesIfNeeded() + val currentColumn = appPreference.getCandidateColumn(isLandscape) + val activeHeight = appPreference.getCandidateVisibleHeightDp(isLandscape, currentColumn) + val candidateTextSize = appPreference.candidate_letter_size ?: 14.0f + return getString( + R.string.setting_home_candidate_height_summary, + appPreference.getCandidateVisibleHeightDp(isLandscape, "1"), + appPreference.getCandidateVisibleHeightDp(isLandscape, "2"), + appPreference.getCandidateVisibleHeightDp(isLandscape, "3"), + currentColumn.toIntOrNull() ?: 1, + activeHeight, + candidateTextSize, + ) + } + private fun currentValueText(destination: SettingDestination): String? { return settingCardEditorController.currentValueLabel(destination) ?.let { getString(R.string.setting_home_current_value, it) } diff --git a/app/src/main/res/layout/fragment_candidate_height_landscape_setting.xml b/app/src/main/res/layout/fragment_candidate_height_landscape_setting.xml index b2a017fa..ece4132c 100644 --- a/app/src/main/res/layout/fragment_candidate_height_landscape_setting.xml +++ b/app/src/main/res/layout/fragment_candidate_height_landscape_setting.xml @@ -1,41 +1,86 @@ - -