소스 검색

login (not completed)

ignalxy 4 년 전
부모
커밋
3fc7fd1e5c

+ 50 - 0
lib/common/global.dart

@@ -0,0 +1,50 @@
+// 提供五套可选主题色
+import 'dart:convert';
+
+import 'package:e2ee_chat/common/profile.dart';
+import 'package:e2ee_chat/network/api.dart';
+import 'package:e2ee_chat/models/user_model.dart';
+import 'package:flutter/material.dart';
+import 'package:shared_preferences/shared_preferences.dart';
+
+const _themes = <MaterialColor>[
+  Colors.blue,
+  Colors.cyan,
+  Colors.teal,
+  Colors.green,
+  Colors.red,
+];
+
+class Global {
+  static Profile profile = Profile();
+
+  // 可选的主题列表
+  static List<MaterialColor> get themes => _themes;
+
+  // 是否为release版
+  static bool get isRelease => bool.fromEnvironment("dart.vm.product");
+
+  static final String emptyToken = "token";
+
+  //初始化全局信息,会在APP启动时执行
+  static init() async {
+    var _prefs = await SharedPreferences.getInstance();
+    var _profile = _prefs.getString("profile");
+    if (_profile != null) {
+      try {
+        profile = Profile.fromJson(jsonDecode(_profile));
+        profile.isLogin = false;
+      } catch (e) {
+        print(e);
+      }
+    }
+    await Api.init();
+    UserModel().login();
+  }
+
+  // 持久化Profile信息
+  static saveProfile() async {
+    var _prefs = await SharedPreferences.getInstance();
+    _prefs.setString("profile", jsonEncode(profile.toJson()));
+  }
+}

+ 36 - 0
lib/common/profile.dart

@@ -0,0 +1,36 @@
+import 'package:flutter_secure_storage/flutter_secure_storage.dart';
+import 'package:json_annotation/json_annotation.dart';
+
+import 'package:e2ee_chat/common/global.dart';
+
+part 'Profile.g.dart';
+
+@JsonSerializable()
+class Profile {
+  Profile();
+
+  factory Profile.fromJson(Map<String, dynamic> json) => _$ProfileFromJson(json);
+  Map<String, dynamic> toJson() => _$ProfileToJson(json);
+
+  int theme = Global.themes[0].value;
+  String? username;
+  bool isLogin = false;
+  String locale = "zh";
+
+
+  String? get token {
+    if (username == null) return null;
+    final storage = FlutterSecureStorage();
+    String? token;
+    storage.read(key: _tokenKey).then((v) => token = v);
+    return token;
+  }
+
+  set token(String? token) {
+    assert(username != null, "username == null when setting token");
+    final storage = FlutterSecureStorage();
+    storage.write(key: _tokenKey, value: token);
+  }
+
+  String get _tokenKey => "e2ee_chat token of $username";
+}

+ 68 - 100
lib/main.dart

@@ -1,113 +1,81 @@
+import 'package:e2ee_chat/routes/home.dart';
 import 'package:flutter/material.dart';
