1
0

6 Коммиты 22b310a937 ... a1a0f46f0f

Автор SHA1 Сообщение Дата
  ValiZhang a1a0f46f0f 本commit主要提交下界面相关变更,提交了一些界面相关的 demo 代码 9 месяцев назад
  ValiZhang 5e8b078191 空null问题解决 11 месяцев назад
  ValiZhang 5bac445180 初步调通 11 месяцев назад
  ValiZhang bd265d2450 仍存在问题的一波提交 11 месяцев назад
  ValiZhang d10eb77137 调整下应用icon和名称 11 месяцев назад
  ValiZhang 736ddfa257 跑个 demo 11 месяцев назад
56 измененных файлов с 1794 добавлено и 144 удалено
  1. 2 1
      android/app/build.gradle.kts
  2. 1 1
      android/app/src/main/AndroidManifest.xml
  3. BIN
      android/app/src/main/res/mipmap-hdpi/ic_launcher.png
  4. BIN
      android/app/src/main/res/mipmap-hdpi/ic_launcher.webp
  5. BIN
      android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp
  6. BIN
      android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
  7. BIN
      android/app/src/main/res/mipmap-mdpi/ic_launcher.png
  8. BIN
      android/app/src/main/res/mipmap-mdpi/ic_launcher.webp
  9. BIN
      android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp
  10. BIN
      android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
  11. BIN
      android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
  12. BIN
      android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
  13. BIN
      android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp
  14. BIN
      android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
  15. BIN
      android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
  16. BIN
      android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
  17. BIN
      android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp
  18. BIN
      android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
  19. BIN
      android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
  20. BIN
      android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
  21. BIN
      android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp
  22. BIN
      android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
  23. 1 0
      android/gradle.properties
  24. 3 0
      devtools_options.yaml
  25. 0 0
      lib/app/app.dart
  26. 0 0
      lib/app/routes/app_router.dart
  27. 0 0
      lib/app/theme/app_theme.dart
  28. 35 0
      lib/core/constants/options.dart
  29. 99 0
      lib/core/models/challenge_model.dart
  30. 16 0
      lib/core/models/dropdown_item.dart
  31. 23 0
      lib/core/models/option_model.dart
  32. 6 0
      lib/core/proviers/database_providers.dart
  33. 10 0
      lib/core/proviers/repository_providers.dart
  34. 0 0
      lib/core/repositories/card_repository.dart
  35. 14 0
      lib/core/repositories/challenge_repository.dart
  36. 55 0
      lib/core/repositories/mock_challenge_repository.dart
  37. 121 0
      lib/core/repositories/sqlite_challenge_repository.dart
  38. 47 0
      lib/core/routes/app_router.dart
  39. 0 0
      lib/core/services/challenge_service.dart
  40. 50 0
      lib/data/local/database_helper.dart
  41. 65 0
      lib/features/challenge/view_models/challenge_add_vm.dart
  42. 30 0
      lib/features/challenge/view_models/challenge_detail_vm.dart
  43. 54 0
      lib/features/challenge/view_models/challenge_edit_vm.dart
  44. 46 0
      lib/features/challenge/view_models/challenge_list_vm.dart
  45. 198 0
      lib/features/challenge/views/add_challenge_screen.dart
  46. 82 0
      lib/features/challenge/views/challenge_detail_screen.dart
  47. 72 0
      lib/features/challenge/views/challenge_list_screen.dart
  48. 109 0
      lib/features/challenge/views/edit_challenge_screen.dart
  49. 359 0
      lib/features/challenge/views/mock_page.dart
  50. 37 0
      lib/features/challenge/views/widgets/challenge_list_card_widget.dart
  51. 69 0
      lib/features/widgets/app_dropdown.dart
  52. 11 110
      lib/main.dart
  53. 2 0
      macos/Flutter/GeneratedPluginRegistrant.swift
  54. 169 1
      pubspec.lock
  55. 8 1
      pubspec.yaml
  56. 0 30
      test/widget_test.dart

+ 2 - 1
android/app/build.gradle.kts

