francwalter

CBM - Java Code

Feb 27th, 2026
63
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Java 52.78 KB | Source Code | 0 0
  1. import android.app.Activity;
  2. import android.view.View;
  3. import android.view.ViewGroup;
  4. import android.widget.LinearLayout;
  5. import android.widget.ListView;
  6. import android.widget.BaseAdapter;
  7. import android.widget.EditText;
  8. import android.widget.Button;
  9. import android.widget.ImageButton;
  10. import android.widget.TextView;
  11. import android.graphics.Color;
  12. import android.text.InputType;
  13. import android.text.TextWatcher;
  14. import android.text.Editable;
  15. import android.content.ClipboardManager;
  16. import android.content.ClipData;
  17. import android.database.sqlite.SQLiteDatabase;
  18. import android.database.Cursor;
  19. import android.util.Patterns;
  20. import android.content.Intent;
  21. import android.net.Uri;
  22. import android.view.ViewTreeObserver;
  23. import android.view.KeyEvent;
  24. import android.widget.GridLayout;
  25. import android.view.Gravity;
  26. import android.widget.FrameLayout;
  27. import android.graphics.drawable.GradientDrawable;
  28. import android.util.DisplayMetrics;
  29. import android.view.WindowManager;
  30. import android.content.res.Configuration;
  31. import java.util.ArrayList;
  32. import java.util.HashMap;
  33. import java.util.function.Consumer;
  34. import java.util.concurrent.Callable;
  35. import com.joaomgcd.taskerm.action.java.JavaCodeException;
  36. import com.joaomgcd.taskerm.action.java.ClassImplementation;
  37. import android.view.MotionEvent;
  38. import android.widget.RadioButton;
  39. import android.widget.RadioGroup;
  40. import android.view.inputmethod.InputMethodManager;
  41. import android.widget.AbsListView;
  42.  
  43. /* === for unix timestrings === */
  44. import java.text.SimpleDateFormat;
  45. import java.util.Date;
  46. import java.util.Locale;
  47.  
  48.  
  49. /* === Get DB path === */
  50. dbPath = tasker.getVariable("CBM_dbpath");
  51. if (dbPath == null) {
  52.     throw new JavaCodeException("CBM_dbpath variable is not set");
  53. }
  54.  
  55. debugEnabled = "true".equals(tasker.getVariable("debug_enabled"));
  56. void logToFile(String message) {
  57.   if (!debugEnabled) return;
  58.   try {
  59.     logFile = "/storage/emulated/0/Tasker/log/clipboard_ui_debug.log";
  60.     tasker.log(message, logFile);
  61.   } catch(Exception e) {
  62.     tasker.log("File log error: " + e.getMessage());
  63.   }
  64. }
  65.  
  66. /* === Global state === */
  67. Activity activity;
  68. FrameLayout rootOverlay;
  69. LinearLayout mainLayout;
  70. ListView listView;
  71. EditText searchEdit;
  72. ImageButton filterBtn, settingsBtn, deleteAllBtn;
  73. LinearLayout buttonBar;
  74. TextView loadingText;
  75. ArrayList allClips = new ArrayList();
  76. ArrayList filteredClips = new ArrayList();
  77. Object adapter;
  78. Runnable applyTheme, updateDataList;
  79. boolean isStarFilterActive = false;
  80. String searchQuery = "";
  81. String currentTheme;
  82. HashMap colorPalette;
  83. View currentOverlay = null;
  84. int screenWidth, screenHeight, desiredWidth, desiredHeight;
  85.  
  86. /* === Get color palette based on theme === */
  87. getColorPalette(themeName) {
  88.     palette = new HashMap();
  89.    
  90.     if (themeName.equals("light")) {
  91.         palette.put("background", Color.parseColor("#F5F5F5"));
  92.         palette.put("card", Color.parseColor("#E0E0E0"));
  93.         palette.put("cardStarred", Color.parseColor("#FFF9C4"));
  94.         palette.put("overlay", Color.parseColor("#FAFAFA"));
  95.         palette.put("overlayDim", Color.parseColor("#CC000000"));
  96.         palette.put("textPrimary", Color.parseColor("#212121"));
  97.         palette.put("textSecondary", Color.parseColor("#757575"));
  98.         palette.put("hint", Color.parseColor("#9E9E9E"));
  99.         palette.put("input", Color.parseColor("#E0E0E0"));
  100.         palette.put("inputText", Color.parseColor("#424242"));
  101.         palette.put("star", Color.parseColor("#FFC107"));
  102.         palette.put("icon", Color.parseColor("#000000"));
  103.         palette.put("button", Color.parseColor("#E0E0E0"));
  104.         palette.put("buttonActive", Color.parseColor("#FFC107"));
  105.         palette.put("header", Color.parseColor("#E0E0E0"));
  106.     } else {
  107.         palette.put("background", Color.parseColor("#1E1E1E"));
  108.         palette.put("card", Color.parseColor("#2D2D2D"));
  109.         palette.put("cardStarred", Color.parseColor("#3D2D00"));
  110.         palette.put("overlay", Color.parseColor("#1E1E1E"));
  111.         palette.put("overlayDim", Color.parseColor("#CC000000"));
  112.         palette.put("textPrimary", Color.WHITE);
  113.         palette.put("textSecondary", Color.GRAY);
  114.         palette.put("hint", Color.GRAY);
  115.         palette.put("input", Color.parseColor("#2D2D2D"));
  116.         palette.put("inputText", Color.WHITE);
  117.         palette.put("star", Color.parseColor("#FFC107"));
  118.         palette.put("icon", Color.parseColor("#FFFFFF"));
  119.         palette.put("button", Color.parseColor("#2D2D2D"));
  120.         palette.put("buttonActive", Color.parseColor("#FFC107"));
  121.         palette.put("header", Color.parseColor("#2D2D2D"));
  122.     }
  123.    
  124.     return palette;
  125. }
  126.  
  127. /* === Detect system theme === */
  128. isSystemDarkMode() {
  129.     config = context.getResources().getConfiguration();
  130.     nightMode = config.uiMode & Configuration.UI_MODE_NIGHT_MASK;
  131.     return nightMode == Configuration.UI_MODE_NIGHT_YES;
  132. }
  133.  
  134. /* === Resolve theme (handles auto mode) === */
  135. resolveTheme(theme) {
  136.     if (theme == null || theme.isEmpty()) {
  137.         return "dark";
  138.     }
  139.     if (theme.equals("auto")) {
  140.         return isSystemDarkMode() ? "dark" : "light";
  141.     }
  142.     return theme;
  143. }
  144.  
  145. /* === Load clips from DB (Safe DB handling) === */
  146. loadClips = new Runnable() {
  147.     run() {
  148.         db = null;
  149.         cursor = null;
  150.         try {
  151.             db = SQLiteDatabase.openDatabase(dbPath, null, SQLiteDatabase.OPEN_READONLY);
  152.             cursor = db.rawQuery("SELECT rowid, text, star, timestamp FROM clip ORDER BY timestamp DESC", null);
  153.             temp = new ArrayList();
  154.            
  155.             // human readable time
  156.             sdf = new SimpleDateFormat("dd.MM.yyyy HH:mm", Locale.getDefault());
  157.  
  158.             while (cursor.moveToNext()) {
  159.                 clip = new Object[4];
  160.                 clip[0] = new Integer(cursor.getInt(0));
  161.                 clip[1] = cursor.getString(1);
  162.                 clip[2] = new Boolean(cursor.getInt(2) == 1);
  163.                
  164.                 // Unix-timestamp to human readable format
  165.                 rawTimestamp = cursor.getString(3);
  166.                 try {
  167.                     // SQLite Unix-Timestamps are often in seconds, Java needs msec (* 1000)
  168.                     long unixTime = Long.parseLong(rawTimestamp);
  169.                     // Falls der Wert kleiner als 10000000000L ist, sind es Sekunden
  170.                     if (unixTime < 10000000000L) unixTime *= 1000;
  171.                     clip[3] = sdf.format(new Date(unixTime));
  172.                 } catch (Exception e) {
  173.                     clip[3] = rawTimestamp; // Fallback, if not Unix-format
  174.                 }
  175.                
  176.                 temp.add(clip);
  177.             }
  178.             allClips = temp;
  179.         } catch (Exception e) {
  180.             logToFile("Load DB Error: " + e.getMessage());
  181.         } finally {
  182.             if (cursor != null) cursor.close();
  183.             if (db != null) db.close();
  184.         }
  185.         updateDataList.run();
  186.     }
  187. };
  188.  
  189. /* === Filter Data List Logic === */
  190. updateDataList = new Runnable() {
  191.     run() {
  192.         filteredClips.clear();
  193.         for (int i=0; i<allClips.size(); i++) {
  194.              Object[] data = (Object[]) allClips.get(i);
  195.              String clipText = (String)data[1];
  196.              boolean clipStar = ((Boolean)data[2]).booleanValue();
  197.  
  198.              if (isStarFilterActive && !clipStar) continue;
  199.              if (searchQuery.length() > 0 && clipText.toLowerCase().indexOf(searchQuery.toLowerCase()) == -1) continue;
  200.              
  201.              Object[] viewData = new Object[5]; // Size increased to 5
  202.              viewData[0] = data[0];
  203.              viewData[1] = data[1];
  204.              viewData[2] = data[2];
  205.              viewData[3] = new Integer(i);
  206.              viewData[4] = data[3]; // Pass timestamp forward
  207.              
  208.              filteredClips.add(viewData);
  209.         }
  210.        
  211.         if (adapter != null) {
  212.             adapter.notifyDataSetChanged();
  213.         }
  214.     }
  215. };
  216.  
  217. /* === Create rounded background === */
  218. createRoundedBackground(color, radius) {
  219.     shape = new GradientDrawable();
  220.     shape.setShape(GradientDrawable.RECTANGLE);
  221.     shape.setCornerRadius(radius);
  222.     shape.setColor(color);
  223.     return shape;
  224. }
  225.  
  226. /* === Create icon-only button === */
  227. createIconOnlyButton(ctx, iconName, bgColor, iconColor) {
  228.     btn = new ImageButton(ctx);
  229.     btn.setBackgroundColor(bgColor);
  230.     btn.setPadding(20, 20, 20, 20);
  231.     btn.setScaleType(android.widget.ImageView.ScaleType.CENTER);
  232.    
  233.     try {
  234.         resId = ctx.getResources().getIdentifier(iconName, "drawable", "net.dinglisch.android.taskerm");
  235.         if (resId != 0) {
  236.             drawable = ctx.getResources().getDrawable(resId);
  237.             if (drawable != null) {
  238.                 drawable.setTint(iconColor);
  239.                 btn.setImageDrawable(drawable);
  240.             }
  241.         }
  242.     } catch (Exception e) {
  243.         logToFile("Failed to load icon: " + e.getMessage());
  244.     }
  245.    
  246.     return btn;
  247. }
  248.  
  249. /* === Remove overlay === */
  250. removeOverlay(overlayView) {
  251.     ((ViewGroup) overlayView.getParent()).removeView(overlayView);
  252.     currentOverlay = null;
  253. }
  254.  
  255. /* === Get current DB trigger (Safe DB handling) === */
  256. getCurrentTrigger() {
  257.     result = new Object[2];
  258.     result[0] = "none";
  259.     result[1] = "";
  260.     db = null;
  261.     cursor = null;
  262.     try {
  263.         db = SQLiteDatabase.openDatabase(dbPath, null, SQLiteDatabase.OPEN_READONLY);
  264.         cursor = db.rawQuery("SELECT sql FROM sqlite_master WHERE type='trigger' AND name='clip_limit'", null);
  265.        
  266.         if (cursor.moveToFirst()) {
  267.             sql = cursor.getString(0);
  268.             if (sql.indexOf("datetime('now'") != -1) {
  269.                 startIdx = sql.indexOf("'-") + 2;
  270.                 endIdx = sql.indexOf(" days", startIdx);
  271.                 if (startIdx != -1 && endIdx != -1) {
  272.                     result[0] = "days";
  273.                     result[1] = sql.substring(startIdx, endIdx);
  274.                 }
  275.             } else if (sql.indexOf("LIMIT") != -1) {
  276.                 startIdx = sql.indexOf("LIMIT ") + 6;
  277.                 endIdx = sql.indexOf(")", startIdx);
  278.                 if (endIdx == -1) endIdx = sql.indexOf(";", startIdx);
  279.                 if (endIdx == -1) endIdx = sql.length();
  280.                
  281.                 if (startIdx != -1) {
  282.                     valueStr = sql.substring(startIdx, endIdx).trim();
  283.                     if (!valueStr.isEmpty()) {
  284.                         result[0] = "count";
  285.                         result[1] = valueStr;
  286.                     }
  287.                 }
  288.             }
  289.         }
  290.     } catch (Exception e) {
  291.         logToFile("Get trigger error: " + e.getMessage());
  292.     } finally {
  293.         if (cursor != null) cursor.close();
  294.         if (db != null) db.close();
  295.     }
  296.     return result;
  297. }
  298.  
  299. /* === Create clip detail overlay === */
  300. createClipDetailOverlay(clipId, clipText, clipStar, data, index) {
  301.     overlay = new FrameLayout(activity);
  302.     overlay.setBackgroundColor(((Integer)colorPalette.get("overlayDim")).intValue());
  303.     overlay.setClickable(true);
  304.     overlay.setFocusable(true);
  305.     overlay.setFocusableInTouchMode(true);
  306.     currentOverlay = overlay;
  307.  
  308.     panel = new LinearLayout(activity);
  309.     panel.setOrientation(LinearLayout.VERTICAL);
  310.     panel.setPadding(20, 20, 20, 20);
  311.     panelParams = new FrameLayout.LayoutParams(desiredWidth, ViewGroup.LayoutParams.WRAP_CONTENT);
  312.     panelParams.gravity = Gravity.CENTER;
  313.     panel.setLayoutParams(panelParams);
  314.     panelBg = ((Integer)colorPalette.get("overlay")).intValue();
  315.     panel.setBackground(createRoundedBackground(panelBg, 20));
  316.    
  317.     panelObserver = new ViewTreeObserver.OnGlobalLayoutListener() {
  318.         onGlobalLayout() {
  319.             if (panel.getHeight() > desiredHeight) {
  320.                 params = (FrameLayout.LayoutParams) panel.getLayoutParams();
  321.                 if (params.height == ViewGroup.LayoutParams.WRAP_CONTENT) {
  322.                     params.height = desiredHeight;
  323.                     panel.setLayoutParams(params);
  324.                 }
  325.             }
  326.             panel.getViewTreeObserver().removeOnGlobalLayoutListener(panelObserver);
  327.         }
  328.     };
  329.     panel.getViewTreeObserver().addOnGlobalLayoutListener(panelObserver);
  330.  
  331.     header = new LinearLayout(activity);
  332.     header.setOrientation(LinearLayout.HORIZONTAL);
  333.     header.setBackgroundColor(((Integer)colorPalette.get("header")).intValue());
  334.     header.setGravity(Gravity.CENTER_VERTICAL);
  335.  
  336.     // Title and Timestamp container
  337.     titleContainer = new LinearLayout(activity);
  338.     titleContainer.setOrientation(LinearLayout.VERTICAL);
  339.     titleContainer.setLayoutParams(new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1));
  340.     titleContainer.setPadding(20, 10, 20, 10);
  341.  
  342.     title = new TextView(activity);
  343.     title.setText("Clip Details");
  344.     title.setTextColor(((Integer)colorPalette.get("textPrimary")).intValue());
  345.     title.setTextSize(18);
  346.  
  347.     timestampView = new TextView(activity);
  348.     String ts = (String) data[4];
  349.     timestampView.setText(ts != null ? ts : "");
  350.     timestampView.setTextColor(((Integer)colorPalette.get("textSecondary")).intValue());
  351.     timestampView.setTextSize(12);
  352.  
  353.     titleContainer.addView(title);
  354.     titleContainer.addView(timestampView);
  355.    
  356.     closeBtn = createIconOnlyButton(activity, "mw_navigation_close", ((Integer)colorPalette.get("header")).intValue(), ((Integer)colorPalette.get("icon")).intValue());
  357.  
  358.     header.addView(titleContainer);
  359.     header.addView(closeBtn);
  360.     panel.addView(header);
  361.  
  362.     buttonGrid = new GridLayout(activity);
  363.     buttonGrid.setOrientation(GridLayout.HORIZONTAL);
  364.     buttonGrid.setPadding(0, 10, 0, 10);
  365.     buttonGrid.setUseDefaultMargins(true);
  366.  
  367.     btnIcons = new String[]{ "mw_content_content_copy", "mw_content_content_paste", "mw_social_share", "mw_editor_mode_edit", clipStar ? "mw_action_grade" : "mw_toggle_star_border", "mw_action_delete", "mw_content_save", "mw_navigation_cancel" };
  368.     btnColors = new int[]{ Color.parseColor("#4CAF50"), Color.parseColor("#FF9800"), Color.parseColor("#03A9F4"), Color.parseColor("#2196F3"), Color.parseColor("#FFC107"), Color.parseColor("#F44336"), Color.parseColor("#2196F3"), Color.parseColor("#757575") };
  369.  
  370.     btnRefs = new ImageButton[btnIcons.length];
  371.     for (int i = 0; i < btnIcons.length; i++) {
  372.         b = createIconOnlyButton(activity, btnIcons[i], btnColors[i], Color.WHITE);
  373.         glp = new GridLayout.LayoutParams();
  374.         glp.width = 0;
  375.         glp.columnSpec = GridLayout.spec(GridLayout.UNDEFINED, 1f);
  376.         glp.setMargins(5, 5, 5, 5);
  377.         b.setLayoutParams(glp);
  378.         buttonGrid.addView(b);
  379.         btnRefs[i] = b;
  380.     }
  381.  
  382.     copyBtn = btnRefs[0];
  383.     pasteBtn = btnRefs[1];
  384.     shareBtn = btnRefs[2];
  385.     editBtn = btnRefs[3];
  386.     starBtn = btnRefs[4];
  387.     deleteBtn = btnRefs[5];
  388.     saveBtn = btnRefs[6];
  389.     cancelBtn = btnRefs[7];
  390.  
  391.     saveBtn.setVisibility(View.GONE);
  392.     cancelBtn.setVisibility(View.GONE);
  393.    
  394.     panel.addView(buttonGrid);
  395.  
  396.     textScroll = new android.widget.ScrollView(activity);
  397.     textScrollParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
  398.     textScroll.setLayoutParams(textScrollParams);
  399.    
  400.     fullText = new EditText(activity);
  401.     fullText.setText(clipText);
  402.     fullText.setBackgroundColor(((Integer)colorPalette.get("input")).intValue());
  403.     fullText.setTextColor(((Integer)colorPalette.get("inputText")).intValue());
  404.     fullText.setTextSize(16);
  405.     fullText.setPadding(20, 20, 20, 30);
  406.     fullText.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_MULTI_LINE);
  407.     fullText.setFocusable(true);
  408.     fullText.setFocusableInTouchMode(false);
  409.     fullText.setClickable(false);
  410.     fullText.setCursorVisible(false);
  411.     fullText.setTextIsSelectable(true);
  412.     textScroll.addView(fullText);
  413.     panel.addView(textScroll);
  414.  
  415.     overlay.addView(panel);
  416.     rootOverlay.addView(overlay);
  417.  
  418.     final FrameLayout finalOverlay = overlay;
  419.     final LinearLayout finalPanel = panel;
  420.     final EditText finalFullText = fullText;
  421.    
  422.     overlay.post(new Runnable() {
  423.         run() { finalOverlay.requestFocus(); }
  424.     });
  425.    
  426.     overlay.setOnKeyListener(new View.OnKeyListener() {
  427.         onKey(View v, int keyCode, KeyEvent event) {
  428.             if (keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_UP) {
  429.                 removeOverlay(finalOverlay);
  430.                 return true;
  431.             }
  432.             return false;
  433.         }
  434.     });
  435.    
  436.     finalOverlay.setOnClickListener(new View.OnClickListener() {
  437.         onClick(View v) { removeOverlay(finalOverlay); }
  438.     });
  439.  
  440.     finalPanel.setClickable(true);
  441.     finalPanel.setOnClickListener(new View.OnClickListener() { onClick(View v) {} });
  442.  
  443.     closeBtn.setOnClickListener(new View.OnClickListener() {
  444.         onClick(View v) { removeOverlay(finalOverlay); }
  445.     });
  446.  
  447.     pasteBtn.setOnClickListener(new View.OnClickListener() {
  448.         onClick(View v) {
  449.             tasker.setVariable("clipboard_paste_text", finalFullText.getText().toString());
  450.             activity.finish();
  451.             variables = new HashMap();
  452.             variables.put("par1", "paste");
  453.             variables.put("clipboard_paste_text", finalFullText.getText().toString());
  454.             tasker.callTask("CBM - Helper", variables);
  455.         }
  456.     });
  457.  
  458.     copyBtn.setOnClickListener(new View.OnClickListener() {
  459.         onClick(View v) {
  460.             cm = (ClipboardManager) activity.getSystemService(Activity.CLIPBOARD_SERVICE);
  461.             cm.setPrimaryClip(ClipData.newPlainText("clip", finalFullText.getText().toString()));
  462.             removeOverlay(finalOverlay);
  463.         }
  464.     });
  465.    
  466.     shareBtn.setOnClickListener(new View.OnClickListener() {
  467.         onClick(View v) {
  468.             shareIntent = new Intent(Intent.ACTION_SEND);
  469.             shareIntent.setType("text/plain");
  470.             shareIntent.putExtra(Intent.EXTRA_TEXT, finalFullText.getText().toString());
  471.             activity.startActivity(Intent.createChooser(shareIntent, "Share clip via"));
  472.             activity.finish();
  473.         }
  474.     });
  475.  
  476.     editBtn.setOnClickListener(new View.OnClickListener() {
  477.         onClick(View v) {
  478.             panelParams = (FrameLayout.LayoutParams) finalPanel.getLayoutParams();
  479.             panelParams.height = (int)(screenHeight * 0.55);
  480.             panelParams.gravity = Gravity.TOP | Gravity.CENTER_HORIZONTAL;
  481.             finalPanel.setLayoutParams(panelParams);
  482.    
  483.             pasteBtn.setVisibility(View.GONE);
  484.             shareBtn.setVisibility(View.GONE);
  485.             editBtn.setVisibility(View.GONE);
  486.             starBtn.setVisibility(View.GONE);
  487.             deleteBtn.setVisibility(View.GONE);
  488.    
  489.             saveBtn.setVisibility(View.VISIBLE);
  490.             cancelBtn.setVisibility(View.VISIBLE);
  491.    
  492.             finalFullText.setFocusable(true);
  493.             finalFullText.setFocusableInTouchMode(true);
  494.             finalFullText.setClickable(true);
  495.             finalFullText.setCursorVisible(true);
  496.            
  497.             finalFullText.setOnKeyListener(new View.OnKeyListener() {
  498.                 onKey(View v, int keyCode, KeyEvent event) {
  499.                     if (keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_UP) {
  500.                         imm = (InputMethodManager) activity.getSystemService(Activity.INPUT_METHOD_SERVICE);
  501.                         imm.hideSoftInputFromWindow(finalFullText.getWindowToken(), 0);
  502.                         removeOverlay(finalOverlay);
  503.                         return true;
  504.                     }
  505.                     return false;
  506.                 }
  507.             });
  508.            
  509.             finalFullText.requestFocus();
  510.             finalFullText.setSelection(0);
  511.    
  512.             finalFullText.post(new Runnable() {
  513.                 run() {
  514.                     imm = (InputMethodManager) activity.getSystemService(Activity.INPUT_METHOD_SERVICE);
  515.                     imm.showSoftInput(finalFullText, InputMethodManager.SHOW_IMPLICIT);
  516.                 }
  517.             });
  518.         }
  519.     });
  520.  
  521.     saveBtn.setOnClickListener(new View.OnClickListener() {
  522.         onClick(View v) {
  523.             newText = finalFullText.getText().toString();
  524.             db = null;
  525.             try {
  526.                 db = SQLiteDatabase.openDatabase(dbPath, null, SQLiteDatabase.OPEN_READWRITE);
  527.                 db.execSQL("UPDATE clip SET text=? WHERE rowid=?", new Object[]{newText, clipId});
  528.                 Object[] originalData = (Object[]) allClips.get(index);
  529.                 originalData[1] = newText;
  530.             } catch (Exception e) {
  531.                 logToFile("Save error: " + e.getMessage());
  532.             } finally {
  533.                 if (db != null) db.close();
  534.             }
  535.            
  536.             removeOverlay(finalOverlay);
  537.             updateDataList.run();
  538.         }
  539.     });
  540.  
  541.     cancelBtn.setOnClickListener(new View.OnClickListener() {
  542.         onClick(View v) { removeOverlay(finalOverlay); }
  543.     });
  544.  
  545.     starBtn.setOnClickListener(new View.OnClickListener() {
  546.         onClick(View v) {
  547.             newStar = clipStar ? 0 : 1;
  548.             db = null;
  549.             try {
  550.                 db = SQLiteDatabase.openDatabase(dbPath, null, SQLiteDatabase.OPEN_READWRITE);
  551.                 db.execSQL("UPDATE clip SET star=? WHERE rowid=?", new Object[]{newStar, clipId});
  552.                 Object[] originalData = (Object[]) allClips.get(index);
  553.                 originalData[2] = new Boolean(newStar == 1);
  554.             } catch (Exception e) {
  555.                 logToFile("Star error: " + e.getMessage());
  556.             } finally {
  557.                 if (db != null) db.close();
  558.             }
  559.            
  560.             removeOverlay(finalOverlay);
  561.             updateDataList.run();
  562.         }
  563.     });
  564.  
  565.     deleteBtn.setOnClickListener(new View.OnClickListener() {
  566.         onClick(View v) {
  567.             db = null;
  568.             try {
  569.                 db = SQLiteDatabase.openDatabase(dbPath, null, SQLiteDatabase.OPEN_READWRITE);
  570.                 db.execSQL("DELETE FROM clip WHERE rowid=?", new Object[]{clipId});
  571.                 allClips.remove(index);
  572.             } catch (Exception e) {
  573.                 logToFile("Delete error: " + e.getMessage());
  574.             } finally {
  575.                 if (db != null) db.close();
  576.             }
  577.            
  578.             removeOverlay(finalOverlay);
  579.             updateDataList.run();
  580.         }
  581.     });
  582. }
  583.  
  584. /* === Create confirmation dialog === */
  585. createConfirmationDialog(title, message, onConfirm) {
  586.     confirmOverlay = new FrameLayout(activity);
  587.     confirmOverlay.setBackgroundColor(((Integer)colorPalette.get("overlayDim")).intValue());
  588.     confirmOverlay.setClickable(true);
  589.     confirmOverlay.setFocusableInTouchMode(true);
  590.     currentOverlay = confirmOverlay;
  591.  
  592.     confirmPanel = new LinearLayout(activity);
  593.     confirmPanel.setOrientation(LinearLayout.VERTICAL);
  594.     panelBg = ((Integer)colorPalette.get("overlay")).intValue();
  595.     confirmPanel.setBackgroundColor(panelBg);
  596.     confirmPanel.setPadding(30, 30, 30, 30);
  597.     confirmPanel.setLayoutParams(new FrameLayout.LayoutParams((int)(desiredWidth * 0.9), ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.CENTER));
  598.     confirmPanel.setBackground(createRoundedBackground(panelBg, 20));
  599.  
  600.     confirmTitle = new TextView(activity);
  601.     confirmTitle.setText(title);
  602.     confirmTitle.setTextColor(((Integer)colorPalette.get("textPrimary")).intValue());
  603.     confirmTitle.setTextSize(18);
  604.     confirmTitle.setPadding(0, 0, 0, 20);
  605.     confirmPanel.addView(confirmTitle);
  606.  
  607.     confirmMessage = new TextView(activity);
  608.     confirmMessage.setText(message);
  609.     confirmMessage.setTextColor(((Integer)colorPalette.get("textSecondary")).intValue());
  610.     confirmMessage.setTextSize(14);
  611.     confirmMessage.setPadding(0, 0, 0, 30);
  612.     confirmPanel.addView(confirmMessage);
  613.  
  614.     confirmButtonLayout = new LinearLayout(activity);
  615.     confirmButtonLayout.setOrientation(LinearLayout.HORIZONTAL);
  616.  
  617.     confirmDeleteBtn = new Button(activity);
  618.     confirmDeleteBtn.setText("Delete All");
  619.     confirmDeleteBtn.setTextColor(Color.WHITE);
  620.     confirmDeleteBtn.setBackgroundColor(Color.parseColor("#F44336"));
  621.     deleteParams = new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1);
  622.     deleteParams.setMargins(0, 0, 10, 0);
  623.     confirmDeleteBtn.setLayoutParams(deleteParams);
  624.  
  625.     confirmCancelBtn = new Button(activity);
  626.     confirmCancelBtn.setText("Cancel");
  627.     confirmCancelBtn.setTextColor(Color.WHITE);
  628.     confirmCancelBtn.setBackgroundColor(Color.parseColor("#757575"));
  629.     confirmCancelBtn.setLayoutParams(new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1));
  630.  
  631.     confirmButtonLayout.addView(confirmDeleteBtn);
  632.     confirmButtonLayout.addView(confirmCancelBtn);
  633.     confirmPanel.addView(confirmButtonLayout);
  634.  
  635.     confirmOverlay.addView(confirmPanel);
  636.     rootOverlay.addView(confirmOverlay);
  637.  
  638.     final FrameLayout finalConfirmOverlay = confirmOverlay;
  639.     final Runnable finalOnConfirm = onConfirm;
  640.  
  641.     confirmCancelBtn.setOnClickListener(new View.OnClickListener() {
  642.         onClick(View v) { removeOverlay(finalConfirmOverlay); }
  643.     });
  644.  
  645.     confirmDeleteBtn.setOnClickListener(new View.OnClickListener() {
  646.         onClick(View v) {
  647.             finalOnConfirm.run();
  648.             removeOverlay(finalConfirmOverlay);
  649.         }
  650.     });
  651. }
  652.  
  653. /* === Rebuild button bar with current theme === */
  654. rebuildButtonBar() {
  655.     buttonBar.removeAllViews();
  656.     buttonColor = ((Integer)colorPalette.get("button")).intValue();
  657.     iconColor = ((Integer)colorPalette.get("icon")).intValue();
  658.    
  659.     filterBtn = createIconOnlyButton(activity, "mw_action_grade", buttonColor, iconColor);
  660.     filterParams = new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1);
  661.     filterParams.setMargins(0, 0, 5, 0);
  662.     filterBtn.setLayoutParams(filterParams);
  663.  
  664.     settingsBtn = createIconOnlyButton(activity, "mw_action_settings", buttonColor, iconColor);
  665.     settingsParams = new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1);
  666.     settingsParams.setMargins(5, 0, 5, 0);
  667.     settingsBtn.setLayoutParams(settingsParams);
  668.  
  669.     deleteAllBtn = createIconOnlyButton(activity, "mw_content_delete_sweep", buttonColor, iconColor);
  670.     deleteAllParams = new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1);
  671.     deleteAllParams.setMargins(5, 0, 0, 0);
  672.     deleteAllBtn.setLayoutParams(deleteAllParams);
  673.  
  674.     buttonBar.addView(filterBtn);
  675.     buttonBar.addView(settingsBtn);
  676.     buttonBar.addView(deleteAllBtn);
  677.    
  678.     filterBtn.setOnClickListener(new View.OnClickListener() {
  679.         onClick(View v) {
  680.             isStarFilterActive = !isStarFilterActive;
  681.             filterBtn.setBackgroundColor(isStarFilterActive ? ((Integer)colorPalette.get("buttonActive")).intValue() : ((Integer)colorPalette.get("button")).intValue());
  682.             updateDataList.run();
  683.         }
  684.     });
  685.    
  686.     deleteAllBtn.setOnClickListener(new View.OnClickListener() {
  687.         onClick(View v) {
  688.             createConfirmationDialog(
  689.                 "Delete All Unstarred Clips?",
  690.                 "This will permanently delete all clips that are not starred.",
  691.                 new Runnable() {
  692.                     run() {
  693.                         db = null;
  694.                         try {
  695.                             db = SQLiteDatabase.openDatabase(dbPath, null, SQLiteDatabase.OPEN_READWRITE);
  696.                             db.execSQL("DELETE FROM clip WHERE star = 0");
  697.                         } catch (Exception e) {
  698.                             logToFile("Delete all error: " + e.getMessage());
  699.                         } finally {
  700.                             if (db != null) db.close();
  701.                         }
  702.                         loadClips.run();
  703.                         logToFile("Deleted all unstarred clips");
  704.                     }
  705.                 }
  706.             );
  707.         }
  708.     });
  709.    
  710.     /* === Settings button === */
  711.     settingsBtn.setOnClickListener(new View.OnClickListener() {
  712.         onClick(View v) {
  713.             currentTrigger = getCurrentTrigger();
  714.             currentType = (String) currentTrigger[0];
  715.             currentValue = (String) currentTrigger[1];
  716.            
  717.             settingsOverlay = new FrameLayout(activity);
  718.             settingsOverlay.setBackgroundColor(((Integer)colorPalette.get("overlayDim")).intValue());
  719.             settingsOverlay.setClickable(true);
  720.             settingsOverlay.setFocusableInTouchMode(true);
  721.             currentOverlay = settingsOverlay;
  722.  
  723.             settingsPanel = new LinearLayout(activity);
  724.             settingsPanel.setOrientation(LinearLayout.VERTICAL);
  725.             panelBg = ((Integer)colorPalette.get("overlay")).intValue();
  726.             settingsPanel.setBackgroundColor(panelBg);
  727.             settingsPanel.setPadding(30, 30, 30, 30);
  728.             settingsPanel.setLayoutParams(new FrameLayout.LayoutParams(desiredWidth, ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.CENTER));
  729.             settingsPanel.setBackground(createRoundedBackground(panelBg, 20));
  730.  
  731.             settingsHeader = new LinearLayout(activity);
  732.             settingsHeader.setOrientation(LinearLayout.HORIZONTAL);
  733.             settingsHeader.setBackgroundColor(((Integer)colorPalette.get("header")).intValue());
  734.  
  735.             settingsTitle = new TextView(activity);
  736.             settingsTitle.setText("Settings");
  737.             settingsTitle.setTextColor(((Integer)colorPalette.get("textPrimary")).intValue());
  738.             settingsTitle.setTextSize(18);
  739.             settingsTitle.setPadding(20, 20, 20, 20);
  740.             settingsTitle.setLayoutParams(new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1));
  741.  
  742.             settingsCloseBtn = createIconOnlyButton(activity, "mw_navigation_close", ((Integer)colorPalette.get("header")).intValue(), ((Integer)colorPalette.get("icon")).intValue());
  743.  
  744.             settingsHeader.addView(settingsTitle);
  745.             settingsHeader.addView(settingsCloseBtn);
  746.             settingsPanel.addView(settingsHeader);
  747.  
  748.             themeLabel = new TextView(activity);
  749.             themeLabel.setText("Theme:");
  750.             themeLabel.setTextColor(((Integer)colorPalette.get("textPrimary")).intValue());
  751.             themeLabel.setTextSize(16);
  752.             themeLabel.setPadding(0, 20, 0, 10);
  753.             settingsPanel.addView(themeLabel);
  754.  
  755.             themeGroup = new RadioGroup(activity);
  756.             themeGroup.setOrientation(RadioGroup.VERTICAL);
  757.  
  758.             darkRadio = new RadioButton(activity);
  759.             darkRadio.setText("Dark");
  760.             darkRadio.setTextColor(((Integer)colorPalette.get("textPrimary")).intValue());
  761.             darkRadio.setButtonTintList(android.content.res.ColorStateList.valueOf(((Integer)colorPalette.get("textPrimary")).intValue()));
  762.             darkRadio.setId(11);
  763.  
  764.             lightRadio = new RadioButton(activity);
  765.             lightRadio.setText("Light");
  766.             lightRadio.setTextColor(((Integer)colorPalette.get("textPrimary")).intValue());
  767.             lightRadio.setButtonTintList(android.content.res.ColorStateList.valueOf(((Integer)colorPalette.get("textPrimary")).intValue()));
  768.             lightRadio.setId(12);
  769.  
  770.             autoRadio = new RadioButton(activity);
  771.             autoRadio.setText("Auto (System)");
  772.             autoRadio.setTextColor(((Integer)colorPalette.get("textPrimary")).intValue());
  773.             autoRadio.setButtonTintList(android.content.res.ColorStateList.valueOf(((Integer)colorPalette.get("textPrimary")).intValue()));
  774.             autoRadio.setId(13);
  775.  
  776.             themeGroup.addView(darkRadio);
  777.             themeGroup.addView(lightRadio);
  778.             themeGroup.addView(autoRadio);
  779.             settingsPanel.addView(themeGroup);
  780.  
  781.             if (currentTheme.equals("light")) {
  782.                 themeGroup.check(12);
  783.             } else if (currentTheme.equals("auto")) {
  784.                 themeGroup.check(13);
  785.             } else {
  786.                 themeGroup.check(11);
  787.             }
  788.  
  789.             divider = new View(activity);
  790.             divider.setBackgroundColor(((Integer)colorPalette.get("textSecondary")).intValue());
  791.             dividerParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 2);
  792.             dividerParams.setMargins(0, 20, 0, 20);
  793.             divider.setLayoutParams(dividerParams);
  794.             settingsPanel.addView(divider);
  795.  
  796.             limitTypeLabel = new TextView(activity);
  797.             limitTypeLabel.setText("Database Limit Type:");
  798.             limitTypeLabel.setTextColor(((Integer)colorPalette.get("textPrimary")).intValue());
  799.             limitTypeLabel.setTextSize(16);
  800.             limitTypeLabel.setPadding(0, 0, 0, 10);
  801.             settingsPanel.addView(limitTypeLabel);
  802.  
  803.             limitTypeGroup = new RadioGroup(activity);
  804.             limitTypeGroup.setOrientation(RadioGroup.VERTICAL);
  805.  
  806.             noneRadio = new RadioButton(activity);
  807.             noneRadio.setText("No Limit");
  808.             noneRadio.setTextColor(((Integer)colorPalette.get("textPrimary")).intValue());
  809.             noneRadio.setButtonTintList(android.content.res.ColorStateList.valueOf(((Integer)colorPalette.get("textPrimary")).intValue()));
  810.             noneRadio.setId(1);
  811.  
  812.             daysRadio = new RadioButton(activity);
  813.             daysRadio.setText("By Days");
  814.             daysRadio.setTextColor(((Integer)colorPalette.get("textPrimary")).intValue());
  815.             daysRadio.setButtonTintList(android.content.res.ColorStateList.valueOf(((Integer)colorPalette.get("textPrimary")).intValue()));
  816.             daysRadio.setId(2);
  817.  
  818.             countRadio = new RadioButton(activity);
  819.             countRadio.setText("By Count");
  820.             countRadio.setTextColor(((Integer)colorPalette.get("textPrimary")).intValue());
  821.             countRadio.setButtonTintList(android.content.res.ColorStateList.valueOf(((Integer)colorPalette.get("textPrimary")).intValue()));
  822.             countRadio.setId(3);
  823.  
  824.             limitTypeGroup.addView(noneRadio);
  825.             limitTypeGroup.addView(daysRadio);
  826.             limitTypeGroup.addView(countRadio);
  827.             settingsPanel.addView(limitTypeGroup);
  828.  
  829.             if (currentType.equals("days")) {
  830.                 limitTypeGroup.check(2);
  831.             } else if (currentType.equals("count")) {
  832.                 limitTypeGroup.check(3);
  833.             } else {
  834.                 limitTypeGroup.check(1);
  835.             }
  836.  
  837.             valueLabel = new TextView(activity);
  838.             valueLabel.setText("Limit Value:");
  839.             valueLabel.setTextColor(((Integer)colorPalette.get("textPrimary")).intValue());
  840.             valueLabel.setTextSize(16);
  841.             valueLabel.setPadding(0, 20, 0, 10);
  842.             settingsPanel.addView(valueLabel);
  843.  
  844.             valueInput = new EditText(activity);
  845.             valueInput.setHint("Enter value...");
  846.             valueInput.setTextColor(((Integer)colorPalette.get("inputText")).intValue());
  847.             valueInput.setHintTextColor(((Integer)colorPalette.get("hint")).intValue());
  848.             valueInput.setBackgroundColor(((Integer)colorPalette.get("input")).intValue());
  849.             valueInput.setPadding(20, 20, 20, 20);
  850.             valueInput.setInputType(InputType.TYPE_CLASS_NUMBER);
  851.             if (!currentValue.isEmpty()) {
  852.                 valueInput.setText(currentValue);
  853.             }
  854.             settingsPanel.addView(valueInput);
  855.  
  856.             statusLabel = new TextView(activity);
  857.             statusLabel.setTextColor(((Integer)colorPalette.get("star")).intValue());
  858.             statusLabel.setTextSize(14);
  859.             statusLabel.setPadding(0, 20, 0, 0);
  860.             if (currentType.equals("none")) {
  861.                 statusLabel.setText("Current: No limit active");
  862.             } else if (currentType.equals("days")) {
  863.                 statusLabel.setText("Current: Delete clips older than " + currentValue + " days (starred excluded)");
  864.             } else if (currentType.equals("count")) {
  865.                 statusLabel.setText("Current: Keep only latest " + currentValue + " clips (starred excluded)");
  866.             }
  867.             settingsPanel.addView(statusLabel);
  868.  
  869.             settingsButtonLayout = new LinearLayout(activity);
  870.             settingsButtonLayout.setOrientation(LinearLayout.HORIZONTAL);
  871.             settingsButtonLayout.setPadding(0, 30, 0, 0);
  872.  
  873.             saveSettingsBtn = createIconOnlyButton(activity, "mw_content_save", Color.parseColor("#2196F3"), Color.WHITE);
  874.             saveSettingsParams = new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1);
  875.             saveSettingsParams.setMargins(0, 0, 10, 0);
  876.             saveSettingsBtn.setLayoutParams(saveSettingsParams);
  877.  
  878.             cancelSettingsBtn = createIconOnlyButton(activity, "mw_navigation_cancel", Color.parseColor("#757575"), Color.WHITE);
  879.             cancelSettingsBtn.setLayoutParams(new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1));
  880.  
  881.             settingsButtonLayout.addView(saveSettingsBtn);
  882.             settingsButtonLayout.addView(cancelSettingsBtn);
  883.             settingsPanel.addView(settingsButtonLayout);
  884.  
  885.             settingsOverlay.addView(settingsPanel);
  886.             rootOverlay.addView(settingsOverlay);
  887.  
  888.             final FrameLayout finalSettingsOverlay = settingsOverlay;
  889.             final RadioGroup finalThemeGroup = themeGroup;
  890.             final RadioGroup finalLimitTypeGroup = limitTypeGroup;
  891.             final EditText finalValueInput = valueInput;
  892.  
  893.             settingsOverlay.setOnKeyListener(new View.OnKeyListener() {
  894.                 onKey(View v, int keyCode, KeyEvent event) {
  895.                     if (keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_UP) {
  896.                         removeOverlay(finalSettingsOverlay);
  897.                         return true;
  898.                     }
  899.                     return false;
  900.                 }
  901.             });
  902.  
  903.             settingsOverlay.requestFocus();
  904.  
  905.             settingsCloseBtn.setOnClickListener(new View.OnClickListener() {
  906.                 onClick(View v) { removeOverlay(finalSettingsOverlay); }
  907.             });
  908.  
  909.             cancelSettingsBtn.setOnClickListener(new View.OnClickListener() {
  910.                 onClick(View v) { removeOverlay(finalSettingsOverlay); }
  911.             });
  912.  
  913.             saveSettingsBtn.setOnClickListener(new View.OnClickListener() {
  914.                 onClick(View v) {
  915.                     themeId = finalThemeGroup.getCheckedRadioButtonId();
  916.                     newTheme = "dark";
  917.                     if (themeId == 12) {
  918.                         newTheme = "light";
  919.                     } else if (themeId == 13) {
  920.                         newTheme = "auto";
  921.                     }
  922.                    
  923.                     themeChanged = !newTheme.equals(currentTheme);
  924.                     if (themeChanged) {
  925.                         tasker.setVariable("theme", newTheme);
  926.                         currentTheme = newTheme;
  927.                     }
  928.                    
  929.                     selectedId = finalLimitTypeGroup.getCheckedRadioButtonId();
  930.                     value = finalValueInput.getText().toString().trim();
  931.                    
  932.                     db = null;
  933.                     try {
  934.                         db = SQLiteDatabase.openDatabase(dbPath, null, SQLiteDatabase.OPEN_READWRITE);
  935.                         db.execSQL("DROP TRIGGER IF EXISTS clip_limit");
  936.                        
  937.                         if (selectedId == 1) {
  938.                             removeOverlay(finalSettingsOverlay);
  939.                             if (themeChanged) applyTheme.run();
  940.                             logToFile("Database limit removed");
  941.                             return;
  942.                         }
  943.                        
  944.                         if (value.isEmpty()) {
  945.                             logToFile("Please enter a value for the limit");
  946.                             return;
  947.                         }
  948.                        
  949.                         if (selectedId == 2) {
  950.                             triggerSql = "CREATE TRIGGER IF NOT EXISTS clip_limit AFTER INSERT ON clip " +
  951.                                         "BEGIN " +
  952.                                         "DELETE FROM clip WHERE star = 0 AND timestamp < strftime('%s', datetime('now', '-" + value + " days')); " +
  953.                                         "END;";
  954.                             db.execSQL(triggerSql);
  955.                             logToFile("Set limit to " + value + " days");
  956.                         } else if (selectedId == 3) {
  957.                             triggerSql = "CREATE TRIGGER IF NOT EXISTS clip_limit AFTER INSERT ON clip " +
  958.                                         "BEGIN " +
  959.                                         "DELETE FROM clip WHERE star = 0 AND rowid NOT IN (" +
  960.                                         "SELECT rowid FROM clip WHERE star = 0 ORDER BY timestamp DESC LIMIT " + value + "); " +
  961.                                         "END;";
  962.                             db.execSQL(triggerSql);
  963.                             logToFile("Set limit to " + value + " clips");
  964.                         }
  965.                     } catch (Exception e) {
  966.                         logToFile("Save settings error: " + e.getMessage());
  967.                     } finally {
  968.                         if (db != null) db.close();
  969.                     }
  970.                    
  971.                     removeOverlay(finalSettingsOverlay);
  972.                     if (themeChanged) applyTheme.run();
  973.                 }
  974.             });
  975.         }
  976.     });
  977.    
  978.     if (isStarFilterActive) {
  979.         filterBtn.setBackgroundColor(((Integer)colorPalette.get("buttonActive")).intValue());
  980.     }
  981. }
  982.  
  983. /* === Helper for Adapter: Create View (Fixed Memory Leak) === */
  984. View createListItemView(ViewGroup parent) {
  985.     card = new LinearLayout(activity);
  986.     card.setOrientation(LinearLayout.VERTICAL);
  987.     card.setPadding(20, 20, 20, 20);
  988.     lp = new AbsListView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
  989.     card.setLayoutParams(lp);
  990.  
  991.     txt = new TextView(activity);
  992.     txt.setTextSize(16);
  993.     txt.setMaxLines(10);
  994.     txt.setEllipsize(android.text.TextUtils.TruncateAt.END);
  995.     card.addView(txt);
  996.    
  997.     indicator = new TextView(activity);
  998.     indicator.setText("...MORE");
  999.     indicator.setTextSize(12);
  1000.     indicator.setPadding(0, 5, 0, 0);
  1001.     indicator.setVisibility(View.GONE);
  1002.    
  1003.     indicatorParams = new LinearLayout.LayoutParams(
  1004.         ViewGroup.LayoutParams.WRAP_CONTENT,
  1005.         ViewGroup.LayoutParams.WRAP_CONTENT
  1006.     );
  1007.     indicatorParams.gravity = Gravity.CENTER_HORIZONTAL;
  1008.     indicator.setLayoutParams(indicatorParams);
  1009.     card.addView(indicator);
  1010.  
  1011.     Object[] holder = new Object[4];
  1012.     holder[0] = txt;
  1013.     holder[1] = indicator;
  1014.     holder[2] = card;
  1015.     holder[3] = null;
  1016.     card.setTag(holder);
  1017.    
  1018.     card.setOnTouchListener(new View.OnTouchListener() {
  1019.         float startX, startY;
  1020.         boolean isLongPress = false;
  1021.         boolean hasMoved = false;
  1022.         Runnable longPressRunnable;
  1023.  
  1024.         onTouch(View v, MotionEvent event) {
  1025.             Object[] tagHolder = (Object[]) v.getTag();
  1026.             if (tagHolder == null || tagHolder[3] == null) return false;
  1027.            
  1028.             Object[] data = (Object[]) tagHolder[3];
  1029.             int clipId = ((Integer)data[0]).intValue();
  1030.             String clipText = (String)data[1];
  1031.             boolean clipStar = ((Boolean)data[2]).booleanValue();
  1032.             int originalIndex = ((Integer)data[3]).intValue();
  1033.  
  1034.             switch (event.getAction()) {
  1035.                 case MotionEvent.ACTION_DOWN:
  1036.                     startX = event.getX();
  1037.                     startY = event.getY();
  1038.                     isLongPress = false;
  1039.                     hasMoved = false;
  1040.                     longPressRunnable = new Runnable() {
  1041.                         run() {
  1042.                             if (!hasMoved) {
  1043.                                 isLongPress = true;
  1044.                                 v.performHapticFeedback(android.view.HapticFeedbackConstants.LONG_PRESS);
  1045.                                 cm = (ClipboardManager) activity.getSystemService(Activity.CLIPBOARD_SERVICE);
  1046.                                 cm.setPrimaryClip(ClipData.newPlainText("clip", clipText));
  1047.                                 activity.finish();
  1048.                             }
  1049.                         }
  1050.                     };
  1051.                     v.postDelayed(longPressRunnable, 500);
  1052.                     return true;
  1053.                 case MotionEvent.ACTION_MOVE:
  1054.                     if (Math.abs(event.getX() - startX) > 10 || Math.abs(event.getY() - startY) > 10) {
  1055.                         hasMoved = true;
  1056.                         if (longPressRunnable != null) v.removeCallbacks(longPressRunnable);
  1057.                     }
  1058.                     return true;
  1059.                 case MotionEvent.ACTION_UP:
  1060.                 case MotionEvent.ACTION_CANCEL:
  1061.                     if (longPressRunnable != null) v.removeCallbacks(longPressRunnable);
  1062.                     if (event.getAction() == MotionEvent.ACTION_UP && !isLongPress && !hasMoved) {
  1063.                         createClipDetailOverlay(clipId, clipText, clipStar, data, originalIndex);
  1064.                     }
  1065.                     return true;
  1066.             }
  1067.             return false;
  1068.         }
  1069.     });
  1070.    
  1071.     return card;
  1072. }
  1073.  
  1074. /* === Helper for Adapter: Bind View === */
  1075. void bindListItemView(View view, int position) {
  1076.     Object[] holder = (Object[]) view.getTag();
  1077.     TextView txt = (TextView) holder[0];
  1078.     TextView indicator = (TextView) holder[1];
  1079.     LinearLayout card = (LinearLayout) holder[2];
  1080.  
  1081.     Object[] data = (Object[]) filteredClips.get(position);
  1082.     holder[3] = data;
  1083.  
  1084.     String clipText = (String)data[1];
  1085.     boolean clipStar = ((Boolean)data[2]).booleanValue();
  1086.  
  1087.     cardColor = clipStar ? ((Integer)colorPalette.get("cardStarred")).intValue() : ((Integer)colorPalette.get("card")).intValue();
  1088.     card.setBackground(createRoundedBackground(cardColor, 15));
  1089.    
  1090.     txt.setTextColor(((Integer)colorPalette.get("textPrimary")).intValue());
  1091.     indicator.setTextColor(((Integer)colorPalette.get("textSecondary")).intValue());
  1092.  
  1093.     txt.setText(clipText);
  1094.    
  1095.     if (clipText.length() > 300 || clipText.split("\n").length > 8) {
  1096.         indicator.setVisibility(View.VISIBLE);
  1097.     } else {
  1098.         indicator.setVisibility(View.GONE);
  1099.     }
  1100. }
  1101.  
  1102. /* === Main activity UI === */
  1103. activityConsumer = new Consumer() {
  1104.     accept(Object actObj) {
  1105.         activity = (Activity) actObj;
  1106.  
  1107.         metrics = new DisplayMetrics();
  1108.         activity.getWindowManager().getDefaultDisplay().getMetrics(metrics);
  1109.         screenWidth = metrics.widthPixels;
  1110.         screenHeight = metrics.heightPixels;
  1111.         desiredWidth = (int)(screenWidth * 0.9);
  1112.         desiredHeight = (int)(screenHeight * 0.8);
  1113.  
  1114.         savedTheme = tasker.getVariable("theme");
  1115.         if (savedTheme == null || savedTheme.isEmpty()) savedTheme = "dark";
  1116.         currentTheme = savedTheme;
  1117.         resolvedTheme = resolveTheme(savedTheme);
  1118.         colorPalette = getColorPalette(resolvedTheme);
  1119.  
  1120.         window = activity.getWindow();
  1121.         window.setLayout(WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.MATCH_PARENT);
  1122.         layoutParams = window.getAttributes();
  1123.         layoutParams.dimAmount = 0.7f;
  1124.         window.setAttributes(layoutParams);
  1125.         window.addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
  1126.         window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN);
  1127.  
  1128.         rootOverlay = new FrameLayout(activity);
  1129.         rootOverlay.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
  1130.         rootOverlay.setClickable(true);
  1131.         rootOverlay.setFocusableInTouchMode(true);
  1132.        
  1133.         rootOverlay.setOnKeyListener(new View.OnKeyListener() {
  1134.             onKey(View v, int keyCode, KeyEvent event) {
  1135.                 if (keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_UP) {
  1136.                     if (currentOverlay != null) {
  1137.                         removeOverlay(currentOverlay);
  1138.                     } else {
  1139.                         activity.finish();
  1140.                     }
  1141.                     return true;
  1142.                 }
  1143.                 return false;
  1144.             }
  1145.         });
  1146.  
  1147.         rootOverlay.setOnClickListener(new View.OnClickListener() {
  1148.             onClick(View v) { activity.finish(); }
  1149.         });
  1150.  
  1151.         mainLayout = new LinearLayout(activity);
  1152.         mainLayout.setOrientation(LinearLayout.VERTICAL);
  1153.         bgColor = ((Integer)colorPalette.get("background")).intValue();
  1154.         mainLayout.setBackgroundColor(bgColor);
  1155.         mainLayout.setPadding(20, 20, 20, 20);
  1156.         mainLayout.setBackground(createRoundedBackground(bgColor, 20));
  1157.        
  1158.         mainLayoutParams = new FrameLayout.LayoutParams(desiredWidth, desiredHeight);
  1159.         mainLayoutParams.gravity = Gravity.CENTER;
  1160.         mainLayout.setLayoutParams(mainLayoutParams);
  1161.         mainLayout.setClickable(true);
  1162.  
  1163.         buttonBar = new LinearLayout(activity);
  1164.         buttonBar.setOrientation(LinearLayout.HORIZONTAL);
  1165.         buttonBar.setPadding(0, 0,0, 15);
  1166.         rebuildButtonBar();
  1167.         mainLayout.addView(buttonBar);
  1168.  
  1169.         searchEdit = new EditText(activity);
  1170.         searchEdit.setHint("Search clips...");
  1171.         searchEdit.setTextColor(((Integer)colorPalette.get("inputText")).intValue());
  1172.         searchEdit.setHintTextColor(((Integer)colorPalette.get("hint")).intValue());
  1173.         searchEdit.setBackgroundColor(((Integer)colorPalette.get("input")).intValue());
  1174.         searchEdit.setPadding(20, 20, 20, 20);
  1175.         searchParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
  1176.         searchParams.setMargins(0, 0, 0, 20);
  1177.         searchEdit.setLayoutParams(searchParams);
  1178.         mainLayout.addView(searchEdit);
  1179.  
  1180.         loadingText = new TextView(activity);
  1181.         loadingText.setText("Loading clips...");
  1182.         loadingText.setTextColor(((Integer)colorPalette.get("textPrimary")).intValue());
  1183.         loadingText.setPadding(10, 40, 10, 20);
  1184.         mainLayout.addView(loadingText);
  1185.        
  1186.         listView = new ListView(activity);
  1187.         listView.setDivider(null);
  1188.         listView.setDividerHeight(20);
  1189.         listView.setClipToPadding(false);
  1190.         listView.setScrollBarStyle(View.SCROLLBARS_INSIDE_OVERLAY);
  1191.        
  1192.         adapter = tasker.implementClass(BaseAdapter.class, new ClassImplementation() {
  1193.             run(Callable superCaller, String methodName, Object[] args) {
  1194.                 if (methodName.equals("getCount")) return filteredClips.size();
  1195.                 if (methodName.equals("getItem")) return filteredClips.get(((Integer)args[0]).intValue());
  1196.                 if (methodName.equals("getItemId")) return (long)((Integer)args[0]).intValue();
  1197.                 if (methodName.equals("getView")) {
  1198.                     int position = ((Integer)args[0]).intValue();
  1199.                     View convertView = (View)args[1];
  1200.                     ViewGroup parent = (ViewGroup)args[2];
  1201.                    
  1202.                     if (convertView == null) convertView = createListItemView(parent);
  1203.                     bindListItemView(convertView, position);
  1204.                     return convertView;
  1205.                 }
  1206.                 return superCaller.call();
  1207.             }
  1208.         });
  1209.        
  1210.         listView.setAdapter(adapter);
  1211.         listView.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 0, 1));
  1212.        
  1213.         mainLayout.addView(listView);
  1214.         rootOverlay.addView(mainLayout);
  1215.         activity.setContentView(rootOverlay);
  1216.        
  1217.         rootOverlay.requestFocus();
  1218.  
  1219.         loadClips.run();
  1220.         loadingText.setVisibility(View.GONE);
  1221.  
  1222.         applyTheme = new Runnable() {
  1223.             run() {
  1224.                 resolvedTheme = resolveTheme(currentTheme);
  1225.                 colorPalette = getColorPalette(resolvedTheme);
  1226.                
  1227.                 bgColor = ((Integer)colorPalette.get("background")).intValue();
  1228.                 mainLayout.setBackgroundColor(bgColor);
  1229.                 mainLayout.setBackground(createRoundedBackground(bgColor, 20));
  1230.                 rebuildButtonBar();
  1231.                
  1232.                 searchEdit.setBackgroundColor(((Integer)colorPalette.get("input")).intValue());
  1233.                 searchEdit.setTextColor(((Integer)colorPalette.get("inputText")).intValue());
  1234.                 searchEdit.setHintTextColor(((Integer)colorPalette.get("hint")).intValue());
  1235.                
  1236.                 listView.invalidateViews();
  1237.             }
  1238.         };
  1239.  
  1240.         searchEdit.addTextChangedListener(new TextWatcher() {
  1241.             beforeTextChanged(CharSequence s, int st, int c, int a) {}
  1242.             onTextChanged(CharSequence s, int st, int b, int c) {}
  1243.             afterTextChanged(final Editable s) {
  1244.                 searchQuery = s.toString();
  1245.                 updateDataList.run();
  1246.             }
  1247.         });
  1248.     }
  1249. };
  1250.  
  1251. tasker.doWithActivity(activityConsumer);
Advertisement
Add Comment
Please, Sign In to add comment