Explorar el Código

1. 修改登录界面
2. 添加Profile界面
3. 添加聊天界面
4. 实现文本聊天
5. 添加gmssl测试

ignatz hace 3 años
padre
commit
60fa424083

+ 6 - 0
android/app/build.gradle

@@ -42,6 +42,9 @@ android {
 
     sourceSets {
         main.java.srcDirs += 'src/main/kotlin'
+        main {
+            jniLibs.srcDirs = ['libs']
+        }
     }
 
     defaultConfig {
@@ -54,6 +57,9 @@ android {
         versionCode flutterVersionCode.toInteger()
         versionName flutterVersionName
         multiDexEnabled true
+        ndk {
+            abiFilters 'x86_64','arm64-v8a','armeabi-v7a','armeabi','x86'
+        }
     }
 
     buildTypes {

BIN
android/app/libs/x86_64/libgmssl.so


BIN
android/app/libs/x86_64/libgmssl.so.3


+ 0 - 0
lib/basic/Api.dart


+ 0 - 0
lib/basic/Block.dart


+ 452 - 0
lib/basic/Encrypt.dart

@@ -0,0 +1,452 @@
+import 'dart:convert';
+import 'dart:ffi';
+import 'dart:io';
+import 'dart:math';
+import 'dart:typed_data';
+import 'package:ffi/ffi.dart';
+import 'package:path_provider/path_provider.dart';
+
+
+// TODO: 让_Block的size可以设置
+
+
+class Block16 extends Struct {
+  @Array(16) external Array<Uint8> buf;
+}
+class Block32 extends Struct {
+  @Array(32) external Array<Uint8> buf;
+}
+
+extension ExBlock16 on Pointer<Block16> {
+  Pointer<Uint8> get p => Pointer.fromAddress(address);
+  Uint8List get list => p.asTypedList(16);
+}
+extension ExBlock32 on Pointer<Block32> {
+  Pointer<Uint8> get p => Pointer.fromAddress(address);
+  Uint8List get list => p.asTypedList(32);
+  void copy(Pointer<Block32> key) {
+    for (int i=0; i<32; ++i) {
+      key.ref.buf[i]=1;
+    }
+  }
+}
+
+class SM2Point extends Struct {
+  external Block32 x, y;
+}
+
+extension ExSM2Point on Pointer<SM2Point> {
+  Pointer<Block32> get x => Pointer.fromAddress(address);
+  Pointer<Block32> get y => x.elementAt(1);
+}
+
+class Block {
+  Block(this.size): p = malloc.allocate(size);
+
+  final int size;
+  final Pointer<Uint8> p;
+  Uint8List get list => p.asTypedList(size);
+  // Pointer<Uint8> elementAt(int index) => p.elementAt(index);
+  // int operator[](int index) => p[index];
+  // void operator[]=(int index, int value) => p[index] = value;
+}
+
+class SM2Key extends Struct {
+  external SM2Point publicKey;
+  external Block32 privateKey;
+}
+extension ExSM2Key on Pointer<SM2Key> {
+  Pointer<SM2Point> get publicKey => Pointer.fromAddress(address);
+  Pointer<Block32> get privateKey => Pointer.fromAddress(address + 64);
+  void generate() {
+    assert(gm.sm2KeyGenerate(this) == 1);
+  }
+  void copy(Pointer<SM2Key> key) {
+  }
+}
+
+
+class SM4cbcCtx extends Struct {
+  @Array(32)
+  external Array<Uint32> sm4Key;
+  external Block16 iv, block;
+  @Size()
+  external int blockNBytes;
+}
+
+extension ExSM4CBCCtx on Pointer<SM4cbcCtx> {
+  Pointer<Uint32> get sm4Key => Pointer.fromAddress(address);
+  Pointer<Block16> get iv => Pointer.fromAddress(sm4Key.elementAt(32).address);
+  Pointer<Block16> get block => iv.elementAt(1);
+  int get size => ref.blockNBytes;
+}
+
+
+final _lib = DynamicLibrary.open("libgmssl.so");
+final gm = GmSSL._();
+
+final stderrAddress = _lib.lookup<UintPtr>("stderr").value;
+
+
+final stdoutAddress = _lib.lookup<UintPtr>("stdout").value;
+
+final _fputs = _lib.lookupFunction<Int Function(Pointer<Utf8>, UintPtr),
+    int Function(Pointer<Utf8>, int)>("fputs");
+
+int fputs(String str, int stream) {
+  return _fputs(str.toNativeUtf8(), stream);
+}
+
+final _puts = _lib.lookupFunction<Int Function(Pointer<Utf8>),
+    int Function(Pointer<Utf8>)>("puts");
+
+int puts(String str) {
+  return _puts(str.toNativeUtf8());
+}
+
+final _freopen = _lib.lookupFunction<
+    UintPtr Function(Pointer<Utf8>, Pointer<Utf8>, UintPtr),
+    int Function(Pointer<Utf8>, Pointer<Utf8>, int)>("freopen");
+
+int freopen(String path, String mode, int stream) {
+  return _freopen(path.toNativeUtf8(), mode.toNativeUtf8(), stream);
+}
+
+
+const sm2MaxPlainText = 255;
+const sm2MaxCipherTextSize = 366;
+
+class GmSSL {
+  GmSSL._();
+
+  Pointer<SM2Key> newSM2Key() => malloc<SM2Key>();
+
+  final _fopen = _lib.lookupFunction<
+      UintPtr Function(Pointer<Utf8>, Pointer<Utf8>),
+      int Function(Pointer<Utf8>, Pointer<Utf8>)
+  >("fopen");
+  final fflush = _lib.lookupFunction<Uint32 Function(UintPtr),
+      int Function(int)>("fflush");
+  final fclose = _lib.lookupFunction<Uint32 Function(UintPtr),
+      int Function(int)>("fclose");
+
+  int fopen(String path, String mode) {
+    return _fopen(path.toNativeUtf8(), mode.toNativeUtf8());
+  }
+
+  final sm2KeyGenerate = _lib.lookupFunction<Int Function(Pointer<SM2Key>),
+      int Function(Pointer<SM2Key>)>("sm2_key_generate");
+  // TODO: something wrong ...
+  final sm2PrivateKeyInfoEncryptToDER = _lib.lookupFunction<
+      Int Function(SM2Key, Pointer<Utf8>, Pointer<Pointer<Uint8>>, Pointer<Size>),
+      int Function(SM2Key, Pointer<Utf8>, Pointer<Pointer<Uint8>>, Pointer<Size>)
+  >("sm2_private_key_info_encrypt_to_der");
+
+  // TODO: unimplemented ...
+  final sm2PrivateKeyInfoDecryptFromDER = _lib.lookupFunction<
+      Int Function(SM2Key, Pointer<Pointer<Uint8>>, Pointer<Size>,
+          Pointer<Utf8>, Pointer<Pointer<Uint8>>, Pointer<Size>),
+      int Function(SM2Key, Pointer<Pointer<Uint8>>, Pointer<Size>,
+          Pointer<Utf8>, Pointer<Pointer<Uint8>>, Pointer<Size>)
+  >("sm2_private_key_info_decrypt_from_der");
+
+
+  final sm2PrivateKeyInfoEncryptToPEM = _lib.lookupFunction<
+      Int Function(Pointer<SM2Key>, Pointer<Utf8>, UintPtr),
+      int Function(Pointer<SM2Key>, Pointer<Utf8>, int)
+  >("sm2_private_key_info_encrypt_to_pem");
+
+  final sm2PrivateKeyInfoDecryptFromPEM = _lib.lookupFunction<
+      Int Function(Pointer<SM2Key>, Pointer<Utf8>, UintPtr),
+      int Function(Pointer<SM2Key>, Pointer<Utf8>, int)
+  >("sm2_private_key_info_decrypt_from_pem");
+
+  final sm2PublicKeyInfoToPEM = _lib.lookupFunction<
+      Int Function(Pointer<SM2Key>, UintPtr), int Function(Pointer<SM2Key>, int)
+  >("sm2_public_key_info_to_pem");
+  final sm2PublicKeyInfoFromPEM = _lib.lookupFunction<
+      Int Function(Pointer<SM2Key>, UintPtr), int Function(Pointer<SM2Key>, int)
+  >("sm2_public_key_info_from_pem");
+  final sm2PublicKeyInfoToDER = _lib.lookupFunction<
+      Int Function(Pointer<SM2Key>, Pointer<UintPtr>, Pointer<Size>),
+      int Function(Pointer<SM2Key>, Pointer<UintPtr>, Pointer<Size>)
+  >("sm2_public_key_info_to_der");
+  final sm2PublicKeyInfoFromDER = _lib.lookupFunction<
+      Int Function(Pointer<SM2Key>, Pointer<Pointer<Uint8>>, Pointer<Size>),
+      int Function(Pointer<SM2Key>, Pointer<Pointer<Uint8>>, Pointer<Size>)
+  >("sm2_public_key_info_from_der");
+
+  final sm2Encrypt = _lib.lookupFunction<
+      Int Function(Pointer<SM2Key>, Pointer<Uint8>, Size, Pointer<Uint8>, Pointer<Size>),
+      int Function(Pointer<SM2Key>, Pointer<Uint8>, int, Pointer<Uint8>, Pointer<Size>)
+  >("sm2_encrypt");
+  final sm2Decrypt = _lib.lookupFunction<
+      Int Function(Pointer<SM2Key>, Pointer<Uint8>, Size, Pointer<Uint8>, Pointer<Size>),
+      int Function(Pointer<SM2Key>, Pointer<Uint8>, int, Pointer<Uint8>, Pointer<Size>)
+  >("sm2_decrypt");
+
+  
+  final sm4cbcEncryptInit = _lib.lookupFunction<
+      Int Function(Pointer<SM4cbcCtx>, Pointer<Block16>, Pointer<Block16>),
+      int Function(Pointer<SM4cbcCtx>, Pointer<Block16>, Pointer<Block16>)
+  >("sm4_cbc_encrypt_init");
+  final sm4cbcEncryptUpdate = _lib.lookupFunction<
+      Int Function(Pointer<SM4cbcCtx>, Pointer<Uint8>, Size, Pointer<Uint8>, Pointer<Size>),
+      int Function(Pointer<SM4cbcCtx>, Pointer<Uint8>, int, Pointer<Uint8>, Pointer<Size>)
+  >("sm4_cbc_encrypt_update");
+  final sm4cbcEncryptFinish = _lib.lookupFunction<
+      Int Function(Pointer<SM4cbcCtx>, Pointer<Uint8>, Pointer<Size>),
+      int Function(Pointer<SM4cbcCtx>, Pointer<Uint8>, Pointer<Size>)
+  >("sm4_cbc_encrypt_finish");
+  final sm4cbcDecryptInit = _lib.lookupFunction<
+      Int Function(Pointer<SM4cbcCtx>, Pointer<Block16>, Pointer<Block16>),
+      int Function(Pointer<SM4cbcCtx>, Pointer<Block16>, Pointer<Block16>)
+  >("sm4_cbc_decrypt_init");
+  final sm4cbcDecryptUpdate = _lib.lookupFunction<
+      Int Function(Pointer<SM4cbcCtx>, Pointer<Uint8>, Size, Pointer<Uint8>, Pointer<Size>),
+      int Function(Pointer<SM4cbcCtx>, Pointer<Uint8>, int, Pointer<Uint8>, Pointer<Size>)
+  >("sm4_cbc_decrypt_update");
+  final sm4cbcDecryptFinish = _lib.lookupFunction<
+      Int Function(Pointer<SM4cbcCtx>, Pointer<Uint8>, Pointer<Size>),
+      int Function(Pointer<SM4cbcCtx>, Pointer<Uint8>, Pointer<Size>)
+  >("sm4_cbc_decrypt_finish");
+}
+
+Uint8List sm4cbcEncrypt(Uint8List input, Uint8List sm4key, Uint8List iv) {
+  assert(sm4key.length == 16, "key length error");
+  assert(iv.length == 16, "key length error");
+  // print("sm4cbcEncrypt start...");
+  // print("malloc...");
+  final ctx = malloc<SM4cbcCtx>();
+  final key = malloc<Block16>();
+  final civ = malloc<Block16>();
+  final inBuf = malloc<Uint8>(4096);
+  final outBuf = malloc<Uint8>(4196);
+  final outLen = malloc<Size>();
+  final output = <int>[];
+  // print("copy...");
+  for (int i=0; i<16; ++i) {
+    key.p[i] = sm4key[i];
+  }
+  for (int i=0; i<16; ++i) {
+    civ.p[i] = iv[i];
+  }
+  // print("init...");
+  assert(gm.sm4cbcEncryptInit(ctx, key, civ) == 1,
+  "sm4cbcEncryptInit error");
+  // print("encrypt...");
+  for (int i=0; i<input.length; i+=4096) {
+    final j = min(i+4096, input.length), len = j-i;
+    for (int k=i; k<j; ++k) {
+      inBuf[k-i] = input[k];
+    }
+    assert(gm.sm4cbcEncryptUpdate(ctx, inBuf, len, outBuf, outLen) == 1,
+    "sm4cbcEncryptUpdate error");
+    output.addAll(outBuf.asTypedList(outLen.value));
+
+  }
+  assert(gm.sm4cbcEncryptFinish(ctx, outBuf, outLen) == 1,
+  "sm4cbcEncryptFinish error");
+
+  output.addAll(outBuf.asTypedList(outLen.value));
+
+  malloc.free(ctx);
+  malloc.free(key);
+  malloc.free(civ);
+  malloc.free(inBuf);
+  malloc.free(outBuf);
+  malloc.free(outLen);
+  // print("sm4cbcEncrypt return...");
+  return Uint8List.fromList(output);
+}
+
+Uint8List sm4cbcDecrypt(Uint8List input, Uint8List sm4key, Uint8List iv) {
+  assert(sm4key.length == 16, "key length error");
+  assert(iv.length == 16, "key length error");
+  // print("sm4cbcDecrypt start...");
+  final ctx = malloc<SM4cbcCtx>();
+  final key = malloc<Block16>();
+  final civ = malloc<Block16>();
+  final inBuf = malloc<Uint8>(4096);
+  final outBuf = malloc<Uint8>(4196);
+  final outLen = malloc<Size>();
+  final output = <int>[];
+  for (int i=0; i<16; ++i) {
+    key.p[i] = sm4key[i];
+  }
+  for (int i=0; i<16; ++i) {
+    civ.p[i] = iv[i];
+  }
+  assert(gm.sm4cbcDecryptInit(ctx, key, civ) == 1,
+  "sm4cbcDecryptInit error");
+  for (int i=0; i<input.length; i+=4096) {
+    final j = min(i+4096, input.length), len = j-i;
+    for (int k=i; k<j; ++k) {
+      inBuf[k-i] = input[k];
+    }
+    assert(gm.sm4cbcDecryptUpdate(ctx, inBuf, len, outBuf, outLen) == 1,
+    "sm4cbcEncryptUpdate error");
+    output.addAll(outBuf.asTypedList(outLen.value));
+  }
+  assert(gm.sm4cbcDecryptFinish(ctx, outBuf, outLen) == 1,
+  "sm4cbcDecryptFinish error");
+
+  output.addAll(outBuf.asTypedList(outLen.value));
+
+  malloc.free(ctx);
+  malloc.free(key);
+  malloc.free(civ);
+  malloc.free(inBuf);
+  malloc.free(outBuf);
+  malloc.free(outLen);
+  // print("sm4cbcDecrypt return...");
+  return Uint8List.fromList(output);
+}
+
+Uint8List sm2PublicKeyInfoToDER(Pointer<SM2Key> key) {
+  final buf = malloc<Uint8>(512);
+  final p = malloc<UintPtr>(1);
+  p.value = buf.address;
+  final len = malloc<Size>(1);
+  len.value = 0;
+  assert(gm.sm2PublicKeyInfoToDER(key, p, len) == 1);
+  assert(len.value >= 0 && len.value < 512, "error: len.value == ${len.value}");
+  final res = buf.asTypedList(len.value);
+  malloc.free(buf);
+  malloc.free(len);
+  malloc.free(p);
+  return res;
+}
+
+int sm2PublicKeyInfoFromDER(Pointer<SM2Key> key, Uint8List der) {
+  final buf = malloc<Uint8>(512);
+  final len = malloc<Size>();
+  len.value = der.length;
+  final p = malloc<Pointer<Uint8>>();
+  p.value = buf;
+  for (int i=0; i<der.length; ++i) {
+    buf[i] = der[i];
+  }
+  final res = gm.sm2PublicKeyInfoFromDER(key, p, len);
+  malloc.free(buf);
+  malloc.free(p);
+  malloc.free(len);
+  return res;
+}
+
+// Future<String> get _tempPemPath async =>
+//     "${(await getTemporaryDirectory()).path}/temp.pem";
+
+
+// Future<String> sm2PublicKeyInfoToPEMString(SM2Key key) async {
+//   final path = await _tempPemPath;
+//   final res = sm2PublicKeyInfoToPEM(key, path);
+//   assert(res == 1);
+//   return File(path).readAsStringSync();
+// }
+
+int sm2PublicKeyInfoToPEM(Pointer<SM2Key> key, String path) {
+  final pf = gm.fopen(path, "w");
+  final res = gm.sm2PublicKeyInfoToPEM(key, pf);
+  // TODO: 封装一个file类隐式执行fflush & fclose
+  gm.fflush(pf);
+  gm.fclose(pf);
+  return res;
+}
+
+int sm2PublicKeyInfoFromPEM(Pointer<SM2Key> key, String path) {
+  final pf = gm.fopen(path, "r");
+  assert(pf != 0, "fopen $path failed");
+  final res = gm.sm2PublicKeyInfoFromPEM(key, pf);
+  gm.fflush(pf);
+  gm.fclose(pf);
+  return res;
+}
+
+// Future<int> sm2PublicKeyInfoFromPEMString(SM2Key key, String pem) async {
+//   final path = await _tempPemPath;
+//   File file = File(path);
+//   file.writeAsStringSync(pem);
+//   return sm2PublicKeyInfoFromPEM(key, path);
+// }
+
+// Future<String> sm2PrivateKeyInfoEncryptToPEMString(
+//     SM2Key key, Pointer<Utf8> pass) async {
+//   final path = await _tempPemPath;
+//   final res = sm2PrivateKeyInfoEncryptToPEM(key, pass, path);
+//   return File(path).readAsStringSync();
+//   // pf = gm.fopen(savePath, "r");
+//   // assert(gm.sm2PrivateKeyInfoDecryptFromPEM(key, pass, pf) == 1);
+// }
+
+void sm2PrivateKeyInfoEncryptToPEM(Pointer<SM2Key> key, Pointer<Utf8> pass, String path) {
+  final pf = gm.fopen(path, "w");
+  assert(pf != 0, "fopen $path failed");
+  final res = gm.sm2PrivateKeyInfoEncryptToPEM(key, pass, pf);
+  gm.fflush(pf);
+  gm.fclose(pf);
+  assert(res == 1);
+}
+
+int sm2PrivateKeyInfoDecryptFromPEM(Pointer<SM2Key> key, Pointer<Utf8> pass, String path) {
+  final pf = gm.fopen(path, "r");
+  assert(pf != 0, "fopen $path failed");
+  final res = gm.sm2PrivateKeyInfoDecryptFromPEM(key, pass, pf);
+  gm.fflush(pf);
+  gm.fclose(pf);
+  return res;
+}
+
+Uint8List sm2Encrypt(Pointer<SM2Key> key, Uint8List plaintext) {
+  final inLen = plaintext.length;
+  assert(inLen <= sm2MaxPlainText,
+  "plaintext length > $sm2MaxPlainText");
+  Pointer<Uint8> input = malloc(sm2MaxPlainText + 1);
+  Pointer<Uint8> output = malloc(sm2MaxCipherTextSize);
+  Pointer<Size> outLen = malloc<Size>();
+  for (int i=0; i<inLen; ++i) {
+    input[i] = plaintext[i];
+  }
+  final res = gm.sm2Encrypt(key, input, inLen, output, outLen);
+  assert(res == 1);
+  malloc.free(input);
+  malloc.free(output);
+  malloc.free(outLen);
+  return output.asTypedList(outLen.value);
+}
+
+Uint8List sm2Decrypt(Pointer<SM2Key> key, Uint8List ciphertext) {
+  final inLen = ciphertext.length;
+  assert(inLen <= sm2MaxCipherTextSize, "ciphertext length > $sm2MaxCipherTextSize");
+  Pointer<Uint8> input = malloc(sm2MaxCipherTextSize);
+  Pointer<Uint8> output = malloc(sm2MaxCipherTextSize);
+  Pointer<Size> outLen = malloc<Size>();
+  for (int i=0; i<inLen; ++i) {
+    input[i] = ciphertext[i];
+  }
+  final res = gm.sm2Decrypt(key, input, inLen, output, outLen);
+  assert(res == 1);
+  malloc.free(input);
+  malloc.free(output);
+  malloc.free(outLen);
+  return output.asTypedList(outLen.value);
+}
+
+Uint8List sm2PrivateKeyInfoEncryptToDER(SM2Key key, Pointer<Utf8> pass) {
+  // throw UnimplementedError("this method run incorrectly");
+  // TODO: something wrong...
+  // TODO: 检查使用完成后是否需要清理&释放内存
+  Pointer<Uint8> buf = malloc.allocate(1024 * sizeOf<Uint8>());
+  Pointer<Pointer<Uint8>> p = malloc.allocate(sizeOf<Pointer>());
+  p.value = buf;
+  Pointer<Size> len = calloc.allocate(sizeOf<Size>());
+
+  final res = gm.sm2PrivateKeyInfoEncryptToDER(key, pass, p, len);
+  assert(res == 1);
+  final list = buf.asTypedList(len.value);
+  // TODO: safety clean
+  malloc.free(buf);
+  malloc.free(p);
+  malloc.free(len);
+  return list;
+}

+ 32 - 23
lib/basic/Entities.dart

@@ -3,32 +3,38 @@ import 'dart:convert';
 import 'package:azlistview/azlistview.dart';
 import 'package:prime_chat/basic/Method.dart';
 
+import 'Global.dart';
+
 /// 图片
 class RemoteImageInfo {
   // String originalName;
-  String path;
-  String fileServer;
+  final String path;
+  final String fileServer;
 
-  RemoteImageInfo(
-      {required this.path, this.fileServer = "$api/file/"});
+  RemoteImageInfo({required this.path, this.fileServer = "$server/file/"});
 
-  RemoteImageInfo.mock() : path = "", fileServer = "";
+  const RemoteImageInfo.mock()
+      : path = "",
+        fileServer = "";
 
-  RemoteImageInfo.fromJson(Map<String, dynamic> json) :
-    // this.originalName = json["originalName"],
-    path = json["path"],
-    fileServer = json["fileServer"];
+  RemoteImageInfo.fromJson(Map<String, dynamic> json)
+      :
+        // this.originalName = json["originalName"],
+        path = json["path"],
+        fileServer = json["fileServer"];
 }
 
-class FriendInfo extends UserProfile with ISuspensionBean {
+class FriendInfo extends ISuspensionBean {
   String? tagIndex;
+  final Profile profile;
 
-  FriendInfo({required super.name, super.avatar, this.tagIndex});
-
+  FriendInfo({required this.profile, this.tagIndex});
 
   @override
   String getSuspensionTag() => tagIndex ?? name.substring(0, 1);
 
+  String get name => profile.name;
+
   // toJson() => {"username": username, "uid": uid};
 
   // @override
@@ -36,15 +42,18 @@ class FriendInfo extends UserProfile with ISuspensionBean {
 }
 
 /// 用户自己的信息
-class UserProfile {
-  String name;
-  final RemoteImageInfo? _avatar;
-
-  UserProfile({required this.name, RemoteImageInfo? avatar}): _avatar = avatar;
-
-  RemoteImageInfo get avatar => _avatar ?? RemoteImageInfo.mock();
-
-  UserProfile.fromJson(Map<String, dynamic> json)
-      : name = json["Name"],
-        _avatar = RemoteImageInfo(path: json["Avatar"]);
+class Profile {
+  final String id;
+  final String name;
+  final RemoteImageInfo avatar;
+
+  const Profile(
+      {this.id = "",
+      required this.name,
+      this.avatar = const RemoteImageInfo.mock()});
+
+  Profile.fromJson(Map<String, dynamic> json)
+      : name = json["name"],
+        id = json["userId"].toString(),
+        avatar = RemoteImageInfo(path: json["avatar"]);
 }

+ 11 - 0
lib/basic/Global.dart

@@ -0,0 +1,11 @@
+
+import 'package:shared_preferences/shared_preferences.dart';
+
+late SharedPreferences prefs;
+const server = "http://api.ignatz.xyz";
+
+
+
+Future init() async {
+  prefs = await SharedPreferences.getInstance();
+}

+ 170 - 135
lib/basic/Method.dart

@@ -8,163 +8,160 @@ import 'package:flutter_easyloading/flutter_easyloading.dart';
 import 'package:flutter_styled_toast/flutter_styled_toast.dart';
 import 'package:path_provider/path_provider.dart';
 import 'package:permission_handler/permission_handler.dart';
+import 'package:prime_chat/basic/Api.dart';
 import 'package:shared_preferences/shared_preferences.dart';
+import 'package:flutter_chat_types/flutter_chat_types.dart' as types;
 
-import 'Entities.dart';
 
+import 'Entities.dart';
+import 'Global.dart';
 
-const api = "http://api.ignatz.xyz";
+class Handler {
+  Handler(this.name) : value = prefs.getString(name) ?? "";
+  final String name;
+  late String value;
+  Future<bool> set(String value) async {
+    this.value = value;
+    return prefs.setString(name, value);
+  }
+}
 
 final method = Method._();
 
 class Method {
   Method._();
 
-  var dio = Dio();
+  Future init() async {
+    _token = Handler("token");
+    _name = Handler("name");
+    _password = Handler("password");
+    _uid = Handler("uid");
+    return await preLogin();
+  }
 
-  var options = Options();
+  late Handler _token;
+  late Handler _name;
+  late Handler _password;
+  late Handler _uid;
+  get dio => token.isEmpty
+      ? Dio()
+      : Dio(BaseOptions(
+          headers: {HttpHeaders.authorizationHeader: "Bearer $token"}));
 
-  Future<int> home() async {
-    try {
-      final res = await dio.get("$api/home");
-      if (res.statusCode == HttpStatus.ok) {
-        var data = jsonDecode(res.toString());
-        final uid = data["uid"];
-        if (await setUID(uid)) return uid;
-      }
-    } on Exception catch(e) {
-      debugPrint("home 网络错误");
-      // debugPrint(e.toString());
-      return 0;
-    }
-    return 0;
-  }
+  get token => _token.value;
+  set token(value) => _token.set(value);
+  get name => _name.value;
+  set name(value) => _name.set(value);
+  get password => _password.value;
+  set password(value) => _password.set(value);
+  get uid => _uid.value;
+  set uid(value) => _uid.set(value);
+  get user => types.User(id: uid);
 
-  Future<bool> login() async {
-    final username = await getUsername();
-    final password = await getPassword();
-    debugPrint('login: username=$username password=$password');
-    if (username.isEmpty || password.isEmpty) return false;
-    try {
-      final res = await dio.post("$api/token", data: {
-        "username": username, "password": password});
-      if (res.statusCode == HttpStatus.ok) {
-        var data = jsonDecode(res.toString());
-        final token = data['token'];
-        dio = Dio(BaseOptions(headers: {
-          HttpHeaders.authorizationHeader: "Bearer $token"
-        }));
-
-        debugPrint('token=$token');
-        setToken(token);
-        return true;
-      }
-    } on Exception catch(e) {
-      debugPrint("login 网络错误");
-      return false;
+  Future _handleDioError(DioError e, {int retry = 0}) async {
+    final res = e.response;
+    final req = e.requestOptions;
+    final data = jsonDecode(res.toString());
+    final code = res == null ? -1 : res.statusCode;
+    debugPrint("api error, path=${req.path} code=$code error=${data['error']}");
+    switch (code) {
+      case HttpStatus.unauthorized:
+        if (await login()) {
+          final res = await request(req.path,
+              data: req.data,
+              queryParameters: req.queryParameters,
+              options: Options(method: req.method),
+              retry: retry);
+          return res;
+        }
+        break;
     }
-    return false;
   }
 
-  Future<List<UserProfile>> getFriends({int depth=0}) async {
+  Future<Response?> request(String path,
+      {data,
+      Map<String, dynamic>? queryParameters,
+      Options? options,
+      int retry = 0}) async {
+    if (retry < 0) return null;
     try {
-      final res = await dio.get('$api/friends');
-      switch (res.statusCode) {
-        case HttpStatus.ok:
-          var data = jsonDecode(res.toString());
-          List<UserProfile> friends = [];
-          data["friends"].forEach((v) => friends.add(UserProfile.fromJson(v)));
-          debugPrint(friends.toString());
-          return friends;
-        case HttpStatus.unauthorized:
-          if (depth < 3) {
-            login();
-            return getFriends(depth: depth+1);
-          }
+      final res = await dio.request(
+        path,
+        data: data,
+        queryParameters: queryParameters,
+        options: options,
+      );
+      return res;
+    } on DioError catch (e) {
+      final ret = await _handleDioError(e, retry: retry);
+      if (ret is Response) {
+        return ret;
       }
-    } on Exception catch (e) {
-      debugPrint("网络错误");
-      return [];
     }
-    return [];
-  }
-
-  Future<String> getUsername() async {
-    final prefs = await SharedPreferences.getInstance();
-    return prefs.getString("username") ?? "";
-  }
-  Future<String> getPassword() async {
-    final prefs = await SharedPreferences.getInstance();
-    return prefs.getString("password") ?? "";
-  }
-
-  Future<bool> setUsername(String username) async {
-    final prefs = await SharedPreferences.getInstance();
-    return prefs.setString("username", username);
-  }
-  Future<bool> setUID(int uid) async {
-    final prefs = await SharedPreferences.getInstance();
-    return prefs.setInt("uid", uid);
-  }
-
-  Future<int> getUID() async {
-    final prefs = await SharedPreferences.getInstance();
-    int uid = prefs.getInt("uid") ?? 0;
-    if (uid == 0) uid = await home();
-    print('uid=$uid');
-    return uid;
-  }
-
-  Future<bool> setPassword(String password) async {
-    final prefs = await SharedPreferences.getInstance();
-    return prefs.setString("password", password);
+    return null;
   }
 
-  Future<bool> clearPassword() async {
-    final prefs = await SharedPreferences.getInstance();
-    return prefs.remove("password");
+  Future<bool> get authorized async {
+    final res = await request("$server/home", options: Options(method: "get"));
+    if (res?.statusCode == HttpStatus.ok) {
+      debugPrint("token=$token authorized");
+      final data = jsonDecode(res.toString());
+      debugPrint("authorized: ${data.toString()}");
+      uid = data["uid"].toString();
+      return true;
+    }
+    return false;
   }
 
-  Future<bool> clearToken() async {
-    final prefs = await SharedPreferences.getInstance();
-    return prefs.remove("token");
-  }
-  Future<bool> clearUID() async {
-    final prefs = await SharedPreferences.getInstance();
-    return prefs.remove("uid");
-  }
+  Future<bool> login() async {
+    final name = _name.value, password = _password.value;
+    debugPrint('login: name=$name password=$password');
+    if (name.isEmpty || password.isEmpty) return false;
 
-  Future<String> getToken() async {
-    final prefs = await SharedPreferences.getInstance();
-    return prefs.getString("token") ?? "";
+    final res = await request("$server/token",
+        data: {"name": name, "password": password},
+        options: Options(method: "post"));
+    if (res?.statusCode == HttpStatus.ok) {
+      final data = jsonDecode(res.toString());
+      token = data['token'];
+      debugPrint('login ok, token=$token');
+      // Todo: 修改uid的更新方式为:login时获取profile
+      return authorized;
+    }
+    return false;
   }
 
-  Future<bool> setToken(String token) async {
-    final prefs = await SharedPreferences.getInstance();
-    return prefs.setString("token", token);
+  logout() {
+    token = "";
+    password = "";
   }
 
   Future<bool> preLogin() async {
-    if (await home() > 0) {
-      return true;
-    }
+    if (await authorized) return true;
     return login();
   }
 
-  logout() async {
-    await method.clearToken();
-    await method.clearPassword();
-    await method.clearUID();
-    dio = Dio();
+  Future<List<Profile>?> getFriends() async {
+    final res =
+        await request('$server/friends', options: Options(method: "get"));
+    if (res?.statusCode == HttpStatus.ok) {
+      var data = jsonDecode(res.toString());
+      List<Profile> friends = [];
+      data["friends"].forEach((v) => friends.add(Profile.fromJson(v)));
+      // debugPrint(friends.toString());
+      return friends;
+    }
+    return null;
   }
 
-  Future<UserProfile?> getProfile([String? name]) async {
-    name = name ?? await getUsername();
+  Future<Profile?> getProfile({String? name}) async {
+    name = name ?? _name.value;
     debugPrint("get profile of $name");
-    final res = await dio.get("$api/profile/$name");
-    if (res.statusCode == HttpStatus.ok) {
+    final res =
+        await request("$server/profile/$name", options: Options(method: "get"));
+    if (res?.statusCode == HttpStatus.ok) {
       final data = jsonDecode(res.toString());
-      final profile = UserProfile.fromJson(data["profile"]);
+      final profile = Profile.fromJson(data["profile"]);
       debugPrint("profile get, name=${profile.name}");
       return profile;
     }
@@ -172,14 +169,14 @@ class Method {
   }
 
   Future download(
-      String url,
-      String savePath, {
-        Map<String, dynamic>? queryParams,
-        CancelToken? cancelToken,
-        dynamic data,
-        Options? options,
-        void Function(int, int)? onReceiveProgress,
-      }) async {
+    String url,
+    String savePath, {
+    Map<String, dynamic>? queryParams,
+    CancelToken? cancelToken,
+    dynamic data,
+    Options? options,
+    void Function(int, int)? onReceiveProgress,
+  }) async {
     try {
       return await dio.download(
         url,
@@ -200,7 +197,7 @@ class Method {
         }
       }
     } on Exception catch (e) {
-      EasyLoading.showError(e.toString());
+      // EasyLoading.showError(e.toString());
     }
   }
 
@@ -224,12 +221,50 @@ class Method {
 
   Future<bool> updateAvatar(Uint8List buff, String suf) async {
     final file = MultipartFile.fromBytes(buff, filename: 'avatar$suf');
-    final res = await dio.post("$api/avatar", data: FormData.fromMap({"file": file}));
-    if (res.statusCode == HttpStatus.ok) {
-      return true;
+    final res =
+        await request("$server/avatar", data: FormData.fromMap({"file": file}));
+    return res?.statusCode == HttpStatus.ok;
+  }
+
+  Future<List<types.Message>?> getMessages(String roomId) async {
+    final res = await request("$server/messages/$roomId", options: Options(method: "get"));
+    if (res?.statusCode != HttpStatus.ok) return null;
+    final data = jsonDecode(res.toString());
+    List<types.Message> messages = [];
+    debugPrint(data.toString());
+    for (var msg in data["messages"]) {
+      messages.add(types.TextMessage(
+        author: types.User(id: msg["authorId"].toString()),
+        id: msg["id"],
+        text: msg["content"],
+        createdAt: msg["createdAt"]*1000 ?? 0
+      ));
     }
-    return false;
+    messages.sort((a, b) => b.createdAt!.compareTo(a.createdAt!));
+    debugPrint(messages.toString());
+    return messages;
   }
 
+  Future<bool> sendMessage(
+      String roomId, types.MessageType type, String content,
+      {int receiverId = 0}) async {
+    final res = await request("$server/message", data: {
+      "roomId": roomId,
+      "type": type.name,
+      "content": content,
+      "receiverId": receiverId
+    }, options: Options(method: "post"));
+    return res?.statusCode == HttpStatus.ok;
+  }
 
-}
+  String directRoomId(String id) {
+    String a = uid, b = id;
+    final res = a.compareTo(b);
+    switch (res) {
+      case 0: throw Exception("id equals to current user's id");
+      case 1: return "direct:$b,$a";
+      case -1: return "direct:$a,$b";
+    }
+    return "";
+  }
+}

+ 0 - 0
lib/basic/Model.dart


+ 161 - 0
lib/basic/Test.dart

@@ -0,0 +1,161 @@
+
+
+import 'dart:convert';
+import 'dart:ffi';
+import 'dart:io';
+import 'dart:math';
+
+import 'package:ffi/ffi.dart';
+import 'package:flutter/cupertino.dart';
+import 'package:flutter/foundation.dart';
+import 'package:path_provider/path_provider.dart';
+import 'package:prime_chat/screens/ChatScreen.dart';
+import 'package:tuple/tuple.dart';
+
+import 'Encrypt.dart';
+
+void testPrint(bool x, String name) {
+  print("test $name: $x");
+}
+
+Uint8List randomVector(int length) {
+  final random = Random.secure();
+  final values = List<int>.generate(length, (i) => random.nextInt(255));
+  return Uint8List.fromList(values);
+}
+
+void testSM2(Pointer<SM2Key> key) {
+  const plaintext = "Hello, GmSSL!";
+  var plain = Uint8List.fromList(plaintext.codeUnits);
+  plain = randomVector(255);
+  try {
+    final encrypted = sm2Encrypt(key, plain);
+    final decrypted = sm2Decrypt(key, encrypted);
+    testPrint(listEquals(decrypted, plain), "sm2 encrypt & decrypt");
+  } on Error catch(e) {
+    debugPrint(e.toString());
+    return;
+  }
+
+}
+
+Future<void> testSM2PrivateKeyPem(Pointer<SM2Key> key) async {
+
+  final pass = "123456".toNativeUtf8();
+  final private = key.privateKey.list;
+  final tempDirPath = (await getTemporaryDirectory()).path;
+  final path = "$tempDirPath/private.pem";
+  sm2PrivateKeyInfoEncryptToPEM(key, pass, path);
+  sm2PrivateKeyInfoDecryptFromPEM(key, pass, path);
+  testPrint(listEquals(private, key.privateKey.list), "private key <-> PEM");
+}
+
+void testSM2PublicKeyDER(Pointer<SM2Key> key) {
+  final public = Tuple2(key.publicKey.x.list, key.publicKey.y.list);
+  try {
+    final der = sm2PublicKeyInfoToDER(key);
+    debugPrint("der.length = ${der.length}");
+    debugPrint("der = $der");
+    final encoded = base64Encode(der);
+    debugPrint("pem = $encoded");
+    final res = sm2PublicKeyInfoFromDER(key, der);
+    // assert(res>=0, "sm2PublicKeyInfoFromDER error");
+    if (res != 1) {
+      debugPrint("sm2PublicKeyInfoFromDER error! res == $res");
+    }
+  } on Error catch(e) {
+    debugPrint(e.toString());
+    return;
+  }
+  testPrint(listEquals(public.item1, key.publicKey.x.list) &&
+      listEquals(public.item2, key.publicKey.y.list), "public key <-> DER");
+}
+
+void testSM4() {
+  final plain = randomVector(1<<16);
+  // debugPrint("plain=");
+  // debugPrint(base64Encode(plain));
+  final sm4key = randomVector(16);
+  final iv = randomVector(16);
+  final sm4enc = sm4cbcEncrypt(plain, sm4key, iv);
+  // debugPrint("sm4enc=");
+  // debugPrint(base64Encode(sm4enc));
+  final sm4dec = sm4cbcDecrypt(sm4enc, sm4key, iv);
+  // debugPrint("sm4dec=");
+  // debugPrint(base64Encode(sm4dec));
+  testPrint(listEquals(plain, sm4dec), "sm4 encrypt & decrypt");
+}
+
+void testPemToDerToKey(Pointer<SM2Key> key, String tempPath) {
+
+  final public = Tuple2(key.publicKey.x.list, key.publicKey.y.list);
+  final private = key.privateKey.list;
+  assert(sm2PublicKeyInfoToPEM(key, tempPath) == 1);
+  File pemFile = File(tempPath);
+  final lines = pemFile.readAsLinesSync();
+  lines.remove("-----BEGIN PUBLIC KEY-----");
+  lines.remove("-----END PUBLIC KEY-----");
+  final content = lines.join();
+  debugPrint("pem=$content");
+  final der = base64Decode(content);
+  debugPrint("der.length=${der.length} der=$der");
+  assert(sm2PublicKeyInfoFromDER(key, der)==1);
+  testPrint(listEquals(public.item1, key.publicKey.x.list) &&
+      listEquals(public.item2, key.publicKey.y.list), "der -> publicKey");
+  final equals = listEquals(private, key.privateKey.list);
+  debugPrint("privateKey equals : $equals");
+}
+
+
+void encryptTest() async {
+  final tempDirPath = (await getTemporaryDirectory()).path;
+  final errorPath = "$tempDirPath/error.txt";
+  final tempPath = "$tempDirPath/temp.pem";
+  final pf = freopen(errorPath, "w", stderrAddress);
+  debugPrint("==========encrypt test start===========");
+  try {
+    // int s = add(1, 5);
+    // debugPrint("test libadd sum = $s");
+    final key = gm.newSM2Key();
+    key.generate();
+
+    testPemToDerToKey(key, tempPath);
+
+    final key1 = gm.newSM2Key();
+
+    testSM2(key);
+    // testSM2(key);
+    // testSM2(key);
+    // testSM2(key);
+    await testSM2PrivateKeyPem(key);
+    testSM2(key);
+    testSM2PublicKeyDER(key);
+    // testSM2PublicKeyDER(key);
+    // testSM2PublicKeyDER(key);
+    // testSM2PublicKeyDER(key);
+    // testSM2(key);
+    // testSM2(key);
+    // testSM2(key);
+    // testSM2(key);
+
+    // TODO: ERROR sm2PublicKeyInfoToDER
+
+    // final private = sm2PrivateKeyInfoDecryptFromPEMString(key, pass);
+    // final publicPem = await sm2PublicKeyInfoToPEMString(key);
+    // debugPrint(privatePem);
+    // debugPrint(publicPem);
+
+    testSM4();
+
+
+  } on Error catch (e) {
+    debugPrint(e.toString());
+  }
+  debugPrint("==========encrypt test end===========");
+  gm.fclose(pf);
+  File errorFile = File(errorPath);
+  final errorInfo = errorFile.readAsStringSync();
+  debugPrint("error info = ");
+  debugPrint(errorInfo);
+
+}

+ 0 - 175
lib/screens/AccountScreen.dart

@@ -1,175 +0,0 @@
-import 'dart:async';
-import 'dart:io';
-
-import 'package:flutter/gestures.dart';
-import 'package:flutter/material.dart';
-import '../basic/Common.dart';
-import '../basic/Method.dart';
-import '../basic/enum/ErrorTypes.dart';
-import 'AboutScreen.dart';
-import 'AppScreen.dart';
-import 'components/ContentLoading.dart';
-
-// 账户设置
-class AccountScreen extends StatefulWidget {
-  const AccountScreen({Key? key}) : super(key: key);
-
-  @override
-  _AccountScreenState createState() => _AccountScreenState();
-}
-
-class _AccountScreenState extends State<AccountScreen> {
-  late bool _logging = false;
-  late String _username = "";
-  late String _password = "";
-  late StreamSubscription<String?> _linkSubscription;
-
-  @override
-  void initState() {
-    // _linkSubscription = linkSubscript(context);
-    _loadProperties();
-    super.initState();
-    // Future.delayed(Duration.zero, () async {
-    //   versionPop(context);
-    //   versionEvent.subscribe(_versionSub);
-    // });
-  }
-
-  // @override
-  // void dispose() {
-    // _linkSubscription.cancel();
-    // versionEvent.unsubscribe(_versionSub);
-    // super.dispose();
-  // }
-
-  _versionSub(_) {
-    // versionPop(context);
-  }
-
-
-  Future _loadProperties() async {
-    final username = await method.getUsername();
-    final password = await method.getPassword();
-    setState(() {
-      _username = username;
-      _password = password;
-    });
-  }
-
-  @override
-  Widget build(BuildContext context) {
-    if (_logging) {
-      return _buildLogging();
-    }
-    return _buildGui();
-  }
-
-  Widget _buildLogging() {
-    return const Scaffold(
-      body: ContentLoading(label: '登录中'),
-    );
-  }
-
-  Widget _buildGui() {
-    return Scaffold(
-      appBar: AppBar(
-        title: const Text('登录'),
-        actions: [
-          IconButton(
-            onPressed: () {
-              Navigator.push(
-                context,
-                MaterialPageRoute(
-                  builder: (context) => const AboutScreen(
-                  ),
-                ),
-              );
-            },
-            icon: const Text('关于'),
-          ),
-          IconButton(
-            onPressed: _logIn,
-            icon: const Icon(Icons.save),
-          ),
-        ],
-      ),
-      body: ListView(
-        children: [
-          ListTile(
-            title: const Text("账号"),
-            subtitle: Text(_username == "" ? "未设置" : _username),
-            onTap: () async {
-              String? input = await displayTextInputDialog(
-                context,
-                src: _username,
-                title: '账号',
-                hint: '请输入账号',
-              );
-              if (input != null) {
-                await method.setUsername(input);
-                setState(() {
-                  _username = input;
-                });
-              }
-            },
-          ),
-          ListTile(
-            title: const Text("密码"),
-            subtitle: Text(_password == "" ? "未设置" : '\u2022' * 10),
-            onTap: () async {
-              String? input = await displayTextInputDialog(
-                context,
-                src: _password,
-                title: '密码',
-                hint: '请输入密码',
-                isPasswd: true,
-              );
-              if (input != null) {
-                await method.setPassword(input);
-                setState(() {
-                  _password = input;
-                });
-              }
-            },
-          ),
-        ],
-      ),
-    );
-  }
-
-  _logIn() async {
-    setState(() {
-      _logging = true;
-    });
-    try {
-      await method.login();
-      if (!mounted) return; // don't use context after async if you're not sure your widget is mounted.
-      Navigator.pushReplacement(
-        context,
-        MaterialPageRoute(builder: (context) => const AppScreen()),
-      );
-    } catch (e, s) {
-      print("$e\n$s");
-      setState(() {
-        _logging = false;
-      });
-      var message = "请检查账号密码或网络环境";
-      switch (errorType("$e")) {
-        case ERROR_TYPE_NETWORK:
-          message = "网络不通";
-          break;
-        case ERROR_TYPE_TIME:
-          message = "请检查设备时间";
-          break;
-      }
-      if ("$e".contains("email") && "$e".contains("password")) {
-        message = "请检查账号密码";
-      }
-      alertDialog(
-        context,
-        '登录失败',
-        "$message\n$e",
-      );
-    }
-  }
-}

+ 1 - 1
lib/screens/AddFriendScreen.dart

@@ -4,7 +4,7 @@ import 'package:prime_chat/basic/Entities.dart';
 class AddFriendScreen extends StatefulWidget {
   const AddFriendScreen(this.profile, {Key? key}) : super(key: key);
 
-  final UserProfile profile;
+  final Profile profile;
 
   @override
   State<AddFriendScreen> createState() => _AddFriendScreenState();

+ 69 - 0
lib/screens/ChatScreen.dart

@@ -0,0 +1,69 @@
+import 'dart:convert';
+
+import 'package:flutter/material.dart';
+import 'package:flutter_chat_ui/flutter_chat_ui.dart';
+import 'package:flutter_chat_types/flutter_chat_types.dart' as types;
+import 'dart:math';
+
+import '../basic/Entities.dart';
+import '../basic/Method.dart';
+
+
+String randomString() {
+  final random = Random.secure();
+  final values = List<int>.generate(16, (i) => random.nextInt(255));
+  return base64UrlEncode(values);
+}
+
+class ChatScreen extends StatefulWidget {
+  const ChatScreen(this.profile, this.roomId, {Key? key}) : super(key: key);
+
+  final Profile profile;
+  final String roomId;
+
+  @override
+  State<ChatScreen> createState() => _ChatScreenState();
+}
+
+class _ChatScreenState extends State<ChatScreen> {
+  late List<types.Message> _messages = [];
+
+  @override
+  void initState() {
+    _load();
+    super.initState();
+  }
+
+  Future _load() async {
+    final ret = await method.getMessages(widget.roomId) ?? [];
+    setState(() {
+      _messages = ret;
+    });
+  }
+
+  @override
+  Widget build(BuildContext context) => Scaffold(
+    body: Chat(
+      messages: _messages,
+      onSendPressed: _handleSendPressed,
+      user: method.user,
+    ),
+  );
+
+  void _addMessage(types.Message message) {
+    setState(() {
+      _messages.insert(0, message);
+    });
+  }
+
+  void _handleSendPressed(types.PartialText message) {
+    final textMessage = types.TextMessage(
+      author: method.user,
+      id: randomString(),
+      text: message.text,
+    );
+    method.sendMessage(widget.roomId, types.MessageType.text, message.text);
+
+    _addMessage(textMessage);
+  }
+}

+ 7 - 5
lib/screens/ContactsScreen.dart

@@ -31,8 +31,10 @@ class _ContactsScreenState extends State<ContactsScreen> {
   void _loadFriends() {
     method.getFriends().then((value) {
       _friends = [];
-      for (var e in value) {
-        _friends.add(FriendInfo(name: e.name, avatar: e.avatar));
+      if (value != null) {
+        for (var e in value) {
+          _friends.add(FriendInfo(profile: e));
+        }
       }
       _handleList(_friends);
     });
@@ -56,7 +58,7 @@ class _ContactsScreenState extends State<ContactsScreen> {
     SuspensionUtil.setShowSuspensionStatus(_friends);
 
     // add header.
-    _friends.insert(0, FriendInfo(name: 'header', tagIndex: '↑'));
+    _friends.insert(0, FriendInfo(profile: const Profile(name: "header"), tagIndex: '↑'));
 
     setState(() {});
   }
@@ -123,13 +125,13 @@ class _ContactsScreenState extends State<ContactsScreen> {
           child: _buildSusWidget(susTag),
         ),
         ListTile(
-          leading: Avatar(model.avatar),
+          leading: Avatar(model.profile.avatar),
           title: Text(model.name),
           onTap: () {
             Navigator.push(
                 context, MaterialPageRoute(
                 builder: (context) => ProfileScreen(
-                    profile: model, isFriend: true)));
+                    profile: model.profile, isFriend: true)));
           },
         )
       ],

+ 11 - 4
lib/screens/InitScreen.dart

@@ -1,13 +1,17 @@
 
 // 初始化界面
+import 'dart:ffi';
+
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/services.dart';
 
 import '../basic/Method.dart';
-import 'AccountScreen.dart';
+import '../basic/Test.dart';
 import 'AppScreen.dart';
 import 'LoginScreen.dart';
+import '../basic/Global.dart' as glob;
+import '../basic/Encrypt.dart';
 
 class InitScreen extends StatefulWidget {
   const InitScreen({Key? key}) : super(key: key);
@@ -26,7 +30,9 @@ class _InitScreenState extends State<InitScreen> {
   }
 
   Future<dynamic> _init() async {
-
+    encryptTest();
+    await glob.init();
+    await method.init();
     if (_authenticating) {
       _goAuthentication();
     } else {
@@ -69,7 +75,8 @@ class _InitScreenState extends State<InitScreen> {
 
   Future _goApplication() async {
     // 登录, 如果token失效重新登录, 网络不好的时候可能需要1分钟
-    if (await method.preLogin()) {
+    // Todo: 直接进入AppScreen
+    if (await method.authorized) {
       // 如果token或username+password有效则直接进入登录好的界面
       Navigator.pushReplacement(
         context,
@@ -79,7 +86,7 @@ class _InitScreenState extends State<InitScreen> {
       // 否则跳转到登录页
       Navigator.pushReplacement(
         context,
-        MaterialPageRoute(builder: (context) => LoginScreen()),
+        MaterialPageRoute(builder: (context) => const LoginScreen()),
       );
     }
   }

+ 5 - 3
lib/screens/LoginScreen.dart

@@ -11,13 +11,15 @@ const users = {
 };
 
 class LoginScreen extends StatelessWidget {
-  Duration get loginTime => Duration(milliseconds: 2250);
+  const LoginScreen({Key? key}) : super(key: key);
+
+  get loginTime => const Duration(milliseconds: 2250);
 
   _authUser(BuildContext context) {
     return (LoginData data) {
       debugPrint('Name: ${data.name}, Password: ${data.password}');
-      method.setUsername(data.name);
-      method.setPassword(data.password);
+      method.name = data.name;
+      method.password = data.password;
       method.login().then((value) {
         if (!value) return false;
         Navigator.pushReplacement(

+ 6 - 22
lib/screens/ProfileScreen.dart

@@ -4,16 +4,18 @@ import 'package:prime_chat/screens/AddFriendScreen.dart';
 import 'package:prime_chat/screens/components/Avatar.dart';
 import 'package:prime_chat/screens/components/UserProfileCard.dart';
 
+import '../basic/Method.dart';
+import 'ChatScreen.dart';
+
 class ProfileScreen extends StatelessWidget {
   const ProfileScreen({Key? key, required this.profile, required this.isFriend})
       : super(key: key);
 
   final bool isFriend;
-  final UserProfile profile;
+  final Profile profile;
 
   @override
   Widget build(BuildContext context) {
-    debugPrint("build profile screen of ${profile.name}");
     List<Widget> children = [
       UserProfileCard(load: () async => profile),
       const Divider(),
@@ -22,8 +24,8 @@ class ProfileScreen extends StatelessWidget {
     if (isFriend) {
       children
           .add(_buildItem(context, Icons.messenger_outline, "发消息", onTap: () {
-        // Navigator.push(context,
-        //     MaterialPageRoute(builder: (context) => AddFriendScreen(profile)));
+        Navigator.push(context,
+            MaterialPageRoute(builder: (context) => ChatScreen(profile, method.directRoomId(profile.id))));
       }));
     } else {
       children.add(_buildItem(context, Icons.person_add_alt_outlined, "加好友", onTap: () {
@@ -38,16 +40,6 @@ class ProfileScreen extends StatelessWidget {
     );
   }
 
-  Widget _buildAddFriend(BuildContext context) {
-    return GestureDetector(
-      child: const Text("添加好友"),
-      onTap: () {
-        Navigator.push(context,
-            MaterialPageRoute(builder: (context) => AddFriendScreen(profile)));
-      },
-    );
-  }
-
   Widget _buildItem(BuildContext context, IconData icon, String title,
       {void Function()? onTap}) {
     return InkWell(
@@ -71,12 +63,4 @@ class ProfileScreen extends StatelessWidget {
           ])),
     );
   }
-
-  Widget _buildInfo(BuildContext context) {
-    return Column(
-      children: [
-        Text(profile.name),
-      ],
-    );
-  }
 }

+ 2 - 2
lib/screens/SearchScreen.dart

@@ -37,11 +37,11 @@ class _SearchScreenState extends State<SearchScreen> {
         ));
   }
 
-  _search(String value) async {
+  _search(String name) async {
     setState(() {
       loading = true;
     });
-    final profile = await method.getProfile(value);
+    final profile = await method.getProfile(name: name);
     debugPrint("get profile = ${profile?.name}");
     setState(() {
       loading = false;

+ 0 - 1
lib/screens/SpaceScreen.dart

@@ -3,7 +3,6 @@ import '../basic/Common.dart';
 
 import '../basic/Method.dart';
 import 'AboutScreen.dart';
-import 'AccountScreen.dart';
 import 'LoginScreen.dart';
 import 'components/Badge.dart';
 import 'components/UserProfileCard.dart';

+ 4 - 4
lib/screens/components/UserProfileCard.dart

@@ -20,14 +20,14 @@ const double _cardHeight = 180;
 class UserProfileCard extends StatefulWidget {
   const UserProfileCard({Key? key, required this.load}) : super(key: key);
 
-  final Future<UserProfile?> Function() load;
+  final Future<Profile?> Function() load;
 
   @override
   State<StatefulWidget> createState() => _UserProfileCardState();
 }
 
 class _UserProfileCardState extends State<UserProfileCard> {
-  late Future<UserProfile?> _future = widget.load();
+  late Future<Profile?> _future = widget.load();
 
   // Future<UserProfile?> _load() async {
   //   var profile = await method.getProfile(widget.name);
@@ -52,8 +52,8 @@ class _UserProfileCardState extends State<UserProfileCard> {
       },
       height: _cardHeight,
       successBuilder:
-          (BuildContext context, AsyncSnapshot<UserProfile?> snapshot) {
-        UserProfile profile = snapshot.data!;
+          (BuildContext context, AsyncSnapshot<Profile?> snapshot) {
+        Profile profile = snapshot.data!;
         return Stack(
           children: [
             SizedBox(

+ 92 - 1
pubspec.lock

@@ -78,6 +78,13 @@ packages:
       url: "https://pub.dartlang.org"
     source: hosted
     version: "1.0.5"
+  diffutil_dart:
+    dependency: transitive
+    description:
+      name: diffutil_dart
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "3.0.0"
   dio:
     dependency: "direct main"
     description:
@@ -85,6 +92,13 @@ packages:
       url: "https://pub.dartlang.org"
     source: hosted
     version: "4.0.6"
+  equatable:
+    dependency: transitive
+    description:
+      name: equatable
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "2.0.5"
   extended_image:
     dependency: transitive
     description:
@@ -107,7 +121,7 @@ packages:
     source: hosted
     version: "1.3.1"
   ffi:
-    dependency: transitive
+    dependency: "direct main"
     description:
       name: ffi
       url: "https://pub.dartlang.org"
@@ -125,6 +139,20 @@ packages:
     description: flutter
     source: sdk
     version: "0.0.0"
+  flutter_chat_types:
+    dependency: transitive
+    description:
+      name: flutter_chat_types
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "3.6.0"
+  flutter_chat_ui:
+    dependency: "direct main"
+    description:
+      name: flutter_chat_ui
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "1.6.6"
   flutter_easyloading:
     dependency: "direct main"
     description:
@@ -132,6 +160,20 @@ packages:
       url: "https://pub.dartlang.org"
     source: hosted
     version: "3.0.5"
+  flutter_link_previewer:
+    dependency: transitive
+    description:
+      name: flutter_link_previewer
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "3.2.0"
+  flutter_linkify:
+    dependency: transitive
+    description:
+      name: flutter_linkify
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "5.0.2"
   flutter_lints:
     dependency: "direct dev"
     description:
@@ -151,6 +193,13 @@ packages:
       url: "https://pub.dartlang.org"
     source: hosted
     version: "4.1.1"
+  flutter_parsed_text:
+    dependency: transitive
+    description:
+      name: flutter_parsed_text
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "2.2.1"
   flutter_spinkit:
     dependency: transitive
     description:
@@ -252,6 +301,20 @@ packages:
       url: "https://pub.dartlang.org"
     source: hosted
     version: "0.6.4"
+  json_annotation:
+    dependency: transitive
+    description:
+      name: json_annotation
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "4.7.0"
+  linkify:
+    dependency: transitive
+    description:
+      name: linkify
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "4.1.0"
   lints:
     dependency: transitive
     description:
@@ -455,6 +518,13 @@ packages:
       url: "https://pub.dartlang.org"
     source: hosted
     version: "3.2.1"
+  scroll_to_index:
+    dependency: transitive
+    description:
+      name: scroll_to_index
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "3.0.1"
   scrollable_positioned_list:
     dependency: transitive
     description:
@@ -572,6 +642,13 @@ packages:
       url: "https://pub.dartlang.org"
     source: hosted
     version: "0.4.12"
+  tuple:
+    dependency: "direct main"
+    description:
+      name: tuple
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "2.0.1"
   typed_data:
     dependency: transitive
     description:
@@ -635,6 +712,13 @@ packages:
       url: "https://pub.dartlang.org"
     source: hosted
     version: "3.0.1"
+  uuid:
+    dependency: "direct main"
+    description:
+      name: uuid
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "3.0.7"
   vector_math:
     dependency: transitive
     description:
@@ -677,6 +761,13 @@ packages:
       url: "https://pub.dartlang.org"
     source: hosted
     version: "2.0.13"
+  visibility_detector:
+    dependency: transitive
+    description:
+      name: visibility_detector
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "0.3.3"
   wechat_assets_picker:
     dependency: "direct main"
     description:

+ 4 - 0
pubspec.yaml

@@ -50,6 +50,10 @@ dependencies:
   photo_view: ^0.14.0
   flutter_login: ^4.1.1
   lpinyin: ^2.0.3
+  flutter_chat_ui: ^1.6.6
+  uuid: ^3.0.7
+  ffi: ^2.0.1
+  tuple: ^2.0.1
 
 dev_dependencies:
   flutter_test: