mod: 图标&样式&网页登录

This commit is contained in:
guozhigq
2023-05-08 10:17:35 +08:00
parent a66bb08ca8
commit 89766a72d9
59 changed files with 449 additions and 149 deletions

View File

@ -5,18 +5,22 @@ class Api {
static const String hotList = '/x/web-interface/popular';
// 视频详情
// 竖屏 https://api.bilibili.com/x/web-interface/view?aid=527403921
// https://api.bilibili.com/x/web-interface/view/detail 获取视频超详细信息(web端)
static const String videoIntro = '/x/web-interface/view';
// 视频详情页 相关视频
static const String relatedList = '/x/web-interface/archive/related';
// 用户(被)关注数、投稿数
// https://api.bilibili.com/x/relation/stat?vmid=697166795
static const String userStat = '/x/relation/stat';
// 评论列表
static const String replyList = '/x/v2/reply';
// 楼中楼
static const String replyReplyList = '/x/v2/reply/reply';
// 用户(被)关注数、投稿数
// https://api.bilibili.com/x/relation/stat?vmid=697166795
static const String userStat = '/x/relation/stat';
// 获取用户信息
static const String userInfo = '/x/web-interface/nav';
}

View File

@ -10,4 +10,13 @@ class UserHttp {
return {'status': false};
}
}
static Future<dynamic> userInfo() async {
var res = await Request().get(Api.userInfo);
if (res.data['code'] == 0) {
return {'status': true, 'data': res.data['data']};
} else {
return {'status': false};
}
}
}

View File

@ -1,3 +1,4 @@
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
import 'package:flutter/material.dart';
import 'package:dynamic_color/dynamic_color.dart';
@ -23,18 +24,24 @@ class MyApp extends StatelessWidget {
return GetMaterialApp(
title: 'PiLiPaLa',
theme: ThemeData(
colorScheme: lightDynamic ??
ColorScheme.fromSeed(
seedColor: Colors.green, brightness: Brightness.light),
useMaterial3: true),
colorScheme: lightDynamic ??
ColorScheme.fromSeed(
seedColor: Colors.green,
brightness: Brightness.light,
),
useMaterial3: true,
),
darkTheme: ThemeData(
colorScheme: darkDynamic ??
ColorScheme.fromSeed(
seedColor: Colors.green, brightness: Brightness.dark),
useMaterial3: true),
colorScheme: darkDynamic ??
ColorScheme.fromSeed(
seedColor: Colors.green,
brightness: Brightness.dark,
),
useMaterial3: true,
),
getPages: Routes.getPages,
home: const MainApp(),
// home: const Scaffold(),
builder: FlutterSmartDialog.init(),
);
}),
);

View File

@ -0,0 +1,7 @@
class UserInfoData {
UserInfoData({
this.isLogin,
});
bool? isLogin;
}

View File

