@@ -8,7 +8,10 @@ import android.speech.RecognizerIntent
88import android.text.*
99import android.view.*
1010import android.widget.ArrayAdapter
11+ import android.widget.EditText
1112import android.widget.LinearLayout
13+ import android.widget.GridLayout
14+ import android.widget.TextView as AndroidTextView
1215import androidx.activity.result.contract.ActivityResultContracts
1316import androidx.activity.viewModels
1417import 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()
0 commit comments