diff --git a/gradlew b/gradlew
old mode 100644
new mode 100755
diff --git a/stallhelper/build.properties b/stallhelper/build.properties
index 17c5765..6c6c26a 100644
--- a/stallhelper/build.properties
+++ b/stallhelper/build.properties
@@ -1,8 +1,8 @@
#Created by .winboll/winboll_app_build.gradle
-#Wed May 13 02:22:06 HKT 2026
-stageCount=2
+#Fri May 22 20:27:28 HKT 2026
+stageCount=9
libraryProject=
baseVersion=15.20
-publishVersion=15.20.1
+publishVersion=15.20.8
buildCount=0
-baseBetaVersion=15.20.2
+baseBetaVersion=15.20.9
diff --git a/stallhelper/src/main/AndroidManifest.xml b/stallhelper/src/main/AndroidManifest.xml
index 4a32a9e..4afde5f 100644
--- a/stallhelper/src/main/AndroidManifest.xml
+++ b/stallhelper/src/main/AndroidManifest.xml
@@ -42,8 +42,10 @@
android:name="cc.winboll.studio.stallhelper.activities.CompleteDiningTableActivity"
android:windowSoftInputMode="adjustResize"/>
-
+
-
+
-
\ No newline at end of file
+
+
+
\ No newline at end of file
diff --git a/stallhelper/src/main/java/cc/winboll/studio/stallhelper/App.java b/stallhelper/src/main/java/cc/winboll/studio/stallhelper/App.java
index 095c241..c28dc06 100644
--- a/stallhelper/src/main/java/cc/winboll/studio/stallhelper/App.java
+++ b/stallhelper/src/main/java/cc/winboll/studio/stallhelper/App.java
@@ -8,6 +8,7 @@ package cc.winboll.studio.stallhelper;
import cc.winboll.studio.libaes.utils.WinBoLLActivityManager;
import cc.winboll.studio.libappbase.GlobalApplication;
import cc.winboll.studio.libappbase.ToastUtils;
+import cc.winboll.studio.libappbase.BuildConfig;
public class App extends GlobalApplication {
@@ -17,8 +18,9 @@ public class App extends GlobalApplication {
public void onCreate() {
super.onCreate();
- super.onCreate();
- setIsDebugging(BuildConfig.DEBUG);
+ if (isDebugging() != true) {
+ setIsDebugging(BuildConfig.DEBUG);
+ }
//setIsDebugging(false);
WinBoLLActivityManager.init(this);
diff --git a/stallhelper/src/main/java/cc/winboll/studio/stallhelper/MainActivity.java b/stallhelper/src/main/java/cc/winboll/studio/stallhelper/MainActivity.java
index e59a874..f8c5f60 100644
--- a/stallhelper/src/main/java/cc/winboll/studio/stallhelper/MainActivity.java
+++ b/stallhelper/src/main/java/cc/winboll/studio/stallhelper/MainActivity.java
@@ -13,6 +13,7 @@ import cc.winboll.studio.stallhelper.activities.AboutActivity;
import cc.winboll.studio.stallhelper.activities.CompleteDiningTableActivity;
import cc.winboll.studio.stallhelper.activities.NoteRecordHelperActivity;
import cc.winboll.studio.stallhelper.activities.PreNullDiningTableActivity;
+import cc.winboll.studio.stallhelper.activities.TaskSchedulerActivity;
import cc.winboll.studio.stallhelper.activities.WinBoLLActivity;
final public class MainActivity extends WinBoLLActivity {
@@ -126,4 +127,9 @@ final public class MainActivity extends WinBoLLActivity {
Intent intent = new Intent(getApplicationContext(), NoteRecordHelperActivity.class);
startActivity(intent);
}
+
+ public void onTaskScheduler(View view) {
+ Intent intent = new Intent(getApplicationContext(), TaskSchedulerActivity.class);
+ startActivity(intent);
+ }
}
diff --git a/stallhelper/src/main/java/cc/winboll/studio/stallhelper/activities/TaskSchedulerActivity.java b/stallhelper/src/main/java/cc/winboll/studio/stallhelper/activities/TaskSchedulerActivity.java
new file mode 100644
index 0000000..dc7126a
--- /dev/null
+++ b/stallhelper/src/main/java/cc/winboll/studio/stallhelper/activities/TaskSchedulerActivity.java
@@ -0,0 +1,766 @@
+package cc.winboll.studio.stallhelper.activities;
+
+import android.app.Dialog;
+import android.os.Bundle;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.view.ContextMenu;
+import android.view.LayoutInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+import android.widget.PopupMenu;
+import android.widget.TextView;
+import androidx.appcompat.widget.Toolbar;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+import cc.winboll.studio.libaes.utils.WinBoLLActivityManager;
+import cc.winboll.studio.libappbase.LogUtils;
+import cc.winboll.studio.libappbase.ToastUtils;
+import cc.winboll.studio.stallhelper.R;
+import cc.winboll.studio.stallhelper.beans.TaskSchedulerBean;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+
+import net.sourceforge.pinyin4j.PinyinHelper;
+import net.sourceforge.pinyin4j.format.HanyuPinyinCaseType;
+import net.sourceforge.pinyin4j.format.HanyuPinyinOutputFormat;
+import net.sourceforge.pinyin4j.format.HanyuPinyinToneType;
+
+/**
+ * @Author ZhanGSKen
+ * @Date 2026/05/18
+ * @Describe 任务排档窗口
+ */
+public class TaskSchedulerActivity extends WinBoLLActivity {
+
+ public static final String TAG = "TaskSchedulerActivity";
+
+ // 时间转换常量
+ private static final int MINUTES_PER_HOUR = 60;
+ private static final int HOURS_PER_DAY = 24;
+ private static final int DAYS_PER_MONTH = 30; // 默认按30天计算
+
+ ArrayList mTaskList;
+ TaskSchedulerAdapter mAdapter;
+ EditText mSearchEditText;
+ String mSearchQuery = "";
+ HanyuPinyinOutputFormat mPinyinFormat;
+
+ @Override
+ public String getTag() {
+ return TAG;
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_task_scheduler);
+ LogUtils.i(TAG, "onCreate: 任务排档页面初始化");
+
+ Toolbar toolbar = findViewById(R.id.toolbar);
+ setSupportActionBar(toolbar);
+ setTitle("任务排档");
+
+ // 初始化任务列表
+ mTaskList = new ArrayList();
+ loadTaskData();
+
+ // 设置搜索框
+ mPinyinFormat = new HanyuPinyinOutputFormat();
+ mPinyinFormat.setCaseType(HanyuPinyinCaseType.LOWERCASE);
+ mPinyinFormat.setToneType(HanyuPinyinToneType.WITHOUT_TONE);
+
+ mSearchEditText = findViewById(R.id.et_search);
+ mSearchEditText.addTextChangedListener(new TextWatcher() {
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+ }
+
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+ }
+
+ @Override
+ public void afterTextChanged(Editable s) {
+ mSearchQuery = s.toString().trim().toLowerCase();
+ filterTaskList();
+ }
+ });
+
+ // 设置 RecyclerView
+ RecyclerView recyclerView = findViewById(R.id.task_scheduler_recycler_view);
+ recyclerView.setLayoutManager(new LinearLayoutManager(getApplicationContext(), LinearLayoutManager.VERTICAL, false));
+ mAdapter = new TaskSchedulerAdapter(mTaskList);
+ recyclerView.setAdapter(mAdapter);
+
+ // 设置悬浮按钮点击事件
+ Button fabAddTask = findViewById(R.id.fab_add_task);
+ fabAddTask.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ showAddTaskDialog();
+ }
+ });
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ if (item.getItemId() == android.R.id.home) {
+ WinBoLLActivityManager.getInstance().finish(this);
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ /**
+ * 从文件加载任务数据
+ */
+ private void loadTaskData() {
+ boolean loadSuccess = TaskSchedulerBean.loadBeanList(this, mTaskList, TaskSchedulerBean.class);
+ if (loadSuccess) {
+ LogUtils.i(TAG, "loadTaskData: 成功加载 " + mTaskList.size() + " 条任务数据");
+ } else {
+ LogUtils.i(TAG, "loadTaskData: 未找到已保存的任务数据,使用空列表");
+ }
+ sortTaskList();
+ }
+
+ private void applyChanges() {
+ sortTaskList();
+ filterTaskList();
+ saveTaskData();
+ }
+
+ /**
+ * 保存任务数据到文件
+ */
+ private void saveTaskData() {
+ boolean saveSuccess = TaskSchedulerBean.saveBeanList(this, mTaskList, TaskSchedulerBean.class);
+ if (saveSuccess) {
+ LogUtils.i(TAG, "saveTaskData: 成功保存 " + mTaskList.size() + " 条任务数据");
+ } else {
+ LogUtils.e(TAG, "saveTaskData: 任务数据保存失败");
+ }
+ }
+
+ /**
+ * 按排档时间倒序排列列表
+ */
+ private void sortTaskList() {
+ Collections.sort(mTaskList, new Comparator() {
+ @Override
+ public int compare(TaskSchedulerBean a, TaskSchedulerBean b) {
+ long diff = a.getCurrentScheduleTime() - b.getCurrentScheduleTime();
+ if (diff > 0) return 1;
+ if (diff < 0) return -1;
+ return 0;
+ }
+ });
+ }
+
+ /**
+ * 格式化时间戳为年月日和小时
+ * @param timestamp 时间戳
+ * @return 格式化的时间字符串,如 "2026-05-18 09"
+ */
+ private String formatTimestamp(long timestamp) {
+ if (timestamp == 0) {
+ return "";
+ }
+ java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyy-MM-dd HH");
+ java.util.Date date = new java.util.Date(timestamp);
+ return sdf.format(date);
+ }
+
+ /**
+ * 解析时间字符串为时间戳
+ * @param timeStr 时间字符串,如 "2026-05-18 09"
+ * @return 时间戳
+ */
+ private long parseTimeString(String timeStr) {
+ if (timeStr == null || timeStr.trim().isEmpty()) {
+ return 0;
+ }
+ try {
+ java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyy-MM-dd HH");
+ java.util.Date date = sdf.parse(timeStr);
+ return date.getTime();
+ } catch (Exception e) {
+ LogUtils.e(TAG, "parseTimeString: 时间解析错误", e);
+ return 0;
+ }
+ }
+
+ /**
+ * 初始化示例数据
+ */
+ private void initSampleData() {
+ java.util.Calendar calendar = java.util.Calendar.getInstance();
+ calendar.set(java.util.Calendar.MINUTE, 0);
+ calendar.set(java.util.Calendar.SECOND, 0);
+ calendar.set(java.util.Calendar.MILLISECOND, 0);
+
+ calendar.set(java.util.Calendar.HOUR_OF_DAY, 9);
+ long time9 = calendar.getTimeInMillis();
+
+ calendar.set(java.util.Calendar.HOUR_OF_DAY, 10);
+ long time10 = calendar.getTimeInMillis();
+
+ calendar.set(java.util.Calendar.HOUR_OF_DAY, 11);
+ calendar.set(java.util.Calendar.MINUTE, 30);
+ long time1130 = calendar.getTimeInMillis();
+
+ calendar.set(java.util.Calendar.HOUR_OF_DAY, 12);
+ calendar.set(java.util.Calendar.MINUTE, 30);
+ long time1230 = calendar.getTimeInMillis();
+
+ calendar.set(java.util.Calendar.HOUR_OF_DAY, 16);
+ calendar.set(java.util.Calendar.MINUTE, 0);
+ long time16 = calendar.getTimeInMillis();
+
+ calendar.set(java.util.Calendar.HOUR_OF_DAY, 17);
+ long time17 = calendar.getTimeInMillis();
+
+ mTaskList.add(new TaskSchedulerBean("准备食材", "购买新鲜蔬菜和肉类", time9, 60));
+ mTaskList.add(new TaskSchedulerBean("烹饪午餐", "按照食谱准备午餐", time10, 90));
+ mTaskList.add(new TaskSchedulerBean("用餐休息", "享受美食并休息", time1130, 60));
+ mTaskList.add(new TaskSchedulerBean("清洁厨房", "清洗餐具和整理厨房", time1230, 45));
+ mTaskList.add(new TaskSchedulerBean("准备晚餐", "提前准备晚餐食材", time16, 60));
+ mTaskList.add(new TaskSchedulerBean("烹饪晚餐", "制作丰盛的晚餐", time17, 90));
+ }
+
+ /**
+ * 将中文转换为拼音(不含声调,小写)
+ */
+ private String toPinyin(String chinese) {
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < chinese.length(); i++) {
+ char c = chinese.charAt(i);
+ try {
+ String[] pinyins = PinyinHelper.toHanyuPinyinStringArray(c, mPinyinFormat);
+ if (pinyins != null && pinyins.length > 0) {
+ sb.append(pinyins[0]);
+ }
+ } catch (Exception e) {
+ sb.append(c);
+ }
+ }
+ return sb.toString();
+ }
+
+ /**
+ * 将中文转换为拼音首字母串(小写)
+ */
+ private String toPinyinInitials(String chinese) {
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < chinese.length(); i++) {
+ char c = chinese.charAt(i);
+ try {
+ String[] pinyins = PinyinHelper.toHanyuPinyinStringArray(c, mPinyinFormat);
+ if (pinyins != null && pinyins.length > 0) {
+ sb.append(pinyins[0].charAt(0));
+ }
+ } catch (Exception e) {
+ sb.append(c);
+ }
+ }
+ return sb.toString();
+ }
+
+ /**
+ * 检查任务名称是否匹配搜索关键词(支持中文/拼音/首字母)
+ */
+ private boolean matchesSearch(TaskSchedulerBean task, String query) {
+ if (query == null || query.isEmpty()) return true;
+ String name = task.getTaskName();
+ if (name == null) return false;
+ if (name.toLowerCase().contains(query)) return true;
+ String pinyin = toPinyin(name);
+ if (pinyin.contains(query)) return true;
+ String initials = toPinyinInitials(name);
+ if (initials.contains(query)) return true;
+ return false;
+ }
+
+ /**
+ * 根据搜索关键词过滤任务列表并刷新界面
+ */
+ private void filterTaskList() {
+ if (mSearchQuery.isEmpty()) {
+ mAdapter.setData(mTaskList);
+ return;
+ }
+ ArrayList filteredList = new ArrayList();
+ for (TaskSchedulerBean task : mTaskList) {
+ if (matchesSearch(task, mSearchQuery)) {
+ filteredList.add(task);
+ }
+ }
+ mAdapter.setData(filteredList);
+ }
+
+ /**
+ * 将月、天、小时转换为总分钟数
+ * @param month 月份
+ * @param day 天数
+ * @param hour 小时
+ * @return 总分钟数
+ */
+ private int convertToTotalMinutes(int month, int day, int hour) {
+ return month * DAYS_PER_MONTH * HOURS_PER_DAY * MINUTES_PER_HOUR +
+ day * HOURS_PER_DAY * MINUTES_PER_HOUR +
+ hour * MINUTES_PER_HOUR;
+ }
+
+ /**
+ * 将总分钟数转换为月、天、小时的格式字符串
+ * @param totalMinutes 总分钟数
+ * @return 格式化的字符串,如 "0月0天1小时"
+ */
+ private String formatDurationToString(int totalMinutes) {
+ int totalHours = totalMinutes / MINUTES_PER_HOUR;
+ int totalDays = totalHours / HOURS_PER_DAY;
+ int month = totalDays / DAYS_PER_MONTH;
+ int day = totalDays % DAYS_PER_MONTH;
+ int hour = totalHours % HOURS_PER_DAY;
+
+ StringBuilder sb = new StringBuilder();
+ if (month > 0) {
+ sb.append(month).append("月");
+ }
+ if (day > 0) {
+ sb.append(day).append("天");
+ }
+ if (hour > 0) {
+ sb.append(hour).append("小时");
+ }
+ if (sb.length() == 0) {
+ sb.append("0小时");
+ }
+ return sb.toString();
+ }
+
+ /**
+ * 显示编辑任务对话框
+ * @param position 要编辑的任务位置
+ */
+ private void showEditTaskDialog(final int position) {
+ if (position < 0 || position >= mAdapter.mDataList.size()) {
+ return;
+ }
+
+ final TaskSchedulerBean task = mAdapter.mDataList.get(position);
+ final Dialog dialog = new Dialog(this);
+ dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
+ dialog.setContentView(R.layout.dialog_task_edit);
+
+ final EditText etTaskName = dialog.findViewById(R.id.dialog_et_task_name);
+ final EditText etCurrentScheduleTime = dialog.findViewById(R.id.dialog_et_start_time);
+ final EditText etMonth = dialog.findViewById(R.id.dialog_et_month);
+ final EditText etDay = dialog.findViewById(R.id.dialog_et_day);
+ final EditText etHour = dialog.findViewById(R.id.dialog_et_hour);
+ final EditText etRemark = dialog.findViewById(R.id.dialog_et_remark);
+ Button btnCancel = dialog.findViewById(R.id.dialog_btn_cancel);
+ Button btnSave = dialog.findViewById(R.id.dialog_btn_save);
+ Button btnNow = dialog.findViewById(R.id.dialog_btn_now);
+
+ etTaskName.setText(task.getTaskName());
+ etCurrentScheduleTime.setText(formatTimestamp(task.getCurrentScheduleTime()));
+ etRemark.setText(task.getRemark());
+
+ int totalMinutes = task.getDurationMinutes();
+ int totalHours = totalMinutes / MINUTES_PER_HOUR;
+ int totalDays = totalHours / HOURS_PER_DAY;
+ int month = totalDays / DAYS_PER_MONTH;
+ int day = totalDays % DAYS_PER_MONTH;
+ int hour = totalHours % HOURS_PER_DAY;
+
+ if (month > 0) {
+ etMonth.setText(Integer.toString(month));
+ }
+ if (day > 0) {
+ etDay.setText(Integer.toString(day));
+ }
+ if (hour > 0) {
+ etHour.setText(Integer.toString(hour));
+ }
+
+ btnNow.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ java.util.Calendar calendar = java.util.Calendar.getInstance();
+ calendar.set(java.util.Calendar.MINUTE, 0);
+ calendar.set(java.util.Calendar.SECOND, 0);
+ calendar.set(java.util.Calendar.MILLISECOND, 0);
+ etCurrentScheduleTime.setText(formatTimestamp(calendar.getTimeInMillis()));
+ }
+ });
+
+ btnCancel.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ dialog.dismiss();
+ }
+ });
+
+ btnSave.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ String taskName = etTaskName.getText().toString().trim();
+ String currentScheduleTime = etCurrentScheduleTime.getText().toString().trim();
+ String monthStr = etMonth.getText().toString().trim();
+ String dayStr = etDay.getText().toString().trim();
+ String hourStr = etHour.getText().toString().trim();
+ String remark = etRemark.getText().toString().trim();
+
+ if (taskName.isEmpty()) {
+ etTaskName.setError("请输入任务名称");
+ return;
+ }
+ if (currentScheduleTime.isEmpty()) {
+ etCurrentScheduleTime.setError("请输入当前排档时间");
+ return;
+ }
+
+ long timestamp = parseTimeString(currentScheduleTime);
+ if (timestamp == 0) {
+ etCurrentScheduleTime.setError("时间格式不正确,请使用 yyyy-MM-dd HH 格式");
+ return;
+ }
+
+ int month = 0, day = 0, hour = 0;
+ try {
+ if (!monthStr.isEmpty()) month = Integer.parseInt(monthStr);
+ if (!dayStr.isEmpty()) day = Integer.parseInt(dayStr);
+ if (!hourStr.isEmpty()) hour = Integer.parseInt(hourStr);
+ } catch (NumberFormatException e) {
+ LogUtils.e(TAG, "showEditTaskDialog: 数字解析错误", e);
+ return;
+ }
+
+ int durationMinutes = convertToTotalMinutes(month, day, hour);
+ task.setTaskName(taskName);
+ task.setCurrentScheduleTime(timestamp);
+ task.setDurationMinutes(durationMinutes);
+ task.setRemark(remark);
+
+ applyChanges();
+ LogUtils.i(TAG, "showEditTaskDialog: 任务 [" + taskName + "] 数据已更新,排档幅度=" + durationMinutes + "分钟");
+ dialog.dismiss();
+ }
+ });
+ dialog.show();
+ }
+
+ /**
+ * 显示添加任务对话框
+ */
+ private void showAddTaskDialog() {
+ final Dialog dialog = new Dialog(this);
+ dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
+ dialog.setContentView(R.layout.dialog_task_edit);
+
+ final EditText etTaskName = dialog.findViewById(R.id.dialog_et_task_name);
+ final EditText etCurrentScheduleTime = dialog.findViewById(R.id.dialog_et_start_time);
+ final EditText etMonth = dialog.findViewById(R.id.dialog_et_month);
+ final EditText etDay = dialog.findViewById(R.id.dialog_et_day);
+ final EditText etHour = dialog.findViewById(R.id.dialog_et_hour);
+ final EditText etRemark = dialog.findViewById(R.id.dialog_et_remark);
+ Button btnCancel = dialog.findViewById(R.id.dialog_btn_cancel);
+ Button btnSave = dialog.findViewById(R.id.dialog_btn_save);
+ Button btnNow = dialog.findViewById(R.id.dialog_btn_now);
+
+ // 设置默认时间为当前时间
+ java.util.Calendar calendar = java.util.Calendar.getInstance();
+ calendar.set(java.util.Calendar.MINUTE, 0);
+ calendar.set(java.util.Calendar.SECOND, 0);
+ calendar.set(java.util.Calendar.MILLISECOND, 0);
+ etCurrentScheduleTime.setText(formatTimestamp(calendar.getTimeInMillis()));
+
+ btnNow.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ java.util.Calendar cal = java.util.Calendar.getInstance();
+ cal.set(java.util.Calendar.MINUTE, 0);
+ cal.set(java.util.Calendar.SECOND, 0);
+ cal.set(java.util.Calendar.MILLISECOND, 0);
+ etCurrentScheduleTime.setText(formatTimestamp(cal.getTimeInMillis()));
+ }
+ });
+
+ btnCancel.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ dialog.dismiss();
+ }
+ });
+
+ btnSave.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ String taskName = etTaskName.getText().toString().trim();
+ String currentScheduleTime = etCurrentScheduleTime.getText().toString().trim();
+ String monthStr = etMonth.getText().toString().trim();
+ String dayStr = etDay.getText().toString().trim();
+ String hourStr = etHour.getText().toString().trim();
+ String remark = etRemark.getText().toString().trim();
+
+ if (taskName.isEmpty()) {
+ etTaskName.setError("请输入任务名称");
+ return;
+ }
+ if (currentScheduleTime.isEmpty()) {
+ etCurrentScheduleTime.setError("请输入当前排档时间");
+ return;
+ }
+
+ long timestamp = parseTimeString(currentScheduleTime);
+ if (timestamp == 0) {
+ etCurrentScheduleTime.setError("时间格式不正确,请使用 yyyy-MM-dd HH 格式");
+ return;
+ }
+
+ int month = 0, day = 0, hour = 0;
+ try {
+ if (!monthStr.isEmpty()) month = Integer.parseInt(monthStr);
+ if (!dayStr.isEmpty()) day = Integer.parseInt(dayStr);
+ if (!hourStr.isEmpty()) hour = Integer.parseInt(hourStr);
+ } catch (NumberFormatException e) {
+ LogUtils.e(TAG, "showAddTaskDialog: 数字解析错误", e);
+ return;
+ }
+
+ int durationMinutes = convertToTotalMinutes(month, day, hour);
+ TaskSchedulerBean newTask = new TaskSchedulerBean(taskName, remark, timestamp, durationMinutes);
+ mTaskList.add(newTask);
+ applyChanges();
+ LogUtils.i(TAG, "showAddTaskDialog: 任务 [" + taskName + "] 已添加,排档幅度=" + durationMinutes + "分钟");
+ dialog.dismiss();
+ }
+ });
+ dialog.show();
+ }
+
+ /**
+ * 显示删除确认对话框
+ * @param position 要删除的任务位置
+ */
+ private void showDeleteConfirmDialog(final int position) {
+ if (position < 0 || position >= mAdapter.mDataList.size()) {
+ return;
+ }
+
+ final TaskSchedulerBean task = mAdapter.mDataList.get(position);
+ final Dialog dialog = new Dialog(this);
+ dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
+ dialog.setContentView(R.layout.dialog_delete_confirm);
+
+ TextView tvMessage = dialog.findViewById(R.id.dialog_delete_message);
+ Button btnCancel = dialog.findViewById(R.id.dialog_btn_cancel);
+ Button btnConfirm = dialog.findViewById(R.id.dialog_btn_confirm);
+
+ tvMessage.setText("确定要删除任务 \"" + task.getTaskName() + "\" 吗?");
+
+ btnCancel.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ dialog.dismiss();
+ }
+ });
+
+ btnConfirm.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ String taskName = task.getTaskName();
+ mTaskList.remove(task);
+ applyChanges();
+ LogUtils.i(TAG, "showDeleteConfirmDialog: 任务 [" + taskName + "] 已删除");
+ dialog.dismiss();
+ }
+ });
+ dialog.show();
+ }
+
+ /**
+ * 显示排档确认对话框
+ * @param position 要排档的任务位置
+ */
+ private void showScheduleConfirmDialog(final int position) {
+ if (position < 0 || position >= mAdapter.mDataList.size()) {
+ return;
+ }
+
+ final TaskSchedulerBean task = mAdapter.mDataList.get(position);
+ long currentTime = task.getCurrentScheduleTime();
+ int duration = task.getDurationMinutes();
+ long newTime = currentTime + duration * 60 * 1000L;
+
+ final Dialog dialog = new Dialog(this);
+ dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
+ dialog.setContentView(R.layout.dialog_schedule_confirm);
+
+ TextView tvTaskName = dialog.findViewById(R.id.dialog_schedule_task_name);
+ TextView tvCurrentTime = dialog.findViewById(R.id.dialog_schedule_current_time);
+ TextView tvDuration = dialog.findViewById(R.id.dialog_schedule_duration);
+ TextView tvRemark = dialog.findViewById(R.id.dialog_schedule_remark);
+ TextView tvNewTime = dialog.findViewById(R.id.dialog_schedule_new_time);
+ Button btnCancel = dialog.findViewById(R.id.dialog_btn_cancel);
+ Button btnConfirm = dialog.findViewById(R.id.dialog_btn_confirm);
+
+ tvTaskName.setText("任务:" + task.getTaskName());
+ tvCurrentTime.setText("当前排档时间:" + formatTimestamp(currentTime));
+ tvDuration.setText("排档幅度:" + formatDurationToString(duration));
+ tvRemark.setText("备注:" + (task.getRemark() != null && !task.getRemark().isEmpty() ? task.getRemark() : "无"));
+ tvNewTime.setText("排档后时间:" + formatTimestamp(newTime));
+
+ btnCancel.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ dialog.dismiss();
+ }
+ });
+
+ btnConfirm.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ dialog.dismiss();
+ performSchedule(position);
+ }
+ });
+
+ dialog.show();
+ }
+
+ /**
+ * 执行排档操作
+ * @param position 点击排档按钮的任务位置
+ */
+ private void performSchedule(int position) {
+ if (position < 0 || position >= mAdapter.mDataList.size()) {
+ return;
+ }
+ TaskSchedulerBean currentTask = mAdapter.mDataList.get(position);
+ long currentTime = currentTask.getCurrentScheduleTime();
+ int duration = currentTask.getDurationMinutes();
+ long newTime = currentTime + duration * 60 * 1000L;
+ LogUtils.i(TAG, "performSchedule: 当前时间 " + formatTimestamp(currentTime) + " + " + duration + "分钟 = " + formatTimestamp(newTime));
+ currentTask.setCurrentScheduleTime(newTime);
+ applyChanges();
+ LogUtils.i(TAG, "performSchedule: 更新任务 [" + currentTask.getTaskName() + "] 时间更新为 " + formatTimestamp(newTime));
+ }
+
+ /**
+ * RecyclerView 适配器
+ */
+ class TaskSchedulerAdapter extends RecyclerView.Adapter {
+
+ ArrayList mDataList;
+
+ public TaskSchedulerAdapter(ArrayList dataList) {
+ mDataList = dataList;
+ }
+
+ public void setData(ArrayList dataList) {
+ mDataList = dataList;
+ notifyDataSetChanged();
+ }
+
+ @Override
+ public int getItemCount() {
+ return mDataList.size();
+ }
+
+ @Override
+ public void onBindViewHolder(RecyclerView.ViewHolder holder, final int position) {
+ final TaskSchedulerBean item = mDataList.get(position);
+ final TaskViewHolder viewHolder = (TaskViewHolder) holder;
+
+ viewHolder.mtvTaskName.setText(item.getTaskName());
+ viewHolder.mtvCurrentScheduleTime.setText(formatTimestamp(item.getCurrentScheduleTime()));
+ viewHolder.mtvDuration.setText(formatDurationToString(item.getDurationMinutes()));
+ viewHolder.mtvRemark.setText(item.getRemark());
+
+ long diffMs = item.getCurrentScheduleTime() - System.currentTimeMillis();
+ int diffMinutes = (int) Math.abs(diffMs / 60000);
+ if (diffMs >= 0) {
+ viewHolder.mtvTimeDiff.setText("剩余" + formatDurationToString(diffMinutes));
+ } else {
+ viewHolder.mtvTimeDiff.setText("已过" + formatDurationToString(diffMinutes));
+ }
+
+ if (item.getCurrentScheduleTime() <= System.currentTimeMillis()) {
+ viewHolder.mllMain.setBackgroundResource(R.color.colorScheduleFutureBackground);
+ } else {
+ viewHolder.mllMain.setBackgroundResource(R.drawable.bg_frame);
+ }
+
+ // 长按直接内置菜单,直接使用当前position
+ viewHolder.mllMain.setOnLongClickListener(new View.OnLongClickListener() {
+ @Override
+ public boolean onLongClick(final View v) {
+ // 使用PopupMenu替代ContextMenu
+ PopupMenu popup = new PopupMenu(TaskSchedulerActivity.this, v);
+ popup.getMenu().add("编辑任务内容");
+ popup.getMenu().add("删除任务");
+ popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
+ @Override
+ public boolean onMenuItemClick(MenuItem item) {
+ String title = item.getTitle().toString();
+ if ("编辑任务内容".equals(title)) {
+ showEditTaskDialog(position);
+ return true;
+ } else if ("删除任务".equals(title)) {
+ showDeleteConfirmDialog(position);
+ return true;
+ }
+ return false;
+ }
+ });
+ // 弹出菜单
+ popup.show();
+ return true;
+ }
+ });
+
+ viewHolder.mbtnSchedule.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ showScheduleConfirmDialog(position);
+ }
+ });
+ }
+
+ @Override
+ public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_task_scheduler, parent, false);
+ return new TaskViewHolder(view);
+ }
+
+ class TaskViewHolder extends RecyclerView.ViewHolder {
+ TextView mtvTaskName;
+ TextView mtvCurrentScheduleTime;
+ TextView mtvTimeDiff;
+ TextView mtvDuration;
+ TextView mtvRemark;
+ LinearLayout mllMain;
+ Button mbtnSchedule;
+
+ TaskViewHolder(View itemView) {
+ super(itemView);
+ mtvTaskName = itemView.findViewById(R.id.item_task_name);
+ mtvCurrentScheduleTime = itemView.findViewById(R.id.item_start_time);
+ mtvTimeDiff = itemView.findViewById(R.id.item_time_diff);
+ mtvDuration = itemView.findViewById(R.id.item_duration);
+ mtvRemark = itemView.findViewById(R.id.item_remark);
+ mllMain = itemView.findViewById(R.id.itemtaskscheduler_llmain);
+ mbtnSchedule = itemView.findViewById(R.id.item_btn_schedule);
+ }
+ }
+ }
+}
+
diff --git a/stallhelper/src/main/java/cc/winboll/studio/stallhelper/beans/TaskSchedulerBean.java b/stallhelper/src/main/java/cc/winboll/studio/stallhelper/beans/TaskSchedulerBean.java
new file mode 100644
index 0000000..61245f0
--- /dev/null
+++ b/stallhelper/src/main/java/cc/winboll/studio/stallhelper/beans/TaskSchedulerBean.java
@@ -0,0 +1,117 @@
+package cc.winboll.studio.stallhelper.beans;
+
+/**
+ * @Author ZhanGSKen
+ * @Date 2026/05/18
+ * @Describe 任务排档数据模型
+ */
+import android.util.JsonReader;
+import android.util.JsonWriter;
+import cc.winboll.studio.libappbase.BaseBean;
+import java.io.IOException;
+
+public class TaskSchedulerBean extends BaseBean {
+
+ public static final String TAG = "TaskSchedulerBean";
+
+ // 任务排档名称
+ String taskName;
+ // 备注
+ String remark;
+ // 当前排档时间(时间戳)
+ long currentScheduleTime;
+ // 排档时间幅度(分钟)- 用于计算下一个任务的起始时间
+ int durationMinutes;
+
+ public TaskSchedulerBean() {
+ this.taskName = "";
+ this.remark = "";
+ this.currentScheduleTime = 0;
+ this.durationMinutes = 0;
+ }
+
+ public TaskSchedulerBean(String taskName, String remark, long currentScheduleTime, int durationMinutes) {
+ this.taskName = taskName;
+ this.remark = remark;
+ this.currentScheduleTime = currentScheduleTime;
+ this.durationMinutes = durationMinutes;
+ }
+
+ public String getTaskName() {
+ return taskName;
+ }
+
+ public void setTaskName(String taskName) {
+ this.taskName = taskName;
+ }
+
+ public String getRemark() {
+ return remark;
+ }
+
+ public void setRemark(String remark) {
+ this.remark = remark;
+ }
+
+ public long getCurrentScheduleTime() {
+ return currentScheduleTime;
+ }
+
+ public void setCurrentScheduleTime(long currentScheduleTime) {
+ this.currentScheduleTime = currentScheduleTime;
+ }
+
+ public int getDurationMinutes() {
+ return durationMinutes;
+ }
+
+ public void setDurationMinutes(int durationMinutes) {
+ this.durationMinutes = durationMinutes;
+ }
+
+ @Override
+ public String getName() {
+ return TaskSchedulerBean.class.getName();
+ }
+
+ @Override
+ public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException {
+ super.writeThisToJsonWriter(jsonWriter);
+ TaskSchedulerBean bean = this;
+ jsonWriter.name("taskName").value(bean.getTaskName());
+ jsonWriter.name("remark").value(bean.getRemark());
+ jsonWriter.name("startTime").value(bean.getCurrentScheduleTime());
+ jsonWriter.name("durationMinutes").value(bean.getDurationMinutes());
+ }
+
+ @Override
+ public boolean initObjectsFromJsonReader(JsonReader jsonReader, String name) throws IOException {
+ if (super.initObjectsFromJsonReader(jsonReader, name)) { return true; } else {
+ if (name.equals("taskName")) {
+ setTaskName(jsonReader.nextString());
+ } else if (name.equals("remark")) {
+ setRemark(jsonReader.nextString());
+ } else if (name.equals("startTime")) {
+ setCurrentScheduleTime(jsonReader.nextLong());
+ } else if (name.equals("durationMinutes")) {
+ setDurationMinutes(jsonReader.nextInt());
+ } else {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public TaskSchedulerBean readBeanFromJsonReader(JsonReader jsonReader) throws IOException {
+ jsonReader.beginObject();
+ while (jsonReader.hasNext()) {
+ String name = jsonReader.nextName();
+ if (!initObjectsFromJsonReader(jsonReader, name)) {
+ jsonReader.skipValue();
+ }
+ }
+ jsonReader.endObject();
+ return this;
+ }
+}
diff --git a/stallhelper/src/main/res/drawable/ic_task_scheduler.xml b/stallhelper/src/main/res/drawable/ic_task_scheduler.xml
new file mode 100644
index 0000000..115a089
--- /dev/null
+++ b/stallhelper/src/main/res/drawable/ic_task_scheduler.xml
@@ -0,0 +1,62 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/stallhelper/src/main/res/layout/activity_main.xml b/stallhelper/src/main/res/layout/activity_main.xml
index 8a91f05..cc17a29 100644
--- a/stallhelper/src/main/res/layout/activity_main.xml
+++ b/stallhelper/src/main/res/layout/activity_main.xml
@@ -67,30 +67,53 @@
-
+
-
+
-
+
-
-
-
+
-
+
+
+
+
+
+
+
+
+
+
+
diff --git a/stallhelper/src/main/res/layout/activity_task_scheduler.xml b/stallhelper/src/main/res/layout/activity_task_scheduler.xml
new file mode 100644
index 0000000..dc5af32
--- /dev/null
+++ b/stallhelper/src/main/res/layout/activity_task_scheduler.xml
@@ -0,0 +1,50 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/stallhelper/src/main/res/layout/dialog_delete_confirm.xml b/stallhelper/src/main/res/layout/dialog_delete_confirm.xml
new file mode 100644
index 0000000..6ea3565
--- /dev/null
+++ b/stallhelper/src/main/res/layout/dialog_delete_confirm.xml
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/stallhelper/src/main/res/layout/dialog_schedule_confirm.xml b/stallhelper/src/main/res/layout/dialog_schedule_confirm.xml
new file mode 100644
index 0000000..3c89156
--- /dev/null
+++ b/stallhelper/src/main/res/layout/dialog_schedule_confirm.xml
@@ -0,0 +1,78 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/stallhelper/src/main/res/layout/dialog_task_edit.xml b/stallhelper/src/main/res/layout/dialog_task_edit.xml
new file mode 100644
index 0000000..1583b8b
--- /dev/null
+++ b/stallhelper/src/main/res/layout/dialog_task_edit.xml
@@ -0,0 +1,174 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/stallhelper/src/main/res/layout/item_task_scheduler.xml b/stallhelper/src/main/res/layout/item_task_scheduler.xml
new file mode 100644
index 0000000..000064e
--- /dev/null
+++ b/stallhelper/src/main/res/layout/item_task_scheduler.xml
@@ -0,0 +1,132 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/stallhelper/src/main/res/values/colors.xml b/stallhelper/src/main/res/values/colors.xml
index efb88de..553ed3e 100644
--- a/stallhelper/src/main/res/values/colors.xml
+++ b/stallhelper/src/main/res/values/colors.xml
@@ -20,4 +20,6 @@
#FFFFFF
#000000
+
+ #FFC8E6C9