mirror of
https://github.com/qinglong-app/qinglong_app.git
synced 2025-10-09 16:48:19 +08:00
add login
This commit is contained in:
BIN
assets/images/loading.png
Normal file
BIN
assets/images/loading.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.9 KiB |
@@ -24,9 +24,13 @@ class _BaseStateWidgetState<T extends BaseViewModel> extends ConsumerState<BaseS
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
WidgetsBinding.instance?.addPostFrameCallback(
|
||||||
|
(timeStamp) {
|
||||||
if (widget.onReady != null) {
|
if (widget.onReady != null) {
|
||||||
widget.onReady!(ref.read<T>(widget.model));
|
widget.onReady!(ref.read<T>(widget.model));
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
|
|
||||||
class BaseViewModel extends ChangeNotifier {
|
|
||||||
|
class ViewModel extends ChangeNotifier{}
|
||||||
|
|
||||||
|
|
||||||
|
class BaseViewModel extends ViewModel {
|
||||||
PageState currentState = PageState.START;
|
PageState currentState = PageState.START;
|
||||||
|
|
||||||
void loading({bool notify = false}) {
|
void loading({bool notify = false}) {
|
||||||
|
|||||||
196
lib/base/common_dialog.dart
Normal file
196
lib/base/common_dialog.dart
Normal 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
16
lib/base/routes.dart
Normal 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
1
lib/base/sp_const.dart
Normal file
@@ -0,0 +1 @@
|
|||||||
|
String sp_UserINfo = "userinfo";
|
||||||
@@ -16,5 +16,9 @@ class ThemeViewModel extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ThemeData darkTheme = ThemeData.dark().copyWith();
|
ThemeData darkTheme = ThemeData.dark().copyWith(
|
||||||
ThemeData lightTheme = ThemeData.light().copyWith();
|
primaryColor: Color(0xffffffff),
|
||||||
|
);
|
||||||
|
ThemeData lightTheme = ThemeData.light().copyWith(
|
||||||
|
primaryColor: Color(0xFF0F77FE),
|
||||||
|
);
|
||||||
|
|||||||
46
lib/base/userinfo_viewmodel.dart
Normal file
46
lib/base/userinfo_viewmodel.dart
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,7 +4,10 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:qinglong_app/base/theme.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';
|
import 'module/home/home_page.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
@@ -12,6 +15,7 @@ void main() {
|
|||||||
ProviderScope(
|
ProviderScope(
|
||||||
overrides: [
|
overrides: [
|
||||||
themeProvider,
|
themeProvider,
|
||||||
|
userInfoProvider,
|
||||||
],
|
],
|
||||||
child: const MyApp(),
|
child: const MyApp(),
|
||||||
),
|
),
|
||||||
@@ -27,9 +31,18 @@ class MyApp extends ConsumerWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
return MaterialApp(
|
return GestureDetector(
|
||||||
|
behavior: HitTestBehavior.opaque,
|
||||||
|
onTap: (){
|
||||||
|
FocusScope.of(context).requestFocus(FocusNode());
|
||||||
|
},
|
||||||
|
child: MaterialApp(
|
||||||
theme: ref.watch<ThemeViewModel>(themeProvider).currentTheme,
|
theme: ref.watch<ThemeViewModel>(themeProvider).currentTheme,
|
||||||
home: HomePage(),
|
onGenerateRoute: (setting) {
|
||||||
|
return Routes.generateRoute(setting);
|
||||||
|
},
|
||||||
|
home: ref.read<UserInfoViewModel>(userInfoProvider).userInfoBean != null ? const HomePage() : LoginPage(),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
154
lib/module/login/login_page.dart
Normal file
154
lib/module/login/login_page.dart
Normal 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);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
19
lib/module/login/login_viewmodel.dart
Normal file
19
lib/module/login/login_viewmodel.dart
Normal 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
7
lib/utils/utils.dart
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
|
|
||||||
|
class Utils {
|
||||||
|
static void hideKeyBoard(BuildContext context) {
|
||||||
|
FocusScope.of(context).requestFocus(FocusNode());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,6 +8,13 @@ packages:
|
|||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.8.2"
|
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:
|
boolean_selector:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ dependencies:
|
|||||||
flutter_riverpod: ^1.0.3
|
flutter_riverpod: ^1.0.3
|
||||||
shared_preferences: ^2.0.12
|
shared_preferences: ^2.0.12
|
||||||
synchronized: ^3.0.0
|
synchronized: ^3.0.0
|
||||||
|
back_button_interceptor: ^5.0.2
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
@@ -31,9 +32,8 @@ flutter:
|
|||||||
uses-material-design: true
|
uses-material-design: true
|
||||||
|
|
||||||
# To add assets to your application, add an assets section, like this:
|
# To add assets to your application, add an assets section, like this:
|
||||||
# assets:
|
assets:
|
||||||
# - images/a_dot_burr.jpeg
|
- assets/images/
|
||||||
# - images/a_dot_ham.jpeg
|
|
||||||
|
|
||||||
# An image asset can refer to one or more resolution-specific "variants", see
|
# An image asset can refer to one or more resolution-specific "variants", see
|
||||||
# https://flutter.dev/assets-and-images/#resolution-aware.
|
# https://flutter.dev/assets-and-images/#resolution-aware.
|
||||||
|
|||||||
Reference in New Issue
Block a user