calendar-widget/android-app-requirements.md
Ching L c790cee472
All checks were successful
continuous-integration/drone/push Build is passing
feat: implement android app with widget support
- 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
2025-11-17 18:28:22 +08:00

11 KiB
Raw Permalink Blame History

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设计要点

// 主界面布局结构
@Composable
fun ControlScreen() {
    Scaffold(
        topBar = { /* 应用标题栏 */ },
        floatingActionButton = { /* 添加任务按钮 */ }
    ) {
        LazyColumn {
            items(tasks) { task ->
                TaskCard(
                    task = task,
                    onExecute = { /* 执行任务 */ },
                    onEdit = { /* 编辑任务 */ },
                    onDelete = { /* 删除任务 */ }
                )
            }
        }
    }
}

2. 展示页面 (DisplayActivity)

功能需求

  • 任务状态可视化

    • 网格布局展示任务卡片
    • 动态适配屏幕大小横屏4列竖屏2列
    • 每个卡片显示任务名称和颜色条
    • 点击卡片快速执行任务
  • 视图模式

    • 紧凑模式:仅显示名称和颜色
    • 详细模式:显示名称、颜色、剩余天数

UI设计要点

@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分钟更新一次
    • 支持手动刷新按钮

实现要点

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 样式)

<!-- 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 接口层

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)
}

本地缓存

@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 实现

@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 配置

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 设计规范

颜色主题

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 提示信息

权限需求

<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)

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 规则

-keep class com.tunpok.calendarwidget.data.model.** { *; }
-keepattributes Signature
-keepattributes *Annotation*

版本管理

  • 使用语义化版本号
  • 维护 CHANGELOG.md
  • 配置自动化构建和发布流程

后续优化建议

  1. 性能优化

    • 实现图片和数据懒加载
    • 使用 WorkManager 进行后台同步
    • 实现智能更新策略活动时5分钟更新空闲时降低频率以节省电量
  2. 功能扩展

    • 添加任务提醒通知
    • 支持任务分类和标签
    • 实现任务统计图表
    • 添加批量操作功能
  3. 用户体验

    • 实现手势操作
    • 添加动画效果
    • 支持快捷方式
    • 深色模式自动切换
  4. 国际化

    • 支持多语言
    • 适配不同地区日期格式
    • 时区自动转换