This commit is contained in:
lihongwei 2025-06-30 16:08:15 +08:00
parent 427f0b12c4
commit 4d7f9b8f39
17 changed files with 142 additions and 220 deletions

View File

@ -12,12 +12,5 @@ import java.util.List;
@Dao
public interface EventEntityDao {
@Insert
long insertEventEntity(EventEntity eventEntity); // 返回新插入的 eventId
@Delete
int deleteEventEntity(EventEntity eventEntity);
// 查询某个方案下的所有事件实体按顺序排序
@Query("SELECT * FROM events WHERE solution_id = :solutionId ORDER BY order_in_solution ASC")
List<EventEntity> getEventsForSolution(long solutionId);
long insertEventEntity(EventEntity eventEntity);
}

View File

@ -20,26 +20,10 @@ public interface SolutionDao {
@Query("SELECT * FROM solutions")
List<Solution> getAllSolutions();
@Query("SELECT * FROM solutions WHERE solutionId = :solutionId LIMIT 1")
Solution getSolutionById(long solutionId);
@Query("SELECT * FROM solutions WHERE solution_name = :solutionName LIMIT 1")
Solution getSolutionByName(String solutionName);
@Delete
int deleteSolution(Solution solution);
@Update
int updateSolution(Solution solution);
@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);

View File

@ -11,14 +11,5 @@ import com.auto.clicker.autoclicker.room.entity.TouchPoint;
@Dao
public interface TouchPointDao {
@Insert
long insertPoint(TouchPoint touchPoint); // 返回新插入的 pointId
@Query("SELECT * FROM touchPoints WHERE pointId = :pointId LIMIT 1")
TouchPoint getPointById(long pointId);
@Delete
int deletePoint(TouchPoint touchPoint);
@Update
int updatePoint(TouchPoint touchPoint);
long insertPoint(TouchPoint touchPoint);
}

View File