+import 'package:provider/provider.dart';
+import 'package:provider/single_child_widget.dart';
+import 'package:flutter_localizations/flutter_localizations.dart';
+
+import 'common/global.dart';
+import 'models/locale_model.dart';
+import 'models/theme_model.dart';
+import 'models/user_model.dart';
+import 'routes/language_route.dart';
+import 'routes/login.dart';
+import 'routes/theme_change.dart';
 
 void main() {
-  runApp(MyApp());
+  Global.init().then((e) => runApp(MyApp()));
 }
 
 class MyApp extends StatelessWidget {
   // This widget is the root of your application.
   @override
   Widget build(BuildContext context) {
-    return MaterialApp(
-      title: 'Flutter Demo',
-      theme: ThemeData(
-        // This is the theme of your application.
-        //
-        // Try running your application with "flutter run". You'll see the
-        // application has a blue toolbar. Then, without quitting the app, try
-        // changing the primarySwatch below to Colors.green and then invoke
-        // "hot reload" (press "r" in the console where you ran "flutter run",
-        // or simply save your changes to "hot reload" in a Flutter IDE).
-        // Notice that the counter didn't reset back to zero; the application
-        // is not restarted.
-        primarySwatch: Colors.blue,
-      ),
-      home: MyHomePage(title: 'Flutter Demo Home Page'),
-    );
-  }
-}
-
-class MyHomePage extends StatefulWidget {
-  MyHomePage({Key? key, required this.title}) : super(key: key);
-
-  // This widget is the home page of your application. It is stateful, meaning
-  // that it has a State object (defined below) that contains fields that affect
-  // how it looks.
-
-  // This class is the configuration for the state. It holds the values (in this
-  // case the title) provided by the parent (in this case the App widget) and
-  // used by the build method of the State. Fields in a Widget subclass are
-  // always marked "final".
-
-  final String title;
-
-  @override
-  _MyHomePageState createState() => _MyHomePageState();
-}
-
-class _MyHomePageState extends State<MyHomePage> {
-  int _counter = 0;
-
-  void _incrementCounter() {
-    setState(() {
-      // This call to setState tells the Flutter framework that something has
-      // changed in this State, which causes it to rerun the build method below
-      // so that the display can reflect the updated values. If we changed
-      // _counter without calling setState(), then the build method would not be
-      // called again, and so nothing would appear to happen.
-      _counter++;
-    });
-  }
-
-  @override
-  Widget build(BuildContext context) {
-    // This method is rerun every time setState is called, for instance as done
-    // by the _incrementCounter method above.
-    //
-    // The Flutter framework has been optimized to make rerunning build methods
-    // fast, so that you can just rebuild anything that needs updating rather
-    // than having to individually change instances of widgets.
-    return Scaffold(
-      appBar: AppBar(
-        // Here we take the value from the MyHomePage object that was created by
-        // the App.build method, and use it to set our appbar title.
-        title: Text(widget.title),
-      ),
-      body: Center(
-        // Center is a layout widget. It takes a single child and positions it
-        // in the middle of the parent.
-        child: Column(
-          // Column is also a layout widget. It takes a list of children and
-          // arranges them vertically. By default, it sizes itself to fit its
-          // children horizontally, and tries to be as tall as its parent.
-          //
-          // Invoke "debug painting" (press "p" in the console, choose the
-          // "Toggle Debug Paint" action from the Flutter Inspector in Android
-          // Studio, or the "Toggle Debug Paint" command in Visual Studio Code)
-          // to see the wireframe for each widget.
-          //
-          // Column has various properties to control how it sizes itself and
-          // how it positions its children. Here we use mainAxisAlignment to
-          // center the children vertically; the main axis here is the vertical
-          // axis because Columns are vertical (the cross axis would be
-          // horizontal).
-          mainAxisAlignment: MainAxisAlignment.center,
-          children: <Widget>[
-            Text(
-              'You have pushed the button this many times:',
-            ),
-            Text(
-              '$_counter',
-              style: Theme.of(context).textTheme.headline4,
+    return MultiProvider(
+      providers: <SingleChildWidget>[
+        ChangeNotifierProvider.value(value: ThemeModel()),
+        ChangeNotifierProvider.value(value: UserModel()),
+        ChangeNotifierProvider.value(value: LocaleModel()),
+      ],
+      child: Consumer2<ThemeModel, LocaleModel>(
+        builder: (BuildContext context, themeModel, localeModel, Widget? child) {
+          return MaterialApp(
+            theme: ThemeData(
+              primarySwatch: themeModel.theme,
             ),
-          ],
-        ),
+            onGenerateTitle: (context){
+              return GmLocalizations.of(context).title;
+            },
+            home: HomeRoute(), //应用主页
+            locale: localeModel.getLocale(),
+            //我们只支持美国英语和中文简体
+            supportedLocales: [
+              const Locale('en', 'US'), // 美国英语
+              const Locale('zh', 'CN'), // 中文简体
+              //其它Locales
+            ],
+            localizationsDelegates: [
+              // 本地化的代理类
+              GlobalMaterialLocalizations.delegate,
+              GlobalWidgetsLocalizations.delegate,
+              GmLocalizationsDelegate()
+            ],
+            localeResolutionCallback:
+                (Locale? _locale, Iterable<Locale> supportedLocales) {
+              if (localeModel.getLocale() != null) {
+                //如果已经选定语言,则不跟随系统
+                return localeModel.getLocale();
+              } else {
+
+                Locale? locale;
+                //APP语言跟随系统语言,如果系统语言不是中文简体或美国英语,
+                //则默认使用美国英语
+                if (supportedLocales.contains(_locale)) {
+                  locale = _locale;
+                } else {
+                  locale = Locale('en', 'US');
+                }
+                return locale;
+              }
+            },
+            // 注册命名路由表
+            routes: <String, WidgetBuilder>{
+              "login": (context) => LoginRoute(),
+              "themes": (context) => ThemeChangeRoute(),
+              "language": (context) => LanguageRoute(),
+            },
+          );
+        },
       ),
-      floatingActionButton: FloatingActionButton(
-        onPressed: _incrementCounter,
-        tooltip: 'Increment',
-        child: Icon(Icons.add),
-      ), // This trailing comma makes auto-formatting nicer for build methods.
     );
   }
-}
+}

+ 15 - 0
lib/models/locale_model.dart

@@ -0,0 +1,15 @@
+import 'package:e2ee_chat/models/profile.dart';
+import 'package:flutter/material.dart';
+
+class LocaleModel extends ProfileChangeNotifier {
+  LocaleModel();
+
+  String get locale => profile.locale;
+
+  Locale? getLocale() => Locale(locale);
+
+  set locale(String value) {
+    profile.locale = value;
+    notifyListeners();
+  }
+}

+ 15 - 0
lib/models/profile.dart

@@ -0,0 +1,15 @@
+import 'package:e2ee_chat/common/profile.dart';
+import 'package:flutter/cupertino.dart';
+
+import '../common/global.dart';
+
+
+class ProfileChangeNotifier extends ChangeNotifier {
+  Profile get profile => Global.profile;
+
+  @override
+  void notifyListeners() {
+    Global.saveProfile();
+    super.notifyListeners();
+  }
+}

+ 21 - 0
lib/models/theme_model.dart

@@ -0,0 +1,21 @@
+import 'package:flutter/material.dart';
+
+import 'profile.dart';
+import '../common/global.dart';
+
+class ThemeModel extends ProfileChangeNotifier {
+  // 获取当前主题,如果为设置主题,则默认使用蓝色主题
+  MaterialColor get theme => Global.themes
+      .firstWhere((e) => e.value == profile.theme, orElse: () => Colors.blue);
+
+  // 主题改变后,通知其依赖项,新主题会立即生效
+  set theme(MaterialColor color) {
+    if (color != theme) {
+      profile.theme = color.shade500.value;
+      // TODO: What is !. operator ?
+      notifyListeners();
+    }
+  }
+
+
+}

+ 35 - 0
lib/models/user_model.dart

@@ -0,0 +1,35 @@
+import 'package:e2ee_chat/network/api.dart';
+
+import 'profile.dart';
+
+class UserModel extends ProfileChangeNotifier {
+
+  bool get isLogin => profile.isLogin;
+
+  _login(String token) {
+    profile.token = token;
+    profile.isLogin = true;
+    notifyListeners();
+  }
+
+  Future<void> login({String? username, String? password}) async {
+    String? token;
+    if (username != null && password != null) {
+      token = await Api().login(username: username, password: password);
+    } else if (!profile.isLogin && profile.username != null) {
+      if (profile.token != null) {
+        token = await Api().login(username: profile.username!, token: profile.token);
+      }
+    }
+    if (token != null) {
+      _login(token);
+    }
+  }
+
+  Future<bool> logout() async {
+    if (profile.isLogin) {
+      // TODO: logout
+    }
+    return false;
+  }
+}

+ 31 - 0
lib/network/api.dart

@@ -0,0 +1,31 @@
+import 'dart:convert';
+import 'dart:io';
+
+import 'package:crypto/crypto.dart';
+import 'package:dio/dio.dart';
+import 'package:e2ee_chat/common/global.dart';
+
+class Api {
+  Api();
+
+  static Dio dio = Dio(BaseOptions(
+    baseUrl: 'http://127.0.0.1:8000/',
+  ));
+
+  static init() async {
+  }
+
+  Future<String?> login({required String username, String? password, String? token}) async {
+    var data = {"username": username, "token": token ?? Global.emptyToken, "password": password};
+    String _password = "";
+    if (password != null) {
+      var bytes = utf8.encode(password);
+      var digest = sha1.convert(bytes);
+      _password = digest.toString();
+    }
+    var r = await dio.post("/account/login", data: data,
+      // options: _options,
+    );
+    return r.data["token"];
+  }
+}

+ 39 - 0
lib/objectbox-model.json

@@ -0,0 +1,39 @@
+{
+  "_note1": "KEEP THIS FILE! Check it into a version control system (VCS) like git.",
+  "_note2": "ObjectBox manages crucial IDs for your object model. See docs for details.",
+  "_note3": "If you have VCS merge conflicts, you must resolve them according to ObjectBox docs.",
+  "entities": [
+    {
+      "id": "1:3444477729893015694",
+      "lastPropertyId": "2:2305308568754167021",
+      "name": "User",
+      "properties": [
+        {
+          "id": "1:5463014948149082651",
+          "name": "id",
+          "type": 6,
+          "flags": 1
+        },
+        {
+          "id": "2:2305308568754167021",
+          "name": "username",
+          "type": 9,
+          "flags": 2080,
+          "indexId": "1:2185831944762227781"
+        }
+      ],
+      "relations": []
+    }
+  ],
+  "lastEntityId": "1:3444477729893015694",
+  "lastIndexId": "1:2185831944762227781",
+  "lastRelationId": "0:0",
+  "lastSequenceId": "0:0",
+  "modelVersion": 5,
+  "modelVersionParserMinimum": 5,
+  "retiredEntityUids": [],
+  "retiredIndexUids": [],
+  "retiredPropertyUids": [],
+  "retiredRelationUids": [],
+  "version": 1
+}

+ 111 - 0
lib/objectbox.g.dart

@@ -0,0 +1,111 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+// ignore_for_file: camel_case_types
+
+import 'dart:typed_data';
+
+import 'package:objectbox/flatbuffers/flat_buffers.dart' as fb;
+import 'package:objectbox/internal.dart'; // generated code can access "internal" functionality
+import 'package:objectbox/objectbox.dart';
+import 'package:objectbox_flutter_libs/objectbox_flutter_libs.dart';
+
+import 'entity/login.dart';
+
+export 'package:objectbox/objectbox.dart'; // so that callers only have to import this file
+
+final _entities = <ModelEntity>[
+  ModelEntity(
+      id: const IdUid(1, 3444477729893015694),
+      name: 'User',
+      lastPropertyId: const IdUid(2, 2305308568754167021),
+      flags: 0,
+      properties: <ModelProperty>[
+        ModelProperty(
+            id: const IdUid(1, 5463014948149082651),
+            name: 'id',
+            type: 6,
+            flags: 1),
+        ModelProperty(
+            id: const IdUid(2, 2305308568754167021),
+            name: 'username',
+            type: 9,
+            flags: 2080,
+            indexId: const IdUid(1, 2185831944762227781))
+      ],
+      relations: <ModelRelation>[],
+      backlinks: <ModelBacklink>[])
+];
+
+/// Open an ObjectBox store with the model declared in this file.
+Future<Store> openStore(
+        {String? directory,
+        int? maxDBSizeInKB,
+        int? fileMode,
+        int? maxReaders,
+        bool queriesCaseSensitiveDefault = true,
+        String? macosApplicationGroup}) async =>
+    Store(getObjectBoxModel(),
+        directory: directory ?? (await defaultStoreDirectory()).path,
+        maxDBSizeInKB: maxDBSizeInKB,
+        fileMode: fileMode,
+        maxReaders: maxReaders,
+        queriesCaseSensitiveDefault: queriesCaseSensitiveDefault,
+        macosApplicationGroup: macosApplicationGroup);
+
+/// ObjectBox model definition, pass it to [Store] - Store(getObjectBoxModel())
+ModelDefinition getObjectBoxModel() {
+  final model = ModelInfo(
+      entities: _entities,
+      lastEntityId: const IdUid(1, 3444477729893015694),
+      lastIndexId: const IdUid(1, 2185831944762227781),
+      lastRelationId: const IdUid(0, 0),
+      lastSequenceId: const IdUid(0, 0),
+      retiredEntityUids: const [],
+      retiredIndexUids: const [],
+      retiredPropertyUids: const [],
+      retiredRelationUids: const [],
+      modelVersion: 5,
+      modelVersionParserMinimum: 5,
+      version: 1);
+
+  final bindings = <Type, EntityDefinition>{
+    Login: EntityDefinition<Login>(
+        model: _entities[0],
+        toOneRelations: (Login object) => [],
+        toManyRelations: (Login object) => {},
+        getId: (Login object) => object.id,
+        setId: (Login object, int id) {
+          object.id = id;
+        },
+        objectToFB: (Login object, fb.Builder fbb) {
+          final usernameOffset = fbb.writeString(object.username);
+          fbb.startTable(3);
+          fbb.addInt64(0, object.id);
+          fbb.addOffset(1, usernameOffset);
+          fbb.finish(fbb.endTable());
+          return object.id;
+        },
+        objectFromFB: (Store store, ByteData fbData) {
+          final buffer = fb.BufferContext(fbData);
+          final rootOffset = buffer.derefObject(0);
+
+          final object = Login(
+              username:
+                  const fb.StringReader().vTableGet(buffer, rootOffset, 6, ''))
+            ..id = const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0);
+
+          return object;
+        })
+  };
+
+  return ModelDefinition(model, bindings);
+}
+
+/// [Login] entity fields to define ObjectBox queries.
+class User_ {
+  /// see [Login.id]
+  static final id = QueryIntegerProperty<Login>(_entities[0].properties[0]);
+
+  /// see [Login.username]
+  static final username = QueryStringProperty<Login>(_entities[0].properties[1]);
+}

+ 61 - 0
lib/routes/home.dart

@@ -0,0 +1,61 @@
+import 'package:e2ee_chat/common/global.dart';
+import 'package:e2ee_chat/network/api.dart';
+import 'package:e2ee_chat/models/user_model.dart';
+import 'package:e2ee_chat/widgets/empty.dart';
+import 'package:flukit/flukit.dart';
+import 'package:flutter/material.dart';
+import 'package:provider/provider.dart';
+
+class HomeRoute extends StatefulWidget {
+  @override
+  _HomeRouteState createState() => _HomeRouteState();
+}
+
+class _HomeRouteState extends State<HomeRoute> {
+  @override
+  Widget build(BuildContext context) {
+    return Scaffold(
+      appBar: AppBar(
+        title: Text(GmLocalizations.of(context).home),
+      ),
+      body: _buildBody(), // 构建主页面
+    );
+  }
+
+  Widget _buildBody() {
+    UserModel userModel = Provider.of<UserModel>(context);
+    if (!userModel.isLogin) {
+      //用户未登录,显示登录按钮
+      return Center(
+        child: ElevatedButton(
+          child: Text(GmLocalizations.of(context).login),
+          onPressed: () => Navigator.of(context).pushNamed("login"),
+        ),
+      );
+    } else {
+      return EmptyWidget();
+      //已登录,则展示项目列表
+      /*
+      return InfiniteListView<Repo>(
+        onRetrieveData: (int page, List<Repo> items, bool refresh) async {
+          var data = await Api(context).getRepos(
+            refresh: refresh,
+            queryParameters: {
+              'page': page,
+              'page_size': 20,
+            },
+          );
+          //把请求到的新数据添加到items中
+          items.addAll(data);
+          // 如果接口返回的数量等于'page_size',则认为还有数据,反之则认为最后一页
+          return data.length==20;
+        },
+        itemBuilder: (List list, int index, BuildContext ctx) {
+          // 项目信息列表项
+          return RepoItem(list[index]);
+        },
+      );
+       */
+    }
+  }
+}

+ 41 - 0
lib/routes/language_route.dart

@@ -0,0 +1,41 @@
+import 'package:e2ee_chat/models/locale_model.dart';
+import 'package:flutter/material.dart';
+import 'package:provider/provider.dart';
+
+class LanguageRoute extends StatelessWidget {
+  @override
+  Widget build(BuildContext context) {
+    var color = Theme.of(context).primaryColor;
+    var localeModel = Provider.of<LocaleModel>(context);
+    var gm = GmLocalizations.of(context);
+    //构建语言选择项
+    Widget _buildLanguageItem(String lan, value) {
+      return ListTile(
+        title: Text(
+          lan,
+          // 对APP当前语言进行高亮显示
+          style: TextStyle(color: localeModel.locale == value ? color : null),
+        ),
+        trailing:
+        localeModel.locale == value ? Icon(Icons.done, color: color) : null,
+        onTap: () {
+          // 更新locale后MaterialApp会重新build
+          localeModel.locale = value;
+        },
+      );
+    }
+
+    return Scaffold(
+      appBar: AppBar(
+        title: Text(gm.language),
+      ),
+      body: ListView(
+        children: <Widget>[
+          _buildLanguageItem("中文简体", "zh_CN"),
+          _buildLanguageItem("English", "en_US"),
+          _buildLanguageItem(gm.auto, null),
+        ],
+      ),
+    );
+  }
+}

+ 119 - 0
lib/routes/login.dart

@@ -0,0 +1,119 @@
+import 'package:dio/dio.dart';
+import 'package:e2ee_chat/common/global.dart';
+import 'package:e2ee_chat/models/theme_model.dart';
+import 'package:e2ee_chat/models/user_model.dart';
+import 'package:flutter/material.dart';
+import 'package:provider/provider.dart';
+import 'package:fluttertoast/fluttertoast.dart';
+
+class LoginRoute extends StatefulWidget {
+  @override
+  _LoginRouteState createState() => _LoginRouteState();
+}
+
+class _LoginRouteState extends State<LoginRoute> {
+  TextEditingController _unameController = new TextEditingController();
+  TextEditingController _pwdController = new TextEditingController();
+  bool pwdShow = false; //密码是否显示明文
+  GlobalKey _formKey = new GlobalKey<FormState>();
+  bool _nameAutoFocus = true;
+
+  @override
+  void initState() {
+    // 自动填充上次登录的用户名,填充后将焦点定位到密码输入框
+    if (Global.profile.username != null) {
+      _unameController.text = Global.profile.username!;
+      _nameAutoFocus = true;
+    }
+    super.initState();
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    var gm = GmLocalizations.of(context);
+    return Scaffold(
+      appBar: AppBar(title: Text(gm.login)),
+      body: Padding(
+        padding: const EdgeInsets.all(16.0),
+        child: Form(
+          key: _formKey,
+          autovalidateMode: AutovalidateMode.always, // TODO: what is AutovalidateMode?
+          child: Column(
+            children: <Widget>[
+              TextFormField(
+                  autofocus: _nameAutoFocus,
+                  controller: _unameController,
+                  decoration: InputDecoration(
+                    labelText: gm.userName,
+                    hintText: gm.userNameOrEmail,
+                    prefixIcon: Icon(Icons.person),
+                  ),
+                  // 校验用户名(不能为空)
+                  validator: (v) {
+                    return v!.trim().isNotEmpty ? null : gm.userNameRequired;
+                  }),
+              TextFormField(
+                controller: _pwdController,
+                autofocus: !_nameAutoFocus,
+                decoration: InputDecoration(
+                    labelText: gm.password,
+                    hintText: gm.password,
+                    prefixIcon: Icon(Icons.lock),
+                    suffixIcon: IconButton(
+                      icon: Icon(
+                          pwdShow ? Icons.visibility_off : Icons.visibility),
+                      onPressed: () {
+                        setState(() {
+                          pwdShow = !pwdShow;
+                        });
+                      },
+                    )),
+                obscureText: !pwdShow,
+                //校验密码(不能为空)
+                validator: (v) {
+                  return v!.trim().isNotEmpty ? null : gm.passwordRequired;
+                },
+              ),
+              Padding(
+                padding: const EdgeInsets.only(top: 25),
+                child: ConstrainedBox(
+                  constraints: BoxConstraints.expand(height: 55.0),
+                  child: ElevatedButton(
+                    style: ButtonStyle(
+                      foregroundColor: MaterialStateProperty.all<Color>(Provider.of<ThemeModel>(context).theme),
+                    ),
+                    onPressed: _onLogin,
+                    child: Text(gm.login),
+                  ),
+                ),
+              ),
+            ],
+          ),
+        ),
+      ),
+    );
+  }
+
+  void _onLogin() async {
+    // 提交前,先验证各个表单字段是否合法
+    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后不触发更新
+        Provider.of<UserModel>(context).login(username: _unameController.text, password: _pwdController.text);
+      } on DioError catch(e) {
+        //登录失败则提示
+        if (e.response?.statusCode == 401) {
+          Fluttertoast.showToast(msg: GmLocalizations.of(context).userNameOrPasswordWrong);
+        } else {
+          Fluttertoast.showToast(msg: e.toString());
+        }
+      }
+      if (Global.profile.isLogin) {
+        // 返回
+        Navigator.of(context).pop();
+      }
+    }
+  }
+}

