add login

This commit is contained in:
jyuesong
2022-01-12 15:45:52 +08:00
parent 9f7164db48
commit 05e4fd6f66
14 changed files with 483 additions and 12 deletions

BIN
assets/images/loading.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

@@ -24,9 +24,13 @@ class _BaseStateWidgetState<T extends BaseViewModel> extends ConsumerState<BaseS
@override
void initState() {
super.initState();
if (widget.onReady != null) {
widget.onReady!(ref.read<T>(widget.model));
}
WidgetsBinding.instance?.addPostFrameCallback(
(timeStamp) {
if (widget.onReady != null) {
widget.onReady!(ref.read<T>(widget.model));
}
},
);
}
@override

View File

@@ -1,6 +1,10 @@
import 'package:flutter/cupertino.dart';
class BaseViewModel extends ChangeNotifier {
class ViewModel extends ChangeNotifier{}
class BaseViewModel extends ViewModel {
PageState currentState = PageState.START;
void loading({bool notify = false}) {

196
lib/base/common_dialog.dart Normal file
View File

@@ -0,0 +1,196 @@
import 'dart:async';
import 'package:back_button_interceptor/back_button_interceptor.dart';
import 'package:flutter/material.dart';
/// @author newtab on 2021/7/16
///常用loading,fail,success对话框
class CommonDialog extends StatelessWidget {
final String? text;
final CommonDialogState? commonDialogState;
const CommonDialog({Key? key, this.text, this.commonDialogState}) : super(key: key);
@override
Widget build(BuildContext context) {
Widget result = Material(
color: Colors.transparent,
child: Align(
alignment: Alignment.center,
child: Container(
decoration: BoxDecoration(color: Color.fromRGBO(17, 17, 17, 0.7), borderRadius: BorderRadius.circular(5)),
constraints: BoxConstraints(
maxWidth: MediaQuery.of(context).size.width / 2,
minWidth: 122,
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
margin: EdgeInsets.only(top: 15.0),
constraints: BoxConstraints(
minHeight: 40,
),
child: IconTheme(
data: IconThemeData(
color: Colors.white,
size: 40.0,
),
child: LoadingIcon()),
),
SizedBox(
height: 15,
),
if (text != null)
Padding(
padding: EdgeInsets.only(
left: 15,
right: 15,
bottom: 15,
),
child: DefaultTextStyle(
textAlign: TextAlign.center,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: TextStyle(color: Colors.white, fontSize: 14),
child: Text(text!),
),
),
],
),
),
),
);
return result;
}
}
class LoadingIcon extends StatefulWidget {
final double size;
LoadingIcon({this.size = 50.0});
@override
State<StatefulWidget> createState() => LoadingIconState();
}
class LoadingIconState extends State<LoadingIcon> with SingleTickerProviderStateMixin {
AnimationController? _controller;
Animation<double>? _doubleAnimation;
@override
void initState() {
super.initState();
_controller = new AnimationController(vsync: this, duration: Duration(milliseconds: 1000))..repeat();
_doubleAnimation = Tween(begin: 0.0, end: 360.0).animate(_controller!)
..addListener(() {
setState(() {});
});
}
@override
void dispose() {
_controller?.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Transform.rotate(
angle: _doubleAnimation!.value ~/ 30 * 30.0 * 0.0174533, child: Image.asset("assets/images/loading.png", width: widget.size, height: widget.size));
}
}
enum CommonDialogState {
LOADING,
SUCCESS,
FAILED,
}
typedef HideCallback = Future Function();
int backButtonIndex = 2;
HideCallback showCommonDialog(
BuildContext context, {
String? text,
bool backButtonClose = false,
CommonDialogState commonDialogState = CommonDialogState.LOADING,
}) {
Completer<VoidCallback> result = Completer<VoidCallback>();
var backButtonName = 'QL_Toast$backButtonIndex';
BackButtonInterceptor.add((stopDefaultButtonEvent, _) {
if (backButtonClose) {
result.future.then((hide) {
hide();
});
}
return true;
}, zIndex: backButtonIndex, name: backButtonName);
backButtonIndex++;
var overlay = OverlayEntry(
maintainState: true,
builder: (_) => WillPopScope(
onWillPop: () async {
var hide = await result.future;
hide();
return false;
},
child: CommonDialog(
text: text,
),
),
);
result.complete(() {
overlay.remove();
BackButtonInterceptor.removeByName(backButtonName);
});
Overlay.of(context)?.insert(overlay);
return () async {
var hide = await result.future;
hide();
};
}
Future successDialog(BuildContext context, String text) {
HideCallback hideCallback = showCommonDialog(
context,
text: text,
commonDialogState: CommonDialogState.SUCCESS,
backButtonClose: true,
);
return Future.delayed(
Duration(
milliseconds: 1000,
), () {
hideCallback();
});
}
Future failDialog(BuildContext context, String text) {
HideCallback hideCallback = showCommonDialog(
context,
text: text,
commonDialogState: CommonDialogState.FAILED,
backButtonClose: true,
);
return Future.delayed(
Duration(
milliseconds: 1000,
), () {
hideCallback();
});
}
HideCallback loadingDialog(BuildContext context, {String? text}) {
return showCommonDialog(
context,
text: text,
commonDialogState: CommonDialogState.LOADING,
backButtonClose: true,
);
}

16
lib/base/routes.dart Normal file
View File

@@ -0,0 +1,16 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:qinglong_app/module/home/home_page.dart';
class Routes {
static const String route_HomePage = "/home/homepage";
static Route<dynamic>? generateRoute(RouteSettings settings) {
switch (settings.name) {
case route_HomePage:
return CupertinoPageRoute(builder: (context) => const HomePage());
}
return null;
}
}

1
lib/base/sp_const.dart Normal file
View File

@@ -0,0 +1 @@
String sp_UserINfo = "userinfo";

View File

@@ -16,5 +16,9 @@ class ThemeViewModel extends ChangeNotifier {
}
}
ThemeData darkTheme = ThemeData.dark().copyWith();
ThemeData lightTheme = ThemeData.light().copyWith();
ThemeData darkTheme = ThemeData.dark().copyWith(
primaryColor: Color(0xffffffff),
);
ThemeData lightTheme = ThemeData.light().copyWith(
primaryColor: Color(0xFF0F77FE),
);

