功能添加

This commit is contained in:
lihongwei 2025-06-30 11:19:16 +08:00
parent 27727eaff0
commit 73e2478168
10 changed files with 188 additions and 211 deletions

View File

@ -9,7 +9,6 @@ import com.auto.clicker.autoclicker.room.entity.Solution;
import java.util.List;
// 这个 POJO 用于表示一个方案以及该方案下的所有事件及其关联的坐标点
public class SolutionWithEventsAndPoints {
@Embedded
public Solution solution;
@ -19,9 +18,8 @@ public class SolutionWithEventsAndPoints {
entityColumn = "solution_id",
entity = EventEntity.class
)
public List<EventWithPoints> events; // 注意这里我们用 EventWithPoints
public List<EventWithPoints> events;
// 内部类用于表示一个事件以及它关联的起点和终点
public static class EventWithPoints {
@Embedded
public EventEntity eventEntity;
@ -31,20 +29,20 @@ public class SolutionWithEventsAndPoints {
entityColumn = "pointId",
entity = TouchPoint.class
)
public TouchPoint clickTouchPoint; // 单击事件的点
public TouchPoint clickTouchPoint;
@Relation(
parentColumn = "slide_start_point_id",
entityColumn = "pointId",
entity = TouchPoint.class
)
public TouchPoint slideStartTouchPoint; // 滑动事件的起点
public TouchPoint slideStartTouchPoint;
@Relation(
parentColumn = "slide_end_point_id",
entityColumn = "pointId",
entity = TouchPoint.class
)
public TouchPoint slideEndTouchPoint; // 滑动事件的终点
public TouchPoint slideEndTouchPoint;
}
}

View File

