Skip to content

Commit 48029eb

Browse files
Merge pull request #585 from KazumaProject/fix/floating
フローティングモードでキーボードが落ちるバグの修正、カスタムキーボードのアイテムの位置を変更できるようにする
2 parents 1613f74 + 246c16f commit 48029eb

12 files changed

Lines changed: 305 additions & 106 deletions

File tree

app/build.gradle

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@ android {
2929
applicationId "com.kazumaproject.markdownhelperkeyboard"
3030
minSdk 24
3131
targetSdk 36
32-
versionCode 651
33-
versionName "1.4.530"
32+
versionCode 653
33+
versionName "1.4.532"
3434
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
3535
}
3636

app/src/main/java/com/kazumaproject/markdownhelperkeyboard/custom_keyboard/data/CustomKeyboardLayout.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,6 @@ data class CustomKeyboardLayout(
1414
val columnCount: Int, // 列数
1515
val rowCount: Int, // 行数
1616
val isRomaji: Boolean = false,
17-
val createdAt: Long = System.currentTimeMillis() // 作成日時
17+
val createdAt: Long = System.currentTimeMillis(), // 作成日時
18+
val sortOrder: Int = 0
1819
)

app/src/main/java/com/kazumaproject/markdownhelperkeyboard/custom_keyboard/database/KeyboardLayoutDao.kt

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,12 @@ import kotlinx.coroutines.flow.Flow
1616
@Dao
1717
interface KeyboardLayoutDao {
1818

19-
@Query("SELECT * FROM keyboard_layouts ORDER BY createdAt DESC")
19+
// ★変更: sortOrder DESC を優先し、同順なら createdAt DESC(従来互換)
20+
@Query("SELECT * FROM keyboard_layouts ORDER BY sortOrder DESC, createdAt DESC")
2021
fun getLayoutsList(): Flow<List<CustomKeyboardLayout>>
2122

22-
@Query("SELECT * FROM keyboard_layouts ORDER BY createdAt DESC")
23+
// ★変更
24+
@Query("SELECT * FROM keyboard_layouts ORDER BY sortOrder DESC, createdAt DESC")
2325
suspend fun getLayoutsListNotFlow(): List<CustomKeyboardLayout>
2426

2527
@Transaction
@@ -118,4 +120,26 @@ interface KeyboardLayoutDao {
118120
@Transaction
119121
@Query("SELECT * FROM keyboard_layouts")
120122
suspend fun getAllFullLayoutsOneShot(): List<FullKeyboardLayout>
123+
124+
// -----------------------------
125+
// ★追加: 並び順永続化
126+
// -----------------------------
127+
128+
@Query("SELECT COALESCE(MAX(sortOrder), 0) FROM keyboard_layouts")
129+
suspend fun getMaxSortOrder(): Int
130+
131+
@Query("UPDATE keyboard_layouts SET sortOrder = :sortOrder WHERE layoutId = :layoutId")
132+
suspend fun updateLayoutSortOrder(layoutId: Long, sortOrder: Int)
133+
134+
/**
135+
* 表示順(上→下)で受け取った layoutId のリストを永続化する。
136+
* sortOrder は「大きいほど上」になるように採番する。
137+
*/
138+
@Transaction
139+
suspend fun updateLayoutOrdersInDisplayOrder(layoutIdsInDisplayOrder: List<Long>) {
140+
val n = layoutIdsInDisplayOrder.size
141+
layoutIdsInDisplayOrder.forEachIndexed { index, id ->
142+
updateLayoutSortOrder(layoutId = id, sortOrder = n - index)
143+
}
144+
}
121145
}

app/src/main/java/com/kazumaproject/markdownhelperkeyboard/custom_keyboard/ui/KeyboardListFragment.kt

Lines changed: 61 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@ import androidx.lifecycle.Lifecycle
1919
import androidx.lifecycle.lifecycleScope
2020
import androidx.navigation.fragment.findNavController
2121
import androidx.recyclerview.widget.DividerItemDecoration
22+
import androidx.recyclerview.widget.ItemTouchHelper
2223
import androidx.recyclerview.widget.LinearLayoutManager
24+
import androidx.recyclerview.widget.RecyclerView
2325
import com.google.android.material.dialog.MaterialAlertDialogBuilder
2426
import com.google.gson.Gson
2527
import com.google.gson.GsonBuilder
@@ -44,6 +46,8 @@ class KeyboardListFragment : Fragment(R.layout.fragment_keyboard_list) {
4446
private var _binding: FragmentKeyboardListBinding? = null
4547
private val binding get() = _binding!!
4648

49+
private var itemTouchHelper: ItemTouchHelper? = null
50+
4751
// [ADD] Launcher for exporting files
4852
private val exportLauncher =
4953
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
@@ -75,7 +79,7 @@ class KeyboardListFragment : Fragment(R.layout.fragment_keyboard_list) {
7579
super.onViewCreated(view, savedInstanceState)
7680
_binding = FragmentKeyboardListBinding.bind(view)
7781

78-
setupMenu() // [ADD] Call menu setup
82+
setupMenu()
7983

8084
val adapter = KeyboardLayoutAdapter(
8185
onItemClick = { layout ->
@@ -90,18 +94,69 @@ class KeyboardListFragment : Fragment(R.layout.fragment_keyboard_list) {
9094
},
9195
onDuplicateClick = { layout ->
9296
viewModel.duplicateLayout(layout.layoutId)
97+
},
98+
onStartDrag = { vh ->
99+
itemTouchHelper?.startDrag(vh)
93100
}
94101
)
102+
95103
binding.keyboardLayoutsRecyclerView.adapter = adapter
96104
binding.keyboardLayoutsRecyclerView.layoutManager = LinearLayoutManager(context)
97105
binding.keyboardLayoutsRecyclerView.addItemDecoration(
98106
DividerItemDecoration(context, DividerItemDecoration.VERTICAL)
99107
)
108+
109+
// ★追加: ItemTouchHelper
110+
val callback = object : ItemTouchHelper.SimpleCallback(
111+
ItemTouchHelper.UP or ItemTouchHelper.DOWN,
112+
0
113+
) {
114+
override fun isLongPressDragEnabled(): Boolean = false // ハンドルのみで開始
115+
override fun isItemViewSwipeEnabled(): Boolean = false
116+
117+
override fun onMove(
118+
recyclerView: RecyclerView,
119+
viewHolder: RecyclerView.ViewHolder,
120+
target: RecyclerView.ViewHolder
121+
): Boolean {
122+
val from = viewHolder.bindingAdapterPosition
123+
val to = target.bindingAdapterPosition
124+
if (from == RecyclerView.NO_POSITION || to == RecyclerView.NO_POSITION) return false
125+
if (from == to) return false
126+
127+
val current = adapter.currentList.toMutableList()
128+
val moved = current.removeAt(from)
129+
current.add(to, moved)
130+
131+
// ListAdapter なので新しいList参照で submit
132+
adapter.submitList(current)
133+
return true
134+
}
135+
136+
override fun clearView(
137+
recyclerView: RecyclerView,
138+
viewHolder: RecyclerView.ViewHolder
139+
) {
140+
super.clearView(recyclerView, viewHolder)
141+
142+
// ★ドロップ後に永続化
143+
val idsInDisplayOrder = adapter.currentList.map { it.layoutId }
144+
viewModel.updateLayoutOrder(idsInDisplayOrder)
145+
}
146+
147+
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) = Unit
148+
}
149+
150+
itemTouchHelper = ItemTouchHelper(callback).also {
151+
it.attachToRecyclerView(binding.keyboardLayoutsRecyclerView)
152+
}
153+
100154
binding.fabAddLayout.setOnClickListener {
101155
val action =
102156
KeyboardListFragmentDirections.actionKeyboardListFragmentToKeyboardEditorFragment(-1L)
103157
findNavController().navigate(action)
104158
}
159+
105160
viewLifecycleOwner.lifecycleScope.launch {
106161
viewModel.layouts.collect { layouts ->
107162
adapter.submitList(layouts)
@@ -135,7 +190,6 @@ class KeyboardListFragment : Fragment(R.layout.fragment_keyboard_list) {
135190
}, viewLifecycleOwner, Lifecycle.State.RESUMED)
136191
}
137192

138-
// [ADD] Functions to launch file pickers
139193
private fun launchExportPicker() {
140194
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
141195
addCategory(Intent.CATEGORY_OPENABLE)
@@ -157,12 +211,11 @@ class KeyboardListFragment : Fragment(R.layout.fragment_keyboard_list) {
157211

158212
private val exportGson: Gson by lazy {
159213
GsonBuilder()
160-
.disableHtmlEscaping() // 余計なエスケープを避ける
161-
.serializeNulls() // null の扱いを安定させる(任意)
214+
.disableHtmlEscaping()
215+
.serializeNulls()
162216
.create()
163217
}
164218

165-
166219
private fun exportLayouts(uri: Uri) {
167220
viewLifecycleOwner.lifecycleScope.launch {
168221
try {
@@ -209,19 +262,18 @@ class KeyboardListFragment : Fragment(R.layout.fragment_keyboard_list) {
209262
return@launch
210263
}
211264

212-
// BOM (U+FEFF) と NULL を除去(現場で混入しがち)
213265
val jsonString = bytes.toString(Charsets.UTF_8)
214266
.trimStart('\uFEFF')
215267
.replace("\u0000", "")
216268

217269
val type = object : TypeToken<List<FullKeyboardLayout>>() {}.type
218270

219271
val gson = GsonBuilder()
220-
.setLenient() // ★ポイント:厳格でなく寛容に読む
272+
.setLenient()
221273
.create()
222274

223275
val reader = JsonReader(StringReader(jsonString)).apply {
224-
isLenient = true // GsonBuilderだけで効かないケースも潰す
276+
isLenient = true
225277
}
226278

227279
val layouts: List<FullKeyboardLayout> = gson.fromJson(reader, type) ?: emptyList()
@@ -265,5 +317,6 @@ class KeyboardListFragment : Fragment(R.layout.fragment_keyboard_list) {
265317
override fun onDestroyView() {
266318
super.onDestroyView()
267319
_binding = null
320+
itemTouchHelper = null
268321
}
269322
}

app/src/main/java/com/kazumaproject/markdownhelperkeyboard/custom_keyboard/ui/KeyboardListViewModel.kt

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,14 @@ class KeyboardListViewModel @Inject constructor(
3131

3232
fun duplicateLayout(id: Long) {
3333
viewModelScope.launch {
34-
repository.duplicateLayout(id) // Repositoryにこのメソッドを追加する必要があります
34+
repository.duplicateLayout(id)
35+
}
36+
}
37+
38+
// ★追加: 並び順を永続化
39+
fun updateLayoutOrder(layoutIdsInDisplayOrder: List<Long>) {
40+
viewModelScope.launch {
41+
repository.updateLayoutOrder(layoutIdsInDisplayOrder)
3542
}
3643
}
3744
}

app/src/main/java/com/kazumaproject/markdownhelperkeyboard/custom_keyboard/ui/adapter/KeyboardLayoutAdapter.kt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package com.kazumaproject.markdownhelperkeyboard.custom_keyboard.ui.adapter
22

3+
import android.annotation.SuppressLint
34
import android.view.LayoutInflater
5+
import android.view.MotionEvent
46
import android.view.ViewGroup
57
import android.widget.PopupMenu
68
import androidx.recyclerview.widget.DiffUtil
@@ -17,11 +19,15 @@ class KeyboardLayoutAdapter(
1719
private val onItemClick: (CustomKeyboardLayout) -> Unit,
1820
private val onDeleteClick: (CustomKeyboardLayout) -> Unit,
1921
private val onDuplicateClick: (CustomKeyboardLayout) -> Unit,
22+
23+
// ★追加: Fragment 側の ItemTouchHelper.startDrag を呼ぶためのコールバック
24+
private val onStartDrag: (RecyclerView.ViewHolder) -> Unit,
2025
) : ListAdapter<CustomKeyboardLayout, KeyboardLayoutAdapter.ViewHolder>(DiffCallback) {
2126

2227
inner class ViewHolder(private val binding: ListItemKeyboardLayoutBinding) :
2328
RecyclerView.ViewHolder(binding.root) {
2429

30+
@SuppressLint("ClickableViewAccessibility")
2531
fun bind(layout: CustomKeyboardLayout) {
2632
binding.keyboardNameText.text = layout.name
2733
val context = binding.root.context
@@ -33,6 +39,14 @@ class KeyboardLayoutAdapter(
3339
// リスト項目全体がクリックされた場合
3440
binding.root.setOnClickListener { onItemClick(layout) }
3541

42+
// ★追加: ドラッグハンドルを押したらドラッグ開始
43+
binding.dragHandleButton.setOnTouchListener { _, event ->
44+
if (event.actionMasked == MotionEvent.ACTION_DOWN) {
45+
onStartDrag(this)
46+
}
47+
false
48+
}
49+
3650
// メニューボタンがクリックされた場合
3751
binding.keyboardMenuButton.setOnClickListener { view ->
3852
PopupMenu(view.context, view).apply {

app/src/main/java/com/kazumaproject/markdownhelperkeyboard/database/AppDatabase.kt

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ import com.kazumaproject.markdownhelperkeyboard.user_template.database.UserTempl
4646
NgWord::class,
4747
ShortcutItem::class
4848
],
49-
version = 15,
49+
version = 16,
5050
exportSchema = false
5151
)
5252
@TypeConverters(
@@ -369,5 +369,12 @@ abstract class AppDatabase : RoomDatabase() {
369369
}
370370
}
371371

372+
val MIGRATION_15_16 = object : Migration(15, 16) {
373+
override fun migrate(db: SupportSQLiteDatabase) {
374+
// keyboard_layouts に sortOrder を追加(既存行は 0)
375+
db.execSQL("ALTER TABLE keyboard_layouts ADD COLUMN sortOrder INTEGER NOT NULL DEFAULT 0")
376+
}
377+
}
378+
372379
}
373380
}

app/src/main/java/com/kazumaproject/markdownhelperkeyboard/ime_service/IMEService.kt

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1127,6 +1127,21 @@ class IMEService : InputMethodService(), LifecycleOwner, InputConnection,
11271127
)
11281128

11291129
floatingKeyboardBinding?.let { floatingKeyboardLayoutBinding ->
1130+
floatingKeyboardLayoutBinding.keyboardViewFloating.applyKeyboardTheme(
1131+
themeMode = keyboardThemeMode ?: "default",
1132+
currentNightMode = currentNightMode,
1133+
isDynamicColorEnabled = DynamicColors.isDynamicColorAvailable(),
1134+
customBgColor = customThemeBgColor ?: Color.WHITE,
1135+
customKeyColor = customThemeKeyColor ?: Color.WHITE,
1136+
customSpecialKeyColor = customThemeSpecialKeyColor ?: Color.GRAY,
1137+
customKeyTextColor = customThemeKeyTextColor ?: Color.BLACK,
1138+
customSpecialKeyTextColor = customThemeSpecialKeyTextColor ?: Color.BLACK,
1139+
liquidGlassEnable = liquidGlassThemePreference ?: false,
1140+
customBorderEnable = customKeyBorderEnablePreference ?: false,
1141+
customBorderColor = customKeyBorderEnableColor ?: Color.BLACK,
1142+
liquidGlassKeyAlphaEnable = liquidGlassKeyBlurRadiousPreference ?: 255,
1143+
borderWidth = customKeyBorderWidth ?: 1
1144+
)
11301145
floatingKeyboardLayoutBinding.keyboardViewFloating.apply {
11311146
setOnFlickListener(object : FlickListener {
11321147
override fun onFlick(gestureType: GestureType, key: Key, char: Char?) {
@@ -1597,7 +1612,7 @@ class IMEService : InputMethodService(), LifecycleOwner, InputConnection,
15971612
override fun onConfigureWindow(win: Window?, isFullscreen: Boolean, isCandidatesOnly: Boolean) {
15981613
super.onConfigureWindow(win, isFullscreen, isCandidatesOnly)
15991614
// Android 12 (API 31) 以上の場合
1600-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && liquidGlassThemePreference == true) {
1615+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && liquidGlassThemePreference == true && isKeyboardFloatingMode != true) {
16011616
// 背景のアプリに対してブラーをかける
16021617
win?.setBackgroundBlurRadius(50)
16031618
}

app/src/main/java/com/kazumaproject/markdownhelperkeyboard/ime_service/di/AppModule.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import com.kazumaproject.markdownhelperkeyboard.database.AppDatabase.Companion.M
2323
import com.kazumaproject.markdownhelperkeyboard.database.AppDatabase.Companion.MIGRATION_12_13
2424
import com.kazumaproject.markdownhelperkeyboard.database.AppDatabase.Companion.MIGRATION_13_14
2525
import com.kazumaproject.markdownhelperkeyboard.database.AppDatabase.Companion.MIGRATION_14_15
26+
import com.kazumaproject.markdownhelperkeyboard.database.AppDatabase.Companion.MIGRATION_15_16
2627
import com.kazumaproject.markdownhelperkeyboard.database.AppDatabase.Companion.MIGRATION_1_2
2728
import com.kazumaproject.markdownhelperkeyboard.database.AppDatabase.Companion.MIGRATION_2_3
2829
import com.kazumaproject.markdownhelperkeyboard.database.AppDatabase.Companion.MIGRATION_3_4
@@ -87,7 +88,8 @@ object AppModule {
8788
MIGRATION_11_12,
8889
MIGRATION_12_13,
8990
MIGRATION_13_14,
90-
MIGRATION_14_15
91+
MIGRATION_14_15,
92+
MIGRATION_15_16
9193
)
9294
.build()
9395

0 commit comments

Comments
 (0)