Skip to content

Commit 149074e

Browse files
committed
Add manual table insertion with column/row input dialog
- New table button in Markdown toolbar (⊞) - Dialog to set rows (1-20) and columns (1-8) - Editable grid for column headers and cell data - Generates proper Markdown table syntax on insert
1 parent 7385ddb commit 149074e

File tree

2 files changed

+261
-0
lines changed

2 files changed

+261
-0
lines changed

app/src/main/kotlin/com/kitabu/app/ui/editor/EditorActivity.kt

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,10 @@ import android.speech.RecognizerIntent
88
import android.text.*
99
import android.view.*
1010
import android.widget.ArrayAdapter
11+
import android.widget.EditText
1112
import android.widget.LinearLayout
13+
import android.widget.GridLayout
14+
import android.widget.TextView as AndroidTextView
1215
import androidx.activity.result.contract.ActivityResultContracts
1316
import androidx.activity.viewModels
1417
import androidx.appcompat.app.AppCompatActivity
@@ -142,6 +145,7 @@ class EditorActivity : AppCompatActivity() {
142145
"" to { insertLine("- [ ] ") },
143146
"" to { insertAtCursor("\n---\n") },
144147
"🔗" to { insertWikiLink() },
148+
"" to { showTableDialog() },
145149
"🎙" to { startVoiceInput() },
146150
"👁" to { togglePreview() }
147151
)
@@ -201,6 +205,152 @@ class EditorActivity : AppCompatActivity() {
201205
et.setSelection(pos + text.length)
202206
}
203207