+ 34 - 0
lib/routes/theme_change.dart

@@ -0,0 +1,34 @@
+
+
+import 'package:e2ee_chat/common/global.dart';
+import 'package:e2ee_chat/models/theme_model.dart';
+import 'package:flutter/material.dart';
+import 'package:provider/provider.dart';
+
+class ThemeChangeRoute extends StatelessWidget{
+  @override
+  Widget build(BuildContext context) {
+    return Scaffold(
+      appBar: AppBar(
+        title: Text(GmLocalizations.of(context).theme),
+      ),
+      body: ListView( //显示主题色块
+        children: Global.themes.map<Widget>((e) {
+          return GestureDetector(
+            child: Padding(
+              padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 16),
+              child: Container(
+                color: e,
+                height: 40,
+              ),
+            ),
+            onTap: () {
+              //主题更新后,MaterialApp会重新build
+              Provider.of<ThemeModel>(context).theme = e;
+            },
+          );
+        }).toList(),
+      ),
+    );
+  }
+}

+ 8 - 0
lib/widgets/empty.dart

@@ -0,0 +1,8 @@
+import 'package:flutter/cupertino.dart';
+
+class EmptyWidget extends StatelessWidget {
+  @override
+  Widget build(BuildContext context) {
+    return Text("empty");
+  }
+}