@ -61,7 +61,7 @@ public class EventRepository {
throw new RuntimeException("Failed to insert touch point for PointEvent.");
EventEntity eventEntity = new EventEntity(
solutionId, // 使用 long 类型的 solutionId
solutionId,
wrapper.getType().name(),
order,
pointId,
@ -86,13 +86,13 @@ public class EventRepository {
throw new RuntimeException("Failed to insert end touch point for SlideEvent.");
EventEntity eventEntity = new EventEntity(
solutionId, // 使用 long 类型的 solutionId
solutionId,
wrapper.getType().name(),
order,
null,
startPointId,
endPointId,
0 // duration for slide events, you might want to get this from SlideEvent
0
);
eventEntityId = eventEntityDao.insertEventEntity(eventEntity);
if (eventEntityId == -1)
@ -198,7 +198,7 @@ public class EventRepository {
}
EventEntity newEventEntity = new EventEntity(
newSolutionId, // 关联到新生成的方案ID
newSolutionId,
eventWithPoints.eventEntity.eventType,
eventWithPoints.eventEntity.orderInSolution,
newClickPointId,
@ -235,7 +235,6 @@ public class EventRepository {
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);
@ -268,7 +267,6 @@ public class EventRepository {
throw new RuntimeException("Failed to duplicate slide end point.");
}
// 创建新的 EventEntity 实例
EventEntity newEventEntity = new EventEntity(
newSolutionId,
eventWithPoints.eventEntity.eventType,

View File

@ -153,19 +153,18 @@ public class AutoClickService extends AccessibilityService {
isRunning = true;
currentEventIndex = 0;
currentLoop = 0; // 重置循环计数
startTimeMillis = System.currentTimeMillis(); // 记录开始时间
currentLoop = 0;
startTimeMillis = System.currentTimeMillis();
EventViewBinder.setAllTouchPointsDraggable(false);
// 根据循环设置调整日志输出
String loopInfo;
if (loopCount > 0) {
loopInfo = "循环 " + (loopCount == 1 ? "1 次" : loopCount + "");
} else if (loopTimeMillis > 0) {
loopInfo = "循环 " + (loopTimeMillis / 1000) + "";
} else {
loopInfo = "无限循环"; // 如果两者都为0
loopInfo = "无限循环";
}
logDebug("开始执行事件队列 - 共 " + runtimeEvents.size() + " 个事件. " + loopInfo);
@ -266,7 +265,7 @@ public class AutoClickService extends AccessibilityService {
int finalClickY = correctedViewTopY + touchPointSize / 2;
Path path = new Path();
path.moveTo(finalClickX, finalClickY); // 使用最终计算出的点击中心坐标
path.moveTo(finalClickX, finalClickY);
GestureDescription.StrokeDescription stroke =
new GestureDescription.StrokeDescription(path, 0, clickDuration);
GestureDescription gesture =
@ -278,8 +277,7 @@ public class AutoClickService extends AccessibilityService {
logDebug("点击完成");
if (isRunning) {
int feedbackIndex = currentEventIndex;
currentEventIndex++; // 事件索引递增
// 延迟执行下一个事件循环逻辑在executeEventsByType中处理
currentEventIndex++;
handler.postDelayed(() -> executeEventsByType(), clickInterval);
flashTouchFeedback(feedbackIndex);
}
@ -289,8 +287,7 @@ public class AutoClickService extends AccessibilityService {
public void onCancelled(GestureDescription gestureDescription) {
Log.e(TAG, "点击被取消");
if (isRunning) {
currentEventIndex++; // 事件索引递增
// 延迟执行下一个事件
currentEventIndex++;
handler.postDelayed(() -> executeEventsByType(), clickInterval + 300);
}
}
@ -324,7 +321,6 @@ public class AutoClickService extends AccessibilityService {
logDebug("滑动终点(修正后): (" + finalEndX + ", " + finalEndY + ")");
Path path = new Path();
// 这里必须使用修正后的坐标才能确保手势在正确的位置执行
path.moveTo(finalStartX, finalStartY);
path.lineTo(finalEndX, finalEndY);
@ -339,8 +335,7 @@ public class AutoClickService extends AccessibilityService {
logDebug("滑动完成");
if (isRunning) {
int feedbackIndex = currentEventIndex;
currentEventIndex++; // 事件索引递增
// 延迟执行下一个事件
currentEventIndex++;
handler.postDelayed(() -> executeEventsByType(), clickInterval);
flashTouchFeedback(feedbackIndex);
}
@ -353,8 +348,7 @@ 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 + " ---");

View File

@ -44,6 +44,8 @@ public class MainActivity extends AppCompatActivity {
private ActivityMainBinding binding;
private boolean isPermissionFlowActive = false;
public static final int FLOATING_NONE = 0;
public static final int FLOATING_SINGLE = 1;
public static final int FLOATING_MULTI = 2;
@ -66,10 +68,6 @@ public class MainActivity extends AppCompatActivity {
isFloatingShown = intent.getIntExtra("isShown", FLOATING_NONE);
logDebug("接收到浮窗状态广播: isShown = " + isFloatingShown);
if (isFloatingShown == FLOATING_NONE) {
} else {
selectedFloatingMode = isFloatingShown;
}
updateSelectionButtons();
}
};
@ -95,9 +93,19 @@ public class MainActivity extends AppCompatActivity {
@Override
protected void onResume() {
super.onResume();
logDebug("onResume: 检查权限和同步服务状态");
checkPermissions();
logDebug("onResume: 同步服务状态并更新启动按钮状态");
syncServiceState();
updateStartButtonState();
if (isPermissionFlowActive) {
binding.getRoot().postDelayed(this::checkAllPermissionsAndRequestInSequence, 300);
}
}
private void updateStartButtonState() {
boolean accessibilityGranted = isAccessibilityServiceEnabled();
boolean overlayGranted = Settings.canDrawOverlays(this);
boolean batteryOptimizationIgnored = isIgnoringBatteryOptimizations();
setStartButtonEnabled(accessibilityGranted && overlayGranted && batteryOptimizationIgnored);
}
private void initData() {
@ -116,10 +124,7 @@ public class MainActivity extends AppCompatActivity {
permissionLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> {
logDebug("权限回调结果: " + result.getResultCode());
binding.getRoot().postDelayed(() -> {
checkPermissions();
syncServiceState();
}, 300);
binding.getRoot().postDelayed(this::checkAllPermissionsAndRequestInSequence, 300);
});
setVideo(Uri.parse("android.resource://" + getPackageName() + "/" + R.raw.bolangblue));
@ -141,7 +146,12 @@ public class MainActivity extends AppCompatActivity {
startActivity(intent);
});
binding.animatedView.setOnClickListener(v -> onToggleFloatingWindowClicked());
binding.animatedView.setOnClickListener(v -> {
if (checkAllPermissionsAndRequestInSequence()) {
isPermissionFlowActive = false;
onToggleFloatingWindowClicked();
}
});
binding.single.setOnClickListener(v -> {
if (isFloatingShown != FLOATING_NONE) {
@ -155,7 +165,7 @@ public class MainActivity extends AppCompatActivity {
binding.multi.setOnClickListener(v -> {
if (isFloatingShown != FLOATING_NONE) {
Toast.makeText(this, "请先关闭悬浮窗再切换模式", Toast.LENGTH_SHORT).show();
Toast.makeText(this, "Please close the floating window first and then switch the mode", Toast.LENGTH_SHORT).show();
return;
}
selectedFloatingMode = FLOATING_MULTI;
@ -166,11 +176,47 @@ public class MainActivity extends AppCompatActivity {
handleIncomingIntent(getIntent());
}
private boolean checkAllPermissionsAndRequestInSequence() {
boolean accessibilityGranted = isAccessibilityServiceEnabled();
boolean overlayGranted = Settings.canDrawOverlays(this);
boolean batteryOptimizationIgnored = isIgnoringBatteryOptimizations();
logDebug("检查权限 (序列流程): 无障碍服务=" + accessibilityGranted + ", 悬浮窗=" + overlayGranted + ", 电池优化=" + batteryOptimizationIgnored);
if (!accessibilityGranted) {
logDebug("无障碍服务未启用,请求权限");
showPermissionRequest(Settings.ACTION_ACCESSIBILITY_SETTINGS, "Please enable accessibility services to ensure the application works properly", "Accessibility Settings cannot be enabled");
setStartButtonEnabled(false);
return false;
}
if (!overlayGranted) {
logDebug("悬浮窗权限未授予,请求权限");
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + getPackageName()));
showPermissionRequest(intent, "Please grant the floating window permission; otherwise, the floating window cannot be displayed", "The floating window setting cannot be opened");
setStartButtonEnabled(false);
return false;
}
if (!batteryOptimizationIgnored) {
logDebug("电池优化未忽略,请求权限");
Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
intent.setData(Uri.parse("package:" + getPackageName()));
showPermissionRequest(intent, "Please disable battery optimization to ensure the stable operation of the application in the background", "The battery optimization Settings cannot be opened");
setStartButtonEnabled(false);
return false;
}
logDebug("所有权限均已授予");
setStartButtonEnabled(true);
isPermissionFlowActive = false;
return true;
}
@Override
protected void onNewIntent(@NonNull Intent intent) {
super.onNewIntent(intent);
// 如果 MainActivity 已经在运行并且再次通过 Intent 启动会调用 onNewIntent
setIntent(intent); // 更新当前的 Intent
setIntent(intent);
handleIncomingIntent(intent);
}
@ -197,7 +243,7 @@ public class MainActivity extends AppCompatActivity {
if (!accessibilityGranted) {
logDebug("无障碍服务未启用,请求权限");
showPermissionRequest(Settings.ACTION_ACCESSIBILITY_SETTINGS, "请启用无障碍服务以便应用正常工作", "无法打开无障碍设置");
showPermissionRequest(Settings.ACTION_ACCESSIBILITY_SETTINGS, "Please enable accessibility services to ensure the application works properly", "Accessibility Settings cannot be enabled");
setStartButtonEnabled(false);
return;
@ -206,7 +252,7 @@ public class MainActivity extends AppCompatActivity {
if (!overlayGranted) {
logDebug("悬浮窗权限未授予,请求权限");
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + getPackageName()));
showPermissionRequest(intent, "请授予悬浮窗权限,否则无法显示浮窗", "无法打开悬浮窗设置");
showPermissionRequest(intent, "Please grant the floating window permission; otherwise, the floating window cannot be displayed", "The floating window setting cannot be opened");
setStartButtonEnabled(false);
return;
@ -216,7 +262,7 @@ public class MainActivity extends AppCompatActivity {
logDebug("电池优化未忽略,请求权限");
Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
intent.setData(Uri.parse("package:" + getPackageName()));
showPermissionRequest(intent, "请禁用电池优化,以确保应用在后台稳定运行", "无法打开电池优化设置");
showPermissionRequest(intent, "Please disable battery optimization to ensure the stable operation of the application in the background", "The battery optimization Settings cannot be opened");
setStartButtonEnabled(false);
return;
@ -424,9 +470,9 @@ public class MainActivity extends AppCompatActivity {
private void showDebounceToast() {
if (debounceToast == null) {
debounceToast = Toast.makeText(this, "点击太频繁", Toast.LENGTH_SHORT);
debounceToast = Toast.makeText(this, "Click too frequently", Toast.LENGTH_SHORT);
} else {
debounceToast.setText("点击太频繁");
debounceToast.setText("Click too frequently");
}
debounceToast.show();
}

View File

@ -155,13 +155,13 @@ public class ScriptsActivity extends AppCompatActivity implements SolutionAdapte
private void showRenameDialog(final Solution solution) {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("重命名方案");
builder.setTitle("Renaming script");
final EditText input = new EditText(this);
input.setText(solution.getSolutionName());
builder.setView(input);
builder.setPositiveButton("确定", (dialog, which) -> {
builder.setPositiveButton("Sure", (dialog, which) -> {
String newName = input.getText().toString().trim();
if (!newName.isEmpty() && !newName.equals(solution.getSolutionName())) {
floatingViewManager.getEventRepository().updateSolutionName(solution.getSolutionId(), newName, new EventRepository.RepositoryCallback<Void>() {
@ -181,7 +181,7 @@ public class ScriptsActivity extends AppCompatActivity implements SolutionAdapte
});
}
});
builder.setNegativeButton("取消", (dialog, which) -> dialog.cancel());
builder.setNegativeButton("Cancel", (dialog, which) -> dialog.cancel());
builder.show();
}
@ -220,10 +220,10 @@ public class ScriptsActivity extends AppCompatActivity implements SolutionAdapte
private void showDeleteConfirmationDialog(final Solution solution) {
new AlertDialog.Builder(this)
.setTitle("删除方案")
.setMessage("确定要删除方案 '" + solution.getSolutionName() + "' 吗?\n此操作不可撤销")
.setPositiveButton("删除", (dialog, which) -> deleteSolution(solution))
.setNegativeButton("取消", null)
.setTitle("Delete the script")
.setMessage("Are you sure you want to delete the script '" + solution.getSolutionName() + "'?\nThis action cannot be undone!")
.setPositiveButton("Delete", (dialog, which) -> deleteSolution(solution))
.setNegativeButton("Cancel", null)
.show();
}
@ -300,12 +300,11 @@ public class ScriptsActivity extends AppCompatActivity implements SolutionAdapte
SolutionWithEventsAndPoints importedData = floatingViewManager.getEventRepository().importSolutionFromJson(jsonString);
if (importedData != null && importedData.solution != null) {
// 保存到数据库
floatingViewManager.getEventRepository().importSolutionFromData(importedData, new EventRepository.RepositoryCallback<Void>() {
@Override
public void onComplete(Void result) {
new Handler(Looper.getMainLooper()).post(() -> {
loadSolutions(); // 刷新列表
loadSolutions();
});
}

View File

@ -23,7 +23,6 @@ public class SolutionAdapter extends RecyclerView.Adapter<SolutionAdapter.Soluti
private OnSolutionClickListener listener;
private OnOptionMenuClickListener optionMenuListener;
// 新增接口定义
public interface OnOptionMenuClickListener {
void onRenameClick(Solution solution);
@ -44,11 +43,10 @@ public class SolutionAdapter extends RecyclerView.Adapter<SolutionAdapter.Soluti
this.optionMenuListener = optionMenuListener;
}
// 新增构造函数用于不需要选项菜单的情况
public SolutionAdapter(List<Solution> solutionList, OnSolutionClickListener listener) {
this.solutionList = solutionList;
this.listener = listener;
this.optionMenuListener = null; // 设置为 null表示不需要选项菜单
this.optionMenuListener = null;
}
@NonNull
@ -56,7 +54,7 @@ public class SolutionAdapter extends RecyclerView.Adapter<SolutionAdapter.Soluti
public SolutionViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_solution, parent, false);
return new SolutionViewHolder(view, parent.getContext()); // 传递 Context
return new SolutionViewHolder(view, parent.getContext());
}
@Override
@ -103,7 +101,7 @@ public class SolutionAdapter extends RecyclerView.Adapter<SolutionAdapter.Soluti
TextView mode;
ImageView play;
ImageView option;
Context context; // 保存 Context
Context context;
SolutionViewHolder(View itemView, Context context) {
super(itemView);

View File

@ -57,7 +57,7 @@ public class ActionFragment extends Fragment {
saveSettingsButton = binding.save;
saveSettingsButton.setOnClickListener(v -> {
applySettingsToService();
Toast.makeText(getContext(), "设置已保存并应用", Toast.LENGTH_SHORT).show();
Toast.makeText(getContext(), "The Settings have been saved and applied", Toast.LENGTH_SHORT).show();
});
return binding.getRoot();
@ -113,9 +113,9 @@ public class ActionFragment extends Fragment {
binding.advancedSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> {
if (isChecked) {
Toast.makeText(getContext(), "防检测模式已开启", Toast.LENGTH_SHORT).show();
Toast.makeText(getContext(), "The anti-detection mode has been enabled", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(getContext(), "防检测模式已关闭", Toast.LENGTH_SHORT).show();
Toast.makeText(getContext(), "The anti-detection mode has been turned off", Toast.LENGTH_SHORT).show();
}
});
}
@ -246,11 +246,11 @@ public class ActionFragment extends Fragment {
private void showInputValue(int type, String value, String unitText) {
String typeName = "";
switch (type) {
case TYPE_INTERVAL: typeName = "点击间隔"; break;
case TYPE_SWIPE_DURATION: typeName = "滑动时长"; break;
case TYPE_REPEAT: typeName = "循环模式"; break;
case TYPE_INTERVAL: typeName = "Click interval"; break;
case TYPE_SWIPE_DURATION: typeName = "Sliding duration"; break;
case TYPE_REPEAT: typeName = "Circular mode"; break;
}
String message = typeName + " : " + (value.isEmpty() ? "未输入" : value) + ", 单位: " + unitText;
String message = typeName + " Value: " + (value.isEmpty() ? "Not entered" : value) + ", Unit: " + unitText;
Toast.makeText(getContext(), message, Toast.LENGTH_SHORT).show();
}
@ -293,7 +293,7 @@ public class ActionFragment extends Fragment {
AutoClickService service = AutoClickService.getInstance();
if (service == null) {
Toast.makeText(getContext(), "自动点击服务未运行,无法应用设置。", Toast.LENGTH_SHORT).show();
Toast.makeText(getContext(), "The auto-click service is not running and the Settings cannot be applied.", Toast.LENGTH_SHORT).show();
Log.e(TAG, "AutoClickService 实例为空,无法应用设置。");
return;
}
@ -311,7 +311,7 @@ public class ActionFragment extends Fragment {
Log.d(TAG, "应用点击间隔到服务: " + intervalValue + "ms");
} catch (NumberFormatException e) {
Log.e(TAG, "点击间隔值解析错误: " + currentIntervalValue, e);
Toast.makeText(getContext(), "点击间隔值无效,请输入有效数字。", Toast.LENGTH_SHORT).show();
Toast.makeText(getContext(), "The click interval value is invalid. Please enter a significant number.", Toast.LENGTH_SHORT).show();
}
} else {
Log.d(TAG, "点击间隔值为空,未应用到服务。");
@ -328,7 +328,7 @@ public class ActionFragment extends Fragment {
Log.d(TAG, "应用滑动时长到服务: " + slideDurationValue + "ms");
} catch (NumberFormatException e) {
Log.e(TAG, "滑动时长值解析错误: " + currentDurationValue, e);
Toast.makeText(getContext(), "滑动时长值无效,请输入有效数字。", Toast.LENGTH_SHORT).show();
Toast.makeText(getContext(), "The sliding duration value is invalid. Please enter a significant number.", Toast.LENGTH_SHORT).show();
}
} else {
Log.d(TAG, "滑动时长值为空,未应用到服务。");
@ -347,7 +347,7 @@ public class ActionFragment extends Fragment {
Log.d(TAG, "应用重复时长: " + repeatDurationMillis + "ms。");
} catch (NumberFormatException e) {
Log.e(TAG, "重复时长值解析错误: " + currentRepeatValue, e);
Toast.makeText(getContext(), "重复时长值无效,请输入有效数字或正确的时间格式。", Toast.LENGTH_SHORT).show();
Toast.makeText(getContext(), "Repeated duration values are invalid. Please enter significant figures or the correct time format.", Toast.LENGTH_SHORT).show();
service.setLoopTime(0);
}
} else {
@ -364,7 +364,7 @@ public class ActionFragment extends Fragment {
Log.d(TAG, "应用重复次数: " + repeatCount + "次。");
} catch (NumberFormatException e) {
Log.e(TAG, "重复次数值解析错误: " + currentRepeatValue, e);
Toast.makeText(getContext(), "重复次数值无效,请输入有效数字。", Toast.LENGTH_SHORT).show();
Toast.makeText(getContext(), "Duplicate values are invalid. Please enter significant figures.", Toast.LENGTH_SHORT).show();
service.setLoopCount(1);
}
} else {

View File

@ -41,7 +41,7 @@ public class UISizeFragment extends Fragment {
setupPointSizeControl();
setupControlBarSizeControl();
} else {
Toast.makeText(getContext(), "自动化服务未运行UI大小设置功能不可用。", Toast.LENGTH_LONG).show();
Toast.makeText(getContext(), "The automated service is not running and the UI size setting function is unavailable.", Toast.LENGTH_LONG).show();
disableAllControlUI();
}
@ -74,7 +74,7 @@ public class UISizeFragment extends Fragment {
int finalSize = seekBar.getProgress() + MIN_POINT_SIZE;
floatingViewManager.setTouchPointSize(finalSize);
Log.d(TAG, "触摸点大小设置为: " + finalSize + "px");
Toast.makeText(getContext(), "触摸点大小已更新", Toast.LENGTH_SHORT).show();
Toast.makeText(getContext(), "The size of the touch points has been updated", Toast.LENGTH_SHORT).show();
}
});
}
@ -109,7 +109,7 @@ public class UISizeFragment extends Fragment {
int finalWidthPx = seekBar.getProgress() + minControlBarWidthPx;
floatingViewManager.setControlBarSize(finalWidthPx);
Log.d(TAG, "控制栏大小设置为: " + pxToDp(finalWidthPx) + "dp (" + finalWidthPx + "px)");
Toast.makeText(getContext(), "控制栏大小已更新", Toast.LENGTH_SHORT).show();
Toast.makeText(getContext(), "The size of the control bar has been updated", Toast.LENGTH_SHORT).show();
}
});
}

View File

@ -1,61 +1,8 @@
package com.auto.clicker.autoclicker.util;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;
public class DraggableHelper {
public interface DragCallback {
void onPositionChanged(int x, int y);
}
public static void makeDraggable(View view,
WindowManager.LayoutParams params,
WindowManager windowManager,
int screenWidth, int screenHeight,
DragCallback callback) {
view.setOnTouchListener(new View.OnTouchListener() {
private float lastX, lastY;
private float paramX, paramY;
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
lastX = event.getRawX();
lastY = event.getRawY();
paramX = params.x;
paramY = params.y;
return true;
case MotionEvent.ACTION_MOVE:
float dx = event.getRawX() - lastX;
float dy = event.getRawY() - lastY;
int newX = (int) (paramX + dx);
int newY = (int) (paramY + dy);
newX = Math.max(0, Math.min(newX, screenWidth - view.getWidth()));
newY = Math.max(0, Math.min(newY, screenHeight - view.getHeight()));
params.x = newX;
params.y = newY;
windowManager.updateViewLayout(view, params);
if (callback != null) {
callback.onPositionChanged(newX, newY);
}
return true;
case MotionEvent.ACTION_UP:
v.performClick();
return true;
}
return false;
}
});
}
}

View File

@ -103,7 +103,6 @@ public class EventViewBinder {
startView.setOnTouchListener(startTouchListener);
activeDragListeners.put(startView, startTouchListener); // 保存监听器
// 拖动终点 - 创建并保存监听器
View.OnTouchListener endTouchListener = createTouchListener(
endView, endParams, windowManager, screenWidth, screenHeight,
(x, y) -> {
@ -113,7 +112,7 @@ public class EventViewBinder {
if (callback != null) callback.onPositionChanged(x, y);
});
endView.setOnTouchListener(endTouchListener);
activeDragListeners.put(endView, endTouchListener); // 保存监听器
activeDragListeners.put(endView, endTouchListener);
windowManager.addView(lineView, lineParams);
windowManager.addView(startView, startParams);

View File

@ -15,28 +15,20 @@ import com.auto.clicker.autoclicker.R;
public class ConnectingLineView extends View {
// 画笔用于绘制连接带设置为填充样式
private final Paint connectionBandPaint;
// 连接带的宽度单位 dp
private final float CONNECTION_BAND_WIDTH_DP = 30f;
// 连接带在屏幕上的实际像素宽度
private float connectionBandWidthPx;
// 绘制时连接带的逻辑起始点和结束点 View 自身的坐标系中
private float drawStartX, drawStartY, drawEndX, drawEndY;
// 连接带的长度
private float lineLength;
// 连接带的旋转角度以度为单位
private float rotationAngle;
// 用于表示方向的 Drawable 图标
private Drawable startArrowDrawable;
private Drawable endArrowDrawable;
public ConnectingLineView(Context context) {
super(context);
// dp 单位的宽度转换为像素单位以便在 Canvas 上绘制
connectionBandWidthPx = dpToPx(context, CONNECTION_BAND_WIDTH_DP);
connectionBandPaint = new Paint();
@ -56,16 +48,11 @@ public class ConnectingLineView extends View {
public void setPoints(float startX, float startY, float endX, float endY) {
// 计算两点间的X和Y坐标差
float deltaX = endX - startX;
float deltaY = endY - startY;
// 计算连接带的长度两点间的直线距离
this.lineLength = (float) Math.sqrt(deltaX * deltaX + deltaY * deltaY);
// 计算连接带的旋转角度
// Math.atan2(y, x) 返回的是从X轴正向到点(x,y)的弧度范围是-PI到PI
// Math.toDegrees 将弧度转换为度数
this.rotationAngle = (float) Math.toDegrees(Math.atan2(deltaY, deltaX));
this.drawStartX = startX;
@ -85,34 +72,21 @@ public class ConnectingLineView extends View {
}
// 保存当前画布的状态
// 这允许我们对画布进行平移和旋转操作而不会影响后续或外部的绘制
canvas.save();
// 1. 平移画布到连接带的起始点
// 这样在绘制时连接带的逻辑起始点就变成了 (0,0)
canvas.translate(drawStartX, drawStartY);
// 2. 旋转画布
// 围绕当前的画布原点即连接带的起始点旋转画布
// 之后绘制的所有内容都会以这个角度显示
canvas.rotate(rotationAngle);
// 3. 绘制连接带的矩形
// RectF 定义了一个矩形由于画布已经旋转和平移
// 这里的 (0, -connectionBandWidthPx / 2) 是矩形的左上角
// (lineLength, connectionBandWidthPx / 2) 是矩形的右下角
// 这样矩形就会以当前 Y 轴为中心从当前 X 轴原点0延伸到 lineLength
RectF rect = new RectF(0, -connectionBandWidthPx / 2, lineLength, connectionBandWidthPx / 2);
canvas.drawRect(rect, connectionBandPaint);
// 4. 绘制起始方向图标
if (startArrowDrawable != null) {
int arrowSize = (int) (connectionBandWidthPx / 2);
// 设置图标的边界这里的坐标是相对于当前已旋转和平移的画布
// 确保图标垂直居中于连接带
startArrowDrawable.setBounds(
75,
(int) (-arrowSize / 2),// 垂直居中
(int) (-arrowSize / 2),
75 + arrowSize,
(int) (arrowSize / 2)
);

View File

@ -114,7 +114,7 @@ public class FloatingSettingDialogManager {
saveCurrentSettings();
applySettingsToService();
removeFloatingTabDialog();
showToastOnUi(context, "设置已保存");
showToastOnUi(context, "The Settings have been saved.");
});
} else {
Log.e(TAG, "Save button not found!");
@ -123,7 +123,7 @@ public class FloatingSettingDialogManager {
if (closeButton != null) {
closeButton.setOnClickListener(v -> {
removeFloatingTabDialog();
showToastOnUi(context, "设置已关闭,未保存更改");
showToastOnUi(context, "The Settings have been turned off and no changes have been saved");
});
} else {
Log.e(TAG, "Close button not found!");
@ -134,10 +134,10 @@ public class FloatingSettingDialogManager {
Log.d(TAG, "浮动标签页弹窗已显示");
} catch (WindowManager.BadTokenException e) {
Log.e(TAG, "无法添加浮动标签页弹窗,可能是权限问题或上下文无效", e);
showToastOnUi(context, "显示弹窗失败,请检查权限");
showToastOnUi(context, "The pop-up window failed to display. Please check the permissions");
} catch (SecurityException e) {
Log.e(TAG, "需要悬浮窗权限才能显示浮动弹窗", e);
showToastOnUi(context, "请授予悬浮窗权限以便显示");
showToastOnUi(context, "Please grant the floating window permission for display");
}
}
@ -320,7 +320,7 @@ public class FloatingSettingDialogManager {
String unitText = context.getString(R.string.duration_unit);
repeatSelectedUnit.setText(unitText);
currentRepeatUnit = unitText;
etRepeatValue.setText(""); // 清空以便用户输入新值
etRepeatValue.setText("");
etRepeatValue.setEnabled(true);
etRepeatValue.setInputType(InputType.TYPE_CLASS_NUMBER);
popupWindow.dismiss();
@ -359,7 +359,7 @@ public class FloatingSettingDialogManager {
AutoClickService service = AutoClickService.getInstance();
if (service == null) {
Log.e(TAG, "AutoClickService 实例为空,无法应用设置。");
showToastOnUi(context, "自动点击服务未运行,无法应用设置");
showToastOnUi(context, "The auto-click service is not running and the Settings cannot be applied");
return;
}
@ -377,7 +377,7 @@ public class FloatingSettingDialogManager {
Log.d(TAG, "应用点击间隔到服务: " + intervalValue + "ms");
} catch (NumberFormatException e) {
Log.e(TAG, "点击间隔值解析错误: " + currentIntervalValue, e);
showToastOnUi(context, "点击间隔值无效,请输入有效数字");
showToastOnUi(context, "The click interval value is invalid. Please enter a significant number");
}
} else {
Log.d(TAG, "点击间隔值为空,未应用到服务。");
@ -394,7 +394,7 @@ public class FloatingSettingDialogManager {
Log.d(TAG, "应用滑动时长到服务: " + slideDurationValue + "ms");
} catch (NumberFormatException e) {
Log.e(TAG, "滑动时长值解析错误: " + currentDurationValue, e);
showToastOnUi(context, "滑动时长值无效,请输入有效数字");
showToastOnUi(context, "The sliding duration value is invalid. Please enter a significant number");
}
} else {
Log.d(TAG, "滑动时长值为空,未应用到服务。");
@ -413,7 +413,7 @@ public class FloatingSettingDialogManager {
Log.d(TAG, "应用重复时长: " + repeatDurationMillis + "ms。");
} catch (NumberFormatException e) {
Log.e(TAG, "重复时长值解析错误: " + currentRepeatValue, e);
showToastOnUi(context, "重复时长值无效,请输入有效数字");
showToastOnUi(context, "Repeated duration values are invalid. Please enter valid numbers");
service.setLoopTime(0); // 错误时取消时间循环
}
} else {
@ -430,7 +430,7 @@ public class FloatingSettingDialogManager {
Log.d(TAG, "应用重复次数: " + repeatCount + "次。");
} catch (NumberFormatException e) {
Log.e(TAG, "重复次数值解析错误: " + currentRepeatValue, e);
showToastOnUi(context, "重复次数值无效,请输入有效数字");
showToastOnUi(context, "Duplicate values are invalid. Please enter significant figures");
service.setLoopCount(1); // 错误时设置为执行一次
}
} else {
@ -465,17 +465,16 @@ public class FloatingSettingDialogManager {
if (!currentSwipeDurationUnit.isEmpty()) {
tvSelectedUnit.setText(currentSwipeDurationUnit);
} else {
tvSelectedUnit.setText(context.getString(R.string.milliseconds_unit)); // 默认单位
tvSelectedUnit.setText(context.getString(R.string.milliseconds_unit));
currentSwipeDurationUnit = context.getString(R.string.milliseconds_unit);
}
// 恢复重复设置
repeatSwitch.setChecked(isRepeatSwitchChecked);
updateRepeatUI(isRepeatSwitchChecked); // 根据开关状态更新UI
updateRepeatUI(isRepeatSwitchChecked);
if (isRepeatSwitchChecked) {
if (currentRepeatUnit.isEmpty()) {
// 如果没有保存的单位默认为无限次
repeatSelectedUnit.setText(context.getString(R.string.infinitely_unit));
currentRepeatUnit = context.getString(R.string.infinitely_unit);
etRepeatValue.setText("");

View File

@ -123,7 +123,7 @@ public class FloatingTabDialogManager {
Toast.makeText(context, "正在加载方案: " + solution.getSolutionName(), Toast.LENGTH_SHORT).show();
} else {
Log.e(TAG, "FloatingViewManager 未设置!无法加载方案。");
Toast.makeText(context, "加载功能不可用,请联系开发者。", Toast.LENGTH_SHORT).show();
Toast.makeText(context, "The loading function is unavailable. Please contact the developer.", Toast.LENGTH_SHORT).show();
}
removeFloatingTabDialog();
});
@ -138,10 +138,10 @@ public class FloatingTabDialogManager {
Log.d(TAG, "浮动标签页弹窗已显示");
} catch (WindowManager.BadTokenException e) {
Log.e(TAG, "无法添加浮动标签页弹窗,可能是权限问题或上下文无效", e);
Toast.makeText(context, "显示弹窗失败,请检查权限。", Toast.LENGTH_SHORT).show();
Toast.makeText(context, "The pop-up window failed to display. Please check the permissions.", Toast.LENGTH_SHORT).show();
} catch (SecurityException e) {
Log.e(TAG, "需要悬浮窗权限才能显示浮动弹窗", e);
Toast.makeText(context, "请授予悬浮窗权限以便显示", Toast.LENGTH_SHORT).show();
Toast.makeText(context, "Please grant the floating window permission for display", Toast.LENGTH_SHORT).show();
}
}
@ -174,7 +174,7 @@ public class FloatingTabDialogManager {
if (floatingViewManager == null) {
Log.e(TAG, "FloatingViewManager 未设置,无法加载方案列表。");
new Handler(Looper.getMainLooper()).post(() ->
Toast.makeText(context, "加载方案列表功能不可用", Toast.LENGTH_SHORT).show()
Toast.makeText(context, "The function of loading the list of schemes is not available", Toast.LENGTH_SHORT).show()
);
return;
}
@ -184,7 +184,7 @@ public class FloatingTabDialogManager {
new Handler(Looper.getMainLooper()).post(() -> {
solutionsAdapter.setSolutions(solutions);
if (solutions.isEmpty()) {
Toast.makeText(context, "没有可加载的方案", Toast.LENGTH_SHORT).show();
Toast.makeText(context, "There is no loadable scheme", Toast.LENGTH_SHORT).show();
}
});
}
@ -193,7 +193,7 @@ public class FloatingTabDialogManager {
public void onError(Exception e) {
new Handler(Looper.getMainLooper()).post(() -> {
Log.e(TAG, "加载方案列表失败: " + e.getMessage(), e);
Toast.makeText(context, "加载方案列表失败", Toast.LENGTH_SHORT).show();
Toast.makeText(context, "The loading of the scheme list failed", Toast.LENGTH_SHORT).show();
});
}
});

View File

@ -113,7 +113,7 @@ public class FloatingViewManager {
public void showFloatingViews(int mode) throws SecurityException {
if (!Settings.canDrawOverlays(context)) {
throw new SecurityException("需要悬浮窗许可");
throw new SecurityException("A floating window permit is required");
}
this.mode = mode;
@ -285,7 +285,7 @@ public class FloatingViewManager {
}
if (runtimeEvents.isEmpty()) {
Toast.makeText(context, "请添加一个触摸点或者滑动事件", Toast.LENGTH_SHORT).show();
Toast.makeText(context, "Please add a touch point or a sliding event", Toast.LENGTH_SHORT).show();
return;
}
@ -303,7 +303,7 @@ public class FloatingViewManager {
private void addPointEvent() {
if (isMultipleRunning) {
Toast.makeText(context, "请停止点击", Toast.LENGTH_SHORT).show();
Toast.makeText(context, "Please stop clicking.", Toast.LENGTH_SHORT).show();
return;
}
@ -322,7 +322,7 @@ public class FloatingViewManager {
private void addSlideEvent() {
if (isMultipleRunning) {
Toast.makeText(context, "请先停止点击", Toast.LENGTH_SHORT).show();
Toast.makeText(context, "Please stop clicking first", Toast.LENGTH_SHORT).show();
return;
}
@ -428,12 +428,12 @@ public class FloatingViewManager {
private void removeLastEvent() {
if (isMultipleRunning) {
Toast.makeText(context, "请先停止点击", Toast.LENGTH_SHORT).show();
Toast.makeText(context, "Please stop clicking first", Toast.LENGTH_SHORT).show();
return;
}
if (runtimeEvents.isEmpty()) {
Toast.makeText(context, "没有更多的事件需要删除", Toast.LENGTH_SHORT).show();
Toast.makeText(context, "There are no more events to be deleted", Toast.LENGTH_SHORT).show();
return;
}
@ -449,7 +449,7 @@ public class FloatingViewManager {
service.stopClicking();
} else {
Log.d(TAG, "自动点击服务没有初始化");
Toast.makeText(context, "请同意无障碍服务权限", Toast.LENGTH_SHORT).show();
Toast.makeText(context, "Please agree to the accessibility service permission", Toast.LENGTH_SHORT).show();
}
removeAllFloatingViews();
@ -467,7 +467,7 @@ public class FloatingViewManager {
service.startClicking();
Log.d(TAG, "开始多点点击事件 " + runtimeEvents.size());
Toast.makeText(context, "多点点击开始", Toast.LENGTH_SHORT).show();
Toast.makeText(context, "Click more to start", Toast.LENGTH_SHORT).show();
updateTouchPointsBackground(R.drawable.touch_point);
@ -476,7 +476,7 @@ public class FloatingViewManager {
private void stopMultiClicking(AutoClickService service) {
service.stopClicking();
Toast.makeText(context, "多点点击停止", Toast.LENGTH_SHORT).show();
Toast.makeText(context, "Click more to stop", Toast.LENGTH_SHORT).show();
updateTouchPointsBackground(R.drawable.ring_has_bg);
@ -592,7 +592,7 @@ public class FloatingViewManager {
private void handleMissingService() {
Log.e(TAG, "自动点击服务没有初始化");
Toast.makeText(context, "请在设置里同意无障碍服务", Toast.LENGTH_LONG).show();
Toast.makeText(context, "Please agree to accessibility services in the Settings", Toast.LENGTH_LONG).show();
Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
@ -650,14 +650,14 @@ public class FloatingViewManager {
public void saveCurrentEventsAsSolution(String solutionName, EventRepository.RepositoryCallback<Long> callback) {
if (solutionName == null || solutionName.trim().isEmpty()) {
Toast.makeText(context, "方案名称不能为空", Toast.LENGTH_SHORT).show();
Toast.makeText(context, "The script name cannot be empty", Toast.LENGTH_SHORT).show();
if (callback != null) {
callback.onComplete(-1L);
}
return;
}
if (runtimeEvents.isEmpty()) {
Toast.makeText(context, "当前没有事件可以保存", Toast.LENGTH_SHORT).show();
Toast.makeText(context, "There are no events that can be saved at present", Toast.LENGTH_SHORT).show();
if (callback != null) {
callback.onComplete(-1L);
}
@ -676,9 +676,9 @@ public class FloatingViewManager {
@Override
public void onError(Exception e) {
Log.e(TAG, "保存方案失败: " + e.getMessage(), e);
Toast.makeText(context, "保存方案失败: " + e.getMessage(), Toast.LENGTH_SHORT).show();
Toast.makeText(context, "Failed to save the script: " + e.getMessage(), Toast.LENGTH_SHORT).show();
if (callback != null) {
callback.onError(e); // 将错误传递给调用者
callback.onError(e);
}
}
});

View File

@ -21,7 +21,7 @@
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="?attr/selectableItemBackground"
android:text="选项卡一"
android:text="Save"
android:textColor="@color/black" />
<Button
@ -30,7 +30,7 @@
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="?attr/selectableItemBackground"
android:text="选项卡二"
android:text="Load"
android:textColor="@color/gray" />
</LinearLayout>