All checks were successful
continuous-integration/drone/push Build is passing
- Created comprehensive Android app requirements document - Built complete Android project with Kotlin and Jetpack Compose - Implemented task management with control and display screens - Added Android widget with 5-minute auto-refresh capability - Integrated Room database for offline support - Set up MVVM architecture with Hilt dependency injection - Configured Retrofit for API communication - Added Material Design 3 theming and UI components
396 lines
11 KiB
Markdown
396 lines
11 KiB
Markdown
# Calendar Widget Android 应用需求文档
|
||
|
||
## 项目概述
|
||
将现有的 Calendar Widget 任务管理系统迁移到 Android 平台,保持核心功能的同时提供原生移动应用体验。
|
||
|
||
## 技术栈
|
||
- **开发语言**: Kotlin
|
||
- **UI框架**: Jetpack Compose
|
||
- **架构模式**: MVVM (Model-View-ViewModel)
|
||
- **网络请求**: Retrofit + OkHttp
|
||
- **本地存储**: Room Database
|
||
- **依赖注入**: Hilt
|
||
- **小组件**: Android App Widget API
|
||
|
||
## 应用结构
|
||
|
||
### 1. 控制页面 (MainActivity)
|
||
|
||
#### 功能需求
|
||
- **任务列表展示**
|
||
- 显示所有任务的列表视图
|
||
- 每个任务项显示:名称、状态颜色、最小间隔天数、上次执行时间
|
||
- 支持下拉刷新更新任务列表
|
||
|
||
- **任务管理**
|
||
- 添加新任务:浮动操作按钮触发对话框
|
||
- 编辑任务:长按任务项进入编辑模式
|
||
- 删除任务:侧滑删除或长按菜单删除
|
||
- 拖拽排序:长按拖动重新排列任务优先级
|
||
|
||
- **任务执行**
|
||
- 点击"执行"按钮记录任务执行时间
|
||
- 成功执行后自动刷新状态颜色
|
||
|
||
#### UI设计要点
|
||
```kotlin
|
||
// 主界面布局结构
|
||
@Composable
|
||
fun ControlScreen() {
|
||
Scaffold(
|
||
topBar = { /* 应用标题栏 */ },
|
||
floatingActionButton = { /* 添加任务按钮 */ }
|
||
) {
|
||
LazyColumn {
|
||
items(tasks) { task ->
|
||
TaskCard(
|
||
task = task,
|
||
onExecute = { /* 执行任务 */ },
|
||
onEdit = { /* 编辑任务 */ },
|
||
onDelete = { /* 删除任务 */ }
|
||
)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### 2. 展示页面 (DisplayActivity)
|
||
|
||
#### 功能需求
|
||
- **任务状态可视化**
|
||
- 网格布局展示任务卡片
|
||
- 动态适配屏幕大小(横屏4列,竖屏2列)
|
||
- 每个卡片显示任务名称和颜色条
|
||
- 点击卡片快速执行任务
|
||
|
||
- **视图模式**
|
||
- 紧凑模式:仅显示名称和颜色
|
||
- 详细模式:显示名称、颜色、剩余天数
|
||
|
||
#### UI设计要点
|
||
```kotlin
|
||
@Composable
|
||
fun DisplayScreen() {
|
||
val configuration = LocalConfiguration.current
|
||
val columns = if (configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) 4 else 2
|
||
|
||
LazyVerticalGrid(
|
||
columns = GridCells.Fixed(columns),
|
||
contentPadding = PaddingValues(16.dp)
|
||
) {
|
||
items(tasks) { task ->
|
||
TaskDisplayCard(
|
||
task = task,
|
||
onClick = { /* 执行任务 */ }
|
||
)
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### 3. Android 小组件 (Widget)
|
||
|
||
#### 功能需求
|
||
- **尺寸支持**
|
||
- 小型 (2x1): 显示1个任务
|
||
- 中型 (2x2): 显示4个任务
|
||
- 大型 (4x2): 显示8个任务
|
||
|
||
- **交互功能**
|
||
- 点击任务执行并刷新小组件
|
||
- 自动每5分钟更新一次
|
||
- 支持手动刷新按钮
|
||
|
||
#### 实现要点
|
||
```kotlin
|
||
class TaskWidgetProvider : AppWidgetProvider() {
|
||
override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) {
|
||
for (appWidgetId in appWidgetIds) {
|
||
val size = getWidgetSize(appWidgetManager, appWidgetId)
|
||
val views = createRemoteViews(context, size)
|
||
appWidgetManager.updateAppWidget(appWidgetId, views)
|
||
}
|
||
// 设置下次更新时间为5分钟后
|
||
scheduleNextUpdate(context)
|
||
}
|
||
|
||
private fun scheduleNextUpdate(context: Context) {
|
||
val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
|
||
val intent = Intent(context, TaskWidgetProvider::class.java).apply {
|
||
action = AppWidgetManager.ACTION_APPWIDGET_UPDATE
|
||
}
|
||
val pendingIntent = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
|
||
|
||
// 5分钟后更新
|
||
val updateInterval = 5 * 60 * 1000L // 5分钟转换为毫秒
|
||
alarmManager.setExactAndAllowWhileIdle(
|
||
AlarmManager.RTC,
|
||
System.currentTimeMillis() + updateInterval,
|
||
pendingIntent
|
||
)
|
||
}
|
||
}
|
||
```
|
||
|
||
#### 小组件布局 (仿照 widget.js 样式)
|
||
```xml
|
||
<!-- widget_layout.xml -->
|
||
<LinearLayout android:background="#f5f5f9"
|
||
android:padding="10dp">
|
||
<GridLayout android:columnCount="@integer/widget_columns"
|
||
android:rowCount="@integer/widget_rows">
|
||
<!-- 任务卡片 -->
|
||
<LinearLayout android:background="#f2f2f2"
|
||
android:padding="10dp"
|
||
android:gravity="center">
|
||
<TextView android:id="@+id/task_name"
|
||
android:textSize="14sp"
|
||
android:textColor="@android:color/black"/>
|
||
<View android:id="@+id/color_bar"
|
||
android:layout_height="10dp"
|
||
android:layout_marginTop="8dp"/>
|
||
</LinearLayout>
|
||
</GridLayout>
|
||
</LinearLayout>
|
||
```
|
||
|
||
## 数据管理
|
||
|
||
### API 接口层
|
||
```kotlin
|
||
interface TaskApiService {
|
||
@GET("tasks")
|
||
suspend fun getTasks(@Header("X-Api-Key") apiKey: String): List<Task>
|
||
|
||
@POST("tasks")
|
||
suspend fun createTask(@Header("X-Api-Key") apiKey: String, @Body task: CreateTaskRequest): Task
|
||
|
||
@PUT("tasks/{id}")
|
||
suspend fun updateTask(@Header("X-Api-Key") apiKey: String, @Path("id") id: Int, @Body task: UpdateTaskRequest): Task
|
||
|
||
@DELETE("tasks/{id}")
|
||
suspend fun deleteTask(@Header("X-Api-Key") apiKey: String, @Path("id") id: Int)
|
||
|
||
@POST("tasks/{id}/schedule")
|
||
suspend fun scheduleTask(@Header("X-Api-Key") apiKey: String, @Path("id") id: Int)
|
||
|
||
@POST("tasks/reorder")
|
||
suspend fun reorderTasks(@Header("X-Api-Key") apiKey: String, @Body request: ReorderRequest)
|
||
}
|
||
```
|
||
|
||
### 本地缓存
|
||
```kotlin
|
||
@Entity(tableName = "tasks")
|
||
data class TaskEntity(
|
||
@PrimaryKey val id: Int,
|
||
val name: String,
|
||
val color: String,
|
||
val minIntervalDays: Int,
|
||
val lastExecutionTime: String?,
|
||
val priority: Int
|
||
)
|
||
|
||
@Dao
|
||
interface TaskDao {
|
||
@Query("SELECT * FROM tasks ORDER BY priority")
|
||
fun getAllTasks(): Flow<List<TaskEntity>>
|
||
|
||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||
suspend fun insertTasks(tasks: List<TaskEntity>)
|
||
|
||
@Query("DELETE FROM tasks")
|
||
suspend fun deleteAllTasks()
|
||
}
|
||
```
|
||
|
||
## 状态管理
|
||
|
||
### ViewModel 实现
|
||
```kotlin
|
||
@HiltViewModel
|
||
class TaskViewModel @Inject constructor(
|
||
private val repository: TaskRepository
|
||
) : ViewModel() {
|
||
private val _uiState = MutableStateFlow(TaskUiState())
|
||
val uiState: StateFlow<TaskUiState> = _uiState.asStateFlow()
|
||
|
||
fun loadTasks() {
|
||
viewModelScope.launch {
|
||
repository.getTasks()
|
||
.catch { /* 处理错误,使用缓存 */ }
|
||
.collect { tasks ->
|
||
_uiState.update { it.copy(tasks = tasks) }
|
||
}
|
||
}
|
||
}
|
||
|
||
fun executeTask(taskId: Int) {
|
||
viewModelScope.launch {
|
||
repository.scheduleTask(taskId)
|
||
loadTasks() // 刷新列表
|
||
updateWidget() // 更新小组件
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
## 应用设置
|
||
|
||
### SharedPreferences 配置
|
||
```kotlin
|
||
object AppSettings {
|
||
private const val PREF_NAME = "calendar_widget_prefs"
|
||
private const val KEY_API_KEY = "api_key"
|
||
private const val KEY_API_HOST = "api_host"
|
||
private const val KEY_AUTO_REFRESH = "auto_refresh"
|
||
private const val KEY_REFRESH_INTERVAL = "refresh_interval"
|
||
|
||
fun saveApiKey(context: Context, apiKey: String) {
|
||
context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
|
||
.edit()
|
||
.putString(KEY_API_KEY, apiKey)
|
||
.apply()
|
||
}
|
||
}
|
||
```
|
||
|
||
### 设置页面功能
|
||
- API密钥配置
|
||
- 服务器地址配置
|
||
- 小组件自动刷新间隔(默认5分钟,可选1/5/10/15/30/60分钟)
|
||
- 主题选择(浅色/深色)
|
||
- 通知设置
|
||
|
||
## UI/UX 设计规范
|
||
|
||
### 颜色主题
|
||
```kotlin
|
||
val CalendarWidgetTheme = lightColorScheme(
|
||
primary = Color(0xFF667EEA),
|
||
secondary = Color(0xFF764BA2),
|
||
background = Color(0xFFF5F5F9),
|
||
surface = Color(0xFFF2F2F2),
|
||
error = Color(0xFFFF4757),
|
||
onPrimary = Color.White,
|
||
onSecondary = Color.White,
|
||
onBackground = Color.Black,
|
||
onSurface = Color.Black
|
||
)
|
||
|
||
// 任务状态颜色
|
||
object TaskColors {
|
||
val Green = Color(0xFF00B894)
|
||
val Yellow = Color(0xFFFDCB6E)
|
||
val Red = Color(0xFFFF4757)
|
||
}
|
||
```
|
||
|
||
### Material Design 3 组件
|
||
- 使用 Material You 动态主题
|
||
- 圆角卡片设计(16dp corner radius)
|
||
- 浮动操作按钮
|
||
- 底部导航栏
|
||
- Snackbar 提示信息
|
||
|
||
## 权限需求
|
||
```xml
|
||
<uses-permission android:name="android.permission.INTERNET" />
|
||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||
<uses-permission android:name="android.permission.VIBRATE" />
|
||
```
|
||
|
||
## 构建配置
|
||
|
||
### build.gradle.kts (Module)
|
||
```kotlin
|
||
android {
|
||
compileSdk = 34
|
||
|
||
defaultConfig {
|
||
applicationId = "com.tunpok.calendarwidget"
|
||
minSdk = 24
|
||
targetSdk = 34
|
||
versionCode = 1
|
||
versionName = "1.0.0"
|
||
}
|
||
|
||
buildFeatures {
|
||
compose = true
|
||
}
|
||
}
|
||
|
||
dependencies {
|
||
// Compose
|
||
implementation("androidx.compose.ui:ui:1.5.4")
|
||
implementation("androidx.compose.material3:material3:1.1.2")
|
||
|
||
// Navigation
|
||
implementation("androidx.navigation:navigation-compose:2.7.5")
|
||
|
||
// Network
|
||
implementation("com.squareup.retrofit2:retrofit:2.9.0")
|
||
implementation("com.squareup.okhttp3:okhttp:4.12.0")
|
||
|
||
// Database
|
||
implementation("androidx.room:room-runtime:2.6.0")
|
||
implementation("androidx.room:room-ktx:2.6.0")
|
||
|
||
// Dependency Injection
|
||
implementation("com.google.dagger:hilt-android:2.48")
|
||
|
||
// Widget
|
||
implementation("androidx.glance:glance-appwidget:1.0.0")
|
||
}
|
||
```
|
||
|
||
## 测试策略
|
||
|
||
### 单元测试
|
||
- Repository 层测试
|
||
- ViewModel 逻辑测试
|
||
- 日期计算和颜色映射测试
|
||
|
||
### UI 测试
|
||
- Compose UI 测试
|
||
- 小组件更新测试
|
||
- 网络请求模拟测试
|
||
|
||
## 发布准备
|
||
|
||
### ProGuard 规则
|
||
```proguard
|
||
-keep class com.tunpok.calendarwidget.data.model.** { *; }
|
||
-keepattributes Signature
|
||
-keepattributes *Annotation*
|
||
```
|
||
|
||
### 版本管理
|
||
- 使用语义化版本号
|
||
- 维护 CHANGELOG.md
|
||
- 配置自动化构建和发布流程
|
||
|
||
## 后续优化建议
|
||
|
||
1. **性能优化**
|
||
- 实现图片和数据懒加载
|
||
- 使用 WorkManager 进行后台同步
|
||
- 实现智能更新策略(活动时5分钟更新,空闲时降低频率以节省电量)
|
||
|
||
2. **功能扩展**
|
||
- 添加任务提醒通知
|
||
- 支持任务分类和标签
|
||
- 实现任务统计图表
|
||
- 添加批量操作功能
|
||
|
||
3. **用户体验**
|
||
- 实现手势操作
|
||
- 添加动画效果
|
||
- 支持快捷方式
|
||
- 深色模式自动切换
|
||
|
||
4. **国际化**
|
||
- 支持多语言
|
||
- 适配不同地区日期格式
|
||
- 时区自动转换 |