mod: 用户信息渲染、退出登录

This commit is contained in:
guozhigq
2023-05-10 00:35:24 +08:00
parent 04668b3591
commit e612e60361
19 changed files with 674 additions and 159 deletions

View File

@ -23,4 +23,7 @@ class Api {
// 获取用户信息
static const String userInfo = '/x/web-interface/nav';
// 获取当前用户状态
static const String userStatOwner = '/x/web-interface/nav/stat';
}

View File

@ -12,6 +12,7 @@ import 'package:dio_cookie_manager/dio_cookie_manager.dart';
class Request {
static final Request _instance = Request._internal();
static late CookieManager cookieManager;
factory Request() => _instance;
@ -31,11 +32,9 @@ class Request {
ignoreExpires: true,
storage: FileStorage(cookiePath),
);
dio.interceptors.add(CookieManager(cookieJar));
var cookie = await CookieManager(cookieJar)
.cookieJar
cookieManager = CookieManager(cookieJar);
dio.interceptors.add(cookieManager);
var cookie = await cookieManager.cookieJar
.loadForRequest(Uri.parse(HttpString.baseUrl));
if (cookie.isEmpty) {
try {
@ -46,6 +45,16 @@ class Request {
}
}
// 移除cookie
static removeCookie() async {
await cookieManager.cookieJar
.saveFromResponse(Uri.parse(HttpString.baseUrl), []);
await cookieManager.cookieJar
.saveFromResponse(Uri.parse(HttpString.baseApiUrl), []);
cookieManager.cookieJar.deleteAll();
dio.interceptors.add(cookieManager);
}
/*
* config it and create
*/

View File

@ -1,6 +1,7 @@
import 'package:pilipala/http/api.dart';
import 'package:pilipala/http/init.dart';
import 'package:pilipala/models/user/info.dart';
import 'package:pilipala/models/user/stat.dart';
class UserHttp {
static Future<dynamic> userStat({required int mid}) async {
@ -17,6 +18,16 @@ class UserHttp {
if (res.data['code'] == 0) {
UserInfoData data = UserInfoData.fromJson(res.data['data']);
return {'status': true, 'data': data};
} else {
return {'status': false, 'msg': res.data['message']};
}
}
static Future<dynamic> userStatOwner() async {
var res = await Request().get(Api.userStatOwner);
if (res.data['code'] == 0) {
UserStat data = UserStat.fromJson(res.data['data']);
return {'status': true, 'data': data};
} else {
return {'status': false};
}

View File

@ -5,9 +5,11 @@ import 'package:dynamic_color/dynamic_color.dart';
import 'package:pilipala/http/init.dart';
import 'package:pilipala/router/app_pages.dart';
import 'package:pilipala/pages/main/view.dart';
import 'package:pilipala/utils/storage.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await GStrorage.init();
await Request.setCookie();
runApp(const MyApp());
}

View File

@ -29,7 +29,7 @@ class UserInfoData {
bool? isLogin;
int? emailVerified;
String? face;
Map? levelInfo;
LevelInfo? levelInfo;
int? mid;
int? mobileVerified;
int? money;
@ -55,7 +55,9 @@ class UserInfoData {
isLogin = json['isLogin'] ?? false;
emailVerified = json['email_verified'];
face = json['face'];
levelInfo = json['level_info'];
levelInfo = json['level_info'] != null
? LevelInfo.fromJson(json['level_info'])
: LevelInfo();
mid = json['mid'];
mobileVerified = json['mobile_verified'];
money = json['money'];
@ -78,3 +80,24 @@ class UserInfoData {
shopUrl = json['shop_url'];
}
}
class LevelInfo {
LevelInfo({
this.currentLevel,
this.currentMin,
this.currentExp,
this.nextExp,
});
int? currentLevel;
int? currentMin;
int? currentExp;
int? nextExp;
LevelInfo.fromJson(Map<String, dynamic> json) {
currentLevel = json['current_level'];
currentMin = json['current_min'];
currentExp = json['current_exp'];
nextExp = json['next_exp'];
}
}

17
lib/models/user/stat.dart Normal file
View File

@ -0,0 +1,17 @@
class UserStat {
UserStat({
this.following,
this.follower,
this.dynamicCount,
});
int? following;
int? follower;
int? dynamicCount;
UserStat.fromJson(Map<String, dynamic> json) {
following = json['following'];
follower = json['follower'];
dynamicCount = json['dynamic_count'];
}
}

View File

@ -1,9 +1,12 @@
import 'package:flutter/cupertino.dart';
import 'package:get/get.dart';
import 'package:flutter/material.dart';
import 'package:hive/hive.dart';
import 'package:pilipala/common/widgets/network_img_layer.dart';
import 'package:pilipala/pages/home/view.dart';
import 'package:pilipala/pages/hot/view.dart';
import 'package:pilipala/pages/mine/view.dart';
import 'package:pilipala/utils/storage.dart';
class MainController extends GetxController {
List<Widget> pages = <Widget>[
@ -11,7 +14,7 @@ class MainController extends GetxController {
const HotPage(),
const MinePage(),
];
List navigationBars = [
RxList navigationBars = [
{
// 'icon': const Icon(Icons.home_outlined),
// 'selectedIcon': const Icon(Icons.home),
@ -51,5 +54,33 @@ class MainController extends GetxController {
),
'label': "我的",
}
];
].obs;
@override
void onInit() {
super.onInit();
readuUserFace();
}
// 设置头像
readuUserFace() async {
Box user = GStrorage.user;
if (user.get(UserBoxKey.userFace) != null) {
navigationBars.last['icon'] =
navigationBars.last['selectedIcon'] = NetworkImgLayer(
width: 25,
height: 25,
type: 'avatar',
src: user.get(UserBoxKey.userFace),
);
navigationBars.last['label'] = '';
}
}
// 重置
resetLast() {
navigationBars.last['icon'] = const Icon(Icons.person_outline);
navigationBars.last['selectedIcon'] = const Icon(Icons.person);
navigationBars.last['label'] = '我的';
}
}

View File

@ -20,7 +20,7 @@ class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
late AnimationController? _animationController;
late Animation<double>? _fadeAnimation;
late Animation<double>? _slideAnimation;
int selectedIndex = 0;
int selectedIndex = 2;
int? _lastSelectTime; //上次点击时间
@override
@ -111,17 +111,19 @@ class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
),
),
),
bottomNavigationBar: NavigationBar(
elevation: 1,
destinations: _mainController.navigationBars.map((e) {
return NavigationDestination(
icon: e['icon'],
selectedIcon: e['selectedIcon'],
label: e['label'],
);
}).toList(),
selectedIndex: selectedIndex,
onDestinationSelected: (value) => setIndex(value),
bottomNavigationBar: Obx(
() => NavigationBar(
elevation: 1,
destinations: _mainController.navigationBars.map((e) {
return NavigationDestination(
icon: e['icon'],
selectedIcon: e['selectedIcon'],
label: e['label'],
);
}).toList(),
selectedIndex: selectedIndex,
onDestinationSelected: (value) => setIndex(value),
),
),
);
}

View File

@ -1,17 +1,69 @@
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
import 'package:hive/hive.dart';
import 'package:pilipala/http/user.dart';
import 'package:pilipala/models/user/info.dart';
import 'package:pilipala/models/user/stat.dart';
import 'package:pilipala/pages/main/controller.dart';
import 'package:pilipala/utils/storage.dart';
class MineController extends GetxController {
UserInfoData? userInfo;
// 用户信息 头像、昵称、lv
Rx<UserInfoData> userInfo = UserInfoData().obs;
// 用户状态 动态、关注、粉丝
Rx<UserStat> userStat = UserStat().obs;
Box user = GStrorage.user;
RxBool userLogin = false.obs;
@override
void onInit() {
super.onInit();
// queryUserInfo();
onLogin() {
Get.toNamed(
'/webview',
parameters: {
'url': 'https://passport.bilibili.com/h5-app/passport/login',
'type': 'login',
'pageTitle': '登录bilibili',
},
);
}
Future queryUserInfo() async {
var res = await UserHttp.userInfo();
if (res['status']) {
if (res['data'].isLogin) {
userInfo.value = res['data'];
user.put(UserBoxKey.userName, res['data'].uname);
user.put(UserBoxKey.userFace, res['data'].face);
user.put(UserBoxKey.userMid, res['data'].mid);
user.put(UserBoxKey.userLogin, true);
userLogin.value = true;
Get.find<MainController>().readuUserFace();
} else {
resetUserInfo();
}
} else {
resetUserInfo();
// SmartDialog.showToast(res['msg']);
}
await queryUserStatOwner();
return res;
}
Future queryUserStatOwner() async {
var res = await UserHttp.userStatOwner();
if (res['status']) {
userStat.value = res['data'];
}
return res;
}
Future resetUserInfo() async {
userInfo.value = UserInfoData();
userStat.value = UserStat();
await user.delete(UserBoxKey.userName);
await user.delete(UserBoxKey.userFace);
await user.delete(UserBoxKey.userMid);
await user.delete(UserBoxKey.userLogin);
userLogin.value = false;
Get.find<MainController>().resetLast();
}
}

View File

@ -1,8 +1,8 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:get/get.dart';
import 'package:pilipala/common/constants.dart';
import 'package:pilipala/common/widgets/network_img_layer.dart';
import 'controller.dart';
class MinePage extends StatefulWidget {
@ -22,18 +22,18 @@ class _MinePageState extends State<MinePage> {
title: null,
actions: [
IconButton(
onPressed: () {},
icon: const Icon(
CupertinoIcons.moon,
onPressed: () {
Get.changeThemeMode(ThemeMode.dark);
},
icon: Icon(
Get.theme == ThemeData.light()
? CupertinoIcons.moon
: CupertinoIcons.sun_max,
size: 22,
),
// icon: const Icon(
// CupertinoIcons.sun_max,
// size: 22,
// ),
),
IconButton(
onPressed: () {},
onPressed: () => Get.toNamed('/setting'),
icon: const Icon(
CupertinoIcons.slider_horizontal_3,
),
@ -43,7 +43,8 @@ class _MinePageState extends State<MinePage> {
),
body: RefreshIndicator(
onRefresh: () async {
await Future.delayed(const Duration(seconds: 2));
await _mineController.queryUserInfo();
await _mineController.queryUserStatOwner();
},
child: LayoutBuilder(
builder: (context, constraint) {
@ -53,127 +54,19 @@ class _MinePageState extends State<MinePage> {
height: constraint.maxHeight,
child: Column(
children: [
InkWell(
onTap: () {
Get.toNamed(
'/webview',
parameters: {
'url':
'https://passport.bilibili.com/h5-app/passport/login',
'type': 'login',
'pageTitle': '登录bilibili',
},
);
FutureBuilder(
future: _mineController.queryUserInfo(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.data['status']) {
return Obx(() => userInfoBuild());
} else {
return userInfoBuild();
}
} else {
return userInfoBuild();
}
},
child: Padding(
padding: const EdgeInsets.only(top: 10, bottom: 10),
child: Row(
children: [
const SizedBox(width: 20),
ClipOval(
child: Container(
width: 75,
height: 75,
color: Theme.of(context)
.colorScheme
.onInverseSurface,
child: Center(
child:
Image.asset('assets/images/loading.png'),
),
),
),
const SizedBox(width: 14),
Text(
'点击登录',
style: Theme.of(context).textTheme.titleMedium,
),
],
),
),
),
const SizedBox(height: 10),
Padding(
padding: const EdgeInsets.only(left: 12, right: 12),
child: LayoutBuilder(
builder: (context, constraints) {
TextStyle style = TextStyle(
fontSize: Theme.of(context)
.textTheme
.titleMedium!
.fontSize,
color: Theme.of(context).colorScheme.primary,
fontWeight: FontWeight.bold);
return SizedBox(
height: constraints.maxWidth / 3 * 0.6,
child: GridView.count(
primary: false,
padding: const EdgeInsets.all(0),
crossAxisCount: 3,
childAspectRatio: 1.67,
children: <Widget>[
InkWell(
onTap: () {},
borderRadius: StyleString.mdRadius,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('-', style: style),
const SizedBox(height: 8),
Text(
'动态',
style: Theme.of(context)
.textTheme
.labelMedium,
),
],
),
),
InkWell(
onTap: () {},
borderRadius: StyleString.mdRadius,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'50',
style: style,
),
const SizedBox(height: 8),
Text(
'关注',
style: Theme.of(context)
.textTheme
.labelMedium,
),
],
),
),
InkWell(
onTap: () {},
borderRadius: StyleString.mdRadius,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'-',
style: style,
),
const SizedBox(height: 8),
Text(
'粉丝',
style: Theme.of(context)
.textTheme
.labelMedium,
),
],
),
),
],
),
);
},
),
),
const SizedBox(height: 20),
Padding(
@ -224,6 +117,251 @@ class _MinePageState extends State<MinePage> {
),
);
}
Widget userInfoBuild() {
return Column(
children: [
const SizedBox(height: 5),
GestureDetector(
onTap: () => _mineController.onLogin(),
child: ClipOval(
child: Container(
width: 85,
height: 85,
color: Theme.of(context).colorScheme.onInverseSurface,
child: Center(
child: _mineController.userInfo.value.face != null
? NetworkImgLayer(
src: _mineController.userInfo.value.face,
width: 85,
height: 85)
: Image.asset('assets/images/loading.png'),
),
),
),
),
const SizedBox(height: 10),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
_mineController.userInfo.value.uname ?? '点击头像登录',
style: Theme.of(context).textTheme.titleMedium,
),
const SizedBox(width: 4),
Image.asset(
'assets/images/lv/lv${_mineController.userInfo.value.levelInfo != null ? _mineController.userInfo.value.levelInfo!.currentLevel : '0'}.png',
height: 10,
),
],
),
const SizedBox(height: 5),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text.rich(TextSpan(children: [
TextSpan(
text: '硬币: ',
style:
TextStyle(color: Theme.of(context).colorScheme.outline)),
TextSpan(
text: (_mineController.userInfo.value.money ?? 'pilipala')
.toString(),
style:
TextStyle(color: Theme.of(context).colorScheme.primary)),
]))
],
),
const SizedBox(height: 5),
if (_mineController.userInfo.value.levelInfo != null) ...[
LayoutBuilder(
builder: (context, BoxConstraints box) {
return SizedBox(
width: box.maxWidth,
height: 24,
child: Stack(
children: [
Positioned(
top: 0,
right: 0,
child: SizedBox(
height: 22,
width: box.maxWidth *
(1 -
(_mineController
.userInfo.value.levelInfo!.currentExp! /
_mineController
.userInfo.value.levelInfo!.nextExp!)),
child: Center(
child: Text(
(_mineController
.userInfo.value.levelInfo!.nextExp! -
_mineController
.userInfo.value.levelInfo!.currentExp!)
.toString(),
style: TextStyle(
color: Theme.of(context).colorScheme.primary,
fontSize: 12,
),
),
),
),
),
],
),
);
},
),
LayoutBuilder(
builder: (context, BoxConstraints box) {
return Container(
width: box.maxWidth,
height: 1,
clipBehavior: Clip.hardEdge,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(4),
color: Theme.of(context).colorScheme.onInverseSurface,
),
child: Stack(
children: [
Positioned(
top: 0,
left: 0,
bottom: 0,
child: Container(
width: box.maxWidth *
(_mineController
.userInfo.value.levelInfo!.currentExp! /
_mineController
.userInfo.value.levelInfo!.nextExp!),
height: 1,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(4),
color: Theme.of(context).colorScheme.primary,
),
),
),
],
),
);
},
),
],
const SizedBox(height: 30),
Padding(
padding: const EdgeInsets.only(left: 12, right: 12),
child: LayoutBuilder(
builder: (context, constraints) {
TextStyle style = TextStyle(
fontSize: Theme.of(context).textTheme.titleMedium!.fontSize,
color: Theme.of(context).colorScheme.primary,
fontWeight: FontWeight.bold);
return SizedBox(
height: constraints.maxWidth / 3 * 0.6,
child: GridView.count(
primary: false,
padding: const EdgeInsets.all(0),
crossAxisCount: 3,
childAspectRatio: 1.67,
children: <Widget>[
InkWell(
onTap: () {},
borderRadius: StyleString.mdRadius,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
AnimatedSwitcher(
duration: const Duration(milliseconds: 400),
transitionBuilder:
(Widget child, Animation<double> animation) {
return ScaleTransition(
scale: animation, child: child);
},
child: Text(
(_mineController.userStat.value.dynamicCount ??
'-')
.toString(),
key: ValueKey<String>(_mineController
.userStat.value.dynamicCount
.toString()),
style: style),
),
const SizedBox(height: 8),
Text(
'动态',
style: Theme.of(context).textTheme.labelMedium,
),
],
),
),
InkWell(
onTap: () {},
borderRadius: StyleString.mdRadius,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
AnimatedSwitcher(
duration: const Duration(milliseconds: 400),
transitionBuilder:
(Widget child, Animation<double> animation) {
return ScaleTransition(
scale: animation, child: child);
},
child: Text(
(_mineController.userStat.value.following ??
'-')
.toString(),
key: ValueKey<String>(_mineController
.userStat.value.following
.toString()),
style: style),
),
const SizedBox(height: 8),
Text(
'关注',
style: Theme.of(context).textTheme.labelMedium,
),
],
),
),
InkWell(
onTap: () {},
borderRadius: StyleString.mdRadius,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
AnimatedSwitcher(
duration: const Duration(milliseconds: 400),
transitionBuilder:
(Widget child, Animation<double> animation) {
return ScaleTransition(
scale: animation, child: child);
},
child: Text(
(_mineController.userStat.value.follower ?? '-')
.toString(),
key: ValueKey<String>(_mineController
.userStat.value.follower
.toString()),
style: style),
),
const SizedBox(height: 8),
Text(
'粉丝',
style: Theme.of(context).textTheme.labelMedium,
),
],
),
),
],
),
);
},
),
),
],
);
}
}
class ActionItem extends StatelessWidget {

View File

@ -0,0 +1,23 @@
import 'package:get/get.dart';
import 'package:hive/hive.dart';
import 'package:pilipala/http/init.dart';
import 'package:pilipala/pages/mine/controller.dart';
import 'package:pilipala/utils/storage.dart';
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
class SettingController extends GetxController {
Box user = GStrorage.user;
RxBool userLogin = false.obs;
@override
void onInit() {
super.onInit();
userLogin.value = user.get(UserBoxKey.userLogin) ?? false;
}
loginOut() async {
await Request.removeCookie();
await Get.find<MineController>().resetUserInfo();
userLogin.value = user.get(UserBoxKey.userLogin) ?? false;
}
}

View File

@ -0,0 +1,4 @@
library setting;
export './controller.dart';
export './view.dart';

View File

@ -0,0 +1,36 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:pilipala/pages/setting/index.dart';
class SettingPage extends StatefulWidget {
const SettingPage({super.key});
@override
State<SettingPage> createState() => _SettingPageState();
}
class _SettingPageState extends State<SettingPage> {
final SettingController _settingController = Get.put(SettingController());
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('设置'),
),
body: Column(
children: [
Obx(
() => Visibility(
visible: _settingController.userLogin.value,
child: ListTile(
onTap: () => _settingController.loginOut(),
dense: false,
title: const Text('退出登录'),
),
),
),
],
),
);
}
}