@ -15,7 +15,7 @@ import java.util.List;
@Dao
public interface SolutionDao {
@Insert
long insertSolution(Solution solution); // 返回新插入的 solutionId
long insertSolution(Solution solution);
@Query("SELECT * FROM solutions")
List<Solution> getAllSolutions();
@ -27,18 +27,22 @@ public interface SolutionDao {
Solution getSolutionByName(String solutionName);
@Delete
int deleteSolution(Solution solution); // 返回删除的行数
int deleteSolution(Solution solution);
@Update
int updateSolution(Solution solution); // 返回更新的行数
int updateSolution(Solution solution);
// SolutionDao 中添加
@Transaction // 表示这是一个事务操作确保数据一致性
@Transaction
@Query("SELECT * FROM solutions WHERE solutionId = :solutionId LIMIT 1")
SolutionWithEventsAndPoints getSolutionWithEventsAndPoints(long solutionId);
// 也可以获取所有方案及其事件
@Transaction
@Query("SELECT * FROM solutions")
List<SolutionWithEventsAndPoints> getAllSolutionsWithEventsAndPoints();
@Query("UPDATE solutions SET solution_name = :newName WHERE solutionId = :solutionId")
int updateSolutionName(long solutionId, String newName);
@Query("DELETE FROM solutions WHERE solutionId = :solutionId")
void deleteSolutionById(long solutionId);
}

View File

@ -7,6 +7,8 @@ import androidx.room.Entity;
import androidx.room.ForeignKey;
import androidx.room.PrimaryKey;
import com.google.gson.annotations.Expose;
@Entity(tableName = "events",
foreignKeys = {
@ForeignKey(entity = Solution.class,
@ -27,27 +29,35 @@ import androidx.room.PrimaryKey;
onDelete = CASCADE)
})
public class EventEntity {
@Expose
@PrimaryKey(autoGenerate = true)
public long eventId;
@Expose
@ColumnInfo(name = "solution_id")
public long solutionId;
@Expose
@ColumnInfo(name = "event_type")
public String eventType;
@Expose
@ColumnInfo(name = "order_in_solution")
public int orderInSolution;
@Expose
@ColumnInfo(name = "click_point_id")
public Long clickPointId;
@Expose
@ColumnInfo(name = "slide_start_point_id")
public Long slideStartPointId;
@Expose
@ColumnInfo(name = "slide_end_point_id")
public Long slideEndPointId;
@Expose
@ColumnInfo(name = "duration")
public long duration;

View File

@ -8,14 +8,20 @@ import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.PrimaryKey;
import com.google.gson.annotations.Expose;
@Entity(tableName = "solutions")
public class Solution {
@Expose
@PrimaryKey(autoGenerate = true)
public long solutionId;
@Expose
@ColumnInfo(name = "solution_name")
public String solutionName;
@Expose
@ColumnInfo(name = "mode")
public int mode;

View File

@ -4,16 +4,21 @@ import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.PrimaryKey;
import com.google.gson.annotations.Expose;
@Entity(tableName = "touchPoints")
public class TouchPoint {
@Expose
@PrimaryKey(autoGenerate = true)
public long pointId;
@Expose
@ColumnInfo(name = "x_coordinate")
public int x;
@Expose
@ColumnInfo(name = "y_coordinate")
public int y;

View File

@ -3,6 +3,8 @@ package com.auto.clicker.autoclicker.room.repository;
import android.content.Context;
import android.util.Log;
import androidx.room.Transaction;
import com.auto.clicker.autoclicker.data.Event;
import com.auto.clicker.autoclicker.data.EventWrapper;
import com.auto.clicker.autoclicker.data.PointEvent;
@ -15,6 +17,8 @@ import com.auto.clicker.autoclicker.room.dao.TouchPointDao;
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.google.gson.Gson;
import com.google.gson.GsonBuilder;
import java.util.List;
import java.util.concurrent.ExecutorService;
@ -27,30 +31,27 @@ public class EventRepository {
private final ExecutorService databaseWriteExecutor;
public EventRepository(Context context) {
AppDatabase db = AppDatabase.getInstance(context); // Updated to getInstance
AppDatabase db = AppDatabase.getInstance(context);
this.solutionDao = db.solutionDao();
this.touchPointDao = db.pointDao();
this.eventEntityDao = db.eventEntityDao();
this.databaseWriteExecutor = Executors.newFixedThreadPool(2);
}
// --- 插入操作 ---
public void insertSolutionWithEvents(Solution solution, List<EventWrapper> runtimeEvents, RepositoryCallback<Long> callback) {
databaseWriteExecutor.execute(() -> {
long solutionId = -1; // Initialize solutionId outside try-catch for broader scope
long solutionId = -1;
try {
// 1. 插入 Solution
solutionId = solutionDao.insertSolution(solution);
if (solutionId == -1) {
throw new RuntimeException("Failed to insert solution.");
}
// 2. 遍历 runtimeEvents插入 TouchPoint EventEntity
for (EventWrapper wrapper : runtimeEvents) {
Event event = wrapper.getEvent();
int order = wrapper.getOrder(); // Using order from EventWrapper
int order = wrapper.getOrder();
long eventEntityId = -1; // To store the result of event entity insertion
long eventEntityId;
if (wrapper.getType() == EventWrapper.EventType.POINT && event instanceof PointEvent) {
PointEvent pointEvent = (PointEvent) event;
@ -59,15 +60,14 @@ public class EventRepository {
if (pointId == -1)
throw new RuntimeException("Failed to insert touch point for PointEvent.");
// 调用 EventEntity 的唯一构造函数将滑动相关字段设为 null 或默认值
EventEntity eventEntity = new EventEntity(
(int) solutionId, // Ensure type compatibility if solutionId is long
solutionId, // 使用 long 类型的 solutionId
wrapper.getType().name(),
order,
pointId, // clickPointId
null, // slideStartPointId (null for point event)
null, // slideEndPointId (null for point event)
0 // duration (0 for point event)
pointId,
null,
null,
0
);
eventEntityId = eventEntityDao.insertEventEntity(eventEntity);
if (eventEntityId == -1)
@ -85,15 +85,14 @@ public class EventRepository {
if (endPointId == -1)
throw new RuntimeException("Failed to insert end touch point for SlideEvent.");
// 调用 EventEntity 的唯一构造函数将点击相关字段设为 null
EventEntity eventEntity = new EventEntity(
(int) solutionId,
solutionId, // 使用 long 类型的 solutionId
wrapper.getType().name(),
order,
null,
startPointId,
endPointId,
0
0 // duration for slide events, you might want to get this from SlideEvent
);
eventEntityId = eventEntityDao.insertEventEntity(eventEntity);
if (eventEntityId == -1)
@ -101,13 +100,10 @@ public class EventRepository {
}
}
if (callback != null) {
// Post the result back to the main thread if needed for UI updates
// For simplicity, directly calling callback here; in a real app, use Handler or LiveData
callback.onComplete(solutionId);
}
} catch (Exception e) {
Log.e("EventRepository", "Error inserting solution with events: " + e.getMessage(), e);
// Consider rolling back the transaction if any part fails
if (callback != null) {
callback.onError(e);
}
@ -115,7 +111,6 @@ public class EventRepository {
});
}
// --- 查询操作 ---
public void getAllSolutions(RepositoryCallback<List<Solution>> callback) {
databaseWriteExecutor.execute(() -> {
try {
@ -128,6 +123,35 @@ public class EventRepository {
});
}
public void updateSolutionName(long solutionId, String newName, RepositoryCallback<Void> callback) {
databaseWriteExecutor.execute(() -> {
try {
int updatedRows = solutionDao.updateSolutionName(solutionId, newName);
if (updatedRows > 0) {
if (callback != null) callback.onComplete(null);
} else {
throw new RuntimeException("Failed to update solution name, solution not found.");
}
} catch (Exception e) {
Log.e("EventRepository", "Error updating solution name: " + e.getMessage(), e);
if (callback != null) callback.onError(e);
}
});
}
@Transaction
public void deleteSolution(long solutionId, RepositoryCallback<Void> callback) {
databaseWriteExecutor.execute(() -> {
try {
solutionDao.deleteSolutionById(solutionId);
if (callback != null) callback.onComplete(null);
} catch (Exception e) {
Log.e("EventRepository", "Error deleting solution: " + e.getMessage(), e);
if (callback != null) callback.onError(e);
}
});
}
public void getSolutionWithEventsAndPoints(long solutionId, RepositoryCallback<SolutionWithEventsAndPoints> callback) {
databaseWriteExecutor.execute(() -> {
try {
@ -140,34 +164,69 @@ public class EventRepository {
});
}
// public void deleteSolution(Solution solution, RepositoryCallback<Integer> 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);
// }
// });
// }
@Transaction
public void saveDuplicateSolution(SolutionWithEventsAndPoints originalData, RepositoryCallback<Void> callback) {
databaseWriteExecutor.execute(() -> {
try {
// 创建新的 Solution 实例
Solution newSolution = new Solution(originalData.solution.solutionName + " (1)", originalData.solution.mode);
long newSolutionId = solutionDao.insertSolution(newSolution);
public void updateSolutionName(long solutionId, String newName, RepositoryCallback<Void> callback) {
if (newSolutionId == -1) {
throw new RuntimeException("Failed to duplicate solution: Could not insert new Solution.");
}
for (SolutionWithEventsAndPoints.EventWithPoints eventWithPoints : originalData.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 duplicate 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 duplicate 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 duplicate slide end point.");
}
// 创建新的 EventEntity 实例
EventEntity newEventEntity = new EventEntity(
newSolutionId,
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 duplicate event entity.");
}
}
if (callback != null) callback.onComplete(null);
} catch (Exception e) {
Log.e("EventRepository", "Error duplicating solution: " + e.getMessage(), e);
if (callback != null) callback.onError(e);
}
});
}
public void deleteSolution(long solutionId, RepositoryCallback<Void> callback) {
}
// 用于复制方案需要保存一个新的方案以及其关联的事件和点
public void saveSolutionWithEventsAndPoints(Solution solution, List<SolutionWithEventsAndPoints.EventWithPoints> eventsWithPoints, RepositoryCallback<Void> callback) {
}
// 获取某个方案及其所有事件和点的数据
public void getSolutionWithEventsAndPoints(String solutionId, RepositoryCallback<SolutionWithEventsAndPoints> callback) {
public String exportSolutionToJson(SolutionWithEventsAndPoints data) {
Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().setPrettyPrinting().create();
return gson.toJson(data);
}
public interface RepositoryCallback<T> {

View File

@ -1,6 +1,5 @@
package com.auto.clicker.autoclicker.ui.activity.menu;
import android.app.AlertDialog;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
@ -12,6 +11,8 @@ import android.widget.EditText;
import android.widget.Toast;
import androidx.activity.EdgeToEdge;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
@ -21,28 +22,24 @@ import androidx.recyclerview.widget.LinearLayoutManager;
import com.auto.clicker.autoclicker.R;
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;
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 String tempExportJson;
private static final String TAG = "ScriptsActivity";
private static final int CREATE_FILE_REQUEST_CODE = 1; // 用于导出文件的请求码
private static final int CREATE_FILE_REQUEST_CODE = 1;
@Override
protected void onCreate(Bundle savedInstanceState) {
@ -57,11 +54,12 @@ public class ScriptsActivity extends AppCompatActivity implements SolutionAdapte
});
binding.scriptsRecyclerView.setLayoutManager(new LinearLayoutManager(this));
// 传入 this 作为 OnOptionMenuClickListener 的实现
// this 作为 OnSolutionClickListener OnOptionMenuClickListener 传入
solutionsAdapter = new SolutionAdapter(new ArrayList<>(), this::onSolutionSelected, this);
binding.scriptsRecyclerView.setAdapter(solutionsAdapter);
floatingViewManager = new FloatingViewManager(this);
// 确保 FloatingViewManager Activity 创建时被初始化
floatingViewManager = new FloatingViewManager(this); // 如果 FloatingViewManager 依赖 Context这里需要传递
binding.btnBack.setOnClickListener(v -> finish());
@ -97,7 +95,9 @@ public class ScriptsActivity extends AppCompatActivity implements SolutionAdapte
return;
}
floatingViewManager.getAllSolutions(new EventRepository.RepositoryCallback<List<Solution>>() {
// 确保 FloatingViewManager 能够提供 EventRepository 实例
// 假设 FloatingViewManager 内部管理着 EventRepository
floatingViewManager.getEventRepository().getAllSolutions(new EventRepository.RepositoryCallback<List<Solution>>() {
@Override
public void onComplete(List<Solution> solutions) {
new Handler(Looper.getMainLooper()).post(() -> {
@ -116,7 +116,7 @@ public class ScriptsActivity extends AppCompatActivity implements SolutionAdapte
new Handler(Looper.getMainLooper()).post(() -> {
Log.e(TAG, "加载方案列表失败: " + e.getMessage(), e);
Toast.makeText(ScriptsActivity.this, "加载方案列表失败。", Toast.LENGTH_SHORT).show();
updateLayout(true); // 加载失败也显示无方案视图
updateLayout(true);
});
}
});
@ -145,29 +145,23 @@ public class ScriptsActivity extends AppCompatActivity implements SolutionAdapte
}
}
// --- 实现 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);
}
@ -182,7 +176,6 @@ public class ScriptsActivity extends AppCompatActivity implements SolutionAdapte
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<Void>() {
@Override
public void onComplete(Void result) {
@ -202,6 +195,8 @@ public class ScriptsActivity extends AppCompatActivity implements SolutionAdapte
});
} 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());
@ -212,60 +207,12 @@ public class ScriptsActivity extends AppCompatActivity implements SolutionAdapte
floatingViewManager.getEventRepository().getSolutionWithEventsAndPoints(originalSolution.getSolutionId(), new EventRepository.RepositoryCallback<SolutionWithEventsAndPoints>() {
@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<SolutionWithEventsAndPoints.EventWithPoints> 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<Void>() {
if (originalData != null) {
floatingViewManager.getEventRepository().saveDuplicateSolution(originalData, new EventRepository.RepositoryCallback<Void>() {
@Override
public void onComplete(Void result) {
new Handler(Looper.getMainLooper()).post(() -> {
Toast.makeText(ScriptsActivity.this, "方案 '" + originalSolution.getSolutionName() + "' 已复制为 '" + newSolution.getSolutionName() + "'", Toast.LENGTH_SHORT).show();
Toast.makeText(ScriptsActivity.this, "方案 '" + originalSolution.getSolutionName() + "' 已复制成功!", Toast.LENGTH_SHORT).show();
loadSolutions(); // 刷新列表
});
}
@ -279,9 +226,9 @@ public class ScriptsActivity extends AppCompatActivity implements SolutionAdapte
}
});
} else {
new Handler(Looper.getMainLooper()).post(() ->
Toast.makeText(ScriptsActivity.this, "无法获取原始方案数据", Toast.LENGTH_SHORT).show()
);
new Handler(Looper.getMainLooper()).post(() -> {
Toast.makeText(ScriptsActivity.this, "无法找到原始方案数据进行复制", Toast.LENGTH_SHORT).show();
});
}
}
@ -289,17 +236,16 @@ 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();
Toast.makeText(ScriptsActivity.this, "获取原始方案数据失败: " + e.getMessage(), Toast.LENGTH_SHORT).show();
});
}
});
}
private void showDeleteConfirmationDialog(final Solution solution) {
new AlertDialog.Builder(this)
.setTitle("删除方案")
.setMessage("确定要删除方案 '" + solution.getSolutionName() + "' 吗?")
.setMessage("确定要删除方案 '" + solution.getSolutionName() + "' 吗?\n此操作不可撤销")
.setPositiveButton("删除", (dialog, which) -> deleteSolution(solution))
.setNegativeButton("取消", null)
.show();
@ -328,26 +274,20 @@ public class ScriptsActivity extends AppCompatActivity implements SolutionAdapte
private void exportSolution(final Solution solution) {
floatingViewManager.getEventRepository().getSolutionWithEventsAndPoints(solution.getSolutionId(), new EventRepository.RepositoryCallback<SolutionWithEventsAndPoints>() {
@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);
public void onComplete(SolutionWithEventsAndPoints data) {
if (data != null) {
tempExportJson = floatingViewManager.getEventRepository().exportSolutionToJson(data);
if (tempExportJson != null && !tempExportJson.isEmpty()) {
Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("application/json"); // 导出为 JSON 文件
intent.setType("application/json");
intent.putExtra(Intent.EXTRA_TITLE, solution.getSolutionName() + ".json");
startActivityForResult(intent, CREATE_FILE_REQUEST_CODE);
// JSON 字符串保存到临时变量或直接传递
// onActivityResult 中处理保存
tempExportJson = jsonString; // 临时保存待导出的JSON数据
});
} else {
Toast.makeText(ScriptsActivity.this, "无法生成导出数据。", Toast.LENGTH_SHORT).show();
}
} else {
new Handler(Looper.getMainLooper()).post(() ->
Toast.makeText(ScriptsActivity.this, "获取方案数据失败,无法导出。", Toast.LENGTH_SHORT).show()
);
Toast.makeText(ScriptsActivity.this, "无法获取方案数据进行导出。", Toast.LENGTH_SHORT).show();
}
}
@ -355,17 +295,14 @@ 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();
Toast.makeText(ScriptsActivity.this, "获取方案数据失败: " + e.getMessage(), Toast.LENGTH_SHORT).show();
});
}
});
}
// 临时变量用于存储待导出的JSON数据
private String tempExportJson;
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == CREATE_FILE_REQUEST_CODE && resultCode == RESULT_OK) {
if (data != null && data.getData() != null) {
@ -385,6 +322,8 @@ public class ScriptsActivity extends AppCompatActivity implements SolutionAdapte
} else {
Toast.makeText(this, "没有可导出的数据。", Toast.LENGTH_SHORT).show();
}
} else {
Toast.makeText(this, "文件创建操作被取消或数据无效。", Toast.LENGTH_SHORT).show();
}
}
}

