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 @@ + + + + + + + + + + + + + +