Kaynağa Gözat

rename
friends api
some models

ignalxy 4 yıl önce
ebeveyn
işleme
b039e1e4f4

+ 16 - 4
lib/common/api.dart

@@ -118,9 +118,11 @@ class Api {
 
   Future<bool> addFriend(String username) async {
     debug("add friend begin");
+    bool _result = true;
     try {
       await dio.post('/friends/add/', data: FormData.fromMap({"username": username}));
     } on DioError catch (e) {
+      _result = false;
       debug("add friend failed: ${ () {
         switch (e.response?.statusCode) {
           case null: return "unknown error";
@@ -128,10 +130,22 @@ class Api {
           case 422:case 400: return "failed";
         }
       } ()}");
-      return false;
     }
     debug("add friend end");
-    return true;
+    return _result;
+  }
+
+  Future<bool> acceptFriend(String username) async {
+    debug("accept friend begin");
+    bool _result = true;
+    try {
+      await dio.post('/friends/accept/', data: FormData.fromMap({"username": username}));
+    } on DioError catch (e) {
+      debug("accept friend failed");
+      _result = false;
+    }
+    debug("accept friend end");
+    return _result;
   }
 
   Future<List<String>?> friendRequest() async {
@@ -164,6 +178,4 @@ class Api {
     debug("delete friend end");
     return _result;
   }
-
-
 }

+ 3 - 2
lib/common/global.dart

@@ -2,14 +2,15 @@
 import 'dart:convert';
 
 import 'package:dio/dio.dart';
-import 'package:e2ee_chat/entities/profile.dart';
 import 'package:e2ee_chat/common/api.dart';
-import 'package:e2ee_chat/models/login_model.dart';
+import 'package:e2ee_chat/model/profile.dart';
 import 'package:e2ee_chat/objectbox.g.dart';
 import 'package:flutter/material.dart';
 import 'package:objectbox/objectbox.dart';
 import 'package:permission_handler/permission_handler.dart';
 
+import '../presenter/login.dart';
+
 const _themes = <MaterialColor>[
   Colors.blue,
   Colors.cyan,

+ 0 - 13
lib/entities/friend.dart

@@ -1,13 +0,0 @@
-import 'package:objectbox/objectbox.dart';
-
-import 'message.dart';
-import 'user.dart';
-
-@Entity()
-class Friend {
-  int id = 0;
-  final user1 = ToOne<User>();
-  final user2 = ToOne<User>();
-  final messages = ToMany<Message>();
-
-}

+ 0 - 13
lib/entities/message.dart

@@ -1,13 +0,0 @@
-import 'package:objectbox/objectbox.dart';
-
-import 'friend.dart';
-import 'user.dart';
-
-@Entity()
-class Message {
-  int id = 0;
-  final from = ToOne<User>();
-  final to = ToOne<User>();
-  final String text = "";
-
-}

+ 0 - 12
lib/entities/user.dart

@@ -1,12 +0,0 @@
-import 'package:objectbox/objectbox.dart';
-
-import 'friend.dart';
-
-@Entity()
-class User {
-  User({required this.id, required this.username});
-  int id;
-  @Unique()
-  final String username;
-  final friends = ToMany<Friend>();
-}

+ 8 - 8
lib/main.dart

@@ -1,5 +1,4 @@
 import 'package:e2ee_chat/objectbox.g.dart';
-import 'package:e2ee_chat/routes/home.dart';
 import 'package:flutter/material.dart';
 import 'package:provider/provider.dart';
 import 'package:provider/single_child_widget.dart';
@@ -7,12 +6,13 @@ import 'package:flutter_localizations/flutter_localizations.dart';
 
 import 'common/global.dart';
 import 'l10n/localization_intl.dart';
-import 'models/locale_model.dart';
-import 'models/theme_model.dart';
-import 'models/login_model.dart';
-import 'routes/language_route.dart';
-import 'routes/login.dart';
-import 'routes/theme_change.dart';
+import 'presenter/locale.dart';
+import 'presenter/theme_model.dart';
+import 'presenter/login.dart';
+import 'view/language.dart';
+import 'view/login.dart';
+import 'view/theme.dart';
+import 'view/home.dart';
 
 void main() {
   WidgetsFlutterBinding.ensureInitialized();
@@ -66,7 +66,7 @@ class MyApp extends StatelessWidget {
             // 注册命名路由表
             routes: <String, WidgetBuilder>{
               "login": (context) => LoginRoute(),
-              "themes": (context) => ThemeChangeRoute(),
+              "themes": (context) => ThemeRoute(),
               "language": (context) => LanguageRoute(),
             },
           );

+ 0 - 0
lib/entities/content.dart → lib/model/content.dart


+ 12 - 0
lib/model/group.dart

@@ -0,0 +1,12 @@
+import 'package:objectbox/objectbox.dart';
+
+import 'message.dart';
+import 'user.dart';
+
+@Entity()
+class Group {
+  int id = 0;
+  final name = "";
+  final members = ToMany<User>();
+  final messages = ToMany<GroupMessage>();
+}

+ 25 - 0
lib/model/message.dart

@@ -0,0 +1,25 @@
+import 'package:e2ee_chat/model/content.dart';
+import 'package:objectbox/objectbox.dart';
+
+import 'group.dart';
+import 'user.dart';
+
+@Entity()
+class Message {
+
+  int id = 0;
+  final from = ToOne<User>();
+  final to = ToOne<User>();
+  final content = ToOne<Content>();
+  int time = 0;
+}
+
+@Entity()
+class GroupMessage {
+  int id = 0;
+  final from = ToOne<User>();
+  final to = ToOne<Group>();
+  final content = ToOne<Content>();
+  int time = 0;
+}
+

+ 6 - 5
lib/entities/profile.dart → lib/model/profile.dart

@@ -4,23 +4,24 @@ import 'package:flutter_secure_storage/flutter_secure_storage.dart';
 import 'package:e2ee_chat/common/global.dart';
 import 'package:objectbox/objectbox.dart';
 
+import 'user.dart';
+
 
 @Entity()
 class Profile {
-  Profile();
-
   int id = 0;
-
   int theme = Global.themes[0].value;
-  String? username;
+  User? user;
   bool isLogin = false;
   bool isLogout = false;
   Locale? locale;
 
+  @Transient()
+  String? get username => user?.username;
 
   @Transient()
   Future<String?> get token async {
-    if (username == null) return null;
+    if (user == null) return null;
     final storage = FlutterSecureStorage();
     return await storage.read(key: _tokenKey);
   }

+ 17 - 0
lib/model/user.dart

@@ -0,0 +1,17 @@
+import 'package:objectbox/objectbox.dart';
+
+import 'message.dart';
+
+@Entity()
+class User {
+  User(this.username);
+  int id = 0;
+  @Unique()
+  final String username;
+  final friends = ToMany<User>();
+  final messages = ToMany<Message>();
+  final groupMessages = ToMany<GroupMessage>();
+  bool isDND = false;
+  bool isStick = false;
+  bool isSpecialAttention = false;
+}

+ 0 - 78
lib/models/message_model.dart

@@ -1,78 +0,0 @@
-import 'package:e2ee_chat/common/global.dart';
-import 'package:flutter/cupertino.dart';
-
-enum DTMessageType {
-  text,
-  image
-}
-
-class MessageListModel extends ChangeNotifier {
-  List items = <MessageItemModel>[];
-
-  int get count => items.length;
-}
-
-class MessageItemModel {
-  MessageItemModel({required this.chatId,
-    required this.userId,
-    required this.userName,
-    required this.chatName,
-    required this.message,
-    required this.messageType,
-    required this.time,
-    this.unReadCount = 0,
-    this.isSingle = false,
-    this.avatar,
-    this.isGroup = false,
-    this.isDisturbing = false,
-    this.isSpecialAttention = false,
-    this.isAtYou = false,
-    this.isAtAll = false,
-    this.isStick = false});
-
-  /// 聊天 Id
-  String chatId;
-
-  /// 用户名称
-  String userName;
-
-  /// 用户Id
-  String userId;
-
-  /// 聊天名称
-  String chatName;
-
-  /// 消息体
-  String message;
-
-  /// message type
-  DTMessageType messageType;
-
-  /// 时间
-  int time;
-
-  /// 未读数量
-  int unReadCount;
-
-  /// 单聊
-  bool isSingle;
-  Image? avatar;
-
-  /// 群聊信息
-  bool isGroup;
-
-  /// 消息免打扰
-  bool isDisturbing;
-
-  /// 是否为置顶
-  bool isStick;
-
-  /// 特别关注
-  bool isSpecialAttention;
-
-  /// 是否 @ 你
-  bool isAtYou;
-
-  /// 是否 @ 全部
-  bool isAtAll;
-}

+ 127 - 52
lib/objectbox-model.json

@@ -19,11 +19,6 @@
           "name": "theme",
           "type": 6
         },
-        {
-          "id": "3:7647214962273172849",
-          "name": "username",
-          "type": 9
-        },
         {
           "id": "4:5923665807684456265",
           "name": "isLogin",
@@ -51,45 +46,9 @@
       ],
       "relations": []
     },
-    {
-      "id": "4:5044745765388820377",
-      "lastPropertyId": "3:214180290351070734",
-      "name": "Friend",
-      "properties": [
-        {
-          "id": "1:5034484793468406763",
-          "name": "id",
-          "type": 6,
-          "flags": 1
-        },
-        {
-          "id": "2:754684519442805673",
-          "name": "user1Id",
-          "type": 11,
-          "flags": 520,
-          "indexId": "2:3790366672903506902",
-          "relationTarget": "User"
-        },
-        {
-          "id": "3:214180290351070734",
-          "name": "user2Id",
-          "type": 11,
-          "flags": 520,
-          "indexId": "3:1460593015267560936",
-          "relationTarget": "User"
-        }
-      ],
-      "relations": [
-        {
-          "id": "1:5078486750954543598",
-          "name": "messages",
-          "targetId": "5:8880211362757189177"
-        }
-      ]
-    },
     {
       "id": "5:8880211362757189177",
-      "lastPropertyId": "3:7721473969164711791",
+      "lastPropertyId": "6:4388824818686674479",
       "name": "Message",
       "properties": [
         {
@@ -113,13 +72,26 @@
           "flags": 520,
           "indexId": "5:5329740598468773345",
           "relationTarget": "User"
+        },
+        {
+          "id": "5:634385368395524800",
+          "name": "time",
+          "type": 6
+        },
+        {
+          "id": "6:4388824818686674479",
+          "name": "contentId",
+          "type": 11,
+          "flags": 520,
+          "indexId": "10:205849996938840771",
+          "relationTarget": "Content"
         }
       ],
       "relations": []
     },
     {
       "id": "6:6066676571331973763",
-      "lastPropertyId": "2:2230815448876041069",
+      "lastPropertyId": "6:3504407138147737485",
       "name": "User",
       "properties": [
         {
@@ -129,37 +101,140 @@
           "flags": 1
         },
         {
-          "id": "2:2230815448876041069",
+          "id": "3:2365120802531976993",
           "name": "username",
           "type": 9,
           "flags": 2080,
-          "indexId": "6:2181766053148811865"
+          "indexId": "9:7432421428375491492"
+        },
+        {
+          "id": "4:3332680204867820806",
+          "name": "isDND",
+          "type": 1
+        },
+        {
+          "id": "5:2234651531374642771",
+          "name": "isStick",
+          "type": 1
+        },
+        {
+          "id": "6:3504407138147737485",
+          "name": "isSpecialAttention",
+          "type": 1
         }
       ],
       "relations": [
         {
           "id": "2:3118435353931894829",
           "name": "friends",
-          "targetId": "4:5044745765388820377"
+          "targetId": "6:6066676571331973763"
+        },
+        {
+          "id": "5:8362659550242546106",
+          "name": "messages",
+          "targetId": "5:8880211362757189177"
+        },
+        {
+          "id": "6:6570620621500888428",
+          "name": "groupMessages",
+          "targetId": "8:8842794649328270120"
+        }
+      ]
+    },
+    {
+      "id": "7:1497196184348190204",
+      "lastPropertyId": "1:8442786984080711776",
+      "name": "Group",
+      "properties": [
+        {
+          "id": "1:8442786984080711776",
+          "name": "id",
+          "type": 6,
+          "flags": 1
+        }
+      ],
+      "relations": [
+        {
+          "id": "3:7924032496731598926",
+          "name": "members",
+          "targetId": "6:6066676571331973763"
+        },
+        {
+          "id": "4:1657013690878572102",
+          "name": "messages",
+          "targetId": "8:8842794649328270120"
         }
       ]
+    },
+    {
+      "id": "8:8842794649328270120",
+      "lastPropertyId": "6:5148143939128616318",
+      "name": "GroupMessage",
+      "properties": [
+        {
+          "id": "1:8727865543427007275",
+          "name": "id",
+          "type": 6,
+          "flags": 1
+        },
+        {
+          "id": "2:5514462395021006262",
+          "name": "fromId",
+          "type": 11,
+          "flags": 520,
+          "indexId": "7:5268783855356873237",
+          "relationTarget": "User"
+        },
+        {
+          "id": "3:1148609827653786396",
+          "name": "toId",
+          "type": 11,
+          "flags": 520,
+          "indexId": "8:7769527806721188121",
+          "relationTarget": "Group"
+        },
+        {
+          "id": "5:7501600871221400279",
+          "name": "time",
+          "type": 6
+        },
+        {
+          "id": "6:5148143939128616318",
+          "name": "contentId",
+          "type": 11,
+          "flags": 520,
+          "indexId": "11:6309601695170211538",
+          "relationTarget": "Content"
+        }
+      ],
+      "relations": []
     }
   ],
-  "lastEntityId": "6:6066676571331973763",
-  "lastIndexId": "6:2181766053148811865",
-  "lastRelationId": "2:3118435353931894829",
+  "lastEntityId": "8:8842794649328270120",
+  "lastIndexId": "11:6309601695170211538",
+  "lastRelationId": "6:6570620621500888428",
   "lastSequenceId": "0:0",
   "modelVersion": 5,
   "modelVersionParserMinimum": 5,
   "retiredEntityUids": [
-    3444477729893015694
+    3444477729893015694,
+    5044745765388820377
+  ],
+  "retiredIndexUids": [
+    2181766053148811865
   ],
-  "retiredIndexUids": [],
   "retiredPropertyUids": [
     5463014948149082651,
     2305308568754167021,
     649621747167423523,
-    4818206049188692305
+    4818206049188692305,
+    2230815448876041069,
+    5034484793468406763,
+    754684519442805673,
+    214180290351070734,
+    7647214962273172849,
+    6701330857882848509,
+    2182219019195059976
   ],
   "retiredRelationUids": [],
   "version": 1

+ 294 - 136
lib/objectbox.g.dart

@@ -9,11 +9,11 @@ import 'package:objectbox/internal.dart'; // generated code can access "internal
 import 'package:objectbox/objectbox.dart';
 import 'package:objectbox_flutter_libs/objectbox_flutter_libs.dart';
 
-import 'entities/content.dart';
-import 'entities/friend.dart';
-import 'entities/message.dart';
-import 'entities/profile.dart';
-import 'entities/user.dart';
+import 'model/content.dart';
+import 'model/group.dart';
+import 'model/message.dart';
+import 'model/profile.dart';
+import 'model/user.dart';
 
 export 'package:objectbox/objectbox.dart'; // so that callers only have to import this file
 
@@ -34,11 +34,6 @@ final _entities = <ModelEntity>[
             name: 'theme',
             type: 6,
             flags: 0),
-        ModelProperty(
-            id: const IdUid(3, 7647214962273172849),
-            name: 'username',
-            type: 9,
-            flags: 0),
         ModelProperty(
             id: const IdUid(4, 5923665807684456265),
             name: 'isLogin',
@@ -66,43 +61,10 @@ final _entities = <ModelEntity>[
       ],
       relations: <ModelRelation>[],
       backlinks: <ModelBacklink>[]),
-  ModelEntity(
-      id: const IdUid(4, 5044745765388820377),
-      name: 'Friend',
-      lastPropertyId: const IdUid(3, 214180290351070734),
-      flags: 0,
-      properties: <ModelProperty>[
-        ModelProperty(
-            id: const IdUid(1, 5034484793468406763),
-            name: 'id',
-            type: 6,
-            flags: 1),
-        ModelProperty(
-            id: const IdUid(2, 754684519442805673),
-            name: 'user1Id',
-            type: 11,
-            flags: 520,
-            indexId: const IdUid(2, 3790366672903506902),
-            relationTarget: 'User'),
-        ModelProperty(
-            id: const IdUid(3, 214180290351070734),
-            name: 'user2Id',
-            type: 11,
-            flags: 520,
-            indexId: const IdUid(3, 1460593015267560936),
-            relationTarget: 'User')
-      ],
-      relations: <ModelRelation>[
-        ModelRelation(
-            id: const IdUid(1, 5078486750954543598),
-            name: 'messages',
-            targetId: const IdUid(5, 8880211362757189177))
-      ],
-      backlinks: <ModelBacklink>[]),
   ModelEntity(
       id: const IdUid(5, 8880211362757189177),
       name: 'Message',
-      lastPropertyId: const IdUid(3, 7721473969164711791),
+      lastPropertyId: const IdUid(6, 4388824818686674479),
       flags: 0,
       properties: <ModelProperty>[
         ModelProperty(
@@ -123,14 +85,26 @@ final _entities = <ModelEntity>[
             type: 11,
             flags: 520,
             indexId: const IdUid(5, 5329740598468773345),
-            relationTarget: 'User')
+            relationTarget: 'User'),
+        ModelProperty(
+            id: const IdUid(5, 634385368395524800),
+            name: 'time',
+            type: 6,
+            flags: 0),
+        ModelProperty(
+            id: const IdUid(6, 4388824818686674479),
+            name: 'contentId',
+            type: 11,
+            flags: 520,
+            indexId: const IdUid(10, 205849996938840771),
+            relationTarget: 'Content')
       ],
       relations: <ModelRelation>[],
       backlinks: <ModelBacklink>[]),
   ModelEntity(
       id: const IdUid(6, 6066676571331973763),
       name: 'User',
-      lastPropertyId: const IdUid(2, 2230815448876041069),
+      lastPropertyId: const IdUid(6, 3504407138147737485),
       flags: 0,
       properties: <ModelProperty>[
         ModelProperty(
@@ -139,18 +113,104 @@ final _entities = <ModelEntity>[
             type: 6,
             flags: 1),
         ModelProperty(
-            id: const IdUid(2, 2230815448876041069),
+            id: const IdUid(3, 2365120802531976993),
             name: 'username',
             type: 9,
             flags: 2080,
-            indexId: const IdUid(6, 2181766053148811865))
+            indexId: const IdUid(9, 7432421428375491492)),
+        ModelProperty(
+            id: const IdUid(4, 3332680204867820806),
+            name: 'isDND',
+            type: 1,
+            flags: 0),
+        ModelProperty(
+            id: const IdUid(5, 2234651531374642771),
+            name: 'isStick',
+            type: 1,
+            flags: 0),
+        ModelProperty(
+            id: const IdUid(6, 3504407138147737485),
+            name: 'isSpecialAttention',
+            type: 1,
+            flags: 0)
       ],
       relations: <ModelRelation>[
         ModelRelation(
             id: const IdUid(2, 3118435353931894829),
             name: 'friends',
-            targetId: const IdUid(4, 5044745765388820377))
+            targetId: const IdUid(6, 6066676571331973763)),
+        ModelRelation(
+            id: const IdUid(5, 8362659550242546106),
+            name: 'messages',
+            targetId: const IdUid(5, 8880211362757189177)),
+        ModelRelation(
+            id: const IdUid(6, 6570620621500888428),
+            name: 'groupMessages',
+            targetId: const IdUid(8, 8842794649328270120))
       ],
+      backlinks: <ModelBacklink>[]),
+  ModelEntity(
+      id: const IdUid(7, 1497196184348190204),
+      name: 'Group',
+      lastPropertyId: const IdUid(1, 8442786984080711776),
+      flags: 0,
+      properties: <ModelProperty>[
+        ModelProperty(
+            id: const IdUid(1, 8442786984080711776),
+            name: 'id',
+            type: 6,
+            flags: 1)
+      ],
+      relations: <ModelRelation>[
+        ModelRelation(
+            id: const IdUid(3, 7924032496731598926),
+            name: 'members',
+            targetId: const IdUid(6, 6066676571331973763)),
+        ModelRelation(
+            id: const IdUid(4, 1657013690878572102),
+            name: 'messages',
+            targetId: const IdUid(8, 8842794649328270120))
+      ],
+      backlinks: <ModelBacklink>[]),
+  ModelEntity(
+      id: const IdUid(8, 8842794649328270120),
+      name: 'GroupMessage',
+      lastPropertyId: const IdUid(6, 5148143939128616318),
+      flags: 0,
+      properties: <ModelProperty>[
+        ModelProperty(
+            id: const IdUid(1, 8727865543427007275),
+            name: 'id',
+            type: 6,
+            flags: 1),
+        ModelProperty(
+            id: const IdUid(2, 5514462395021006262),
+            name: 'fromId',
+            type: 11,
+            flags: 520,
+            indexId: const IdUid(7, 5268783855356873237),
+            relationTarget: 'User'),
+        ModelProperty(
+            id: const IdUid(3, 1148609827653786396),
+            name: 'toId',
+            type: 11,
+            flags: 520,
+            indexId: const IdUid(8, 7769527806721188121),
+            relationTarget: 'Group'),
+        ModelProperty(
+            id: const IdUid(5, 7501600871221400279),
+            name: 'time',
+            type: 6,
+            flags: 0),
+        ModelProperty(
+            id: const IdUid(6, 5148143939128616318),
+            name: 'contentId',
+            type: 11,
+            flags: 520,
+            indexId: const IdUid(11, 6309601695170211538),
+            relationTarget: 'Content')
+      ],
+      relations: <ModelRelation>[],
       backlinks: <ModelBacklink>[])
 ];
 
