mod: merge main
@ -47,10 +47,11 @@ android {
|
||||
applicationId "com.example.pilipala"
|
||||
// You can update the following values to match your application needs.
|
||||
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
|
||||
minSdkVersion flutter.minSdkVersion
|
||||
// minSdkVersion flutter.minSdkVersion
|
||||
targetSdkVersion flutter.targetSdkVersion
|
||||
versionCode flutterVersionCode.toInteger()
|
||||
versionName flutterVersionName
|
||||
minSdkVersion 19
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
|
After Width: | Height: | Size: 4.7 KiB |
After Width: | Height: | Size: 4.7 KiB |
After Width: | Height: | Size: 3.1 KiB |
After Width: | Height: | Size: 3.1 KiB |
After Width: | Height: | Size: 6.3 KiB |
After Width: | Height: | Size: 6.3 KiB |
After Width: | Height: | Size: 9.6 KiB |
After Width: | Height: | Size: 9.6 KiB |
After Width: | Height: | Size: 13 KiB |
After Width: | Height: | Size: 13 KiB |
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
<monochrome android:drawable="@drawable/ic_launcher_monochrome"/>
|
||||
</adaptive-icon>
|
Before Width: | Height: | Size: 544 B After Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 442 B After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 721 B After Width: | Height: | Size: 2.6 KiB |
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 4.2 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 5.5 KiB |
4
android/app/src/main/res/values/colors.xml
Normal file
@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="ic_launcher_background">#ffffff</color>
|
||||
</resources>
|
BIN
assets/fonts/ArchivoNarrow-BoldItalic.ttf
Normal file
BIN
assets/fonts/fansCard.ttf
Normal file
BIN
assets/images/logo/logo_android.png
Normal file
After Width: | Height: | Size: 26 KiB |
BIN
assets/images/logo/logo_big.png
Normal file
After Width: | Height: | Size: 45 KiB |
BIN
assets/images/logo/logo_ios.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
assets/images/lv/lv0.png
Normal file
After Width: | Height: | Size: 514 B |
BIN
assets/images/lv/lv1.png
Normal file
After Width: | Height: | Size: 524 B |
BIN
assets/images/lv/lv2.png
Normal file
After Width: | Height: | Size: 518 B |
BIN
assets/images/lv/lv3.png
Normal file
After Width: | Height: | Size: 541 B |
BIN
assets/images/lv/lv4.png
Normal file
After Width: | Height: | Size: 498 B |
BIN
assets/images/lv/lv5.png
Normal file
After Width: | Height: | Size: 539 B |
BIN
assets/images/lv/lv6.png
Normal file
After Width: | Height: | Size: 517 B |
@ -2,23 +2,44 @@ PODS:
|
||||
- connectivity_plus (0.0.1):
|
||||
- Flutter
|
||||
- ReachabilitySwift
|
||||
- device_info_plus (0.0.1):
|
||||
- Flutter
|
||||
- Flutter (1.0.0)
|
||||
- FMDB (2.7.5):
|
||||
- FMDB/standard (= 2.7.5)
|
||||
- FMDB/standard (2.7.5)
|
||||
- image_gallery_saver (1.5.0):
|
||||
- Flutter
|
||||
- path_provider_foundation (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- permission_handler_apple (9.0.4):
|
||||
- Flutter
|
||||
- ReachabilitySwift (5.0.0)
|
||||
- share_plus (0.0.1):
|
||||
- Flutter
|
||||
- sqflite (0.0.2):
|
||||
- Flutter
|
||||
- FMDB (>= 2.7.5)
|
||||
- url_launcher_ios (0.0.1):
|
||||
- Flutter
|
||||
- webview_cookie_manager (0.0.1):
|
||||
- Flutter
|
||||
- webview_flutter_wkwebview (0.0.1):
|
||||
- Flutter
|
||||
|
||||
DEPENDENCIES:
|
||||
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`)
|
||||
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
|
||||
- Flutter (from `Flutter`)
|
||||
- image_gallery_saver (from `.symlinks/plugins/image_gallery_saver/ios`)
|
||||
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/ios`)
|
||||
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
|
||||
- share_plus (from `.symlinks/plugins/share_plus/ios`)
|
||||
- sqflite (from `.symlinks/plugins/sqflite/ios`)
|
||||
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
||||
- webview_cookie_manager (from `.symlinks/plugins/webview_cookie_manager/ios`)
|
||||
- webview_flutter_wkwebview (from `.symlinks/plugins/webview_flutter_wkwebview/ios`)
|
||||
|
||||
SPEC REPOS:
|
||||
trunk:
|
||||
@ -28,21 +49,42 @@ SPEC REPOS:
|
||||
EXTERNAL SOURCES:
|
||||
connectivity_plus:
|
||||
:path: ".symlinks/plugins/connectivity_plus/ios"
|
||||
device_info_plus:
|
||||
:path: ".symlinks/plugins/device_info_plus/ios"
|
||||
Flutter:
|
||||
:path: Flutter
|
||||
image_gallery_saver:
|
||||
:path: ".symlinks/plugins/image_gallery_saver/ios"
|
||||
path_provider_foundation:
|
||||
:path: ".symlinks/plugins/path_provider_foundation/ios"
|
||||
permission_handler_apple:
|
||||
:path: ".symlinks/plugins/permission_handler_apple/ios"
|
||||
share_plus:
|
||||
:path: ".symlinks/plugins/share_plus/ios"
|
||||
sqflite:
|
||||
:path: ".symlinks/plugins/sqflite/ios"
|
||||
url_launcher_ios:
|
||||
:path: ".symlinks/plugins/url_launcher_ios/ios"
|
||||
webview_cookie_manager:
|
||||
:path: ".symlinks/plugins/webview_cookie_manager/ios"
|
||||
webview_flutter_wkwebview:
|
||||
:path: ".symlinks/plugins/webview_flutter_wkwebview/ios"
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
connectivity_plus: 413a8857dd5d9f1c399a39130850d02fe0feaf7e
|
||||
device_info_plus: e5c5da33f982a436e103237c0c85f9031142abed
|
||||
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
|
||||
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
|
||||
image_gallery_saver: 259eab68fb271cfd57d599904f7acdc7832e7ef2
|
||||
path_provider_foundation: c68054786f1b4f3343858c1e1d0caaded73f0be9
|
||||
permission_handler_apple: 44366e37eaf29454a1e7b1b7d736c2cceaeb17ce
|
||||
ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825
|
||||
share_plus: 056a1e8ac890df3e33cb503afffaf1e9b4fbae68
|
||||
sqflite: 6d358c025f5b867b29ed92fc697fd34924e11904
|
||||
url_launcher_ios: 08a3dfac5fb39e8759aeb0abbd5d9480f30fc8b4
|
||||
webview_cookie_manager: eaf920722b493bd0f7611b5484771ca53fed03f7
|
||||
webview_flutter_wkwebview: 2e2d318f21a5e036e2c3f26171342e95908bd60a
|
||||
|
||||
PODFILE CHECKSUM: ef19549a9bc3046e7bb7d2fab4d021637c0c58a3
|
||||
|
||||
COCOAPODS: 1.11.3
|
||||
COCOAPODS: 1.12.1
|
||||
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 295 B After Width: | Height: | Size: 309 B |
Before Width: | Height: | Size: 406 B After Width: | Height: | Size: 660 B |
Before Width: | Height: | Size: 450 B After Width: | Height: | Size: 1.0 KiB |
Before Width: | Height: | Size: 282 B After Width: | Height: | Size: 443 B |
Before Width: | Height: | Size: 462 B After Width: | Height: | Size: 1.0 KiB |
Before Width: | Height: | Size: 704 B After Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 406 B After Width: | Height: | Size: 660 B |
Before Width: | Height: | Size: 586 B After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 862 B After Width: | Height: | Size: 2.3 KiB |
After Width: | Height: | Size: 858 B |
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 1020 B |
After Width: | Height: | Size: 2.3 KiB |
Before Width: | Height: | Size: 862 B After Width: | Height: | Size: 2.3 KiB |
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 3.6 KiB |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 762 B After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 2.9 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 3.3 KiB |
119
lib/common/skeleton/video_card_h.dart
Normal file
@ -0,0 +1,119 @@
|
||||
import 'package:pilipala/common/constants.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'skeleton.dart';
|
||||
|
||||
class VideoCardHSkeleton extends StatefulWidget {
|
||||
const VideoCardHSkeleton({super.key});
|
||||
|
||||
@override
|
||||
State<VideoCardHSkeleton> createState() => _VideoCardHSkeletonState();
|
||||
}
|
||||
|
||||
class _VideoCardHSkeletonState extends State<VideoCardHSkeleton> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Skeleton(
|
||||
child: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(
|
||||
StyleString.cardSpace, 7, StyleString.cardSpace, 7),
|
||||
child: LayoutBuilder(
|
||||
builder: (context, boxConstraints) {
|
||||
double width =
|
||||
(boxConstraints.maxWidth - StyleString.cardSpace * 6) / 2;
|
||||
return SizedBox(
|
||||
height: width / StyleString.aspectRatio,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
AspectRatio(
|
||||
aspectRatio: StyleString.aspectRatio,
|
||||
child: LayoutBuilder(
|
||||
builder: (context, boxConstraints) {
|
||||
double maxWidth = boxConstraints.maxWidth;
|
||||
double maxHeight = boxConstraints.maxHeight;
|
||||
double PR = MediaQuery.of(context).devicePixelRatio;
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onInverseSurface,
|
||||
borderRadius: BorderRadius.circular(
|
||||
StyleString.imgRadius.x),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
// VideoContent(videoItem: videoItem)
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(10, 4, 6, 4),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onInverseSurface,
|
||||
width: 200,
|
||||
height: 11,
|
||||
margin: const EdgeInsets.only(bottom: 5),
|
||||
),
|
||||
Container(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onInverseSurface,
|
||||
width: 150,
|
||||
height: 13,
|
||||
),
|
||||
const Spacer(),
|
||||
Container(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onInverseSurface,
|
||||
width: 100,
|
||||
height: 13,
|
||||
margin: const EdgeInsets.only(bottom: 5),
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onInverseSurface,
|
||||
width: 40,
|
||||
height: 13,
|
||||
margin: const EdgeInsets.only(right: 8),
|
||||
),
|
||||
Container(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onInverseSurface,
|
||||
width: 40,
|
||||
height: 13,
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
)),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
Divider(
|
||||
height: 1,
|
||||
indent: 8,
|
||||
endIndent: 12,
|
||||
color: Theme.of(context).dividerColor.withOpacity(0.08),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -23,14 +23,7 @@ class VideoCardVSkeleton extends StatelessWidget {
|
||||
return Container(
|
||||
clipBehavior: Clip.hardEdge,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
border: Border.all(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.outline
|
||||
.withOpacity(0.1),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
@ -49,15 +42,15 @@ class VideoCardVSkeleton extends StatelessWidget {
|
||||
Container(
|
||||
width: 200,
|
||||
height: 13,
|
||||
margin: const EdgeInsets.only(bottom: 5),
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
),
|
||||
const SizedBox(height: 5),
|
||||
Container(
|
||||
width: 150,
|
||||
height: 13,
|
||||
margin: const EdgeInsets.only(bottom: 12),
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Container(
|
||||
width: 80,
|
||||
height: 13,
|
||||
|
78
lib/common/skeleton/video_reply.dart
Normal file
@ -0,0 +1,78 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'skeleton.dart';
|
||||
|
||||
class VideoReplySkeleton extends StatelessWidget {
|
||||
const VideoReplySkeleton({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Color bgColor = Theme.of(context).colorScheme.onInverseSurface;
|
||||
return Skeleton(
|
||||
child: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(12, 8, 8, 2),
|
||||
child: Row(
|
||||
children: [
|
||||
ClipOval(
|
||||
child: Container(
|
||||
width: 34,
|
||||
height: 34,
|
||||
color: bgColor,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Container(
|
||||
width: 80,
|
||||
height: 13,
|
||||
color: bgColor,
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
Container(
|
||||
width: double.infinity,
|
||||
margin:
|
||||
const EdgeInsets.only(top: 4, left: 57, right: 6, bottom: 6),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
width: 300,
|
||||
height: 14,
|
||||
margin: const EdgeInsets.only(bottom: 4),
|
||||
color: bgColor,
|
||||
),
|
||||
Container(
|
||||
width: 180,
|
||||
height: 14,
|
||||
margin: const EdgeInsets.only(bottom: 10),
|
||||
color: bgColor,
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 40,
|
||||
height: 14,
|
||||
margin: const EdgeInsets.only(bottom: 4),
|
||||
color: bgColor,
|
||||
),
|
||||
const Spacer(),
|
||||
Container(
|
||||
width: 40,
|
||||
height: 14,
|
||||
margin: const EdgeInsets.only(bottom: 4),
|
||||
color: bgColor,
|
||||
),
|
||||
const SizedBox(width: 8)
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
33
lib/common/widgets/appbar.dart
Normal file
@ -0,0 +1,33 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class AppBarWidget extends StatelessWidget implements PreferredSizeWidget {
|
||||
const AppBarWidget({
|
||||
required this.child,
|
||||
required this.controller,
|
||||
required this.visible,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
final PreferredSizeWidget child;
|
||||
final AnimationController controller;
|
||||
final bool visible;
|
||||
|
||||
@override
|
||||
// TODO: implement preferredSize
|
||||
Size get preferredSize => child.preferredSize;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
visible ? controller.reverse() : controller.forward();
|
||||
return SlideTransition(
|
||||
position: Tween<Offset>(
|
||||
begin: Offset.zero,
|
||||
end: const Offset(0, -1),
|
||||
).animate(CurvedAnimation(
|
||||
parent: controller,
|
||||
curve: Curves.easeInOutBack,
|
||||
)),
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
}
|
34
lib/common/widgets/http_error.dart
Normal file
@ -0,0 +1,34 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class HttpError extends StatelessWidget {
|
||||
HttpError({required this.errMsg, required this.fn, super.key});
|
||||
|
||||
String errMsg = '';
|
||||
final Function()? fn;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SliverToBoxAdapter(
|
||||
child: SizedBox(
|
||||
height: 150,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
errMsg,
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
fn!();
|
||||
},
|
||||
child: const Text('点击重试'))
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:pilipala/common/constants.dart';
|
||||
|
||||
class NetworkImgLayer extends StatelessWidget {
|
||||
final String? src;
|
||||
@ -29,11 +30,16 @@ class NetworkImgLayer extends StatelessWidget {
|
||||
// double pr = 2;
|
||||
return src != ''
|
||||
? ClipRRect(
|
||||
borderRadius: BorderRadius.circular(type == 'avatar' ? 50 : 4),
|
||||
borderRadius: BorderRadius.circular(type == 'avatar'
|
||||
? 50
|
||||
: type == 'emote'
|
||||
? 0
|
||||
: StyleString.imgRadius.x),
|
||||
child: CachedNetworkImage(
|
||||
imageUrl: src!,
|
||||
width: width ?? double.infinity,
|
||||
height: height ?? double.infinity,
|
||||
alignment: Alignment.center,
|
||||
maxWidthDiskCache: ((cacheW ?? width!) * pr).toInt(),
|
||||
// maxHeightDiskCache: (cacheH ?? height!).toInt(),
|
||||
memCacheWidth: ((cacheW ?? width!) * pr).toInt(),
|
||||
|
@ -1,3 +1,4 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:pilipala/utils/utils.dart';
|
||||
|
||||
@ -11,21 +12,21 @@ class StatDanMu extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Color color =
|
||||
theme == 'white' ? Colors.white : Theme.of(context).colorScheme.outline;
|
||||
return Row(
|
||||
children: [
|
||||
Image.asset(
|
||||
'assets/images/dm_$theme.png',
|
||||
width: size == 'medium' ? 16 : 14,
|
||||
height: size == 'medium' ? 16 : 14,
|
||||
Icon(
|
||||
CupertinoIcons.ellipses_bubble,
|
||||
size: 14,
|
||||
color: color,
|
||||
),
|
||||
const SizedBox(width: 2),
|
||||
const SizedBox(width: 3),
|
||||
Text(
|
||||
Utils.numFormat(danmu!),
|
||||
style: TextStyle(
|
||||
fontSize: size == 'medium' ? 12 : 11,
|
||||
color: theme == 'white'
|
||||
? Colors.white
|
||||
: Theme.of(context).colorScheme.outline,
|
||||
color: color,
|
||||
),
|
||||
)
|
||||
],
|
||||
|
24
lib/common/widgets/stat/up.dart
Normal file
@ -0,0 +1,24 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class UpTag extends StatelessWidget {
|
||||
const UpTag({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
width: 14,
|
||||
height: 10,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(2),
|
||||
border: Border.all(color: Theme.of(context).colorScheme.outline)),
|
||||
margin: const EdgeInsets.only(right: 4),
|
||||
child: Center(
|
||||
child: Text(
|
||||
'UP',
|
||||
style: TextStyle(
|
||||
fontSize: 6, color: Theme.of(context).colorScheme.outline),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,3 +1,4 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:pilipala/utils/utils.dart';
|
||||
|
||||
@ -6,26 +7,26 @@ class StatView extends StatelessWidget {
|
||||
final int? view;
|
||||
final String? size;
|
||||
|
||||
const StatView({Key? key, this.theme, this.view, this.size}) : super(key: key);
|
||||
const StatView({Key? key, this.theme, this.view, this.size})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Color color =
|
||||
theme == 'white' ? Colors.white : Theme.of(context).colorScheme.outline;
|
||||
return Row(
|
||||
children: [
|
||||
Image.asset(
|
||||
'assets/images/view_$theme.png',
|
||||
width: size == 'medium' ? 16 : 14,
|
||||
height: size == 'medium' ? 16 : 14,
|
||||
Icon(
|
||||
CupertinoIcons.play_rectangle,
|
||||
size: 13,
|
||||
color: color,
|
||||
),
|
||||
const SizedBox(width: 2),
|
||||
const SizedBox(width: 3),
|
||||
Text(
|
||||
Utils.numFormat(view!),
|
||||
// videoItem['stat']['view'].toString(),
|
||||
style: TextStyle(
|
||||
fontSize: size == 'medium' ? 12 : 11,
|
||||
color: theme == 'white'
|
||||
? Colors.white
|
||||
: Theme.of(context).colorScheme.outline,
|
||||
color: color,
|
||||
),
|
||||
),
|
||||
],
|
||||
|
@ -7,6 +7,7 @@ import 'package:pilipala/common/widgets/network_img_layer.dart';
|
||||
|
||||
// 视频卡片 - 水平布局
|
||||
class VideoCardH extends StatelessWidget {
|
||||
// ignore: prefer_typing_uninitialized_variables
|
||||
var videoItem;
|
||||
Function()? longPress;
|
||||
Function()? longPressEnd;
|
||||
@ -20,9 +21,9 @@ class VideoCardH extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Material(
|
||||
child: Ink(
|
||||
child: GestureDetector(
|
||||
int aid = videoItem.aid;
|
||||
String heroTag = Utils.makeHeroTag(aid);
|
||||
return GestureDetector(
|
||||
onLongPress: () {
|
||||
longPress!();
|
||||
},
|
||||
@ -32,14 +33,16 @@ class VideoCardH extends StatelessWidget {
|
||||
child: InkWell(
|
||||
onTap: () async {
|
||||
await Future.delayed(const Duration(milliseconds: 200));
|
||||
int aid = videoItem.aid ?? videoItem.id;
|
||||
Get.toNamed('/video?aid=$aid',
|
||||
arguments: {'videoItem': videoItem});
|
||||
arguments: {'videoItem': videoItem, 'heroTag': heroTag});
|
||||
},
|
||||
child: Container(
|
||||
child: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(
|
||||
StyleString.cardSpace, 5, StyleString.cardSpace, 5),
|
||||
child: LayoutBuilder(builder: (context, boxConstraints) {
|
||||
StyleString.cardSpace, 7, StyleString.cardSpace, 7),
|
||||
child: LayoutBuilder(
|
||||
builder: (context, boxConstraints) {
|
||||
double width =
|
||||
(boxConstraints.maxWidth - StyleString.cardSpace * 6) / 2;
|
||||
return SizedBox(
|
||||
@ -50,22 +53,24 @@ class VideoCardH extends StatelessWidget {
|
||||
children: [
|
||||
AspectRatio(
|
||||
aspectRatio: StyleString.aspectRatio,
|
||||
// child: ClipRRect(
|
||||
// borderRadius: StyleString.mdRadius,
|
||||
child: LayoutBuilder(
|
||||
builder: (context, boxConstraints) {
|
||||
double maxWidth = boxConstraints.maxWidth;
|
||||
double maxHeight = boxConstraints.maxHeight;
|
||||
double PR = MediaQuery.of(context).devicePixelRatio;
|
||||
double PR =
|
||||
MediaQuery.of(context).devicePixelRatio;
|
||||
return Stack(
|
||||
children: [
|
||||
NetworkImgLayer(
|
||||
Hero(
|
||||
tag: heroTag,
|
||||
child: NetworkImgLayer(
|
||||
// src: videoItem['pic'] +
|
||||
// '@${(maxWidth * 2).toInt()}w',
|
||||
src: videoItem.pic + '@.webp',
|
||||
width: maxWidth,
|
||||
height: maxHeight,
|
||||
),
|
||||
),
|
||||
// Image.network( videoItem['pic'], width: double.infinity, height: double.infinity,),
|
||||
Positioned(
|
||||
right: 4,
|
||||
@ -74,30 +79,36 @@ class VideoCardH extends StatelessWidget {
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 1, horizontal: 6),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
color: Colors.black54.withOpacity(0.4)),
|
||||
borderRadius:
|
||||
BorderRadius.circular(4),
|
||||
color:
|
||||
Colors.black54.withOpacity(0.4)),
|
||||
child: Text(
|
||||
Utils.timeFormat(videoItem.duration!),
|
||||
style: const TextStyle(
|
||||
fontSize: 11, color: Colors.white),
|
||||
),
|
||||
),
|
||||
// Image.network( videoItem['pic'], width: double.infinity, height: double.infinity,),
|
||||
)
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
// ),
|
||||
),
|
||||
VideoContent(videoItem: videoItem)
|
||||
],
|
||||
),
|
||||
);
|
||||
}),
|
||||
// height: 124,
|
||||
},
|
||||
),
|
||||
),
|
||||
Divider(
|
||||
height: 1,
|
||||
indent: 8,
|
||||
endIndent: 12,
|
||||
color: Theme.of(context).dividerColor.withOpacity(0.08),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
@ -126,7 +137,7 @@ class VideoContent extends StatelessWidget {
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
const Spacer(),
|
||||
if (videoItem.rcmdReason != '' &&
|
||||
if (videoItem.rcmdReason != null &&
|
||||
videoItem.rcmdReason.content != '')
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 2, horizontal: 5),
|
||||
@ -145,12 +156,6 @@ class VideoContent extends StatelessWidget {
|
||||
const SizedBox(height: 4),
|
||||
Row(
|
||||
children: [
|
||||
Image.asset(
|
||||
'assets/images/up_gray.png',
|
||||
width: 14,
|
||||
height: 12,
|
||||
),
|
||||
const SizedBox(width: 2),
|
||||
Text(
|
||||
videoItem.owner.name,
|
||||
style: TextStyle(
|
||||
|
@ -22,6 +22,7 @@ class VideoCardV extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
String heroTag = Utils.makeHeroTag(videoItem.id);
|
||||
return Card(
|
||||
elevation: 0.8,
|
||||
clipBehavior: Clip.hardEdge,
|
||||
@ -40,7 +41,7 @@ class VideoCardV extends StatelessWidget {
|
||||
onTap: () async {
|
||||
await Future.delayed(const Duration(milliseconds: 200));
|
||||
Get.toNamed('/video?aid=${videoItem.id}',
|
||||
arguments: {'videoItem': videoItem});
|
||||
arguments: {'videoItem': videoItem, 'heroTag': heroTag});
|
||||
},
|
||||
child: Column(
|
||||
children: [
|
||||
@ -57,13 +58,16 @@ class VideoCardV extends StatelessWidget {
|
||||
double PR = MediaQuery.of(context).devicePixelRatio;
|
||||
return Stack(
|
||||
children: [
|
||||
NetworkImgLayer(
|
||||
Hero(
|
||||
tag: heroTag,
|
||||
child: NetworkImgLayer(
|
||||
// 指定图片尺寸
|
||||
// src: videoItem.pic + '@${(maxWidth * 2).toInt()}w',
|
||||
src: videoItem.pic + '@.webp',
|
||||
width: maxWidth,
|
||||
height: maxHeight,
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
left: 0,
|
||||
right: 0,
|
||||
@ -77,7 +81,7 @@ class VideoCardV extends StatelessWidget {
|
||||
duration: videoItem.duration,
|
||||
),
|
||||
),
|
||||
)
|
||||
),
|
||||
],
|
||||
);
|
||||
}),
|
||||
@ -141,6 +145,25 @@ class VideoContent extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 4)
|
||||
] else if (videoItem.isFollowed == 1) ...[
|
||||
Container(
|
||||
padding: const EdgeInsets.fromLTRB(3, 1, 3, 1),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.primaryContainer
|
||||
.withOpacity(0.6),
|
||||
borderRadius: BorderRadius.circular(3)),
|
||||
child: Text(
|
||||
'已关注',
|
||||
style: TextStyle(
|
||||
fontSize:
|
||||
Theme.of(context).textTheme.labelSmall!.fontSize,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 4)
|
||||
],
|
||||
Expanded(
|
||||
child: LayoutBuilder(builder:
|
||||
|
@ -1,9 +1,124 @@
|
||||
class Api {
|
||||
// 推荐视频
|
||||
static const String recommendList = '/x/web-interface/index/top/feed/rcmd';
|
||||
static const String recommendList = '/x/web-interface/index/top/rcmd';
|
||||
|
||||
// 热门视频
|
||||
static const String hotList = '/x/web-interface/popular';
|
||||
|
||||
// 视频详情
|
||||
// 竖屏 https://api.bilibili.com/x/web-interface/view?aid=527403921
|
||||
static const String videoDetail = '/x/web-interface/view';
|
||||
// https://api.bilibili.com/x/web-interface/view/detail 获取视频超详细信息(web端)
|
||||
static const String videoIntro = '/x/web-interface/view';
|
||||
// 视频详情 超详细
|
||||
// https://api.bilibili.com/x/web-interface/view/detail?aid=527403921
|
||||
|
||||
/// https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/video/action.md
|
||||
// 点赞 Post
|
||||
/// aid num 稿件avid 必要(可选) avid与bvid任选一个
|
||||
/// bvid str 稿件bvid 必要(可选) avid与bvid任选一个
|
||||
/// like num 操作方式 必要 1:点赞 2:取消赞
|
||||
// csrf str CSRF Token(位于cookie) 必要
|
||||
// https://api.bilibili.com/x/web-interface/archive/like
|
||||
static const String likeVideo = '/x/web-interface/archive/like';
|
||||
|
||||
//判断视频是否被点赞(双端)Get
|
||||
// access_key str APP登录Token APP方式必要
|
||||
/// aid num 稿件avid 必要(可选) avid与bvid任选一个
|
||||
/// bvid str 稿件bvid 必要(可选) avid与bvid任选一个
|
||||
// https://api.bilibili.com/x/web-interface/archive/has/like
|
||||
static const String hasLikeVideo = '/x/web-interface/archive/has/like';
|
||||
|
||||
// 视频点踩 web端不支持
|
||||
|
||||
// 投币视频(web端)POST
|
||||
/// aid num 稿件avid 必要(可选) avid与bvid任选一个
|
||||
/// bvid str 稿件bvid 必要(可选) avid与bvid任选一个
|
||||
/// multiply num 投币数量 必要 上限为2
|
||||
/// select_like num 是否附加点赞 非必要 0:不点赞 1:同时点赞 默认为0
|
||||
// csrf str CSRF Token(位于cookie) 必要
|
||||
// https://api.bilibili.com/x/web-interface/coin/add
|
||||
static const String coinVideo = '/x/web-interface/coin/add';
|
||||
|
||||
// 判断视频是否被投币(双端)GET
|
||||
// access_key str APP登录Token APP方式必要
|
||||
/// aid num 稿件avid 必要(可选) avid与bvid任选一个
|
||||
/// bvid str 稿件bvid 必要(可选) avid与bvid任选一个
|
||||
/// https://api.bilibili.com/x/web-interface/archive/coins
|
||||
static const String hasCoinVideo = '/x/web-interface/archive/coins';
|
||||
|
||||
// 收藏视频(双端)POST
|
||||
// access_key str APP登录Token APP方式必要
|
||||
/// rid num 稿件avid 必要
|
||||
/// type num 必须为2 必要
|
||||
/// add_media_ids nums 需要加入的收藏夹mlid 非必要 同时添加多个,用,(%2C)分隔
|
||||
/// del_media_ids nums 需要取消的收藏夹mlid 非必要 同时取消多个,用,(%2C)分隔
|
||||
// csrf str CSRF Token(位于cookie) Cookie方式必要
|
||||
// https://api.bilibili.com/medialist/gateway/coll/resource/deal
|
||||
// https://api.bilibili.com/x/v3/fav/resource/deal
|
||||
static const String favVideo = '/x/v3/fav/resource/deal';
|
||||
|
||||
// 判断视频是否被收藏(双端)GET
|
||||
/// aid
|
||||
// https://api.bilibili.com/x/v2/fav/video/favoured
|
||||
static const String hasFavVideo = '/x/v2/fav/video/favoured';
|
||||
|
||||
// 分享视频 (Web端) POST
|
||||
// https://api.bilibili.com/x/web-interface/share/add
|
||||
// aid num 稿件avid 必要(可选) avid与bvid任选一个
|
||||
// bvid str 稿件bvid 必要(可选) avid与bvid任选一个
|
||||
// csrf str CSRF Token(位于cookie) 必要
|
||||
|
||||
// 一键三连
|
||||
// https://api.bilibili.com/x/web-interface/archive/like/triple
|
||||
// aid num 稿件avid 必要(可选) avid与bvid任选一个
|
||||
// bvid str 稿件bvid 必要(可选) avid与bvid任选一个
|
||||
// csrf str CSRF Token(位于cookie) 必要
|
||||
static const String oneThree = '/x/web-interface/archive/like/triple';
|
||||
|
||||
// 获取指定用户创建的所有收藏夹信息
|
||||
// 该接口也能查询目标内容id存在于那些收藏夹中
|
||||
// up_mid num 目标用户mid 必要
|
||||
// type num 目标内容属性 非必要 默认为全部 0:全部 2:视频稿件
|
||||
// rid num 目标 视频稿件avid
|
||||
static const String videoInFolder = '/x/v3/fav/folder/created/list-all';
|
||||
|
||||
// 视频详情页 相关视频
|
||||
static const String relatedList = '/x/web-interface/archive/related';
|
||||
|
||||
// 评论列表
|
||||
static const String replyList = '/x/v2/reply';
|
||||
|
||||
// 楼中楼
|
||||
static const String replyReplyList = '/x/v2/reply/reply';
|
||||
|
||||
// 发表评论
|
||||
// https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/comment/action.md
|
||||
static const String replyAdd = '/x/v2/reply/add';
|
||||
|
||||
// 用户(被)关注数、投稿数
|
||||
// https://api.bilibili.com/x/relation/stat?vmid=697166795
|
||||
static const String userStat = '/x/relation/stat';
|
||||
|
||||
// 获取用户信息
|
||||
static const String userInfo = '/x/web-interface/nav';
|
||||
|
||||
// 获取当前用户状态
|
||||
static const String userStatOwner = '/x/web-interface/nav/stat';
|
||||
|
||||
// 收藏夹
|
||||
// https://api.bilibili.com/x/v3/fav/folder/created/list?pn=1&ps=10&up_mid=17340771
|
||||
static const String userFavFolder = '/x/v3/fav/folder/created/list';
|
||||
|
||||
/// 收藏夹 详情
|
||||
/// media_id int 收藏夹id
|
||||
/// pn int 当前页
|
||||
/// ps int pageSize
|
||||
/// keyword String 搜索词
|
||||
/// order String 排序方式 view 最多播放 mtime 最近收藏 pubtime 最近投稿
|
||||
/// tid int 分区id
|
||||
// https://api.bilibili.com/x/v3/fav/resource/list?media_id=76614671&pn=1&ps=20&keyword=&order=mtime&type=0&tid=0
|
||||
static const String userFavFolderDetail = '/x/v3/fav/resource/list';
|
||||
|
||||
// 正在直播的up & 关注的up
|
||||
// https://api.bilibili.com/x/polymer/web-dynamic/v1/portal
|
||||
}
|
||||
|
@ -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,27 @@ 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);
|
||||
}
|
||||
|
||||
// 从cookie中获取 csrf token
|
||||
static Future<String> getCsrf() async {
|
||||
var cookies = await cookieManager.cookieJar
|
||||
.loadForRequest(Uri.parse(HttpString.baseApiUrl));
|
||||
// for (var i in cookies) {
|
||||
// print(i);
|
||||
// }
|
||||
var token = cookies.firstWhere((e) => e.name == 'bili_jct').value;
|
||||
return token;
|
||||
}
|
||||
|
||||
/*
|
||||
* config it and create
|
||||
*/
|
||||
@ -111,20 +131,21 @@ class Request {
|
||||
return response;
|
||||
} on DioError catch (e) {
|
||||
print('get error: $e');
|
||||
return Future.error(ApiInterceptor.dioError(e));
|
||||
return Future.error(await ApiInterceptor.dioError(e));
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* post请求
|
||||
*/
|
||||
post(url, {data, options, cancelToken, extra}) async {
|
||||
post(url, {data, queryParameters, options, cancelToken, extra}) async {
|
||||
print('post-data: $data');
|
||||
Response response;
|
||||
try {
|
||||
response = await dio.post(
|
||||
url,
|
||||
data: data,
|
||||
queryParameters: queryParameters,
|
||||
options: options,
|
||||
cancelToken: cancelToken,
|
||||
);
|
||||
@ -132,7 +153,7 @@ class Request {
|
||||
return response;
|
||||
} on DioError catch (e) {
|
||||
print('post error: $e');
|
||||
return Future.error(ApiInterceptor.dioError(e));
|
||||
return Future.error(await ApiInterceptor.dioError(e));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,10 +1,12 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:connectivity_plus/connectivity_plus.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:get/get.dart' hide Response;
|
||||
|
||||
class ApiInterceptor extends Interceptor {
|
||||
@override
|
||||
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
|
||||
// print("请求之前");
|
||||
print("请求之前");
|
||||
// 在请求之前添加头部或认证信息
|
||||
// options.headers['Authorization'] = 'Bearer token';
|
||||
// options.headers['Content-Type'] = 'application/json';
|
||||
@ -13,15 +15,14 @@ class ApiInterceptor extends Interceptor {
|
||||
|
||||
@override
|
||||
void onResponse(Response response, ResponseInterceptorHandler handler) {
|
||||
// print("响应之前");
|
||||
handler.next(response);
|
||||
}
|
||||
|
||||
@override
|
||||
void onError(DioError err, ErrorInterceptorHandler handler) {
|
||||
void onError(DioError err, ErrorInterceptorHandler handler) async {
|
||||
// 处理网络请求错误
|
||||
|
||||
handler.next(err);
|
||||
// handler.next(err);
|
||||
SmartDialog.showToast(await dioError(err));
|
||||
super.onError(err, handler);
|
||||
}
|
||||
|
||||
@ -43,7 +44,7 @@ class ApiInterceptor extends Interceptor {
|
||||
return "发送请求超时,请检查网络设置";
|
||||
case DioErrorType.unknown:
|
||||
var res = await checkConect();
|
||||
return "$res 网络异常,请稍后重试!";
|
||||
return res + " \n 网络异常,请稍后重试!";
|
||||
default:
|
||||
return "Dio异常";
|
||||
}
|
||||
|
70
lib/http/reply.dart
Normal file
@ -0,0 +1,70 @@
|
||||
import 'package:pilipala/http/api.dart';
|
||||
import 'package:pilipala/http/init.dart';
|
||||
|
||||
class ReplyHttp {
|
||||
static Future replyList({
|
||||
required String oid,
|
||||
required int pageNum,
|
||||
required int type,
|
||||
int sort = 1,
|
||||
}) async {
|
||||
var res = await Request().get(Api.replyList, data: {
|
||||
'oid': oid,
|
||||
'pn': pageNum,
|
||||
'type': type,
|
||||
'sort': 1,
|
||||
});
|
||||
if (res.data['code'] == 0) {
|
||||
return {
|
||||
'status': true,
|
||||
'data': res.data['data'],
|
||||
};
|
||||
} else {
|
||||
Map errMap = {
|
||||
-400: '请求错误',
|
||||
-404: '无此项',
|
||||
12002: '评论区已关闭',
|
||||
12009: '评论主体的type不合法',
|
||||
};
|
||||
return {
|
||||
'status': false,
|
||||
'date': [],
|
||||
'msg': errMap[res.data['code']] ?? '请求异常',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
static Future replyReplyList({
|
||||
required String oid,
|
||||
required String root,
|
||||
required int pageNum,
|
||||
required int type,
|
||||
int sort = 1,
|
||||
}) async {
|
||||
var res = await Request().get(Api.replyReplyList, data: {
|
||||
'oid': oid,
|
||||
'root': root,
|
||||
'pn': pageNum,
|
||||
'type': type,
|
||||
'sort': 1,
|
||||
});
|
||||
if (res.data['code'] == 0) {
|
||||
return {
|
||||
'status': true,
|
||||
'data': res.data['data'],
|
||||
};
|
||||
} else {
|
||||
Map errMap = {
|
||||
-400: '请求错误',
|
||||
-404: '无此项',
|
||||
12002: '评论区已关闭',
|
||||
12009: '评论主体的type不合法',
|
||||
};
|
||||
return {
|
||||
'status': false,
|
||||
'date': [],
|
||||
'msg': errMap[res.data['code']] ?? '请求异常',
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
79
lib/http/user.dart
Normal file
@ -0,0 +1,79 @@
|
||||
import 'package:pilipala/http/api.dart';
|
||||
import 'package:pilipala/http/init.dart';
|
||||
import 'package:pilipala/models/user/fav_detail.dart';
|
||||
import 'package:pilipala/models/user/fav_folder.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 {
|
||||
var res = await Request().get(Api.userStat, data: {'vmid': mid});
|
||||
if (res.data['code'] == 0) {
|
||||
return {'status': true, 'data': res.data['data']};
|
||||
} else {
|
||||
return {'status': false};
|
||||
}
|
||||
}
|
||||
|
||||
static Future<dynamic> userInfo() async {
|
||||
var res = await Request().get(Api.userInfo);
|
||||
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, 'data': [], 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
|
||||
// 收藏夹
|
||||
static Future<dynamic> userfavFolder({
|
||||
required int pn,
|
||||
required int ps,
|
||||
required int mid,
|
||||
}) async {
|
||||
var res = await Request().get(Api.userFavFolder, data: {
|
||||
'pn': pn,
|
||||
'ps': ps,
|
||||
'up_mid': mid,
|
||||
});
|
||||
if (res.data['code'] == 0) {
|
||||
FavFolderData data = FavFolderData.fromJson(res.data['data']);
|
||||
return {'status': true, 'data': data};
|
||||
} else {
|
||||
return {'status': false, 'data': [], 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
|
||||
static Future<dynamic> userFavFolderDetail(
|
||||
{required int mediaId,
|
||||
required int pn,
|
||||
required int ps,
|
||||
String keyword = '',
|
||||
String order = 'mtime'}) async {
|
||||
var res = await Request().get(Api.userFavFolderDetail, data: {
|
||||
'media_id': mediaId,
|
||||
'pn': pn,
|
||||
'ps': ps,
|
||||
'keyword': keyword,
|
||||
'order': order,
|
||||
'type': 0,
|
||||
'tid': 0
|
||||
});
|
||||
if (res.data['code'] == 0) {
|
||||
FavDetailData data = FavDetailData.fromJson(res.data['data']);
|
||||
return {'status': true, 'data': data};
|
||||
} else {
|
||||
return {'status': false, 'data': [], 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
}
|
227
lib/http/video.dart
Normal file
@ -0,0 +1,227 @@
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:pilipala/http/api.dart';
|
||||
import 'package:pilipala/http/init.dart';
|
||||
import 'package:pilipala/models/common/reply_type.dart';
|
||||
import 'package:pilipala/models/model_hot_video_item.dart';
|
||||
import 'package:pilipala/models/model_rec_video_item.dart';
|
||||
import 'package:pilipala/models/user/fav_folder.dart';
|
||||
import 'package:pilipala/models/video_detail_res.dart';
|
||||
|
||||
/// res.data['code'] == 0 请求正常返回结果
|
||||
/// res.data['data'] 为结果
|
||||
/// 返回{'status': bool, 'data': List}
|
||||
/// view层根据 status 判断渲染逻辑
|
||||
class VideoHttp {
|
||||
// 首页推荐视频
|
||||
static Future rcmdVideoList({required int ps, required int freshIdx}) async {
|
||||
try {
|
||||
var res = await Request().get(
|
||||
Api.recommendList,
|
||||
data: {
|
||||
'feed_version': 'V4',
|
||||
'ps': ps,
|
||||
'fresh_idx': freshIdx,
|
||||
},
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
List<RecVideoItemModel> list = [];
|
||||
for (var i in res.data['data']['item']) {
|
||||
list.add(RecVideoItemModel.fromJson(i));
|
||||
}
|
||||
return {'status': true, 'data': list};
|
||||
} else {
|
||||
return {'status': false, 'data': [], 'msg': ''};
|
||||
}
|
||||
} catch (err) {
|
||||
return {'status': false, 'data': [], 'msg': err.toString()};
|
||||
}
|
||||
}
|
||||
|
||||
// 最热视频
|
||||
static Future hotVideoList({required int pn, required int ps}) async {
|
||||
try {
|
||||
var res = await Request().get(
|
||||
Api.hotList,
|
||||
data: {'pn': pn, 'ps': ps},
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
List<HotVideoItemModel> list = [];
|
||||
for (var i in res.data['data']['list']) {
|
||||
list.add(HotVideoItemModel.fromJson(i));
|
||||
}
|
||||
return {'status': true, 'data': list};
|
||||
} else {
|
||||
return {'status': false, 'data': []};
|
||||
}
|
||||
} catch (err) {
|
||||
return {'status': false, 'data': [], 'msg': err};
|
||||
}
|
||||
}
|
||||
|
||||
// 视频信息 标题、简介
|
||||
static Future videoIntro({required String aid}) async {
|
||||
var res = await Request().get(Api.videoIntro, data: {'aid': aid});
|
||||
VideoDetailResponse result = VideoDetailResponse.fromJson(res.data);
|
||||
if (result.code == 0) {
|
||||
return {'status': true, 'data': result.data!};
|
||||
} else {
|
||||
Map errMap = {
|
||||
-400: '请求错误',
|
||||
-403: '权限不足',
|
||||
-404: '无视频',
|
||||
62002: '稿件不可见',
|
||||
62004: '稿件审核中',
|
||||
};
|
||||
return {
|
||||
'status': false,
|
||||
'data': null,
|
||||
'msg': errMap[result.code] ?? '请求异常',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 相关视频
|
||||
static Future relatedVideoList({required String aid}) async {
|
||||
var res = await Request().get(Api.relatedList, data: {'aid': aid});
|
||||
if (res.data['code'] == 0) {
|
||||
List<HotVideoItemModel> list = [];
|
||||
for (var i in res.data['data']) {
|
||||
list.add(HotVideoItemModel.fromJson(i));
|
||||
}
|
||||
return {'status': true, 'data': list};
|
||||
} else {
|
||||
return {'status': false, 'data': []};
|
||||
}
|
||||
}
|
||||
|
||||
// 获取点赞状态
|
||||
static Future hasLikeVideo({required String aid}) async {
|
||||
var res = await Request().get(Api.hasLikeVideo, data: {'aid': aid});
|
||||
if (res.data['code'] == 0) {
|
||||
return {'status': true, 'data': res.data['data']};
|
||||
} else {
|
||||
return {'status': false, 'data': []};
|
||||
}
|
||||
}
|
||||
|
||||
// 获取投币状态
|
||||
static Future hasCoinVideo({required String aid}) async {
|
||||
var res = await Request().get(Api.hasCoinVideo, data: {'aid': aid});
|
||||
if (res.data['code'] == 0) {
|
||||
return {'status': true, 'data': res.data['data']};
|
||||
} else {
|
||||
return {'status': true, 'data': []};
|
||||
}
|
||||
}
|
||||
|
||||
// 获取收藏状态
|
||||
static Future hasFavVideo({required String aid}) async {
|
||||
var res = await Request().get(Api.hasFavVideo, data: {'aid': aid});
|
||||
if (res.data['code'] == 0) {
|
||||
return {'status': true, 'data': res.data['data']};
|
||||
} else {
|
||||
return {'status': false, 'data': []};
|
||||
}
|
||||
}
|
||||
|
||||
// 一键三连
|
||||
static Future oneThree({required String aid}) async {
|
||||
var res = await Request().post(
|
||||
Api.oneThree,
|
||||
queryParameters: {
|
||||
'aid': aid,
|
||||
'csrf': await Request.getCsrf(),
|
||||
},
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
return {'status': true, 'data': res.data['data']};
|
||||
} else {
|
||||
return {'status': false, 'data': [], 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
|
||||
// (取消)点赞
|
||||
static Future likeVideo({required String aid, required bool type}) async {
|
||||
var res = await Request().post(
|
||||
Api.likeVideo,
|
||||
queryParameters: {
|
||||
'aid': aid,
|
||||
'like': type ? 1 : 2,
|
||||
'csrf': await Request.getCsrf(),
|
||||
},
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
return {'status': true, 'data': res.data['data']};
|
||||
} else {
|
||||
return {'status': false, 'data': [], 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
|
||||
// (取消)收藏
|
||||
static Future favVideo(
|
||||
{required String aid, String? addIds, String? delIds}) async {
|
||||
var res = await Request().post(Api.favVideo, queryParameters: {
|
||||
'rid': aid,
|
||||
'type': 2,
|
||||
'add_media_ids': addIds ?? '',
|
||||
'del_media_ids': delIds ?? '',
|
||||
'csrf': await Request.getCsrf(),
|
||||
});
|
||||
if (res.data['code'] == 0) {
|
||||
return {'status': true, 'data': res.data['data']};
|
||||
} else {
|
||||
return {'status': false, 'data': []};
|
||||
}
|
||||
}
|
||||
|
||||
// 查看视频被收藏在哪个文件夹
|
||||
static Future videoInFolder({required int mid, required String rid}) async {
|
||||
var res = await Request()
|
||||
.get(Api.videoInFolder, data: {'up_mid': mid, 'rid': rid});
|
||||
if (res.data['code'] == 0) {
|
||||
FavFolderData data = FavFolderData.fromJson(res.data['data']);
|
||||
return {'status': true, 'data': data};
|
||||
} else {
|
||||
return {'status': false, 'data': []};
|
||||
}
|
||||
}
|
||||
|
||||
// 发表评论 replyAdd
|
||||
|
||||
// type num 评论区类型代码 必要 类型代码见表
|
||||
// oid num 目标评论区id 必要
|
||||
// root num 根评论rpid 非必要 二级评论以上使用
|
||||
// parent num 父评论rpid 非必要 二级评论同根评论id 大于二级评论为要回复的评论id
|
||||
// message str 发送评论内容 必要 最大1000字符
|
||||
// plat num 发送平台标识 非必要 1:web端 2:安卓客户端 3:ios客户端 4:wp客户端
|
||||
static Future replyAdd({
|
||||
required ReplyType type,
|
||||
required int oid,
|
||||
required String message,
|
||||
int? root,
|
||||
int? parent,
|
||||
}) async {
|
||||
if(message == ''){
|
||||
return {'status': false, 'data': [], 'msg': '请输入评论内容'};
|
||||
}
|
||||
print('root:$root');
|
||||
print('parent: $parent');
|
||||
|
||||
var res = await Request()
|
||||
.post(Api.replyAdd, queryParameters: {
|
||||
'type': type.index,
|
||||
'oid': oid,
|
||||
'root': root ?? '',
|
||||
'parent': parent == null || parent == 0 ? '' : parent,
|
||||
'message': message,
|
||||
'csrf': await Request.getCsrf(),
|
||||
});
|
||||
log(res.toString());
|
||||
if (res.data['code'] == 0) {
|
||||
return {'status': true, 'data': res.data['data']};
|
||||
} else {
|
||||
return {'status': false, 'data': []};
|
||||
}
|
||||
}
|
||||
}
|
@ -1,12 +1,15 @@
|
||||
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';
|
||||
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());
|
||||
}
|
||||
@ -25,12 +28,22 @@ class MyApp extends StatelessWidget {
|
||||
theme: ThemeData(
|
||||
colorScheme: lightDynamic ??
|
||||
ColorScheme.fromSeed(
|
||||
seedColor: Colors.green, brightness: Brightness.light),
|
||||
useMaterial3: true),
|
||||
darkTheme: ThemeData(colorScheme: darkDynamic, useMaterial3: true),
|
||||
seedColor: Colors.green,
|
||||
brightness: Brightness.light,
|
||||
),
|
||||
useMaterial3: true,
|
||||
),
|
||||
darkTheme: ThemeData(
|
||||
colorScheme: darkDynamic ??
|
||||
ColorScheme.fromSeed(
|
||||
seedColor: Colors.green,
|
||||
brightness: Brightness.dark,
|
||||
),
|
||||
useMaterial3: true,
|
||||
),
|
||||
getPages: Routes.getPages,
|
||||
home: const MainApp(),
|
||||
// home: const Scaffold(),
|
||||
builder: FlutterSmartDialog.init(),
|
||||
);
|
||||
}),
|
||||
);
|
||||
|
46
lib/models/common/reply_type.dart
Normal file
@ -0,0 +1,46 @@
|
||||
enum ReplyType {
|
||||
unset,
|
||||
// 视频
|
||||
video,
|
||||
// 话题
|
||||
topic,
|
||||
// 活动
|
||||
activity,
|
||||
// 小视频
|
||||
videoS,
|
||||
// 小黑屋封禁信息
|
||||
blockMsg,
|
||||
// 公告信息
|
||||
publicMsg,
|
||||
// 直播活动
|
||||
liveActivity,
|
||||
// 活动稿件
|
||||
activityFile,
|
||||
// 直播公告
|
||||
livePublic,
|
||||
// 相簿
|
||||
album,
|
||||
// 专栏
|
||||
column,
|
||||
// 票务
|
||||
ticket,
|
||||
// 音频
|
||||
audio,
|
||||
|
||||
// 点评
|
||||
comment,
|
||||
// 动态
|
||||
dynamics,
|
||||
// 播单
|
||||
playList,
|
||||
// 音乐播单
|
||||
musicPlayList,
|
||||
// 漫画
|
||||
comics1,
|
||||
// 漫画
|
||||
comics2,
|
||||
// 漫画
|
||||
comics3,
|
||||
// 课程
|
||||
course,
|
||||
}
|
@ -80,7 +80,9 @@ class HotVideoItemModel {
|
||||
pubLocation = json["pub_location"];
|
||||
seasontype = json["seasontype"];
|
||||
isOgv = json["isOgv"];
|
||||
rcmdReason = RcmdReason.fromJson(json['rcmd_reason']);
|
||||
rcmdReason = json['rcmd_reason'] != ''
|
||||
? RcmdReason.fromJson(json['rcmd_reason'])
|
||||
: null;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -13,6 +13,7 @@ class RecVideoItemModel {
|
||||
this.pubdate,
|
||||
this.owner,
|
||||
this.stat,
|
||||
this.isFollowed,
|
||||
this.rcmdReason,
|
||||
});
|
||||
|
||||
@ -27,6 +28,7 @@ class RecVideoItemModel {
|
||||
int? pubdate = -1;
|
||||
Owner? owner;
|
||||
Stat? stat;
|
||||
int? isFollowed;
|
||||
RcmdReason? rcmdReason;
|
||||
|
||||
RecVideoItemModel.fromJson(Map<String, dynamic> json) {
|
||||
@ -41,6 +43,7 @@ class RecVideoItemModel {
|
||||
pubdate = json["pubdate"];
|
||||
owner = Owner.fromJson(json["owner"]);
|
||||
stat = Stat.fromJson(json["stat"]);
|
||||
isFollowed = json["is_followed"] ?? 0;
|
||||
rcmdReason = json["rcmd_reason"] != null
|
||||
? RcmdReason.fromJson(json["rcmd_reason"])
|
||||
: RcmdReason(content: '');
|
||||
|
100
lib/models/user/fav_detail.dart
Normal file
@ -0,0 +1,100 @@
|
||||
import 'package:pilipala/models/model_owner.dart';
|
||||
|
||||
class FavDetailData {
|
||||
FavDetailData({
|
||||
this.info,
|
||||
this.medias,
|
||||
this.hasMore,
|
||||
});
|
||||
|
||||
Map? info;
|
||||
List<FavDetailItemData>? medias;
|
||||
bool? hasMore;
|
||||
|
||||
FavDetailData.fromJson(Map<String, dynamic> json) {
|
||||
info = json['info'];
|
||||
medias = json['medias'] != null
|
||||
? json['medias']
|
||||
.map<FavDetailItemData>((e) => FavDetailItemData.fromJson(e))
|
||||
.toList()
|
||||
: [FavDetailItemData()];
|
||||
hasMore = json['has_more'];
|
||||
}
|
||||
}
|
||||
|
||||
class FavDetailItemData {
|
||||
FavDetailItemData({
|
||||
this.id,
|
||||
this.type,
|
||||
this.title,
|
||||
this.pic,
|
||||
this.intro,
|
||||
this.page,
|
||||
this.duration,
|
||||
this.owner,
|
||||
this.attr,
|
||||
this.cntInfo,
|
||||
this.link,
|
||||
this.ctime,
|
||||
this.pubdate,
|
||||
this.favTime,
|
||||
this.bvId,
|
||||
this.bvid,
|
||||
// this.season,
|
||||
// this.ogv,
|
||||
this.stat,
|
||||
});
|
||||
|
||||
int? id;
|
||||
int? type;
|
||||
String? title;
|
||||
String? pic;
|
||||
String? intro;
|
||||
int? page;
|
||||
int? duration;
|
||||
Owner? owner;
|
||||
int? attr;
|
||||
Map? cntInfo;
|
||||
String? link;
|
||||
int? ctime;
|
||||
int? pubdate;
|
||||
int? favTime;
|
||||
String? bvId;
|
||||
String? bvid;
|
||||
Stat? stat;
|
||||
|
||||
FavDetailItemData.fromJson(Map<String, dynamic> json) {
|
||||
id = json['id'];
|
||||
type = json['type'];
|
||||
title = json['title'];
|
||||
pic = json['cover'];
|
||||
intro = json['intro'];
|
||||
page = json['page'];
|
||||
duration = json['duration'];
|
||||
owner = Owner.fromJson(json['upper']);
|
||||
attr = json['attr'];
|
||||
cntInfo = json['cnt_info'];
|
||||
link = json['link'];
|
||||
ctime = json['ctime'];
|
||||
pubdate = json['pubtime'];
|
||||
favTime = json['fav_time'];
|
||||
bvId = json['bv_id'];
|
||||
bvid = json['bvid'];
|
||||
stat = Stat.fromJson(json['cnt_info']);
|
||||
}
|
||||
}
|
||||
|
||||
class Stat {
|
||||
Stat({
|
||||
this.view,
|
||||
this.danmaku,
|
||||
});
|
||||
|
||||
int? view;
|
||||
int? danmaku;
|
||||
|
||||
Stat.fromJson(Map<String, dynamic> json) {
|
||||
view = json['play'];
|
||||
danmaku = json['danmaku'];
|
||||
}
|
||||
}
|
108
lib/models/user/fav_folder.dart
Normal file
@ -0,0 +1,108 @@
|
||||
class FavFolderData {
|
||||
FavFolderData({
|
||||
this.count,
|
||||
this.list,
|
||||
this.hasMore,
|
||||
});
|
||||
|
||||
int? count;
|
||||
List<FavFolderItemData>? list;
|
||||
bool? hasMore;
|
||||
|
||||
FavFolderData.fromJson(Map<String, dynamic> json) {
|
||||
count = json['count'];
|
||||
list = json['list'] != null
|
||||
? json['list']
|
||||
.map<FavFolderItemData>((e) => FavFolderItemData.fromJson(e))
|
||||
.toList()
|
||||
: [FavFolderItemData()];
|
||||
hasMore = json['has_more'];
|
||||
}
|
||||
}
|
||||
|
||||
class FavFolderItemData {
|
||||
FavFolderItemData({
|
||||
this.id,
|
||||
this.fid,
|
||||
this.mid,
|
||||
this.attr,
|
||||
this.title,
|
||||
this.cover,
|
||||
this.upper,
|
||||
this.coverType,
|
||||
this.intro,
|
||||
this.ctime,
|
||||
this.mtime,
|
||||
this.state,
|
||||
this.favState,
|
||||
this.mediaCount,
|
||||
this.viewCount,
|
||||
this.vt,
|
||||
this.playSwitch,
|
||||
this.type,
|
||||
this.link,
|
||||
this.bvid,
|
||||
});
|
||||
|
||||
int? id;
|
||||
int? fid;
|
||||
int? mid;
|
||||
int? attr;
|
||||
String? title;
|
||||
String? cover;
|
||||
Upper? upper;
|
||||
int? coverType;
|
||||
String? intro;
|
||||
int? ctime;
|
||||
int? mtime;
|
||||
int? state;
|
||||
int? favState;
|
||||
int? mediaCount;
|
||||
int? viewCount;
|
||||
int? vt;
|
||||
int? playSwitch;
|
||||
int? type;
|
||||
String? link;
|
||||
String? bvid;
|
||||
|
||||
FavFolderItemData.fromJson(Map<String, dynamic> json) {
|
||||
id = json['id'];
|
||||
fid = json['fid'];
|
||||
mid = json['mid'];
|
||||
attr = json['attr'];
|
||||
title = json['title'];
|
||||
cover = json['cover'];
|
||||
upper = json['upper'] != null ? Upper.fromJson(json['upper']) : Upper();
|
||||
coverType = json['cover_type'];
|
||||
intro = json['intro'];
|
||||
ctime = json['ctime'];
|
||||
mtime = json['mtime'];
|
||||
state = json['state'];
|
||||
favState = json['fav_state'];
|
||||
mediaCount = json['media_count'];
|
||||
viewCount = json['view_count'];
|
||||
vt = json['vt'];
|
||||
playSwitch = json['play_switch'];
|
||||
type = json['type'];
|
||||
link = json['link'];
|
||||
bvid = json['bvid'];
|
||||
}
|
||||
}
|
||||
|
||||
class Upper {
|
||||
Upper({
|
||||
this.mid,
|
||||
this.name,
|
||||
this.face,
|
||||
});
|
||||
|
||||
int? mid;
|
||||
String? name;
|
||||
String? face;
|
||||
|
||||
Upper.fromJson(Map<String, dynamic> json) {
|
||||
mid = json['mid'];
|
||||
name = json['name'];
|
||||
face = json['face'];
|
||||
}
|
||||
}
|
103
lib/models/user/info.dart
Normal file
@ -0,0 +1,103 @@
|
||||
class UserInfoData {
|
||||
UserInfoData({
|
||||
this.isLogin,
|
||||
this.emailVerified,
|
||||
this.face,
|
||||
this.levelInfo,
|
||||
this.mid,
|
||||
this.mobileVerified,
|
||||
this.money,
|
||||
this.moral,
|
||||
this.official,
|
||||
this.officialVerify,
|
||||
this.pendant,
|
||||
this.scores,
|
||||
this.uname,
|
||||
this.vipDueDate,
|
||||
this.vipStatus,
|
||||
this.vipType,
|
||||
this.vipPayType,
|
||||
this.vipThemeType,
|
||||
this.vipLabel,
|
||||
this.vipAvatarSub,
|
||||
this.vipNicknameColor,
|
||||
this.wallet,
|
||||
this.hasShop,
|
||||
this.shopUrl,
|
||||
});
|
||||
|
||||
bool? isLogin;
|
||||
int? emailVerified;
|
||||
String? face;
|
||||
LevelInfo? levelInfo;
|
||||
int? mid;
|
||||
int? mobileVerified;
|
||||
int? money;
|
||||
int? moral;
|
||||
Map? official;
|
||||
Map? officialVerify;
|
||||
Map? pendant;
|
||||
int? scores;
|
||||
String? uname;
|
||||
int? vipDueDate;
|
||||
int? vipStatus;
|
||||
int? vipType;
|
||||
int? vipPayType;
|
||||
int? vipThemeType;
|
||||
Map? vipLabel;
|
||||
int? vipAvatarSub;
|
||||
String? vipNicknameColor;
|
||||
Map? wallet;
|
||||
bool? hasShop;
|
||||
String? shopUrl;
|
||||
|
||||
UserInfoData.fromJson(Map<String, dynamic> json) {
|
||||
isLogin = json['isLogin'] ?? false;
|
||||
emailVerified = json['email_verified'];
|
||||
face = json['face'];
|
||||
levelInfo = json['level_info'] != null
|
||||
? LevelInfo.fromJson(json['level_info'])
|
||||
: LevelInfo();
|
||||
mid = json['mid'];
|
||||
mobileVerified = json['mobile_verified'];
|
||||
money = json['money'];
|
||||
moral = json['moral'];
|
||||
official = json['official'];
|
||||
officialVerify = json['officialVerify'];
|
||||
pendant = json['pendant'];
|
||||
scores = json['scores'];
|
||||
uname = json['uname'];
|
||||
vipDueDate = json['vipDueDate'];
|
||||
vipStatus = json['vipStatus'];
|
||||
vipType = json['vipType'];
|
||||
vipPayType = json['vip_pay_type'];
|
||||
vipThemeType = json['vip_theme_type'];
|
||||
vipLabel = json['vip_label'];
|
||||
vipAvatarSub = json['vip_avatar_subscript'];
|
||||
vipNicknameColor = json['vip_nickname_color'];
|
||||
wallet = json['wallet'];
|
||||
hasShop = json['has_shop'];
|
||||
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
@ -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'];
|
||||
}
|
||||
}
|
17
lib/models/video/reply/config.dart
Normal file
@ -0,0 +1,17 @@
|
||||
class ReplyConfig {
|
||||
ReplyConfig({
|
||||
this.showtopic,
|
||||
this.showUpFlag,
|
||||
this.readOnly,
|
||||
});
|
||||
|
||||
int? showtopic;
|
||||
bool? showUpFlag;
|
||||
bool? readOnly;
|
||||
|
||||
ReplyConfig.fromJson(Map<String, dynamic> json) {
|
||||
showtopic = json['showtopic'];
|
||||
showUpFlag = json['show_up_flag'];
|
||||
readOnly = json['read_only'];
|
||||
}
|
||||
}
|
29
lib/models/video/reply/content.dart
Normal file
@ -0,0 +1,29 @@
|
||||
class ReplyContent {
|
||||
ReplyContent({
|
||||
this.message,
|
||||
this.atNameToMid, // @的用户的mid null
|
||||
this.memebers, // 被@的用户List 如果有的话 []
|
||||
this.emote, // 表情包 如果有的话 null
|
||||
this.jumpUrl, // {}
|
||||
this.pictures, // {}
|
||||
this.vote,
|
||||
});
|
||||
|
||||
String? message;
|
||||
Map? atNameToMid;
|
||||
List? memebers;
|
||||
Map? emote;
|
||||
Map? jumpUrl;
|
||||
List? pictures;
|
||||
Map? vote;
|
||||
|
||||
ReplyContent.fromJson(Map<String, dynamic> json) {
|
||||
message = json['message'];
|
||||
atNameToMid = json['at_name_to_mid'] ?? {};
|
||||
memebers = json['memebers'] ?? [];
|
||||
emote = json['emote'] ?? {};
|
||||
jumpUrl = json['jump_url'] ?? {};
|
||||
pictures = json['pictures'] ?? [];
|
||||
vote = json['vote'] ?? {};
|
||||
}
|
||||
}
|
40
lib/models/video/reply/data.dart
Normal file
@ -0,0 +1,40 @@
|
||||
import 'package:pilipala/models/video/reply/item.dart';
|
||||
|
||||
import 'config.dart';
|
||||
import 'page.dart';
|
||||
import 'upper.dart';
|
||||
|
||||
class ReplyData {
|
||||
ReplyData({
|
||||
this.page,
|
||||
this.config,
|
||||
this.replies,
|
||||
this.topReplies,
|
||||
this.upper,
|
||||
});
|
||||
|
||||
ReplyPage? page;
|
||||
ReplyConfig? config;
|
||||
late List<ReplyItemModel>? replies;
|
||||
late List<ReplyItemModel>? topReplies;
|
||||
ReplyUpper? upper;
|
||||
|
||||
ReplyData.fromJson(Map<String, dynamic> json) {
|
||||
page = ReplyPage.fromJson(json['page']);
|
||||
config = ReplyConfig.fromJson(json['config']);
|
||||
replies = json['replies'] != null
|
||||
? json['replies']
|
||||
.map<ReplyItemModel>(
|
||||
(item) => ReplyItemModel.fromJson(item, json['upper']['mid']))
|
||||
.toList()
|
||||
: [];
|
||||
topReplies = json['top_replies'] != null
|
||||
? json['top_replies']
|
||||
.map<ReplyItemModel>((item) => ReplyItemModel.fromJson(
|
||||
item, json['upper']['mid'],
|
||||
isTopStatus: true))
|
||||
.toList()
|
||||
: [];
|
||||
upper = ReplyUpper.fromJson(json['upper']);
|
||||
}
|
||||
}
|
159
lib/models/video/reply/item.dart
Normal file
@ -0,0 +1,159 @@
|
||||
import 'content.dart';
|
||||
import 'member.dart';
|
||||
|
||||
class ReplyItemModel {
|
||||
ReplyItemModel({
|
||||
this.rpid,
|
||||
this.oid,
|
||||
this.type,
|
||||
this.mid,
|
||||
this.root,
|
||||
this.parent,
|
||||
this.dialog,
|
||||
this.count,
|
||||
this.floor,
|
||||
this.state,
|
||||
this.fansgrade,
|
||||
this.attr,
|
||||
this.ctime,
|
||||
this.rpidStr,
|
||||
this.rootStr,
|
||||
this.parentStr,
|
||||
this.like,
|
||||
this.action,
|
||||
this.member,
|
||||
this.content,
|
||||
this.replies,
|
||||
this.assist,
|
||||
this.upAction,
|
||||
this.invisible,
|
||||
this.replyControl,
|
||||
this.isUp,
|
||||
this.isTop,
|
||||
this.cardLabel,
|
||||
});
|
||||
|
||||
int? rpid;
|
||||
int? oid;
|
||||
int? type;
|
||||
int? mid;
|
||||
int? root;
|
||||
int? parent;
|
||||
int? dialog;
|
||||
int? count;
|
||||
int? floor;
|
||||
int? state;
|
||||
int? fansgrade;
|
||||
int? attr;
|
||||
int? ctime;
|
||||
String? rpidStr;
|
||||
String? rootStr;
|
||||
String? parentStr;
|
||||
int? like;
|
||||
int? action;
|
||||
ReplyMember? member;
|
||||
ReplyContent? content;
|
||||
List? replies;
|
||||
int? assist;
|
||||
UpAction? upAction;
|
||||
bool? invisible;
|
||||
ReplyControl? replyControl;
|
||||
bool? isUp;
|
||||
bool? isTop = false;
|
||||
List? cardLabel;
|
||||
|
||||
ReplyItemModel.fromJson(Map<String, dynamic> json, upperMid,
|
||||
{isTopStatus = false}) {
|
||||
rpid = json['rpid'];
|
||||
oid = json['oid'];
|
||||
type = json['type'];
|
||||
mid = json['mid'];
|
||||
root = json['root'];
|
||||
parent = json['parent'];
|
||||
dialog = json['dialog'];
|
||||
count = json['count'];
|
||||
floor = json['floor'];
|
||||
state = json['state'];
|
||||
fansgrade = json['fansgrade'];
|
||||
attr = json['attr'];
|
||||
ctime = json['ctime'];
|
||||
rpidStr = json['rpid_str'];
|
||||
rootStr = json['root_str'];
|
||||
parentStr = json['parent_str'];
|
||||
like = json['like'];
|
||||
action = json['action'];
|
||||
member = ReplyMember.fromJson(json['member']);
|
||||
content = ReplyContent.fromJson(json['content']);
|
||||
replies = json['replies'] != null
|
||||
? json['replies']
|
||||
.map((item) => ReplyItemModel.fromJson(item, upperMid))
|
||||
.toList()
|
||||
: [];
|
||||
assist = json['assist'];
|
||||
upAction = UpAction.fromJson(json['up_action']);
|
||||
invisible = json['invisible'];
|
||||
replyControl = json['reply_control'] == null
|
||||
? null
|
||||
: ReplyControl.fromJson(json['reply_control']);
|
||||
isUp = upperMid.toString() == json['member']['mid'];
|
||||
isTop = isTopStatus;
|
||||
cardLabel = json['card_label'] != null
|
||||
? json['card_label'].map((e) => e['text_content']).toList()
|
||||
: [];
|
||||
}
|
||||
}
|
||||
|
||||
class UpAction {
|
||||
UpAction({this.like, this.reply});
|
||||
|
||||
bool? like;
|
||||
bool? reply;
|
||||
|
||||
UpAction.fromJson(Map<String, dynamic> json) {
|
||||
like = json['like'];
|
||||
reply = json['reply'];
|
||||
}
|
||||
}
|
||||
|
||||
class ReplyControl {
|
||||
ReplyControl({
|
||||
this.upReply,
|
||||
this.isUpTop,
|
||||
this.upLike,
|
||||
this.isShow,
|
||||
this.entryText,
|
||||
this.titleText,
|
||||
this.time,
|
||||
this.location,
|
||||
});
|
||||
|
||||
bool? upReply;
|
||||
bool? isUpTop;
|
||||
bool? upLike;
|
||||
bool? isShow;
|
||||
String? entryText;
|
||||
String? titleText;
|
||||
String? time;
|
||||
String? location;
|
||||
|
||||
ReplyControl.fromJson(Map<String, dynamic> json) {
|
||||
upReply = json['up_reply'] ?? false;
|
||||
isUpTop = json['is_up_top'] ?? false;
|
||||
upLike = json['up_like'] ?? false;
|
||||
if (json['sub_reply_entry_text'] != null) {
|
||||
final RegExp regex = RegExp(r"\d+");
|
||||
final RegExpMatch match = regex.firstMatch(
|
||||
json['sub_reply_entry_text'] == null
|
||||
? ''
|
||||
: json['sub_reply_entry_text']!)!;
|
||||
isShow = int.parse(match.group(0)!) >= 3;
|
||||
} else {
|
||||
isShow = false;
|
||||
}
|
||||
|
||||
entryText = json['sub_reply_entry_text'];
|
||||
titleText = json['sub_reply_title_text'];
|
||||
time = json['time_desc'];
|
||||
location = json['location'] != null ? json['location'].split(':')[1] : '';
|
||||
}
|
||||
}
|
71
lib/models/video/reply/member.dart
Normal file
@ -0,0 +1,71 @@
|
||||
import 'package:get/get.dart';
|
||||
|
||||
class ReplyMember {
|
||||
ReplyMember({
|
||||
this.mid,
|
||||
this.uname,
|
||||
this.sign,
|
||||
this.avatar,
|
||||
this.level,
|
||||
this.pendant,
|
||||
this.officialVerify,
|
||||
this.vip,
|
||||
this.fansDetail,
|
||||
});
|
||||
|
||||
String? mid;
|
||||
String? uname;
|
||||
String? sign;
|
||||
String? avatar;
|
||||
int? level;
|
||||
Pendant? pendant;
|
||||
Map? officialVerify;
|
||||
Map? vip;
|
||||
Map? fansDetail;
|
||||
UserSailing? userSailing;
|
||||
|
||||
ReplyMember.fromJson(Map<String, dynamic> json) {
|
||||
mid = json['mid'];
|
||||
uname = json['uname'];
|
||||
sign = json['sign'];
|
||||
avatar = json['avatar'];
|
||||
level = json['level_info']['current_level'];
|
||||
pendant = Pendant.fromJson(json['pendant']);
|
||||
officialVerify = json['officia_verify'];
|
||||
vip = json['vip'];
|
||||
fansDetail = json['fans_detail'];
|
||||
userSailing = json['user_sailing'] != null
|
||||
? UserSailing.fromJson(json['user_sailing'])
|
||||
: UserSailing();
|
||||
}
|
||||
}
|
||||
|
||||
class Pendant {
|
||||
Pendant({
|
||||
this.pid,
|
||||
this.name,
|
||||
this.image,
|
||||
});
|
||||
|
||||
int? pid;
|
||||
String? name;
|
||||
String? image;
|
||||
|
||||
Pendant.fromJson(Map<String, dynamic> json) {
|
||||
pid = json['pid'];
|
||||
name = json['name'];
|
||||
image = json['image'];
|
||||
}
|
||||
}
|
||||
|
||||
class UserSailing {
|
||||
UserSailing({this.pendant, this.cardbg});
|
||||
|
||||
Map? pendant;
|
||||
Map? cardbg;
|
||||
|
||||
UserSailing.fromJson(Map<String, dynamic> json) {
|
||||
pendant = json['pendant'];
|
||||
cardbg = json['cardbg'];
|
||||
}
|
||||
}
|
20
lib/models/video/reply/page.dart
Normal file
@ -0,0 +1,20 @@
|
||||
class ReplyPage {
|
||||
ReplyPage({
|
||||
this.num,
|
||||
this.size,
|
||||
this.count,
|
||||
this.acount,
|
||||
});
|
||||
|
||||
int? num;
|
||||
int? size;
|
||||
int? count;
|
||||
int? acount;
|
||||
|
||||
ReplyPage.fromJson(Map<String, dynamic> json) {
|
||||
num = json['num'];
|
||||
size = json['size'];
|
||||
count = json['count'];
|
||||
acount = json['acount'];
|
||||
}
|
||||
}
|
1
lib/models/video/reply/top_replies.dart
Normal file
@ -0,0 +1 @@
|
||||
class ReplyTop {}
|
18
lib/models/video/reply/upper.dart
Normal file
@ -0,0 +1,18 @@
|
||||
import 'item.dart';
|
||||
|
||||
class ReplyUpper {
|
||||
ReplyUpper({
|
||||
this.mid,
|
||||
this.top,
|
||||
});
|
||||
|
||||
int? mid;
|
||||
ReplyItemModel? top;
|
||||
|
||||
ReplyUpper.fromJson(Map<String, dynamic> json) {
|
||||
mid = json['mid'];
|
||||
top = json['top'] != null
|
||||
? ReplyItemModel.fromJson(json['top'], json['mid'])
|
||||
: null;
|
||||
}
|
||||
}
|
524
lib/models/video_detail_res.dart
Normal file
@ -0,0 +1,524 @@
|
||||
import 'dart:convert';
|
||||
|
||||
class VideoDetailResponse {
|
||||
int? code;
|
||||
String? message;
|
||||
int? ttl;
|
||||
VideoDetailData? data;
|
||||
|
||||
VideoDetailResponse({
|
||||
this.code,
|
||||
this.message,
|
||||
this.ttl,
|
||||
this.data,
|
||||
});
|
||||
|
||||
VideoDetailResponse.fromJson(Map<String, dynamic> json) {
|
||||
code = json["code"];
|
||||
message = json["message"];
|
||||
ttl = json["ttl"];
|
||||
data = json["data"] == null ? null : VideoDetailData.fromJson(json["data"]);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = <String, dynamic>{};
|
||||
data["code"] = code;
|
||||
data["message"] = message;
|
||||
data["ttl"] = ttl;
|
||||
data["data"] = data;
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
class VideoDetailData {
|
||||
String? bvid;
|
||||
int? aid;
|
||||
int? videos;
|
||||
int? tid;
|
||||
String? tname;
|
||||
int? copyright;
|
||||
String? pic;
|
||||
String? title;
|
||||
int? pubdate;
|
||||
int? ctime;
|
||||
String? desc;
|
||||
List<DescV2>? descV2;
|
||||
int? state;
|
||||
int? duration;
|
||||
Map<String, int>? rights;
|
||||
Owner? owner;
|
||||
Stat? stat;
|
||||
String? videoDynamic;
|
||||
int? cid;
|
||||
Dimension? dimension;
|
||||
dynamic premiere;
|
||||
int? teenageMode;
|
||||
bool? isChargeableSeason;
|
||||
bool? isStory;
|
||||
bool? noCache;
|
||||
List<Page>? pages;
|
||||
Subtitle? subtitle;
|
||||
// Label? label;
|
||||
bool? isSeasonDisplay;
|
||||
UserGarb? userGarb;
|
||||
HonorReply? honorReply;
|
||||
String? likeIcon;
|
||||
bool? needJumpBv;
|
||||
|
||||
VideoDetailData({
|
||||
this.bvid,
|
||||
this.aid,
|
||||
this.videos,
|
||||
this.tid,
|
||||
this.tname,
|
||||
this.copyright,
|
||||
this.pic,
|
||||
this.title,
|
||||
this.pubdate,
|
||||
this.ctime,
|
||||
this.desc,
|
||||
this.descV2,
|
||||
this.state,
|
||||
this.duration,
|
||||
this.rights,
|
||||
this.owner,
|
||||
this.stat,
|
||||
this.videoDynamic,
|
||||
this.cid,
|
||||
this.dimension,
|
||||
this.premiere,
|
||||
this.teenageMode,
|
||||
this.isChargeableSeason,
|
||||
this.isStory,
|
||||
this.noCache,
|
||||
this.pages,
|
||||
this.subtitle,
|
||||
this.isSeasonDisplay,
|
||||
this.userGarb,
|
||||
this.honorReply,
|
||||
this.likeIcon,
|
||||
this.needJumpBv,
|
||||
});
|
||||
|
||||
VideoDetailData.fromJson(Map<String, dynamic> json) {
|
||||
bvid = json["bvid"];
|
||||
aid = json["aid"];
|
||||
videos = json["videos"];
|
||||
tid = json["tid"];
|
||||
tname = json["tname"];
|
||||
copyright = json["copyright"];
|
||||
pic = json["pic"];
|
||||
title = json["title"];
|
||||
pubdate = json["pubdate"];
|
||||
ctime = json["ctime"];
|
||||
desc = json["desc"];
|
||||
descV2 = json["desc_v2"] == null
|
||||
? []
|
||||
: List<DescV2>.from(json["desc_v2"]!.map((e) => DescV2.fromJson(e)));
|
||||
state = json["state"];
|
||||
duration = json["duration"];
|
||||
rights =
|
||||
Map.from(json["rights"]!).map((k, v) => MapEntry<String, int>(k, v));
|
||||
owner = json["owner"] == null ? null : Owner.fromJson(json["owner"]);
|
||||
stat = json["stat"] == null ? null : Stat.fromJson(json["stat"]);
|
||||
videoDynamic = json["dynamic"];
|
||||
cid = json["cid"];
|
||||
dimension = json["dimension"] == null
|
||||
? null
|
||||
: Dimension.fromJson(json["dimension"]);
|
||||
premiere = json["premiere"];
|
||||
teenageMode = json["teenage_mode"];
|
||||
isChargeableSeason = json["is_chargeable_season"];
|
||||
isStory = json["is_story"];
|
||||
noCache = json["no_cache"];
|
||||
pages = json["pages"] == null
|
||||
? []
|
||||
: List<Page>.from(json["pages"]!.map((e) => Page.fromJson(e)));
|
||||
subtitle =
|
||||
json["subtitle"] == null ? null : Subtitle.fromJson(json["subtitle"]);
|
||||
isSeasonDisplay = json["is_season_display"];
|
||||
userGarb =
|
||||
json["user_garb"] == null ? null : UserGarb.fromJson(json["user_garb"]);
|
||||
honorReply = json["honor_reply"] == null
|
||||
? null
|
||||
: HonorReply.fromJson(json["honor_reply"]);
|
||||
likeIcon = json["like_icon"];
|
||||
needJumpBv = json["need_jump_bv"];
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
"bvid": bvid,
|
||||
"aid": aid,
|
||||
"videos": videos,
|
||||
"tid": tid,
|
||||
"tname": tname,
|
||||
"copyright": copyright,
|
||||
"pic": pic,
|
||||
"title": title,
|
||||
"pubdate": pubdate,
|
||||
"ctime": ctime,
|
||||
"desc": desc,
|
||||
"desc_v2": descV2 == null
|
||||
? []
|
||||
: List<dynamic>.from(descV2!.map((e) => e.toJson())),
|
||||
"state": state,
|
||||
"duration": duration,
|
||||
"rights":
|
||||
Map.from(rights!).map((k, v) => MapEntry<String, dynamic>(k, v)),
|
||||
"owner": owner?.toJson(),
|
||||
"stat": stat?.toJson(),
|
||||
"dynamic": videoDynamic,
|
||||
"cid": cid,
|
||||
"dimension": dimension?.toJson(),
|
||||
"premiere": premiere,
|
||||
"teenage_mode": teenageMode,
|
||||
"is_chargeable_season": isChargeableSeason,
|
||||
"is_story": isStory,
|
||||
"no_cache": noCache,
|
||||
"pages": pages == null
|
||||
? []
|
||||
: List<dynamic>.from(pages!.map((e) => e.toJson())),
|
||||
"subtitle": subtitle?.toJson(),
|
||||
"is_season_display": isSeasonDisplay,
|
||||
"user_garb": userGarb?.toJson(),
|
||||
"honor_reply": honorReply?.toJson(),
|
||||
"like_icon": likeIcon,
|
||||
"need_jump_bv": needJumpBv,
|
||||
};
|
||||
}
|
||||
|
||||
class DescV2 {
|
||||
String? rawText;
|
||||
int? type;
|
||||
int? bizId;
|
||||
|
||||
DescV2({
|
||||
this.rawText,
|
||||
this.type,
|
||||
this.bizId,
|
||||
});
|
||||
|
||||
fromRawJson(String str) {
|
||||
return DescV2.fromJson(json.decode(str));
|
||||
}
|
||||
|
||||
String toRawJson() => json.encode(toJson());
|
||||
|
||||
DescV2.fromJson(Map<String, dynamic> json) {
|
||||
rawText = json["raw_text"];
|
||||
type = json["type"];
|
||||
bizId = json["biz_id"];
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = <String, dynamic>{};
|
||||
|
||||
data["raw_text"] = rawText;
|
||||
data["type"] = type;
|
||||
data["biz_id"] = bizId;
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
class Dimension {
|
||||
int? width;
|
||||
int? height;
|
||||
int? rotate;
|
||||
|
||||
Dimension({
|
||||
this.width,
|
||||
this.height,
|
||||
this.rotate,
|
||||
});
|
||||
|
||||
fromRawJson(String str) => Dimension.fromJson(json.decode(str));
|
||||
|
||||
String toRawJson() => json.encode(toJson());
|
||||
|
||||
Dimension.fromJson(Map<String, dynamic> json) {
|
||||
width = json["width"];
|
||||
height = json["height"];
|
||||
rotate = json["rotate"];
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = <String, dynamic>{};
|
||||
|
||||
data["width"] = width;
|
||||
data["height"] = height;
|
||||
data["rotate"] = rotate;
|
||||
data["data"] = data;
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
class HonorReply {
|
||||
List<Honor>? honor;
|
||||
|
||||
HonorReply({
|
||||
this.honor,
|
||||
});
|
||||
|
||||
fromRawJson(String str) => HonorReply.fromJson(json.decode(str));
|
||||
|
||||
String toRawJson() => json.encode(toJson());
|
||||
|
||||
HonorReply.fromJson(Map<String, dynamic> json) {
|
||||
honor = json["honor"] == null
|
||||
? []
|
||||
: List<Honor>.from(json["honor"]!.map((x) => Honor.fromJson(x)));
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = <String, dynamic>{};
|
||||
|
||||
data["honor"] =
|
||||
honor == null ? [] : List<dynamic>.from(honor!.map((x) => x.toJson()));
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
class Honor {
|
||||
int? aid;
|
||||
int? type;
|
||||
String? desc;
|
||||
int? weeklyRecommendNum;
|
||||
|
||||
Honor({
|
||||
this.aid,
|
||||
this.type,
|
||||
this.desc,
|
||||
this.weeklyRecommendNum,
|
||||
});
|
||||
|
||||
fromRawJson(String str) => Honor.fromJson(json.decode(str));
|
||||
|
||||
String toRawJson() => json.encode(toJson());
|
||||
|
||||
Honor.fromJson(Map<String, dynamic> json) {
|
||||
aid = json["aid"];
|
||||
type = json["type"];
|
||||
desc = json["desc"];
|
||||
weeklyRecommendNum = json["weekly_recommend_num"];
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = <String, dynamic>{};
|
||||
|
||||
data["aid"] = aid;
|
||||
data["type"] = type;
|
||||
data["desc"] = desc;
|
||||
data["weekly_recommend_num"] = weeklyRecommendNum;
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
class Owner {
|
||||
int? mid;
|
||||
String? name;
|
||||
String? face;
|
||||
|
||||
Owner({
|
||||
this.mid,
|
||||
this.name,
|
||||
this.face,
|
||||
});
|
||||
|
||||
fromRawJson(String str) => Owner.fromJson(json.decode(str));
|
||||
|
||||
String toRawJson() => json.encode(toJson());
|
||||
|
||||
Owner.fromJson(Map<String, dynamic> json) {
|
||||
mid = json["mid"];
|
||||
name = json["name"];
|
||||
face = json["face"];
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = <String, dynamic>{};
|
||||
data["mid"] = mid;
|
||||
data["name"] = name;
|
||||
data["face"] = face;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
class Page {
|
||||
int? cid;
|
||||
int? page;
|
||||
String? from;
|
||||
String? pagePart;
|
||||
int? duration;
|
||||
String? vid;
|
||||
String? weblink;
|
||||
Dimension? dimension;
|
||||
String? firstFrame;
|
||||
|
||||
Page({
|
||||
this.cid,
|
||||
this.page,
|
||||
this.from,
|
||||
this.pagePart,
|
||||
this.duration,
|
||||
this.vid,
|
||||
this.weblink,
|
||||
this.dimension,
|
||||
this.firstFrame,
|
||||
});
|
||||
|
||||
fromRawJson(String str) => Page.fromJson(json.decode(str));
|
||||
|
||||
String toRawJson() => json.encode(toJson());
|
||||
|
||||
Page.fromJson(Map<String, dynamic> json) {
|
||||
cid = json["cid"];
|
||||
page = json["page"];
|
||||
from = json["from"];
|
||||
pagePart = json["part"];
|
||||
duration = json["duration"];
|
||||
vid = json["vid"];
|
||||
weblink = json["weblink"];
|
||||
dimension = json["dimension"] == null
|
||||
? null
|
||||
: Dimension.fromJson(json["dimension"]);
|
||||
firstFrame = json["first_frame"];
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = <String, dynamic>{};
|
||||
data["cid"] = cid;
|
||||
data["page"] = page;
|
||||
data["from"] = from;
|
||||
data["part"] = pagePart;
|
||||
data["duration"] = duration;
|
||||
data["vid"] = vid;
|
||||
data["weblink"] = weblink;
|
||||
data["dimension"] = dimension?.toJson();
|
||||
data["first_frame"] = firstFrame;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
class Stat {
|
||||
int? aid;
|
||||
int? view;
|
||||
int? danmaku;
|
||||
int? reply;
|
||||
int? favorite;
|
||||
int? coin;
|
||||
int? share;
|
||||
int? nowRank;
|
||||
int? hisRank;
|
||||
int? like;
|
||||
int? dislike;
|
||||
String? evaluation;
|
||||
String? argueMsg;
|
||||
|
||||
Stat({
|
||||
this.aid,
|
||||
this.view,
|
||||
this.danmaku,
|
||||
this.reply,
|
||||
this.favorite,
|
||||
this.coin,
|
||||
this.share,
|
||||
this.nowRank,
|
||||
this.hisRank,
|
||||
this.like,
|
||||
this.dislike,
|
||||
this.evaluation,
|
||||
this.argueMsg,
|
||||
});
|
||||
|
||||
fromRawJson(String str) => Stat.fromJson(json.decode(str));
|
||||
|
||||
String toRawJson() => json.encode(toJson());
|
||||
|
||||
Stat.fromJson(Map<String, dynamic> json) {
|
||||
aid = json["aid"];
|
||||
view = json["view"];
|
||||
danmaku = json["danmaku"];
|
||||
reply = json["reply"];
|
||||
favorite = json["favorite"];
|
||||
coin = json["coin"];
|
||||
share = json["share"];
|
||||
nowRank = json["now_rank"];
|
||||
hisRank = json["his_rank"];
|
||||
like = json["like"];
|
||||
dislike = json["dislike"];
|
||||
evaluation = json["evaluation"];
|
||||
argueMsg = json["argue_msg"];
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = <String, dynamic>{};
|
||||
|
||||
data["aid"] = aid;
|
||||
data["view"] = view;
|
||||
data["danmaku"] = danmaku;
|
||||
data["reply"] = reply;
|
||||
data["favorite"] = favorite;
|
||||
data["coin"] = coin;
|
||||
data["share"] = share;
|
||||
data["now_rank"] = nowRank;
|
||||
data["his_rank"] = hisRank;
|
||||
data["like"] = like;
|
||||
data["dislike"] = dislike;
|
||||
data["evaluation"] = evaluation;
|
||||
data["argue_msg"] = argueMsg;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
class Subtitle {
|
||||
bool? allowSubmit;
|
||||
List<dynamic>? list;
|
||||
|
||||
Subtitle({
|
||||
this.allowSubmit,
|
||||
this.list,
|
||||
});
|
||||
|
||||
fromRawJson(String str) => Subtitle.fromJson(json.decode(str));
|
||||
|
||||
String toRawJson() => json.encode(toJson());
|
||||
|
||||
Subtitle.fromJson(Map<String, dynamic> json) {
|
||||
allowSubmit = json["allow_submit"];
|
||||
list = json["list"] == null
|
||||
? []
|
||||
: List<dynamic>.from(json["list"]!.map((x) => x));
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = <String, dynamic>{};
|
||||
|
||||
data["allow_submit"] = allowSubmit;
|
||||
data["list"] = list == null ? [] : List<dynamic>.from(list!.map((x) => x));
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
class UserGarb {
|
||||
String? urlImageAniCut;
|
||||
|
||||
UserGarb({
|
||||
this.urlImageAniCut,
|
||||
});
|
||||
|
||||
fromRawJson(String str) => UserGarb.fromJson(json.decode(str));
|
||||
|
||||
String toRawJson() => json.encode(toJson());
|
||||
|
||||
UserGarb.fromJson(Map<String, dynamic> json) {
|
||||
urlImageAniCut = json["url_image_ani_cut"];
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => {"url_image_ani_cut": urlImageAniCut};
|
||||
}
|
||||
|
||||
class Label {}
|
18
lib/pages/fav/controller.dart
Normal file
@ -0,0 +1,18 @@
|
||||
import 'package:get/get.dart';
|
||||
import 'package:pilipala/http/user.dart';
|
||||
import 'package:pilipala/models/user/fav_folder.dart';
|
||||
import 'package:pilipala/utils/storage.dart';
|
||||
|
||||
class FavController extends GetxController {
|
||||
Rx<FavFolderData> favFolderData = FavFolderData().obs;
|
||||
|
||||
Future<dynamic> queryFavFolder() async {
|
||||
var res = await await UserHttp.userfavFolder(
|
||||
pn: 1,
|
||||
ps: 10,
|
||||
mid: GStrorage.user.get(UserBoxKey.userMid),
|
||||
);
|
||||
favFolderData.value = res['data'];
|
||||
return res;
|
||||
}
|
||||
}
|
4
lib/pages/fav/index.dart
Normal file
@ -0,0 +1,4 @@
|
||||
library fav;
|
||||
|
||||
export './controller.dart';
|
||||
export './view.dart';
|
75
lib/pages/fav/view.dart
Normal file
@ -0,0 +1,75 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:pilipala/common/widgets/http_error.dart';
|
||||
import 'package:pilipala/pages/fav/index.dart';
|
||||
|
||||
class FavPage extends StatefulWidget {
|
||||
const FavPage({super.key});
|
||||
|
||||
@override
|
||||
State<FavPage> createState() => _FavPageState();
|
||||
}
|
||||
|
||||
class _FavPageState extends State<FavPage> {
|
||||
final FavController _favController = Get.put(FavController());
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
centerTitle: false,
|
||||
title: const Text('我的收藏'),
|
||||
),
|
||||
body: FutureBuilder(
|
||||
future: _favController.queryFavFolder(),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.done) {
|
||||
Map data = snapshot.data as Map;
|
||||
if (data['status']) {
|
||||
return Obx(
|
||||
() => ListView.builder(
|
||||
itemCount: _favController.favFolderData.value.list!.length,
|
||||
itemBuilder: (context, index) {
|
||||
return ListTile(
|
||||
onTap: () => Get.toNamed(
|
||||
'/favDetail',
|
||||
arguments:
|
||||
_favController.favFolderData.value.list![index],
|
||||
parameters: {
|
||||
'mediaId': _favController
|
||||
.favFolderData.value.list![index].id
|
||||
.toString(),
|
||||
},
|
||||
),
|
||||
leading: const Icon(Icons.folder_special_outlined),
|
||||
minLeadingWidth: 0,
|
||||
title: Text(_favController
|
||||
.favFolderData.value.list![index].title!),
|
||||
subtitle: Text(
|
||||
'${_favController.favFolderData.value.list![index].mediaCount}个内容',
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.outline,
|
||||
fontSize: Theme.of(context)
|
||||
.textTheme
|
||||
.labelSmall!
|
||||
.fontSize),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return HttpError(
|
||||
errMsg: data['msg'],
|
||||
fn: () => setState(() {}),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// 骨架屏
|
||||
return Text('请求中');
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
50
lib/pages/favDetail/controller.dart
Normal file
@ -0,0 +1,50 @@
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:pilipala/http/user.dart';
|
||||
import 'package:pilipala/http/video.dart';
|
||||
import 'package:pilipala/models/user/fav_detail.dart';
|
||||
import 'package:pilipala/models/user/fav_folder.dart';
|
||||
|
||||
class FavDetailController extends GetxController {
|
||||
FavFolderItemData? item;
|
||||
Rx<FavDetailData> favDetailData = FavDetailData().obs;
|
||||
int? mediaId;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
item = Get.arguments;
|
||||
if (Get.parameters.keys.isNotEmpty) {
|
||||
mediaId = int.parse(Get.parameters['mediaId']!);
|
||||
}
|
||||
super.onInit();
|
||||
}
|
||||
|
||||
Future<dynamic> queryUserFavFolderDetail() async {
|
||||
var res = await await UserHttp.userFavFolderDetail(
|
||||
pn: 1,
|
||||
ps: 15,
|
||||
mediaId: mediaId!,
|
||||
);
|
||||
favDetailData.value = res['data'];
|
||||
return res;
|
||||
}
|
||||
|
||||
onCancelFav(int id) async {
|
||||
var result = await VideoHttp.favVideo(
|
||||
aid: id.toString(), addIds: '', delIds: mediaId.toString());
|
||||
if (result['status']) {
|
||||
if (result['data']['prompt']) {
|
||||
List<FavDetailItemData> dataList = favDetailData.value.medias!;
|
||||
for (var i in dataList) {
|
||||
if (i.id == id) {
|
||||
dataList.remove(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
favDetailData.value.medias = dataList;
|
||||
favDetailData.refresh();
|
||||
SmartDialog.showToast('取消收藏');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
4
lib/pages/favDetail/index.dart
Normal file
@ -0,0 +1,4 @@
|
||||
library favdetail;
|
||||
|
||||
export './controller.dart';
|
||||
export './view.dart';
|
219
lib/pages/favDetail/view.dart
Normal file
@ -0,0 +1,219 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:pilipala/common/widgets/http_error.dart';
|
||||
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
||||
import 'package:pilipala/pages/favDetail/index.dart';
|
||||
|
||||
import 'widget/fav_video_card.dart';
|
||||
|
||||
class FavDetailPage extends StatefulWidget {
|
||||
const FavDetailPage({super.key});
|
||||
|
||||
@override
|
||||
State<FavDetailPage> createState() => _FavDetailPageState();
|
||||
}
|
||||
|
||||
class _FavDetailPageState extends State<FavDetailPage> {
|
||||
late final ScrollController _controller = ScrollController();
|
||||
final FavDetailController _favDetailController =
|
||||
Get.put(FavDetailController());
|
||||
late StreamController<bool> titleStreamC; // a
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
titleStreamC = StreamController<bool>();
|
||||
_controller.addListener(
|
||||
() {
|
||||
if (_controller.offset > 160) {
|
||||
titleStreamC.add(true);
|
||||
} else if (_controller.offset <= 160) {
|
||||
titleStreamC.add(false);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: CustomScrollView(
|
||||
controller: _controller,
|
||||
slivers: [
|
||||
SliverAppBar(
|
||||
expandedHeight: 260 - MediaQuery.of(context).padding.top,
|
||||
pinned: true,
|
||||
title: StreamBuilder(
|
||||
stream: titleStreamC.stream,
|
||||
initialData: false,
|
||||
builder: (context, AsyncSnapshot snapshot) {
|
||||
return AnimatedOpacity(
|
||||
opacity: snapshot.data ? 1 : 0,
|
||||
curve: Curves.easeOut,
|
||||
duration: const Duration(milliseconds: 500),
|
||||
child: Row(
|
||||
children: [
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
_favDetailController.item!.title!,
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
Text(
|
||||
'共${_favDetailController.item!.mediaCount!}条视频',
|
||||
style: Theme.of(context).textTheme.labelMedium,
|
||||
)
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
// actions: [
|
||||
// IconButton(
|
||||
// onPressed: () {},
|
||||
// icon: const Icon(Icons.more_vert),
|
||||
// ),
|
||||
// const SizedBox(width: 4)
|
||||
// ],
|
||||
flexibleSpace: FlexibleSpaceBar(
|
||||
background: Container(
|
||||
decoration: BoxDecoration(
|
||||
border: Border(
|
||||
bottom: BorderSide(
|
||||
color: Theme.of(context).dividerColor.withOpacity(0.2),
|
||||
),
|
||||
),
|
||||
),
|
||||
padding: EdgeInsets.only(
|
||||
top: kTextTabBarHeight +
|
||||
MediaQuery.of(context).padding.top +
|
||||
30,
|
||||
left: 20,
|
||||
right: 20),
|
||||
child: SizedBox(
|
||||
height: 200,
|
||||
child: Row(
|
||||
// mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 180,
|
||||
height: 110,
|
||||
child: NetworkImgLayer(
|
||||
width: 180,
|
||||
height: 110,
|
||||
src: _favDetailController.item!.cover,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 14),
|
||||
Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
_favDetailController.item!.title!,
|
||||
style: TextStyle(
|
||||
fontSize: Theme.of(context)
|
||||
.textTheme
|
||||
.titleMedium!
|
||||
.fontSize,
|
||||
fontWeight: FontWeight.bold),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
_favDetailController.item!.upper!.name!,
|
||||
style: TextStyle(
|
||||
fontSize: Theme.of(context)
|
||||
.textTheme
|
||||
.labelSmall!
|
||||
.fontSize,
|
||||
color: Theme.of(context).colorScheme.outline),
|
||||
)
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(top: 15, bottom: 8, left: 14),
|
||||
child: Obx(
|
||||
() => Text(
|
||||
'共${_favDetailController.favDetailData.value.medias != null ? _favDetailController.favDetailData.value.medias!.length : '-'}条视频',
|
||||
style: TextStyle(
|
||||
fontSize:
|
||||
Theme.of(context).textTheme.labelMedium!.fontSize,
|
||||
color: Theme.of(context).colorScheme.outline,
|
||||
letterSpacing: 1),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
FutureBuilder(
|
||||
future: _favDetailController.queryUserFavFolderDetail(),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.done) {
|
||||
Map data = snapshot.data;
|
||||
if (data['status']) {
|
||||
if (_favDetailController.item!.mediaCount == 0) {
|
||||
return const SliverToBoxAdapter(
|
||||
child: SizedBox(
|
||||
height: 300,
|
||||
child: Center(child: Text('没有内容')),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return Obx(
|
||||
() => SliverList(
|
||||
delegate: SliverChildBuilderDelegate((context, index) {
|
||||
return FavVideoCardH(
|
||||
videoItem: _favDetailController
|
||||
.favDetailData.value.medias![index],
|
||||
);
|
||||
},
|
||||
childCount: _favDetailController
|
||||
.favDetailData.value.medias!.length),
|
||||
),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
return HttpError(
|
||||
errMsg: data['msg'],
|
||||
fn: () => setState(() {}),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
return const SliverToBoxAdapter(
|
||||
child: SizedBox(
|
||||
height: 300,
|
||||
child: Center(child: Text('加载中')),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: SizedBox(
|
||||
height: MediaQuery.of(context).padding.bottom + 20,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
165
lib/pages/favDetail/widget/fav_video_card.dart
Normal file
@ -0,0 +1,165 @@
|
||||
import 'package:get/get.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:pilipala/common/constants.dart';
|
||||
import 'package:pilipala/common/widgets/stat/danmu.dart';
|
||||
import 'package:pilipala/common/widgets/stat/view.dart';
|
||||
import 'package:pilipala/utils/utils.dart';
|
||||
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
||||
|
||||
import '../controller.dart';
|
||||
|
||||
// 收藏视频卡片 - 水平布局
|
||||
class FavVideoCardH extends StatelessWidget {
|
||||
var videoItem;
|
||||
final FavDetailController _favDetailController =
|
||||
Get.put(FavDetailController());
|
||||
|
||||
FavVideoCardH({Key? key, required this.videoItem}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
int id = videoItem.id;
|
||||
String heroTag = Utils.makeHeroTag(id);
|
||||
return Dismissible(
|
||||
movementDuration: const Duration(milliseconds: 300),
|
||||
background: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.errorContainer,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: const [
|
||||
Icon(Icons.clear_all_rounded),
|
||||
SizedBox(width: 6),
|
||||
Text('取消收藏')
|
||||
],
|
||||
)),
|
||||
direction: DismissDirection.endToStart,
|
||||
key: ValueKey<int>(videoItem.id),
|
||||
onDismissed: (DismissDirection direction) {
|
||||
_favDetailController.onCancelFav(videoItem.id);
|
||||
// widget.onDeleteNotice();
|
||||
},
|
||||
child: InkWell(
|
||||
onTap: () async {
|
||||
await Future.delayed(const Duration(milliseconds: 200));
|
||||
Get.toNamed('/video?aid=$id',
|
||||
arguments: {'videoItem': videoItem, 'heroTag': heroTag});
|
||||
},
|
||||
child: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(12, 5, 12, 5),
|
||||
child: LayoutBuilder(
|
||||
builder: (context, boxConstraints) {
|
||||
double width =
|
||||
(boxConstraints.maxWidth - StyleString.cardSpace * 6) / 2;
|
||||
return SizedBox(
|
||||
height: width / StyleString.aspectRatio,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
AspectRatio(
|
||||
aspectRatio: StyleString.aspectRatio,
|
||||
child: LayoutBuilder(
|
||||
builder: (context, boxConstraints) {
|
||||
double maxWidth = boxConstraints.maxWidth;
|
||||
double maxHeight = boxConstraints.maxHeight;
|
||||
double PR =
|
||||
MediaQuery.of(context).devicePixelRatio;
|
||||
return Stack(
|
||||
children: [
|
||||
Hero(
|
||||
tag: heroTag,
|
||||
child: NetworkImgLayer(
|
||||
// src: videoItem['pic'] +
|
||||
// '@${(maxWidth * 2).toInt()}w',
|
||||
src: videoItem.pic + '@.webp',
|
||||
width: maxWidth,
|
||||
height: maxHeight,
|
||||
),
|
||||
),
|
||||
// Image.network( videoItem['pic'], width: double.infinity, height: double.infinity,),
|
||||
Positioned(
|
||||
right: 4,
|
||||
bottom: 4,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 1, horizontal: 6),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius:
|
||||
BorderRadius.circular(4),
|
||||
color:
|
||||
Colors.black54.withOpacity(0.4)),
|
||||
child: Text(
|
||||
Utils.timeFormat(videoItem.duration!),
|
||||
style: const TextStyle(
|
||||
fontSize: 11, color: Colors.white),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
VideoContent(videoItem: videoItem)
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class VideoContent extends StatelessWidget {
|
||||
final videoItem;
|
||||
const VideoContent({super.key, required this.videoItem});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(10, 2, 6, 0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
videoItem.title,
|
||||
textAlign: TextAlign.start,
|
||||
style: TextStyle(
|
||||
fontSize: Theme.of(context).textTheme.titleSmall!.fontSize,
|
||||
fontWeight: FontWeight.w500),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
const Spacer(),
|
||||
Text(
|
||||
videoItem.owner.name,
|
||||
style: TextStyle(
|
||||
fontSize: Theme.of(context).textTheme.labelSmall!.fontSize,
|
||||
color: Theme.of(context).colorScheme.outline,
|
||||
),
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
StatView(
|
||||
theme: 'gray',
|
||||
view: videoItem.cntInfo['play'],
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
StatDanMu(theme: 'gray', danmu: videoItem.cntInfo['danmaku'])
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,7 +1,6 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:pilipala/http/api.dart';
|
||||
import 'package:pilipala/http/init.dart';
|
||||
import 'package:pilipala/http/video.dart';
|
||||
import 'package:pilipala/models/model_rec_video_item.dart';
|
||||
|
||||
class HomeController extends GetxController {
|
||||
@ -17,28 +16,27 @@ class HomeController extends GetxController {
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
queryRcmdFeed('init');
|
||||
// queryRcmdFeed('init');
|
||||
}
|
||||
|
||||
// 获取推荐
|
||||
Future queryRcmdFeed(type) async {
|
||||
var res = await Request().get(
|
||||
Api.recommendList,
|
||||
data: {'feed_version': "V3", 'ps': count, 'fresh_idx': _currentPage},
|
||||
var res = await VideoHttp.rcmdVideoList(
|
||||
ps: count,
|
||||
freshIdx: _currentPage,
|
||||
);
|
||||
List<RecVideoItemModel> list = [];
|
||||
for (var i in res.data['data']['item']) {
|
||||
list.add(RecVideoItemModel.fromJson(i));
|
||||
}
|
||||
if (res['status']) {
|
||||
if (type == 'init') {
|
||||
videoList.value = list;
|
||||
videoList.value = res['data'];
|
||||
} else if (type == 'onRefresh') {
|
||||
videoList.insertAll(0, list);
|
||||
videoList.insertAll(0, res['data']);
|
||||
} else if (type == 'onLoad') {
|
||||
videoList.addAll(list);
|
||||
videoList.addAll(res['data']);
|
||||
}
|
||||
_currentPage += 1;
|
||||
}
|
||||
isLoadingMore = false;
|
||||
return res;
|
||||
}
|
||||
|
||||
// 下拉刷新
|
||||
@ -48,7 +46,6 @@ class HomeController extends GetxController {
|
||||
|
||||
// 上拉加载
|
||||
Future onLoad() async {
|
||||
await Future.delayed(const Duration(milliseconds: 500));
|
||||
queryRcmdFeed('onLoad');
|
||||
}
|
||||
|
||||
|
@ -3,6 +3,7 @@ import 'package:get/get.dart';
|
||||
import 'package:pilipala/common/skeleton/video_card_v.dart';
|
||||
import 'package:pilipala/common/widgets/animated_dialog.dart';
|
||||
import 'package:pilipala/common/widgets/overlay_pop.dart';
|
||||
import 'package:pilipala/common/widgets/http_error.dart';
|
||||
import 'package:pilipala/common/widgets/video_card_v.dart';
|
||||
import './controller.dart';
|
||||
import 'package:pilipala/common/constants.dart';
|
||||
@ -18,6 +19,7 @@ class HomePage extends StatefulWidget {
|
||||
class _HomePageState extends State<HomePage>
|
||||
with AutomaticKeepAliveClientMixin {
|
||||
final HomeController _homeController = Get.put(HomeController());
|
||||
Future? _futureBuilderFuture;
|
||||
List videoList = [];
|
||||
|
||||
@override
|
||||
@ -26,6 +28,7 @@ class _HomePageState extends State<HomePage>
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_futureBuilderFuture = _homeController.queryRcmdFeed('init');
|
||||
_homeController.videoList.listen((value) {
|
||||
videoList = value;
|
||||
setState(() {});
|
||||
@ -71,37 +74,25 @@ class _HomePageState extends State<HomePage>
|
||||
? EdgeInsets.zero
|
||||
: const EdgeInsets.fromLTRB(
|
||||
StyleString.cardSpace, 0, StyleString.cardSpace, 8),
|
||||
sliver: SliverGrid(
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||
// 行间距
|
||||
mainAxisSpacing: StyleString.cardSpace,
|
||||
// 列间距
|
||||
crossAxisSpacing: StyleString.cardSpace,
|
||||
// 列数
|
||||
crossAxisCount: _homeController.crossAxisCount,
|
||||
mainAxisExtent: MediaQuery.of(context).size.width /
|
||||
_homeController.crossAxisCount /
|
||||
StyleString.aspectRatio +
|
||||
72),
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(BuildContext context, int index) {
|
||||
return videoList.isNotEmpty
|
||||
? VideoCardV(
|
||||
videoItem: videoList[index],
|
||||
longPress: () {
|
||||
_homeController.popupDialog =
|
||||
_createPopupDialog(videoList[index]);
|
||||
Overlay.of(context)
|
||||
.insert(_homeController.popupDialog!);
|
||||
sliver: FutureBuilder(
|
||||
future: _futureBuilderFuture,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.done) {
|
||||
Map data = snapshot.data as Map;
|
||||
if (data['status']) {
|
||||
return Obx(() => contentGrid(
|
||||
_homeController, _homeController.videoList));
|
||||
} else {
|
||||
return HttpError(
|
||||
errMsg: data['msg'],
|
||||
fn: () => setState(() {}),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// 骨架屏
|
||||
return contentGrid(_homeController, []);
|
||||
}
|
||||
},
|
||||
longPressEnd: () {
|
||||
_homeController.popupDialog?.remove();
|
||||
},
|
||||
)
|
||||
: const VideoCardVSkeleton();
|
||||
},
|
||||
childCount: videoList.isNotEmpty ? videoList.length : 10,
|
||||
),
|
||||
),
|
||||
),
|
||||
const LoadingMore()
|
||||
@ -116,6 +107,42 @@ class _HomePageState extends State<HomePage>
|
||||
return OverlayEntry(
|
||||
builder: (context) => AnimatedDialog(
|
||||
child: OverlayPop(videoItem: videoItem),
|
||||
));
|
||||
}
|
||||
|
||||
Widget contentGrid(ctr, videoList) {
|
||||
return SliverGrid(
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||
// 行间距
|
||||
mainAxisSpacing: StyleString.cardSpace,
|
||||
// 列间距
|
||||
crossAxisSpacing: StyleString.cardSpace,
|
||||
// 列数
|
||||
crossAxisCount: ctr.crossAxisCount,
|
||||
mainAxisExtent: MediaQuery.of(context).size.width /
|
||||
ctr.crossAxisCount /
|
||||
StyleString.aspectRatio +
|
||||
70,
|
||||
),
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(BuildContext context, int index) {
|
||||
return videoList!.isNotEmpty
|
||||
?
|
||||
// VideoCardV(videoItem: videoList![index])
|
||||
VideoCardV(
|
||||
videoItem: videoList[index],
|
||||
longPress: () {
|
||||
_homeController.popupDialog =
|
||||
_createPopupDialog(videoList[index]);
|
||||
Overlay.of(context).insert(_homeController.popupDialog!);
|
||||
},
|
||||
longPressEnd: () {
|
||||
_homeController.popupDialog?.remove();
|
||||
},
|
||||
)
|
||||
: const VideoCardVSkeleton();
|
||||
},
|
||||
childCount: videoList!.isNotEmpty ? videoList!.length : 10,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -1,5 +1,9 @@
|
||||
import 'dart:io';
|
||||
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/pages/mine/view.dart';
|
||||
|
||||
class HomeAppBar extends StatelessWidget {
|
||||
const HomeAppBar({super.key});
|
||||
@ -9,11 +13,7 @@ class HomeAppBar extends StatelessWidget {
|
||||
return SliverAppBar(
|
||||
// forceElevated: true,
|
||||
scrolledUnderElevation: 0,
|
||||
toolbarHeight: Platform.isAndroid
|
||||
? (MediaQuery.of(context).padding.top + 6)
|
||||
: Platform.isIOS
|
||||
? MediaQuery.of(context).padding.top - 2
|
||||
: kToolbarHeight,
|
||||
toolbarHeight: MediaQuery.of(context).padding.top,
|
||||
expandedHeight: kToolbarHeight + MediaQuery.of(context).padding.top,
|
||||
automaticallyImplyLeading: false,
|
||||
pinned: true,
|
||||
@ -29,19 +29,26 @@ class HomeAppBar extends StatelessWidget {
|
||||
title: const Text(
|
||||
'PiLiPaLa',
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
letterSpacing: 1,
|
||||
fontFamily: 'ArchivoNarrow',
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
IconButton(
|
||||
onPressed: () {},
|
||||
icon: const Icon(Icons.notifications_none_rounded),
|
||||
icon: const Icon(CupertinoIcons.search, size: 22),
|
||||
),
|
||||
// IconButton(
|
||||
// onPressed: () {},
|
||||
// icon: const Icon(CupertinoIcons.bell, size: 22),
|
||||
// ),
|
||||
IconButton(
|
||||
onPressed: () {},
|
||||
icon: const Icon(Icons.search_rounded),
|
||||
onPressed: () {
|
||||
Get.bottomSheet(const MinePage());
|
||||
},
|
||||
icon: const Icon(CupertinoIcons.person, size: 22),
|
||||
),
|
||||
const SizedBox(width: 10)
|
||||
],
|
||||
|
@ -1,8 +1,6 @@
|
||||
import 'package:flutter/animation.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:pilipala/http/api.dart';
|
||||
import 'package:pilipala/http/init.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:pilipala/http/video.dart';
|
||||
import 'package:pilipala/models/model_hot_video_item.dart';
|
||||
|
||||
class HotController extends GetxController {
|
||||
@ -14,31 +12,24 @@ class HotController extends GetxController {
|
||||
bool flag = false;
|
||||
OverlayEntry? popupDialog;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
queryHotFeed('init');
|
||||
}
|
||||
|
||||
// 获取推荐
|
||||
Future queryHotFeed(type) async {
|
||||
var res = await Request().get(
|
||||
Api.hotList,
|
||||
data: {'pn': _currentPage, 'ps': _count},
|
||||
var res = await VideoHttp.hotVideoList(
|
||||
pn: _currentPage,
|
||||
ps: _count,
|
||||
);
|
||||
List<HotVideoItemModel> list = [];
|
||||
for (var i in res.data['data']['list']) {
|
||||
list.add(HotVideoItemModel.fromJson(i));
|
||||
}
|
||||
if (res['status']) {
|
||||
if (type == 'init') {
|
||||
videoList.value = list;
|
||||
videoList.value = res['data'];
|
||||
} else if (type == 'onRefresh') {
|
||||
videoList.insertAll(0, list);
|
||||
videoList.insertAll(0, res['data']);
|
||||
} else if (type == 'onLoad') {
|
||||
videoList.addAll(list);
|
||||
videoList.addAll(res['data']);
|
||||
}
|
||||
_currentPage += 1;
|
||||
}
|
||||
isLoadingMore = false;
|
||||
return res;
|
||||
}
|
||||
|
||||
// 下拉刷新
|
||||
|
@ -2,6 +2,8 @@ import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:pilipala/common/widgets/animated_dialog.dart';
|
||||
import 'package:pilipala/common/widgets/overlay_pop.dart';
|
||||
import 'package:pilipala/common/skeleton/video_card_h.dart';
|
||||
import 'package:pilipala/common/widgets/http_error.dart';
|
||||
import 'package:pilipala/common/widgets/video_card_h.dart';
|
||||
import 'package:pilipala/pages/hot/controller.dart';
|
||||
import 'package:pilipala/pages/home/widgets/app_bar.dart';
|
||||
@ -16,6 +18,7 @@ class HotPage extends StatefulWidget {
|
||||
class _HotPageState extends State<HotPage> with AutomaticKeepAliveClientMixin {
|
||||
final HotController _hotController = Get.put(HotController());
|
||||
List videoList = [];
|
||||
Future? _futureBuilderFuture;
|
||||
|
||||
@override
|
||||
bool get wantKeepAlive => true;
|
||||
@ -23,11 +26,7 @@ class _HotPageState extends State<HotPage> with AutomaticKeepAliveClientMixin {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_hotController.videoList.listen((value) {
|
||||
videoList = value;
|
||||
setState(() {});
|
||||
});
|
||||
|
||||
_futureBuilderFuture = _hotController.queryHotFeed('init');
|
||||
_hotController.scrollController.addListener(
|
||||
() {
|
||||
if (_hotController.scrollController.position.pixels >=
|
||||
@ -54,20 +53,46 @@ class _HotPageState extends State<HotPage> with AutomaticKeepAliveClientMixin {
|
||||
controller: _hotController.scrollController,
|
||||
slivers: [
|
||||
const HomeAppBar(),
|
||||
SliverList(
|
||||
FutureBuilder(
|
||||
future: _futureBuilderFuture,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.done) {
|
||||
Map data = snapshot.data as Map;
|
||||
if (data['status']) {
|
||||
return Obx(
|
||||
() => SliverList(
|
||||
delegate: SliverChildBuilderDelegate((context, index) {
|
||||
return VideoCardH(
|
||||
videoItem: videoList[index],
|
||||
videoItem: _hotController.videoList[index],
|
||||
longPress: () {
|
||||
_hotController.popupDialog =
|
||||
_createPopupDialog(videoList[index]);
|
||||
Overlay.of(context).insert(_hotController.popupDialog!);
|
||||
_hotController.popupDialog = _createPopupDialog(
|
||||
_hotController.videoList[index]);
|
||||
Overlay.of(context)
|
||||
.insert(_hotController.popupDialog!);
|
||||
},
|
||||
longPressEnd: () {
|
||||
_hotController.popupDialog?.remove();
|
||||
},
|
||||
);
|
||||
}, childCount: videoList.length)),
|
||||
}, childCount: _hotController.videoList.length),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return HttpError(
|
||||
errMsg: data['msg'],
|
||||
fn: () => setState(() {}),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// 骨架屏
|
||||
return SliverList(
|
||||
delegate: SliverChildBuilderDelegate((context, index) {
|
||||
return const VideoCardHSkeleton();
|
||||
}, childCount: 5),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: SizedBox(
|
||||
height: MediaQuery.of(context).padding.bottom + 10,
|
||||
|
@ -1,30 +1,97 @@
|
||||
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/pages/media/index.dart';
|
||||
import 'package:pilipala/utils/storage.dart';
|
||||
|
||||
class MainController extends GetxController {
|
||||
List<Widget> pages = <Widget>[
|
||||
const HomePage(),
|
||||
const HotPage(),
|
||||
const MinePage(),
|
||||
const MediaPage(),
|
||||
];
|
||||
List navigationBars = [
|
||||
RxList navigationBars = [
|
||||
{
|
||||
'icon': const Icon(Icons.home_outlined),
|
||||
'selectedIcon': const Icon(Icons.home),
|
||||
// 'icon': const Icon(Icons.home_outlined),
|
||||
// 'selectedIcon': const Icon(Icons.home),
|
||||
'icon': const Icon(
|
||||
CupertinoIcons.square_favorites_alt,
|
||||
size: 21,
|
||||
),
|
||||
'selectedIcon': const Icon(
|
||||
CupertinoIcons.square_favorites_alt_fill,
|
||||
size: 21,
|
||||
),
|
||||
'label': "推荐",
|
||||
},
|
||||
{
|
||||
'icon': const Icon(Icons.whatshot_outlined),
|
||||
'selectedIcon': const Icon(Icons.whatshot_rounded),
|
||||
// 'icon': const Icon(Icons.whatshot_outlined),
|
||||
// 'selectedIcon': const Icon(Icons.whatshot_rounded),
|
||||
'icon': const Icon(
|
||||
CupertinoIcons.flame,
|
||||
size: 20,
|
||||
),
|
||||
'selectedIcon': const Icon(
|
||||
CupertinoIcons.flame_fill,
|
||||
size: 20,
|
||||
),
|
||||
'label': "热门",
|
||||
},
|
||||
// {
|
||||
// 'icon': const Icon(
|
||||
// CupertinoIcons.person,
|
||||
// size: 21,
|
||||
// ),
|
||||
// 'selectedIcon': const Icon(
|
||||
// CupertinoIcons.person_fill,
|
||||
// size: 21,
|
||||
// ),
|
||||
// 'label': "我的",
|
||||
// },
|
||||
{
|
||||
'icon': const Icon(Icons.person_outline),
|
||||
'selectedIcon': const Icon(Icons.person),
|
||||
'label': "我的",
|
||||
// 'icon': const Icon(Icons.person_outline),
|
||||
// 'selectedIcon': const Icon(Icons.person),
|
||||
'icon': const Icon(
|
||||
CupertinoIcons.folder,
|
||||
size: 20,
|
||||
),
|
||||
'selectedIcon': const Icon(
|
||||
CupertinoIcons.folder_fill,
|
||||
size: 20,
|
||||
),
|
||||
'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'] = '我的';
|
||||
// }
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
|
||||
final MainController _mainController = Get.put(MainController());
|
||||
final HomeController _homeController = Get.put(HomeController());
|
||||
final HotController _hotController = Get.put(HotController());
|
||||
PageController? _pageController;
|
||||
|
||||
late AnimationController? _animationController;
|
||||
late Animation<double>? _fadeAnimation;
|
||||
@ -36,6 +37,7 @@ class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
|
||||
_slideAnimation =
|
||||
Tween(begin: 0.8, end: 1.0).animate(_animationController!);
|
||||
_lastSelectTime = DateTime.now().millisecondsSinceEpoch;
|
||||
_pageController = PageController(initialPage: selectedIndex);
|
||||
}
|
||||
|
||||
void setIndex(int value) async {
|
||||
@ -47,7 +49,7 @@ class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
|
||||
});
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
_pageController!.jumpToPage(value);
|
||||
var currentPage = _mainController.pages[value];
|
||||
if (currentPage is HomePage) {
|
||||
if (_homeController.flag) {
|
||||
@ -98,13 +100,19 @@ class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
|
||||
reverseCurve: Curves.linear,
|
||||
),
|
||||
),
|
||||
child: IndexedStack(
|
||||
index: selectedIndex,
|
||||
child: PageView(
|
||||
controller: _pageController,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
onPageChanged: (index) {
|
||||
selectedIndex = index;
|
||||
setState(() {});
|
||||
},
|
||||
children: _mainController.pages,
|
||||
),
|
||||
),
|
||||
),
|
||||
bottomNavigationBar: NavigationBar(
|
||||
bottomNavigationBar: Obx(
|
||||
() => NavigationBar(
|
||||
elevation: 1,
|
||||
destinations: _mainController.navigationBars.map((e) {
|
||||
return NavigationDestination(
|
||||
@ -116,6 +124,7 @@ class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
|
||||
selectedIndex: selectedIndex,
|
||||
onDestinationSelected: (value) => setIndex(value),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|