From 5a78b467fc9f4e07c3f32cf051786bd0900c0168 Mon Sep 17 00:00:00 2001 From: lihongwei Date: Mon, 30 Jun 2025 14:18:51 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8A=9F=E8=83=BD=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../room/SolutionWithEventsAndPoints.java | 10 +- .../room/repository/EventRepository.java | 67 ++++++++++ .../ui/activity/menu/ScriptsActivity.java | 124 +++++++++++------- .../ui/adapter/floating/SolutionAdapter.java | 18 +-- .../autoclicker/view/FloatingViewManager.java | 2 +- app/src/main/res/layout/activity_scripts.xml | 42 ++++-- app/src/main/res/layout/item_solution.xml | 4 +- .../main/res/layout/option_menu_layout.xml | 8 +- 8 files changed, 197 insertions(+), 78 deletions(-) diff --git a/app/src/main/java/com/auto/clicker/autoclicker/room/SolutionWithEventsAndPoints.java b/app/src/main/java/com/auto/clicker/autoclicker/room/SolutionWithEventsAndPoints.java index 0bc5aa7..3ac99e0 100644 --- a/app/src/main/java/com/auto/clicker/autoclicker/room/SolutionWithEventsAndPoints.java +++ b/app/src/main/java/com/auto/clicker/autoclicker/room/SolutionWithEventsAndPoints.java @@ -9,8 +9,11 @@ import com.auto.clicker.autoclicker.room.entity.Solution; import java.util.List; +import com.google.gson.annotations.Expose; // 确保导入 @Expose + public class SolutionWithEventsAndPoints { @Embedded + @Expose public Solution solution; @Relation( @@ -18,10 +21,12 @@ public class SolutionWithEventsAndPoints { entityColumn = "solution_id", entity = EventEntity.class ) + @Expose public List events; public static class EventWithPoints { @Embedded + @Expose public EventEntity eventEntity; @Relation( @@ -29,6 +34,7 @@ public class SolutionWithEventsAndPoints { entityColumn = "pointId", entity = TouchPoint.class ) + @Expose public TouchPoint clickTouchPoint; @Relation( @@ -36,6 +42,7 @@ public class SolutionWithEventsAndPoints { entityColumn = "pointId", entity = TouchPoint.class ) + @Expose public TouchPoint slideStartTouchPoint; @Relation( @@ -43,6 +50,7 @@ public class SolutionWithEventsAndPoints { entityColumn = "pointId", entity = TouchPoint.class ) + @Expose public TouchPoint slideEndTouchPoint; } -} +} \ No newline at end of file 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 e006279..a426c66 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 @@ -164,6 +164,73 @@ public class EventRepository { }); } + @Transaction + public void importSolutionFromData(SolutionWithEventsAndPoints importedData, RepositoryCallback callback) { + databaseWriteExecutor.execute(() -> { + try { + Solution newSolution = new Solution(importedData.solution.solutionName, importedData.solution.mode); + long newSolutionId = solutionDao.insertSolution(newSolution); + + if (newSolutionId == -1) { + throw new RuntimeException("Failed to import solution: Could not insert new Solution."); + } + + for (SolutionWithEventsAndPoints.EventWithPoints eventWithPoints : importedData.events) { + Long newClickPointId = null; + if (eventWithPoints.clickTouchPoint != null) { + TouchPoint newClickPoint = new TouchPoint(eventWithPoints.clickTouchPoint.x, eventWithPoints.clickTouchPoint.y); + newClickPointId = touchPointDao.insertPoint(newClickPoint); + if (newClickPointId == -1) throw new RuntimeException("Failed to import click point."); + } + + Long newSlideStartPointId = null; + if (eventWithPoints.slideStartTouchPoint != null) { + TouchPoint newSlideStartPoint = new TouchPoint(eventWithPoints.slideStartTouchPoint.x, eventWithPoints.slideStartTouchPoint.y); + newSlideStartPointId = touchPointDao.insertPoint(newSlideStartPoint); + if (newSlideStartPointId == -1) throw new RuntimeException("Failed to import slide start point."); + } + + Long newSlideEndPointId = null; + if (eventWithPoints.slideEndTouchPoint != null) { + TouchPoint newSlideEndPoint = new TouchPoint(eventWithPoints.slideEndTouchPoint.x, eventWithPoints.slideEndTouchPoint.y); + newSlideEndPointId = touchPointDao.insertPoint(newSlideEndPoint); + if (newSlideEndPointId == -1) throw new RuntimeException("Failed to import slide end point."); + } + + EventEntity newEventEntity = new EventEntity( + newSolutionId, // 关联到新生成的方案ID + eventWithPoints.eventEntity.eventType, + eventWithPoints.eventEntity.orderInSolution, + newClickPointId, + newSlideStartPointId, + newSlideEndPointId, + eventWithPoints.eventEntity.duration + ); + long newEventEntityId = eventEntityDao.insertEventEntity(newEventEntity); + if (newEventEntityId == -1) { + throw new RuntimeException("Failed to import event entity."); + } + } + if (callback != null) callback.onComplete(null); + } catch (Exception e) { + Log.e("EventRepository", "Error importing solution: " + e.getMessage(), e); + if (callback != null) { + callback.onError(e); + } + } + }); + } + + public SolutionWithEventsAndPoints importSolutionFromJson(String jsonString) { + try { + Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create(); + return gson.fromJson(jsonString, SolutionWithEventsAndPoints.class); + } catch (Exception e) { + Log.e("EventRepository", "Error parsing JSON for import: " + e.getMessage(), e); + return null; + } + } + @Transaction public void saveDuplicateSolution(SolutionWithEventsAndPoints originalData, RepositoryCallback callback) { databaseWriteExecutor.execute(() -> { 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 b478618..58ed125 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 @@ -8,7 +8,6 @@ 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.annotation.Nullable; @@ -28,6 +27,9 @@ 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 java.io.BufferedReader; +import java.io.InputStream; +import java.io.InputStreamReader; import java.io.OutputStream; import java.util.ArrayList; import java.util.List; @@ -40,6 +42,7 @@ public class ScriptsActivity extends AppCompatActivity implements SolutionAdapte private static final String TAG = "ScriptsActivity"; private static final int CREATE_FILE_REQUEST_CODE = 1; + private static final int PICK_FILE_REQUEST_CODE = 2; @Override protected void onCreate(Bundle savedInstanceState) { @@ -54,12 +57,10 @@ public class ScriptsActivity extends AppCompatActivity implements SolutionAdapte }); binding.scriptsRecyclerView.setLayoutManager(new LinearLayoutManager(this)); - // 将 this 作为 OnSolutionClickListener 和 OnOptionMenuClickListener 传入 solutionsAdapter = new SolutionAdapter(new ArrayList<>(), this::onSolutionSelected, this); binding.scriptsRecyclerView.setAdapter(solutionsAdapter); - // 确保 FloatingViewManager 在 Activity 创建时被初始化 - floatingViewManager = new FloatingViewManager(this); // 如果 FloatingViewManager 依赖 Context,这里需要传递 + floatingViewManager = new FloatingViewManager(this); binding.btnBack.setOnClickListener(v -> finish()); @@ -69,13 +70,11 @@ public class ScriptsActivity extends AppCompatActivity implements SolutionAdapte }); binding.importScripts.setOnClickListener(v -> { - Toast.makeText(this, "导入方案功能待实现", Toast.LENGTH_SHORT).show(); - // TODO: 实现导入方案的逻辑 + pickJsonFileForImport(); }); - binding.fabImportScripts.setOnClickListener(v -> { - Toast.makeText(this, "右下角导入方案功能待实现", Toast.LENGTH_SHORT).show(); - // TODO: 实现右下角导入方案的逻辑,可能与上面的importScripts按钮功能相同或类似 + binding.customImportView.setOnClickListener(v -> { + pickJsonFileForImport(); }); loadSolutions(); @@ -90,24 +89,16 @@ public class ScriptsActivity extends AppCompatActivity implements SolutionAdapte private void loadSolutions() { if (floatingViewManager == null) { Log.e(TAG, "FloatingViewManager 未设置,无法加载方案列表。"); - Toast.makeText(this, "加载方案列表功能不可用,请联系开发者。", Toast.LENGTH_SHORT).show(); updateLayout(true); return; } - // 确保 FloatingViewManager 能够提供 EventRepository 实例 - // 假设 FloatingViewManager 内部管理着 EventRepository floatingViewManager.getEventRepository().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(); - } }); } @@ -115,7 +106,6 @@ public class ScriptsActivity extends AppCompatActivity implements SolutionAdapte 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); }); } @@ -126,11 +116,11 @@ public class ScriptsActivity extends AppCompatActivity implements SolutionAdapte if (isEmpty) { binding.noScriptsGroup.setVisibility(View.VISIBLE); binding.scriptsRecyclerView.setVisibility(View.GONE); - binding.fabImportScripts.setVisibility(View.GONE); + binding.customImportView.setVisibility(View.GONE); } else { binding.noScriptsGroup.setVisibility(View.GONE); binding.scriptsRecyclerView.setVisibility(View.VISIBLE); - binding.fabImportScripts.setVisibility(View.VISIBLE); + binding.customImportView.setVisibility(View.VISIBLE); } } @@ -138,10 +128,8 @@ public class ScriptsActivity extends AppCompatActivity implements SolutionAdapte 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(); } } @@ -180,8 +168,7 @@ public class ScriptsActivity extends AppCompatActivity implements SolutionAdapte @Override public void onComplete(Void result) { new Handler(Looper.getMainLooper()).post(() -> { - Toast.makeText(ScriptsActivity.this, "方案已重命名为: " + newName, Toast.LENGTH_SHORT).show(); - loadSolutions(); // 刷新列表 + loadSolutions(); }); } @@ -189,14 +176,9 @@ public class ScriptsActivity extends AppCompatActivity implements SolutionAdapte 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(); - } else { - Toast.makeText(ScriptsActivity.this, "新名称与原名称相同。", Toast.LENGTH_SHORT).show(); } }); builder.setNegativeButton("取消", (dialog, which) -> dialog.cancel()); @@ -211,23 +193,18 @@ public class ScriptsActivity extends AppCompatActivity implements SolutionAdapte floatingViewManager.getEventRepository().saveDuplicateSolution(originalData, new EventRepository.RepositoryCallback() { @Override public void onComplete(Void result) { - new Handler(Looper.getMainLooper()).post(() -> { - Toast.makeText(ScriptsActivity.this, "方案 '" + originalSolution.getSolutionName() + "' 已复制成功!", Toast.LENGTH_SHORT).show(); - loadSolutions(); // 刷新列表 - }); + new Handler(Looper.getMainLooper()).post(ScriptsActivity.this::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(); }); } } @@ -236,7 +213,6 @@ public class ScriptsActivity extends AppCompatActivity implements SolutionAdapte 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(); }); } }); @@ -256,8 +232,7 @@ public class ScriptsActivity extends AppCompatActivity implements SolutionAdapte @Override public void onComplete(Void result) { new Handler(Looper.getMainLooper()).post(() -> { - Toast.makeText(ScriptsActivity.this, "方案 '" + solution.getSolutionName() + "' 已删除。", Toast.LENGTH_SHORT).show(); - loadSolutions(); // 刷新列表 + loadSolutions(); }); } @@ -265,12 +240,18 @@ public class ScriptsActivity extends AppCompatActivity implements SolutionAdapte 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 pickJsonFileForImport() { + Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); + intent.addCategory(Intent.CATEGORY_OPENABLE); + intent.setType("application/json"); + startActivityForResult(intent, PICK_FILE_REQUEST_CODE); + } + private void exportSolution(final Solution solution) { floatingViewManager.getEventRepository().getSolutionWithEventsAndPoints(solution.getSolutionId(), new EventRepository.RepositoryCallback() { @Override @@ -283,11 +264,7 @@ public class ScriptsActivity extends AppCompatActivity implements SolutionAdapte intent.setType("application/json"); intent.putExtra(Intent.EXTRA_TITLE, solution.getSolutionName() + ".json"); startActivityForResult(intent, CREATE_FILE_REQUEST_CODE); - } else { - Toast.makeText(ScriptsActivity.this, "无法生成导出数据。", Toast.LENGTH_SHORT).show(); } - } else { - Toast.makeText(ScriptsActivity.this, "无法获取方案数据进行导出。", Toast.LENGTH_SHORT).show(); } } @@ -295,12 +272,57 @@ public class ScriptsActivity extends AppCompatActivity implements SolutionAdapte 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 importSolutionFromFile(Uri uri) { + try { + InputStream inputStream = getContentResolver().openInputStream(uri); + if (inputStream == null) { + return; + } + + StringBuilder stringBuilder = new StringBuilder(); + try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) { + String line; + while ((line = reader.readLine()) != null) { + stringBuilder.append(line); + } + } + + String jsonString = stringBuilder.toString(); + if (jsonString.isEmpty()) { + return; + } + + SolutionWithEventsAndPoints importedData = floatingViewManager.getEventRepository().importSolutionFromJson(jsonString); + + if (importedData != null && importedData.solution != null) { + // 保存到数据库 + floatingViewManager.getEventRepository().importSolutionFromData(importedData, new EventRepository.RepositoryCallback() { + @Override + public void onComplete(Void result) { + new Handler(Looper.getMainLooper()).post(() -> { + loadSolutions(); // 刷新列表 + }); + } + + @Override + public void onError(Exception e) { + new Handler(Looper.getMainLooper()).post(() -> { + Log.e(TAG, "导入方案失败: " + e.getMessage(), e); + }); + } + }); + } + + } catch (Exception e) { + Log.e(TAG, "读取或解析导入文件失败: " + e.getMessage(), e); + } + } + @Override protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { super.onActivityResult(requestCode, resultCode, data); @@ -311,20 +333,20 @@ public class ScriptsActivity extends AppCompatActivity implements SolutionAdapte 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; // 清除临时数据 + tempExportJson = null; } - } else { - Toast.makeText(this, "没有可导出的数据。", Toast.LENGTH_SHORT).show(); } - } else { - Toast.makeText(this, "文件创建操作被取消或数据无效。", Toast.LENGTH_SHORT).show(); + } + } else if (requestCode == PICK_FILE_REQUEST_CODE && resultCode == RESULT_OK) { + if (data != null && data.getData() != null) { + Uri uri = data.getData(); + importSolutionFromFile(uri); } } } + } \ 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 545d6be..f8262ff 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 @@ -76,12 +76,12 @@ public class SolutionAdapter extends RecyclerView.Adapter { - holder.showOptionMenu(solution, optionMenuListener); // 调用方法显示菜单 + holder.showOptionMenu(solution, optionMenuListener); }); } else { - holder.option.setVisibility(View.GONE); // 隐藏按钮 + holder.option.setVisibility(View.GONE); } } @@ -114,28 +114,25 @@ public class SolutionAdapter extends RecyclerView.Adapter { menuListener.onRenameClick(solution); - popupWindow.dismiss(); // 关闭弹窗 + popupWindow.dismiss(); }); tvDuplicate.setOnClickListener(v -> { @@ -153,7 +150,6 @@ public class SolutionAdapter extends RecyclerView.Adapter - + app:layout_constraintEnd_toEndOf="parent"> + + + + + + \ 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 8354f60..e50e320 100644 --- a/app/src/main/res/layout/item_solution.xml +++ b/app/src/main/res/layout/item_solution.xml @@ -48,7 +48,9 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/script_option" - android:visibility="gone" + android:paddingEnd="10dp" + android:paddingStart="10dp" + android:visibility="visible" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent" /> diff --git a/app/src/main/res/layout/option_menu_layout.xml b/app/src/main/res/layout/option_menu_layout.xml index 5466184..500bc08 100644 --- a/app/src/main/res/layout/option_menu_layout.xml +++ b/app/src/main/res/layout/option_menu_layout.xml @@ -14,7 +14,7 @@ android:background="?attr/selectableItemBackground" android:padding="12dp" android:text="Rename" - android:textColor="@color/black" + android:textColor="@color/white" android:textSize="16sp" /> \ No newline at end of file