@@ -174,17 +234,24 @@ Future<Store> openStore(
 ModelDefinition getObjectBoxModel() {
   final model = ModelInfo(
       entities: _entities,
-      lastEntityId: const IdUid(6, 6066676571331973763),
-      lastIndexId: const IdUid(6, 2181766053148811865),
-      lastRelationId: const IdUid(2, 3118435353931894829),
+      lastEntityId: const IdUid(8, 8842794649328270120),
+      lastIndexId: const IdUid(11, 6309601695170211538),
+      lastRelationId: const IdUid(6, 6570620621500888428),
       lastSequenceId: const IdUid(0, 0),
-      retiredEntityUids: const [3444477729893015694],
-      retiredIndexUids: const [],
+      retiredEntityUids: const [3444477729893015694, 5044745765388820377],
+      retiredIndexUids: const [2181766053148811865],
       retiredPropertyUids: const [
         5463014948149082651,
         2305308568754167021,
         649621747167423523,
-        4818206049188692305
+        4818206049188692305,
+        2230815448876041069,
+        5034484793468406763,
+        754684519442805673,
+        214180290351070734,
+        7647214962273172849,
+        6701330857882848509,
+        2182219019195059976
       ],
       retiredRelationUids: const [],
       modelVersion: 5,
@@ -201,13 +268,9 @@ ModelDefinition getObjectBoxModel() {
           object.id = id;
         },
         objectToFB: (Profile object, fb.Builder fbb) {
-          final usernameOffset = object.username == null
-              ? null
-              : fbb.writeString(object.username!);
           fbb.startTable(8);
           fbb.addInt64(0, object.id);
           fbb.addInt64(1, object.theme);
-          fbb.addOffset(2, usernameOffset);
           fbb.addBool(3, object.isLogin);
           fbb.addBool(6, object.isLogout);
           fbb.finish(fbb.endTable());
@@ -220,8 +283,6 @@ ModelDefinition getObjectBoxModel() {
           final object = Profile()
             ..id = const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0)
             ..theme = const fb.Int64Reader().vTableGet(buffer, rootOffset, 6, 0)
-            ..username =
-                const fb.StringReader().vTableGetNullable(buffer, rootOffset, 8)
             ..isLogin =
                 const fb.BoolReader().vTableGet(buffer, rootOffset, 10, false)
             ..isLogout =
@@ -252,52 +313,22 @@ ModelDefinition getObjectBoxModel() {
 
           return object;
         }),
-    Friend: EntityDefinition<Friend>(
-        model: _entities[2],
-        toOneRelations: (Friend object) => [object.user1, object.user2],
-        toManyRelations: (Friend object) =>
-            {RelInfo<Friend>.toMany(1, object.id): object.messages},
-        getId: (Friend object) => object.id,
-        setId: (Friend object, int id) {
-          object.id = id;
-        },
-        objectToFB: (Friend object, fb.Builder fbb) {
-          fbb.startTable(4);
-          fbb.addInt64(0, object.id);
-          fbb.addInt64(1, object.user1.targetId);
-          fbb.addInt64(2, object.user2.targetId);
-          fbb.finish(fbb.endTable());
-          return object.id;
-        },
-        objectFromFB: (Store store, ByteData fbData) {
-          final buffer = fb.BufferContext(fbData);
-          final rootOffset = buffer.derefObject(0);
-
-          final object = Friend()
-            ..id = const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0);
-          object.user1.targetId =
-              const fb.Int64Reader().vTableGet(buffer, rootOffset, 6, 0);
-          object.user1.attach(store);
-          object.user2.targetId =
-              const fb.Int64Reader().vTableGet(buffer, rootOffset, 8, 0);
-          object.user2.attach(store);
-          InternalToManyAccess.setRelInfo(object.messages, store,
-              RelInfo<Friend>.toMany(1, object.id), store.box<Friend>());
-          return object;
-        }),
     Message: EntityDefinition<Message>(
-        model: _entities[3],
-        toOneRelations: (Message object) => [object.from, object.to],
+        model: _entities[2],
+        toOneRelations: (Message object) =>
+            [object.from, object.to, object.content],
         toManyRelations: (Message object) => {},
         getId: (Message object) => object.id,
         setId: (Message object, int id) {
           object.id = id;
         },
         objectToFB: (Message object, fb.Builder fbb) {
-          fbb.startTable(4);
+          fbb.startTable(7);
           fbb.addInt64(0, object.id);
           fbb.addInt64(1, object.from.targetId);
           fbb.addInt64(2, object.to.targetId);
+          fbb.addInt64(4, object.time);
+          fbb.addInt64(5, object.content.targetId);
           fbb.finish(fbb.endTable());
           return object.id;
         },
@@ -306,29 +337,40 @@ ModelDefinition getObjectBoxModel() {
           final rootOffset = buffer.derefObject(0);
 
           final object = Message()
-            ..id = const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0);
+            ..id = const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0)
+            ..time =
+                const fb.Int64Reader().vTableGet(buffer, rootOffset, 12, 0);
           object.from.targetId =
               const fb.Int64Reader().vTableGet(buffer, rootOffset, 6, 0);
           object.from.attach(store);
           object.to.targetId =
               const fb.Int64Reader().vTableGet(buffer, rootOffset, 8, 0);
           object.to.attach(store);
+          object.content.targetId =
+              const fb.Int64Reader().vTableGet(buffer, rootOffset, 14, 0);
+          object.content.attach(store);
           return object;
         }),
     User: EntityDefinition<User>(
-        model: _entities[4],
+        model: _entities[3],
         toOneRelations: (User object) => [],
-        toManyRelations: (User object) =>
-            {RelInfo<User>.toMany(2, object.id): object.friends},
+        toManyRelations: (User object) => {
+              RelInfo<User>.toMany(2, object.id): object.friends,
+              RelInfo<User>.toMany(5, object.id): object.messages,
+              RelInfo<User>.toMany(6, object.id): object.groupMessages
+            },
         getId: (User object) => object.id,
         setId: (User object, int id) {
           object.id = id;
         },
         objectToFB: (User object, fb.Builder fbb) {
           final usernameOffset = fbb.writeString(object.username);
-          fbb.startTable(3);
+          fbb.startTable(7);
           fbb.addInt64(0, object.id);
-          fbb.addOffset(1, usernameOffset);
+          fbb.addOffset(2, usernameOffset);
+          fbb.addBool(3, object.isDND);
+          fbb.addBool(4, object.isStick);
+          fbb.addBool(5, object.isSpecialAttention);
           fbb.finish(fbb.endTable());
           return object.id;
         },
@@ -337,11 +379,87 @@ ModelDefinition getObjectBoxModel() {
           final rootOffset = buffer.derefObject(0);
 
           final object = User(
-              id: const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0),
-              username:
-                  const fb.StringReader().vTableGet(buffer, rootOffset, 6, ''));
+              const fb.StringReader().vTableGet(buffer, rootOffset, 8, ''))
+            ..id = const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0)
+            ..isDND =
+                const fb.BoolReader().vTableGet(buffer, rootOffset, 10, false)
+            ..isStick =
+                const fb.BoolReader().vTableGet(buffer, rootOffset, 12, false)
+            ..isSpecialAttention =
+                const fb.BoolReader().vTableGet(buffer, rootOffset, 14, false);
           InternalToManyAccess.setRelInfo(object.friends, store,
               RelInfo<User>.toMany(2, object.id), store.box<User>());