View File

@ -1,6 +1,7 @@
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
import 'package:pilipala/http/constants.dart';
import 'package:pilipala/http/init.dart';
import 'package:pilipala/http/user.dart';
import 'package:pilipala/pages/mine/index.dart';
import 'package:pilipala/utils/cookie.dart';
@ -21,6 +22,12 @@ class WebviewController extends GetxController {
pageTitle = Get.parameters['pageTitle']!;
webviewInit();
if (type == 'login') {
controller.clearCache();
controller.clearLocalStorage();
WebViewCookieManager().clearCookies();
controller.setUserAgent(Request().headerUa('mob'));
}
}
webviewInit() {
@ -49,7 +56,7 @@ class WebviewController extends GetxController {
if (result['status'] && result['data'].isLogin) {
SmartDialog.showToast('登录成功');
Get.find<MineController>().userInfo = result['data'];
// Get.back();
Get.back();
}
} catch (e) {
print(e);

View File

@ -4,6 +4,7 @@ import 'package:pilipala/pages/hot/index.dart';
import 'package:pilipala/pages/preview/index.dart';
import 'package:pilipala/pages/video/detail/index.dart';
import 'package:pilipala/pages/webview/index.dart';
import 'package:pilipala/pages/setting/index.dart';
class Routes {
static final List<GetPage> getPages = [
@ -16,6 +17,8 @@ class Routes {
// 图片预览
GetPage(name: '/preview', page: () => const ImagePreview()),
//
GetPage(name: '/webview', page: () => const WebviewPage())
GetPage(name: '/webview', page: () => const WebviewPage()),
// 设置
GetPage(name: '/setting', page: () => const SettingPage()),
];
}

View File

@ -2,7 +2,6 @@ import 'dart:io';
import 'package:cookie_jar/cookie_jar.dart';
import 'package:pilipala/http/init.dart';
import 'package:pilipala/utils/utils.dart';
import 'package:dio_cookie_manager/dio_cookie_manager.dart';
class SetCookie {
static onSet(List cookiesList, String url) async {

24
lib/utils/storage.dart Normal file
View File

@ -0,0 +1,24 @@
import 'package:hive/hive.dart';
import 'package:path_provider/path_provider.dart';
class GStrorage {
static late final Box user;
static Future<void> init() async {
final dir = await getApplicationDocumentsDirectory();
final path = dir.path;
Hive.init('$path/hive');
user = await Hive.openBox('user');
}
}
// 约定 key
class UserBoxKey {
static const String userName = 'userName';
// 头像
static const String userFace = 'userFace';
// mid
static const String userMid = 'userMid';
// 登录状态
static const String userLogin = 'userLogin';
}