capacitor-sora-editor 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +110 -0
- package/android/build.gradle +77 -0
- package/android/src/main/AndroidManifest.xml +13 -0
- package/android/src/main/java/com/github/soraeditor/capacitor/EditorActivity.kt +54 -0
- package/android/src/main/java/com/github/soraeditor/capacitor/EditorViewModel.kt +471 -0
- package/android/src/main/java/com/github/soraeditor/capacitor/SoraEditorPlugin.kt +33 -0
- package/android/src/main/java/com/github/soraeditor/capacitor/ui/EditorScreen.kt +1016 -0
- package/android/src/main/java/com/github/soraeditor/capacitor/ui/theme/Theme.kt +38 -0
- package/android/src/main/res/values/styles.xml +6 -0
- package/dist/esm/definitions.d.ts +5 -0
- package/dist/esm/definitions.js +2 -0
- package/dist/esm/definitions.js.map +1 -0
- package/dist/esm/index.d.ts +4 -0
- package/dist/esm/index.js +7 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/web.d.ts +7 -0
- package/dist/esm/web.js +8 -0
- package/dist/esm/web.js.map +1 -0
- package/dist/plugin.cjs.js +24 -0
- package/dist/plugin.cjs.js.map +1 -0
- package/dist/plugin.js +27 -0
- package/dist/plugin.js.map +1 -0
- package/package.json +76 -0
|
@@ -0,0 +1,471 @@
|
|
|
1
|
+
package com.github.soraeditor.capacitor
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import androidx.lifecycle.ViewModel
|
|
5
|
+
import androidx.lifecycle.viewModelScope
|
|
6
|
+
import kotlinx.coroutines.flow.MutableStateFlow
|
|
7
|
+
import kotlinx.coroutines.flow.StateFlow
|
|
8
|
+
import kotlinx.coroutines.flow.asStateFlow
|
|
9
|
+
import kotlinx.coroutines.flow.update
|
|
10
|
+
import kotlinx.coroutines.launch
|
|
11
|
+
import java.io.File
|
|
12
|
+
import org.json.JSONObject
|
|
13
|
+
|
|
14
|
+
data class EditorUiState(
|
|
15
|
+
val content: String = "",
|
|
16
|
+
val filePath: String = "",
|
|
17
|
+
val fileName: String = "",
|
|
18
|
+
val isModified: Boolean = false,
|
|
19
|
+
val fontSize: Float = 18f,
|
|
20
|
+
val showLineNumbers: Boolean = true,
|
|
21
|
+
val wordWrap: Boolean = false,
|
|
22
|
+
val isReadOnly: Boolean = false,
|
|
23
|
+
val showToolbar: Boolean = true,
|
|
24
|
+
val showSearch: Boolean = false,
|
|
25
|
+
val searchQuery: String = "",
|
|
26
|
+
val replaceText: String = "",
|
|
27
|
+
val currentMatch: Int = 0,
|
|
28
|
+
val totalMatches: Int = 0,
|
|
29
|
+
val showToc: Boolean = false,
|
|
30
|
+
val tocMode: String = "chars", // "chars" or "lines"
|
|
31
|
+
val showSettings: Boolean = false,
|
|
32
|
+
val backgroundColor: String = "#FFFFFF",
|
|
33
|
+
val isDarkTheme: Boolean = false,
|
|
34
|
+
val currentCursorPos: Int = 0,
|
|
35
|
+
val autoSave: Boolean = true,
|
|
36
|
+
val showFileProperties: Boolean = false,
|
|
37
|
+
val showRenameDialog: Boolean = false,
|
|
38
|
+
val showExitConfirmation: Boolean = false,
|
|
39
|
+
val cursorLine: Int = 1,
|
|
40
|
+
val cursorColumn: Int = 0,
|
|
41
|
+
val originalContent: String = "",
|
|
42
|
+
val showStatusBar: Boolean = true,
|
|
43
|
+
val showSymbolBar: Boolean = true,
|
|
44
|
+
val uiColor: String = "#F5F5F5",
|
|
45
|
+
val tocColor: String = "#FFFFFF",
|
|
46
|
+
val searchColor: String = "#F5F5F5",
|
|
47
|
+
val menuColor: String = "#FFFFFF"
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
class EditorViewModel : ViewModel() {
|
|
51
|
+
private val _uiState = MutableStateFlow(EditorUiState())
|
|
52
|
+
val uiState: StateFlow<EditorUiState> = _uiState.asStateFlow()
|
|
53
|
+
|
|
54
|
+
fun setCursorPosition(pos: Int, line: Int, col: Int) {
|
|
55
|
+
_uiState.update { it.copy(
|
|
56
|
+
currentCursorPos = pos,
|
|
57
|
+
cursorLine = line + 1, // 1-indexed for display
|
|
58
|
+
cursorColumn = col
|
|
59
|
+
) }
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
fun loadFile(context: Context, filePath: String) {
|
|
63
|
+
viewModelScope.launch {
|
|
64
|
+
try {
|
|
65
|
+
// Convert URI to actual path if needed
|
|
66
|
+
var actualPath = if (filePath.startsWith("file://")) {
|
|
67
|
+
filePath.substring(7) // Remove "file://" prefix
|
|
68
|
+
} else {
|
|
69
|
+
filePath
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Decode URI-encoded characters (like %E4%BD%9C%E8%80%85 for Chinese)
|
|
73
|
+
actualPath = java.net.URLDecoder.decode(actualPath, "UTF-8")
|
|
74
|
+
|
|
75
|
+
android.util.Log.d("EditorViewModel", "Loading file from: $actualPath")
|
|
76
|
+
val file = File(actualPath)
|
|
77
|
+
|
|
78
|
+
if (file.exists()) {
|
|
79
|
+
val content = file.readText()
|
|
80
|
+
android.util.Log.d("EditorViewModel", "File loaded successfully, size: ${content.length}")
|
|
81
|
+
_uiState.value = _uiState.value.copy(
|
|
82
|
+
content = content,
|
|
83
|
+
filePath = actualPath,
|
|
84
|
+
fileName = file.name,
|
|
85
|
+
originalContent = content,
|
|
86
|
+
isModified = false
|
|
87
|
+
)
|
|
88
|
+
} else {
|
|
89
|
+
android.util.Log.e("EditorViewModel", "File does not exist: $actualPath")
|
|
90
|
+
}
|
|
91
|
+
} catch (e: Exception) {
|
|
92
|
+
android.util.Log.e("EditorViewModel", "Error loading file", e)
|
|
93
|
+
e.printStackTrace()
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
private var saveJob: kotlinx.coroutines.Job? = null
|
|
99
|
+
|
|
100
|
+
fun updateContent(context: Context, newContent: String) {
|
|
101
|
+
val normalizedNew = newContent.replace("\r\n", "\n")
|
|
102
|
+
val normalizedCurrent = _uiState.value.content.replace("\r\n", "\n")
|
|
103
|
+
|
|
104
|
+
// If content hasn't changed (ignoring line endings), don't update
|
|
105
|
+
if (normalizedNew == normalizedCurrent && _uiState.value.content.isNotEmpty()) return
|
|
106
|
+
|
|
107
|
+
// Check for trivial change (trailing whitespace/newline only) when not modified yet
|
|
108
|
+
// This prevents updating the timestamp just because the editor added a newline
|
|
109
|
+
val isTrivialChange = !_uiState.value.isModified &&
|
|
110
|
+
normalizedNew.trimEnd() == normalizedCurrent.trimEnd()
|
|
111
|
+
|
|
112
|
+
if (isTrivialChange) {
|
|
113
|
+
// Update content so state matches editor, but don't mark modified or save
|
|
114
|
+
_uiState.update { it.copy(content = newContent) }
|
|
115
|
+
return
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
_uiState.update { it.copy(
|
|
119
|
+
content = newContent,
|
|
120
|
+
isModified = true
|
|
121
|
+
) }
|
|
122
|
+
|
|
123
|
+
if (_uiState.value.autoSave) {
|
|
124
|
+
queueAutoSave(context)
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
private fun queueAutoSave(context: Context) {
|
|
129
|
+
saveJob?.cancel()
|
|
130
|
+
saveJob = viewModelScope.launch {
|
|
131
|
+
kotlinx.coroutines.delay(1000) // Debounce 1s
|
|
132
|
+
if (_uiState.value.isModified) {
|
|
133
|
+
saveFile(context)
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
fun saveFile(context: Context): Boolean {
|
|
139
|
+
return try {
|
|
140
|
+
val contentToSave = _uiState.value.content
|
|
141
|
+
val path = _uiState.value.filePath
|
|
142
|
+
|
|
143
|
+
android.util.Log.d("EditorViewModel", "Saving file to: $path, content length: ${contentToSave.length}")
|
|
144
|
+
|
|
145
|
+
val file = File(path)
|
|
146
|
+
file.writeText(contentToSave)
|
|
147
|
+
|
|
148
|
+
_uiState.update { state ->
|
|
149
|
+
// Only clear isModified if the content we just saved is still the current content
|
|
150
|
+
if (state.content == contentToSave) {
|
|
151
|
+
state.copy(isModified = false)
|
|
152
|
+
} else {
|
|
153
|
+
state
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
true
|
|
157
|
+
} catch (e: Exception) {
|
|
158
|
+
android.util.Log.e("EditorViewModel", "Failed to save file", e)
|
|
159
|
+
e.printStackTrace()
|
|
160
|
+
false
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
fun saveOnExit(context: Context) {
|
|
165
|
+
val state = _uiState.value
|
|
166
|
+
// Cancel pending auto-save and save immediately if needed
|
|
167
|
+
saveJob?.cancel()
|
|
168
|
+
|
|
169
|
+
if (state.isModified || state.autoSave) {
|
|
170
|
+
saveFile(context)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Logic to rename based on first line, ONLY on exit
|
|
174
|
+
val fileName = state.fileName
|
|
175
|
+
// Heuristic: If filename is just numbers (timestamp) or starts with "Untitled", consider it "New"
|
|
176
|
+
val isDefaultName = fileName.matches(Regex("^\\d+(\\.txt)?$")) ||
|
|
177
|
+
fileName.startsWith("Untitled") ||
|
|
178
|
+
fileName.startsWith("NewFile")
|
|
179
|
+
|
|
180
|
+
if (isDefaultName) {
|
|
181
|
+
val content = state.content
|
|
182
|
+
val firstLine = content.lineSequence().firstOrNull()?.trim() ?: ""
|
|
183
|
+
// Sanitize title: remove invalid chars, limit length
|
|
184
|
+
val validTitle = firstLine.replace(Regex("[\\\\/:*?\"<>|]"), "").take(20).trim()
|
|
185
|
+
|
|
186
|
+
if (validTitle.isNotEmpty() && validTitle != fileName.removeSuffix(".txt")) {
|
|
187
|
+
val newName = "$validTitle.txt"
|
|
188
|
+
android.util.Log.d("EditorViewModel", "Auto-renaming on exit: $fileName -> $newName")
|
|
189
|
+
renameFile(newName)
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
fun setFontSize(context: Context, size: Float) {
|
|
195
|
+
_uiState.value = _uiState.value.copy(fontSize = size)
|
|
196
|
+
saveSettings(context)
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
fun toggleLineNumbers(context: Context) {
|
|
200
|
+
_uiState.update { it.copy(showLineNumbers = !it.showLineNumbers) }
|
|
201
|
+
saveSettings(context)
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
fun toggleWordWrap(context: Context) {
|
|
205
|
+
_uiState.update { it.copy(wordWrap = !it.wordWrap) }
|
|
206
|
+
saveSettings(context)
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
fun toggleReadOnly() {
|
|
210
|
+
_uiState.update { state ->
|
|
211
|
+
val nextReadOnly = !state.isReadOnly
|
|
212
|
+
state.copy(
|
|
213
|
+
isReadOnly = nextReadOnly,
|
|
214
|
+
showToolbar = if (nextReadOnly) false else true
|
|
215
|
+
)
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
fun toggleToolbar() {
|
|
220
|
+
_uiState.update { it.copy(showToolbar = !it.showToolbar) }
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
fun setShowToolbar(show: Boolean) {
|
|
224
|
+
_uiState.update { it.copy(showToolbar = show) }
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
fun setShowSearch(show: Boolean) {
|
|
228
|
+
_uiState.update { it.copy(showSearch = show) }
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
fun setSearchQuery(query: String) {
|
|
232
|
+
_uiState.update { it.copy(searchQuery = query) }
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
fun setReplaceText(text: String) {
|
|
236
|
+
_uiState.update { it.copy(replaceText = text) }
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
fun setShowToc(show: Boolean) {
|
|
240
|
+
_uiState.update { it.copy(showToc = show) }
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
fun setTocMode(mode: String) {
|
|
244
|
+
_uiState.update { it.copy(tocMode = mode) }
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
fun setShowSettings(show: Boolean) {
|
|
248
|
+
_uiState.update { it.copy(showSettings = show) }
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
fun setBackgroundColor(context: Context, color: String) {
|
|
252
|
+
_uiState.update { it.copy(backgroundColor = color) }
|
|
253
|
+
saveSettings(context)
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
fun setDarkTheme(isDark: Boolean) {
|
|
257
|
+
_uiState.update { it.copy(isDarkTheme = isDark) }
|
|
258
|
+
}
|
|
259
|
+
fun setMatchResults(current: Int, total: Int) {
|
|
260
|
+
_uiState.update { it.copy(currentMatch = current, totalMatches = total) }
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
fun setAutoSave(context: Context, enabled: Boolean) {
|
|
264
|
+
_uiState.update { it.copy(autoSave = enabled) }
|
|
265
|
+
saveSettings(context)
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
fun toggleStatusBar(context: Context) {
|
|
269
|
+
_uiState.update { it.copy(showStatusBar = !it.showStatusBar) }
|
|
270
|
+
saveSettings(context)
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
fun toggleSymbolBar(context: Context) {
|
|
274
|
+
_uiState.update { it.copy(showSymbolBar = !it.showSymbolBar) }
|
|
275
|
+
saveSettings(context)
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
fun setShowStatusBar(context: Context, show: Boolean) {
|
|
279
|
+
_uiState.update { it.copy(showStatusBar = show) }
|
|
280
|
+
saveSettings(context)
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
fun setShowSymbolBar(context: Context, show: Boolean) {
|
|
284
|
+
_uiState.update { it.copy(showSymbolBar = show) }
|
|
285
|
+
saveSettings(context)
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
fun setShowFileProperties(show: Boolean) {
|
|
289
|
+
_uiState.update { it.copy(showFileProperties = show) }
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
fun setShowRenameDialog(show: Boolean) {
|
|
293
|
+
_uiState.update { it.copy(showRenameDialog = show) }
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
fun setShowExitConfirmation(show: Boolean) {
|
|
297
|
+
_uiState.update { it.copy(showExitConfirmation = show) }
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
fun setUiColor(context: Context, color: String) {
|
|
301
|
+
_uiState.update { it.copy(uiColor = color) }
|
|
302
|
+
saveSettings(context)
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
fun setTocColor(context: Context, color: String) {
|
|
306
|
+
_uiState.update { it.copy(tocColor = color) }
|
|
307
|
+
saveSettings(context)
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
fun setSearchColor(context: Context, color: String) {
|
|
311
|
+
_uiState.update { it.copy(searchColor = color) }
|
|
312
|
+
saveSettings(context)
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
fun setMenuColor(context: Context, color: String) {
|
|
316
|
+
_uiState.update { it.copy(menuColor = color) }
|
|
317
|
+
saveSettings(context)
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
fun renameFile(newName: String): Boolean {
|
|
321
|
+
val currentFile = File(_uiState.value.filePath)
|
|
322
|
+
val parent = currentFile.parentFile
|
|
323
|
+
val newFile = File(parent, newName)
|
|
324
|
+
return if (currentFile.renameTo(newFile)) {
|
|
325
|
+
_uiState.value = _uiState.value.copy(
|
|
326
|
+
filePath = newFile.absolutePath,
|
|
327
|
+
fileName = newFile.name
|
|
328
|
+
)
|
|
329
|
+
true
|
|
330
|
+
} else {
|
|
331
|
+
false
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
fun moveToRecycleBin(): Boolean {
|
|
336
|
+
return try {
|
|
337
|
+
val file = File(_uiState.value.filePath)
|
|
338
|
+
if (!file.exists()) return false
|
|
339
|
+
|
|
340
|
+
// Assume the notes root is the parent of the first folder that doesn't start with '.'
|
|
341
|
+
// Or more simply, let's just create a .recycle folder in the same directory as the file for now,
|
|
342
|
+
// or go up until we find a reasonable root.
|
|
343
|
+
// Let's go with a sibling ".recycle" folder in the same directory.
|
|
344
|
+
val parent = file.parentFile ?: return false
|
|
345
|
+
val recycleDir = File(parent, ".recycle")
|
|
346
|
+
if (!recycleDir.exists()) {
|
|
347
|
+
recycleDir.mkdirs()
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
val targetFile = File(recycleDir, file.name)
|
|
351
|
+
// If target exists, append timestamp
|
|
352
|
+
val finalTarget = if (targetFile.exists()) {
|
|
353
|
+
File(recycleDir, "${System.currentTimeMillis()}_${file.name}")
|
|
354
|
+
} else {
|
|
355
|
+
targetFile
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
file.renameTo(finalTarget)
|
|
359
|
+
} catch (e: Exception) {
|
|
360
|
+
e.printStackTrace()
|
|
361
|
+
false
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
fun resetSettings(context: Context) {
|
|
366
|
+
_uiState.update { it.copy(
|
|
367
|
+
fontSize = 18f,
|
|
368
|
+
showLineNumbers = true,
|
|
369
|
+
wordWrap = false,
|
|
370
|
+
backgroundColor = "#FFFFFF",
|
|
371
|
+
autoSave = true,
|
|
372
|
+
showStatusBar = true,
|
|
373
|
+
showSymbolBar = true,
|
|
374
|
+
uiColor = "#F5F5F5",
|
|
375
|
+
tocColor = "#FFFFFF",
|
|
376
|
+
searchColor = "#F5F5F5",
|
|
377
|
+
menuColor = "#FFFFFF"
|
|
378
|
+
) }
|
|
379
|
+
saveSettings(context)
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
fun getFileDetails(): Map<String, String> {
|
|
383
|
+
val file = File(_uiState.value.filePath)
|
|
384
|
+
val details = mutableMapOf<String, String>()
|
|
385
|
+
details["文件名"] = file.name
|
|
386
|
+
details["路径"] = file.absolutePath
|
|
387
|
+
details["大小"] = "${file.length()} 字节"
|
|
388
|
+
details["最后修改"] = java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss", java.util.Locale.getDefault()).format(java.util.Date(file.lastModified()))
|
|
389
|
+
return details
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
private fun saveSettings(context: Context) {
|
|
393
|
+
val prefs = context.getSharedPreferences("editor_settings", Context.MODE_PRIVATE)
|
|
394
|
+
val json = JSONObject().apply {
|
|
395
|
+
put("fontSize", _uiState.value.fontSize.toDouble())
|
|
396
|
+
put("showLineNumbers", _uiState.value.showLineNumbers)
|
|
397
|
+
put("wordWrap", _uiState.value.wordWrap)
|
|
398
|
+
put("backgroundColor", _uiState.value.backgroundColor)
|
|
399
|
+
put("autoSave", _uiState.value.autoSave)
|
|
400
|
+
put("showStatusBar", _uiState.value.showStatusBar)
|
|
401
|
+
put("showSymbolBar", _uiState.value.showSymbolBar)
|
|
402
|
+
put("uiColor", _uiState.value.uiColor)
|
|
403
|
+
put("tocColor", _uiState.value.tocColor)
|
|
404
|
+
put("searchColor", _uiState.value.searchColor)
|
|
405
|
+
put("menuColor", _uiState.value.menuColor)
|
|
406
|
+
}
|
|
407
|
+
prefs.edit().putString("settings_json", json.toString()).apply()
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
fun loadSettings(context: Context) {
|
|
411
|
+
val prefs = context.getSharedPreferences("editor_settings", Context.MODE_PRIVATE)
|
|
412
|
+
val jsonStr = prefs.getString("settings_json", null) ?: return
|
|
413
|
+
try {
|
|
414
|
+
val json = JSONObject(jsonStr)
|
|
415
|
+
_uiState.value = _uiState.value.copy(
|
|
416
|
+
fontSize = json.optDouble("fontSize", 18.0).toFloat(),
|
|
417
|
+
showLineNumbers = json.optBoolean("showLineNumbers", true),
|
|
418
|
+
wordWrap = json.optBoolean("wordWrap", false),
|
|
419
|
+
backgroundColor = json.optString("backgroundColor", "#FFFFFF"),
|
|
420
|
+
autoSave = json.optBoolean("autoSave", true),
|
|
421
|
+
showStatusBar = json.optBoolean("showStatusBar", true),
|
|
422
|
+
showSymbolBar = json.optBoolean("showSymbolBar", true),
|
|
423
|
+
uiColor = json.optString("uiColor", "#F5F5F5"),
|
|
424
|
+
tocColor = json.optString("tocColor", "#FFFFFF"),
|
|
425
|
+
searchColor = json.optString("searchColor", "#F5F5F5"),
|
|
426
|
+
menuColor = json.optString("menuColor", "#FFFFFF")
|
|
427
|
+
)
|
|
428
|
+
} catch (e: Exception) {
|
|
429
|
+
e.printStackTrace()
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
fun applySettingsFromJson(context: Context, jsonStr: String): Boolean {
|
|
434
|
+
return try {
|
|
435
|
+
val json = JSONObject(jsonStr)
|
|
436
|
+
_uiState.value = _uiState.value.copy(
|
|
437
|
+
fontSize = json.optDouble("fontSize", _uiState.value.fontSize.toDouble()).toFloat(),
|
|
438
|
+
showLineNumbers = json.optBoolean("showLineNumbers", _uiState.value.showLineNumbers),
|
|
439
|
+
wordWrap = json.optBoolean("wordWrap", _uiState.value.wordWrap),
|
|
440
|
+
backgroundColor = json.optString("backgroundColor", _uiState.value.backgroundColor),
|
|
441
|
+
autoSave = json.optBoolean("autoSave", _uiState.value.autoSave),
|
|
442
|
+
showStatusBar = json.optBoolean("showStatusBar", _uiState.value.showStatusBar),
|
|
443
|
+
showSymbolBar = json.optBoolean("showSymbolBar", _uiState.value.showSymbolBar),
|
|
444
|
+
uiColor = json.optString("uiColor", _uiState.value.uiColor),
|
|
445
|
+
tocColor = json.optString("tocColor", _uiState.value.tocColor),
|
|
446
|
+
searchColor = json.optString("searchColor", _uiState.value.searchColor),
|
|
447
|
+
menuColor = json.optString("menuColor", _uiState.value.menuColor)
|
|
448
|
+
)
|
|
449
|
+
saveSettings(context)
|
|
450
|
+
true
|
|
451
|
+
} catch (e: Exception) {
|
|
452
|
+
e.printStackTrace()
|
|
453
|
+
false
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
fun getSettingsJson(): String {
|
|
458
|
+
return JSONObject().apply {
|
|
459
|
+
put("fontSize", _uiState.value.fontSize.toDouble())
|
|
460
|
+
put("showLineNumbers", _uiState.value.showLineNumbers)
|
|
461
|
+
put("wordWrap", _uiState.value.wordWrap)
|
|
462
|
+
put("backgroundColor", _uiState.value.backgroundColor)
|
|
463
|
+
put("autoSave", _uiState.value.autoSave)
|
|
464
|
+
put("showStatusBar", _uiState.value.showStatusBar)
|
|
465
|
+
put("showSymbolBar", _uiState.value.showSymbolBar)
|
|
466
|
+
put("uiColor", _uiState.value.uiColor)
|
|
467
|
+
put("tocColor", _uiState.value.tocColor)
|
|
468
|
+
put("searchColor", _uiState.value.searchColor)
|
|
469
|
+
} .toString(4)
|
|
470
|
+
}
|
|
471
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
package com.github.soraeditor.capacitor
|
|
2
|
+
|
|
3
|
+
import android.content.Intent
|
|
4
|
+
import com.getcapacitor.JSObject
|
|
5
|
+
import com.getcapacitor.Plugin
|
|
6
|
+
import com.getcapacitor.PluginCall
|
|
7
|
+
import com.getcapacitor.PluginMethod
|
|
8
|
+
import com.getcapacitor.annotation.CapacitorPlugin
|
|
9
|
+
|
|
10
|
+
@CapacitorPlugin(name = "SoraEditor")
|
|
11
|
+
class SoraEditorPlugin : Plugin() {
|
|
12
|
+
|
|
13
|
+
@PluginMethod
|
|
14
|
+
fun openEditor(call: PluginCall) {
|
|
15
|
+
val filePath = call.getString("filePath") ?: ""
|
|
16
|
+
|
|
17
|
+
android.util.Log.d("SoraEditorPlugin", "openEditor called with filePath: $filePath")
|
|
18
|
+
|
|
19
|
+
if (filePath.isEmpty()) {
|
|
20
|
+
android.util.Log.e("SoraEditorPlugin", "File path is empty")
|
|
21
|
+
call.reject("File path is required")
|
|
22
|
+
return
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
activity.runOnUiThread {
|
|
26
|
+
val intent = Intent(context, EditorActivity::class.java)
|
|
27
|
+
intent.putExtra("FILE_PATH", filePath)
|
|
28
|
+
android.util.Log.d("SoraEditorPlugin", "Starting EditorActivity")
|
|
29
|
+
activity.startActivity(intent)
|
|
30
|
+
call.resolve()
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|