+ 495 - 1
pubspec.lock

@@ -1,6 +1,27 @@
 # Generated by pub
 # See https://dart.dev/tools/pub/glossary#lockfile
 packages:
+  _fe_analyzer_shared:
+    dependency: transitive
+    description:
+      name: _fe_analyzer_shared
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "22.0.0"
+  analyzer:
+    dependency: transitive
+    description:
+      name: analyzer
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "1.7.1"
+  args:
+    dependency: transitive
+    description:
+      name: args
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "2.1.1"
   async:
     dependency: transitive
     description:
@@ -15,6 +36,62 @@ packages:
       url: "https://pub.flutter-io.cn"
     source: hosted
     version: "2.1.0"
+  build:
+    dependency: transitive
+    description:
+      name: build
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "2.0.2"
+  build_config:
+    dependency: transitive
+    description:
+      name: build_config
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "0.4.7"
+  build_daemon:
+    dependency: transitive
+    description:
+      name: build_daemon
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "2.1.10"
+  build_resolvers:
+    dependency: transitive
+    description:
+      name: build_resolvers
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "2.0.3"
+  build_runner:
+    dependency: "direct dev"
+    description:
+      name: build_runner
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "1.12.2"
+  build_runner_core:
+    dependency: transitive
+    description:
+      name: build_runner_core
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "6.1.12"
+  built_collection:
+    dependency: transitive
+    description:
+      name: built_collection
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "5.1.0"
+  built_value:
+    dependency: transitive
+    description:
+      name: built_value
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "8.1.0"
   characters:
     dependency: transitive
     description:
@@ -29,6 +106,20 @@ packages:
       url: "https://pub.flutter-io.cn"
     source: hosted
     version: "1.2.0"
+  checked_yaml:
+    dependency: transitive
+    description:
+      name: checked_yaml
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "2.0.1"
+  cli_util:
+    dependency: transitive
+    description:
+      name: cli_util
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "0.3.3"
   clock:
     dependency: transitive
     description:
@@ -36,6 +127,13 @@ packages:
       url: "https://pub.flutter-io.cn"
     source: hosted
     version: "1.1.0"
+  code_builder:
+    dependency: transitive
+    description:
+      name: code_builder
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "3.7.0"
   collection:
     dependency: transitive
     description:
@@ -43,6 +141,20 @@ packages:
       url: "https://pub.flutter-io.cn"
     source: hosted
     version: "1.15.0"
+  convert:
+    dependency: transitive
+    description:
+      name: convert
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "3.0.1"
+  crypto:
+    dependency: "direct main"
+    description:
+      name: crypto
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "3.0.1"
   cupertino_icons:
     dependency: "direct main"
     description:
@@ -50,6 +162,20 @@ packages:
       url: "https://pub.flutter-io.cn"
     source: hosted
     version: "1.0.3"
+  dart_style:
+    dependency: transitive
+    description:
+      name: dart_style
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "2.0.1"
+  dio:
+    dependency: "direct main"
+    description:
+      name: dio
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "4.0.0"
   fake_async:
     dependency: transitive
     description:
@@ -57,16 +183,138 @@ packages:
       url: "https://pub.flutter-io.cn"
     source: hosted
     version: "1.2.0"
+  ffi:
+    dependency: transitive
+    description:
+      name: ffi
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "1.1.2"
+  file:
+    dependency: transitive
+    description:
+      name: file
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "6.1.2"
+  fixnum:
+    dependency: transitive
+    description:
+      name: fixnum
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "1.0.0"
+  flukit:
+    dependency: "direct main"
+    description:
+      name: flukit
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "1.0.2"
   flutter:
     dependency: "direct main"
     description: flutter
     source: sdk
     version: "0.0.0"
+  flutter_localizations:
+    dependency: "direct main"
+    description: flutter
+    source: sdk
+    version: "0.0.0"
+  flutter_secure_storage:
+    dependency: "direct main"
+    description:
+      name: flutter_secure_storage
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "4.2.0"
   flutter_test:
     dependency: "direct dev"
     description: flutter
     source: sdk
     version: "0.0.0"
+  flutter_web_plugins:
+    dependency: transitive
+    description: flutter
+    source: sdk
+    version: "0.0.0"
+  fluttertoast:
+    dependency: "direct main"
+    description:
+      name: fluttertoast
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "8.0.7"
+  glob:
+    dependency: transitive
+    description:
+      name: glob
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "2.0.1"
+  graphs:
+    dependency: transitive
+    description:
+      name: graphs
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "1.0.0"
+  http_multi_server:
+    dependency: transitive
+    description:
+      name: http_multi_server
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "3.0.1"
+  http_parser:
+    dependency: transitive
+    description:
+      name: http_parser
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "4.0.0"
+  intl:
+    dependency: transitive
+    description:
+      name: intl
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "0.17.0"
+  io:
+    dependency: transitive
+    description:
+      name: io
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "1.0.3"
+  js:
+    dependency: transitive
+    description:
+      name: js
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "0.6.3"
+  json_annotation:
+    dependency: transitive
+    description:
+      name: json_annotation
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "4.0.1"
+  json_serializable:
+    dependency: "direct dev"
+    description:
+      name: json_serializable
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "4.1.3"
+  logging:
+    dependency: transitive
+    description:
+      name: logging
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "1.0.1"
   matcher:
     dependency: transitive
     description:
@@ -81,6 +329,48 @@ packages:
       url: "https://pub.flutter-io.cn"
     source: hosted
     version: "1.3.0"
+  mime:
+    dependency: transitive
+    description:
+      name: mime
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "1.0.0"
+  nested:
+    dependency: transitive
+    description:
+      name: nested
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "1.0.0"
+  objectbox:
+    dependency: "direct main"
+    description:
+      name: objectbox
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "1.1.0"
+  objectbox_flutter_libs:
+    dependency: "direct main"
+    description:
+      name: objectbox_flutter_libs
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "1.1.0"
+  objectbox_generator:
+    dependency: "direct dev"
+    description:
+      name: objectbox_generator
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "1.1.0"
+  package_config:
+    dependency: transitive
+    description:
+      name: package_config
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "2.0.0"
   path:
     dependency: transitive
     description:
@@ -88,11 +378,165 @@ packages:
       url: "https://pub.flutter-io.cn"
     source: hosted
     version: "1.8.0"
+  path_provider:
+    dependency: transitive
+    description:
+      name: path_provider
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "2.0.2"
+  path_provider_linux:
+    dependency: transitive
+    description:
+      name: path_provider_linux
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "2.0.0"
+  path_provider_macos:
+    dependency: transitive
+    description:
+      name: path_provider_macos
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "2.0.0"
+  path_provider_platform_interface:
+    dependency: transitive
+    description:
+      name: path_provider_platform_interface
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "2.0.1"
+  path_provider_windows:
+    dependency: transitive
+    description:
+      name: path_provider_windows
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "2.0.1"
+  pedantic:
+    dependency: transitive
+    description:
+      name: pedantic
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "1.11.1"
+  platform:
+    dependency: transitive
+    description:
+      name: platform
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "3.0.0"
+  plugin_platform_interface:
+    dependency: transitive
+    description:
+      name: plugin_platform_interface
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "2.0.1"
+  pool:
+    dependency: transitive
+    description:
+      name: pool
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "1.5.0"
+  process:
+    dependency: transitive
+    description:
+      name: process
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "4.2.1"
+  provider:
+    dependency: "direct main"
+    description:
+      name: provider
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "5.0.0"
+  pub_semver:
+    dependency: transitive
+    description:
+      name: pub_semver
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "2.0.0"
+  pubspec_parse:
+    dependency: transitive
+    description:
+      name: pubspec_parse
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "1.0.0"
+  shared_preferences:
+    dependency: "direct main"
+    description:
+      name: shared_preferences
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "2.0.6"
+  shared_preferences_linux:
+    dependency: transitive
+    description:
+      name: shared_preferences_linux
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "2.0.0"
+  shared_preferences_macos:
+    dependency: transitive
+    description:
+      name: shared_preferences_macos
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "2.0.0"
+  shared_preferences_platform_interface:
+    dependency: transitive
+    description:
+      name: shared_preferences_platform_interface
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "2.0.0"
+  shared_preferences_web:
+    dependency: transitive
+    description:
+      name: shared_preferences_web
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "2.0.0"
+  shared_preferences_windows:
+    dependency: transitive
+    description:
+      name: shared_preferences_windows
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "2.0.0"
+  shelf:
+    dependency: transitive
+    description:
+      name: shelf
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "1.2.0"
+  shelf_web_socket:
+    dependency: transitive
+    description:
+      name: shelf_web_socket
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "1.0.1"
   sky_engine:
     dependency: transitive
     description: flutter
     source: sdk
     version: "0.0.99"