+          InternalToManyAccess.setRelInfo(object.messages, store,
+              RelInfo<User>.toMany(5, object.id), store.box<User>());
+          InternalToManyAccess.setRelInfo(object.groupMessages, store,
+              RelInfo<User>.toMany(6, object.id), store.box<User>());
+          return object;
+        }),
+    Group: EntityDefinition<Group>(
+        model: _entities[4],
+        toOneRelations: (Group object) => [],
+        toManyRelations: (Group object) => {
+              RelInfo<Group>.toMany(3, object.id): object.members,
+              RelInfo<Group>.toMany(4, object.id): object.messages
+            },
+        getId: (Group object) => object.id,
+        setId: (Group object, int id) {
+          object.id = id;
+        },
+        objectToFB: (Group object, fb.Builder fbb) {
+          fbb.startTable(2);
+          fbb.addInt64(0, object.id);
+          fbb.finish(fbb.endTable());
+          return object.id;
+        },
+        objectFromFB: (Store store, ByteData fbData) {
+          final buffer = fb.BufferContext(fbData);
+          final rootOffset = buffer.derefObject(0);
+
+          final object = Group()
+            ..id = const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0);
+          InternalToManyAccess.setRelInfo(object.members, store,
+              RelInfo<Group>.toMany(3, object.id), store.box<Group>());
+          InternalToManyAccess.setRelInfo(object.messages, store,
+              RelInfo<Group>.toMany(4, object.id), store.box<Group>());
+          return object;
+        }),
+    GroupMessage: EntityDefinition<GroupMessage>(
+        model: _entities[5],
+        toOneRelations: (GroupMessage object) =>
+            [object.from, object.to, object.content],
+        toManyRelations: (GroupMessage object) => {},
+        getId: (GroupMessage object) => object.id,
+        setId: (GroupMessage object, int id) {
+          object.id = id;
+        },
+        objectToFB: (GroupMessage object, fb.Builder fbb) {
+          fbb.startTable(7);
+          fbb.addInt64(0, object.id);
+          fbb.addInt64(1, object.from.targetId);
+          fbb.addInt64(2, object.to.targetId);
+          fbb.addInt64(4, object.time);
+          fbb.addInt64(5, object.content.targetId);
+          fbb.finish(fbb.endTable());
+          return object.id;
+        },
+        objectFromFB: (Store store, ByteData fbData) {
+          final buffer = fb.BufferContext(fbData);
+          final rootOffset = buffer.derefObject(0);
+
+          final object = GroupMessage()
+            ..id = const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0)
+            ..time =
+                const fb.Int64Reader().vTableGet(buffer, rootOffset, 12, 0);
+          object.from.targetId =
+              const fb.Int64Reader().vTableGet(buffer, rootOffset, 6, 0);
+          object.from.attach(store);
+          object.to.targetId =
+              const fb.Int64Reader().vTableGet(buffer, rootOffset, 8, 0);
+          object.to.attach(store);
+          object.content.targetId =
+              const fb.Int64Reader().vTableGet(buffer, rootOffset, 14, 0);
+          object.content.attach(store);
           return object;
         })
   };
