capacitor-dex-editor 0.0.50 → 0.0.52
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.
|
@@ -6,11 +6,17 @@ import android.graphics.Color;
|
|
|
6
6
|
import android.graphics.Typeface;
|
|
7
7
|
import android.os.Build;
|
|
8
8
|
import android.os.Bundle;
|
|
9
|
+
import android.os.Handler;
|
|
10
|
+
import android.os.Looper;
|
|
11
|
+
import android.text.Editable;
|
|
9
12
|
import android.text.InputType;
|
|
13
|
+
import android.text.TextWatcher;
|
|
14
|
+
import android.view.Gravity;
|
|
10
15
|
import android.view.View;
|
|
11
16
|
import android.view.ViewGroup;
|
|
12
17
|
import android.view.Window;
|
|
13
18
|
import android.view.WindowManager;
|
|
19
|
+
import android.view.inputmethod.InputMethodManager;
|
|
14
20
|
import android.widget.CheckBox;
|
|
15
21
|
import android.widget.EditText;
|
|
16
22
|
import android.widget.FrameLayout;
|
|
@@ -43,6 +49,20 @@ public class SmaliEditorActivity extends AppCompatActivity {
|
|
|
43
49
|
private LinearLayout root;
|
|
44
50
|
private View topSpacer;
|
|
45
51
|
private View bottomSpacer;
|
|
52
|
+
|
|
53
|
+
// 搜索面板相关
|
|
54
|
+
private LinearLayout searchPanel;
|
|
55
|
+
private EditText searchInput;
|
|
56
|
+
private EditText replaceInput;
|
|
57
|
+
private TextView searchResultText;
|
|
58
|
+
private TextView prevBtn, nextBtn, replaceBtn, replaceAllBtn;
|
|
59
|
+
private int searchMatchCount = 0;
|
|
60
|
+
private int currentMatchIndex = 0;
|
|
61
|
+
private boolean isSearchPanelVisible = false;
|
|
62
|
+
|
|
63
|
+
// 搜索防抖
|
|
64
|
+
private Handler searchHandler = new Handler(Looper.getMainLooper());
|
|
65
|
+
private Runnable searchRunnable;
|
|
46
66
|
|
|
47
67
|
@Override
|
|
48
68
|
protected void onCreate(Bundle savedInstanceState) {
|
|
@@ -91,9 +111,14 @@ public class SmaliEditorActivity extends AppCompatActivity {
|
|
|
91
111
|
editView.setSyntaxLanguageFileName(syntaxFile);
|
|
92
112
|
editView.setEditedMode(!readOnly);
|
|
93
113
|
editView.setTypeface(Typeface.MONOSPACE);
|
|
94
|
-
editView.setTextSize(
|
|
114
|
+
editView.setTextSize(16);
|
|
95
115
|
root.addView(editView);
|
|
96
116
|
|
|
117
|
+
// 搜索面板(初始隐藏)
|
|
118
|
+
searchPanel = createSearchPanel();
|
|
119
|
+
searchPanel.setVisibility(View.GONE);
|
|
120
|
+
root.addView(searchPanel);
|
|
121
|
+
|
|
97
122
|
// 底部安全区域占位
|
|
98
123
|
bottomSpacer = new View(this);
|
|
99
124
|
bottomSpacer.setBackgroundColor(Color.parseColor("#1E1E1E"));
|
|
@@ -102,18 +127,19 @@ public class SmaliEditorActivity extends AppCompatActivity {
|
|
|
102
127
|
|
|
103
128
|
setContentView(root);
|
|
104
129
|
|
|
105
|
-
//
|
|
130
|
+
// 处理安全区域和键盘
|
|
106
131
|
ViewCompat.setOnApplyWindowInsetsListener(root, (v, windowInsets) -> {
|
|
107
|
-
Insets
|
|
132
|
+
Insets systemBars = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars());
|
|
133
|
+
Insets ime = windowInsets.getInsets(WindowInsetsCompat.Type.ime());
|
|
108
134
|
|
|
109
135
|
// 更新顶部占位高度
|
|
110
136
|
ViewGroup.LayoutParams topParams = topSpacer.getLayoutParams();
|
|
111
|
-
topParams.height =
|
|
137
|
+
topParams.height = systemBars.top;
|
|
112
138
|
topSpacer.setLayoutParams(topParams);
|
|
113
139
|
|
|
114
|
-
//
|
|
140
|
+
// 更新底部占位高度(考虑键盘)
|
|
115
141
|
ViewGroup.LayoutParams bottomParams = bottomSpacer.getLayoutParams();
|
|
116
|
-
bottomParams.height =
|
|
142
|
+
bottomParams.height = Math.max(systemBars.bottom, ime.bottom);
|
|
117
143
|
bottomSpacer.setLayoutParams(bottomParams);
|
|
118
144
|
|
|
119
145
|
return WindowInsetsCompat.CONSUMED;
|
|
@@ -168,7 +194,7 @@ public class SmaliEditorActivity extends AppCompatActivity {
|
|
|
168
194
|
|
|
169
195
|
// 查找按钮
|
|
170
196
|
ImageButton searchBtn = createIconButton("🔍");
|
|
171
|
-
searchBtn.setOnClickListener(v ->
|
|
197
|
+
searchBtn.setOnClickListener(v -> toggleSearchPanel());
|
|
172
198
|
toolbar.addView(searchBtn);
|
|
173
199
|
|
|
174
200
|
// 跳转行号按钮
|
|
@@ -217,92 +243,243 @@ public class SmaliEditorActivity extends AppCompatActivity {
|
|
|
217
243
|
}
|
|
218
244
|
|
|
219
245
|
private String lastSearchText = "";
|
|
220
|
-
private String lastReplaceText = "";
|
|
221
|
-
|
|
222
|
-
private void showSearchDialog() {
|
|
223
|
-
LinearLayout layout = new LinearLayout(this);
|
|
224
|
-
layout.setOrientation(LinearLayout.VERTICAL);
|
|
225
|
-
layout.setPadding(48, 32, 48, 16);
|
|
226
246
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
247
|
+
// 创建 MT 风格的底部搜索面板
|
|
248
|
+
private LinearLayout createSearchPanel() {
|
|
249
|
+
LinearLayout panel = new LinearLayout(this);
|
|
250
|
+
panel.setOrientation(LinearLayout.VERTICAL);
|
|
251
|
+
panel.setBackgroundColor(Color.parseColor("#2D2D2D"));
|
|
252
|
+
panel.setPadding(16, 8, 16, 8);
|
|
253
|
+
|
|
254
|
+
// 第一行:搜索输入框 + 结果计数
|
|
255
|
+
LinearLayout row1 = new LinearLayout(this);
|
|
256
|
+
row1.setOrientation(LinearLayout.HORIZONTAL);
|
|
257
|
+
row1.setGravity(Gravity.CENTER_VERTICAL);
|
|
258
|
+
|
|
259
|
+
// 搜索输入框
|
|
260
|
+
searchInput = new EditText(this);
|
|
261
|
+
searchInput.setHint("查找");
|
|
231
262
|
searchInput.setTextColor(Color.WHITE);
|
|
232
263
|
searchInput.setHintTextColor(Color.GRAY);
|
|
233
|
-
|
|
264
|
+
searchInput.setBackgroundResource(R.drawable.round_edittext_bg);
|
|
265
|
+
searchInput.setPadding(24, 16, 24, 16);
|
|
266
|
+
searchInput.setSingleLine(true);
|
|
267
|
+
LinearLayout.LayoutParams searchParams = new LinearLayout.LayoutParams(
|
|
268
|
+
0, ViewGroup.LayoutParams.WRAP_CONTENT, 1);
|
|
269
|
+
searchParams.setMargins(0, 0, 8, 0);
|
|
270
|
+
searchInput.setLayoutParams(searchParams);
|
|
271
|
+
|
|
272
|
+
// 实时搜索(带防抖)
|
|
273
|
+
searchInput.addTextChangedListener(new TextWatcher() {
|
|
274
|
+
@Override
|
|
275
|
+
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
|
|
276
|
+
@Override
|
|
277
|
+
public void onTextChanged(CharSequence s, int start, int before, int count) {}
|
|
278
|
+
@Override
|
|
279
|
+
public void afterTextChanged(Editable s) {
|
|
280
|
+
// 移除之前的延迟任务,避免重复触发
|
|
281
|
+
if (searchRunnable != null) {
|
|
282
|
+
searchHandler.removeCallbacks(searchRunnable);
|
|
283
|
+
}
|
|
284
|
+
// 延迟300ms再搜索(用户停止输入后再执行)
|
|
285
|
+
searchRunnable = () -> performSearch();
|
|
286
|
+
searchHandler.postDelayed(searchRunnable, 300);
|
|
287
|
+
}
|
|
288
|
+
});
|
|
289
|
+
row1.addView(searchInput);
|
|
290
|
+
|
|
291
|
+
// 搜索结果计数
|
|
292
|
+
searchResultText = new TextView(this);
|
|
293
|
+
searchResultText.setText("0");
|
|
294
|
+
searchResultText.setTextColor(Color.parseColor("#888888"));
|
|
295
|
+
searchResultText.setTextSize(14);
|
|
296
|
+
searchResultText.setPadding(16, 0, 16, 0);
|
|
297
|
+
row1.addView(searchResultText);
|
|
298
|
+
|
|
299
|
+
// 关闭按钮
|
|
300
|
+
TextView closeBtn = createTextButton("×");
|
|
301
|
+
closeBtn.setTextSize(24);
|
|
302
|
+
closeBtn.setOnClickListener(v -> hideSearchPanel());
|
|
303
|
+
row1.addView(closeBtn);
|
|
304
|
+
|
|
305
|
+
panel.addView(row1);
|
|
306
|
+
|
|
307
|
+
// 第二行:上一个、下一个、替换、全部
|
|
308
|
+
LinearLayout row2 = new LinearLayout(this);
|
|
309
|
+
row2.setOrientation(LinearLayout.HORIZONTAL);
|
|
310
|
+
row2.setGravity(Gravity.CENTER_VERTICAL);
|
|
311
|
+
row2.setPadding(0, 8, 0, 0);
|
|
312
|
+
|
|
313
|
+
prevBtn = createTextButton("上个");
|
|
314
|
+
prevBtn.setOnClickListener(v -> findPrevious());
|
|
315
|
+
prevBtn.setEnabled(false);
|
|
316
|
+
row2.addView(prevBtn);
|
|
317
|
+
|
|
318
|
+
nextBtn = createTextButton("下个");
|
|
319
|
+
nextBtn.setOnClickListener(v -> findNext());
|
|
320
|
+
nextBtn.setEnabled(false);
|
|
321
|
+
row2.addView(nextBtn);
|
|
322
|
+
|
|
323
|
+
replaceBtn = createTextButton("替换");
|
|
324
|
+
replaceBtn.setOnClickListener(v -> replaceCurrent());
|
|
325
|
+
replaceBtn.setEnabled(false);
|
|
326
|
+
row2.addView(replaceBtn);
|
|
327
|
+
|
|
328
|
+
replaceAllBtn = createTextButton("全部");
|
|
329
|
+
replaceAllBtn.setOnClickListener(v -> replaceAll());
|
|
330
|
+
replaceAllBtn.setEnabled(false);
|
|
331
|
+
row2.addView(replaceAllBtn);
|
|
234
332
|
|
|
235
333
|
// 替换输入框
|
|
236
|
-
|
|
237
|
-
replaceInput.setHint("替换为
|
|
238
|
-
replaceInput.setText(lastReplaceText);
|
|
334
|
+
replaceInput = new EditText(this);
|
|
335
|
+
replaceInput.setHint("替换为");
|
|
239
336
|
replaceInput.setTextColor(Color.WHITE);
|
|
240
337
|
replaceInput.setHintTextColor(Color.GRAY);
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
.setNeutralButton("全部替换", null)
|
|
255
|
-
.create();
|
|
256
|
-
|
|
257
|
-
dialog.setOnShowListener(d -> {
|
|
258
|
-
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(v -> {
|
|
259
|
-
String text = searchInput.getText().toString();
|
|
260
|
-
if (!text.isEmpty()) {
|
|
261
|
-
lastSearchText = text;
|
|
262
|
-
if (regexCheckbox.isChecked()) {
|
|
263
|
-
editView.find(text);
|
|
264
|
-
} else {
|
|
265
|
-
editView.find(java.util.regex.Pattern.quote(text));
|
|
266
|
-
}
|
|
267
|
-
// 跳转到第一个匹配项
|
|
268
|
-
editView.next();
|
|
269
|
-
}
|
|
270
|
-
});
|
|
338
|
+
replaceInput.setBackgroundResource(R.drawable.round_edittext_bg);
|
|
339
|
+
replaceInput.setPadding(24, 16, 24, 16);
|
|
340
|
+
replaceInput.setSingleLine(true);
|
|
341
|
+
LinearLayout.LayoutParams replaceParams = new LinearLayout.LayoutParams(
|
|
342
|
+
0, ViewGroup.LayoutParams.WRAP_CONTENT, 1);
|
|
343
|
+
replaceParams.setMargins(8, 0, 0, 0);
|
|
344
|
+
replaceInput.setLayoutParams(replaceParams);
|
|
345
|
+
row2.addView(replaceInput);
|
|
346
|
+
|
|
347
|
+
panel.addView(row2);
|
|
348
|
+
|
|
349
|
+
return panel;
|
|
350
|
+
}
|
|
271
351
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
}
|
|
283
|
-
editView.next();
|
|
284
|
-
editView.replaceFirst(replace);
|
|
285
|
-
}
|
|
286
|
-
});
|
|
352
|
+
private TextView createTextButton(String text) {
|
|
353
|
+
TextView btn = new TextView(this);
|
|
354
|
+
btn.setText(text);
|
|
355
|
+
btn.setTextColor(Color.parseColor("#63B5F7"));
|
|
356
|
+
btn.setTextSize(14);
|
|
357
|
+
btn.setPadding(24, 12, 24, 12);
|
|
358
|
+
btn.setClickable(true);
|
|
359
|
+
btn.setFocusable(true);
|
|
360
|
+
return btn;
|
|
361
|
+
}
|
|
287
362
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
363
|
+
private void toggleSearchPanel() {
|
|
364
|
+
if (isSearchPanelVisible) {
|
|
365
|
+
hideSearchPanel();
|
|
366
|
+
} else {
|
|
367
|
+
showSearchPanel();
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
private void showSearchPanel() {
|
|
372
|
+
searchPanel.setVisibility(View.VISIBLE);
|
|
373
|
+
isSearchPanelVisible = true;
|
|
374
|
+
searchInput.requestFocus();
|
|
375
|
+
// 显示键盘
|
|
376
|
+
InputMethodManager imm = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
|
|
377
|
+
imm.showSoftInput(searchInput, InputMethodManager.SHOW_IMPLICIT);
|
|
378
|
+
|
|
379
|
+
// 如果有之前的搜索内容,重新搜索
|
|
380
|
+
if (!lastSearchText.isEmpty()) {
|
|
381
|
+
searchInput.setText(lastSearchText);
|
|
382
|
+
searchInput.setSelection(lastSearchText.length());
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
private void hideSearchPanel() {
|
|
387
|
+
searchPanel.setVisibility(View.GONE);
|
|
388
|
+
isSearchPanelVisible = false;
|
|
389
|
+
// 隐藏键盘
|
|
390
|
+
InputMethodManager imm = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
|
|
391
|
+
imm.hideSoftInputFromWindow(searchInput.getWindowToken(), 0);
|
|
392
|
+
// 清除高亮
|
|
393
|
+
editView.find("");
|
|
394
|
+
editView.postInvalidate();
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
private void performSearch() {
|
|
398
|
+
String text = searchInput.getText().toString();
|
|
399
|
+
lastSearchText = text;
|
|
400
|
+
|
|
401
|
+
if (text.isEmpty()) {
|
|
402
|
+
searchMatchCount = 0;
|
|
403
|
+
currentMatchIndex = 0;
|
|
404
|
+
searchResultText.setText("0/0");
|
|
405
|
+
editView.find("");
|
|
406
|
+
updateSearchButtonsState();
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// 执行搜索(使用普通文本,非正则)
|
|
411
|
+
editView.find(java.util.regex.Pattern.quote(text));
|
|
412
|
+
|
|
413
|
+
// 获取匹配数量
|
|
414
|
+
searchMatchCount = editView.getMatchCount();
|
|
415
|
+
currentMatchIndex = 0;
|
|
416
|
+
|
|
417
|
+
if (searchMatchCount > 0) {
|
|
418
|
+
// 跳转到第一个匹配
|
|
419
|
+
editView.next();
|
|
420
|
+
currentMatchIndex = 1;
|
|
421
|
+
searchResultText.setText(currentMatchIndex + "/" + searchMatchCount);
|
|
422
|
+
} else {
|
|
423
|
+
searchResultText.setText("0/0");
|
|
424
|
+
}
|
|
425
|
+
updateSearchButtonsState();
|
|
426
|
+
}
|
|
304
427
|
|
|
305
|
-
|
|
428
|
+
private void findNext() {
|
|
429
|
+
if (searchMatchCount > 0) {
|
|
430
|
+
editView.next();
|
|
431
|
+
currentMatchIndex++;
|
|
432
|
+
if (currentMatchIndex > searchMatchCount) {
|
|
433
|
+
currentMatchIndex = 1;
|
|
434
|
+
}
|
|
435
|
+
searchResultText.setText(currentMatchIndex + "/" + searchMatchCount);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
private void findPrevious() {
|
|
440
|
+
if (searchMatchCount > 0) {
|
|
441
|
+
editView.previous();
|
|
442
|
+
currentMatchIndex--;
|
|
443
|
+
if (currentMatchIndex < 1) {
|
|
444
|
+
currentMatchIndex = searchMatchCount;
|
|
445
|
+
}
|
|
446
|
+
searchResultText.setText(currentMatchIndex + "/" + searchMatchCount);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
private void replaceCurrent() {
|
|
451
|
+
if (searchMatchCount > 0 && !readOnly) {
|
|
452
|
+
String replacement = replaceInput.getText().toString();
|
|
453
|
+
editView.replaceFirst(replacement);
|
|
454
|
+
Toast.makeText(this, "已替换当前匹配项", Toast.LENGTH_SHORT).show();
|
|
455
|
+
performSearch(); // 重新搜索更新计数
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
private void replaceAll() {
|
|
460
|
+
if (searchMatchCount > 0 && !readOnly) {
|
|
461
|
+
String replacement = replaceInput.getText().toString();
|
|
462
|
+
int count = searchMatchCount;
|
|
463
|
+
editView.replaceAll(replacement);
|
|
464
|
+
Toast.makeText(this, "已替换 " + count + " 处", Toast.LENGTH_SHORT).show();
|
|
465
|
+
performSearch(); // 重新搜索更新计数
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
private void updateSearchButtonsState() {
|
|
470
|
+
boolean isEnabled = !searchInput.getText().toString().isEmpty() && searchMatchCount > 0;
|
|
471
|
+
prevBtn.setEnabled(isEnabled);
|
|
472
|
+
nextBtn.setEnabled(isEnabled);
|
|
473
|
+
replaceBtn.setEnabled(isEnabled && !readOnly);
|
|
474
|
+
replaceAllBtn.setEnabled(isEnabled && !readOnly);
|
|
475
|
+
|
|
476
|
+
// 更新按钮颜色
|
|
477
|
+
int enabledColor = Color.parseColor("#63B5F7");
|
|
478
|
+
int disabledColor = Color.parseColor("#555555");
|
|
479
|
+
prevBtn.setTextColor(isEnabled ? enabledColor : disabledColor);
|
|
480
|
+
nextBtn.setTextColor(isEnabled ? enabledColor : disabledColor);
|
|
481
|
+
replaceBtn.setTextColor((isEnabled && !readOnly) ? enabledColor : disabledColor);
|
|
482
|
+
replaceAllBtn.setTextColor((isEnabled && !readOnly) ? enabledColor : disabledColor);
|
|
306
483
|
}
|
|
307
484
|
|
|
308
485
|
private void showGotoLineDialog() {
|
|
@@ -1753,11 +1753,22 @@ public class EditView extends View {
|
|
|
1753
1753
|
if (!mReplaceList.isEmpty())
|
|
1754
1754
|
mReplaceList.clear();
|
|
1755
1755
|
|
|
1756
|
+
if (regex == null || regex.isEmpty()) {
|
|
1757
|
+
postInvalidate();
|
|
1758
|
+
return;
|
|
1759
|
+
}
|
|
1760
|
+
|
|
1756
1761
|
Matcher matcher = Pattern.compile(regex).matcher(mGapBuffer.toString());
|
|
1757
1762
|
|
|
1758
1763
|
while (matcher.find()) {
|
|
1759
1764
|
mReplaceList.add(new Pair<Integer, Integer>(matcher.start(), matcher.end()));
|
|
1760
1765
|
}
|
|
1766
|
+
postInvalidate();
|
|
1767
|
+
}
|
|
1768
|
+
|
|
1769
|
+
// Get number of matches
|
|
1770
|
+
public int getMatchCount() {
|
|
1771
|
+
return mReplaceList.size();
|
|
1761
1772
|
}
|
|
1762
1773
|
|
|
1763
1774
|
// Replace first match
|