mod: merge main
@ -47,10 +47,11 @@ android {
|
|||||||
applicationId "com.example.pilipala"
|
applicationId "com.example.pilipala"
|
||||||
// You can update the following values to match your application needs.
|
// 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.
|
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
|
||||||
minSdkVersion flutter.minSdkVersion
|
// minSdkVersion flutter.minSdkVersion
|
||||||
targetSdkVersion flutter.targetSdkVersion
|
targetSdkVersion flutter.targetSdkVersion
|
||||||
versionCode flutterVersionCode.toInteger()
|
versionCode flutterVersionCode.toInteger()
|
||||||
versionName flutterVersionName
|
versionName flutterVersionName
|
||||||
|
minSdkVersion 19
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
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):
|
- connectivity_plus (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- ReachabilitySwift
|
- ReachabilitySwift
|
||||||
|
- device_info_plus (0.0.1):
|
||||||
|
- Flutter
|
||||||
- Flutter (1.0.0)
|
- Flutter (1.0.0)
|
||||||
- FMDB (2.7.5):
|
- FMDB (2.7.5):
|
||||||
- FMDB/standard (= 2.7.5)
|
- FMDB/standard (= 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):
|
- path_provider_foundation (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
|
- permission_handler_apple (9.0.4):
|
||||||
|
- Flutter
|
||||||
- ReachabilitySwift (5.0.0)
|
- ReachabilitySwift (5.0.0)
|
||||||
|
- share_plus (0.0.1):
|
||||||
|
- Flutter
|
||||||
- sqflite (0.0.2):
|
- sqflite (0.0.2):
|
||||||
- Flutter
|
- Flutter
|
||||||
- FMDB (>= 2.7.5)
|
- 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:
|
DEPENDENCIES:
|
||||||
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`)
|
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`)
|
||||||
|
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
|
||||||
- Flutter (from `Flutter`)
|
- Flutter (from `Flutter`)
|
||||||
|
- image_gallery_saver (from `.symlinks/plugins/image_gallery_saver/ios`)
|
||||||
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/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`)
|
- 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:
|
SPEC REPOS:
|
||||||
trunk:
|
trunk:
|
||||||
@ -28,21 +49,42 @@ SPEC REPOS:
|
|||||||
EXTERNAL SOURCES:
|
EXTERNAL SOURCES:
|
||||||
connectivity_plus:
|
connectivity_plus:
|
||||||
:path: ".symlinks/plugins/connectivity_plus/ios"
|
:path: ".symlinks/plugins/connectivity_plus/ios"
|
||||||
|
device_info_plus:
|
||||||
|
:path: ".symlinks/plugins/device_info_plus/ios"
|
||||||
Flutter:
|
Flutter:
|
||||||
:path: Flutter
|
:path: Flutter
|
||||||
|
image_gallery_saver:
|
||||||
|
:path: ".symlinks/plugins/image_gallery_saver/ios"
|
||||||
path_provider_foundation:
|
path_provider_foundation:
|
||||||
:path: ".symlinks/plugins/path_provider_foundation/ios"
|
: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:
|
sqflite:
|
||||||
:path: ".symlinks/plugins/sqflite/ios"
|
: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:
|
SPEC CHECKSUMS:
|
||||||
connectivity_plus: 413a8857dd5d9f1c399a39130850d02fe0feaf7e
|
connectivity_plus: 413a8857dd5d9f1c399a39130850d02fe0feaf7e
|
||||||
|
device_info_plus: e5c5da33f982a436e103237c0c85f9031142abed
|
||||||
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
|
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
|
||||||
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
|
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
|
||||||
|
image_gallery_saver: 259eab68fb271cfd57d599904f7acdc7832e7ef2
|
||||||
path_provider_foundation: c68054786f1b4f3343858c1e1d0caaded73f0be9
|
path_provider_foundation: c68054786f1b4f3343858c1e1d0caaded73f0be9
|
||||||
|
permission_handler_apple: 44366e37eaf29454a1e7b1b7d736c2cceaeb17ce
|
||||||
ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825
|
ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825
|
||||||
|
share_plus: 056a1e8ac890df3e33cb503afffaf1e9b4fbae68
|
||||||
sqflite: 6d358c025f5b867b29ed92fc697fd34924e11904
|
sqflite: 6d358c025f5b867b29ed92fc697fd34924e11904
|
||||||
|
url_launcher_ios: 08a3dfac5fb39e8759aeb0abbd5d9480f30fc8b4
|
||||||
|
webview_cookie_manager: eaf920722b493bd0f7611b5484771ca53fed03f7
|
||||||
|
webview_flutter_wkwebview: 2e2d318f21a5e036e2c3f26171342e95908bd60a
|
||||||
|
|
||||||
PODFILE CHECKSUM: ef19549a9bc3046e7bb7d2fab4d021637c0c58a3
|
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(
|
return Container(
|
||||||
clipBehavior: Clip.hardEdge,
|
clipBehavior: Clip.hardEdge,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Theme.of(context).colorScheme.background,
|
|
||||||
borderRadius: BorderRadius.circular(6),
|
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(
|
Container(
|
||||||
width: 200,
|
width: 200,
|
||||||
height: 13,
|
height: 13,
|
||||||
|
margin: const EdgeInsets.only(bottom: 5),
|
||||||
color: Theme.of(context).colorScheme.background,
|
color: Theme.of(context).colorScheme.background,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 5),
|
|
||||||
Container(
|
Container(
|
||||||
width: 150,
|
width: 150,
|
||||||
height: 13,
|
height: 13,
|
||||||
|
margin: const EdgeInsets.only(bottom: 12),
|
||||||
color: Theme.of(context).colorScheme.background,
|
color: Theme.of(context).colorScheme.background,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 12),
|
|
||||||
Container(
|
Container(
|
||||||
width: 80,
|
width: 80,
|
||||||
height: 13,
|
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:flutter/material.dart';
|
||||||
import 'package:cached_network_image/cached_network_image.dart';
|
import 'package:cached_network_image/cached_network_image.dart';
|
||||||
|
import 'package:pilipala/common/constants.dart';
|
||||||
|
|
||||||
class NetworkImgLayer extends StatelessWidget {
|
class NetworkImgLayer extends StatelessWidget {
|
||||||
final String? src;
|
final String? src;
|
||||||
@ -29,11 +30,16 @@ class NetworkImgLayer extends StatelessWidget {
|
|||||||
// double pr = 2;
|
// double pr = 2;
|
||||||
return src != ''
|
return src != ''
|
||||||
? ClipRRect(
|
? ClipRRect(
|
||||||
borderRadius: BorderRadius.circular(type == 'avatar' ? 50 : 4),
|
borderRadius: BorderRadius.circular(type == 'avatar'
|
||||||
|
? 50
|
||||||
|
: type == 'emote'
|
||||||
|
? 0
|
||||||
|
: StyleString.imgRadius.x),
|
||||||
child: CachedNetworkImage(
|
child: CachedNetworkImage(
|
||||||
imageUrl: src!,
|
imageUrl: src!,
|
||||||
width: width ?? double.infinity,
|
width: width ?? double.infinity,
|
||||||
height: height ?? double.infinity,
|
height: height ?? double.infinity,
|
||||||
|
alignment: Alignment.center,
|
||||||
maxWidthDiskCache: ((cacheW ?? width!) * pr).toInt(),
|
maxWidthDiskCache: ((cacheW ?? width!) * pr).toInt(),
|
||||||
// maxHeightDiskCache: (cacheH ?? height!).toInt(),
|
// maxHeightDiskCache: (cacheH ?? height!).toInt(),
|
||||||
memCacheWidth: ((cacheW ?? width!) * pr).toInt(),
|
memCacheWidth: ((cacheW ?? width!) * pr).toInt(),
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:pilipala/utils/utils.dart';
|
import 'package:pilipala/utils/utils.dart';
|
||||||
|
|
||||||
@ -11,21 +12,21 @@ class StatDanMu extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
Color color =
|
||||||
|
theme == 'white' ? Colors.white : Theme.of(context).colorScheme.outline;
|
||||||
return Row(
|
return Row(
|
||||||
children: [
|
children: [
|
||||||
Image.asset(
|
Icon(
|
||||||
'assets/images/dm_$theme.png',
|
CupertinoIcons.ellipses_bubble,
|
||||||
width: size == 'medium' ? 16 : 14,
|
size: 14,
|
||||||
height: size == 'medium' ? 16 : 14,
|
color: color,
|
||||||
),
|
),
|
||||||
const SizedBox(width: 2),
|
const SizedBox(width: 3),
|
||||||
Text(
|
Text(
|
||||||
Utils.numFormat(danmu!),
|
Utils.numFormat(danmu!),
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: size == 'medium' ? 12 : 11,
|
fontSize: size == 'medium' ? 12 : 11,
|
||||||
color: theme == 'white'
|
color: color,
|
||||||
? Colors.white
|
|
||||||
: Theme.of(context).colorScheme.outline,
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
|
|||||||
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:flutter/material.dart';
|
||||||
import 'package:pilipala/utils/utils.dart';
|
import 'package:pilipala/utils/utils.dart';
|
||||||
|
|
||||||
@ -6,26 +7,26 @@ class StatView extends StatelessWidget {
|
|||||||
final int? view;
|
final int? view;
|
||||||
final String? size;
|
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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
Color color =
|
||||||
|
theme == 'white' ? Colors.white : Theme.of(context).colorScheme.outline;
|
||||||
return Row(
|
return Row(
|
||||||
children: [
|
children: [
|
||||||
Image.asset(
|
Icon(
|
||||||
'assets/images/view_$theme.png',
|
CupertinoIcons.play_rectangle,
|
||||||
width: size == 'medium' ? 16 : 14,
|
size: 13,
|
||||||
height: size == 'medium' ? 16 : 14,
|
color: color,
|
||||||
),
|
),
|
||||||
const SizedBox(width: 2),
|
const SizedBox(width: 3),
|
||||||
Text(
|
Text(
|
||||||
Utils.numFormat(view!),
|
Utils.numFormat(view!),
|
||||||
// videoItem['stat']['view'].toString(),
|
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: size == 'medium' ? 12 : 11,
|
fontSize: size == 'medium' ? 12 : 11,
|
||||||
color: theme == 'white'
|
color: color,
|
||||||
? Colors.white
|
|
||||||
: Theme.of(context).colorScheme.outline,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import 'package:pilipala/common/widgets/network_img_layer.dart';
|
|||||||
|
|
||||||
// 视频卡片 - 水平布局
|
// 视频卡片 - 水平布局
|
||||||
class VideoCardH extends StatelessWidget {
|
class VideoCardH extends StatelessWidget {
|
||||||
|
// ignore: prefer_typing_uninitialized_variables
|
||||||
var videoItem;
|
var videoItem;
|
||||||
Function()? longPress;
|
Function()? longPress;
|
||||||
Function()? longPressEnd;
|
Function()? longPressEnd;
|
||||||
@ -20,84 +21,94 @@ class VideoCardH extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Material(
|
int aid = videoItem.aid;
|
||||||
child: Ink(
|
String heroTag = Utils.makeHeroTag(aid);
|
||||||
child: GestureDetector(
|
return GestureDetector(
|
||||||
onLongPress: () {
|
onLongPress: () {
|
||||||
longPress!();
|
longPress!();
|
||||||
},
|
},
|
||||||
onLongPressEnd: (details) {
|
onLongPressEnd: (details) {
|
||||||
longPressEnd!();
|
longPressEnd!();
|
||||||
},
|
},
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
await Future.delayed(const Duration(milliseconds: 200));
|
await Future.delayed(const Duration(milliseconds: 200));
|
||||||
int aid = videoItem.aid ?? videoItem.id;
|
Get.toNamed('/video?aid=$aid',
|
||||||
Get.toNamed('/video?aid=$aid',
|
arguments: {'videoItem': videoItem, 'heroTag': heroTag});
|
||||||
arguments: {'videoItem': videoItem});
|
},
|
||||||
},
|
child: Column(
|
||||||
child: Container(
|
children: [
|
||||||
|
Padding(
|
||||||
padding: const EdgeInsets.fromLTRB(
|
padding: const EdgeInsets.fromLTRB(
|
||||||
StyleString.cardSpace, 5, StyleString.cardSpace, 5),
|
StyleString.cardSpace, 7, StyleString.cardSpace, 7),
|
||||||
child: LayoutBuilder(builder: (context, boxConstraints) {
|
child: LayoutBuilder(
|
||||||
double width =
|
builder: (context, boxConstraints) {
|
||||||
(boxConstraints.maxWidth - StyleString.cardSpace * 6) / 2;
|
double width =
|
||||||
return SizedBox(
|
(boxConstraints.maxWidth - StyleString.cardSpace * 6) / 2;
|
||||||
height: width / StyleString.aspectRatio,
|
return SizedBox(
|
||||||
child: Row(
|
height: width / StyleString.aspectRatio,
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
child: Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
children: [
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
AspectRatio(
|
children: [
|
||||||
aspectRatio: StyleString.aspectRatio,
|
AspectRatio(
|
||||||
// child: ClipRRect(
|
aspectRatio: StyleString.aspectRatio,
|
||||||
// borderRadius: StyleString.mdRadius,
|
child: LayoutBuilder(
|
||||||
child: LayoutBuilder(
|
builder: (context, boxConstraints) {
|
||||||
builder: (context, boxConstraints) {
|
double maxWidth = boxConstraints.maxWidth;
|
||||||
double maxWidth = boxConstraints.maxWidth;
|
double maxHeight = boxConstraints.maxHeight;
|
||||||
double maxHeight = boxConstraints.maxHeight;
|
double PR =
|
||||||
double PR = MediaQuery.of(context).devicePixelRatio;
|
MediaQuery.of(context).devicePixelRatio;
|
||||||
return Stack(
|
return Stack(
|
||||||
children: [
|
children: [
|
||||||
NetworkImgLayer(
|
Hero(
|
||||||
// src: videoItem['pic'] +
|
tag: heroTag,
|
||||||
// '@${(maxWidth * 2).toInt()}w',
|
child: NetworkImgLayer(
|
||||||
src: videoItem.pic + '@.webp',
|
// src: videoItem['pic'] +
|
||||||
width: maxWidth,
|
// '@${(maxWidth * 2).toInt()}w',
|
||||||
height: maxHeight,
|
src: videoItem.pic + '@.webp',
|
||||||
),
|
width: maxWidth,
|
||||||
// Image.network( videoItem['pic'], width: double.infinity, height: double.infinity,),
|
height: maxHeight,
|
||||||
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),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
// Image.network( videoItem['pic'], width: double.infinity, height: double.infinity,),
|
// 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)
|
||||||
),
|
],
|
||||||
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,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
if (videoItem.rcmdReason != '' &&
|
if (videoItem.rcmdReason != null &&
|
||||||
videoItem.rcmdReason.content != '')
|
videoItem.rcmdReason.content != '')
|
||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 2, horizontal: 5),
|
padding: const EdgeInsets.symmetric(vertical: 2, horizontal: 5),
|
||||||
@ -145,12 +156,6 @@ class VideoContent extends StatelessWidget {
|
|||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Image.asset(
|
|
||||||
'assets/images/up_gray.png',
|
|
||||||
width: 14,
|
|
||||||
height: 12,
|
|
||||||
),
|
|
||||||
const SizedBox(width: 2),
|
|
||||||
Text(
|
Text(
|
||||||
videoItem.owner.name,
|
videoItem.owner.name,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
|
|||||||
@ -22,6 +22,7 @@ class VideoCardV extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
String heroTag = Utils.makeHeroTag(videoItem.id);
|
||||||
return Card(
|
return Card(
|
||||||
elevation: 0.8,
|
elevation: 0.8,
|
||||||
clipBehavior: Clip.hardEdge,
|
clipBehavior: Clip.hardEdge,
|
||||||
@ -40,7 +41,7 @@ class VideoCardV extends StatelessWidget {
|
|||||||
onTap: () async {
|
onTap: () async {
|
||||||
await Future.delayed(const Duration(milliseconds: 200));
|
await Future.delayed(const Duration(milliseconds: 200));
|
||||||
Get.toNamed('/video?aid=${videoItem.id}',
|
Get.toNamed('/video?aid=${videoItem.id}',
|
||||||
arguments: {'videoItem': videoItem});
|
arguments: {'videoItem': videoItem, 'heroTag': heroTag});
|
||||||
},
|
},
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
@ -57,12 +58,15 @@ class VideoCardV extends StatelessWidget {
|
|||||||
double PR = MediaQuery.of(context).devicePixelRatio;
|
double PR = MediaQuery.of(context).devicePixelRatio;
|
||||||
return Stack(
|
return Stack(
|
||||||
children: [
|
children: [
|
||||||
NetworkImgLayer(
|
Hero(
|
||||||
// 指定图片尺寸
|
tag: heroTag,
|
||||||
// src: videoItem.pic + '@${(maxWidth * 2).toInt()}w',
|
child: NetworkImgLayer(
|
||||||
src: videoItem.pic + '@.webp',
|
// 指定图片尺寸
|
||||||
width: maxWidth,
|
// src: videoItem.pic + '@${(maxWidth * 2).toInt()}w',
|
||||||
height: maxHeight,
|
src: videoItem.pic + '@.webp',
|
||||||
|
width: maxWidth,
|
||||||
|
height: maxHeight,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
Positioned(
|
Positioned(
|
||||||
left: 0,
|
left: 0,
|
||||||
@ -77,7 +81,7 @@ class VideoCardV extends StatelessWidget {
|
|||||||
duration: videoItem.duration,
|
duration: videoItem.duration,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
@ -141,6 +145,25 @@ class VideoContent extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 4)
|
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(
|
Expanded(
|
||||||
child: LayoutBuilder(builder:
|
child: LayoutBuilder(builder:
|
||||||
|
|||||||
@ -1,9 +1,124 @@
|
|||||||
class Api {
|
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';
|
static const String hotList = '/x/web-interface/popular';
|
||||||
|
|
||||||
// 视频详情
|
// 视频详情
|
||||||
// 竖屏 https://api.bilibili.com/x/web-interface/view?aid=527403921
|
// 竖屏 https://api.bilibili.com/x/web-interface/view?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 {
|
class Request {
|
||||||
static final Request _instance = Request._internal();
|
static final Request _instance = Request._internal();
|
||||||
|
static late CookieManager cookieManager;
|
||||||
|
|
||||||
factory Request() => _instance;
|
factory Request() => _instance;
|
||||||
|
|
||||||
@ -31,11 +32,9 @@ class Request {
|
|||||||
ignoreExpires: true,
|
ignoreExpires: true,
|
||||||
storage: FileStorage(cookiePath),
|
storage: FileStorage(cookiePath),
|
||||||
);
|
);
|
||||||
|
cookieManager = CookieManager(cookieJar);
|
||||||
dio.interceptors.add(CookieManager(cookieJar));
|
dio.interceptors.add(cookieManager);
|
||||||
|
var cookie = await cookieManager.cookieJar
|
||||||
var cookie = await CookieManager(cookieJar)
|
|
||||||
.cookieJar
|
|
||||||
.loadForRequest(Uri.parse(HttpString.baseUrl));
|
.loadForRequest(Uri.parse(HttpString.baseUrl));
|
||||||
if (cookie.isEmpty) {
|
if (cookie.isEmpty) {
|
||||||
try {
|
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
|
* config it and create
|
||||||
*/
|
*/
|
||||||
@ -111,20 +131,21 @@ class Request {
|
|||||||
return response;
|
return response;
|
||||||
} on DioError catch (e) {
|
} on DioError catch (e) {
|
||||||
print('get error: $e');
|
print('get error: $e');
|
||||||
return Future.error(ApiInterceptor.dioError(e));
|
return Future.error(await ApiInterceptor.dioError(e));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* post请求
|
* post请求
|
||||||
*/
|
*/
|
||||||
post(url, {data, options, cancelToken, extra}) async {
|
post(url, {data, queryParameters, options, cancelToken, extra}) async {
|
||||||
print('post-data: $data');
|
print('post-data: $data');
|
||||||
Response response;
|
Response response;
|
||||||
try {
|
try {
|
||||||
response = await dio.post(
|
response = await dio.post(
|
||||||
url,
|
url,
|
||||||
data: data,
|
data: data,
|
||||||
|
queryParameters: queryParameters,
|
||||||
options: options,
|
options: options,
|
||||||
cancelToken: cancelToken,
|
cancelToken: cancelToken,
|
||||||
);
|
);
|
||||||
@ -132,7 +153,7 @@ class Request {
|
|||||||
return response;
|
return response;
|
||||||
} on DioError catch (e) {
|
} on DioError catch (e) {
|
||||||
print('post error: $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:dio/dio.dart';
|
||||||
import 'package:connectivity_plus/connectivity_plus.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 {
|
class ApiInterceptor extends Interceptor {
|
||||||
@override
|
@override
|
||||||
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
|
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
|
||||||
// print("请求之前");
|
print("请求之前");
|
||||||
// 在请求之前添加头部或认证信息
|
// 在请求之前添加头部或认证信息
|
||||||
// options.headers['Authorization'] = 'Bearer token';
|
// options.headers['Authorization'] = 'Bearer token';
|
||||||
// options.headers['Content-Type'] = 'application/json';
|
// options.headers['Content-Type'] = 'application/json';
|
||||||
@ -13,15 +15,14 @@ class ApiInterceptor extends Interceptor {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void onResponse(Response response, ResponseInterceptorHandler handler) {
|
void onResponse(Response response, ResponseInterceptorHandler handler) {
|
||||||
// print("响应之前");
|
|
||||||
handler.next(response);
|
handler.next(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@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);
|
super.onError(err, handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -43,7 +44,7 @@ class ApiInterceptor extends Interceptor {
|
|||||||
return "发送请求超时,请检查网络设置";
|
return "发送请求超时,请检查网络设置";
|
||||||
case DioErrorType.unknown:
|
case DioErrorType.unknown:
|
||||||
var res = await checkConect();
|
var res = await checkConect();
|
||||||
return "$res 网络异常,请稍后重试!";
|
return res + " \n 网络异常,请稍后重试!";
|
||||||
default:
|
default:
|
||||||
return "Dio异常";
|
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:get/get.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:dynamic_color/dynamic_color.dart';
|
import 'package:dynamic_color/dynamic_color.dart';
|
||||||
import 'package:pilipala/http/init.dart';
|
import 'package:pilipala/http/init.dart';
|
||||||
import 'package:pilipala/router/app_pages.dart';
|
import 'package:pilipala/router/app_pages.dart';
|
||||||
import 'package:pilipala/pages/main/view.dart';
|
import 'package:pilipala/pages/main/view.dart';
|
||||||
|
import 'package:pilipala/utils/storage.dart';
|
||||||
|
|
||||||
void main() async {
|
void main() async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
|
await GStrorage.init();
|
||||||
await Request.setCookie();
|
await Request.setCookie();
|
||||||
runApp(const MyApp());
|
runApp(const MyApp());
|
||||||
}
|
}
|
||||||
@ -23,14 +26,24 @@ class MyApp extends StatelessWidget {
|
|||||||
return GetMaterialApp(
|
return GetMaterialApp(
|
||||||
title: 'PiLiPaLa',
|
title: 'PiLiPaLa',
|
||||||
theme: ThemeData(
|
theme: ThemeData(
|
||||||
colorScheme: lightDynamic ??
|
colorScheme: lightDynamic ??
|
||||||
ColorScheme.fromSeed(
|
ColorScheme.fromSeed(
|
||||||
seedColor: Colors.green, brightness: Brightness.light),
|
seedColor: Colors.green,
|
||||||
useMaterial3: true),
|
brightness: Brightness.light,
|
||||||
darkTheme: ThemeData(colorScheme: darkDynamic, useMaterial3: true),
|
),
|
||||||
|
useMaterial3: true,
|
||||||
|
),
|
||||||
|
darkTheme: ThemeData(
|
||||||
|
colorScheme: darkDynamic ??
|
||||||
|
ColorScheme.fromSeed(
|
||||||
|
seedColor: Colors.green,
|
||||||
|
brightness: Brightness.dark,
|
||||||
|
),
|
||||||
|
useMaterial3: true,
|
||||||
|
),
|
||||||
getPages: Routes.getPages,
|
getPages: Routes.getPages,
|
||||||
home: const MainApp(),
|
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"];
|
pubLocation = json["pub_location"];
|
||||||
seasontype = json["seasontype"];
|
seasontype = json["seasontype"];
|
||||||
isOgv = json["isOgv"];
|
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.pubdate,
|
||||||
this.owner,
|
this.owner,
|
||||||
this.stat,
|
this.stat,
|
||||||
|
this.isFollowed,
|
||||||
this.rcmdReason,
|
this.rcmdReason,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -27,6 +28,7 @@ class RecVideoItemModel {
|
|||||||
int? pubdate = -1;
|
int? pubdate = -1;
|
||||||
Owner? owner;
|
Owner? owner;
|
||||||
Stat? stat;
|
Stat? stat;
|
||||||
|
int? isFollowed;
|
||||||
RcmdReason? rcmdReason;
|
RcmdReason? rcmdReason;
|
||||||
|
|
||||||
RecVideoItemModel.fromJson(Map<String, dynamic> json) {
|
RecVideoItemModel.fromJson(Map<String, dynamic> json) {
|
||||||
@ -41,6 +43,7 @@ class RecVideoItemModel {
|
|||||||
pubdate = json["pubdate"];
|
pubdate = json["pubdate"];
|
||||||
owner = Owner.fromJson(json["owner"]);
|
owner = Owner.fromJson(json["owner"]);
|
||||||
stat = Stat.fromJson(json["stat"]);
|
stat = Stat.fromJson(json["stat"]);
|
||||||
|
isFollowed = json["is_followed"] ?? 0;
|
||||||
rcmdReason = json["rcmd_reason"] != null
|
rcmdReason = json["rcmd_reason"] != null
|
||||||
? RcmdReason.fromJson(json["rcmd_reason"])
|
? RcmdReason.fromJson(json["rcmd_reason"])
|
||||||
: RcmdReason(content: '');
|
: 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:flutter/cupertino.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:pilipala/http/api.dart';
|
import 'package:pilipala/http/video.dart';
|
||||||
import 'package:pilipala/http/init.dart';
|
|
||||||
import 'package:pilipala/models/model_rec_video_item.dart';
|
import 'package:pilipala/models/model_rec_video_item.dart';
|
||||||
|
|
||||||
class HomeController extends GetxController {
|
class HomeController extends GetxController {
|
||||||
@ -17,28 +16,27 @@ class HomeController extends GetxController {
|
|||||||
@override
|
@override
|
||||||
void onInit() {
|
void onInit() {
|
||||||
super.onInit();
|
super.onInit();
|
||||||
queryRcmdFeed('init');
|
// queryRcmdFeed('init');
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取推荐
|
// 获取推荐
|
||||||
Future queryRcmdFeed(type) async {
|
Future queryRcmdFeed(type) async {
|
||||||
var res = await Request().get(
|
var res = await VideoHttp.rcmdVideoList(
|
||||||
Api.recommendList,
|
ps: count,
|
||||||
data: {'feed_version': "V3", 'ps': count, 'fresh_idx': _currentPage},
|
freshIdx: _currentPage,
|
||||||
);
|
);
|
||||||
List<RecVideoItemModel> list = [];
|
if (res['status']) {
|
||||||
for (var i in res.data['data']['item']) {
|
if (type == 'init') {
|
||||||
list.add(RecVideoItemModel.fromJson(i));
|
videoList.value = res['data'];
|
||||||
|
} else if (type == 'onRefresh') {
|
||||||
|
videoList.insertAll(0, res['data']);
|
||||||
|
} else if (type == 'onLoad') {
|
||||||
|
videoList.addAll(res['data']);
|
||||||
|
}
|
||||||
|
_currentPage += 1;
|
||||||
}
|
}
|
||||||
if (type == 'init') {
|
|
||||||
videoList.value = list;
|
|
||||||
} else if (type == 'onRefresh') {
|
|
||||||
videoList.insertAll(0, list);
|
|
||||||
} else if (type == 'onLoad') {
|
|
||||||
videoList.addAll(list);
|
|
||||||
}
|
|
||||||
_currentPage += 1;
|
|
||||||
isLoadingMore = false;
|
isLoadingMore = false;
|
||||||
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 下拉刷新
|
// 下拉刷新
|
||||||
@ -48,7 +46,6 @@ class HomeController extends GetxController {
|
|||||||
|
|
||||||
// 上拉加载
|
// 上拉加载
|
||||||
Future onLoad() async {
|
Future onLoad() async {
|
||||||
await Future.delayed(const Duration(milliseconds: 500));
|
|
||||||
queryRcmdFeed('onLoad');
|
queryRcmdFeed('onLoad');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import 'package:get/get.dart';
|
|||||||
import 'package:pilipala/common/skeleton/video_card_v.dart';
|
import 'package:pilipala/common/skeleton/video_card_v.dart';
|
||||||
import 'package:pilipala/common/widgets/animated_dialog.dart';
|
import 'package:pilipala/common/widgets/animated_dialog.dart';
|
||||||
import 'package:pilipala/common/widgets/overlay_pop.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 'package:pilipala/common/widgets/video_card_v.dart';
|
||||||
import './controller.dart';
|
import './controller.dart';
|
||||||
import 'package:pilipala/common/constants.dart';
|
import 'package:pilipala/common/constants.dart';
|
||||||
@ -18,6 +19,7 @@ class HomePage extends StatefulWidget {
|
|||||||
class _HomePageState extends State<HomePage>
|
class _HomePageState extends State<HomePage>
|
||||||
with AutomaticKeepAliveClientMixin {
|
with AutomaticKeepAliveClientMixin {
|
||||||
final HomeController _homeController = Get.put(HomeController());
|
final HomeController _homeController = Get.put(HomeController());
|
||||||
|
Future? _futureBuilderFuture;
|
||||||
List videoList = [];
|
List videoList = [];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -26,6 +28,7 @@ class _HomePageState extends State<HomePage>
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
_futureBuilderFuture = _homeController.queryRcmdFeed('init');
|
||||||
_homeController.videoList.listen((value) {
|
_homeController.videoList.listen((value) {
|
||||||
videoList = value;
|
videoList = value;
|
||||||
setState(() {});
|
setState(() {});
|
||||||
@ -71,37 +74,25 @@ class _HomePageState extends State<HomePage>
|
|||||||
? EdgeInsets.zero
|
? EdgeInsets.zero
|
||||||
: const EdgeInsets.fromLTRB(
|
: const EdgeInsets.fromLTRB(
|
||||||
StyleString.cardSpace, 0, StyleString.cardSpace, 8),
|
StyleString.cardSpace, 0, StyleString.cardSpace, 8),
|
||||||
sliver: SliverGrid(
|
sliver: FutureBuilder(
|
||||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
future: _futureBuilderFuture,
|
||||||
// 行间距
|
builder: (context, snapshot) {
|
||||||
mainAxisSpacing: StyleString.cardSpace,
|
if (snapshot.connectionState == ConnectionState.done) {
|
||||||
// 列间距
|
Map data = snapshot.data as Map;
|
||||||
crossAxisSpacing: StyleString.cardSpace,
|
if (data['status']) {
|
||||||
// 列数
|
return Obx(() => contentGrid(
|
||||||
crossAxisCount: _homeController.crossAxisCount,
|
_homeController, _homeController.videoList));
|
||||||
mainAxisExtent: MediaQuery.of(context).size.width /
|
} else {
|
||||||
_homeController.crossAxisCount /
|
return HttpError(
|
||||||
StyleString.aspectRatio +
|
errMsg: data['msg'],
|
||||||
72),
|
fn: () => setState(() {}),
|
||||||
delegate: SliverChildBuilderDelegate(
|
);
|
||||||
(BuildContext context, int index) {
|
}
|
||||||
return videoList.isNotEmpty
|
} else {
|
||||||
? VideoCardV(
|
// 骨架屏
|
||||||
videoItem: videoList[index],
|
return contentGrid(_homeController, []);
|
||||||
longPress: () {
|
}
|
||||||
_homeController.popupDialog =
|
},
|
||||||
_createPopupDialog(videoList[index]);
|
|
||||||
Overlay.of(context)
|
|
||||||
.insert(_homeController.popupDialog!);
|
|
||||||
},
|
|
||||||
longPressEnd: () {
|
|
||||||
_homeController.popupDialog?.remove();
|
|
||||||
},
|
|
||||||
)
|
|
||||||
: const VideoCardVSkeleton();
|
|
||||||
},
|
|
||||||
childCount: videoList.isNotEmpty ? videoList.length : 10,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const LoadingMore()
|
const LoadingMore()
|
||||||
@ -114,8 +105,44 @@ class _HomePageState extends State<HomePage>
|
|||||||
|
|
||||||
OverlayEntry _createPopupDialog(videoItem) {
|
OverlayEntry _createPopupDialog(videoItem) {
|
||||||
return OverlayEntry(
|
return OverlayEntry(
|
||||||
builder: (context) => AnimatedDialog(
|
builder: (context) => AnimatedDialog(
|
||||||
child: OverlayPop(videoItem: videoItem),
|
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 'dart:io';
|
||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.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 {
|
class HomeAppBar extends StatelessWidget {
|
||||||
const HomeAppBar({super.key});
|
const HomeAppBar({super.key});
|
||||||
@ -9,11 +13,7 @@ class HomeAppBar extends StatelessWidget {
|
|||||||
return SliverAppBar(
|
return SliverAppBar(
|
||||||
// forceElevated: true,
|
// forceElevated: true,
|
||||||
scrolledUnderElevation: 0,
|
scrolledUnderElevation: 0,
|
||||||
toolbarHeight: Platform.isAndroid
|
toolbarHeight: MediaQuery.of(context).padding.top,
|
||||||
? (MediaQuery.of(context).padding.top + 6)
|
|
||||||
: Platform.isIOS
|
|
||||||
? MediaQuery.of(context).padding.top - 2
|
|
||||||
: kToolbarHeight,
|
|
||||||
expandedHeight: kToolbarHeight + MediaQuery.of(context).padding.top,
|
expandedHeight: kToolbarHeight + MediaQuery.of(context).padding.top,
|
||||||
automaticallyImplyLeading: false,
|
automaticallyImplyLeading: false,
|
||||||
pinned: true,
|
pinned: true,
|
||||||
@ -29,19 +29,26 @@ class HomeAppBar extends StatelessWidget {
|
|||||||
title: const Text(
|
title: const Text(
|
||||||
'PiLiPaLa',
|
'PiLiPaLa',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 18,
|
fontSize: 20,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
letterSpacing: 1,
|
letterSpacing: 1,
|
||||||
|
fontFamily: 'ArchivoNarrow',
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () {},
|
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(
|
IconButton(
|
||||||
onPressed: () {},
|
onPressed: () {
|
||||||
icon: const Icon(Icons.search_rounded),
|
Get.bottomSheet(const MinePage());
|
||||||
|
},
|
||||||
|
icon: const Icon(CupertinoIcons.person, size: 22),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 10)
|
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:get/get.dart';
|
||||||
import 'package:pilipala/http/api.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:pilipala/http/init.dart';
|
import 'package:pilipala/http/video.dart';
|
||||||
import 'package:pilipala/models/model_hot_video_item.dart';
|
import 'package:pilipala/models/model_hot_video_item.dart';
|
||||||
|
|
||||||
class HotController extends GetxController {
|
class HotController extends GetxController {
|
||||||
@ -14,31 +12,24 @@ class HotController extends GetxController {
|
|||||||
bool flag = false;
|
bool flag = false;
|
||||||
OverlayEntry? popupDialog;
|
OverlayEntry? popupDialog;
|
||||||
|
|
||||||
@override
|
|
||||||
void onInit() {
|
|
||||||
super.onInit();
|
|
||||||
queryHotFeed('init');
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取推荐
|
// 获取推荐
|
||||||
Future queryHotFeed(type) async {
|
Future queryHotFeed(type) async {
|
||||||
var res = await Request().get(
|
var res = await VideoHttp.hotVideoList(
|
||||||
Api.hotList,
|
pn: _currentPage,
|
||||||
data: {'pn': _currentPage, 'ps': _count},
|
ps: _count,
|
||||||
);
|
);
|
||||||
List<HotVideoItemModel> list = [];
|
if (res['status']) {
|
||||||
for (var i in res.data['data']['list']) {
|
if (type == 'init') {
|
||||||
list.add(HotVideoItemModel.fromJson(i));
|
videoList.value = res['data'];
|
||||||
|
} else if (type == 'onRefresh') {
|
||||||
|
videoList.insertAll(0, res['data']);
|
||||||
|
} else if (type == 'onLoad') {
|
||||||
|
videoList.addAll(res['data']);
|
||||||
|
}
|
||||||
|
_currentPage += 1;
|
||||||
}
|
}
|
||||||
if (type == 'init') {
|
|
||||||
videoList.value = list;
|
|
||||||
} else if (type == 'onRefresh') {
|
|
||||||
videoList.insertAll(0, list);
|
|
||||||
} else if (type == 'onLoad') {
|
|
||||||
videoList.addAll(list);
|
|
||||||
}
|
|
||||||
_currentPage += 1;
|
|
||||||
isLoadingMore = false;
|
isLoadingMore = false;
|
||||||
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 下拉刷新
|
// 下拉刷新
|
||||||
|
|||||||
@ -2,6 +2,8 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:pilipala/common/widgets/animated_dialog.dart';
|
import 'package:pilipala/common/widgets/animated_dialog.dart';
|
||||||
import 'package:pilipala/common/widgets/overlay_pop.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/common/widgets/video_card_h.dart';
|
||||||
import 'package:pilipala/pages/hot/controller.dart';
|
import 'package:pilipala/pages/hot/controller.dart';
|
||||||
import 'package:pilipala/pages/home/widgets/app_bar.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 {
|
class _HotPageState extends State<HotPage> with AutomaticKeepAliveClientMixin {
|
||||||
final HotController _hotController = Get.put(HotController());
|
final HotController _hotController = Get.put(HotController());
|
||||||
List videoList = [];
|
List videoList = [];
|
||||||
|
Future? _futureBuilderFuture;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool get wantKeepAlive => true;
|
bool get wantKeepAlive => true;
|
||||||
@ -23,11 +26,7 @@ class _HotPageState extends State<HotPage> with AutomaticKeepAliveClientMixin {
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_hotController.videoList.listen((value) {
|
_futureBuilderFuture = _hotController.queryHotFeed('init');
|
||||||
videoList = value;
|
|
||||||
setState(() {});
|
|
||||||
});
|
|
||||||
|
|
||||||
_hotController.scrollController.addListener(
|
_hotController.scrollController.addListener(
|
||||||
() {
|
() {
|
||||||
if (_hotController.scrollController.position.pixels >=
|
if (_hotController.scrollController.position.pixels >=
|
||||||
@ -54,20 +53,46 @@ class _HotPageState extends State<HotPage> with AutomaticKeepAliveClientMixin {
|
|||||||
controller: _hotController.scrollController,
|
controller: _hotController.scrollController,
|
||||||
slivers: [
|
slivers: [
|
||||||
const HomeAppBar(),
|
const HomeAppBar(),
|
||||||
SliverList(
|
FutureBuilder(
|
||||||
delegate: SliverChildBuilderDelegate((context, index) {
|
future: _futureBuilderFuture,
|
||||||
return VideoCardH(
|
builder: (context, snapshot) {
|
||||||
videoItem: videoList[index],
|
if (snapshot.connectionState == ConnectionState.done) {
|
||||||
longPress: () {
|
Map data = snapshot.data as Map;
|
||||||
_hotController.popupDialog =
|
if (data['status']) {
|
||||||
_createPopupDialog(videoList[index]);
|
return Obx(
|
||||||
Overlay.of(context).insert(_hotController.popupDialog!);
|
() => SliverList(
|
||||||
},
|
delegate: SliverChildBuilderDelegate((context, index) {
|
||||||
longPressEnd: () {
|
return VideoCardH(
|
||||||
_hotController.popupDialog?.remove();
|
videoItem: _hotController.videoList[index],
|
||||||
},
|
longPress: () {
|
||||||
);
|
_hotController.popupDialog = _createPopupDialog(
|
||||||
}, childCount: videoList.length)),
|
_hotController.videoList[index]);
|
||||||
|
Overlay.of(context)
|
||||||
|
.insert(_hotController.popupDialog!);
|
||||||
|
},
|
||||||
|
longPressEnd: () {
|
||||||
|
_hotController.popupDialog?.remove();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}, 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(
|
SliverToBoxAdapter(
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
height: MediaQuery.of(context).padding.bottom + 10,
|
height: MediaQuery.of(context).padding.bottom + 10,
|
||||||
|
|||||||
@ -1,30 +1,97 @@
|
|||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:flutter/material.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/home/view.dart';
|
||||||
import 'package:pilipala/pages/hot/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 {
|
class MainController extends GetxController {
|
||||||
List<Widget> pages = <Widget>[
|
List<Widget> pages = <Widget>[
|
||||||
const HomePage(),
|
const HomePage(),
|
||||||
const HotPage(),
|
const HotPage(),
|
||||||
const MinePage(),
|
const MediaPage(),
|
||||||
];
|
];
|
||||||
List navigationBars = [
|
RxList navigationBars = [
|
||||||
{
|
{
|
||||||
'icon': const Icon(Icons.home_outlined),
|
// 'icon': const Icon(Icons.home_outlined),
|
||||||
'selectedIcon': const Icon(Icons.home),
|
// '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': "推荐",
|
'label': "推荐",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'icon': const Icon(Icons.whatshot_outlined),
|
// 'icon': const Icon(Icons.whatshot_outlined),
|
||||||
'selectedIcon': const Icon(Icons.whatshot_rounded),
|
// 'selectedIcon': const Icon(Icons.whatshot_rounded),
|
||||||
|
'icon': const Icon(
|
||||||
|
CupertinoIcons.flame,
|
||||||
|
size: 20,
|
||||||
|
),
|
||||||
|
'selectedIcon': const Icon(
|
||||||
|
CupertinoIcons.flame_fill,
|
||||||
|
size: 20,
|
||||||
|
),
|
||||||
'label': "热门",
|
'label': "热门",
|
||||||
},
|
},
|
||||||
|
// {
|
||||||
|
// 'icon': const Icon(
|
||||||
|
// CupertinoIcons.person,
|
||||||
|
// size: 21,
|
||||||
|
// ),
|
||||||
|
// 'selectedIcon': const Icon(
|
||||||
|
// CupertinoIcons.person_fill,
|
||||||
|
// size: 21,
|
||||||
|
// ),
|
||||||
|
// 'label': "我的",
|
||||||
|
// },
|
||||||
{
|
{
|
||||||
'icon': const Icon(Icons.person_outline),
|
// 'icon': const Icon(Icons.person_outline),
|
||||||
'selectedIcon': const Icon(Icons.person),
|
// 'selectedIcon': const Icon(Icons.person),
|
||||||
'label': "我的",
|
'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 MainController _mainController = Get.put(MainController());
|
||||||
final HomeController _homeController = Get.put(HomeController());
|
final HomeController _homeController = Get.put(HomeController());
|
||||||
final HotController _hotController = Get.put(HotController());
|
final HotController _hotController = Get.put(HotController());
|
||||||
|
PageController? _pageController;
|
||||||
|
|
||||||
late AnimationController? _animationController;
|
late AnimationController? _animationController;
|
||||||
late Animation<double>? _fadeAnimation;
|
late Animation<double>? _fadeAnimation;
|
||||||
@ -36,6 +37,7 @@ class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
|
|||||||
_slideAnimation =
|
_slideAnimation =
|
||||||
Tween(begin: 0.8, end: 1.0).animate(_animationController!);
|
Tween(begin: 0.8, end: 1.0).animate(_animationController!);
|
||||||
_lastSelectTime = DateTime.now().millisecondsSinceEpoch;
|
_lastSelectTime = DateTime.now().millisecondsSinceEpoch;
|
||||||
|
_pageController = PageController(initialPage: selectedIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
void setIndex(int value) async {
|
void setIndex(int value) async {
|
||||||
@ -47,7 +49,7 @@ class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
|
|||||||
});
|
});
|
||||||
setState(() {});
|
setState(() {});
|
||||||
}
|
}
|
||||||
|
_pageController!.jumpToPage(value);
|
||||||
var currentPage = _mainController.pages[value];
|
var currentPage = _mainController.pages[value];
|
||||||
if (currentPage is HomePage) {
|
if (currentPage is HomePage) {
|
||||||
if (_homeController.flag) {
|
if (_homeController.flag) {
|
||||||
@ -98,23 +100,30 @@ class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
|
|||||||
reverseCurve: Curves.linear,
|
reverseCurve: Curves.linear,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: IndexedStack(
|
child: PageView(
|
||||||
index: selectedIndex,
|
controller: _pageController,
|
||||||
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
|
onPageChanged: (index) {
|
||||||
|
selectedIndex = index;
|
||||||
|
setState(() {});
|
||||||
|
},
|
||||||
children: _mainController.pages,
|
children: _mainController.pages,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
bottomNavigationBar: NavigationBar(
|
bottomNavigationBar: Obx(
|
||||||
elevation: 1,
|
() => NavigationBar(
|
||||||
destinations: _mainController.navigationBars.map((e) {
|
elevation: 1,
|
||||||
return NavigationDestination(
|
destinations: _mainController.navigationBars.map((e) {
|
||||||
icon: e['icon'],
|
return NavigationDestination(
|
||||||
selectedIcon: e['selectedIcon'],
|
icon: e['icon'],
|
||||||
label: e['label'],
|
selectedIcon: e['selectedIcon'],
|
||||||
);
|
label: e['label'],
|
||||||
}).toList(),
|
);
|
||||||
selectedIndex: selectedIndex,
|
}).toList(),
|
||||||
onDestinationSelected: (value) => setIndex(value),
|
selectedIndex: selectedIndex,
|
||||||
|
onDestinationSelected: (value) => setIndex(value),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||