View File

@ -41,7 +41,14 @@ public class SolutionAdapter extends RecyclerView.Adapter<SolutionAdapter.Soluti
public SolutionAdapter(List<Solution> solutionList, OnSolutionClickListener listener, OnOptionMenuClickListener optionMenuListener) {
this.solutionList = solutionList;
this.listener = listener;
this.optionMenuListener = optionMenuListener; // 初始化
this.optionMenuListener = optionMenuListener;
}
// 新增构造函数用于不需要选项菜单的情况
public SolutionAdapter(List<Solution> solutionList, OnSolutionClickListener listener) {
this.solutionList = solutionList;
this.listener = listener;
this.optionMenuListener = null; // 设置为 null表示不需要选项菜单
}
@NonNull
@ -68,12 +75,14 @@ public class SolutionAdapter extends RecyclerView.Adapter<SolutionAdapter.Soluti
}
});
// option 按钮设置点击监听器
holder.option.setOnClickListener(v -> {
if (optionMenuListener != null) {
if (optionMenuListener != null) {
holder.option.setVisibility(View.VISIBLE); // 显示按钮
holder.option.setOnClickListener(v -> {
holder.showOptionMenu(solution, optionMenuListener); // 调用方法显示菜单
}
});
});
} else {
holder.option.setVisibility(View.GONE); // 隐藏按钮
}
}
@Override

View File

@ -1,54 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="292dp"
android:layout_height="wrap_content"
android:background="@drawable/bg_rounded_rect"
android:backgroundTint="#164958"
android:padding="16dp">
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="My Script 1"
android:textColor="@android:color/white"
android:textSize="20sp"
android:textStyle="bold"
app:layout_constraintBottom_toTopOf="@+id/mode"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed" />
<TextView
android:id="@+id/mode"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="1-point Mode"
android:textColor="@color/text_gray"
android:textSize="14sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/title" />
<ImageView
android:id="@+id/play"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/solution_play"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/option"
android:layout_marginEnd="32dp"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/option"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/script_option"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -48,6 +48,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/script_option"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />