功能添加

This commit is contained in:
lihongwei 2025-06-26 09:53:16 +08:00
parent 6693521d11
commit bd3121eee1
9 changed files with 732 additions and 142 deletions

View File

@ -24,11 +24,16 @@ public class ForegroundService extends Service {
private static final int NOTIFICATION_ID = 1; private static final int NOTIFICATION_ID = 1;
private FloatingViewManager floatingViewManager; private FloatingViewManager floatingViewManager;
private boolean isFloatingViewShown = false; private boolean isFloatingViewShown = false;
private static ForegroundService instance;
@Override @Override
public void onCreate() { public void onCreate() {
super.onCreate(); super.onCreate();
instance = this;
createNotificationChannel(); createNotificationChannel();
try { try {
floatingViewManager = new FloatingViewManager(this); floatingViewManager = new FloatingViewManager(this);
} catch (Exception e) { } catch (Exception e) {
@ -68,6 +73,14 @@ public class ForegroundService extends Service {
return START_STICKY; return START_STICKY;
} }
public static ForegroundService getInstance() {
return instance;
}
public FloatingViewManager getFloatingViewManager() {
return floatingViewManager;
}
private void createNotificationChannel() { private void createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel( NotificationChannel channel = new NotificationChannel(
@ -86,6 +99,9 @@ public class ForegroundService extends Service {
@Override @Override
public void onDestroy() { public void onDestroy() {
super.onDestroy(); super.onDestroy();
instance = null;
if (floatingViewManager != null) { if (floatingViewManager != null) {
floatingViewManager.removeAllFloatingViews(); floatingViewManager.removeAllFloatingViews();
isFloatingViewShown = false; isFloatingViewShown = false;

View File

@ -1,20 +1,137 @@
package com.auto.clicker.autoclicker.ui.fragment.setting; package com.auto.clicker.autoclicker.ui.fragment.setting;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.os.Bundle; import android.os.Bundle;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.PopupWindow;
import android.widget.TextView;
import android.widget.Toast;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import com.auto.clicker.autoclicker.R; import com.auto.clicker.autoclicker.R;
import com.auto.clicker.autoclicker.databinding.FragmentActionBinding;
public class ActionFragment extends Fragment { public class ActionFragment extends Fragment {
private FragmentActionBinding binding;
@Override @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) { Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_action, container, false); binding = FragmentActionBinding.inflate(inflater, container, false);
binding.intervalSelectorContainer.setOnClickListener(this::showIntervalUnitSelection);
binding.unitSelectorContainer.setOnClickListener(this::showSwipeDurationUnitSelection);
return binding.getRoot();
}
private void showIntervalUnitSelection(View anchorView) {
// 加载弹窗布局
View popupView = getLayoutInflater().inflate(R.layout.dialog_unit_selection, null);
PopupWindow popupWindow = new PopupWindow(
popupView,
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT,
true // 设置为 true以便点击外部区域时自动关闭
);
// 设置背景否则点击外部无法关闭
popupWindow.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
popupWindow.setOutsideTouchable(true);
// 获取弹窗中的TextViews
TextView tvMs = popupView.findViewById(R.id.tv_unit_ms);
TextView tvSec = popupView.findViewById(R.id.tv_unit_sec);
TextView tvMin = popupView.findViewById(R.id.tv_unit_min);
// Interval 单位有毫秒分钟
tvMs.setVisibility(View.VISIBLE);
tvSec.setVisibility(View.VISIBLE);
tvMin.setVisibility(View.VISIBLE);
tvMs.setOnClickListener(v -> {
binding.intervalSelectedUnit.setText(getString(R.string.milliseconds_unit));
popupWindow.dismiss();
showInputValue("Interval", binding.etIntervalValue.getText().toString(), getString(R.string.milliseconds_unit));
});
tvSec.setOnClickListener(v -> {
binding.intervalSelectedUnit.setText(getString(R.string.seconds_unit));
popupWindow.dismiss();
showInputValue("Interval", binding.etIntervalValue.getText().toString(), getString(R.string.seconds_unit));
});
tvMin.setOnClickListener(v -> {
binding.intervalSelectedUnit.setText(getString(R.string.minutes_unit));
popupWindow.dismiss();
showInputValue("Interval", binding.etIntervalValue.getText().toString(), getString(R.string.minutes_unit));
});
// 显示弹窗使其显示在anchorView下方
popupWindow.showAsDropDown(anchorView);
}
private void showSwipeDurationUnitSelection(View anchorView) {
// 加载弹窗布局
View popupView = getLayoutInflater().inflate(R.layout.dialog_unit_selection, null);
PopupWindow popupWindow = new PopupWindow(
popupView,
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT,
true // 设置为 true以便点击外部区域时自动关闭
);
// 设置背景否则点击外部无法关闭
popupWindow.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
popupWindow.setOutsideTouchable(true);
// 获取弹窗中的TextViews
TextView tvMs = popupView.findViewById(R.id.tv_unit_ms);
TextView tvSec = popupView.findViewById(R.id.tv_unit_sec);
TextView tvMin = popupView.findViewById(R.id.tv_unit_min);
// Swipe duration 单位只有毫秒
tvMs.setVisibility(View.VISIBLE);
tvSec.setVisibility(View.VISIBLE);
tvMin.setVisibility(View.GONE); // 隐藏分钟选项
tvMs.setOnClickListener(v -> {
binding.tvSelectedUnit.setText(getString(R.string.milliseconds_unit));
popupWindow.dismiss();
showInputValue("Swipe Duration", binding.etDurationValue.getText().toString(), getString(R.string.milliseconds_unit));
});
tvSec.setOnClickListener(v -> {
binding.tvSelectedUnit.setText(getString(R.string.seconds_unit));
popupWindow.dismiss();
showInputValue("Swipe Duration", binding.etDurationValue.getText().toString(), getString(R.string.seconds_unit));
});
// 显示弹窗使其显示在anchorView下方
popupWindow.showAsDropDown(anchorView);
}
private void showInputValue(String type, String value, String unit) {
String message = type + " 值: " + (value.isEmpty() ? "未输入" : value) + ", 单位: " + unit;
Toast.makeText(getContext(), message, Toast.LENGTH_SHORT).show();
if (type.equals("Interval")) {
long intervalValue = value.isEmpty() ? 0 : Long.parseLong(value);
// 根据 unit 进行单位转换或保存
} else if (type.equals("Swipe Duration")) {
long durationValue = value.isEmpty() ? 0 : Long.parseLong(value);
// 根据 unit 进行单位转换或保存
}
}
@Override
public void onDestroyView() {
super.onDestroyView();
binding = null;
} }
} }

View File

@ -1,20 +1,166 @@
package com.auto.clicker.autoclicker.ui.fragment.setting; package com.auto.clicker.autoclicker.ui.fragment.setting;
import android.os.Bundle; import android.os.Bundle;
import android.util.Log;
import android.util.TypedValue;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.SeekBar;
import android.widget.Toast;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import com.auto.clicker.autoclicker.R; import com.auto.clicker.autoclicker.databinding.FragmentUISizeBinding;
import com.auto.clicker.autoclicker.service.ForegroundService;
import com.auto.clicker.autoclicker.view.FloatingViewManager;
public class UISizeFragment extends Fragment { public class UISizeFragment extends Fragment {
private static final String TAG = "UISizeFragment";
private FragmentUISizeBinding binding;
private FloatingViewManager floatingViewManager;
private static final int MIN_POINT_SIZE = 50;
private static final int MAX_POINT_SIZE = 200;
private static final int MIN_CONTROL_BAR_WIDTH_DP = 20;
private static final int MAX_CONTROL_BAR_WIDTH_DP = 80;
@Override @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) { Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_u_i_size, container, false); binding = FragmentUISizeBinding.inflate(inflater, container, false);
ForegroundService service = ForegroundService.getInstance();
if (service != null) {
floatingViewManager = service.getFloatingViewManager();
}
if (floatingViewManager != null) {
setupPointSizeControl();
setupControlBarSizeControl();
} else {
Toast.makeText(getContext(), "自动化服务未运行UI大小设置功能不可用。", Toast.LENGTH_LONG).show();
disableAllControlUI();
}
return binding.getRoot();
}
private void setupPointSizeControl() {
binding.pointSeekbar.setMax(MAX_POINT_SIZE - MIN_POINT_SIZE);
int currentPointSize = floatingViewManager.getTouchPointSize();
currentPointSize = Math.max(MIN_POINT_SIZE, Math.min(MAX_POINT_SIZE, currentPointSize));
binding.pointSeekbar.setProgress(currentPointSize - MIN_POINT_SIZE);
updatePointSizeUI(currentPointSize);
binding.pointSeekbar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
int newSize = progress + MIN_POINT_SIZE;
updatePointSizeUI(newSize);
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
int finalSize = seekBar.getProgress() + MIN_POINT_SIZE;
floatingViewManager.setTouchPointSize(finalSize);
Log.d(TAG, "触摸点大小设置为: " + finalSize + "px");
Toast.makeText(getContext(), "触摸点大小已更新", Toast.LENGTH_SHORT).show();
}
});
}
private void setupControlBarSizeControl() {
int minControlBarWidthPx = dpToPx(MIN_CONTROL_BAR_WIDTH_DP);
int maxControlBarWidthPx = dpToPx(MAX_CONTROL_BAR_WIDTH_DP);
binding.barSeekbar.setMax(maxControlBarWidthPx - minControlBarWidthPx);
int currentBarWidthPx = floatingViewManager.getControlBarWidth();
currentBarWidthPx = Math.max(minControlBarWidthPx, Math.min(maxControlBarWidthPx, currentBarWidthPx));
binding.barSeekbar.setProgress(currentBarWidthPx - minControlBarWidthPx);
binding.currentBarSizeText.setText(String.valueOf(pxToDp(currentBarWidthPx)) + "dp");
binding.barSeekbar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
int newWidthPx = progress + minControlBarWidthPx;
binding.currentBarSizeText.setText(String.valueOf(pxToDp(newWidthPx)) + "dp");
updateControlBarPreview(newWidthPx);
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
int finalWidthPx = seekBar.getProgress() + minControlBarWidthPx;
floatingViewManager.setControlBarSize(finalWidthPx);
Log.d(TAG, "控制栏大小设置为: " + pxToDp(finalWidthPx) + "dp (" + finalWidthPx + "px)");
Toast.makeText(getContext(), "控制栏大小已更新", Toast.LENGTH_SHORT).show();
}
});
}
private int dpToPx(int dp) {
if (getContext() == null) return dp;
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, getContext().getResources().getDisplayMetrics());
}
private int pxToDp(int px) {
if (getContext() == null) return px;
return (int) (px / getContext().getResources().getDisplayMetrics().density);
}
private void updatePointSizeUI(int size) {
binding.currentPointSizeText.setText(String.valueOf(size) + "px");
ViewGroup.LayoutParams params = binding.ringPreview.getLayoutParams();
params.width = size;
params.height = size;
binding.ringPreview.setLayoutParams(params);
float textSize = (float) size / (MAX_POINT_SIZE / 19f);
binding.ringText.setTextSize(textSize);
}
private void updateControlBarPreview(int width) {
ViewGroup.LayoutParams params = binding.controlBar.getLayoutParams();
params.width = width;
params.height = (int) (width * (249f / 37f));
binding.controlBar.setLayoutParams(params);
}
private void disableAllControlUI() {
disablePointSizeControlUI();
binding.barSeekbar.setEnabled(false);
binding.barSizeContainer.setAlpha(0.5f);
}
private void disablePointSizeControlUI() {
binding.pointSeekbar.setEnabled(false);
binding.currentPointSizeText.setText("N/A");
binding.ringPreview.setVisibility(View.GONE);
binding.ringText.setVisibility(View.GONE);
binding.pointSizeContainer.setAlpha(0.5f);
}
@Override
public void onDestroyView() {
super.onDestroyView();
binding = null;
} }
} }

View File

@ -5,6 +5,7 @@ import android.graphics.Color;
import android.graphics.PixelFormat; import android.graphics.PixelFormat;
import android.os.Build; import android.os.Build;
import android.util.Log; import android.util.Log;
import android.util.TypedValue;
import android.view.Gravity; import android.view.Gravity;
import android.view.WindowManager; import android.view.WindowManager;
import android.widget.TextView; import android.widget.TextView;
@ -23,7 +24,7 @@ public class EventViewBinder {
public static void bindPoint(Context context, WindowManager windowManager, public static void bindPoint(Context context, WindowManager windowManager,
PointEvent pointEvent, int touchPointSize, PointEvent pointEvent, int touchPointSize,
int screenWidth, int screenHeight, int screenWidth, int screenHeight,
OnPositionChangedCallback callback,int order) { OnPositionChangedCallback callback,int order,float initialTextSize) {
TextView pointView = createTouchPointView(context, String.valueOf(order)); TextView pointView = createTouchPointView(context, String.valueOf(order));
Log.d("bind","id: " + order); Log.d("bind","id: " + order);
@ -35,6 +36,8 @@ public class EventViewBinder {
pointEvent.setView(pointView); pointEvent.setView(pointView);
pointEvent.setLayoutParams(params); pointEvent.setLayoutParams(params);
pointView.setTextSize(TypedValue.COMPLEX_UNIT_SP, initialTextSize);
DraggableHelper.makeDraggable(pointView, params, windowManager, DraggableHelper.makeDraggable(pointView, params, windowManager,
screenWidth, screenHeight, screenWidth, screenHeight,
(x, y) -> { (x, y) -> {
@ -49,7 +52,7 @@ public class EventViewBinder {
public static void bindSlide(Context context, WindowManager windowManager, public static void bindSlide(Context context, WindowManager windowManager,
SlideEvent slideEvent, int touchPointSize, SlideEvent slideEvent, int touchPointSize,
int screenWidth, int screenHeight, int screenWidth, int screenHeight,
OnPositionChangedCallback callback,int order) { OnPositionChangedCallback callback,int order,float initialTextSize) {
PointEvent start = slideEvent.getStartPoint(); PointEvent start = slideEvent.getStartPoint();
PointEvent end = slideEvent.getEndPoint(); PointEvent end = slideEvent.getEndPoint();
@ -69,6 +72,9 @@ public class EventViewBinder {
end.setView(endView); end.setView(endView);
end.setLayoutParams(endParams); end.setLayoutParams(endParams);
startView.setTextSize(TypedValue.COMPLEX_UNIT_SP, initialTextSize);
endView.setTextSize(TypedValue.COMPLEX_UNIT_SP, initialTextSize);
ConnectingLineView lineView = new ConnectingLineView(context); ConnectingLineView lineView = new ConnectingLineView(context);
WindowManager.LayoutParams lineParams = createLineViewParams(context); WindowManager.LayoutParams lineParams = createLineViewParams(context);
@ -108,7 +114,6 @@ public class EventViewBinder {
touchPointView.setGravity(Gravity.CENTER); touchPointView.setGravity(Gravity.CENTER);
touchPointView.setText(label); touchPointView.setText(label);
touchPointView.setTextColor(Color.WHITE); touchPointView.setTextColor(Color.WHITE);
touchPointView.setTextSize(19);
return touchPointView; return touchPointView;
} }
@ -148,7 +153,7 @@ public class EventViewBinder {
return params; return params;
} }
private static void updateLinePosition(SlideEvent slideEvent, int touchPointSize) { public static void updateLinePosition(SlideEvent slideEvent, int touchPointSize) {
PointEvent start = slideEvent.getStartPoint(); PointEvent start = slideEvent.getStartPoint();
PointEvent end = slideEvent.getEndPoint(); PointEvent end = slideEvent.getEndPoint();
ConnectingLineView lineView = slideEvent.getLineView(); ConnectingLineView lineView = slideEvent.getLineView();

View File

@ -5,6 +5,7 @@ import android.content.BroadcastReceiver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.graphics.PixelFormat; import android.graphics.PixelFormat;
import android.graphics.Point; import android.graphics.Point;
import android.os.Build; import android.os.Build;
@ -13,15 +14,18 @@ import android.os.Looper;
import android.provider.Settings; import android.provider.Settings;
import android.text.InputType; import android.text.InputType;
import android.util.Log; import android.util.Log;
import android.util.TypedValue;
import android.view.Gravity; import android.view.Gravity;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.view.View; import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager; import android.view.WindowManager;
import android.view.inputmethod.InputMethodManager; import android.view.inputmethod.InputMethodManager;
import android.widget.EditText; import android.widget.EditText;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.LinearLayout; import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
@ -52,7 +56,10 @@ import java.util.List;
public class FloatingViewManager { public class FloatingViewManager {
private static final String TAG = "FloatingViewManager"; private static final String TAG = "FloatingViewManager";
public static final long DEBOUNCE_INTERVAL = 500; public static final long DEBOUNCE_INTERVAL = 500;
public static final int TOUCH_POINT_SIZE = 100;
private int touchPointSize;
private int controlBarWidth;
private final Context context; private final Context context;
private final WindowManager windowManager; private final WindowManager windowManager;
@ -74,6 +81,13 @@ public class FloatingViewManager {
private final EventRepository eventRepository; private final EventRepository eventRepository;
private static final String PREFS_NAME = "AutoClickerPrefs";
private static final String KEY_TOUCH_POINT_SIZE = "touch_point_size";
private static final String KEY_CONTROL_BAR_WIDTH = "control_bar_width";
private static final int DEFAULT_TOUCH_POINT_SIZE = 100;
private static final int DEFAULT_CONTROL_BAR_WIDTH_DP = 38;
public FloatingViewManager(Context context) { public FloatingViewManager(Context context) {
this.context = context; this.context = context;
this.windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); this.windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
@ -83,6 +97,10 @@ public class FloatingViewManager {
this.floatingTabDialogManager = new FloatingTabDialogManager(context); this.floatingTabDialogManager = new FloatingTabDialogManager(context);
this.floatingTabDialogManager.setFloatingViewManager(this); this.floatingTabDialogManager.setFloatingViewManager(this);
this.eventRepository = new EventRepository(context.getApplicationContext()); this.eventRepository = new EventRepository(context.getApplicationContext());
this.touchPointSize = 100;
loadSettings();
registerBroadcastReceiver(); registerBroadcastReceiver();
} }
@ -112,16 +130,24 @@ public class FloatingViewManager {
} }
private void initMultipleControlBar() { private void initMultipleControlBar() {
if (multipleControlBarView != null) {
safeRemoveView(multipleControlBarView);
multipleControlBarView = null;
}
multipleControlBarView = (LinearLayout) LayoutInflater.from(context).inflate(R.layout.multiple_control_bar, null); multipleControlBarView = (LinearLayout) LayoutInflater.from(context).inflate(R.layout.multiple_control_bar, null);
multipleControlBarParams = createControlBarParams(); multipleControlBarParams = createControlBarParams();
setupDraggableView(multipleControlBarView, multipleControlBarParams, this::updateMoreControlBarPosition); multipleControlBarParams.width = this.controlBarWidth;
multipleControlBarParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
setupDraggableView(multipleControlBarView, multipleControlBarParams, this::updateMoreControlBarPosition);
setupControlButtons(multipleControlBarView); setupControlButtons(multipleControlBarView);
updateControlBarImageViews(multipleControlBarView, this.controlBarWidth);
} }
private WindowManager.LayoutParams createControlBarParams() { private WindowManager.LayoutParams createControlBarParams() {
// 创建控制栏的悬浮窗参数
int overlayType = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O ? int overlayType = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O ?
WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY : WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY :
WindowManager.LayoutParams.TYPE_PHONE; WindowManager.LayoutParams.TYPE_PHONE;
@ -257,12 +283,14 @@ public class FloatingViewManager {
return; return;
} }
PointEvent pointEvent = new PointEvent(eventOrderCounter, screenWidth / 2 - TOUCH_POINT_SIZE / 2, PointEvent pointEvent = new PointEvent(eventOrderCounter, screenWidth / 2 - touchPointSize / 2,
screenHeight / 2 - TOUCH_POINT_SIZE / 2); screenHeight / 2 - touchPointSize / 2);
float currentTextSize = calculateTextSizeForPointSize(this.touchPointSize);
EventViewBinder.bindPoint(context, windowManager, pointEvent, EventViewBinder.bindPoint(context, windowManager, pointEvent,
TOUCH_POINT_SIZE, screenWidth, screenHeight, this.touchPointSize, screenWidth, screenHeight,
(x, y) -> updateServicePositions(), eventOrderCounter); (x, y) -> updateServicePositions(), eventOrderCounter, currentTextSize);
runtimeEvents.add(new EventWrapper(EventWrapper.EventType.POINT, pointEvent, eventOrderCounter)); runtimeEvents.add(new EventWrapper(EventWrapper.EventType.POINT, pointEvent, eventOrderCounter));
eventOrderCounter++; eventOrderCounter++;
@ -278,9 +306,11 @@ public class FloatingViewManager {
PointEvent endPoint = new PointEvent(eventOrderCounter, screenWidth / 2 + 100, screenHeight / 2 - 50); PointEvent endPoint = new PointEvent(eventOrderCounter, screenWidth / 2 + 100, screenHeight / 2 - 50);
SlideEvent slideEvent = new SlideEvent(eventOrderCounter, startPoint, endPoint); SlideEvent slideEvent = new SlideEvent(eventOrderCounter, startPoint, endPoint);
float currentTextSize = calculateTextSizeForPointSize(this.touchPointSize);
EventViewBinder.bindSlide(context, windowManager, slideEvent, EventViewBinder.bindSlide(context, windowManager, slideEvent,
TOUCH_POINT_SIZE, screenWidth, screenHeight, this.touchPointSize, screenWidth, screenHeight,
(x, y) -> updateServicePositions(), eventOrderCounter); (x, y) -> updateServicePositions(), eventOrderCounter, currentTextSize);
runtimeEvents.add(new EventWrapper(EventWrapper.EventType.SLIDE, slideEvent, eventOrderCounter)); runtimeEvents.add(new EventWrapper(EventWrapper.EventType.SLIDE, slideEvent, eventOrderCounter));
eventOrderCounter++; eventOrderCounter++;
@ -649,11 +679,13 @@ public class FloatingViewManager {
if (clickPoint != null) { if (clickPoint != null) {
PointEvent pointEvent = new PointEvent(eventEntity.orderInSolution, clickPoint.x, clickPoint.y); PointEvent pointEvent = new PointEvent(eventEntity.orderInSolution, clickPoint.x, clickPoint.y);
float currentTextSize = calculateTextSizeForPointSize(FloatingViewManager.this.touchPointSize);
EventViewBinder.bindPoint(context, windowManager, EventViewBinder.bindPoint(context, windowManager,
pointEvent, pointEvent,
TOUCH_POINT_SIZE, FloatingViewManager.this.touchPointSize,
screenWidth, screenHeight, screenWidth, screenHeight,
(x, y) -> updateServicePositions(), eventEntity.orderInSolution); (x, y) -> updateServicePositions(), eventEntity.orderInSolution, currentTextSize);
runtimeEvents.add(new EventWrapper(type, pointEvent, pointEvent.getId())); runtimeEvents.add(new EventWrapper(type, pointEvent, pointEvent.getId()));
} }
@ -667,11 +699,13 @@ public class FloatingViewManager {
SlideEvent slideEvent = new SlideEvent(eventEntity.orderInSolution, startPointEvent, endPointEvent); SlideEvent slideEvent = new SlideEvent(eventEntity.orderInSolution, startPointEvent, endPointEvent);
float currentTextSize = calculateTextSizeForPointSize(FloatingViewManager.this.touchPointSize);
EventViewBinder.bindSlide(context, windowManager, EventViewBinder.bindSlide(context, windowManager,
slideEvent, slideEvent,
TOUCH_POINT_SIZE, FloatingViewManager.this.touchPointSize,
screenWidth, screenHeight, screenWidth, screenHeight,
(x, y) -> updateServicePositions(), eventEntity.orderInSolution); (x, y) -> updateServicePositions(), eventEntity.orderInSolution, currentTextSize);
runtimeEvents.add(new EventWrapper(type, slideEvent, slideEvent.getId())); runtimeEvents.add(new EventWrapper(type, slideEvent, slideEvent.getId()));
} }
@ -715,4 +749,202 @@ public class FloatingViewManager {
lastToggleTime = currentTime; lastToggleTime = currentTime;
return false; return false;
} }
private void loadSettings() {
SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
this.touchPointSize = prefs.getInt(KEY_TOUCH_POINT_SIZE, DEFAULT_TOUCH_POINT_SIZE);
int defaultControlBarWidthPx = dpToPx(DEFAULT_CONTROL_BAR_WIDTH_DP);
this.controlBarWidth = prefs.getInt(KEY_CONTROL_BAR_WIDTH, defaultControlBarWidthPx);
Log.d(TAG, "设置已加载:触摸点大小=" + this.touchPointSize + ", 控制栏宽度=" + this.controlBarWidth);
}
public void setTouchPointSize(int newSize) {
if (this.touchPointSize == newSize || newSize <= 0) {
return;
}
this.touchPointSize = newSize;
saveTouchPointSize(newSize);
for (EventWrapper wrapper : new ArrayList<>(runtimeEvents)) {
if (wrapper.getType() == EventWrapper.EventType.POINT) {
PointEvent pe = (PointEvent) wrapper.getEvent();
updatePointViewSize(pe);
} else if (wrapper.getType() == EventWrapper.EventType.SLIDE) {
SlideEvent se = (SlideEvent) wrapper.getEvent();
updateSlideViewSize(se);
}
}
}
private void updatePointViewSize(PointEvent pointEvent) {
if (pointEvent.getView() != null && pointEvent.getLayoutParams() != null) {
TextView pointView = pointEvent.getView();
WindowManager.LayoutParams params = pointEvent.getLayoutParams();
params.width = touchPointSize;
params.height = touchPointSize;
float newTextSize = calculateTextSizeForPointSize(touchPointSize);
pointView.setTextSize(TypedValue.COMPLEX_UNIT_SP, newTextSize);
try {
windowManager.updateViewLayout(pointView, params);
} catch (IllegalArgumentException e) {
Log.w(TAG, "尝试更新不存在的点击点视图", e);
}
}
}
private void updateSlideViewSize(SlideEvent slideEvent) {
float newTextSize = calculateTextSizeForPointSize(touchPointSize); // 调用新方法计算
if (slideEvent.getStartPoint().getView() != null && slideEvent.getStartPoint().getLayoutParams() != null) {
TextView startView = slideEvent.getStartPoint().getView();
WindowManager.LayoutParams startParams = slideEvent.getStartPoint().getLayoutParams();
startParams.width = touchPointSize;
startParams.height = touchPointSize;
startView.setTextSize(TypedValue.COMPLEX_UNIT_SP, newTextSize);
try {
windowManager.updateViewLayout(startView, startParams);
} catch (IllegalArgumentException e) {
Log.w(TAG, "尝试更新不存在的滑动起点视图", e);
}
}
if (slideEvent.getEndPoint().getView() != null && slideEvent.getEndPoint().getLayoutParams() != null) {
TextView endView = slideEvent.getEndPoint().getView();
WindowManager.LayoutParams endParams = slideEvent.getEndPoint().getLayoutParams();
endParams.width = touchPointSize;
endParams.height = touchPointSize;
endView.setTextSize(TypedValue.COMPLEX_UNIT_SP, newTextSize);
try {
windowManager.updateViewLayout(endView, endParams);
} catch (IllegalArgumentException e) {
Log.w(TAG, "尝试更新不存在的滑动终点视图", e);
}
}
EventViewBinder.updateLinePosition(slideEvent, touchPointSize);
}
private float calculateTextSizeForPointSize(int pointSize) {
final int MIN_POINT_SIZE_FOR_CALC = 50;
final int MAX_POINT_SIZE_FOR_CALC = 200;
final float MIN_TEXT_SIZE_SP = 10f;
final float MAX_TEXT_SIZE_SP = 30f;
float ratio = (float) (pointSize - MIN_POINT_SIZE_FOR_CALC) / (MAX_POINT_SIZE_FOR_CALC - MIN_POINT_SIZE_FOR_CALC);
return MIN_TEXT_SIZE_SP + (MAX_TEXT_SIZE_SP - MIN_TEXT_SIZE_SP) * ratio;
}
private void saveTouchPointSize(int size) {
SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
prefs.edit().putInt(KEY_TOUCH_POINT_SIZE, size).apply();
Log.d(TAG, "触摸点大小已保存: " + size);
}
private void saveControlBarWidth(int width) {
SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
prefs.edit().putInt(KEY_CONTROL_BAR_WIDTH, width).apply();
Log.d(TAG, "控制栏宽度已保存: " + width);
}
public int getTouchPointSize() {
return this.touchPointSize;
}
private void updateControlBarImageViews(ViewGroup parentView, int newBarWidthPx) {
float scaleFactor = (float) newBarWidthPx / dpToPx(DEFAULT_CONTROL_BAR_WIDTH_DP);
for (int i = 0; i < parentView.getChildCount(); i++) {
View child = parentView.getChildAt(i);
if (child instanceof ViewGroup) {
// 递归处理嵌套的 ViewGroup
updateControlBarImageViews((ViewGroup) child, newBarWidthPx);
} else if (child instanceof ImageView) {
ImageView imageView = (ImageView) child;
ViewGroup.LayoutParams params = imageView.getLayoutParams();
if (imageView.getId() == R.id.cut_off) {
params.width = ViewGroup.LayoutParams.MATCH_PARENT;
params.height = dpToPx(1);
imageView.setLayoutParams(params);
continue;
}
int originalImgWidthDp = 0;
int originalImgHeightDp = 0;
if (imageView.getId() == R.id.play_button || imageView.getId() == R.id.add_button || imageView.getId() == R.id.remove_button || imageView.getId() == R.id.close_button) {
originalImgWidthDp = 13;
if (imageView.getId() == R.id.remove_button) {
originalImgHeightDp = 2;
} else {
originalImgHeightDp = 13;
}
} else if (imageView.getId() == R.id.slide_button) {
originalImgWidthDp = 17;
originalImgHeightDp = 17;
} else if (imageView.getId() == R.id.save_button) {
originalImgWidthDp = 15;
originalImgHeightDp = 15;
} else if (imageView.getId() == R.id.eye_button) {
originalImgWidthDp = 16;
originalImgHeightDp = 9;
} else if (imageView.getId() == R.id.settings_button) {
originalImgWidthDp = 14;
originalImgHeightDp = 14;
}
if (originalImgWidthDp > 0) {
// 根据原始 dp 值和缩放因子计算新的像素尺寸
params.width = (int) (dpToPx(originalImgWidthDp) * scaleFactor);
params.height = (int) (dpToPx(originalImgHeightDp) * scaleFactor);
imageView.setLayoutParams(params);
} else {
if (params.width > 0) {
params.width = (int) (dpToPx(params.width) * scaleFactor);
}
if (params.height > 0) {
params.height = (int) (dpToPx(params.height) * scaleFactor);
}
imageView.setLayoutParams(params);
}
}
}
}
private int dpToPx(int dp) {
if (context == null) return dp;
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, context.getResources().getDisplayMetrics());
}
public void setControlBarSize(int newWidth) {
if (this.controlBarWidth == newWidth || newWidth <= 0) {
return;
}
this.controlBarWidth = newWidth;
saveControlBarWidth(newWidth);
if (multipleControlBarView != null && multipleControlBarParams != null) {
multipleControlBarParams.width = this.controlBarWidth;
multipleControlBarParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
try {
windowManager.updateViewLayout(multipleControlBarView, multipleControlBarParams);
updateControlBarImageViews(multipleControlBarView, this.controlBarWidth);
} catch (IllegalArgumentException e) {
Log.w(TAG, "尝试更新不存在的控制栏视图", e);
}
}
}
public int getControlBarWidth() {
return controlBarWidth;
}
} }

View File

@ -0,0 +1,45 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/bg_rounded_rect"
android:orientation="vertical"
android:padding="8dp">
<TextView
android:id="@+id/tv_unit_ms"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:padding="10dp"
android:text="@string/milliseconds_unit"
android:textColor="@color/text_gray"
android:textSize="16sp" />
<TextView
android:id="@+id/tv_unit_sec"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:padding="10dp"
android:text="@string/seconds_unit"
android:textColor="@color/text_gray"
android:textSize="16sp" />
<TextView
android:id="@+id/tv_unit_min"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:padding="10dp"
android:text="@string/minutes_unit"
android:textColor="@color/text_gray"
android:textSize="16sp" />
</LinearLayout>

View File

@ -1,9 +1,9 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android" <androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:context=".ui.fragment.setting.UISizeFragment"> tools:context=".ui.fragment.setting.UISizeFragment">
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
@ -11,136 +11,164 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingBottom="25dp"> android:paddingBottom="25dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/point_size_container"
android:layout_width="0dp"
android:layout_height="180dp"
android:layout_marginStart="20dp"
android:layout_marginTop="14dp"
android:layout_marginEnd="20dp"
android:background="@drawable/bg_rounded_rect"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@+id/pint_size_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="19dp"
android:layout_marginTop="7dp"
android:text="Action Point Size"
android:textColor="@color/text_gray"
android:textSize="15sp"
android:textStyle="bold"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<SeekBar
android:id="@+id/point_seekbar"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="14dp"
android:layout_marginEnd="11dp"
android:layout_marginTop="29dp"
android:max="100"
android:maxHeight="5dp"
android:progress="0"
app:layout_constraintTop_toBottomOf="@+id/pint_size_title"
android:progressDrawable="@drawable/seek_bar_color_white"
android:thumb="@drawable/seekbar_thumb_white" />
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/ring_with_text_container" android:id="@+id/point_size_container"
android:layout_width="wrap_content" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="180dp"
android:layout_marginTop="27dp" android:layout_marginStart="20dp"
app:layout_constraintStart_toStartOf="parent" android:layout_marginTop="14dp"
android:layout_marginEnd="20dp"
android:background="@drawable/bg_rounded_rect"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/point_seekbar"> app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<ImageView <TextView
android:id="@+id/ring" android:id="@+id/pint_size_title"
android:layout_width="33dp" android:layout_width="wrap_content"
android:layout_height="33dp" android:layout_height="wrap_content"
android:src="@drawable/ring_has_bg" android:layout_marginStart="19dp"
app:layout_constraintBottom_toBottomOf="parent" android:layout_marginTop="7dp"
app:layout_constraintEnd_toEndOf="parent" android:text="Action Point Size"
android:textColor="@color/text_gray"
android:textSize="15sp"
android:textStyle="bold"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />
<TextView <TextView
android:id="@+id/ring_text" android:id="@+id/current_point_size_text"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="P" android:layout_marginTop="7dp"
android:textColor="#FFFFFF" android:layout_marginEnd="19dp"
android:textSize="19sp" android:textColor="@color/text_gray"
android:textSize="15sp"
android:textStyle="bold" android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="@+id/ring" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintEnd_toEndOf="@+id/ring" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="@+id/ring" tools:text="100px" />
app:layout_constraintTop_toTopOf="@+id/ring" />
<SeekBar
android:id="@+id/point_seekbar"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="14dp"
android:layout_marginTop="29dp"
android:layout_marginEnd="11dp"
android:max="100"
android:maxHeight="5dp"
android:progress="0"
android:progressDrawable="@drawable/seek_bar_color_white"
android:thumb="@drawable/seekbar_thumb_white"
app:layout_constraintTop_toBottomOf="@+id/pint_size_title" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/ring_with_text_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="27dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/point_seekbar">
<ImageView
android:id="@+id/ring_preview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ring_has_bg"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/ring_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="P"
android:textColor="#FFFFFF"
android:textSize="19sp"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="@+id/ring_preview"
app:layout_constraintEnd_toEndOf="@+id/ring_preview"
app:layout_constraintStart_toStartOf="@+id/ring_preview"
app:layout_constraintTop_toTopOf="@+id/ring_preview" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/bar_size_container"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_marginTop="22dp"
android:layout_marginEnd="20dp"
android:background="@drawable/bg_rounded_rect"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/point_size_container">
<TextView
android:id="@+id/bar_size_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="19dp"
android:layout_marginTop="7dp"
android:text="Control Bar Size"
android:textColor="@color/text_gray"
android:textSize="15sp"
android:textStyle="bold"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/current_bar_size_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="7dp"
android:layout_marginEnd="19dp"
android:textColor="@color/text_gray"
android:textSize="15sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="38dp" />
<SeekBar
android:id="@+id/bar_seekbar"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="14dp"
android:layout_marginTop="29dp"
android:layout_marginEnd="11dp"
android:max="100"
android:maxHeight="5dp"
android:progress="0"
android:progressDrawable="@drawable/seek_bar_color_white"
android:thumb="@drawable/seekbar_thumb_white"
app:layout_constraintTop_toBottomOf="@+id/bar_size_title" />
<ImageView
android:id="@+id/control_bar"
android:layout_width="37dp"
android:layout_height="249dp"
android:layout_marginTop="50dp"
android:layout_marginBottom="50dp"
android:src="@mipmap/multi_control_bar"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/bar_seekbar" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/bar_size_container"
android:layout_width="0dp"
android:layout_height="419dp"
android:layout_marginStart="20dp"
android:layout_marginTop="22dp"
android:layout_marginEnd="20dp"
android:background="@drawable/bg_rounded_rect"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/point_size_container">
<TextView
android:id="@+id/bar_size_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="19dp"
android:layout_marginTop="7dp"
android:text="Control Bar Size"
android:textColor="@color/text_gray"
android:textSize="15sp"
android:textStyle="bold"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<SeekBar
android:id="@+id/bar_seekbar"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="14dp"
android:layout_marginEnd="11dp"
android:layout_marginTop="29dp"
android:max="100"
android:maxHeight="5dp"
android:progress="0"
app:layout_constraintTop_toBottomOf="@+id/bar_size_title"
android:progressDrawable="@drawable/seek_bar_color_white"
android:thumb="@drawable/seekbar_thumb_white" />
<ImageView
android:layout_width="37dp"
android:layout_height="249dp"
android:id="@+id/control_bar"
android:src="@mipmap/multi_control_bar"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/bar_seekbar"
android:layout_marginTop="50dp" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.core.widget.NestedScrollView> </androidx.core.widget.NestedScrollView>

View File

@ -9,8 +9,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="center_horizontal" android:gravity="center_horizontal"
android:orientation="vertical" android:orientation="vertical">
android:paddingHorizontal="10dp">
<ImageView <ImageView
android:id="@+id/play_button" android:id="@+id/play_button"

View File

@ -45,5 +45,7 @@ functionalities.</string>
<string name="hello_blank_fragment">Hello blank fragment</string> <string name="hello_blank_fragment">Hello blank fragment</string>
<string name="preview"><u>Preview</u></string> <string name="preview"><u>Preview</u></string>
<string name="accessibility_service_description">This service enables auto-clicking on the screen.</string> <string name="accessibility_service_description">This service enables auto-clicking on the screen.</string>
<string name="milliseconds_unit">milliseconds</string>
<string name="seconds_unit">seconds</string>
<string name="minutes_unit">minutes</string>
</resources> </resources>