@@ -8,7 +8,8 @@ plugins {
 android {
     namespace = "com.example.japp_flutter"
     compileSdk = flutter.compileSdkVersion
-    ndkVersion = flutter.ndkVersion
+    // ndkVersion = flutter.ndkVersion
+    ndkVersion = project.properties["org.gradle.ndk.version"] as String
 
     compileOptions {
         sourceCompatibility = JavaVersion.VERSION_11

+ 1 - 1
android/app/src/main/AndroidManifest.xml

@@ -1,6 +1,6 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android">
     <application
-        android:label="japp_flutter"
+        android:label="JAPP"
         android:name="${applicationName}"
         android:icon="@mipmap/ic_launcher">
         <activity

BIN
android/app/src/main/res/mipmap-hdpi/ic_launcher.png


BIN
android/app/src/main/res/mipmap-hdpi/ic_launcher.webp


BIN
android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp


BIN
android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp


BIN
android/app/src/main/res/mipmap-mdpi/ic_launcher.png


BIN
android/app/src/main/res/mipmap-mdpi/ic_launcher.webp


BIN
android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp


BIN
android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp


BIN
android/app/src/main/res/mipmap-xhdpi/ic_launcher.png


BIN
android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp


BIN
android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp


BIN
android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp


BIN
android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png


BIN
android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp


BIN
android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp


BIN
android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp


BIN
android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png


BIN
android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp


BIN
android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp


BIN
android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp


+ 1 - 0
android/gradle.properties

@@ -3,3 +3,4 @@ systemProp.https.proxyHost=mirrors.aliyun.com
 org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
 android.useAndroidX=true
 android.enableJetifier=true
+org.gradle.ndk.version=28.0.12674087

+ 3 - 0
devtools_options.yaml

@@ -0,0 +1,3 @@
+description: This file stores settings for Dart & Flutter DevTools.
+documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states
+extensions:

+ 0 - 0
lib/app/app.dart


+ 0 - 0
lib/app/routes/app_router.dart


+ 0 - 0
lib/app/theme/app_theme.dart


+ 35 - 0
lib/core/constants/options.dart

@@ -0,0 +1,35 @@
+import 'package:flutter/material.dart';
+import 'package:japp_flutter/core/models/option_model.dart';
+import 'package:japp_flutter/core/models/dropdown_item.dart';
+
+// 通用状态选项
+const List<OptionModel<String>> statusOptions = [
+  OptionModel(value: 'active', label: '激活', icon: Icons.check_circle),
+  OptionModel(value: 'inactive', label: '未激活', icon: Icons.pause_circle),
+  OptionModel(value: 'archived', label: '已归档', icon: Icons.archive),
+];
+
+// 通用优先级选项
+const List<OptionModel<int>> priorityOptions = [
+  OptionModel(value: 1, label: '低'),
+  OptionModel(value: 2, label: '中'),
+  OptionModel(value: 3, label: '高'),
+];
+
+// 通用优先级选项
+const List<OptionModel<int>> difficultOptions = [
+  OptionModel(value: 1, label: '简单'),
+  OptionModel(value: 2, label: '普通'),
+  OptionModel(value: 3, label: '困难'),
+  OptionModel(value: 4, label: '地狱'),
+];
+
+
+
+// 挑战目标难度选项
+const List<DropdownItem<int>> challengeDifficultyOpts = [
+  DropdownItem(value: 1, label: '简单', icon: Icons.sentiment_very_satisfied),
+  DropdownItem(value: 2, label: '普通',icon: Icons.sentiment_satisfied),
+  DropdownItem(value: 3, label: '困难',icon:Icons.sentiment_dissatisfied),
+  DropdownItem(value: 4, label: '地狱',icon: Icons.sentiment_very_dissatisfied),
+];

+ 99 - 0
lib/core/models/challenge_model.dart

@@ -0,0 +1,99 @@
+import 'package:flutter/foundation.dart';
+
+// 待办
+@immutable
+class ChallengeModel {
+  final int? id;
+  final int actionType; // 1-愿景 2-战略目标 3-挑战目标 4-待办目标
+  final String title;
+  final String description;
+  final String cover;
+  final int sort;
+  final int status; // 1-正常 2-进行中 3-已完成 4-失败放弃
+  final int difficulty; // 1-简单 2-正常 3-困难 4-地狱
+  final String remark;
+  final DateTime? startDate;
+  final DateTime? endDate;
+  final DateTime? finishDate;
+  final DateTime? planFinishDate;
+  final int parentId;
+
+  const ChallengeModel({
+    required this.id,
+    required this.actionType,
+    required this.title,
+    required this.description,
+    required this.cover,
+    required this.sort,
+    required this.status,
+    required this.difficulty,
+    required this.remark,
+    required this.startDate,
+    required this.endDate,
+    required this.finishDate,
+    required this.parentId,
+    required this.planFinishDate,
+  });
+
+  factory ChallengeModel.fromJson(Map<String, dynamic> json) {
+    return ChallengeModel(
+      id: json['id'] as int?,
+      title: json['title'] as String,
+      description: json['description'] as String,
+      startDate: DateTime.parse(json['start_date'] as String),
+      endDate: DateTime.parse(json['end_date'] as String),
+      cover: json['cover'] as String,
+      sort: json['sort'] as int,
+      status: json['status'] as int,
+      remark: json['remark'] as String,
+      finishDate: DateTime.parse(json['finish_date'] as String),
+      parentId: json['parent_id'] as int, 
+      actionType: 0, 
+      difficulty: 0, 
+      planFinishDate: DateTime.parse(json['end_date'] as String),
+    );
+  }
+
+  /// 转换为JSON
+  // Map<String, dynamic> toJson() => {
+  //   if (id != null) 'id': id,
+  //   'title': title,
+  //   'description': description,
+  //   'startDate': startDate.toIso8601String(),
+  //   'endDate': endDate.toIso8601String(),
+  // };
+
+  ChallengeModel copyWith({
+    int? id,
+    int? actionType,
+    String? title,
+    String? description,
+    int? status,
+    int? sort,
+    String? cover,
+    String? remark,
+    int? difficulty,
+    DateTime? startDate,
+    DateTime? endDate,
+    DateTime? finishDate,
+    DateTime? planFinishDate,
+    int? parentId,
+  }) {
+    return ChallengeModel(
+      id: id ?? this.id,
+      actionType: actionType ?? this.actionType,
+      title: title ?? this.title,
+      description: description ?? this.description,
+      startDate: startDate ?? this.startDate,
+      endDate: endDate ?? this.endDate, 
+      cover: cover ?? this.cover, 
+      sort: sort ?? this.sort, 
+      status: status ?? this.status, 
+      remark: remark ?? this.remark,
+      finishDate: finishDate ?? this.finishDate,
+      planFinishDate: planFinishDate ?? this.planFinishDate,
+      parentId: parentId ?? this.parentId,
+      difficulty: difficulty ?? this.difficulty,
+    );
+  }
+}

+ 16 - 0
lib/core/models/dropdown_item.dart

@@ -0,0 +1,16 @@
+import 'package:flutter/material.dart';
+
+
+class DropdownItem<T> {
+  final T value;
+  final String label;
+  final IconData? icon;
+  final Color? iconColor;
+
+  const DropdownItem({
+    required this.value,
+    required this.label,
+    this.icon,
+    this.iconColor,
+  });
+}

+ 23 - 0
lib/core/models/option_model.dart

@@ -0,0 +1,23 @@
+import 'package:flutter/material.dart';
+
+class OptionModel<T> {
+  final T value;
+  final String label;
+  final IconData? icon;
+  
+  const OptionModel({
+    required this.value,
+    required this.label,
+    this.icon,
+  });
+  
+  @override
+  bool operator ==(Object other) =>
+      identical(this, other) ||
+      other is OptionModel &&
+          runtimeType == other.runtimeType &&
+          value == other.value;
+  
+  @override
+  int get hashCode => value.hashCode;
+}

+ 6 - 0
lib/core/proviers/database_providers.dart

@@ -0,0 +1,6 @@
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:japp_flutter/data/local/database_helper.dart';
+
+final databaseHelperProvider = Provider<DatabaseHelper>((ref) {
+  return DatabaseHelper(); // 单例模式
+});

+ 10 - 0
lib/core/proviers/repository_providers.dart

@@ -0,0 +1,10 @@
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:japp_flutter/core/proviers/database_providers.dart';
+import 'package:japp_flutter/core/repositories/challenge_repository.dart';
+import 'package:japp_flutter/core/repositories/sqlite_challenge_repository.dart';
+
+// 基础 Repository 提供者(全局可用)
+final challengeRepositoryProvider = Provider<ChallengeRepository>((ref) {
+  // 依赖注入 DatabaseHelper
+  return SqliteChallengeRepository(ref.read(databaseHelperProvider));
+});

+ 0 - 0
lib/core/repositories/card_repository.dart


+ 14 - 0
lib/core/repositories/challenge_repository.dart

@@ -0,0 +1,14 @@
+import 'package:japp_flutter/core/models/challenge_model.dart';
+
+abstract class ChallengeRepository {
+  Future<List<ChallengeModel>> getChallenges({
+    int page=1,
+    int pageSize = 20,
+    int actionType= 1,
+  });
+  Future<ChallengeModel?> getChallengeById(int id);
+  Future<ChallengeModel> addChallenge(ChallengeModel challenge);
+  Future<ChallengeModel> updateChallenge(ChallengeModel challenge);
+  Future<void> deleteChallenge(int id);
+
+}

+ 55 - 0
lib/core/repositories/mock_challenge_repository.dart

@@ -0,0 +1,55 @@
+import 'package:japp_flutter/core/models/challenge_model.dart';
+import 'package:japp_flutter/core/repositories/challenge_repository.dart';
+
+
+class MockChallengeRepository implements ChallengeRepository {
+  final List<ChallengeModel> _mockData = [
+    ChallengeModel(
+      id: 1,
+      title: 'Learn Flutter',
+      startDate: DateTime.now(),
+      endDate: DateTime.now().add(Duration(days: 30)), 
+      description: '', 
+      difficulty: 1, actionType: 1, cover: '', sort: 1, status: 1, remark: '', finishDate: DateTime.now(), parentId: 0, planFinishDate: DateTime.now(),
+      // 其他字段...
+    ),
+  ];
+
+  @override
+  Future<List<ChallengeModel>> getChallenges({
+        int page=1,
+    int pageSize = 20,
+    int actionType= 1,
+  }) async {
+    await Future.delayed(Duration(seconds: 1)); // 模拟网络延迟
+    return _mockData;
+  }
+  
+  @override
+  Future<ChallengeModel> addChallenge(ChallengeModel challenge) {
+    // TODO: implement addChallenge
+    throw UnimplementedError();
+  }
+  
+  @override
+  Future<void> deleteChallenge(int id) {
+    // TODO: implement deleteChallenge
+    throw UnimplementedError();
+  }
+  
+  @override
+  Future<ChallengeModel?> getChallengeById(int id) async{
+    await Future.delayed(Duration(seconds: 1)); // 模拟网络延迟
+    return _mockData[0];
+  }
+  
+  
+  @override
+  Future<ChallengeModel> updateChallenge(ChallengeModel challenge) {
+    // TODO: implement updateChallenge
+    throw UnimplementedError();
+  }
+  
+
+  // 其他方法实现...
+}

+ 121 - 0
lib/core/repositories/sqlite_challenge_repository.dart

@@ -0,0 +1,121 @@
+import 'package:japp_flutter/core/models/challenge_model.dart';
+import 'package:japp_flutter/core/repositories/challenge_repository.dart';
+import 'package:japp_flutter/data/local/database_helper.dart';
+import 'package:sqflite/sqflite.dart';
+
+class SqliteChallengeRepository implements ChallengeRepository {
+  final DatabaseHelper dbHelper;
+
+  SqliteChallengeRepository(this.dbHelper);
+
+  @override
+  Future<List<ChallengeModel>> getChallenges({
+    int page = 1,
+    int pageSize = 20,
+    int actionType = 1,
+  }) async {
+    final db = await dbHelper.database;
+    final maps = await db.query(DatabaseHelper.chllangetTable);
+    return maps.map(_mapDatabaseToModel).toList();
+  }
+
+  @override
+  Future<ChallengeModel> addChallenge(ChallengeModel challenge) async {
+    final db = await dbHelper.database;
+    await db.insert(
+      DatabaseHelper.chllangetTable,
+      _mapModelToDatabase(challenge),
+      conflictAlgorithm: ConflictAlgorithm.replace,
+    );
+    return challenge;
+  }
+
+  // 其他方法实现类似...
+
+  // Helper: 数据库行转 Model
+  ChallengeModel _mapDatabaseToModel(Map<String, dynamic> map) {
+    return ChallengeModel(
+      id: map[DatabaseHelper.columnId],
+      title: map[DatabaseHelper.columnTitle],
+      description: map['description'],
+      actionType: map['action_type'],
+      cover: map['cover'],
+      sort: map['sort'],
+      status: map['status'],
+      remark: map['remark'],
+      parentId: map['parent_id'],
+      difficulty: map['difficulty'],
+      finishDate: map['finish_date']
+          ? DateTime.fromMillisecondsSinceEpoch(map['finish_date'])
+          : null,
+      planFinishDate: map['plan_finish_date']
+          ? DateTime.fromMillisecondsSinceEpoch(map['plan_finish_date'])
+          : null,
+      startDate: map['start_date']
+          ? DateTime.fromMillisecondsSinceEpoch(map['start_date'])
+          : null,
+      endDate: map['end_date']
+          ? DateTime.fromMillisecondsSinceEpoch(map['end_date'])
+          : null,
+    );
+  }
+
+  // Helper: Model 转数据库行
+  Map<String, dynamic> _mapModelToDatabase(ChallengeModel model) {
+    return {
+      DatabaseHelper.columnId: model.id,
+      DatabaseHelper.columnTitle: model.title,
+      'action_type':model.actionType,
+      'cover':model.cover,
+      'sort':model.sort,
+      'status':model.status,
+      'remark':model.remark,
+      'parent_id':model.parentId,
+      'description': model.description,
+      'difficulty': model.difficulty,
+      'start_date': model.startDate?.millisecondsSinceEpoch,
+      'end_date': model.endDate?.millisecondsSinceEpoch,
+      'finish_date':model.finishDate,
+      'plan_finish_date':model.planFinishDate,
+    };
+  }
+
+  @override
+  Future<void> deleteChallenge(int id) async {
+    final db = await dbHelper.database;
+    await db.delete(
+      DatabaseHelper.chllangetTable,
+      where: '${DatabaseHelper.columnId} = ?',
+      whereArgs: [id],
+    );
+  }
+
+  @override
+  Future<ChallengeModel?> getChallengeById(int id) async {
+    final db = await dbHelper.database;
+    final maps = await db.query(
+      DatabaseHelper.chllangetTable,
+      where: '${DatabaseHelper.columnId} = ?',
+      whereArgs: [id],
+      limit: 1,
+    );
+
+    if (maps.isNotEmpty) {
+      return _mapDatabaseToModel(maps.first);
+    }
+    return null;
+  }
+
+  @override
+  Future<ChallengeModel> updateChallenge(ChallengeModel challenge) async {
+    final db = await dbHelper.database;
+    await db.update(
+      DatabaseHelper.chllangetTable,
+      _mapModelToDatabase(challenge),
+      where: '${DatabaseHelper.columnId} = ?',
+      whereArgs: [challenge.id],
+      conflictAlgorithm: ConflictAlgorithm.replace,
+    );
+    return challenge;
+  }
+}

+ 47 - 0
lib/core/routes/app_router.dart

@@ -0,0 +1,47 @@
+import 'package:flutter/material.dart';
+import 'package:japp_flutter/core/models/challenge_model.dart';
+import 'package:japp_flutter/features/challenge/views/add_challenge_screen.dart';
+import 'package:japp_flutter/features/challenge/views/challenge_detail_screen.dart';
+import 'package:japp_flutter/features/challenge/views/challenge_list_screen.dart';
+import 'package:japp_flutter/features/challenge/views/edit_challenge_screen.dart';
+import 'package:japp_flutter/features/challenge/views/mock_page.dart';
+
+class AppRouter {
+  static const String challengeList = '/';
+  static const String challengeDetail = '/challenge/detail';
+  static const String addChallenge = '/challenge/add';
+  static const String editChallenge = '/challenge/edit';
+  static const String mockPage = '/mock/page';
+
+  static Route<dynamic> generateRoute(RouteSettings settings) {
+    switch (settings.name) {
+      case challengeList:
+        return MaterialPageRoute(builder: (_) => const ChallengeListScreen());
+      case challengeDetail:
+        final challengeId = settings.arguments as int;
+        return MaterialPageRoute(
+          builder: (_) => ChallengeDetailScreen(challengeId: challengeId),
+        );
+      case addChallenge:
+        return MaterialPageRoute(builder: (_) => const AddChallengeScreen());
+      case editChallenge:
+        final challenge = settings.arguments as ChallengeModel;
+        return MaterialPageRoute(
+          builder: (_) => ChallengeEditScreen(initialChallenge: challenge),
+        );
+      case mockPage:
+        return MaterialPageRoute(builder: (_)=> const MockPage());
+      default:
+        return MaterialPageRoute(
+          builder: (_) => Scaffold(
+            body: Center(child: Text('No route defined for ${settings.name}')),
+          ),
+        );
+    }
+  }
+
+  // 静态跳转方法(可选)
+  static void navigateTo(BuildContext context, String routeName, {Object? args}) {
+    Navigator.pushNamed(context, routeName, arguments: args);
+  }
+}

+ 0 - 0
lib/core/services/challenge_service.dart


+ 50 - 0
lib/data/local/database_helper.dart

@@ -0,0 +1,50 @@
+import 'package:sqflite/sqflite.dart';
+import 'package:path/path.dart';
+
+class DatabaseHelper {
+  static const _databaseName = 'jay_world_app.db';
+  static const _databaseVersion = 1;
+
+  static const chllangetTable = 'challenges';
+  static const columnId = 'id';
+  static const columnTitle = 'title';
+  // 其他字段...
+
+  static Database? _database;
+
+  Future<Database> get database async {
+    if (_database != null) return _database!;
+    _database = await _initDatabase();
+    return _database!;
+  }
+
+  Future<Database> _initDatabase() async {
+    final path = join(await getDatabasesPath(), _databaseName);
+    return await openDatabase(
+      path,
+      version: _databaseVersion,
+      onCreate: _onCreate,
+    );
+  }
+
+  Future<void> _onCreate(Database db, int version) async {
+    await db.execute('''
+      CREATE TABLE $chllangetTable (
+        $columnId INTEGER PRIMARY KEY AUTOINCREMENT,
+        $columnTitle TEXT NOT NULL,
+        action_type INTEGER NOT NULL,
+        description TEXT NOT NULL,
+        cover TEXT NOT NULL,
+        sort INTEGER NOT NULL,
+        status INTEGER NOT NULL,
+        difficulty INTEGER NOT NULL,
+        remark TEXT NOT NULL,
+        start_date INTEGER,
+        end_date INTEGER,
+        finish_date INTEGER,
+        plan_finish_date INTEGER,
+        parent_id INTEGER NOT NULL
+      );
+    ''');
+  }
+}

+ 65 - 0
lib/features/challenge/view_models/challenge_add_vm.dart

@@ -0,0 +1,65 @@
+// features/challenge/add/add_challenge_viewmodel.dart
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:japp_flutter/core/models/challenge_model.dart';
+import 'package:japp_flutter/core/proviers/repository_providers.dart';
+import 'package:japp_flutter/core/repositories/challenge_repository.dart';
+
+final addChallengeProvider =
+    AsyncNotifierProvider<AddChallengeViewModel, ChallengeModel>(
+      AddChallengeViewModel.new,
+    );
+
+class AddChallengeViewModel extends AsyncNotifier<ChallengeModel> {
+  ChallengeRepository get _repository => ref.read(challengeRepositoryProvider);
+
+  @override
+  Future<ChallengeModel> build() async {
+    // 初始化为空挑战模板
+    return ChallengeModel(
+      id: null,
+      title: '',
+      description: '',
+      startDate: DateTime.now(),
+      endDate: DateTime.now().add(const Duration(days: 30)),
+      actionType: 1,
+      cover: '',
+      sort: 200,
+      status: 1,
+      difficulty: 1,
+      remark: '',
+      finishDate: null,
+      parentId: 1,
+      planFinishDate: null
+    );
+  }
+
+  /// 更新挑战标题
+  void updateTitle(String title) {
+    state = AsyncData(state.value!.copyWith(title: title));
+  }
+
+  /// 更新挑战描述
+  void updateDescription(String description) {
+    state = AsyncData(state.value!.copyWith(description: description));
+  }
+
+  /// 更新难度级别
+  void updateDifficulty(int difficulty) {
+    state = AsyncData(state.value!.copyWith(difficulty: difficulty));
+  }
+
+  /// 更新日期范围
+  void updateDateRange(DateTime start, DateTime end) {
+    state = AsyncData(state.value!.copyWith(startDate: start, endDate: end));
+  }
+
+  /// 提交新挑战
+  Future<void> submitChallenge() async {
+    if (state.value == null) return;
+
+    state = const AsyncLoading();
+    state = await AsyncValue.guard(
+      () => _repository.addChallenge(state.value!),
+    );
+  }
+}

+ 30 - 0
lib/features/challenge/view_models/challenge_detail_vm.dart

@@ -0,0 +1,30 @@
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:japp_flutter/core/models/challenge_model.dart';
+import 'package:japp_flutter/core/proviers/repository_providers.dart';
+import 'package:japp_flutter/core/repositories/challenge_repository.dart';
+
+
+
+final challengeDetailProvider = AsyncNotifierProvider.autoDispose<ChallengeDetailViewModel, ChallengeModel?>(
+  ChallengeDetailViewModel.new,
+);
+
+
+class ChallengeDetailViewModel extends AutoDisposeAsyncNotifier<ChallengeModel?> {
+  ChallengeRepository get _repository => ref.read(challengeRepositoryProvider);
+
+  @override
+  Future<ChallengeModel?> build() async {
+    return null;
+  }
+
+  Future<void> loadChallenge(int challengeId) async {
+    state = const AsyncLoading();
+    state = await AsyncValue.guard(() => _repository.getChallengeById(challengeId));
+  }
+
+  // 手动刷新用
+  Future<void> refreshChallenge(int challengeId) async {
+    await loadChallenge(challengeId);
+  }
+}

+ 54 - 0
lib/features/challenge/view_models/challenge_edit_vm.dart

@@ -0,0 +1,54 @@
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:japp_flutter/core/models/challenge_model.dart';
+import 'package:japp_flutter/core/proviers/repository_providers.dart';
+import 'package:japp_flutter/core/repositories/challenge_repository.dart';
+
+final challengeEditProvider = AsyncNotifierProvider.autoDispose<ChallengeEditViewModel, ChallengeModel?>(
+  ChallengeEditViewModel.new,
+);
+
+class ChallengeEditViewModel extends AutoDisposeAsyncNotifier<ChallengeModel?> {
+  ChallengeRepository get _repository => ref.read(challengeRepositoryProvider);
+
+  @override
+  Future<ChallengeModel?> build() async {
+    // 初始化为 null,由外部传入初始数据
+    return null;
+  }
+
+  /// 初始化编辑数据(从详情页传入)
+  void initialize(ChallengeModel challenge) {
+    state = AsyncData(challenge);
+  }
+
+  /// 更新挑战标题
+  void updateTitle(String title) {
+    state.whenData((challenge) {
+      state = AsyncData(challenge!.copyWith(title: title));
+    });
+  }
+
+  /// 更新挑战描述
+  void updateDescription(String description) {
+    state.whenData((challenge) {
+      state = AsyncData(challenge!.copyWith(description: description));
+    });
+  }
+
+  /// 更新挑战描述
+  void updateDifficulty(int difficulty) {
+    state.whenData((challenge) {
+      state = AsyncData(challenge!.copyWith(difficulty: difficulty));
+    });
+  }
+
+
+  /// 保存挑战
+  Future<void> saveChallenge() async {
+    final challenge = state.value;
+    if (challenge == null) return;
+
+    state = const AsyncLoading();
+    state = await AsyncValue.guard(() => _repository.updateChallenge(challenge));
+  }
+}

+ 46 - 0
lib/features/challenge/view_models/challenge_list_vm.dart

@@ -0,0 +1,46 @@
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:japp_flutter/core/models/challenge_model.dart';
+import 'package:japp_flutter/core/proviers/repository_providers.dart';
+import 'package:riverpod_annotation/riverpod_annotation.dart';
+import 'package:japp_flutter/core/repositories/challenge_repository.dart';
+
+
+final challengeListProvider = AsyncNotifierProvider<ChallengeListViewModel, List<ChallengeModel>>(
+  ChallengeListViewModel.new,
+);
+
+class ChallengeListViewModel extends AsyncNotifier<List<ChallengeModel>> {
+  ChallengeRepository get _repository => ref.read(challengeRepositoryProvider);
+
+  @override
+  Future<List<ChallengeModel>> build() async {
+    // 初始加载数据(可选)
+    return loadChallenges(); // 初始化为空列表,或直接调用 loadChallenges()
+  }
+
+
+  Future<List<ChallengeModel>> loadChallenges() async {
+    state = const AsyncValue.loading();
+    try {
+      final challenges = await _repository.getChallenges();
+      state = AsyncValue.data(challenges);
+      return challenges;
+    } catch (e, stack) {
+      state = AsyncValue.error(e, stack);
+      
+      rethrow;
+    }
+  }
+
+  // /// 加载挑战列表(自动处理加载/错误状态)
+  // Future<void> loadChallenges() async {
+  //   state = const AsyncLoading();
+  //   state = await AsyncValue.guard(() => _repository.getAllChallenges());
+  // }
+
+  /// 刷新数据(别名方法)
+  Future<void> refresh() async {
+      state = const AsyncValue.loading();
+      await loadChallenges(); // 复用加载逻辑
+  }
+}

+ 198 - 0
lib/features/challenge/views/add_challenge_screen.dart

@@ -0,0 +1,198 @@
+// features/challenge/add/add_challenge_screen.dart
+import 'package:flutter/material.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:intl/intl.dart';
+import 'package:japp_flutter/core/constants/options.dart';
+import 'package:japp_flutter/core/models/challenge_model.dart';
+import 'package:japp_flutter/features/challenge/view_models/challenge_add_vm.dart';
+
+class AddChallengeScreen extends ConsumerWidget {
+  const AddChallengeScreen({super.key});
+
+  @override
+  Widget build(BuildContext context, WidgetRef ref) {
+    final challengeState = ref.watch(addChallengeProvider);
+    final theme = Theme.of(context);
+
+    return Scaffold(
+      appBar: AppBar(
+        title: const Text('创建新挑战'),
+        actions: [
+          IconButton(
+            icon: const Icon(Icons.check),
+            onPressed: () async {
+              await ref.read(addChallengeProvider.notifier).submitChallenge();
+              if (context.mounted) Navigator.pop(context);
+            },
+            tooltip: '提交',
+          ),
+        ],
+      ),
+      body: challengeState.when(
+        loading: () => const Center(child: CircularProgressIndicator()),
+        error: (error, _) => Center(child: Text('错误: $error')),
+        data: (challenge) => _BuildForm(challenge: challenge),
+      ),
+    );
+  }
+}
+
+class _BuildForm extends ConsumerWidget {
+  final ChallengeModel challenge;
+
+  const _BuildForm({required this.challenge});
+
+  @override
+  Widget build(BuildContext context, WidgetRef ref) {
+    return SingleChildScrollView(
+      padding: const EdgeInsets.all(16),
+      child: Column(
+        crossAxisAlignment: CrossAxisAlignment.start,
+        children: [
+          _TitleField(initialValue: challenge.title),
+          const SizedBox(height: 24),
+          _DateRangeField(
+            startDate: challenge.startDate!,
+            endDate: challenge.endDate!,
+          ),
+          const SizedBox(height: 24),
+          _DifficultySelector(currentDifficulty: challenge.difficulty),
+          const SizedBox(height: 24),
+          _DescriptionField(initialValue: challenge.description),
+        ],
+      ),
+    );
+  }
+}
+
+class _TitleField extends ConsumerWidget {
+  final String initialValue;
+
+  const _TitleField({required this.initialValue});
+
+  @override
+  Widget build(BuildContext context, WidgetRef ref) {
+    return TextFormField(
+      initialValue: initialValue,
+      decoration: const InputDecoration(
+        labelText: '挑战标题*',
+        border: OutlineInputBorder(),
+      ),
+      onChanged: (value) => ref.read(addChallengeProvider.notifier).updateTitle(value),
+    );
+  }
+}
+
+class _DescriptionField extends ConsumerWidget {
+  final String initialValue;
+
+  const _DescriptionField({required this.initialValue});
+
+  @override
+  Widget build(BuildContext context, WidgetRef ref) {
+    return TextFormField(
+      initialValue: initialValue,
+      decoration: const InputDecoration(
+        labelText: '挑战描述',
+        border: OutlineInputBorder(),
+        alignLabelWithHint: true,
+      ),
+      maxLines: 5,
+      onChanged: (value) => ref.read(addChallengeProvider.notifier).updateDescription(value),
+    );
+  }
+}
+
+class _DifficultySelector extends ConsumerWidget {
+  final int currentDifficulty;
+  // static const difficulties = ['简单', '中等', '困难', '地狱'];
+
+  const _DifficultySelector({required this.currentDifficulty});
+
+  @override
+  Widget build(BuildContext context, WidgetRef ref) {
+    return Column(
+      crossAxisAlignment: CrossAxisAlignment.start,
+      children: [
+        const Text('难度级别*', style: TextStyle(fontSize: 16)),
+        const SizedBox(height: 8),
+        Wrap(
+          spacing: 8,
+          children: difficultOptions.map((option) {
+            return ChoiceChip(
+              label: Text(option.label),
+              selected: currentDifficulty == option.value,
+              onSelected: (selected) {
+                if (selected) {
+                  ref.read(addChallengeProvider.notifier).updateDifficulty(option.value);
+                }
+              },
+            );
+          }).toList(),
+        ),
+      ],
+    );
+  }
+}
+
+class _DateRangeField extends ConsumerWidget {
+  final DateTime startDate;
+  final DateTime endDate;
+
+  const _DateRangeField({
+    required this.startDate,
+    required this.endDate,
+  });
+
+  Future<void> _selectDate(BuildContext context, bool isStartDate, WidgetRef ref) async {
+    final initialDate = isStartDate ? startDate : endDate;
+    final picked = await showDatePicker(
+      context: context,
+      initialDate: initialDate,
+      firstDate: DateTime.now(),
+      lastDate: DateTime(2100),
+    );
+
+    if (picked != null) {
+      final newStart = isStartDate ? picked : startDate;
+      final newEnd = isStartDate ? endDate : picked;
+      ref.read(addChallengeProvider.notifier).updateDateRange(newStart, newEnd);
+    }
+  }
+
+  @override
+  Widget build(BuildContext context, WidgetRef ref) {
+    return Column(
+      crossAxisAlignment: CrossAxisAlignment.start,
+      children: [
+        const Text('挑战周期*', style: TextStyle(fontSize: 16)),
+        const SizedBox(height: 8),
+        Row(
+          children: [
+            Expanded(
+              child: OutlinedButton(
+                onPressed: () => _selectDate(context, true, ref),
+                child: Text(DateFormat('yyyy/MM/dd').format(startDate)),
+              ),
+            ),
+            const Padding(
+              padding: EdgeInsets.symmetric(horizontal: 8),
+              child: Text('至'),
+            ),
+            Expanded(
+              child: OutlinedButton(
+                onPressed: () => _selectDate(context, false, ref),
+                child: Text(DateFormat('yyyy/MM/dd').format(endDate)),
+              ),
+            ),
+          ],
+        ),
+        const SizedBox(height: 4),
+        Text(
+          '总天数: ${endDate.difference(startDate).inDays}天',
+          style: const TextStyle(color: Colors.grey),
+        ),
+      ],
+    );
+  }
+}

+ 82 - 0
lib/features/challenge/views/challenge_detail_screen.dart

@@ -0,0 +1,82 @@
+import 'package:flutter/material.dart';
+import 'package:intl/intl.dart';
+import 'package:japp_flutter/core/models/challenge_model.dart';
+import 'package:japp_flutter/features/challenge/view_models/challenge_detail_vm.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+
+class ChallengeDetailScreen extends ConsumerWidget {
+  final int challengeId;
+
+  const ChallengeDetailScreen({super.key, required this.challengeId});
+
+  @override
+  Widget build(BuildContext context, WidgetRef ref) {
+    final challengeAsync = ref.watch(challengeDetailProvider);
+
+    return Scaffold(
+      appBar: AppBar(
+        title: const Text("挑战详情"),
+        actions: [
+          IconButton(
+            icon: const Icon(Icons.edit),
+            onPressed: () {
+              final challenge = challengeAsync.value;
+              if (challenge != null) {
+                Navigator.pushNamed(context, '/edit/${challenge.id}');
+              }
+            },
+          ),
+        ],
+      ),
+      body: switch (challengeAsync) {
+        AsyncError(:final error) => Center(child: Text('加载失败: $error')),
+        AsyncData(value: final value) => _buildChallengeContent(value!),
+        _ => const Center(child: CircularProgressIndicator()),
+
+      },
+    );
+  }
+
+  Widget _buildChallengeContent(ChallengeModel challenge) {
+    return SingleChildScrollView(
+      padding: const EdgeInsets.all(16),
+      child: Column(
+        crossAxisAlignment: CrossAxisAlignment.start,
+        children: [
+          Text(
+            challenge.title,
+            style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
+          ),
+          const SizedBox(height: 16),
+          Text(
+            challenge.description,
+            style: const TextStyle(fontSize: 16, height: 1.5),
+          ),
+        ],
+      ),
+    );
+  }
+
+  Widget _buildDateInfo(String label, DateTime date) {
+    return Padding(
+      padding: const EdgeInsets.only(bottom: 8),
+      child: Row(
+        children: [
+          Text('$label: ', style: const TextStyle(fontWeight: FontWeight.bold)),
+          Text(DateFormat('yyyy年MM月dd日').format(date)),
+        ],
+      ),
+    );
+  }
+
+  Widget _buildParticipantInfo(int count) {
+    return Row(
+      children: [
+        const Icon(Icons.people, size: 20),
+        const SizedBox(width: 8),
+        Text('$count 人参与'),
+      ],
+    );
+  }
+
+}

+ 72 - 0
lib/features/challenge/views/challenge_list_screen.dart

@@ -0,0 +1,72 @@
+// challenge_list_screen.dart
+import 'package:flutter/material.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:japp_flutter/core/models/challenge_model.dart';
+import 'package:japp_flutter/features/challenge/view_models/challenge_list_vm.dart';
+import 'package:japp_flutter/features/challenge/views/widgets/challenge_list_card_widget.dart';
+
+class ChallengeListScreen extends ConsumerWidget {
+  const ChallengeListScreen({super.key});
+
+  @override
+  Widget build(BuildContext context, WidgetRef ref) {
+    final challengesState = ref.watch(challengeListProvider);
+
+    return Scaffold(
+      appBar: AppBar(
+        title: const Text('挑战列表'),
+        actions: [
+          IconButton(
+            icon: const Icon(Icons.refresh),
+            onPressed: () => ref.read(challengeListProvider.notifier).refresh(),
+          ),
+        ],
+      ),
+      body: _buildBody(challengesState, ref),
+      floatingActionButton: FloatingActionButton(
+        onPressed: () => Navigator.pushNamed(context, '/challenge/add'),
+        child: const Icon(Icons.add),
+      ),
+    );
+  }
+
+  Widget _buildBody(AsyncValue<List<ChallengeModel>> state, WidgetRef ref) {
+    return state.when(
+      loading: () => const Center(child: CircularProgressIndicator()),
+      error: (error, stack) => Center(
+        child: Column(
+          mainAxisAlignment: MainAxisAlignment.center,
+          children: [
+            const Icon(Icons.error, color: Colors.red, size: 48),
+            const SizedBox(height: 16),
+            Text('加载失败: $error', style: const TextStyle(color: Colors.red)),
+            const SizedBox(height: 16),
+            ElevatedButton(
+              onPressed: () =>
+                  ref.read(challengeListProvider.notifier).refresh(),
+              child: const Text('重试'),
+            ),
+          ],
+        ),
+      ),
+      data: (challenges) => challenges.isEmpty
+          ? const Center(child: Text('暂无挑战', style: TextStyle(fontSize: 18)))
+          : RefreshIndicator(
+              onRefresh: () =>
+                  ref.read(challengeListProvider.notifier).refresh(),
+              child: ListView.builder(
+                padding: const EdgeInsets.all(16),
+                itemCount: challenges.length,
+                itemBuilder: (_, index) => ChallengeListCard(challenge: challenges[index]),
+                
+                // itemBuilder: (context, index) => ListTile(
+                //   title: Text(challenges[index].title),
+                //   // 其他列表项内容
+                // ),
+
+
+              ),
+            ),
+    );
+  }
+}

+ 109 - 0
lib/features/challenge/views/edit_challenge_screen.dart

@@ -0,0 +1,109 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:japp_flutter/core/models/challenge_model.dart';
+import 'package:japp_flutter/features/challenge/view_models/challenge_edit_vm.dart';
+
+class ChallengeEditScreen extends ConsumerWidget {
+  final ChallengeModel? initialChallenge;
+
+  const ChallengeEditScreen({
+    super.key,
+    this.initialChallenge,
+  });
+
+  @override
+  Widget build(BuildContext context, WidgetRef ref) {
+    final challengeState = ref.watch(challengeEditProvider);
+    final theme = Theme.of(context);
+
+    return Scaffold(
+      appBar: AppBar(
+        title: Text(initialChallenge == null ? '创建挑战' : '编辑挑战'),
+        actions: [
+          IconButton(
+            icon: const Icon(Icons.save),
+            onPressed: () async {
+              await ref.read(challengeEditProvider.notifier).saveChallenge();
+              if (context.mounted) Navigator.pop(context);
+            },
+          ),
+        ],
+      ),
+      body: challengeState.when(
+        loading: () => const Center(child: CircularProgressIndicator()),
+        error: (error, _) => Center(child: Text('错误: $error')),
+        data: (challenge) {
+          if (challenge == null) {
+            return const Center(child: Text('未初始化数据'));
+          }
+
+          return SingleChildScrollView(
+            padding: const EdgeInsets.all(16),
+            child: Column(
+              crossAxisAlignment: CrossAxisAlignment.start,
+              children: [
+                _buildTitleField(challenge, ref),
+                const SizedBox(height: 24),
+                _buildDifficultySelector(challenge, ref),
+                const SizedBox(height: 24),
+                _buildDescriptionField(challenge, ref),
+              ],
+            ),
+          );
+        },
+      ),
+    );
+  }
+
+  Widget _buildTitleField(ChallengeModel challenge, WidgetRef ref) {
+    return TextFormField(
+      initialValue: challenge.title,
+      decoration: const InputDecoration(
+        labelText: '挑战标题',
+        border: OutlineInputBorder(),
+      ),
+      onChanged: (value) => ref.read(challengeEditProvider.notifier).updateTitle(value),
+    );
+  }
+
+  Widget _buildDescriptionField(ChallengeModel challenge, WidgetRef ref) {
+    return TextFormField(
+      initialValue: challenge.description,
+      decoration: const InputDecoration(
+        labelText: '挑战描述',
+        border: OutlineInputBorder(),
+        alignLabelWithHint: true,
+      ),
+      maxLines: 5,
+      onChanged: (value) => ref.read(challengeEditProvider.notifier).updateDescription(value),
+    );
+  }
+
+  Widget _buildDifficultySelector(ChallengeModel challenge, WidgetRef ref) {
+    // const difficulties = ['简单', '中等', '困难'];
+
+    const difficulties = [1,2,3,4];
+    
+    return Column(
+      crossAxisAlignment: CrossAxisAlignment.start,
+      children: [
+        const Text('难度级别', style: TextStyle(fontSize: 16)),
+        const SizedBox(height: 8),
+        Wrap(
+          spacing: 8,
+          children: difficulties.map((level) {
+            return ChoiceChip(
+              label: Text(level.toString()),
+              selected: challenge.difficulty == level,
+              onSelected: (selected) {
+                if (selected) {
+                  ref.read(challengeEditProvider.notifier).updateDifficulty(level);
+                }
+              },
+            );
+          }).toList(),
+        ),
+      ],
+    );
+  }
+}

+ 359 - 0
lib/features/challenge/views/mock_page.dart

@@ -0,0 +1,359 @@
+import 'package:flutter/material.dart';
+import 'package:intl/intl.dart';
+import 'package:japp_flutter/core/constants/options.dart';
+import 'package:japp_flutter/features/widgets/app_dropdown.dart';
+
+
+class MockPage extends StatelessWidget {
+  const MockPage({super.key});
+
+  @override
+  Widget build(BuildContext context) {
+    return MaterialApp(
+      title: 'Flutter 表单示例',
+      theme: ThemeData(
+        primarySwatch: Colors.blue,
+      ),
+      home: const MyFormPage(),
+    );
+  }
+}
+
+// 表单数据模型
+class FormData {
+  String name;
+  String email;
+  String phone;
+  String password;
+  bool agreeTerms;
+  DateTime? startDate;
+  DateTime? endDate;
+  int? difficult;
+  FormData({
+    this.name = '',
+    this.email = 'fin@qq.com',
+    this.phone = '13770656091',
+    this.password = '',
+    this.agreeTerms = false,
+    this.startDate,
+    this.endDate,
+    this.difficult = 2,
+  });
+
+  // 转换为Map方便打印
+  Map<String, dynamic> toMap() {
+    return {
+      'name': name,
+      'email': email,
+      'phone': phone,
+      'password': password.replaceAll(RegExp(r'.'), '*'), // 密码用*号代替
+      'agreeTerms': agreeTerms,
+      'startDate': startDate != null ? DateFormat('yyyy-MM-dd').format(startDate!) : null,
+      'endDate': endDate != null ? DateFormat('yyyy-MM-dd').format(endDate!) : null,
+      'difficult':difficult
+    };
+  }
+}
+
+
+
+// 难度等级模型
+class DifficultyLevel {
+  final int value;
+  final String label;
+  final IconData? icon;
+
+  const DifficultyLevel({
+    required this.value,
+    required this.label,
+    this.icon,
+  });
+}
+
+// 预定义的难度等级
+const List<DifficultyLevel> difficultyLevels = [
+  DifficultyLevel(value: 1, label: '简单', icon: Icons.sentiment_very_satisfied),
+  DifficultyLevel(value: 2, label: '中等', icon: Icons.sentiment_satisfied),
+  DifficultyLevel(value: 3, label: '困难', icon: Icons.sentiment_dissatisfied),
+  DifficultyLevel(value: 4, label: '专家', icon: Icons.sentiment_very_dissatisfied),
+];
+
+
+class MyFormPage extends StatefulWidget {
+  const MyFormPage({super.key});
+
+  @override
+  State<MyFormPage> createState() => _MyFormPageState();
+}
+
+class _MyFormPageState extends State<MyFormPage> {
+  final _formKey = GlobalKey<FormState>();
+  final FormData _formData = FormData(
+      startDate: DateTime.now(), // 默认开始日期为今天
+      endDate: DateTime.now().add(const Duration(days: 7)), // 默认结束日期为7天后
+  );
+
+  // 选择开始日期
+  Future<void> _selectStartDate() async {
+    final DateTime? picked = await showDatePicker(
+      context: context,
+      initialDate: _formData.startDate ?? DateTime.now(),
+      firstDate: DateTime(2000),
+      lastDate: DateTime(2100),
+    );
+    if (picked != null && picked != _formData.startDate) {
+      setState(() {
+        _formData.startDate = picked;
+        // 如果结束日期早于开始日期,重置结束日期
+        if (_formData.endDate != null && _formData.endDate!.isBefore(picked)) {
+          _formData.endDate = null;
+        }
+      });
+    }
+  }
+
+  // 选择结束日期
+  Future<void> _selectEndDate() async {
+    final DateTime? picked = await showDatePicker(
+      context: context,
+      initialDate: _formData.endDate ?? (_formData.startDate ?? DateTime.now()),
+      firstDate: _formData.startDate ?? DateTime.now(),
+      lastDate: DateTime(2100),
+    );
+    if (picked != null && picked != _formData.endDate) {
+      setState(() {
+        _formData.endDate = picked;
+      });
+    }
+  }
+
+
+  // 提交表单
+  void _submitForm() {
+    if (_formKey.currentState!.validate()) {
+      _formKey.currentState!.save();
+      
+      // 获取完整的难度信息(不仅仅是value)
+      final selectedDifficulty = difficultyLevels.firstWhere(
+        (level) => level.value == _formData.difficult,
+        orElse: () => const DifficultyLevel(value: 0, label: '未知'),
+      );
+
+      print('表单提交数据: ${_formData.toMap()}');
+      
+      // 显示提交成功的提示
+      ScaffoldMessenger.of(context).showSnackBar(
+        const SnackBar(content: Text('表单提交成功! 数据已打印到控制台')),
+      );
+    }
+  }
+
+
+  @override
+  Widget build(BuildContext context) {
+    return Scaffold(
+      appBar: AppBar(
+        title: const Text('Flutter 表单示例'),
+      ),
+      body: Padding(
+        padding: const EdgeInsets.all(16.0),
+        child: Form(
+          key: _formKey,
+          child: ListView(
+            children: [
+              TextFormField(
+                decoration: const InputDecoration(
+                  labelText: '姓名',
+                  hintText: '请输入您的姓名',
+                  border: OutlineInputBorder(),
+                ),
+                validator: (value) {
+                  if (value == null || value.isEmpty) {
+                    return '请输入姓名';
+                  }
+                  return null;
+                },
+                onSaved: (value) => _formData.name = value!,
+              ),
+              const SizedBox(height: 16),
+              TextFormField(
+                initialValue: _formData.email,
+                decoration: const InputDecoration(
+                  labelText: '电子邮件',
+                  hintText: '请输入您的电子邮件',
+                  border: OutlineInputBorder(),
+                ),
+                keyboardType: TextInputType.emailAddress,
+                validator: (value) {
+                  if (value == null || value.isEmpty) {
+                    return '请输入电子邮件';
+                  }
+                  if (!RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$').hasMatch(value)) {
+                    return '请输入有效的电子邮件地址';
+                  }
+                  return null;
+                },
+                onSaved: (value) => _formData.email = value!,
+              ),
+              const SizedBox(height: 16),
+              TextFormField(
+                initialValue: _formData.phone,
+                decoration: const InputDecoration(
+                  labelText: '手机号码',
+                  hintText: '请输入您的手机号码',
+                  border: OutlineInputBorder(),
+                ),
+                keyboardType: TextInputType.phone,
+                validator: (value) {
+                  if (value == null || value.isEmpty) {
+                    return '请输入手机号码';
+                  }
+                  if (!RegExp(r'^[0-9]{11}$').hasMatch(value)) {
+                    return '请输入有效的11位手机号码';
+                  }
+                  return null;
+                },
+                onSaved: (value) => _formData.phone = value!,
+              ),
+              const SizedBox(height: 16),
+              TextFormField(
+                decoration: const InputDecoration(
+                  labelText: '密码',
+                  hintText: '请输入您的密码',
+                  border: OutlineInputBorder(),
+                ),
+                obscureText: true,
+                validator: (value) {
+                  if (value == null || value.isEmpty) {
+                    return '请输入密码';
+                  }
+                  if (value.length < 6) {
+                    return '密码长度不能少于6位';
+                  }
+                  return null;
+                },
+                onSaved: (value) => _formData.password = value!,
+              ),
+              const SizedBox(height: 16),
+              // 开始日期选择
+              InkWell(
+                onTap: _selectStartDate,
+                child: InputDecorator(
+                  decoration: const InputDecoration(
+                    labelText: '开始日期',
+                    border: OutlineInputBorder(),
+                  ),
+                  child: Row(
+                    mainAxisAlignment: MainAxisAlignment.spaceBetween,
+                    children: [
+                      Text(
+                        _formData.startDate != null
+                            ? DateFormat('yyyy-MM-dd').format(_formData.startDate!)
+                            : '请选择开始日期',
+                      ),
+                      const Icon(Icons.calendar_today, size: 20),
+                    ],
+                  ),
+                ),
+              ),
+              const SizedBox(height: 16),
+              // 结束日期选择
+              InkWell(
+                onTap: _formData.startDate == null
+                    ? null
+                    : _selectEndDate,
+                child: InputDecorator(
+                  decoration: const InputDecoration(
+                    labelText: '结束日期',
+                    border: OutlineInputBorder(),
+                  ),
+                  child: Row(
+                    mainAxisAlignment: MainAxisAlignment.spaceBetween,
+                    children: [
+                      Text(
+                        _formData.endDate != null
+                            ? DateFormat('yyyy-MM-dd').format(_formData.endDate!)
+                            : '请选择结束日期',
+                      ),
+                      const Icon(Icons.calendar_today, size: 20),
+                    ],
+                  ),
+                ),
+              ),
+              const SizedBox(height: 16),
+              // 难度选择下拉框
+              // DropdownButtonFormField<int>(
+              //   decoration: const InputDecoration(
+              //     labelText: '挑战难度',
+              //     border: OutlineInputBorder(),
+              //   ),
+              //   value: _formData.difficult,
+              //   items: difficultyLevels.map((level) {
+              //     return DropdownMenuItem<int>(
+              //       value: level.value,
+              //       child: Row(
+              //         children: [
+              //           if (level.icon != null) 
+              //             Icon(level.icon, size: 20),
+              //           const SizedBox(width: 8),
+              //           Text(level.label),
+              //         ],
+              //       ),
+              //     );
+              //   }).toList(),
+              //   onChanged: (value) {
+              //     setState(() {
+              //       _formData.difficult = value;
+              //     });
+              //   },
+              //   validator: (value) {
+              //     if (value == null) {
+              //       return '请选择难度等级';
+              //     }
+              //     return null;
+              //   },
+              //   onSaved: (value) => _formData.difficult = value,
+              // ),
+
+              AppDropdown(
+                label: '挑战难度', 
+                value: _formData.difficult, 
+                items: challengeDifficultyOpts, 
+                onChanged: (value) {
+                  setState(() {
+                    _formData.difficult = value;
+                  });
+                },
+              ),
+
+
+              const SizedBox(height: 16),
+
+              Row(
+                children: [
+                  Checkbox(
+                    value: _formData.agreeTerms,
+                    onChanged: (value) {
+                      setState(() {
+                        _formData.agreeTerms = value!;
+                      });
+                    },
+                  ),
+                  const Text('我同意用户协议'),
+                ],
+              ),
+              const SizedBox(height: 24),
+              ElevatedButton(
+                onPressed: _submitForm,
+                style: ElevatedButton.styleFrom(
+                  padding: const EdgeInsets.symmetric(vertical: 16),
+                ),
+                child: const Text('提交表单'),
+              ),
+            ],
+          ),
+        ),
+      ),
+    );
+  }
+}

+ 37 - 0
lib/features/challenge/views/widgets/challenge_list_card_widget.dart

@@ -0,0 +1,37 @@
+import 'package:flutter/material.dart';
+import 'package:japp_flutter/core/models/challenge_model.dart';
+
+class ChallengeListCard extends StatelessWidget {
+  final ChallengeModel challenge;
+
+  const ChallengeListCard({super.key, required this.challenge});
+  @override
+  Widget build(BuildContext context) {
+    final theme = Theme.of(context);
+
+    return Card(
+      elevation: 2,
+      shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
+      margin: const EdgeInsets.only(bottom: 16),
+      child: Padding(
+        padding: const EdgeInsets.all(16),
+        child: Column(
+          crossAxisAlignment: CrossAxisAlignment.start,
+          children: [
+            Text(
+              challenge.title,
+              style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
+            ),
+            const SizedBox(height: 16),
+
+            Text(
+              challenge.description,
+              style: TextStyle(color: Colors.grey[700], fontSize: 14),
+            ),
+            const SizedBox(height: 16),
+          ],
+        ),
+      ),
+    );
+  }
+}

+ 69 - 0
lib/features/widgets/app_dropdown.dart

@@ -0,0 +1,69 @@
+import 'package:flutter/material.dart';
+import 'package:japp_flutter/core/models/dropdown_item.dart';
+
+class AppDropdown<T> extends StatelessWidget {
+  final String label;
+  final T? value;
+  final List<DropdownItem<T>> items; // 改为接收通用列表
+  final ValueChanged<T?> onChanged;
+  final FormFieldValidator<T>? validator;
+  final String? hintText;
+  final bool isExpanded;
+  final Widget? prefixIcon;
+  final InputBorder? border;
+  final TextStyle? textStyle;
+  final double? iconSize;
+  final Color? iconColor;
+
+  const AppDropdown({
+    super.key,
+    required this.label,
+    required this.value,
+    required this.items,
+    required this.onChanged,
+    this.validator,
+    this.hintText,
+    this.isExpanded = true,
+    this.prefixIcon,
+    this.border,
+    this.textStyle,
+    this.iconSize = 20.0,
+    this.iconColor,
+  });
+
+  @override
+  Widget build(BuildContext context) {
+    return DropdownButtonFormField<T>(
+      decoration: InputDecoration(
+        labelText: label,
+        hintText: hintText,
+        border: border ?? const OutlineInputBorder(),
+        prefixIcon: prefixIcon,
+      ),
+      value: value,
+      items: items.map((item) {
+        return DropdownMenuItem<T>(
+          value: item.value,
+          child: Row(
+            children: [
+              if (item.icon != null)
+                Icon(
+                  item.icon,
+                  size: iconSize,
+                  color: item.iconColor ?? iconColor,
+                ),
+              if (item.icon != null) const SizedBox(width: 8),
+              Text(
+                item.label,
+                style: textStyle ?? Theme.of(context).textTheme.bodyMedium,
+              ),
+            ],
+          ),
+        );
+      }).toList(),
+      onChanged: onChanged,
+      validator: validator,
+      isExpanded: isExpanded,
+    );
+  }
+}

+ 11 - 110
lib/main.dart

@@ -1,122 +1,23 @@
 import 'package:flutter/material.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:japp_flutter/core/routes/app_router.dart';
 
-void main() {
-  runApp(const MyApp());
-}
+void main() => runApp(ProviderScope(child: const ChallengeApp()));
 
-class MyApp extends StatelessWidget {
-  const MyApp({super.key});
+class ChallengeApp extends StatelessWidget {
+  const ChallengeApp({super.key});
 
-  // This widget is the root of your application.
   @override
   Widget build(BuildContext context) {
     return MaterialApp(
-      title: 'Flutter Demo',
+      title: '挑战列表',
+      debugShowCheckedModeBanner: false,
       theme: ThemeData(
-        // This is the theme of your application.
-        //
-        // TRY THIS: Try running your application with "flutter run". You'll see
-        // the application has a purple toolbar. Then, without quitting the app,
-        // try changing the seedColor in the colorScheme below to Colors.green
-        // and then invoke "hot reload" (save your changes or press the "hot
-        // reload" button in a Flutter-supported IDE, or press "r" if you used
-        // the command line to start the app).
-        //
-        // Notice that the counter didn't reset back to zero; the application
-        // state is not lost during the reload. To reset the state, use hot
-        // restart instead.
-        //
-        // This works for code too, not just values: Most code changes can be
-        // tested with just a hot reload.
-        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
-      ),
-      home: const MyHomePage(title: 'Flutter Demo Home Page'),
-    );
-  }
-}
-
-class MyHomePage extends StatefulWidget {
-  const MyHomePage({super.key, required this.title});
-
-  // This widget is the home page of your application. It is stateful, meaning
-  // that it has a State object (defined below) that contains fields that affect
-  // how it looks.
-
-  // This class is the configuration for the state. It holds the values (in this
-  // case the title) provided by the parent (in this case the App widget) and
-  // used by the build method of the State. Fields in a Widget subclass are
-  // always marked "final".
-
-  final String title;
-
-  @override
-  State<MyHomePage> createState() => _MyHomePageState();
-}
-
-class _MyHomePageState extends State<MyHomePage> {
-  int _counter = 0;
-
-  void _incrementCounter() {
-    setState(() {
-      // This call to setState tells the Flutter framework that something has
-      // changed in this State, which causes it to rerun the build method below
-      // so that the display can reflect the updated values. If we changed
-      // _counter without calling setState(), then the build method would not be
-      // called again, and so nothing would appear to happen.
-      _counter++;
-    });
-  }
-
-  @override
-  Widget build(BuildContext context) {
-    // This method is rerun every time setState is called, for instance as done
-    // by the _incrementCounter method above.
-    //
-    // The Flutter framework has been optimized to make rerunning build methods
-    // fast, so that you can just rebuild anything that needs updating rather
-    // than having to individually change instances of widgets.
-    return Scaffold(
-      appBar: AppBar(
-        // TRY THIS: Try changing the color here to a specific color (to
-        // Colors.amber, perhaps?) and trigger a hot reload to see the AppBar
-        // change color while the other colors stay the same.
-        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
-        // Here we take the value from the MyHomePage object that was created by
-        // the App.build method, and use it to set our appbar title.
-        title: Text(widget.title),
-      ),
-      body: Center(
-        // Center is a layout widget. It takes a single child and positions it
-        // in the middle of the parent.
-        child: Column(
-          // Column is also a layout widget. It takes a list of children and
-          // arranges them vertically. By default, it sizes itself to fit its
-          // children horizontally, and tries to be as tall as its parent.
-          //
-          // Column has various properties to control how it sizes itself and
-          // how it positions its children. Here we use mainAxisAlignment to
-          // center the children vertically; the main axis here is the vertical
-          // axis because Columns are vertical (the cross axis would be
-          // horizontal).
-          //
-          // TRY THIS: Invoke "debug painting" (choose the "Toggle Debug Paint"
-          // action in the IDE, or press "p" in the console), to see the
-          // wireframe for each widget.
-          mainAxisAlignment: MainAxisAlignment.center,
-          children: <Widget>[
-            const Text('You have pushed the button this many times:'),
-            Text(
-              '$_counter',
-              style: Theme.of(context).textTheme.headlineMedium,
-            ),
-          ],
-        ),
+        primarySwatch: Colors.blue,
+        appBarTheme: const AppBarTheme(elevation: 1),
       ),
-      floatingActionButton: FloatingActionButton(
-        onPressed: _incrementCounter,
-        tooltip: 'Increment',
-        child: const Icon(Icons.add),
-      ), // This trailing comma makes auto-formatting nicer for build methods.
+      initialRoute: AppRouter.mockPage,
+      onGenerateRoute: AppRouter.generateRoute,
     );
   }
 }

+ 2 - 0
macos/Flutter/GeneratedPluginRegistrant.swift

@@ -5,6 +5,8 @@
 import FlutterMacOS
 import Foundation
 
+import sqflite_darwin
 
 func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
+  SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
 }

+ 169 - 1
pubspec.lock

@@ -49,6 +49,22 @@ packages:
       url: "https://pub.flutter-io.cn"
     source: hosted
     version: "1.0.8"
+  dio:
+    dependency: "direct main"
+    description:
+      name: dio
+      sha256: "253a18bbd4851fecba42f7343a1df3a9a4c1d31a2c1b37e221086b4fa8c8dbc9"
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "5.8.0+1"
+  dio_web_adapter:
+    dependency: transitive
+    description:
+      name: dio_web_adapter
+      sha256: "7586e476d70caecaf1686d21eee7247ea43ef5c345eab9e0cc3583ff13378d78"
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "2.1.1"
   fake_async:
     dependency: transitive
     description:
@@ -62,6 +78,14 @@ packages:
     description: flutter
     source: sdk
     version: "0.0.0"
+  flutter_hooks:
+    dependency: "direct main"
+    description:
+      name: flutter_hooks
+      sha256: b772e710d16d7a20c0740c4f855095026b31c7eb5ba3ab67d2bd52021cd9461d
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "0.21.2"
   flutter_lints:
     dependency: "direct dev"
     description:
@@ -70,11 +94,35 @@ packages:
       url: "https://pub.flutter-io.cn"
     source: hosted
     version: "5.0.0"
+  flutter_riverpod:
+    dependency: "direct main"
+    description:
+      name: flutter_riverpod
+      sha256: "9532ee6db4a943a1ed8383072a2e3eeda041db5657cdf6d2acecf3c21ecbe7e1"
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "2.6.1"
   flutter_test:
     dependency: "direct dev"
     description: flutter
     source: sdk
     version: "0.0.0"
+  http_parser:
+    dependency: transitive
+    description:
+      name: http_parser
+      sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571"
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "4.1.2"
+  intl:
+    dependency: "direct main"
+    description:
+      name: intl
+      sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5"
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "0.20.2"
   leak_tracker:
     dependency: transitive
     description:
@@ -131,6 +179,14 @@ packages:
       url: "https://pub.flutter-io.cn"
     source: hosted
     version: "1.16.0"
+  nested:
+    dependency: transitive
+    description:
+      name: nested
+      sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20"
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "1.0.0"
   path:
     dependency: transitive
     description:
@@ -139,6 +195,46 @@ packages:
       url: "https://pub.flutter-io.cn"
     source: hosted
     version: "1.9.1"
+  platform:
+    dependency: transitive
+    description:
+      name: platform
+      sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984"
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "3.1.6"
+  plugin_platform_interface:
+    dependency: transitive
+    description:
+      name: plugin_platform_interface
+      sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "2.1.8"
+  provider:
+    dependency: "direct main"
+    description:
+      name: provider
+      sha256: "4abbd070a04e9ddc287673bf5a030c7ca8b685ff70218720abab8b092f53dd84"
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "6.1.5"
+  riverpod:
+    dependency: transitive
+    description:
+      name: riverpod
+      sha256: "59062512288d3056b2321804332a13ffdd1bf16df70dcc8e506e411280a72959"
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "2.6.1"
+  riverpod_annotation:
+    dependency: "direct main"
+    description:
+      name: riverpod_annotation
+      sha256: e14b0bf45b71326654e2705d462f21b958f987087be850afd60578fcd502d1b8
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "2.6.1"
   sky_engine:
     dependency: transitive
     description: flutter
@@ -152,6 +248,46 @@ packages:
       url: "https://pub.flutter-io.cn"
     source: hosted
     version: "1.10.1"
+  sqflite:
+    dependency: "direct main"
+    description:
+      name: sqflite
+      sha256: e2297b1da52f127bc7a3da11439985d9b536f75070f3325e62ada69a5c585d03
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "2.4.2"
+  sqflite_android:
+    dependency: transitive
+    description:
+      name: sqflite_android
+      sha256: "2b3070c5fa881839f8b402ee4a39c1b4d561704d4ebbbcfb808a119bc2a1701b"
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "2.4.1"
+  sqflite_common:
+    dependency: transitive
+    description:
+      name: sqflite_common
+      sha256: "84731e8bfd8303a3389903e01fb2141b6e59b5973cacbb0929021df08dddbe8b"
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "2.5.5"
+  sqflite_darwin:
+    dependency: transitive
+    description:
+      name: sqflite_darwin
+      sha256: "279832e5cde3fe99e8571879498c9211f3ca6391b0d818df4e17d9fff5c6ccb3"
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "2.4.2"
+  sqflite_platform_interface:
+    dependency: transitive
+    description:
+      name: sqflite_platform_interface
+      sha256: "8dd4515c7bdcae0a785b0062859336de775e8c65db81ae33dd5445f35be61920"
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "2.4.0"
   stack_trace:
     dependency: transitive
     description:
@@ -160,6 +296,14 @@ packages:
       url: "https://pub.flutter-io.cn"
     source: hosted
     version: "1.12.1"
+  state_notifier:
+    dependency: transitive
+    description:
+      name: state_notifier
+      sha256: b8677376aa54f2d7c58280d5a007f9e8774f1968d1fb1c096adcb4792fba29bb
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "1.0.0"
   stream_channel:
     dependency: transitive
     description:
@@ -176,6 +320,14 @@ packages:
       url: "https://pub.flutter-io.cn"
     source: hosted
     version: "1.4.1"
+  synchronized:
+    dependency: transitive
+    description:
+      name: synchronized
+      sha256: c254ade258ec8282947a0acbbc90b9575b4f19673533ee46f2f6e9b3aeefd7c0
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "3.4.0"
   term_glyph:
     dependency: transitive
     description:
@@ -192,6 +344,14 @@ packages:
       url: "https://pub.flutter-io.cn"
     source: hosted
     version: "0.7.4"
+  typed_data:
+    dependency: transitive
+    description:
+      name: typed_data
+      sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "1.4.0"
   vector_math:
     dependency: transitive
     description:
@@ -208,6 +368,14 @@ packages:
       url: "https://pub.flutter-io.cn"
     source: hosted
     version: "15.0.0"
+  web:
+    dependency: transitive
+    description:
+      name: web
+      sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a"
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "1.1.1"
 sdks:
   dart: ">=3.8.1 <4.0.0"
-  flutter: ">=3.18.0-18.0.pre.54"
+  flutter: ">=3.24.0"

+ 8 - 1
pubspec.yaml

@@ -34,7 +34,14 @@ dependencies:
   # The following adds the Cupertino Icons font to your application.
   # Use with the CupertinoIcons class for iOS style icons.
   cupertino_icons: ^1.0.8
-
+  intl: ^0.20.2
+  provider: ^6.0.0 # 核心状态管理
+  dio: ^5.0.0       # 网络请求
+  sqflite: ^2.4.2
+  flutter_hooks: ^0.21.2
+  flutter_riverpod: ^2.6.1
+  riverpod_annotation: ^2.6.1
+  
 dev_dependencies:
   flutter_test:
     sdk: flutter

+ 0 - 30
test/widget_test.dart

@@ -1,30 +0,0 @@
-// This is a basic Flutter widget test.
-//
-// To perform an interaction with a widget in your test, use the WidgetTester
-// utility in the flutter_test package. For example, you can send tap and scroll
-// gestures. You can also use WidgetTester to find child widgets in the widget
-// tree, read text, and verify that the values of widget properties are correct.
-
-import 'package:flutter/material.dart';
-import 'package:flutter_test/flutter_test.dart';
-
-import 'package:japp_flutter/main.dart';
-
-void main() {
-  testWidgets('Counter increments smoke test', (WidgetTester tester) async {
-    // Build our app and trigger a frame.
-    await tester.pumpWidget(const MyApp());
-
-    // Verify that our counter starts at 0.
-    expect(find.text('0'), findsOneWidget);
-    expect(find.text('1'), findsNothing);
-
-    // Tap the '+' icon and trigger a frame.
-    await tester.tap(find.byIcon(Icons.add));
-    await tester.pump();
-
-    // Verify that our counter has incremented.
-    expect(find.text('0'), findsNothing);
-    expect(find.text('1'), findsOneWidget);
-  });
-}