@@ -358,17 +476,13 @@ class Profile_ {
   static final theme =
       QueryIntegerProperty<Profile>(_entities[0].properties[1]);
 
-  /// see [Profile.username]
-  static final username =
-      QueryStringProperty<Profile>(_entities[0].properties[2]);
-
   /// see [Profile.isLogin]
   static final isLogin =
-      QueryBooleanProperty<Profile>(_entities[0].properties[3]);
+      QueryBooleanProperty<Profile>(_entities[0].properties[2]);
 
   /// see [Profile.isLogout]
   static final isLogout =
-      QueryBooleanProperty<Profile>(_entities[0].properties[4]);
+      QueryBooleanProperty<Profile>(_entities[0].properties[3]);
 }
 
 /// [Content] entity fields to define ObjectBox queries.
@@ -377,47 +491,91 @@ class Content_ {
   static final id = QueryIntegerProperty<Content>(_entities[1].properties[0]);
 }
 
-/// [Friend] entity fields to define ObjectBox queries.
-class Friend_ {
-  /// see [Friend.id]
-  static final id = QueryIntegerProperty<Friend>(_entities[2].properties[0]);
-
-  /// see [Friend.user1]
-  static final user1 =
-      QueryRelationToOne<Friend, User>(_entities[2].properties[1]);
-
-  /// see [Friend.user2]
-  static final user2 =
-      QueryRelationToOne<Friend, User>(_entities[2].properties[2]);
-
-  /// see [Friend.messages]
-  static final messages =
-      QueryRelationToMany<Friend, Message>(_entities[2].relations[0]);
-}
-
 /// [Message] entity fields to define ObjectBox queries.
 class Message_ {
   /// see [Message.id]
-  static final id = QueryIntegerProperty<Message>(_entities[3].properties[0]);
+  static final id = QueryIntegerProperty<Message>(_entities[2].properties[0]);
 
   /// see [Message.from]
   static final from =
-      QueryRelationToOne<Message, User>(_entities[3].properties[1]);
+      QueryRelationToOne<Message, User>(_entities[2].properties[1]);
 
   /// see [Message.to]
   static final to =
-      QueryRelationToOne<Message, User>(_entities[3].properties[2]);
+      QueryRelationToOne<Message, User>(_entities[2].properties[2]);
+
+  /// see [Message.time]
+  static final time = QueryIntegerProperty<Message>(_entities[2].properties[3]);
+
+  /// see [Message.content]
+  static final content =
+      QueryRelationToOne<Message, Content>(_entities[2].properties[4]);
 }
 
 /// [User] entity fields to define ObjectBox queries.
 class User_ {
   /// see [User.id]
-  static final id = QueryIntegerProperty<User>(_entities[4].properties[0]);
+  static final id = QueryIntegerProperty<User>(_entities[3].properties[0]);
 
   /// see [User.username]
-  static final username = QueryStringProperty<User>(_entities[4].properties[1]);
+  static final username = QueryStringProperty<User>(_entities[3].properties[1]);
+
+  /// see [User.isDND]
+  static final isDND = QueryBooleanProperty<User>(_entities[3].properties[2]);
+
+  /// see [User.isStick]
+  static final isStick = QueryBooleanProperty<User>(_entities[3].properties[3]);
+
+  /// see [User.isSpecialAttention]
+  static final isSpecialAttention =
+      QueryBooleanProperty<User>(_entities[3].properties[4]);
 
   /// see [User.friends]
   static final friends =
-      QueryRelationToMany<User, Friend>(_entities[4].relations[0]);
+      QueryRelationToMany<User, User>(_entities[3].relations[0]);
+
+  /// see [User.messages]
+  static final messages =
+      QueryRelationToMany<User, Message>(_entities[3].relations[1]);
+
+  /// see [User.groupMessages]
+  static final groupMessages =
+      QueryRelationToMany<User, GroupMessage>(_entities[3].relations[2]);
+}
+
+/// [Group] entity fields to define ObjectBox queries.
+class Group_ {
+  /// see [Group.id]
+  static final id = QueryIntegerProperty<Group>(_entities[4].properties[0]);
+
+  /// see [Group.members]
+  static final members =
+      QueryRelationToMany<Group, User>(_entities[4].relations[0]);
+
+  /// see [Group.messages]
+  static final messages =
+      QueryRelationToMany<Group, GroupMessage>(_entities[4].relations[1]);
+}
+
+/// [GroupMessage] entity fields to define ObjectBox queries.
+class GroupMessage_ {
+  /// see [GroupMessage.id]
+  static final id =
+      QueryIntegerProperty<GroupMessage>(_entities[5].properties[0]);
+
+  /// see [GroupMessage.from]
+  static final from =
+      QueryRelationToOne<GroupMessage, User>(_entities[5].properties[1]);
+
+  /// see [GroupMessage.to]
+  static final to =
+      QueryRelationToOne<GroupMessage, Group>(_entities[5].properties[2]);
+
+  /// see [GroupMessage.time]
+  static final time =
+      QueryIntegerProperty<GroupMessage>(_entities[5].properties[3]);
+
+  /// see [GroupMessage.content]
+  static final content =
+      QueryRelationToOne<GroupMessage, Content>(_entities[5].properties[4]);
 }