View File

@@ -0,0 +1,46 @@
import 'dart:convert';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:qinglong_app/base/base_viewmodel.dart';
import 'package:qinglong_app/utils/sp_utils.dart';
import 'sp_const.dart';
var userInfoProvider = ChangeNotifierProvider((ref) => UserInfoViewModel());
class UserInfoViewModel extends ViewModel {
UserInfoBean? _userInfoBean;
UserInfoViewModel() {
String userInfoJson = SpUtil.getString(sp_UserINfo);
if (userInfoJson.isNotEmpty) {
_userInfoBean = UserInfoBean.fromJson(jsonDecode(userInfoJson));
}
}
void updateUserInfoBean(UserInfoBean userInfoBean) {
_userInfoBean = userInfoBean;
SpUtil.putString(sp_UserINfo, jsonEncode(userInfoBean));
}
UserInfoBean? get userInfoBean => _userInfoBean;
}
class UserInfoBean {
String? name;
int? age;
UserInfoBean({this.name, this.age});
UserInfoBean.fromJson(Map<String, dynamic> json) {
name = json['name'];
age = json['age'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['name'] = this.name;
data['age'] = this.age;
return data;
}
}

View File

@@ -4,7 +4,10 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:qinglong_app/base/theme.dart';
import 'package:qinglong_app/module/login/login_page.dart';
import 'base/routes.dart';
import 'base/userinfo_viewmodel.dart';
import 'module/home/home_page.dart';
void main() {
@@ -12,6 +15,7 @@ void main() {
ProviderScope(
overrides: [
themeProvider,
userInfoProvider,
],
child: const MyApp(),
),
@@ -27,9 +31,18 @@ class MyApp extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
return MaterialApp(
theme: ref.watch<ThemeViewModel>(themeProvider).currentTheme,
home: HomePage(),
return GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: (){
FocusScope.of(context).requestFocus(FocusNode());
},
child: MaterialApp(
theme: ref.watch<ThemeViewModel>(themeProvider).currentTheme,
onGenerateRoute: (setting) {
return Routes.generateRoute(setting);
},
home: ref.read<UserInfoViewModel>(userInfoProvider).userInfoBean != null ? const HomePage() : LoginPage(),
),
);
}
}

View File

@@ -0,0 +1,154 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:qinglong_app/base/common_dialog.dart';
import 'package:qinglong_app/base/routes.dart';
import 'package:qinglong_app/module/login/login_viewmodel.dart';
import 'package:qinglong_app/utils/utils.dart';
class LoginPage extends StatefulWidget {
const LoginPage({Key? key}) : super(key: key);
@override
_LoginPageState createState() => _LoginPageState();
}
class _LoginPageState extends State<LoginPage> {
final TextEditingController _userNameController = TextEditingController();
final TextEditingController _passwordController = TextEditingController();
HideCallback? _hideCallback;
@override
Widget build(BuildContext context) {
return Scaffold(
body: Consumer(builder: (context, ref, child) {
var model = ref.watch<LoginViewModel>(loginProvider);
if (model.isLoading) {
WidgetsBinding.instance?.addPostFrameCallback((timeStamp) {
_hideCallback ??= loadingDialog(context, text: "登录中...");
});
} else {
if (_hideCallback != null) {
WidgetsBinding.instance?.addPostFrameCallback((timeStamp) {
_hideCallback!();
});
}
}
if (model.loginSuccess) {
WidgetsBinding.instance?.addPostFrameCallback((timeStamp) {
Navigator.of(context).pushNamed(Routes.route_HomePage);
});
}
return SizedBox(
height: MediaQuery.of(context).size.height,
child: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SizedBox(
height: MediaQuery.of(context).size.height / 3,
),
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 15,
),
child: Row(
children: [
const SizedBox(
child: Text(
"用户名:",
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
),
),
width: 60,
),
const SizedBox(
width: 15,
),
Expanded(
child: TextField(
onChanged: (_) {
setState(() {});
},
controller: _userNameController,
decoration: const InputDecoration(
contentPadding: EdgeInsets.fromLTRB(0, 5, 0, 5),
hintText: "请输入用户名",
),
autofocus: false,
),
),
],
),
),
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 15,
vertical: 10,
),
child: Row(
children: [
const SizedBox(
child: Text(
"密码:",
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
),
),
width: 60,
),
const SizedBox(
width: 15,
),
Expanded(
child: TextField(
onChanged: (_) {
setState(() {});
},
controller: _passwordController,
obscureText: true,
decoration: const InputDecoration(
contentPadding: EdgeInsets.fromLTRB(0, 5, 0, 5),
hintText: "请输入密码",
),
autofocus: false,
),
),
],
),
),
const SizedBox(
height: 30,
),
SizedBox(
width: MediaQuery.of(context).size.width - 30,
child: CupertinoButton(
color: (_userNameController.text.isNotEmpty && _passwordController.text.isNotEmpty)
? Theme.of(context).primaryColor
: Theme.of(context).primaryColor.withOpacity(0.3),
child: const Text(
"登录",
style: TextStyle(
fontSize: 16,
),
),
onPressed: () {
Utils.hideKeyBoard(context);
model.login(_userNameController.text, _passwordController.text);
}),
),
],
),
),
);
}),
);
}
}