@ -30,7 +30,7 @@ class HomeAppBar extends StatelessWidget {
title: const Text(
'PiLiPaLa',
style: TextStyle(
fontSize: 19,
fontSize: 20,
fontWeight: FontWeight.bold,
letterSpacing: 1,
fontFamily: 'ArchivoNarrow',

View File

@ -0,0 +1,14 @@
import 'package:get/get.dart';
import 'package:pilipala/http/user.dart';
class MineController extends GetxController {
@override
void onInit() {
super.onInit();
// queryUserInfo();
}
Future queryUserInfo() async {
var res = await UserHttp.userInfo();
}
}

View File

@ -1,6 +1,8 @@
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 'controller.dart';
class MinePage extends StatefulWidget {
const MinePage({super.key});
@ -10,6 +12,8 @@ class MinePage extends StatefulWidget {
}
class _MinePageState extends State<MinePage> {
final MineController _mineController = Get.put(MineController());
@override
Widget build(BuildContext context) {
return Scaffold(
@ -30,136 +34,161 @@ class _MinePageState extends State<MinePage> {
const SizedBox(width: 10),
],
),
body: Padding(
padding: const EdgeInsets.only(left: 12, right: 12),
body: RefreshIndicator(
onRefresh: () async {
await Future.delayed(Duration(seconds: 2));
},
child: Column(
children: [
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'),
InkWell(
onTap: () {
Get.toNamed(
'/webview',
parameters: {
'url':
'https://passport.bilibili.com/h5-app/passport/login',
'type': 'login',
'pageTitle': '登录bilibili',
},
);
},
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(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),
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,
),
],
Padding(
padding: const EdgeInsets.only(left: 12, right: 12),
child: LayoutBuilder(
builder: (context, constraints) {
return SizedBox(
height: constraints.maxWidth / 4 * 0.8,
child: GridView.count(
primary: false,
padding: const EdgeInsets.all(0),
crossAxisCount: 4,
childAspectRatio: 1.25,
children: <Widget>[
ActionItem(
icon: const Icon(FontAwesomeIcons.download),
onTap: () => {},
text: '离线缓存',
),
),
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,
),
],
ActionItem(
icon: const Icon(FontAwesomeIcons.clockRotateLeft),
onTap: () => {},
text: '历史记录',
),
),
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,
),
],
ActionItem(
icon: const Icon(FontAwesomeIcons.star),
onTap: () => {},
text: '我的收藏',
),
),
],
),
);
},
),
const SizedBox(height: 20),
LayoutBuilder(
builder: (context, constraints) {
return SizedBox(
height: constraints.maxWidth / 4 * 0.8,
child: GridView.count(
primary: false,
padding: const EdgeInsets.all(0),
crossAxisCount: 4,
childAspectRatio: 1.25,
children: <Widget>[
ActionItem(
icon: const Icon(FontAwesomeIcons.download),
onTap: () => {},
text: '离线缓存',
),
ActionItem(
icon: const Icon(FontAwesomeIcons.clockRotateLeft),
onTap: () => {},
text: '历史记录',
),
ActionItem(
icon: const Icon(FontAwesomeIcons.star),
onTap: () => {},
text: '我的收藏',
),
ActionItem(
icon: const Icon(FontAwesomeIcons.film),
onTap: () => {},
text: '稍后再看',
),
],
),
);
},
ActionItem(
icon: const Icon(FontAwesomeIcons.film),
onTap: () => {},
text: '稍后再看',
),
],
),
);
},
),
),
],
),
@ -188,7 +217,10 @@ class ActionItem extends StatelessWidget {
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(icon!.icon!),
Icon(
icon!.icon!,
color: Theme.of(context).colorScheme.outline,
),
const SizedBox(height: 8),
Text(
text!,

View File

@ -247,7 +247,7 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
children: const [
Icon(
FontAwesomeIcons.lemon,
size: 17,
size: 15,
),
SizedBox(width: 4),
Text('关注'),
@ -334,10 +334,7 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
? widget.videoDetail!.stat!.coin!.toString()
: '-'),
ActionItem(
icon: const Icon(
FontAwesomeIcons.heart,
size: 17,
),
icon: const Icon(FontAwesomeIcons.star),
onTap: () => {},
selectStatus: false,
loadingStatus: widget.loadingStatus,
@ -386,10 +383,11 @@ class ActionItem extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(icon!.icon!,
size: 20,
color: selectStatus
? Theme.of(context).primaryColor
: Theme.of(context).colorScheme.outline),
const SizedBox(height: 2),
const SizedBox(height: 4),
AnimatedOpacity(
opacity: loadingStatus! ? 0 : 1,
duration: const Duration(milliseconds: 200),

View File

@ -164,7 +164,7 @@ class ReplyItem extends StatelessWidget {
TextSpan(
children: [
if (replyItem!.isTop!)
WidgetSpan(child: UpTag(tagText: '置顶')),
WidgetSpan(child: UpTag(tagText: 'TOP')),
buildContent(context, replyItem!.content!),
],
),
@ -200,6 +200,15 @@ class ReplyItem extends StatelessWidget {
.labelMedium!
.copyWith(color: Theme.of(context).colorScheme.outline),
),
if (replyItem!.replyControl != null &&
replyItem!.replyControl!.location != null)
Text(
'${replyItem!.replyControl!.location!}',
style: Theme.of(context)
.textTheme
.labelMedium!
.copyWith(color: Theme.of(context).colorScheme.outline),
),
const Spacer(),
if (replyControl!.isUpTop!)
Icon(Icons.favorite, color: Colors.red[400], size: 18),
@ -209,7 +218,7 @@ class ReplyItem extends StatelessWidget {
child: Row(
children: [
Icon(
Icons.thumb_up_alt_outlined,
FontAwesomeIcons.thumbsUp,
size: 16,
color: color,
),

View File

@ -74,7 +74,7 @@ class _VideoDetailPageState extends State<VideoDetailPage> {
body: Column(
children: [
Container(
height: 50,
height: 45,
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
@ -91,7 +91,6 @@ class _VideoDetailPageState extends State<VideoDetailPage> {
margin: const EdgeInsets.only(left: 20),
child: Obx(
() => TabBar(
splashBorderRadius: BorderRadius.circular(6),
dividerColor: Colors.transparent,
tabs: videoDetailController.tabs
.map((String name) => Tab(text: name))

View File

@ -0,0 +1,69 @@
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
import 'package:pilipala/http/constants.dart';
import 'package:pilipala/http/user.dart';
import 'package:pilipala/utils/cookie.dart';
import 'package:webview_cookie_manager/webview_cookie_manager.dart';
import 'package:webview_flutter/webview_flutter.dart';
class WebviewController extends GetxController {
String url = '';
String type = '';
String pageTitle = '';
final WebViewController controller = WebViewController();
@override
void onInit() {
super.onInit();
url = Get.parameters['url']!;
type = Get.parameters['type']!;
pageTitle = Get.parameters['pageTitle']!;
webviewInit();
}
webviewInit() {
controller
..setJavaScriptMode(JavaScriptMode.unrestricted)
..setNavigationDelegate(
NavigationDelegate(
// 页面加载
onProgress: (int progress) {
// Update loading bar.
},
onPageStarted: (String url) {},
// 加载完成
onPageFinished: (String url) async {
if (url.startsWith(
'https://passport.bilibili.com/web/sso/exchange_cookie') ||
url.startsWith('https://m.bilibili.com/')) {
try {
var cookies =
await WebviewCookieManager().getCookies(HttpString.baseUrl);
var apiCookies =
await WebviewCookieManager().getCookies(HttpString.baseUrl);
await SetCookie.onSet(cookies, HttpString.baseUrl);
await SetCookie.onSet(apiCookies, HttpString.baseApiUrl);
var result = await UserHttp.userInfo();
bool isLogin = result['data']['isLogin'];
if (isLogin) {
SmartDialog.showToast('登录成功');
Get.back();
}
} catch (e) {
print(e);
}
}
},
onWebResourceError: (WebResourceError error) {},
onNavigationRequest: (NavigationRequest request) {
if (request.url.startsWith('https://www.youtube.com/')) {
return NavigationDecision.prevent;
}
return NavigationDecision.navigate;
},
),
)
..loadRequest(Uri.parse(url));
}
}

View File

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

View File

@ -0,0 +1,29 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'controller.dart';
import 'package:webview_flutter/webview_flutter.dart';
class WebviewPage extends StatefulWidget {
const WebviewPage({super.key});
@override
State<WebviewPage> createState() => _WebviewPageState();
}
class _WebviewPageState extends State<WebviewPage> {
final WebviewController _webviewController = Get.put(WebviewController());
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
centerTitle: false,
title: Text(
_webviewController.pageTitle,
style: Theme.of(context).textTheme.titleMedium,
),
),
body: WebViewWidget(controller: _webviewController.controller),
);
}
}

View File

@ -3,6 +3,7 @@ import 'package:pilipala/pages/home/index.dart';
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';
class Routes {
static final List<GetPage> getPages = [
@ -13,6 +14,8 @@ class Routes {
// 视频详情
GetPage(name: '/video', page: () => const VideoDetailPage()),
// 图片预览
GetPage(name: '/preview', page: () => const ImagePreview())
GetPage(name: '/preview', page: () => const ImagePreview()),
//
GetPage(name: '/webview', page: () => const WebviewPage())
];
}

27
lib/utils/cookie.dart Normal file
View File

@ -0,0 +1,27 @@
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 {
// domain url
List<Cookie> jarCookies = [];
if (cookiesList.isNotEmpty) {
for (var i in cookiesList) {
Cookie jarCookie = Cookie(i.name, i.value);
jarCookies.add(jarCookie);
}
}
String cookiePath = await Utils.getCookiePath();
PersistCookieJar cookieJar = PersistCookieJar(
ignoreExpires: true,
storage: FileStorage(cookiePath),
);
await cookieJar.saveFromResponse(Uri.parse(url), jarCookies);
// 重新设置 cookie
Request.setCookie();
return true;
}
}