+  source_gen:
+    dependency: transitive
+    description:
+      name: source_gen
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "1.0.2"
   source_span:
     dependency: transitive
     description:
@@ -114,6 +558,13 @@ packages:
       url: "https://pub.flutter-io.cn"
     source: hosted
     version: "2.1.0"
+  stream_transform:
+    dependency: transitive
+    description:
+      name: stream_transform
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "2.0.0"
   string_scanner:
     dependency: transitive
     description:
@@ -135,6 +586,13 @@ packages:
       url: "https://pub.flutter-io.cn"
     source: hosted
     version: "0.3.0"
+  timing:
+    dependency: transitive
+    description:
+      name: timing
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "1.0.0"
   typed_data:
     dependency: transitive
     description:
@@ -149,5 +607,41 @@ packages:
       url: "https://pub.flutter-io.cn"
     source: hosted
     version: "2.1.0"
+  watcher:
+    dependency: transitive
+    description:
+      name: watcher
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "1.0.0"
+  web_socket_channel:
+    dependency: transitive
+    description:
+      name: web_socket_channel
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "2.1.0"
+  win32:
+    dependency: transitive
+    description:
+      name: win32
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "2.2.5"
+  xdg_directories:
+    dependency: transitive
+    description:
+      name: xdg_directories
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "0.2.0"
+  yaml:
+    dependency: transitive
+    description:
+      name: yaml
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "3.1.0"
 sdks:
-  dart: ">=2.12.0 <3.0.0"
+  dart: ">=2.13.0 <3.0.0"
+  flutter: ">=2.0.0"

+ 18 - 0
pubspec.yaml

@@ -23,7 +23,22 @@ environment:
 dependencies:
   flutter:
     sdk: flutter
+  flutter_localizations:
+    sdk: flutter
+  fluttertoast: ^8.0.7
+  provider: ^5.0.0
+
+  shared_preferences: ^2.0.6
+  flutter_secure_storage: ^4.2.0
+  objectbox: ^1.0.0
+  objectbox_flutter_libs: any
+  # for ObjectBox Sync use this dependency instead:
+  # objectbox_sync_flutter_libs: any
+
+  dio: ^4.0.0
+  crypto: ^3.0.1
 
+  flukit: ^1.0.2
 
   # The following adds the Cupertino Icons font to your application.
   # Use with the CupertinoIcons class for iOS style icons.
@@ -32,6 +47,9 @@ dependencies:
 dev_dependencies:
   flutter_test:
     sdk: flutter
+  build_runner: ^1.0.0
+  objectbox_generator: any
+  json_serializable: ^4.1.3
 
 # For information on the generic Dart part of this file, see the
 # following page: https://dart.dev/tools/pub/pubspec