+ 2 - 1
lib/models/locale_model.dart → lib/presenter/locale.dart

@@ -1,6 +1,7 @@
-import 'package:e2ee_chat/models/profile.dart';
 import 'package:flutter/material.dart';
 
+import 'profile.dart';
+
 class LocaleModel extends ProfileChangeNotifier {
   LocaleModel();
 

+ 10 - 12
lib/models/login_model.dart → lib/presenter/login.dart

@@ -1,27 +1,25 @@
 import 'package:dio/dio.dart';
-import 'package:e2ee_chat/common/global.dart';
-import 'package:e2ee_chat/common/api.dart';
 import 'package:flutter/material.dart';
 import 'package:provider/provider.dart';
 
+import 'package:e2ee_chat/common/global.dart';
+import 'package:e2ee_chat/common/api.dart';
+import '../model/user.dart';
+
 import 'profile.dart';
 
 class LoginModel extends ProfileChangeNotifier {
 
-  /*
-  UserModel._();
-
-  static UserModel _instance = UserModel._();
-
-  factory UserModel() => _instance!;
-   */
-
   // TODO: online keep status; A logout, B logout?
   bool get isLogin => profile.isLogin;
 
+  User? get user => profile.user;
+
   Future<bool> login({String? username, String? password}) async {
     debug('UserModel login begin');
-    profile.username = username ?? profile.username;
+    if (username != null) {
+      profile.user = User(username);
+    }
     bool _result = false;
     String? token = await profile.token;
     if (profile.username != null && (password != null || token != null)) {
@@ -57,7 +55,7 @@ class LoginModel extends ProfileChangeNotifier {
     if (await login(username: username, password: password)) {
       return true;
     } else if (await Api().register(username, password)) {
-      return login(username: username, password: password);
+      return await login(username: username, password: password);
     }
     return false;
   }

+ 1 - 2
lib/models/profile.dart → lib/presenter/profile.dart

@@ -1,9 +1,8 @@
-import 'package:e2ee_chat/entities/profile.dart';
+import 'package:e2ee_chat/model/profile.dart';
 import 'package:flutter/cupertino.dart';
 
 import '../common/global.dart';
 
-
 class ProfileChangeNotifier extends ChangeNotifier {
   Profile get profile => Global.profile;
 

+ 73 - 0
lib/presenter/session.dart

@@ -0,0 +1,73 @@
+import 'package:e2ee_chat/common/global.dart';
+import 'package:e2ee_chat/model/message.dart';
+import 'package:e2ee_chat/model/user.dart';
+import 'package:flutter/cupertino.dart';
+
+import 'login.dart';
+
+enum DTMessageType {
+  text,
+  image
+}
+
+class SessionListModel extends ChangeNotifier {
+
+  List<SessionModel> get sessions {
+    var user = LoginModel().user;
+    var list = <SessionModel>[];
+    if (user != null) {
+      var friends = user.friends;
+      for (var i in friends) {
+        list.add(SessionModel(i));
+      }
+    }
+    return list;
+  }
+
+  int get count => sessions.length;
+}
+
+class SessionModel extends SessionListModel {
+
+  // TODO: 仅支持单聊
+
+  SessionModel(this._user);
+
+  final User _user;
+
+  /// 消息体
+  Message get lastMessage => _user.messages.last;
+
+  /// 未读数量
+  int get unReadCount {
+    // TODO: 未读消息数量
+    return 0;
+  }
+
+  get chatName => _user.username;
+
+
+  /// 单聊
+  get isSingle => true;
+
+  /// 群聊信息
+  get isGroup => false;
+
+  /// 消息免打扰
+  get isDND => _user.isDND;
+
+  /// 是否为置顶
+  get isStick => _user.isStick;
+
+  /// 特别关注
+  get isSpecialAttention => _user.isSpecialAttention;
+
+  // TODO: 是否 @ 你
+  get isAtYou => false;
+
+  // TODO: 是否 @ 全部
+  get isAtAll => false;
+
+  // TODO: user avatar
+  get avatar => Global.defaultAvatar;
+}

+ 0 - 1
lib/models/theme_model.dart → lib/presenter/theme_model.dart

@@ -12,7 +12,6 @@ class ThemeModel extends ProfileChangeNotifier {
   set theme(MaterialColor color) {
     if (color != theme) {
       profile.theme = color.shade500.value;
-      // TODO: What is !. operator ?
       notifyListeners();
     }
   }

+ 0 - 0
lib/widgets/friends.dart → lib/view/friends.dart


+ 0 - 0
lib/widgets/group.dart → lib/view/group.dart


+ 6 - 6
lib/routes/home.dart → lib/view/home.dart

@@ -1,16 +1,16 @@
 import 'package:e2ee_chat/common/global.dart';
 import 'package:e2ee_chat/l10n/localization_intl.dart';
 import 'package:e2ee_chat/common/api.dart';
-import 'package:e2ee_chat/models/theme_model.dart';
-import 'package:e2ee_chat/models/login_model.dart';
+import 'package:e2ee_chat/presenter/theme_model.dart';
 import 'package:e2ee_chat/widgets/empty.dart';
-import 'package:e2ee_chat/widgets/friends.dart';
-import 'package:e2ee_chat/widgets/group.dart';
-import 'package:e2ee_chat/widgets/message.dart';
 import 'package:e2ee_chat/widgets/mydrawer.dart';
 import 'package:flutter/material.dart';
 import 'package:provider/provider.dart';
 
+import 'friends.dart';
+import 'group.dart';
+import 'message.dart';
+
 class HomeRoute extends StatefulWidget {
   @override
   _HomeRouteState createState() => _HomeRouteState();
@@ -32,7 +32,7 @@ class _HomeRouteState extends State<HomeRoute> {
         title: Text(gm.home),
         actions: <Widget>[
           IconButton(onPressed: () {
-            Scaffold.of(context).openDrawer();
+            // TODO: 弹出下拉菜单
           }, icon: Icon(Icons.add))
         ],
       ),

+ 1 - 1
lib/routes/language_route.dart → lib/view/language.dart

@@ -1,5 +1,5 @@
 import 'package:e2ee_chat/l10n/localization_intl.dart';
-import 'package:e2ee_chat/models/locale_model.dart';
+import 'package:e2ee_chat/presenter/locale.dart';
 import 'package:flutter/material.dart';
 import 'package:provider/provider.dart';
 

+ 5 - 8
lib/routes/login.dart → lib/view/login.dart

@@ -1,8 +1,8 @@
 import 'package:dio/dio.dart';
 import 'package:e2ee_chat/common/global.dart';
 import 'package:e2ee_chat/l10n/localization_intl.dart';
-import 'package:e2ee_chat/models/theme_model.dart';
-import 'package:e2ee_chat/models/login_model.dart';
+import 'package:e2ee_chat/presenter/login.dart';
+import 'package:e2ee_chat/presenter/theme_model.dart';
 import 'package:flutter/material.dart';
 import 'package:provider/provider.dart';
 import 'package:fluttertoast/fluttertoast.dart';
@@ -60,8 +60,7 @@ class _LoginRouteState extends State<LoginRoute> {
                     hintText: GmLocalizations.of(context).password,
                     prefixIcon: Icon(Icons.lock),
                     suffixIcon: IconButton(
-                      icon: Icon(
-                          pwdShow ? Icons.visibility_off : Icons.visibility),
+                      icon: Icon(pwdShow ? Icons.visibility_off : Icons.visibility),
                       onPressed: () {
                         setState(() {
                           pwdShow = !pwdShow;
@@ -101,11 +100,9 @@ class _LoginRouteState extends State<LoginRoute> {
     if ((_formKey.currentState as FormState).validate()) {
       Fluttertoast.showToast(msg: GmLocalizations.of(context).loading);
       try {
-        // user = await Git(context).login(_unameController.text, _pwdController.text);
-        // 因为登录页返回后,首页会build,所以我们传false,更新user后不触发更新
         await LoginModel().loginOrRegister(_unameController.text, _pwdController.text);
         debug('isLogin: ${Global.profile.isLogin}');
-      } on DioError catch(e) {
+      } on DioError catch (e) {
         //登录失败则提示
         if (e.response?.statusCode == 401) {
           Fluttertoast.showToast(msg: GmLocalizations.of(context).userNameOrPasswordWrong);
@@ -121,4 +118,4 @@ class _LoginRouteState extends State<LoginRoute> {
       }
     }
   }
-}
+}

+ 166 - 0
lib/view/message.dart

@@ -0,0 +1,166 @@
+import 'package:e2ee_chat/common/global.dart';
+import 'package:e2ee_chat/presenter/session.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_slidable/flutter_slidable.dart';
+import 'package:provider/provider.dart';
+
+class MessageList extends StatefulWidget {
+  @override
+  _MessageListState createState() => _MessageListState();
+}
+
+class _MessageListState extends State<MessageList> {
+  @override
+  Widget build(BuildContext context) {
+    return ChangeNotifierProvider<SessionListModel>(
+      create: (context) {
+        return SessionListModel();
+      },
+      child: Builder(
+        builder: (context) {
+          var provider = Provider.of<SessionListModel>(context);
+          return ListView.builder(
+              itemCount: provider.count,
+              itemBuilder: (context, index) {
+                var _item = provider.sessions[index];
+                return Slidable(
+                  actionPane: SlidableDrawerActionPane(),
+                  actionExtentRatio: 0.25,
+                  secondaryActions: <Widget>[
+                    IconSlideAction(
+                      caption: '取消置顶',
+                      color: Colors.black45,
+                      icon: Icons.more_horiz,
+                      onTap: () {},
+                    ),
+                    IconSlideAction(
+                      caption: '删除',
+                      color: Colors.redAccent,
+                      icon: Icons.delete,
+                      onTap: () => {},
+                    ),
+                  ],
+                  child: Padding(
+                    padding: EdgeInsets.only(left: 36, bottom: 20, top: 20),
+                    child: Row(
+                      crossAxisAlignment: CrossAxisAlignment.start,
+                      children: <Widget>[
+                        Expanded(child: MessageItem(_item)),
+                        _item.isStick
+                            ? Container(
+                                width: 40,
+                                height: 48,
+                                margin: EdgeInsets.only(top: 10, right: 10),
+                                child: Icon(Icons.star))
+                            : Padding(
+                                padding: EdgeInsets.symmetric(horizontal: 24),
+                              )
+                      ],
+                    ),
+                  ),
+                );
+              });
+        },
+      ),
+    );
+  }
+}
+
+class MessageItem extends StatelessWidget {
+  final SessionModel model;
+
+  MessageItem(this.model);
+
+  @override
+  Widget build(BuildContext context) {
+    return Row(
+      mainAxisAlignment: MainAxisAlignment.spaceBetween,
+      children: <Widget>[
+        model.avatar ?? Global.defaultAvatar,
+        Expanded(
+          child: Column(
+            children: <Widget>[
+              Padding(
+                padding: EdgeInsets.symmetric(vertical: 8),
+                child: Row(
+                  mainAxisAlignment: MainAxisAlignment.spaceBetween,
+                  children: <Widget>[
+                    Expanded(
+                      child: Text(
+                        model.chatName,
+                        style: TextStyle(fontSize: 34),
+                        overflow: TextOverflow.ellipsis,
+                      ),
+                    ),
+                    Text(
+                      _formatDate(),
+                      style: TextStyle(fontSize: 26),
+                      overflow: TextOverflow.ellipsis,
+                    ),
+                  ],
+                ),
+              ),
+              Row(
+                mainAxisAlignment: MainAxisAlignment.spaceBetween,
+                children: <Widget>[
+                  Expanded(
+                    child: RichText(
+                      text: TextSpan(children: [
+                        TextSpan(
+                          text: model.isAtYou ? "[@你]" : "",
+                          style: TextStyle(fontSize: 28),
+                        ),
+                        TextSpan(
+                          text: model.isSpecialAttention ? "[特别关注]" : "",
+                          style: TextStyle(fontSize: 28),
+                        ),
+                        TextSpan(
+                          text: model.isAtAll ? "[@所有人]" : "",
+                          style: TextStyle(fontSize: 28),
+                        ),
+                        TextSpan(
+                          text: model.lastMessage.content.target!.plaintext,
+                          style: TextStyle(fontSize: 28),
+                        )
+                      ]),
+                      overflow: TextOverflow.ellipsis,
+                    ),
+                  ),
+                  (model.unReadCount > 0 && !model.isDND)
+                      ? Container(
+                          width: 32,
+                          height: 32,
+                          alignment: Alignment.center,
+                          decoration: BoxDecoration(borderRadius: BorderRadius.circular(20)),
+                          child: Text(
+                            model.unReadCount.toString(),
+                            style: TextStyle(color: Colors.white, fontSize: 26),
+                          ))
+                      : Container(),
+                  model.isDND
+                      ? Row(
+                          children: <Widget>[
+                            Icon(Icons.visibility_off),
+                            model.unReadCount > 0
+                                ? Icon(
+                                    Icons.chat_bubble,
+                                    color: Colors.red,
+                                  ) // TODO: 小红点
+                                : Container()
+                          ],
+                        )
+                      : Container()
+                ],
+              )
+            ],
+          ),
+        )
+      ],
+    );
+  }
+
+  String _formatDate() {
+    DateTime dateTime = DateTime.fromMillisecondsSinceEpoch(model.lastMessage.time);
+    return "${dateTime.hour}:${dateTime.minute}";
+  }
+}

+ 2 - 2
lib/routes/theme_change.dart → lib/view/theme.dart

@@ -2,11 +2,11 @@
 
 import 'package:e2ee_chat/common/global.dart';
 import 'package:e2ee_chat/l10n/localization_intl.dart';
-import 'package:e2ee_chat/models/theme_model.dart';
+import 'package:e2ee_chat/presenter/theme_model.dart';
 import 'package:flutter/material.dart';
 import 'package:provider/provider.dart';
 
-class ThemeChangeRoute extends StatelessWidget{
+class ThemeRoute extends StatelessWidget{
 
   @override
   Widget build(BuildContext context) {

+ 0 - 168
lib/widgets/message.dart

@@ -1,168 +0,0 @@
-import 'package:e2ee_chat/common/global.dart';
-import 'package:e2ee_chat/models/message_model.dart';
-import 'package:flutter/material.dart';
-import 'package:flutter_slidable/flutter_slidable.dart';
-import 'package:provider/provider.dart';
-
-class MessageList extends StatefulWidget {
-  @override
-  _MessageListState createState() => _MessageListState();
-}
-
-class _MessageListState extends State<MessageList> {
-  @override
-  Widget build(BuildContext context) {
-    return ChangeNotifierProvider<MessageListModel>(
-      create: (context) {
-        return MessageListModel();
-      },
-      child: Builder(
-        builder: (context) {
-          var provider = Provider.of<MessageListModel>(context);
-          return ListView.builder(itemCount: provider.count, itemBuilder: (context, index) {
-            var _item = provider.items[index];
-            return Slidable(
-              actionPane: SlidableDrawerActionPane(),
-              actionExtentRatio: 0.25,
-              secondaryActions: <Widget>[
-                IconSlideAction(
-                  caption: '取消置顶',
-                  color: Colors.black45,
-                  icon: Icons.more_horiz,
-                  onTap: () {},
-                ),
-                IconSlideAction(
-                  caption: '删除',
-                  color: Colors.redAccent,
-                  icon: Icons.delete,
-                  onTap: () => {},
-                ),
-              ],
-              child: Padding(
-                padding: EdgeInsets.only(left: 36, bottom: 20, top: 20),
-                child: Row(
-                  crossAxisAlignment: CrossAxisAlignment.start,
-                  children: <Widget>[
-                    Expanded(child: MessageItem(_item)),
-                    _item.isStick
-                        ? Container(
-                      width: 40,
-                      height: 48,
-                      margin: EdgeInsets.only(top: 10, right: 10),
-                      child: Icon(Icons.star)
-                    )
-                        : Padding(
-                      padding: EdgeInsets.symmetric(horizontal: 24),
-                    )
-                  ],
-                ),
-              ),
-            );
-          });
-        },
-      ),
-    );
-  }
-}
-
-class MessageItem extends StatelessWidget {
-  final MessageItemModel model;
-
-  MessageItem(this.model);
-
-  @override
-  Widget build(BuildContext context) {
-    return Row(
-      mainAxisAlignment: MainAxisAlignment.spaceBetween,
-      children: <Widget>[
-        model.avatar ?? Global.defaultAvatar,
-        Expanded(
-          child: Column(
-            children: <Widget>[
-              Padding(
-                padding: EdgeInsets.symmetric(vertical: 8),
-                child: Row(
-                  mainAxisAlignment: MainAxisAlignment.spaceBetween,
-                  children: <Widget>[
-                    Expanded(
-                      child: Text(
-                        model.chatName,
-                        style: TextStyle(fontSize: 34),
-                        overflow: TextOverflow.ellipsis,
-                      ),
-                    ),
-                    Text(
-                      _formatDate(),
-                      style: TextStyle(fontSize: 26),
-                      overflow: TextOverflow.ellipsis,
-                    ),
-                  ],
-                ),
-              ),
-              Row(
-                mainAxisAlignment: MainAxisAlignment.spaceBetween,
-                children: <Widget>[
-                  Expanded(
-                    child: RichText(
-                      text: TextSpan(children: [
-                        TextSpan(
-                          text: model.isAtYou ? "[@你]" : "",
-                          style:
-                          TextStyle(fontSize: 28),
-                        ),
-                        TextSpan(
-                          text: model.isSpecialAttention ? "[特别关注]" : "",
-                          style:
-                          TextStyle(fontSize: 28),
-                        ),
-                        TextSpan(
-                          text: model.isAtAll ? "[@所有人]" : "",
-                          style:
-                          TextStyle(fontSize: 28),
-                        ),
-                        TextSpan(
-                          text: model.message,
-                          style: TextStyle(fontSize: 28),
-                        )
-                      ]),
-                      overflow: TextOverflow.ellipsis,
-                    ),
-                  ),
-                  (model.unReadCount > 0 && !model.isDisturbing)
-                      ? Container(
-                      width: 32,
-                      height: 32,
-                      alignment: Alignment.center,
-                      decoration: BoxDecoration(
-                          borderRadius: BorderRadius.circular(20)),
-                      child: Text(
-                        model.unReadCount.toString(),
-                        style: TextStyle(
-                            color: Colors.white, fontSize: 26),
-                      ))
-                      : Container(),
-                  model.isDisturbing
-                      ? Row(
-                    children: <Widget>[
-                      Icon(Icons.visibility_off),
-                      model.unReadCount > 0
-                          ? Icon(Icons.chat_bubble, color: Colors.red,) // TODO: 小红点
-                          : Container()
-                    ],
-                  )
-                      : Container()
-                ],
-              )
-            ],
-          ),
-        )
-      ],
-    );
-  }
-
-  String _formatDate() {
-    DateTime dateTime =
-    DateTime.fromMillisecondsSinceEpoch(model.time);
-    return "${dateTime.hour}:${dateTime.minute}";
-  }
-}

+ 1 - 1
lib/widgets/mydrawer.dart

@@ -1,5 +1,5 @@
 import 'package:e2ee_chat/common/global.dart';
-import 'package:e2ee_chat/models/login_model.dart';
+import 'package:e2ee_chat/presenter/login.dart';
 import 'package:flutter/material.dart';
 
 class MyDrawer extends StatelessWidget {

+ 14 - 0
pubspec.lock

@@ -29,6 +29,13 @@ packages:
       url: "https://pub.flutter-io.cn"
     source: hosted
     version: "2.6.1"
+  azlistview:
+    dependency: "direct main"
+    description:
+      name: azlistview
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "1.1.1"
   boolean_selector:
     dependency: transitive
     description:
@@ -483,6 +490,13 @@ packages:
       url: "https://pub.flutter-io.cn"
     source: hosted
     version: "1.0.0"
+  scrollable_positioned_list:
+    dependency: transitive
+    description:
+      name: scrollable_positioned_list
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "0.1.10"
   shelf:
     dependency: transitive
     description:

+ 1 - 0
pubspec.yaml

@@ -37,6 +37,7 @@ dependencies:
   crypto: ^3.0.1
   permission_handler: ^8.1.2
   flutter_slidable: ^0.6.0
+  azlistview: ^1.1.1
 
   # The following adds the Cupertino Icons font to your application.
   # Use with the CupertinoIcons class for iOS style icons.