View File

@@ -0,0 +1,19 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:qinglong_app/base/base_viewmodel.dart';
var loginProvider = ChangeNotifierProvider((ref) => LoginViewModel());
class LoginViewModel extends ViewModel {
bool loginSuccess = false;
bool isLoading = false;
void login(String userName, String password) {
isLoading = true;
notifyListeners();
Future.delayed(Duration(seconds: 2), () {
isLoading = false;
loginSuccess = true;
notifyListeners();
});
}
}

7
lib/utils/utils.dart Normal file
View File

@@ -0,0 +1,7 @@
import 'package:flutter/cupertino.dart';
class Utils {
static void hideKeyBoard(BuildContext context) {
FocusScope.of(context).requestFocus(FocusNode());
}
}

View File

@@ -8,6 +8,13 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.8.2"
back_button_interceptor:
dependency: "direct main"
description:
name: back_button_interceptor
url: "https://pub.flutter-io.cn"
source: hosted
version: "5.0.2"
boolean_selector:
dependency: transitive
description:

View File

@@ -15,6 +15,7 @@ dependencies:
flutter_riverpod: ^1.0.3
shared_preferences: ^2.0.12
synchronized: ^3.0.0
back_button_interceptor: ^5.0.2
dev_dependencies:
flutter_test:
@@ -31,9 +32,8 @@ flutter:
uses-material-design: true
# To add assets to your application, add an assets section, like this:
# assets:
# - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg
assets:
- assets/images/
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/assets-and-images/#resolution-aware.