208+
// ── Table insertion ────────────────────────────────────────────────
209+
210+
private fun showTableDialog() {
211+
val dialogView = layoutInflater.inflate(R.layout.dialog_table_input, null)
212+
val etRows = dialogView.findViewById<EditText>(R.id.etTableRows)
213+
val etCols = dialogView.findViewById<EditText>(R.id.etTableCols)
214+
val gridContainer = dialogView.findViewById<GridLayout>(R.id.gridTableInput)
215+
val btnBuild = dialogView.findViewById<android.widget.Button>(R.id.btnBuildTable)
216+
217+
etRows.setText("3")
218+
etCols.setText("3")
219+
220+
fun rebuildGrid() {
221+
val rows = etRows.text.toString().toIntOrNull()?.coerceIn(1, 20) ?: 0
222+
val cols = etCols.text.toString().toIntOrNull()?.coerceIn(1, 8) ?: 0
223+
if (rows == 0 || cols == 0) return
224+
225+
gridContainer.removeAllViews()
226+
gridContainer.columnCount = cols + 1 // +1 for row label column
227+
228+
// Top-left empty cell
229+
gridContainer.addView(AndroidTextView(this).apply {
230+
text = ""
231+
textSize = 12f
232+
setTextColor(ContextCompat.getColor(context, R.color.text_hint))
233+
})
234+
235+
// Column headers (editable)
236+
val headerLabels = mutableListOf<EditText>()
237+
for (c in 0 until cols) {
238+
val headerEt = EditText(this).apply {
239+
hint = "Col ${c + 1}"
240+
textSize = 13f
241+
setTextColor(ContextCompat.getColor(context, R.color.accent))
242+
setHintTextColor(ContextCompat.getColor(context, R.color.text_hint))
243+
setBackgroundColor(0x00000000)
244+
setPadding(6, 8, 6, 8)
245+
setTextColor(android.graphics.Color.WHITE)
246+
inputType = android.text.InputType.TYPE_CLASS_TEXT or android.text.InputType.TYPE_TEXT_FLAG_CAP_SENTENCES
247+
layoutParams = GridLayout.LayoutParams().apply {
248+
width = 0
249+
columnSpec = GridLayout.spec(c + 1, 1f)
250+
}
251+
}
252+
headerLabels.add(headerEt)
253+
gridContainer.addView(headerEt)
254+
}
255+
256+
// Data rows
257+
val dataEdits = mutableListOf<MutableList<EditText>>()
258+
for (r in 0 until rows) {
259+
// Row number label
260+
gridContainer.addView(AndroidTextView(this).apply {
261+
text = "R${r + 1}"
262+
textSize = 12f
263+
setTextColor(ContextCompat.getColor(context, R.color.text_hint))
264+
gravity = android.view.Gravity.CENTER
265+
setPadding(4, 8, 8, 8)
266+
})
267+
268+
val rowEdits = mutableListOf<EditText>()
269+
for (c in 0 until cols) {
270+
val cellEt = EditText(this).apply {
271+
hint = "..."
272+
textSize = 13f
273+
setTextColor(ContextCompat.getColor(context, R.color.text_primary))
274+
setHintTextColor(ContextCompat.getColor(context, R.color.text_hint))
275+
setBackgroundColor(0x00000000)
276+
setPadding(6, 8, 6, 8)
277+
inputType = android.text.InputType.TYPE_CLASS_TEXT or android.text.InputType.TYPE_TEXT_FLAG_CAP_SENTENCES
278+
layoutParams = GridLayout.LayoutParams().apply {
279+
width = 0
280+
columnSpec = GridLayout.spec(c + 1, 1f)
281+
}
282+
}
283+
rowEdits.add(cellEt)
284+
gridContainer.addView(cellEt)
285+
}
286+
dataEdits.add(rowEdits)
287+
}
288+
289+
// Store references for the build button
290+
gridContainer.setTag(0, headerLabels)
291+
gridContainer.setTag(1, dataEdits)
292+
}
293+
294+
// Initial build
295+
rebuildGrid()
296+
297+
// Rebuild when rows/cols change
298+
etRows.addTextChangedListener(object : TextWatcher {
299+
override fun beforeTextChanged(s: CharSequence?, st: Int, c: Int, a: Int) = Unit
300+
override fun onTextChanged(s: CharSequence?, st: Int, b: Int, c: Int) = rebuildGrid()
301+
override fun afterTextChanged(s: Editable?) = Unit
302+
})
303+
etCols.addTextChangedListener(object : TextWatcher {
304+
override fun beforeTextChanged(s: CharSequence?, st: Int, c: Int, a: Int) = Unit
305+
override fun onTextChanged(s: CharSequence?, st: Int, b: Int, c: Int) = rebuildGrid()
306+
override fun afterTextChanged(s: Editable?) = Unit
307+
})
308+
309+
val dialog = MaterialAlertDialogBuilder(this, R.style.KitabuDialog)
310+
.setTitle("Insert Table")
311+
.setView(dialogView)
312+
.setNegativeButton("Cancel", null)
313+
.create()
314+
315+
btnBuild.setOnClickListener {
316+
@Suppress("UNCHECKED_CAST")
317+
val headers = (gridContainer.getTag(0) as? List<EditText>)?.map { it.text.toString().trim() } ?: emptyList()
318+
@Suppress("UNCHECKED_CAST")
319+
val allRows = (gridContainer.getTag(1) as? List<List<EditText>>)?.map { row ->
320+
row.map { it.text.toString().trim() }
321+
} ?: emptyList()
322+
323+
val tableMd = buildMarkdownTable(headers, allRows)
324+
insertAtCursor(tableMd)
325+
dialog.dismiss()
326+
}
327+
328+
dialog.show()
329+
}
330+
331+
private fun buildMarkdownTable(headers: List<String>, rows: List<List<String>>): String {
332+
val cols = headers.size.coerceAtLeast(if (rows.isNotEmpty()) rows[0].size else 0)
333+
if (cols == 0) return ""
334+
335+
val sb = StringBuilder()
336+
sb.append("\n")
337+
338+
// Header row
339+
sb.append("| ${headers.map { it.ifBlank { "Col" } }.joinToString(" | ")} |\n")
340+
341+
// Separator
342+
sb.append("| ${headers.indices.joinToString(" | ") { "---" } |\n")
343+
344+
// Data rows
345+
rows.forEach { row ->
346+
val cells = row + List(cols - row.size) { "" }
347+
sb.append("| ${cells.joinToString(" | ")} |\n")
348+
}
349+
350+
sb.append("\n")
351+
return sb.toString()
352+
}
353+
204354
private fun insertWikiLink() {
205355
lifecycleScope.launch {
206356
val titlePrefix = binding.etContent.text.toString()
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
3+
android:layout_width="match_parent"
4+
android:layout_height="wrap_content"
5+
android:orientation="vertical"
6+
android:padding="20dp"
7+
android:background="@color/bg_surface">
8+
9+
<TextView
10+
android:layout_width="wrap_content"
11+
android:layout_height="wrap_content"
12+
android:text="Set table dimensions, then fill in the grid below."
13+
android:textColor="@color/text_secondary"
14+
android:textSize="13sp"
15+
android:layout_marginBottom="16dp"/>
16+
17+
<LinearLayout
18+
android:layout_width="match_parent"
19+
android:layout_height="wrap_content"
20+
android:orientation="horizontal"
21+
android:gravity="center_vertical"
22+
android:layout_marginBottom="12dp">
23+
24+
<TextView
25+
android:layout_width="wrap_content"
26+
android:layout_height="wrap_content"
27+
android:text="Rows:"
28+
android:textColor="@color/text_primary"
29+
android:textSize="14sp"
30+
android:fontFamily="@font/inter_semibold"
31+
android:layout_marginEnd="8dp"/>
32+
33+
<EditText
34+
android:id="@+id/etTableRows"
35+
android:layout_width="0dp"
36+
android:layout_height="40dp"
37+
android:layout_weight="1"
38+
android:inputType="number"
39+
android:text="3"
40+
android:textColor="@color/text_primary"
41+
android:fontFamily="@font/inter_medium"
42+
android:background="@color/bg_elevated"
43+
android:paddingHorizontal="12dp"
44+
android:paddingVertical="4dp"
45+
android:gravity="center"
46+
android:layout_marginEnd="16dp"/>
47+
48+
<TextView
49+
android:layout_width="wrap_content"
50+
android:layout_height="wrap_content"
51+
android:text="Columns:"
52+
android:textColor="@color/text_primary"
53+
android:textSize="14sp"
54+
android:fontFamily="@font/inter_semibold"
55+
android:layout_marginEnd="8dp"/>
56+
57+
<EditText
58+
android:id="@+id/etTableCols"
59+
android:layout_width="0dp"
60+
android:layout_height="40dp"
61+
android:layout_weight="1"
62+
android:inputType="number"
63+
android:text="3"
64+
android:textColor="@color/text_primary"
65+
android:fontFamily="@font/inter_medium"
66+
android:background="@color/bg_elevated"
67+
android:paddingHorizontal="12dp"
68+
android:paddingVertical="4dp"
69+
android:gravity="center"/>
70+
71+
</LinearLayout>
72+
73+
<View
74+
android:layout_width="match_parent"
75+
android:layout_height="1dp"
76+
android:background="@color/divider"
77+
android:layout_marginBottom="12dp"/>
78+
79+
<!-- Scrollable grid for manual data entry -->
80+
<HorizontalScrollView
81+
android:layout_width="match_parent"
82+
android:layout_height="wrap_content">
83+
84+
<GridLayout
85+
android:id="@+id/gridTableInput"
86+
android:layout_width="wrap_content"
87+
android:layout_height="wrap_content"
88+
android:columnCount="4"
89+
android:useDefaultMargins="false"
90+
android:padding="4dp"/>
91+
92+
</HorizontalScrollView>
93+
94+
<View
95+
android:layout_width="match_parent"
96+
android:layout_height="1dp"
97+
android:background="@color/divider"
98+
android:layout_marginTop="12dp"
99+
android:layout_marginBottom="12dp"/>
100+
101+
<Button
102+
android:id="@+id/btnBuildTable"
103+
android:layout_width="match_parent"
104+
android:layout_height="48dp"
105+
android:text="Insert Table"
106+
android:textSize="14sp"
107+
android:fontFamily="@font/inter_semibold"
108+
android:backgroundTint="@color/accent"
109+
android:textColor="@color/bg_primary"/>
110+
111+
</LinearLayout>

0 commit comments

Comments
 (0)