From 27727eaff05159257bd4c4f9d4372f6bbe830642 Mon Sep 17 00:00:00 2001 From: lihongwei Date: Fri, 27 Jun 2025 17:59:56 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8A=9F=E8=83=BD=E6=B7=BB=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 2 + .../clicker/autoclicker/MyApplication.java | 1 + .../room/repository/EventRepository.java | 57 +-- .../autoclicker/service/AutoClickService.java | 125 +++++- .../ui/activity/main/MainActivity.java | 42 +- .../ui/activity/menu/ScriptsActivity.java | 363 +++++++++++++++++- .../ui/adapter/floating/SolutionAdapter.java | 102 ++++- .../ui/adapter/script/ScriptListAdapter.java | 47 --- .../ui/dialog/CreateModeSelectionDialog.java | 101 +++++ .../fragment/floating/LoadScriptFragment.java | 66 ---- .../fragment/floating/SaveScriptFragment.java | 66 ---- .../view/FloatingSettingDialogManager.java | 345 +++++++++++------ .../autoclicker/view/FloatingViewManager.java | 5 +- .../res/drawable/bg_mode_item_selector.xml | 5 + app/src/main/res/drawable/bg_selected.xml | 12 + .../res/drawable/ic_circle_filled_check.xml | 9 + .../main/res/drawable/ic_circle_hollow.xml | 9 + app/src/main/res/drawable/import_script.xml | 42 ++ app/src/main/res/drawable/multi_dialog.xml | 45 +++ app/src/main/res/drawable/single_dialog.xml | 14 + app/src/main/res/layout/activity_scripts.xml | 39 +- .../layout/dialog_create_mode_selection.xml | 167 ++++++++ .../res/layout/floating_setting_dialog.xml | 31 +- app/src/main/res/layout/item_solution.xml | 24 +- .../main/res/layout/option_menu_layout.xml | 50 +++ app/src/main/res/values/colors.xml | 1 + 26 files changed, 1399 insertions(+), 371 deletions(-) delete mode 100644 app/src/main/java/com/auto/clicker/autoclicker/ui/adapter/script/ScriptListAdapter.java create mode 100644 app/src/main/java/com/auto/clicker/autoclicker/ui/dialog/CreateModeSelectionDialog.java delete mode 100644 app/src/main/java/com/auto/clicker/autoclicker/ui/fragment/floating/LoadScriptFragment.java delete mode 100644 app/src/main/java/com/auto/clicker/autoclicker/ui/fragment/floating/SaveScriptFragment.java create mode 100644 app/src/main/res/drawable/bg_mode_item_selector.xml create mode 100644 app/src/main/res/drawable/bg_selected.xml create mode 100644 app/src/main/res/drawable/ic_circle_filled_check.xml create mode 100644 app/src/main/res/drawable/ic_circle_hollow.xml create mode 100644 app/src/main/res/drawable/import_script.xml create mode 100644 app/src/main/res/drawable/multi_dialog.xml create mode 100644 app/src/main/res/drawable/single_dialog.xml create mode 100644 app/src/main/res/layout/dialog_create_mode_selection.xml create mode 100644 app/src/main/res/layout/option_menu_layout.xml diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 8637e1e..bc74911 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -50,4 +50,6 @@ dependencies { implementation ("androidx.room:room-runtime:2.7.1") annotationProcessor ("androidx.room:room-compiler:2.7.1") + + implementation("com.google.code.gson:gson:2.12.1") } \ No newline at end of file diff --git a/app/src/main/java/com/auto/clicker/autoclicker/MyApplication.java b/app/src/main/java/com/auto/clicker/autoclicker/MyApplication.java index 5919b95..944d1d2 100644 --- a/app/src/main/java/com/auto/clicker/autoclicker/MyApplication.java +++ b/app/src/main/java/com/auto/clicker/autoclicker/MyApplication.java @@ -13,6 +13,7 @@ public class MyApplication extends Application { super.onCreate(); application = this; + } public static Context getContext() { diff --git a/app/src/main/java/com/auto/clicker/autoclicker/room/repository/EventRepository.java b/app/src/main/java/com/auto/clicker/autoclicker/room/repository/EventRepository.java index 316f595..0d84d61 100644 --- a/app/src/main/java/com/auto/clicker/autoclicker/room/repository/EventRepository.java +++ b/app/src/main/java/com/auto/clicker/autoclicker/room/repository/EventRepository.java @@ -1,6 +1,7 @@ package com.auto.clicker.autoclicker.room.repository; import android.content.Context; +import android.util.Log; import com.auto.clicker.autoclicker.data.Event; import com.auto.clicker.autoclicker.data.EventWrapper; @@ -9,34 +10,26 @@ import com.auto.clicker.autoclicker.data.SlideEvent; import com.auto.clicker.autoclicker.room.AppDatabase; import com.auto.clicker.autoclicker.room.SolutionWithEventsAndPoints; import com.auto.clicker.autoclicker.room.dao.EventEntityDao; -import com.auto.clicker.autoclicker.room.dao.TouchPointDao; import com.auto.clicker.autoclicker.room.dao.SolutionDao; +import com.auto.clicker.autoclicker.room.dao.TouchPointDao; import com.auto.clicker.autoclicker.room.entity.EventEntity; -import com.auto.clicker.autoclicker.room.entity.TouchPoint; import com.auto.clicker.autoclicker.room.entity.Solution; +import com.auto.clicker.autoclicker.room.entity.TouchPoint; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import android.content.Context; - -import java.util.List; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; - -import android.util.Log; // Add Log import for debugging - public class EventRepository { private final SolutionDao solutionDao; - private final TouchPointDao touchPointDao; // Renamed from PointDao + private final TouchPointDao touchPointDao; private final EventEntityDao eventEntityDao; private final ExecutorService databaseWriteExecutor; public EventRepository(Context context) { AppDatabase db = AppDatabase.getInstance(context); // Updated to getInstance this.solutionDao = db.solutionDao(); - this.touchPointDao = db.pointDao(); // Assuming db.pointDao() returns TouchPointDao now + this.touchPointDao = db.pointDao(); this.eventEntityDao = db.eventEntityDao(); this.databaseWriteExecutor = Executors.newFixedThreadPool(2); } @@ -147,20 +140,36 @@ public class EventRepository { }); } - // --- 删除操作 --- - public void deleteSolution(Solution solution, RepositoryCallback callback) { - databaseWriteExecutor.execute(() -> { - try { - int rowsAffected = solutionDao.deleteSolution(solution); - if (callback != null) callback.onComplete(rowsAffected); - } catch (Exception e) { - Log.e("EventRepository", "Error deleting solution: " + e.getMessage(), e); - if (callback != null) callback.onError(e); - } - }); +// public void deleteSolution(Solution solution, RepositoryCallback callback) { +// databaseWriteExecutor.execute(() -> { +// try { +// int rowsAffected = solutionDao.deleteSolution(solution); +// if (callback != null) callback.onComplete(rowsAffected); +// } catch (Exception e) { +// Log.e("EventRepository", "Error deleting solution: " + e.getMessage(), e); +// if (callback != null) callback.onError(e); +// } +// }); +// } + + public void updateSolutionName(long solutionId, String newName, RepositoryCallback callback) { + + } + + public void deleteSolution(long solutionId, RepositoryCallback callback) { + + } + + // 用于复制方案,需要保存一个新的方案以及其关联的事件和点 + public void saveSolutionWithEventsAndPoints(Solution solution, List eventsWithPoints, RepositoryCallback callback) { + + } + + // 获取某个方案及其所有事件和点的数据 + public void getSolutionWithEventsAndPoints(String solutionId, RepositoryCallback callback) { + } - // 假设的回调接口,可以根据需要自定义 public interface RepositoryCallback { void onComplete(T result); diff --git a/app/src/main/java/com/auto/clicker/autoclicker/service/AutoClickService.java b/app/src/main/java/com/auto/clicker/autoclicker/service/AutoClickService.java index ac06164..942da98 100644 --- a/app/src/main/java/com/auto/clicker/autoclicker/service/AutoClickService.java +++ b/app/src/main/java/com/auto/clicker/autoclicker/service/AutoClickService.java @@ -48,6 +48,11 @@ public class AutoClickService extends AccessibilityService { private int slideDuration = 500; private int currentEventIndex = 0; + private int loopCount = 1; + private long loopTimeMillis = 0; + private long startTimeMillis; + private int currentLoop = 0; + @Override public void onCreate() { super.onCreate(); @@ -122,6 +127,46 @@ public class AutoClickService extends AccessibilityService { logDebug("设置滑动时长: " + duration + "ms"); } + /** + * 设置事件队列的循环次数。 + * + * @param count 循环次数。设置为0表示无限循环,设置为1表示不循环(执行一次)。 + */ + public void setLoopCount(int count) { + if (count < 0) { + Log.w(TAG, "循环次数不能为负数,已设置为默认值 1"); + this.loopCount = 1; + } else { + this.loopCount = count; + } + logDebug("设置循环次数: " + loopCount + " 次"); + // 如果设置了循环次数,则重置循环时间为0,确保两种模式不冲突 + if (this.loopCount > 0) { + this.loopTimeMillis = 0; + logDebug("已重置循环时间为 0ms"); + } + } + + /** + * 设置事件队列的循环总时间。 + * + * @param timeMillis 循环总时间,单位毫秒。设置为0表示不按时间循环。 + */ + public void setLoopTime(long timeMillis) { + if (timeMillis < 0) { + Log.w(TAG, "循环时间不能为负数,已设置为默认值 0ms"); + this.loopTimeMillis = 0; + } else { + this.loopTimeMillis = timeMillis; + } + logDebug("设置循环时间: " + loopTimeMillis + "ms"); + // 如果设置了循环时间,则重置循环次数为0,确保两种模式不冲突 + if (this.loopTimeMillis > 0) { + this.loopCount = 0; + logDebug("已重置循环次数为 0"); + } + } + public void startClicking() { if (!isRunning) { if (runtimeEvents.isEmpty()) { @@ -131,10 +176,22 @@ public class AutoClickService extends AccessibilityService { isRunning = true; currentEventIndex = 0; + currentLoop = 0; // 重置循环计数 + startTimeMillis = System.currentTimeMillis(); // 记录开始时间 EventViewBinder.setAllTouchPointsDraggable(false); - logDebug("开始执行事件队列 - 共 " + runtimeEvents.size() + " 个事件"); + // 根据循环设置调整日志输出 + String loopInfo; + if (loopCount > 0) { + loopInfo = "循环 " + (loopCount == 1 ? "1 次" : loopCount + " 次"); + } else if (loopTimeMillis > 0) { + loopInfo = "循环 " + (loopTimeMillis / 1000) + " 秒"; + } else { + loopInfo = "无限循环"; // 如果两者都为0 + } + logDebug("开始执行事件队列 - 共 " + runtimeEvents.size() + " 个事件. " + loopInfo); + executeEventsByType(); } } @@ -146,6 +203,11 @@ public class AutoClickService extends AccessibilityService { EventViewBinder.setAllTouchPointsDraggable(true); + // 重置循环状态 + currentEventIndex = 0; // 停止时重置事件索引 + currentLoop = 0; // 停止时重置循环计数 + startTimeMillis = 0; // 停止时重置开始时间 + logDebug("停止执行事件"); } } @@ -155,15 +217,52 @@ public class AutoClickService extends AccessibilityService { } private void executeEventsByType() { - if (!isRunning || runtimeEvents.isEmpty()) { - logDebug("跳过执行:服务未运行或事件队列为空"); + if (!isRunning) { + logDebug("跳过执行:服务未运行"); return; } + if (runtimeEvents.isEmpty()) { + logDebug("跳过执行:事件队列为空"); + stopClicking(); // 如果事件队列为空,则停止服务 + return; + } + + // 在每一轮循环开始前排序,确保顺序正确 runtimeEvents.sort(Comparator.comparingInt(EventWrapper::getOrder)); + // 检查是否完成了一轮事件列表 if (currentEventIndex >= runtimeEvents.size()) { - currentEventIndex = 0; + currentEventIndex = 0; // 重置事件索引,准备开始新一轮循环 + currentLoop++; // 完成一轮,增加循环计数 + logDebug("完成第 " + currentLoop + " 轮事件执行。"); + + // 判断是否继续循环 + boolean continueLoop = false; + if (loopCount > 0) { // 按次数循环模式 + if (currentLoop < loopCount) { + continueLoop = true; + logDebug("继续第 " + (currentLoop + 1) + " 轮循环 (共 " + loopCount + " 轮)"); + } else { + logDebug("已达到设定的循环次数 (" + loopCount + " 次),停止循环。"); + } + } else if (loopTimeMillis > 0) { // 按时间循环模式 + long elapsedTime = System.currentTimeMillis() - startTimeMillis; + if (elapsedTime < loopTimeMillis) { + continueLoop = true; + logDebug("继续循环 (已用时 " + (elapsedTime / 1000) + "s, 总时长 " + (loopTimeMillis / 1000) + "s)"); + } else { + logDebug("已达到设定的循环时间 (" + (loopTimeMillis / 1000) + "s),停止循环。"); + } + } else { // 无限循环模式 (loopCount == 0 && loopTimeMillis == 0) + continueLoop = true; + logDebug("无限循环模式,继续下一轮循环。"); + } + + if (!continueLoop) { + stopClicking(); // 不再继续循环,则停止服务 + return; + } } EventWrapper wrapper = runtimeEvents.get(currentEventIndex); @@ -175,8 +274,8 @@ public class AutoClickService extends AccessibilityService { performSlide((SlideEvent) event); } else { logDebug("未知或不匹配的事件类型,跳过执行: " + wrapper.getType()); - currentEventIndex++; - handler.postDelayed(this::executeEventsByType, clickInterval); + currentEventIndex++; // 跳过当前事件 + handler.postDelayed(this::executeEventsByType, clickInterval); // 立即尝试下一个事件 } } @@ -206,7 +305,8 @@ public class AutoClickService extends AccessibilityService { logDebug("点击完成"); if (isRunning) { int feedbackIndex = currentEventIndex; - currentEventIndex++; + currentEventIndex++; // 事件索引递增 + // 延迟执行下一个事件,循环逻辑在executeEventsByType中处理 handler.postDelayed(() -> executeEventsByType(), clickInterval); flashTouchFeedback(feedbackIndex); } @@ -216,7 +316,8 @@ public class AutoClickService extends AccessibilityService { public void onCancelled(GestureDescription gestureDescription) { Log.e(TAG, "点击被取消"); if (isRunning) { - currentEventIndex++; + currentEventIndex++; // 事件索引递增 + // 延迟执行下一个事件 handler.postDelayed(() -> executeEventsByType(), clickInterval + 300); } } @@ -265,12 +366,12 @@ public class AutoClickService extends AccessibilityService { logDebug("滑动完成"); if (isRunning) { int feedbackIndex = currentEventIndex; - currentEventIndex++; + currentEventIndex++; // 事件索引递增 + // 延迟执行下一个事件 handler.postDelayed(() -> executeEventsByType(), clickInterval); flashTouchFeedback(feedbackIndex); } logDebug("--- onCompleted: 下一个事件将是 index " + currentEventIndex + " ---"); - // 再次打印,确认 slideEvent.getStartPoint() 和 getEndPoint() 是否被意外修改 logDebug("onCompleted 后,SlideEvent 起点: (" + slideEvent.getStartPoint().getX() + ", " + slideEvent.getStartPoint().getY() + ")"); logDebug("onCompleted 后,SlideEvent 终点: (" + slideEvent.getEndPoint().getX() + ", " + slideEvent.getEndPoint().getY() + ")"); } @@ -279,11 +380,11 @@ public class AutoClickService extends AccessibilityService { public void onCancelled(GestureDescription gestureDescription) { Log.e(TAG, "滑动被取消"); if (isRunning) { - currentEventIndex++; + currentEventIndex++; // 事件索引递增 + // 延迟执行下一个事件 handler.postDelayed(() -> executeEventsByType(), clickInterval + 300); } logDebug("--- onCancelled: 下一个事件将是 index " + currentEventIndex + " ---"); - // 再次打印,确认 slideEvent.getStartPoint() 和 getEndPoint() 是否被意外修改 logDebug("onCancelled 后,SlideEvent 起点: (" + slideEvent.getStartPoint().getX() + ", " + slideEvent.getStartPoint().getY() + ")"); logDebug("onCancelled 后,SlideEvent 终点: (" + slideEvent.getEndPoint().getX() + ", " + slideEvent.getEndPoint().getY() + ")"); } diff --git a/app/src/main/java/com/auto/clicker/autoclicker/ui/activity/main/MainActivity.java b/app/src/main/java/com/auto/clicker/autoclicker/ui/activity/main/MainActivity.java index 8709064..c49802f 100644 --- a/app/src/main/java/com/auto/clicker/autoclicker/ui/activity/main/MainActivity.java +++ b/app/src/main/java/com/auto/clicker/autoclicker/ui/activity/main/MainActivity.java @@ -20,6 +20,7 @@ import android.widget.VideoView; import androidx.activity.EdgeToEdge; import androidx.activity.result.ActivityResultLauncher; import androidx.activity.result.contract.ActivityResultContracts; +import androidx.annotation.NonNull; import androidx.appcompat.app.AppCompatActivity; import androidx.core.content.ContextCompat; import androidx.core.graphics.Insets; @@ -57,12 +58,14 @@ public class MainActivity extends AppCompatActivity { private int isFloatingShown; private int selectedFloatingMode = FLOATING_SINGLE; + public static final String EXTRA_FLOATING_MODE = "extra_floating_mode"; + private final BroadcastReceiver floatingReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { isFloatingShown = intent.getIntExtra("isShown", FLOATING_NONE); logDebug("接收到浮窗状态广播: isShown = " + isFloatingShown); - + if (isFloatingShown == FLOATING_NONE) { } else { selectedFloatingMode = isFloatingShown; @@ -160,6 +163,29 @@ public class MainActivity extends AppCompatActivity { logDebug("选择多点模式. selectedFloatingMode=" + selectedFloatingMode); }); + handleIncomingIntent(getIntent()); + } + + @Override + protected void onNewIntent(@NonNull Intent intent) { + super.onNewIntent(intent); + // 如果 MainActivity 已经在运行,并且再次通过 Intent 启动,会调用 onNewIntent + setIntent(intent); // 更新当前的 Intent + handleIncomingIntent(intent); + } + + private void handleIncomingIntent(Intent intent) { + int mode = intent.getIntExtra(EXTRA_FLOATING_MODE, FLOATING_NONE); + Log.d(TAG, "mode: " + mode); + if (mode != FLOATING_NONE) { + if (mode == FLOATING_SINGLE) { + binding.single.performClick(); + binding.animatedView.performClick(); + } else if (mode == FLOATING_MULTI) { + binding.multi.performClick(); + binding.animatedView.performClick(); + } + } } private void checkPermissions() { @@ -173,7 +199,7 @@ public class MainActivity extends AppCompatActivity { logDebug("无障碍服务未启用,请求权限"); showPermissionRequest(Settings.ACTION_ACCESSIBILITY_SETTINGS, "请启用无障碍服务以便应用正常工作", "无法打开无障碍设置"); setStartButtonEnabled(false); - + return; } @@ -182,7 +208,7 @@ public class MainActivity extends AppCompatActivity { Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + getPackageName())); showPermissionRequest(intent, "请授予悬浮窗权限,否则无法显示浮窗", "无法打开悬浮窗设置"); setStartButtonEnabled(false); - + return; } @@ -192,13 +218,13 @@ public class MainActivity extends AppCompatActivity { intent.setData(Uri.parse("package:" + getPackageName())); showPermissionRequest(intent, "请禁用电池优化,以确保应用在后台稳定运行", "无法打开电池优化设置"); setStartButtonEnabled(false); - + return; } logDebug("所有权限均已授予"); setStartButtonEnabled(true); - + } private void showPermissionRequest(String action, String toastMessage, String errorMessage) { @@ -253,7 +279,7 @@ public class MainActivity extends AppCompatActivity { logDebug("同步服务状态: 当前浮窗模式 = " + currentFloatingMode); isFloatingShown = currentFloatingMode; PrefUtils.setFloatingShown(this, isFloatingShown); - + updateSelectionButtons(); } @@ -286,14 +312,14 @@ public class MainActivity extends AppCompatActivity { } lastClickTime = currentTime; logDebug("点击启动/隐藏浮窗按钮. 当前 isFloatingShown=" + isFloatingShown + ", selectedFloatingMode=" + selectedFloatingMode); - + if (!isAccessibilityServiceEnabled() || !Settings.canDrawOverlays(this) || !isIgnoringBatteryOptimizations()) { Toast.makeText(this, "请授予所有必要权限", Toast.LENGTH_LONG).show(); checkPermissions(); logDebug("权限不足,无法启动浮窗,重新检查权限"); return; } - + if (isFloatingShown != FLOATING_NONE && isFloatingShown != selectedFloatingMode) { Toast.makeText(this, "已显示其他悬浮窗,请先关闭", Toast.LENGTH_SHORT).show(); logDebug("已显示其他模式的浮窗,请先关闭"); diff --git a/app/src/main/java/com/auto/clicker/autoclicker/ui/activity/menu/ScriptsActivity.java b/app/src/main/java/com/auto/clicker/autoclicker/ui/activity/menu/ScriptsActivity.java index dfb6b95..5f8d7c8 100644 --- a/app/src/main/java/com/auto/clicker/autoclicker/ui/activity/menu/ScriptsActivity.java +++ b/app/src/main/java/com/auto/clicker/autoclicker/ui/activity/menu/ScriptsActivity.java @@ -1,19 +1,48 @@ package com.auto.clicker.autoclicker.ui.activity.menu; +import android.app.AlertDialog; +import android.content.Intent; +import android.net.Uri; import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.util.Log; +import android.view.View; +import android.widget.EditText; +import android.widget.Toast; import androidx.activity.EdgeToEdge; import androidx.appcompat.app.AppCompatActivity; import androidx.core.graphics.Insets; import androidx.core.view.ViewCompat; import androidx.core.view.WindowInsetsCompat; +import androidx.recyclerview.widget.LinearLayoutManager; import com.auto.clicker.autoclicker.R; -import com.auto.clicker.autoclicker.databinding.ActivityFeedbackBinding; import com.auto.clicker.autoclicker.databinding.ActivityScriptsBinding; +import com.auto.clicker.autoclicker.room.SolutionWithEventsAndPoints; +import com.auto.clicker.autoclicker.room.entity.EventEntity; +import com.auto.clicker.autoclicker.room.entity.Solution; +import com.auto.clicker.autoclicker.room.entity.TouchPoint; +import com.auto.clicker.autoclicker.room.repository.EventRepository; +import com.auto.clicker.autoclicker.ui.adapter.floating.SolutionAdapter; +import com.auto.clicker.autoclicker.ui.dialog.CreateModeSelectionDialog; +import com.auto.clicker.autoclicker.view.FloatingViewManager; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; -public class ScriptsActivity extends AppCompatActivity { +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +public class ScriptsActivity extends AppCompatActivity implements SolutionAdapter.OnOptionMenuClickListener { private ActivityScriptsBinding binding; + private SolutionAdapter solutionsAdapter; + private FloatingViewManager floatingViewManager; + + private static final String TAG = "ScriptsActivity"; + private static final int CREATE_FILE_REQUEST_CODE = 1; // 用于导出文件的请求码 @Override protected void onCreate(Bundle savedInstanceState) { @@ -27,6 +56,336 @@ public class ScriptsActivity extends AppCompatActivity { return insets; }); + binding.scriptsRecyclerView.setLayoutManager(new LinearLayoutManager(this)); + // 传入 this 作为 OnOptionMenuClickListener 的实现 + solutionsAdapter = new SolutionAdapter(new ArrayList<>(), this::onSolutionSelected, this); + binding.scriptsRecyclerView.setAdapter(solutionsAdapter); + + floatingViewManager = new FloatingViewManager(this); + binding.btnBack.setOnClickListener(v -> finish()); + + binding.create.setOnClickListener(v -> { + CreateModeSelectionDialog dialog = new CreateModeSelectionDialog(); + dialog.show(getSupportFragmentManager(), "CreateModeSelectionDialog"); + }); + + binding.importScripts.setOnClickListener(v -> { + Toast.makeText(this, "导入方案功能待实现", Toast.LENGTH_SHORT).show(); + // TODO: 实现导入方案的逻辑 + }); + + binding.fabImportScripts.setOnClickListener(v -> { + Toast.makeText(this, "右下角导入方案功能待实现", Toast.LENGTH_SHORT).show(); + // TODO: 实现右下角导入方案的逻辑,可能与上面的importScripts按钮功能相同或类似 + }); + + loadSolutions(); + } + + @Override + protected void onResume() { + super.onResume(); + loadSolutions(); + } + + private void loadSolutions() { + if (floatingViewManager == null) { + Log.e(TAG, "FloatingViewManager 未设置,无法加载方案列表。"); + Toast.makeText(this, "加载方案列表功能不可用,请联系开发者。", Toast.LENGTH_SHORT).show(); + updateLayout(true); + return; + } + + floatingViewManager.getAllSolutions(new EventRepository.RepositoryCallback>() { + @Override + public void onComplete(List solutions) { + new Handler(Looper.getMainLooper()).post(() -> { + solutionsAdapter.setSolutions(solutions); + updateLayout(solutions.isEmpty()); + if (solutions.isEmpty()) { + Toast.makeText(ScriptsActivity.this, "没有可加载的方案。", Toast.LENGTH_SHORT).show(); + } else { + Toast.makeText(ScriptsActivity.this, "方案加载成功!", Toast.LENGTH_SHORT).show(); + } + }); + } + + @Override + public void onError(Exception e) { + new Handler(Looper.getMainLooper()).post(() -> { + Log.e(TAG, "加载方案列表失败: " + e.getMessage(), e); + Toast.makeText(ScriptsActivity.this, "加载方案列表失败。", Toast.LENGTH_SHORT).show(); + updateLayout(true); // 加载失败也显示无方案视图 + }); + } + }); + } + + private void updateLayout(boolean isEmpty) { + if (isEmpty) { + binding.noScriptsGroup.setVisibility(View.VISIBLE); + binding.scriptsRecyclerView.setVisibility(View.GONE); + binding.fabImportScripts.setVisibility(View.GONE); + } else { + binding.noScriptsGroup.setVisibility(View.GONE); + binding.scriptsRecyclerView.setVisibility(View.VISIBLE); + binding.fabImportScripts.setVisibility(View.VISIBLE); + } + } + + private void onSolutionSelected(Solution solution) { + if (floatingViewManager != null) { + floatingViewManager.showFloatingViews(solution.getMode()); + floatingViewManager.loadSolution(solution.getSolutionId()); + Toast.makeText(this, "正在加载方案: " + solution.getSolutionName(), Toast.LENGTH_SHORT).show(); + } else { + Log.e(TAG, "FloatingViewManager 未设置!无法加载方案。"); + Toast.makeText(this, "加载功能不可用,请联系开发者。", Toast.LENGTH_SHORT).show(); + } + } + + // --- 实现 SolutionAdapter.OnOptionMenuClickListener 接口的方法 --- + + @Override + public void onRenameClick(Solution solution) { + // 实现重命名逻辑 + showRenameDialog(solution); + } + + @Override + public void onDuplicateClick(Solution solution) { + // 实现复制逻辑 + duplicateSolution(solution); + } + + @Override + public void onDeleteClick(Solution solution) { + // 实现删除逻辑 + showDeleteConfirmationDialog(solution); + } + + @Override + public void onExportClick(Solution solution) { + // 实现导出逻辑 + exportSolution(solution); + } + + private void showRenameDialog(final Solution solution) { + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle("重命名方案"); + + final EditText input = new EditText(this); + input.setText(solution.getSolutionName()); + builder.setView(input); + + builder.setPositiveButton("确定", (dialog, which) -> { + String newName = input.getText().toString().trim(); + if (!newName.isEmpty() && !newName.equals(solution.getSolutionName())) { + // 调用 EventRepository 更新方案名称 + floatingViewManager.getEventRepository().updateSolutionName(solution.getSolutionId(), newName, new EventRepository.RepositoryCallback() { + @Override + public void onComplete(Void result) { + new Handler(Looper.getMainLooper()).post(() -> { + Toast.makeText(ScriptsActivity.this, "方案已重命名为: " + newName, Toast.LENGTH_SHORT).show(); + loadSolutions(); // 刷新列表 + }); + } + + @Override + public void onError(Exception e) { + new Handler(Looper.getMainLooper()).post(() -> { + Log.e(TAG, "重命名失败: " + e.getMessage(), e); + Toast.makeText(ScriptsActivity.this, "重命名失败: " + e.getMessage(), Toast.LENGTH_SHORT).show(); + }); + } + }); + } else if (newName.isEmpty()) { + Toast.makeText(ScriptsActivity.this, "方案名称不能为空。", Toast.LENGTH_SHORT).show(); + } + }); + builder.setNegativeButton("取消", (dialog, which) -> dialog.cancel()); + builder.show(); + } + + private void duplicateSolution(final Solution originalSolution) { + floatingViewManager.getEventRepository().getSolutionWithEventsAndPoints(originalSolution.getSolutionId(), new EventRepository.RepositoryCallback() { + @Override + public void onComplete(SolutionWithEventsAndPoints originalData) { + if (originalData != null && originalData.solution != null) { + // 创建新的 Solution 对象 + Solution newSolution = new Solution(); + newSolution.setSolutionId(UUID.randomUUID().toString()); // 生成新的ID + newSolution.setSolutionName(originalSolution.getSolutionName() + " (1)"); + newSolution.setMode(originalSolution.getMode()); + + // 复制事件和点(深拷贝) + List newEventsWithPoints = new ArrayList<>(); + if (originalData.events != null) { + for (SolutionWithEventsAndPoints.EventWithPoints originalEvent : originalData.events) { + SolutionWithEventsAndPoints.EventWithPoints newEvent = new SolutionWithEventsAndPoints.EventWithPoints(); + // 复制 EventEntity + newEvent.eventEntity = new EventEntity(); + newEvent.eventEntity.eventId = UUID.randomUUID().toString(); // 新的事件ID + newEvent.eventEntity.solutionId = newSolution.getSolutionId(); // 关联新的方案ID + newEvent.eventEntity.eventType = originalEvent.eventEntity.eventType; + newEvent.eventEntity.orderInSolution = originalEvent.eventEntity.orderInSolution; + + // 复制 TouchPoint + if (originalEvent.clickTouchPoint != null) { + newEvent.clickTouchPoint = new TouchPoint(); + newEvent.clickTouchPoint.pointId = UUID.randomUUID().toString(); // 新的点ID + newEvent.clickTouchPoint.eventId = newEvent.eventEntity.eventId; // 关联新的事件ID + newEvent.clickTouchPoint.x = originalEvent.clickTouchPoint.x; + newEvent.clickTouchPoint.y = originalEvent.clickTouchPoint.y; + newEvent.clickTouchPoint.pointOrder = originalEvent.clickTouchPoint.pointOrder; + } + if (originalEvent.slideStartTouchPoint != null) { + newEvent.slideStartTouchPoint = new TouchPoint(); + newEvent.slideStartTouchPoint.pointId = UUID.randomUUID().toString(); // 新的点ID + newEvent.slideStartTouchPoint.eventId = newEvent.eventEntity.eventId; // 关联新的事件ID + newEvent.slideStartTouchPoint.x = originalEvent.slideStartTouchPoint.x; + newEvent.slideStartTouchPoint.y = originalEvent.slideStartTouchPoint.y; + newEvent.slideStartTouchPoint.pointOrder = originalEvent.slideStartTouchPoint.pointOrder; + } + if (originalEvent.slideEndTouchPoint != null) { + newEvent.slideEndTouchPoint = new TouchPoint(); + newEvent.slideEndTouchPoint.pointId = UUID.randomUUID().toString(); // 新的点ID + newEvent.slideEndTouchPoint.eventId = newEvent.eventEntity.eventId; // 关联新的事件ID + newEvent.slideEndTouchPoint.x = originalEvent.slideEndTouchPoint.x; + newEvent.slideEndTouchPoint.y = originalEvent.slideEndTouchPoint.y; + newEvent.slideEndTouchPoint.pointOrder = originalEvent.slideEndTouchPoint.pointOrder; + } + newEventsWithPoints.add(newEvent); + } + } + + // 保存新的方案及其事件和点 + floatingViewManager.getEventRepository().saveSolutionWithEventsAndPoints(newSolution, newEventsWithPoints, new EventRepository.RepositoryCallback() { + @Override + public void onComplete(Void result) { + new Handler(Looper.getMainLooper()).post(() -> { + Toast.makeText(ScriptsActivity.this, "方案 '" + originalSolution.getSolutionName() + "' 已复制为 '" + newSolution.getSolutionName() + "'", Toast.LENGTH_SHORT).show(); + loadSolutions(); // 刷新列表 + }); + } + + @Override + public void onError(Exception e) { + new Handler(Looper.getMainLooper()).post(() -> { + Log.e(TAG, "复制方案失败: " + e.getMessage(), e); + Toast.makeText(ScriptsActivity.this, "复制方案失败: " + e.getMessage(), Toast.LENGTH_SHORT).show(); + }); + } + }); + } else { + new Handler(Looper.getMainLooper()).post(() -> + Toast.makeText(ScriptsActivity.this, "无法获取原始方案数据。", Toast.LENGTH_SHORT).show() + ); + } + } + + @Override + public void onError(Exception e) { + new Handler(Looper.getMainLooper()).post(() -> { + Log.e(TAG, "获取原始方案数据失败: " + e.getMessage(), e); + Toast.makeText(ScriptsActivity.this, "复制方案失败: " + e.getMessage(), Toast.LENGTH_SHORT).show(); + }); + } + }); + } + + + private void showDeleteConfirmationDialog(final Solution solution) { + new AlertDialog.Builder(this) + .setTitle("删除方案") + .setMessage("确定要删除方案 '" + solution.getSolutionName() + "' 吗?") + .setPositiveButton("删除", (dialog, which) -> deleteSolution(solution)) + .setNegativeButton("取消", null) + .show(); + } + + private void deleteSolution(final Solution solution) { + floatingViewManager.getEventRepository().deleteSolution(solution.getSolutionId(), new EventRepository.RepositoryCallback() { + @Override + public void onComplete(Void result) { + new Handler(Looper.getMainLooper()).post(() -> { + Toast.makeText(ScriptsActivity.this, "方案 '" + solution.getSolutionName() + "' 已删除。", Toast.LENGTH_SHORT).show(); + loadSolutions(); // 刷新列表 + }); + } + + @Override + public void onError(Exception e) { + new Handler(Looper.getMainLooper()).post(() -> { + Log.e(TAG, "删除方案失败: " + e.getMessage(), e); + Toast.makeText(ScriptsActivity.this, "删除方案失败: " + e.getMessage(), Toast.LENGTH_SHORT).show(); + }); + } + }); + } + + private void exportSolution(final Solution solution) { + floatingViewManager.getEventRepository().getSolutionWithEventsAndPoints(solution.getSolutionId(), new EventRepository.RepositoryCallback() { + @Override + public void onComplete(SolutionWithEventsAndPoints solutionData) { + if (solutionData != null) { + new Handler(Looper.getMainLooper()).post(() -> { + Gson gson = new GsonBuilder().setPrettyPrinting().create(); + String jsonString = gson.toJson(solutionData); + + Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT); + intent.addCategory(Intent.CATEGORY_OPENABLE); + intent.setType("application/json"); // 导出为 JSON 文件 + intent.putExtra(Intent.EXTRA_TITLE, solution.getSolutionName() + ".json"); + startActivityForResult(intent, CREATE_FILE_REQUEST_CODE); + + // 将 JSON 字符串保存到临时变量或直接传递 + // 在 onActivityResult 中处理保存 + tempExportJson = jsonString; // 临时保存待导出的JSON数据 + }); + } else { + new Handler(Looper.getMainLooper()).post(() -> + Toast.makeText(ScriptsActivity.this, "获取方案数据失败,无法导出。", Toast.LENGTH_SHORT).show() + ); + } + } + + @Override + public void onError(Exception e) { + new Handler(Looper.getMainLooper()).post(() -> { + Log.e(TAG, "获取方案数据失败: " + e.getMessage(), e); + Toast.makeText(ScriptsActivity.this, "导出方案失败: " + e.getMessage(), Toast.LENGTH_SHORT).show(); + }); + } + }); + } + + // 临时变量,用于存储待导出的JSON数据 + private String tempExportJson; + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (requestCode == CREATE_FILE_REQUEST_CODE && resultCode == RESULT_OK) { + if (data != null && data.getData() != null) { + Uri uri = data.getData(); + if (tempExportJson != null) { + try (OutputStream outputStream = getContentResolver().openOutputStream(uri)) { + if (outputStream != null) { + outputStream.write(tempExportJson.getBytes()); + Toast.makeText(this, "方案已成功导出!", Toast.LENGTH_SHORT).show(); + } + } catch (Exception e) { + Log.e(TAG, "保存文件失败: " + e.getMessage(), e); + Toast.makeText(this, "保存文件失败: " + e.getMessage(), Toast.LENGTH_SHORT).show(); + } finally { + tempExportJson = null; // 清除临时数据 + } + } else { + Toast.makeText(this, "没有可导出的数据。", Toast.LENGTH_SHORT).show(); + } + } + } } } \ No newline at end of file diff --git a/app/src/main/java/com/auto/clicker/autoclicker/ui/adapter/floating/SolutionAdapter.java b/app/src/main/java/com/auto/clicker/autoclicker/ui/adapter/floating/SolutionAdapter.java index 7a26f02..a49f29e 100644 --- a/app/src/main/java/com/auto/clicker/autoclicker/ui/adapter/floating/SolutionAdapter.java +++ b/app/src/main/java/com/auto/clicker/autoclicker/ui/adapter/floating/SolutionAdapter.java @@ -1,10 +1,13 @@ package com.auto.clicker.autoclicker.ui.adapter.floating; +import android.content.Context; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; +import android.widget.PopupWindow; import android.widget.TextView; + import androidx.annotation.NonNull; import androidx.recyclerview.widget.RecyclerView; @@ -13,18 +16,32 @@ import com.auto.clicker.autoclicker.room.entity.Solution; import java.util.List; + public class SolutionAdapter extends RecyclerView.Adapter { private List solutionList; private OnSolutionClickListener listener; + private OnOptionMenuClickListener optionMenuListener; + + // 新增接口定义 + public interface OnOptionMenuClickListener { + void onRenameClick(Solution solution); + + void onDuplicateClick(Solution solution); + + void onDeleteClick(Solution solution); + + void onExportClick(Solution solution); + } public interface OnSolutionClickListener { void onSolutionClick(Solution solution); } - public SolutionAdapter(List solutionList, OnSolutionClickListener listener) { + public SolutionAdapter(List solutionList, OnSolutionClickListener listener, OnOptionMenuClickListener optionMenuListener) { this.solutionList = solutionList; this.listener = listener; + this.optionMenuListener = optionMenuListener; // 初始化 } @NonNull @@ -32,25 +49,31 @@ public class SolutionAdapter extends RecyclerView.Adapter { + holder.play.setOnClickListener(v -> { if (listener != null) { listener.onSolutionClick(solution); } }); + // 为 option 按钮设置点击监听器 + holder.option.setOnClickListener(v -> { + if (optionMenuListener != null) { + holder.showOptionMenu(solution, optionMenuListener); // 调用方法显示菜单 + } + }); } @Override @@ -67,15 +90,62 @@ public class SolutionAdapter extends RecyclerView.Adapter { + menuListener.onRenameClick(solution); + popupWindow.dismiss(); // 关闭弹窗 + }); + + tvDuplicate.setOnClickListener(v -> { + menuListener.onDuplicateClick(solution); + popupWindow.dismiss(); + }); + + tvDelete.setOnClickListener(v -> { + menuListener.onDeleteClick(solution); + popupWindow.dismiss(); + }); + + tvExport.setOnClickListener(v -> { + menuListener.onExportClick(solution); + popupWindow.dismiss(); + }); + + // 显示 PopupWindow,锚定在 option 按钮下方 + popupWindow.showAsDropDown(option); } } -} +} \ No newline at end of file diff --git a/app/src/main/java/com/auto/clicker/autoclicker/ui/adapter/script/ScriptListAdapter.java b/app/src/main/java/com/auto/clicker/autoclicker/ui/adapter/script/ScriptListAdapter.java deleted file mode 100644 index bf92cd8..0000000 --- a/app/src/main/java/com/auto/clicker/autoclicker/ui/adapter/script/ScriptListAdapter.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.auto.clicker.autoclicker.ui.adapter.script; - -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import androidx.annotation.NonNull; -import androidx.recyclerview.widget.RecyclerView; - -import com.auto.clicker.autoclicker.R; -import com.auto.clicker.autoclicker.room.entity.Solution; - -import java.util.List; - -public class ScriptListAdapter extends RecyclerView.Adapter { - private List solutionList; - - public ScriptListAdapter(List solutionList) { - this.solutionList = solutionList; - } - - @NonNull - @Override - public ViewHolder onCreateViewHolder(@NonNull ViewGroup parentGroup, int viewType) { - LayoutInflater inflater = LayoutInflater.from(parentGroup.getContext()); - View itemLayout = inflater.inflate(R.layout.item_script, parentGroup, false); - return new ViewHolder(itemLayout); - } - - @Override - public void onBindViewHolder(@NonNull ViewHolder itemHolder, int pos) { - - } - - @Override - public int getItemCount() { - return solutionList.size(); - } - - static class ViewHolder extends RecyclerView.ViewHolder { - - public ViewHolder(@NonNull View itemView) { - super(itemView); - - } - } -} diff --git a/app/src/main/java/com/auto/clicker/autoclicker/ui/dialog/CreateModeSelectionDialog.java b/app/src/main/java/com/auto/clicker/autoclicker/ui/dialog/CreateModeSelectionDialog.java new file mode 100644 index 0000000..9d0e912 --- /dev/null +++ b/app/src/main/java/com/auto/clicker/autoclicker/ui/dialog/CreateModeSelectionDialog.java @@ -0,0 +1,101 @@ +package com.auto.clicker.autoclicker.ui.dialog; + +import android.content.Intent; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.ImageView; +import android.widget.Toast; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.constraintlayout.widget.ConstraintLayout; + +import com.auto.clicker.autoclicker.R; +import com.auto.clicker.autoclicker.ui.activity.main.MainActivity; +import com.google.android.material.bottomsheet.BottomSheetDialogFragment; + +public class CreateModeSelectionDialog extends BottomSheetDialogFragment { + + public static final String EXTRA_FLOATING_MODE = "extra_floating_mode"; + public static final int FLOATING_SINGLE = 1; + public static final int FLOATING_MULTI = 2; + + private int selectedMode = FLOATING_SINGLE; + + private ConstraintLayout layoutSingleMode; + private ConstraintLayout layoutMultiMode; + private ImageView iconSingle; + private ImageView iconMulti; + + public CreateModeSelectionDialog() { + } + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.dialog_create_mode_selection, container, false); + + ImageView btnClose = view.findViewById(R.id.btn_close_dialog); + layoutSingleMode = view.findViewById(R.id.layout_single_mode); + layoutMultiMode = view.findViewById(R.id.layout_multi_mode); + Button btnStartCreate = view.findViewById(R.id.btn_start_create); + + iconSingle = view.findViewById(R.id.icon_single); + iconMulti = view.findViewById(R.id.icon_multi); + + btnClose.setOnClickListener(v -> dismiss()); + + layoutSingleMode.setOnClickListener(v -> { + setSelectedMode(FLOATING_SINGLE); + }); + + layoutMultiMode.setOnClickListener(v -> { + setSelectedMode(FLOATING_MULTI); + }); + + btnStartCreate.setOnClickListener(v -> { + if (selectedMode == 0) { + Toast.makeText(getContext(), "请选择模式", Toast.LENGTH_SHORT).show(); + return; + } + navigateToMainActivity(selectedMode); + }); + + updateModeSelectionUI(); + + return view; + } + + private void setSelectedMode(int mode) { + if (this.selectedMode != mode) { + this.selectedMode = mode; + updateModeSelectionUI(); + } + } + + private void updateModeSelectionUI() { + if (selectedMode == FLOATING_SINGLE) { + layoutSingleMode.setSelected(true); + iconSingle.setImageResource(R.drawable.ic_circle_filled_check); + + layoutMultiMode.setSelected(false); + iconMulti.setImageResource(R.drawable.ic_circle_hollow); + } else if (selectedMode == FLOATING_MULTI) { + layoutMultiMode.setSelected(true); + iconMulti.setImageResource(R.drawable.ic_circle_filled_check); + + layoutSingleMode.setSelected(false); + iconSingle.setImageResource(R.drawable.ic_circle_hollow); + } + } + + private void navigateToMainActivity(int mode) { + Intent intent = new Intent(getActivity(), MainActivity.class); + intent.putExtra(EXTRA_FLOATING_MODE, mode); + startActivity(intent); + dismiss(); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/auto/clicker/autoclicker/ui/fragment/floating/LoadScriptFragment.java b/app/src/main/java/com/auto/clicker/autoclicker/ui/fragment/floating/LoadScriptFragment.java deleted file mode 100644 index 49f1444..0000000 --- a/app/src/main/java/com/auto/clicker/autoclicker/ui/fragment/floating/LoadScriptFragment.java +++ /dev/null @@ -1,66 +0,0 @@ -package com.auto.clicker.autoclicker.ui.fragment.floating; - -import android.os.Bundle; - -import androidx.fragment.app.Fragment; - -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import com.auto.clicker.autoclicker.R; - -/** - * A simple {@link Fragment} subclass. - * Use the {@link LoadScriptFragment#newInstance} factory method to - * create an instance of this fragment. - */ -public class LoadScriptFragment extends Fragment { - - // TODO: Rename parameter arguments, choose names that match - // the fragment initialization parameters, e.g. ARG_ITEM_NUMBER - private static final String ARG_PARAM1 = "param1"; - private static final String ARG_PARAM2 = "param2"; - - // TODO: Rename and change types of parameters - private String mParam1; - private String mParam2; - - public LoadScriptFragment() { - // Required empty public constructor - } - - /** - * Use this factory method to create a new instance of - * this fragment using the provided parameters. - * - * @param param1 Parameter 1. - * @param param2 Parameter 2. - * @return A new instance of fragment LoadScriptFragment. - */ - // TODO: Rename and change types and number of parameters - public static LoadScriptFragment newInstance(String param1, String param2) { - LoadScriptFragment fragment = new LoadScriptFragment(); - Bundle args = new Bundle(); - args.putString(ARG_PARAM1, param1); - args.putString(ARG_PARAM2, param2); - fragment.setArguments(args); - return fragment; - } - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - if (getArguments() != null) { - mParam1 = getArguments().getString(ARG_PARAM1); - mParam2 = getArguments().getString(ARG_PARAM2); - } - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - // Inflate the layout for this fragment - return inflater.inflate(R.layout.fragment_load_script, container, false); - } -} \ No newline at end of file diff --git a/app/src/main/java/com/auto/clicker/autoclicker/ui/fragment/floating/SaveScriptFragment.java b/app/src/main/java/com/auto/clicker/autoclicker/ui/fragment/floating/SaveScriptFragment.java deleted file mode 100644 index a2f1471..0000000 --- a/app/src/main/java/com/auto/clicker/autoclicker/ui/fragment/floating/SaveScriptFragment.java +++ /dev/null @@ -1,66 +0,0 @@ -package com.auto.clicker.autoclicker.ui.fragment.floating; - -import android.os.Bundle; - -import androidx.fragment.app.Fragment; - -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import com.auto.clicker.autoclicker.R; - -/** - * A simple {@link Fragment} subclass. - * Use the {@link SaveScriptFragment#newInstance} factory method to - * create an instance of this fragment. - */ -public class SaveScriptFragment extends Fragment { - - // TODO: Rename parameter arguments, choose names that match - // the fragment initialization parameters, e.g. ARG_ITEM_NUMBER - private static final String ARG_PARAM1 = "param1"; - private static final String ARG_PARAM2 = "param2"; - - // TODO: Rename and change types of parameters - private String mParam1; - private String mParam2; - - public SaveScriptFragment() { - // Required empty public constructor - } - - /** - * Use this factory method to create a new instance of - * this fragment using the provided parameters. - * - * @param param1 Parameter 1. - * @param param2 Parameter 2. - * @return A new instance of fragment SaveScriptFragment. - */ - // TODO: Rename and change types and number of parameters - public static SaveScriptFragment newInstance(String param1, String param2) { - SaveScriptFragment fragment = new SaveScriptFragment(); - Bundle args = new Bundle(); - args.putString(ARG_PARAM1, param1); - args.putString(ARG_PARAM2, param2); - fragment.setArguments(args); - return fragment; - } - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - if (getArguments() != null) { - mParam1 = getArguments().getString(ARG_PARAM1); - mParam2 = getArguments().getString(ARG_PARAM2); - } - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - // Inflate the layout for this fragment - return inflater.inflate(R.layout.fragment_save_script, container, false); - } -} \ No newline at end of file diff --git a/app/src/main/java/com/auto/clicker/autoclicker/view/FloatingSettingDialogManager.java b/app/src/main/java/com/auto/clicker/autoclicker/view/FloatingSettingDialogManager.java index 88ec55e..7dd2ff4 100644 --- a/app/src/main/java/com/auto/clicker/autoclicker/view/FloatingSettingDialogManager.java +++ b/app/src/main/java/com/auto/clicker/autoclicker/view/FloatingSettingDialogManager.java @@ -12,7 +12,6 @@ import android.util.Log; import android.view.ContextThemeWrapper; import android.view.Gravity; import android.view.LayoutInflater; -import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.WindowManager; @@ -22,6 +21,7 @@ import android.widget.PopupWindow; import android.widget.TextView; import android.widget.Toast; +import androidx.appcompat.widget.AppCompatButton; import androidx.appcompat.widget.SwitchCompat; import androidx.constraintlayout.widget.ConstraintLayout; @@ -36,8 +36,6 @@ public class FloatingSettingDialogManager { private ConstraintLayout floatingDialogView; private WindowManager.LayoutParams floatingDialogParams; - private FloatingViewManager floatingViewManager; - private static final int TYPE_INTERVAL = 0; private static final int TYPE_SWIPE_DURATION = 1; private static final int TYPE_REPEAT = 2; @@ -53,16 +51,22 @@ public class FloatingSettingDialogManager { private TextView tvSelectedUnit; private EditText etRepeatValue; private TextView repeatSelectedUnit; + private AppCompatButton saveButton; + private AppCompatButton closeButton; + + private String currentIntervalUnit = ""; + private String currentSwipeDurationUnit = ""; + private String currentRepeatUnit = ""; + private String currentIntervalValue = ""; + private String currentDurationValue = ""; + private String currentRepeatValue = ""; + private boolean isRepeatSwitchChecked = false; public FloatingSettingDialogManager(Context context) { this.context = context; this.windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); } - public void setFloatingViewManager(FloatingViewManager manager) { - this.floatingViewManager = manager; - } - public void showFloatingTabDialog() { if (floatingDialogView != null) { removeFloatingTabDialog(); @@ -85,20 +89,46 @@ public class FloatingSettingDialogManager { etRepeatValue = floatingDialogView.findViewById(R.id.dialog_et_repeat_value); repeatSelectedUnit = floatingDialogView.findViewById(R.id.dialog_repeat_selected_unit); + saveButton = floatingDialogView.findViewById(R.id.save); + closeButton = floatingDialogView.findViewById(R.id.close); + intervalSelectorContainer.setOnClickListener(v -> showUnitSelection(v, TYPE_INTERVAL)); unitSelectorContainer.setOnClickListener(v -> showUnitSelection(v, TYPE_SWIPE_DURATION)); setupRepeatMode(); + // --- 恢复UI状态 --- + restoreUIState(); - floatingDialogView.setOnTouchListener((v, event) -> { - if (event.getAction() == MotionEvent.ACTION_OUTSIDE) { - // 如果是外部点击事件,就关闭对话框 +// floatingDialogView.setOnTouchListener((v, event) -> { +// if (event.getAction() == MotionEvent.ACTION_OUTSIDE) { +// // 如果是外部点击事件,就关闭对话框 +// saveCurrentSettings(); +// removeFloatingTabDialog(); +// Log.d(TAG, "检测到外部点击,浮动标签页弹窗已关闭。"); +// return true; // 消费此事件 +// } +// return false; // 其他事件不处理 +// }); + + if (saveButton != null) { + saveButton.setOnClickListener(v -> { + saveCurrentSettings(); + applySettingsToService(); removeFloatingTabDialog(); - Log.d(TAG, "检测到外部点击,浮动标签页弹窗已关闭。"); - return true; // 消费此事件 - } - return false; // 其他事件不处理 - }); + showToastOnUi(context, "设置已保存!"); + }); + } else { + Log.e(TAG, "Save button not found!"); + } + + if (closeButton != null) { + closeButton.setOnClickListener(v -> { + removeFloatingTabDialog(); + showToastOnUi(context, "设置已关闭,未保存更改。"); + }); + } else { + Log.e(TAG, "Close button not found!"); + } try { windowManager.addView(floatingDialogView, floatingDialogParams); @@ -128,6 +158,8 @@ public class FloatingSettingDialogManager { tvSelectedUnit = null; etRepeatValue = null; repeatSelectedUnit = null; + saveButton = null; + closeButton = null; Log.d(TAG, "浮动标签页弹窗已移除"); } catch (IllegalArgumentException e) { @@ -163,31 +195,35 @@ public class FloatingSettingDialogManager { } repeatSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> { - if (isChecked) { - repeatInput.setVisibility(View.VISIBLE); - repeatSelectorContainer.setVisibility(View.VISIBLE); - repeatSelectedUnit.setText(context.getString(R.string.infinitely_unit)); + isRepeatSwitchChecked = isChecked; // 保存开关状态 + updateRepeatUI(isChecked); + }); + + repeatSelectorContainer.setOnClickListener(v -> showUnitSelection(v, TYPE_REPEAT)); + } + + private void updateRepeatUI(boolean isChecked) { + if (isChecked) { + repeatInput.setVisibility(View.VISIBLE); + repeatSelectorContainer.setVisibility(View.VISIBLE); + // 只有当单位是“无限次”时才禁用输入框 + if (currentRepeatUnit.equals(context.getString(R.string.infinitely_unit)) || repeatSelectedUnit.getText().toString().equals(context.getString(R.string.infinitely_unit))) { etRepeatValue.setText(""); etRepeatValue.setEnabled(false); etRepeatValue.setInputType(InputType.TYPE_NULL); + repeatSelectedUnit.setText(context.getString(R.string.infinitely_unit)); // 确保显示无限次 } else { - repeatInput.setVisibility(View.GONE); - repeatSelectorContainer.setVisibility(View.GONE); + etRepeatValue.setEnabled(true); + etRepeatValue.setInputType(InputType.TYPE_CLASS_NUMBER); // 默认设置为数字输入 + // 恢复之前的输入值和单位 + etRepeatValue.setText(currentRepeatValue); + repeatSelectedUnit.setText(currentRepeatUnit.isEmpty() ? context.getString(R.string.cycle_unit) : currentRepeatUnit); } - }); - // 根据初始状态设置可见性 - if (!repeatSwitch.isChecked()) { + } else { repeatInput.setVisibility(View.GONE); repeatSelectorContainer.setVisibility(View.GONE); - } else { - repeatSelectedUnit.setText(context.getString(R.string.infinitely_unit)); - etRepeatValue.setText(""); - etRepeatValue.setEnabled(false); - etRepeatValue.setInputType(InputType.TYPE_NULL); } - - repeatSelectorContainer.setOnClickListener(v -> showUnitSelection(v, TYPE_REPEAT)); } private void showUnitSelection(View anchorView, int type) { @@ -242,10 +278,10 @@ public class FloatingSettingDialogManager { String unitText = context.getString(R.string.milliseconds_unit); if (type == TYPE_INTERVAL) { intervalSelectedUnit.setText(unitText); - showInputValue("Interval", etIntervalValue.getText().toString(), unitText); + currentIntervalUnit = unitText; } else { tvSelectedUnit.setText(unitText); - showInputValue("Swipe Duration", etDurationValue.getText().toString(), unitText); + currentSwipeDurationUnit = unitText; } popupWindow.dismiss(); }); @@ -254,10 +290,10 @@ public class FloatingSettingDialogManager { String unitText = context.getString(R.string.seconds_unit); if (type == TYPE_INTERVAL) { intervalSelectedUnit.setText(unitText); - showInputValue("Interval", etIntervalValue.getText().toString(), unitText); + currentIntervalUnit = unitText; } else { tvSelectedUnit.setText(unitText); - showInputValue("Swipe Duration", etDurationValue.getText().toString(), unitText); + currentSwipeDurationUnit = unitText; } popupWindow.dismiss(); }); @@ -266,38 +302,41 @@ public class FloatingSettingDialogManager { tvMin.setOnClickListener(v -> { String unitText = context.getString(R.string.minutes_unit); intervalSelectedUnit.setText(unitText); + currentIntervalUnit = unitText; popupWindow.dismiss(); - showInputValue("Interval", etIntervalValue.getText().toString(), unitText); }); } } if (type == TYPE_REPEAT) { tvInfinitely.setOnClickListener(v -> { - repeatSelectedUnit.setText(context.getString(R.string.infinitely_unit)); + String unitText = context.getString(R.string.infinitely_unit); + repeatSelectedUnit.setText(unitText); + currentRepeatUnit = unitText; etRepeatValue.setText(""); etRepeatValue.setEnabled(false); etRepeatValue.setInputType(InputType.TYPE_NULL); popupWindow.dismiss(); - showInputValue("Repeat", etRepeatValue.getText().toString(), context.getString(R.string.infinitely_unit)); }); tvDuration.setOnClickListener(v -> { - repeatSelectedUnit.setText(context.getString(R.string.duration_unit)); - etRepeatValue.setText(""); + String unitText = context.getString(R.string.duration_unit); + repeatSelectedUnit.setText(unitText); + currentRepeatUnit = unitText; + etRepeatValue.setText(""); // 清空以便用户输入新值 etRepeatValue.setEnabled(true); etRepeatValue.setInputType(InputType.TYPE_CLASS_NUMBER); popupWindow.dismiss(); - showInputValue("Repeat", etRepeatValue.getText().toString(), context.getString(R.string.duration_unit)); }); tvCycle.setOnClickListener(v -> { - repeatSelectedUnit.setText(context.getString(R.string.cycle_unit)); + String unitText = context.getString(R.string.cycle_unit); + repeatSelectedUnit.setText(unitText); + currentRepeatUnit = unitText; etRepeatValue.setText(""); etRepeatValue.setEnabled(true); etRepeatValue.setInputType(InputType.TYPE_CLASS_NUMBER); popupWindow.dismiss(); - showInputValue("Repeat", etRepeatValue.getText().toString(), context.getString(R.string.cycle_unit)); }); } @@ -306,98 +345,164 @@ public class FloatingSettingDialogManager { } else { popupWindow.showAsDropDown(anchorView); } - } - private void showInputValue(String type, String value, String unit) { - String message = type + " 值: " + (value.isEmpty() ? "未输入" : value) + ", 单位: " + unit; - showToastOnUi(context, message); + private void saveCurrentSettings() { + currentIntervalValue = etIntervalValue.getText().toString(); + currentDurationValue = etDurationValue.getText().toString(); + currentRepeatValue = etRepeatValue.getText().toString(); + Log.d(TAG, "UI设置已保存到Manager: Interval=" + currentIntervalValue + " " + currentIntervalUnit + + ", Duration=" + currentDurationValue + " " + currentSwipeDurationUnit + + ", Repeat=" + currentRepeatValue + " " + currentRepeatUnit + + ", RepeatSwitch=" + isRepeatSwitchChecked); + } + + private void applySettingsToService() { AutoClickService service = AutoClickService.getInstance(); + if (service == null) { + Log.e(TAG, "AutoClickService 实例为空,无法应用设置。"); + showToastOnUi(context, "自动点击服务未运行,无法应用设置。"); + return; + } - switch (type) { - case "Interval": - long intervalValue = 0; - if (!value.isEmpty()) { - try { - intervalValue = Long.parseLong(value); - // 根据 unit 进行单位转换 - if (unit.equals(context.getString(R.string.seconds_unit))) { - intervalValue *= 1000; // 如果单位是秒,转换为毫秒 - } - if (service != null) { - service.setClickInterval(intervalValue); - } else { - Log.e(TAG, "AutoClickService 实例为空,无法设置点击间隔。"); - } - } catch (NumberFormatException e) { - Log.e(TAG, "点击间隔值解析错误: " + value, e); - showToastOnUi(context, "点击间隔值无效,请输入有效数字。"); - } + // 应用点击间隔 + long intervalValue = 0; + if (!currentIntervalValue.isEmpty()) { + try { + intervalValue = Long.parseLong(currentIntervalValue); + if (currentIntervalUnit.equals(context.getString(R.string.seconds_unit))) { + intervalValue *= 1000; + } else if (currentIntervalUnit.equals(context.getString(R.string.minutes_unit))) { + intervalValue *= 60 * 1000; } - // 你可以在这里将 intervalValue 保存到你的数据模型或 SharedPreferences - break; + service.setClickInterval(intervalValue); + Log.d(TAG, "应用点击间隔到服务: " + intervalValue + "ms"); + } catch (NumberFormatException e) { + Log.e(TAG, "点击间隔值解析错误: " + currentIntervalValue, e); + showToastOnUi(context, "点击间隔值无效,请输入有效数字。"); + } + } else { + // 如果为空,设置一个默认值或不设置,这取决于你的业务逻辑 + // service.setClickInterval(默认值); + Log.d(TAG, "点击间隔值为空,未应用到服务。"); + } - case "Swipe Duration": - int slideDurationValue = 0; - if (!value.isEmpty()) { - try { - slideDurationValue = Integer.parseInt(value); - if (unit.equals(context.getString(R.string.seconds_unit))) { - slideDurationValue *= 1000; // 如果单位是秒,转换为毫秒 - } - if (service != null) { - service.setSlideDuration(slideDurationValue); - } else { - Log.e(TAG, "AutoClickService 实例为空,无法设置滑动时长。"); - } - } catch (NumberFormatException e) { - Log.e(TAG, "滑动时长值解析错误: " + value, e); - showToastOnUi(context, "滑动时长值无效,请输入有效数字。"); - } + int slideDurationValue = 0; + if (!currentDurationValue.isEmpty()) { + try { + slideDurationValue = Integer.parseInt(currentDurationValue); + if (currentSwipeDurationUnit.equals(context.getString(R.string.seconds_unit))) { + slideDurationValue *= 1000; } - // 你可以在这里将 slideDurationValue 保存到你的数据模型或 SharedPreferences - break; + service.setSlideDuration(slideDurationValue); + Log.d(TAG, "应用滑动时长到服务: " + slideDurationValue + "ms"); + } catch (NumberFormatException e) { + Log.e(TAG, "滑动时长值解析错误: " + currentDurationValue, e); + showToastOnUi(context, "滑动时长值无效,请输入有效数字。"); + } + } else { + // 如果为空,设置一个默认值或不设置 + // service.setSlideDuration(默认值); + Log.d(TAG, "滑动时长值为空,未应用到服务。"); + } - case "Repeat": - if (unit.equals(context.getString(R.string.infinitely_unit))) { - // Repeat 为无限次,不需要处理输入值 - // 确保 etRepeatValue 是禁用的 + // 应用重复设置 + if (isRepeatSwitchChecked) { + if (currentRepeatUnit.equals(context.getString(R.string.infinitely_unit))) { + service.setLoopCount(0); // 无限循环 + Log.d(TAG, "应用重复模式: 无限次。"); + } else if (currentRepeatUnit.equals(context.getString(R.string.duration_unit))) { + long repeatDurationMillis = 0; + if (!currentRepeatValue.isEmpty()) { + try { + repeatDurationMillis = Long.parseLong(currentRepeatValue); + // 根据你的UI单位,这里可能需要进一步转换,例如用户输入的是秒 + // repeatDurationMillis *= 1000L; + service.setLoopTime(repeatDurationMillis); + Log.d(TAG, "应用重复时长: " + repeatDurationMillis + "ms。"); + } catch (NumberFormatException e) { + Log.e(TAG, "重复时长值解析错误: " + currentRepeatValue, e); + showToastOnUi(context, "重复时长值无效,请输入有效数字。"); + service.setLoopTime(0); // 错误时,取消时间循环 + } + } else { + service.setLoopTime(0); // 如果值为空,取消时间循环 + Log.d(TAG, "重复时长值为空,取消时间循环。"); + } + service.setLoopCount(0); // 确保与时间循环互斥 + } else if (currentRepeatUnit.equals(context.getString(R.string.cycle_unit))) { + int repeatCount = 0; + if (!currentRepeatValue.isEmpty()) { + try { + repeatCount = Integer.parseInt(currentRepeatValue); + service.setLoopCount(repeatCount); + Log.d(TAG, "应用重复次数: " + repeatCount + "次。"); + } catch (NumberFormatException e) { + Log.e(TAG, "重复次数值解析错误: " + currentRepeatValue, e); + showToastOnUi(context, "重复次数值无效,请输入有效数字。"); + service.setLoopCount(1); // 错误时,设置为执行一次 + } + } else { + service.setLoopCount(1); // 如果值为空,设置为执行一次 + Log.d(TAG, "重复次数值为空,设置为执行一次。"); + } + service.setLoopTime(0); // 确保与次数循环互斥 + } + } else { + service.setLoopCount(1); + service.setLoopTime(0); + Log.d(TAG, "重复开关关闭,服务设置为执行一次。"); + } + } + + private void restoreUIState() { + // 恢复点击间隔 + if (!currentIntervalValue.isEmpty()) { + etIntervalValue.setText(currentIntervalValue); + } + if (!currentIntervalUnit.isEmpty()) { + intervalSelectedUnit.setText(currentIntervalUnit); + } else { + intervalSelectedUnit.setText(context.getString(R.string.milliseconds_unit)); // 默认单位 + currentIntervalUnit = context.getString(R.string.milliseconds_unit); + } + + // 恢复滑动时长 + if (!currentDurationValue.isEmpty()) { + etDurationValue.setText(currentDurationValue); + } + if (!currentSwipeDurationUnit.isEmpty()) { + tvSelectedUnit.setText(currentSwipeDurationUnit); + } else { + tvSelectedUnit.setText(context.getString(R.string.milliseconds_unit)); // 默认单位 + currentSwipeDurationUnit = context.getString(R.string.milliseconds_unit); + } + + // 恢复重复设置 + repeatSwitch.setChecked(isRepeatSwitchChecked); + updateRepeatUI(isRepeatSwitchChecked); // 根据开关状态更新UI + + if (isRepeatSwitchChecked) { + if (currentRepeatUnit.isEmpty()) { + // 如果没有保存的单位,默认为无限次 + repeatSelectedUnit.setText(context.getString(R.string.infinitely_unit)); + currentRepeatUnit = context.getString(R.string.infinitely_unit); + etRepeatValue.setText(""); + etRepeatValue.setEnabled(false); + etRepeatValue.setInputType(InputType.TYPE_NULL); + } else { + repeatSelectedUnit.setText(currentRepeatUnit); + if (currentRepeatUnit.equals(context.getString(R.string.infinitely_unit))) { etRepeatValue.setText(""); etRepeatValue.setEnabled(false); etRepeatValue.setInputType(InputType.TYPE_NULL); - showToastOnUi(context, "重复模式: 无限次"); - // AutoClickService 内部已是循环执行,无限次意味着一直运行直到用户停止。 - // 如果需要更明确的无限次模式,可以在 AutoClickService 中设置一个标志位。 - } else if (unit.equals(context.getString(R.string.duration_unit))) { - // Repeat 为时长,处理时间格式的输入 (例如 "HH:mm:ss" -> 毫秒) - String durationValueStr = value.isEmpty() ? "0" : value; - // TODO: 你需要在这里添加解析时间格式的逻辑,例如: - // long parsedDuration = parseDurationString(durationValueStr); // 自定义方法解析时间 - // 如果 AutoClickService 需要总运行时间限制,你需要修改 AutoClickService 来支持。 - // 否则,你可能需要在悬浮窗管理类中实现一个计时器,当达到设定时长时调用 service.stopClicking()。 - etRepeatValue.setEnabled(true); - etRepeatValue.setInputType(InputType.TYPE_CLASS_NUMBER); // 这里暂时设置为数字,如果需要特定时间输入,需要更复杂的实现 - showToastOnUi(context, "重复时长: " + durationValueStr); - } else if (unit.equals(context.getString(R.string.cycle_unit))) { - // Repeat 为次数,处理整数输入 - int repeatCount = 0; - if (!value.isEmpty()) { - try { - repeatCount = Integer.parseInt(value); - // TODO: 如果 AutoClickService 需要总循环次数限制,你需要修改 AutoClickService 来支持。 - // 可以在 AutoClickService 中添加一个变量来跟踪剩余循环次数,并在每次事件队列执行完毕后递减。 - // 当次数为 0 时,调用 stopClicking()。 - } catch (NumberFormatException e) { - Log.e(TAG, "重复次数值解析错误: " + value, e); - showToastOnUi(context, "重复次数值无效,请输入有效数字。"); - } - } + } else { + etRepeatValue.setText(currentRepeatValue); etRepeatValue.setEnabled(true); etRepeatValue.setInputType(InputType.TYPE_CLASS_NUMBER); - showToastOnUi(context, "重复次数: " + repeatCount); } - break; + } } } diff --git a/app/src/main/java/com/auto/clicker/autoclicker/view/FloatingViewManager.java b/app/src/main/java/com/auto/clicker/autoclicker/view/FloatingViewManager.java index 9a2f15a..dfb6b03 100644 --- a/app/src/main/java/com/auto/clicker/autoclicker/view/FloatingViewManager.java +++ b/app/src/main/java/com/auto/clicker/autoclicker/view/FloatingViewManager.java @@ -97,7 +97,6 @@ public class FloatingViewManager { this.floatingTabDialogManager = new FloatingTabDialogManager(context); this.floatingSettingDialogManager = new FloatingSettingDialogManager(context); this.floatingTabDialogManager.setFloatingViewManager(this); - this.floatingSettingDialogManager.setFloatingViewManager(this); this.eventRepository = new EventRepository(context.getApplicationContext()); this.touchPointSize = 100; @@ -926,4 +925,8 @@ public class FloatingViewManager { playButton.setImageResource(R.drawable.play); } } + + public EventRepository getEventRepository() { + return eventRepository; + } } \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_mode_item_selector.xml b/app/src/main/res/drawable/bg_mode_item_selector.xml new file mode 100644 index 0000000..0650c4d --- /dev/null +++ b/app/src/main/res/drawable/bg_mode_item_selector.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/bg_selected.xml b/app/src/main/res/drawable/bg_selected.xml new file mode 100644 index 0000000..8ad237a --- /dev/null +++ b/app/src/main/res/drawable/bg_selected.xml @@ -0,0 +1,12 @@ + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_circle_filled_check.xml b/app/src/main/res/drawable/ic_circle_filled_check.xml new file mode 100644 index 0000000..15589a4 --- /dev/null +++ b/app/src/main/res/drawable/ic_circle_filled_check.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_circle_hollow.xml b/app/src/main/res/drawable/ic_circle_hollow.xml new file mode 100644 index 0000000..741c6be --- /dev/null +++ b/app/src/main/res/drawable/ic_circle_hollow.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/import_script.xml b/app/src/main/res/drawable/import_script.xml new file mode 100644 index 0000000..1afdd2e --- /dev/null +++ b/app/src/main/res/drawable/import_script.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/multi_dialog.xml b/app/src/main/res/drawable/multi_dialog.xml new file mode 100644 index 0000000..32cd411 --- /dev/null +++ b/app/src/main/res/drawable/multi_dialog.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + diff --git a/app/src/main/res/drawable/single_dialog.xml b/app/src/main/res/drawable/single_dialog.xml new file mode 100644 index 0000000..5f07947 --- /dev/null +++ b/app/src/main/res/drawable/single_dialog.xml @@ -0,0 +1,14 @@ + + + + diff --git a/app/src/main/res/layout/activity_scripts.xml b/app/src/main/res/layout/activity_scripts.xml index 0cbfcd9..128218e 100644 --- a/app/src/main/res/layout/activity_scripts.xml +++ b/app/src/main/res/layout/activity_scripts.xml @@ -30,6 +30,12 @@ app:layout_constraintStart_toEndOf="@+id/btn_back" app:layout_constraintTop_toTopOf="@+id/btn_back" /> + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_create_mode_selection.xml b/app/src/main/res/layout/dialog_create_mode_selection.xml new file mode 100644 index 0000000..4ec0e22 --- /dev/null +++ b/app/src/main/res/layout/dialog_create_mode_selection.xml @@ -0,0 +1,167 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/floating_setting_dialog.xml b/app/src/main/res/layout/floating_setting_dialog.xml index 6bb6769..94c8efc 100644 --- a/app/src/main/res/layout/floating_setting_dialog.xml +++ b/app/src/main/res/layout/floating_setting_dialog.xml @@ -2,7 +2,7 @@ @@ -285,4 +285,33 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_solution.xml b/app/src/main/res/layout/item_solution.xml index 2491fe7..1c86ff1 100644 --- a/app/src/main/res/layout/item_solution.xml +++ b/app/src/main/res/layout/item_solution.xml @@ -1,28 +1,28 @@ + app:layout_constraintTop_toBottomOf="@+id/name" /> + + diff --git a/app/src/main/res/layout/option_menu_layout.xml b/app/src/main/res/layout/option_menu_layout.xml new file mode 100644 index 0000000..5466184 --- /dev/null +++ b/app/src/main/res/layout/option_menu_layout.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 2e305d9..9fe88d3 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -10,4 +10,5 @@ #446672 #1A546B #171717 + #368298 \ No newline at end of file