Merge pull request #2 from guozhigq/feature-videoReply
Feature video reply
This commit is contained in:
@ -51,6 +51,7 @@ android {
|
||||
targetSdkVersion flutter.targetSdkVersion
|
||||
versionCode flutterVersionCode.toInteger()
|
||||
versionName flutterVersionName
|
||||
minSdkVersion 17
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
|
BIN
assets/fonts/fansCard.ttf
Normal file
BIN
assets/fonts/fansCard.ttf
Normal file
Binary file not shown.
@ -2,46 +2,90 @@ PODS:
|
||||
- connectivity_plus (0.0.1):
|
||||
- Flutter
|
||||
- ReachabilitySwift
|
||||
- device_info_plus (0.0.1):
|
||||
- Flutter
|
||||
- Flutter (1.0.0)
|
||||
- flutter_inappwebview (0.0.1):
|
||||
- Flutter
|
||||
- flutter_inappwebview/Core (= 0.0.1)
|
||||
- OrderedSet (~> 5.0)
|
||||
- flutter_inappwebview/Core (0.0.1):
|
||||
- Flutter
|
||||
- OrderedSet (~> 5.0)
|
||||
- FMDB (2.7.5):
|
||||
- FMDB/standard (= 2.7.5)
|
||||
- FMDB/standard (2.7.5)
|
||||
- image_gallery_saver (1.5.0):
|
||||
- Flutter
|
||||
- OrderedSet (5.0.0)
|
||||
- path_provider_foundation (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- permission_handler_apple (9.0.4):
|
||||
- Flutter
|
||||
- ReachabilitySwift (5.0.0)
|
||||
- share_plus (0.0.1):
|
||||
- Flutter
|
||||
- sqflite (0.0.2):
|
||||
- Flutter
|
||||
- FMDB (>= 2.7.5)
|
||||
- url_launcher_ios (0.0.1):
|
||||
- Flutter
|
||||
|
||||
DEPENDENCIES:
|
||||
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`)
|
||||
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
|
||||
- Flutter (from `Flutter`)
|
||||
- flutter_inappwebview (from `.symlinks/plugins/flutter_inappwebview/ios`)
|
||||
- image_gallery_saver (from `.symlinks/plugins/image_gallery_saver/ios`)
|
||||
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/ios`)
|
||||
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
|
||||
- share_plus (from `.symlinks/plugins/share_plus/ios`)
|
||||
- sqflite (from `.symlinks/plugins/sqflite/ios`)
|
||||
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
||||
|
||||
SPEC REPOS:
|
||||
trunk:
|
||||
- FMDB
|
||||
- OrderedSet
|
||||
- ReachabilitySwift
|
||||
|
||||
EXTERNAL SOURCES:
|
||||
connectivity_plus:
|
||||
:path: ".symlinks/plugins/connectivity_plus/ios"
|
||||
device_info_plus:
|
||||
:path: ".symlinks/plugins/device_info_plus/ios"
|
||||
Flutter:
|
||||
:path: Flutter
|
||||
flutter_inappwebview:
|
||||
:path: ".symlinks/plugins/flutter_inappwebview/ios"
|
||||
image_gallery_saver:
|
||||
:path: ".symlinks/plugins/image_gallery_saver/ios"
|
||||
path_provider_foundation:
|
||||
:path: ".symlinks/plugins/path_provider_foundation/ios"
|
||||
permission_handler_apple:
|
||||
:path: ".symlinks/plugins/permission_handler_apple/ios"
|
||||
share_plus:
|
||||
:path: ".symlinks/plugins/share_plus/ios"
|
||||
sqflite:
|
||||
:path: ".symlinks/plugins/sqflite/ios"
|
||||
url_launcher_ios:
|
||||
:path: ".symlinks/plugins/url_launcher_ios/ios"
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
connectivity_plus: 413a8857dd5d9f1c399a39130850d02fe0feaf7e
|
||||
device_info_plus: e5c5da33f982a436e103237c0c85f9031142abed
|
||||
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
|
||||
flutter_inappwebview: bfd58618f49dc62f2676de690fc6dcda1d6c3721
|
||||
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
|
||||
image_gallery_saver: 259eab68fb271cfd57d599904f7acdc7832e7ef2
|
||||
OrderedSet: aaeb196f7fef5a9edf55d89760da9176ad40b93c
|
||||
path_provider_foundation: c68054786f1b4f3343858c1e1d0caaded73f0be9
|
||||
permission_handler_apple: 44366e37eaf29454a1e7b1b7d736c2cceaeb17ce
|
||||
ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825
|
||||
share_plus: 056a1e8ac890df3e33cb503afffaf1e9b4fbae68
|
||||
sqflite: 6d358c025f5b867b29ed92fc697fd34924e11904
|
||||
url_launcher_ios: 08a3dfac5fb39e8759aeb0abbd5d9480f30fc8b4
|
||||
|
||||
PODFILE CHECKSUM: ef19549a9bc3046e7bb7d2fab4d021637c0c58a3
|
||||
|
||||
|
33
lib/common/widgets/appbar.dart
Normal file
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,
|
||||
);
|
||||
}
|
||||
}
|
@ -30,8 +30,11 @@ class NetworkImgLayer extends StatelessWidget {
|
||||
// double pr = 2;
|
||||
return src != ''
|
||||
? ClipRRect(
|
||||
borderRadius: BorderRadius.circular(
|
||||
type == 'avatar' ? 50 : StyleString.imgRadius.x),
|
||||
borderRadius: BorderRadius.circular(type == 'avatar'
|
||||
? 50
|
||||
: type == 'emote'
|
||||
? 0
|
||||
: StyleString.imgRadius.x),
|
||||
child: CachedNetworkImage(
|
||||
imageUrl: src!,
|
||||
width: width ?? double.infinity,
|
||||
|
@ -16,4 +16,7 @@ class Api {
|
||||
|
||||
// 评论列表
|
||||
static const String replyList = '/x/v2/reply';
|
||||
|
||||
// 楼中楼
|
||||
static const String replyReplyList = '/x/v2/reply/reply';
|
||||
}
|
||||
|
@ -33,4 +33,38 @@ class ReplyHttp {
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
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']] ?? '请求异常',
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -22,10 +22,12 @@ class ReplyData {
|
||||
ReplyData.fromJson(Map<String, dynamic> json) {
|
||||
page = ReplyPage.fromJson(json['page']);
|
||||
config = ReplyConfig.fromJson(json['config']);
|
||||
replies = json['replies']
|
||||
.map<ReplyItemModel>(
|
||||
(item) => ReplyItemModel.fromJson(item, json['upper']['mid']))
|
||||
.toList();
|
||||
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(
|
||||
|
@ -149,6 +149,6 @@ class ReplyControl {
|
||||
entryText = json['sub_reply_entry_text'];
|
||||
titleText = json['sub_reply_title_text'];
|
||||
time = json['time_desc'];
|
||||
location = json['location'];
|
||||
location = json['location'] ?? '';
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import 'dart:convert' as convert;
|
||||
import 'package:get/get.dart';
|
||||
|
||||
class ReplyMember {
|
||||
ReplyMember({
|
||||
@ -22,6 +22,7 @@ class ReplyMember {
|
||||
Map? officialVerify;
|
||||
Map? vip;
|
||||
Map? fansDetail;
|
||||
UserSailing? userSailing;
|
||||
|
||||
ReplyMember.fromJson(Map<String, dynamic> json) {
|
||||
mid = json['mid'];
|
||||
@ -30,9 +31,12 @@ class ReplyMember {
|
||||
avatar = json['avatar'];
|
||||
level = json['level_info']['current_level'];
|
||||
pendant = Pendant.fromJson(json['pendant']);
|
||||
officialVerify = json['officia_vVerify'];
|
||||
officialVerify = json['officia_verify'];
|
||||
vip = json['vip'];
|
||||
fansDetail = json['fans_detail'];
|
||||
userSailing = json['user_sailing'] != null
|
||||
? UserSailing.fromJson(json['user_sailing'])
|
||||
: UserSailing();
|
||||
}
|
||||
}
|
||||
|
||||
@ -53,3 +57,15 @@ class Pendant {
|
||||
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'];
|
||||
}
|
||||
}
|
||||
|
77
lib/pages/preview/controller.dart
Normal file
77
lib/pages/preview/controller.dart
Normal file
@ -0,0 +1,77 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:device_info_plus/device_info_plus.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'dart:typed_data';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
import 'package:image_gallery_saver/image_gallery_saver.dart';
|
||||
import 'package:pilipala/utils/utils.dart';
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
|
||||
class PreviewController extends GetxController {
|
||||
DeviceInfoPlugin deviceInfo = DeviceInfoPlugin();
|
||||
RxInt initialPage = 0.obs;
|
||||
RxInt currentPage = 1.obs;
|
||||
RxList imgList = [].obs;
|
||||
bool storage = true;
|
||||
bool videos = true;
|
||||
bool photos = true;
|
||||
bool visiable = true;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
if (Get.arguments != null) {
|
||||
initialPage.value = Get.arguments['initialPage']!;
|
||||
currentPage.value = Get.arguments['initialPage']! + 1;
|
||||
imgList.value = Get.arguments['imgList'];
|
||||
}
|
||||
}
|
||||
|
||||
requestPermission() async {
|
||||
Map<Permission, PermissionStatus> statuses = await [
|
||||
Permission.storage,
|
||||
// Permission.photos
|
||||
].request();
|
||||
|
||||
final info = statuses[Permission.storage].toString();
|
||||
// final photosInfo = statuses[Permission.photos].toString();
|
||||
|
||||
print('授权状态:$info');
|
||||
}
|
||||
|
||||
// 图片保存
|
||||
void onSaveImg() async {
|
||||
var response = await Dio().get(imgList[initialPage.value],
|
||||
options: Options(responseType: ResponseType.bytes));
|
||||
final result = await ImageGallerySaver.saveImage(
|
||||
Uint8List.fromList(response.data),
|
||||
quality: 100,
|
||||
name: "pic_vvex${DateTime.now().toString().split('-').join()}");
|
||||
if (result != null) {
|
||||
if (result['isSuccess']) {
|
||||
print('已保存到相册');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 图片分享
|
||||
void onShareImg() async {
|
||||
requestPermission();
|
||||
var response = await Dio().get(imgList[initialPage.value],
|
||||
options: Options(responseType: ResponseType.bytes));
|
||||
final temp = await getTemporaryDirectory();
|
||||
String imgName =
|
||||
"pic_vvex${DateTime.now().toString().split('-').join()}.jpg";
|
||||
var path = '${temp.path}/$imgName';
|
||||
File(path).writeAsBytesSync(response.data);
|
||||
Share.shareXFiles([XFile(path)], subject: imgList[initialPage.value]);
|
||||
}
|
||||
|
||||
// 浏览器中查看
|
||||
void onBrowserImg() async {
|
||||
Utils.openURL(imgList[initialPage.value]);
|
||||
}
|
||||
}
|
4
lib/pages/preview/index.dart
Normal file
4
lib/pages/preview/index.dart
Normal file
@ -0,0 +1,4 @@
|
||||
library preview;
|
||||
|
||||
export './controller.dart';
|
||||
export './view.dart';
|
188
lib/pages/preview/view.dart
Normal file
188
lib/pages/preview/view.dart
Normal file
@ -0,0 +1,188 @@
|
||||
import 'package:get/get.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:extended_image/extended_image.dart';
|
||||
import 'package:pilipala/common/widgets/appbar.dart';
|
||||
import 'controller.dart';
|
||||
|
||||
typedef DoubleClickAnimationListener = void Function();
|
||||
|
||||
class ImagePreview extends StatefulWidget {
|
||||
const ImagePreview({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
_ImagePreviewState createState() => _ImagePreviewState();
|
||||
}
|
||||
|
||||
class _ImagePreviewState extends State<ImagePreview>
|
||||
with TickerProviderStateMixin {
|
||||
final PreviewController _previewController = Get.put(PreviewController());
|
||||
late AnimationController animationController;
|
||||
late AnimationController _doubleClickAnimationController;
|
||||
Animation<double>? _doubleClickAnimation;
|
||||
late DoubleClickAnimationListener _doubleClickAnimationListener;
|
||||
List<double> doubleTapScales = <double>[1.0, 2.0];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
animationController = AnimationController(
|
||||
vsync: this, duration: const Duration(milliseconds: 400));
|
||||
_doubleClickAnimationController = AnimationController(
|
||||
duration: const Duration(milliseconds: 250), vsync: this);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
animationController.dispose();
|
||||
_doubleClickAnimationController.dispose();
|
||||
clearGestureDetailsCache();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
extendBodyBehindAppBar: true,
|
||||
appBar: AppBarWidget(
|
||||
controller: animationController,
|
||||
visible: _previewController.visiable,
|
||||
child: AppBar(
|
||||
backgroundColor: Theme.of(context).colorScheme.background,
|
||||
elevation: 0,
|
||||
centerTitle: false,
|
||||
title: Obx(
|
||||
() => Text.rich(
|
||||
TextSpan(children: [
|
||||
TextSpan(text: _previewController.currentPage.toString()),
|
||||
const TextSpan(text: ' / '),
|
||||
TextSpan(text: _previewController.imgList.length.toString()),
|
||||
]),
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
PopupMenuButton(
|
||||
icon: const Icon(Icons.more_vert),
|
||||
tooltip: 'action',
|
||||
itemBuilder: (BuildContext context) => <PopupMenuEntry>[
|
||||
PopupMenuItem(
|
||||
value: 'share',
|
||||
onTap: _previewController.onShareImg,
|
||||
child: const Text('分享'),
|
||||
),
|
||||
PopupMenuItem(
|
||||
value: 'save',
|
||||
onTap: _previewController.onSaveImg,
|
||||
child: const Text('保存'),
|
||||
),
|
||||
PopupMenuItem(
|
||||
value: 'browser',
|
||||
onTap: _previewController.onBrowserImg,
|
||||
child: const Text('浏览器中查看'),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
body: GestureDetector(
|
||||
onTap: () {
|
||||
_previewController.visiable = !_previewController.visiable;
|
||||
setState(() {});
|
||||
},
|
||||
child: ExtendedImageGesturePageView.builder(
|
||||
controller: ExtendedPageController(
|
||||
initialPage: _previewController.initialPage.value,
|
||||
pageSpacing: 0,
|
||||
),
|
||||
onPageChanged: (int index) {
|
||||
_previewController.initialPage.value = index;
|
||||
_previewController.currentPage.value = index + 1;
|
||||
},
|
||||
canScrollPage: (GestureDetails? gestureDetails) =>
|
||||
gestureDetails!.totalScale! <= 1.0,
|
||||
preloadPagesCount: 2,
|
||||
itemCount: _previewController.imgList.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
return ExtendedImage.network(
|
||||
_previewController.imgList[index],
|
||||
fit: BoxFit.contain,
|
||||
mode: ExtendedImageMode.gesture,
|
||||
onDoubleTap: (ExtendedImageGestureState state) {
|
||||
final Offset? pointerDownPosition = state.pointerDownPosition;
|
||||
final double? begin = state.gestureDetails!.totalScale;
|
||||
double end;
|
||||
|
||||
//remove old
|
||||
_doubleClickAnimation
|
||||
?.removeListener(_doubleClickAnimationListener);
|
||||
|
||||
//stop pre
|
||||
_doubleClickAnimationController.stop();
|
||||
|
||||
//reset to use
|
||||
_doubleClickAnimationController.reset();
|
||||
|
||||
if (begin == doubleTapScales[0]) {
|
||||
end = doubleTapScales[1];
|
||||
} else {
|
||||
end = doubleTapScales[0];
|
||||
}
|
||||
|
||||
_doubleClickAnimationListener = () {
|
||||
state.handleDoubleTap(
|
||||
scale: _doubleClickAnimation!.value,
|
||||
doubleTapPosition: pointerDownPosition);
|
||||
};
|
||||
_doubleClickAnimation = _doubleClickAnimationController
|
||||
.drive(Tween<double>(begin: begin, end: end));
|
||||
|
||||
_doubleClickAnimation!
|
||||
.addListener(_doubleClickAnimationListener);
|
||||
|
||||
_doubleClickAnimationController.forward();
|
||||
},
|
||||
loadStateChanged: (ExtendedImageState state) {
|
||||
if (state.extendedImageLoadState == LoadState.loading) {
|
||||
final ImageChunkEvent? loadingProgress =
|
||||
state.loadingProgress;
|
||||
final double? progress =
|
||||
loadingProgress?.expectedTotalBytes != null
|
||||
? loadingProgress!.cumulativeBytesLoaded /
|
||||
loadingProgress.expectedTotalBytes!
|
||||
: null;
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
SizedBox(
|
||||
width: 150.0,
|
||||
child: LinearProgressIndicator(value: progress),
|
||||
),
|
||||
const SizedBox(height: 10.0),
|
||||
Text('${((progress ?? 0.0) * 100).toInt()}%'),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
initGestureConfigHandler: (ExtendedImageState state) {
|
||||
return GestureConfig(
|
||||
inPageView: true,
|
||||
initialScale: 1.0,
|
||||
maxScale: 5.0,
|
||||
animationMaxScale: 6.0,
|
||||
initialAlignment: InitialAlignment.center,
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
floatingActionButton: FloatingActionButton(
|
||||
onPressed: () => _previewController.onSaveImg(),
|
||||
child: const Icon(Icons.save_alt_rounded),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,22 +1,80 @@
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:pilipala/http/reply.dart';
|
||||
import 'package:pilipala/models/video/reply/data.dart';
|
||||
import 'package:pilipala/models/video/reply/item.dart';
|
||||
|
||||
class VideoReplyController extends GetxController {
|
||||
// 视频aid
|
||||
String aid = Get.parameters['aid']!;
|
||||
VideoReplyController(
|
||||
this.aid,
|
||||
this.rpid,
|
||||
this.level,
|
||||
);
|
||||
final ScrollController scrollController = ScrollController();
|
||||
// 视频aid 请求时使用的oid
|
||||
String? aid;
|
||||
// 层级 2为楼中楼
|
||||
String? level;
|
||||
// rpid 请求楼中楼回复
|
||||
String? rpid;
|
||||
RxList<ReplyItemModel> replyList = [ReplyItemModel()].obs;
|
||||
// 当前页
|
||||
int currentPage = 0;
|
||||
bool isLoadingMore = false;
|
||||
RxBool noMore = false.obs;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
queryReplyList();
|
||||
}
|
||||
|
||||
Future queryReplyList() async {
|
||||
var res = await ReplyHttp.replyList(oid: aid, pageNum: 1, type: 1);
|
||||
Future queryReplyList({type = 'init'}) async {
|
||||
isLoadingMore = true;
|
||||
var res = level == '1'
|
||||
? await ReplyHttp.replyList(
|
||||
oid: aid!, pageNum: currentPage + 1, type: 1)
|
||||
: await ReplyHttp.replyReplyList(
|
||||
oid: aid!, root: rpid!, pageNum: currentPage + 1, type: 1);
|
||||
if (res['status']) {
|
||||
res['data'] = ReplyData.fromJson(res['data']);
|
||||
if (res['data'].replies.isNotEmpty) {
|
||||
currentPage = currentPage + 1;
|
||||
noMore.value = false;
|
||||
} else {
|
||||
if (currentPage == 0) {
|
||||
} else {
|
||||
noMore.value = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (res['data'].replies.length >= res['data'].page.count) {
|
||||
noMore.value = true;
|
||||
}
|
||||
if (type == 'init') {
|
||||
List<ReplyItemModel> replies = res['data'].replies;
|
||||
// 添加置顶回复
|
||||
if (res['data'].upper.top != null) {
|
||||
bool flag = false;
|
||||
for (var i = 0; i < res['data'].topReplies.length; i++) {
|
||||
if (res['data'].topReplies[i].rpid == res['data'].upper.top.rpid) {
|
||||
flag = true;
|
||||
}
|
||||
}
|
||||
if (!flag) {
|
||||
replies.insert(0, res['data'].upper.top);
|
||||
}
|
||||
}
|
||||
replies.insertAll(0, res['data'].topReplies);
|
||||
res['data'].replies = replies;
|
||||
replyList.value = res['data'].replies!;
|
||||
} else {
|
||||
replyList.addAll(res['data'].replies!);
|
||||
res['data'].replies.addAll(replyList);
|
||||
}
|
||||
}
|
||||
isLoadingMore = false;
|
||||
return res;
|
||||
}
|
||||
|
||||
// 上拉加载
|
||||
Future onLoad() async {
|
||||
queryReplyList(type: 'onLoad');
|
||||
}
|
||||
}
|
||||
|
@ -7,7 +7,15 @@ import 'controller.dart';
|
||||
import 'widgets/reply_item.dart';
|
||||
|
||||
class VideoReplyPanel extends StatefulWidget {
|
||||
const VideoReplyPanel({super.key});
|
||||
int oid;
|
||||
int rpid;
|
||||
String level;
|
||||
VideoReplyPanel({
|
||||
this.oid = 0,
|
||||
this.rpid = 0,
|
||||
this.level = '',
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
State<VideoReplyPanel> createState() => _VideoReplyPanelState();
|
||||
@ -15,66 +23,108 @@ class VideoReplyPanel extends StatefulWidget {
|
||||
|
||||
class _VideoReplyPanelState extends State<VideoReplyPanel>
|
||||
with AutomaticKeepAliveClientMixin {
|
||||
final VideoReplyController _videoReplyController =
|
||||
Get.put(VideoReplyController(), tag: Get.arguments['heroTag']);
|
||||
late VideoReplyController _videoReplyController;
|
||||
|
||||
// List<ReplyItemModel>? replyList;
|
||||
Future? _futureBuilderFuture;
|
||||
// 添加页面缓存
|
||||
@override
|
||||
bool get wantKeepAlive => true;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FutureBuilder(
|
||||
future: _videoReplyController.queryReplyList(),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.done) {
|
||||
if (snapshot.data['status']) {
|
||||
List<ReplyItemModel> replies = snapshot.data['data'].replies;
|
||||
// 添加置顶回复
|
||||
if (snapshot.data['data'].upper.top != null) {
|
||||
bool flag = false;
|
||||
for (var i = 0;
|
||||
i < snapshot.data['data'].topReplies.length;
|
||||
i++) {
|
||||
if (snapshot.data['data'].topReplies[i].rpid ==
|
||||
snapshot.data['data'].upper.top.rpid) {
|
||||
flag = true;
|
||||
}
|
||||
}
|
||||
if (!flag) {
|
||||
replies.insert(0, snapshot.data['data'].upper.top);
|
||||
}
|
||||
}
|
||||
void initState() {
|
||||
super.initState();
|
||||
if (widget.level == '2') {
|
||||
_videoReplyController = Get.put(
|
||||
VideoReplyController(
|
||||
widget.oid.toString(), widget.rpid.toString(), '2'),
|
||||
tag: widget.rpid.toString());
|
||||
} else {
|
||||
_videoReplyController = Get.put(
|
||||
VideoReplyController(Get.parameters['aid']!, '', '1'),
|
||||
tag: Get.arguments['heroTag']);
|
||||
}
|
||||
|
||||
replies.insertAll(0, snapshot.data['data'].topReplies);
|
||||
// 请求成功
|
||||
return SliverList(
|
||||
delegate: SliverChildBuilderDelegate((context, index) {
|
||||
if (index == replies.length) {
|
||||
return SizedBox(height: MediaQuery.of(context).padding.bottom);
|
||||
} else {
|
||||
return ReplyItem(
|
||||
replyItem: replies[index],
|
||||
isUp:
|
||||
replies[index].mid == snapshot.data['data'].upper.mid);
|
||||
}
|
||||
}, childCount: replies.length + 1));
|
||||
} else {
|
||||
// 请求错误
|
||||
return HttpError(
|
||||
errMsg: snapshot.data['msg'],
|
||||
fn: () => setState(() {}),
|
||||
);
|
||||
_futureBuilderFuture = _videoReplyController.queryReplyList();
|
||||
_videoReplyController.scrollController.addListener(
|
||||
() {
|
||||
if (_videoReplyController.scrollController.position.pixels >=
|
||||
_videoReplyController.scrollController.position.maxScrollExtent -
|
||||
300) {
|
||||
if (!_videoReplyController.isLoadingMore) {
|
||||
_videoReplyController.onLoad();
|
||||
}
|
||||
} else {
|
||||
// 骨架屏
|
||||
return SliverList(
|
||||
delegate: SliverChildBuilderDelegate((context, index) {
|
||||
return const VideoCardHSkeleton();
|
||||
}, childCount: 5),
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return RefreshIndicator(
|
||||
onRefresh: () async {
|
||||
setState(() {});
|
||||
_videoReplyController.currentPage = 0;
|
||||
return await _videoReplyController.queryReplyList();
|
||||
},
|
||||
child: CustomScrollView(
|
||||
controller: _videoReplyController.scrollController,
|
||||
key: const PageStorageKey<String>('评论'),
|
||||
slivers: <Widget>[
|
||||
FutureBuilder(
|
||||
future: _futureBuilderFuture,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.done) {
|
||||
Map data = snapshot.data as Map;
|
||||
if (data['status']) {
|
||||
// 请求成功
|
||||
return Obx(
|
||||
() => SliverList(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(context, index) {
|
||||
if (index == _videoReplyController.replyList.length) {
|
||||
return Container(
|
||||
padding: EdgeInsets.only(
|
||||
bottom:
|
||||
MediaQuery.of(context).padding.bottom),
|
||||
height:
|
||||
MediaQuery.of(context).padding.bottom + 60,
|
||||
child: Center(
|
||||
child: Obx(() => Text(
|
||||
_videoReplyController.noMore.value
|
||||
? '没有更多了'
|
||||
: '加载中')),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return ReplyItem(
|
||||
replyItem: _videoReplyController.replyList[index],
|
||||
);
|
||||
}
|
||||
},
|
||||
childCount: _videoReplyController.replyList.length + 1,
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
// 请求错误
|
||||
return HttpError(
|
||||
errMsg: data['msg'],
|
||||
fn: () => setState(() {}),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// 骨架屏
|
||||
return SliverList(
|
||||
delegate: SliverChildBuilderDelegate((context, index) {
|
||||
return const VideoCardHSkeleton();
|
||||
}, childCount: 5),
|
||||
);
|
||||
}
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,14 @@
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
||||
import 'package:pilipala/models/video/reply/item.dart';
|
||||
import 'package:pilipala/pages/video/detail/reply/index.dart';
|
||||
import 'package:pilipala/utils/utils.dart';
|
||||
|
||||
class ReplyItem extends StatelessWidget {
|
||||
ReplyItem({super.key, this.replyItem, required this.isUp});
|
||||
ReplyItem({super.key, this.replyItem});
|
||||
ReplyItemModel? replyItem;
|
||||
bool isUp = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -16,11 +17,13 @@ class ReplyItem extends StatelessWidget {
|
||||
child: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(12, 6, 8, 0),
|
||||
padding: const EdgeInsets.fromLTRB(12, 8, 8, 2),
|
||||
child: content(context),
|
||||
),
|
||||
Divider(
|
||||
height: 1,
|
||||
indent: 52,
|
||||
endIndent: 10,
|
||||
color: Theme.of(context).dividerColor.withOpacity(0.08),
|
||||
)
|
||||
],
|
||||
@ -30,14 +33,42 @@ class ReplyItem extends StatelessWidget {
|
||||
|
||||
Widget lfAvtar(context) {
|
||||
return Container(
|
||||
margin: const EdgeInsets.only(top: 5),
|
||||
child: NetworkImgLayer(
|
||||
src: replyItem!.member!.avatar,
|
||||
width: 30,
|
||||
height: 30,
|
||||
type: 'avatar',
|
||||
),
|
||||
);
|
||||
margin: const EdgeInsets.only(top: 5),
|
||||
child: Stack(
|
||||
children: [
|
||||
NetworkImgLayer(
|
||||
src: replyItem!.member!.avatar,
|
||||
width: 34,
|
||||
height: 34,
|
||||
type: 'avatar',
|
||||
),
|
||||
if (replyItem!.member!.officialVerify != null &&
|
||||
replyItem!.member!.officialVerify!['type'] == 0)
|
||||
Positioned(
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(7),
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
),
|
||||
child: Icon(
|
||||
Icons.offline_bolt,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
size: 16,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
// child:
|
||||
// NetworkImgLayer(
|
||||
// src: replyItem!.member!.avatar,
|
||||
// width: 30,
|
||||
// height: 30,
|
||||
// type: 'avatar',
|
||||
// ),
|
||||
);
|
||||
}
|
||||
|
||||
Widget content(context) {
|
||||
@ -45,51 +76,80 @@ class ReplyItem extends StatelessWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
// 头像、昵称
|
||||
Row(
|
||||
// 两端对齐
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: <Widget>[
|
||||
GestureDetector(
|
||||
// onTap: () =>
|
||||
// Get.toNamed('/member/${reply.userName}', parameters: {
|
||||
// 'memberAvatar': reply.avatar,
|
||||
// 'heroTag': reply.userName + heroTag,
|
||||
// }),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
lfAvtar(context),
|
||||
const SizedBox(width: 12),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
GestureDetector(
|
||||
// onTap: () =>
|
||||
// Get.toNamed('/member/${reply.userName}', parameters: {
|
||||
// 'memberAvatar': reply.avatar,
|
||||
// 'heroTag': reply.userName + heroTag,
|
||||
// }),
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
decoration: BoxDecoration(
|
||||
image: replyItem!.member!.userSailing!.cardbg != null
|
||||
? DecorationImage(
|
||||
fit: BoxFit.cover,
|
||||
image: NetworkImage(
|
||||
replyItem!.member!.userSailing!.cardbg!['image'],
|
||||
),
|
||||
)
|
||||
: null,
|
||||
),
|
||||
child: Stack(
|
||||
children: [
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
lfAvtar(context),
|
||||
const SizedBox(width: 12),
|
||||
Text(
|
||||
replyItem!.member!.uname!,
|
||||
style: TextStyle(
|
||||
color: replyItem!.isUp! ||
|
||||
replyItem!.member!.vip!['vipType'] > 0
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Theme.of(context).colorScheme.outline,
|
||||
fontSize:
|
||||
Theme.of(context).textTheme.titleSmall!.fontSize,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
Image.asset(
|
||||
'assets/images/lv/lv${replyItem!.member!.level}.png',
|
||||
height: 11,
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
if (replyItem!.isUp!) UpTag(),
|
||||
],
|
||||
),
|
||||
if (replyItem!.member!.userSailing!.cardbg != null &&
|
||||
replyItem!.member!.userSailing!.cardbg!['fan']['number'] >
|
||||
0)
|
||||
Positioned(
|
||||
top: 8,
|
||||
left: Get.size.width / 7 * 5.6,
|
||||
child: DefaultTextStyle(
|
||||
style: TextStyle(
|
||||
fontFamily: 'fansCard',
|
||||
fontSize: 9,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Text('NO.'),
|
||||
Text(
|
||||
replyItem!.member!.uname!,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.titleSmall!
|
||||
.copyWith(
|
||||
color: replyItem!.isUp!
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Theme.of(context).colorScheme.outline,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
Image.asset(
|
||||
'assets/images/lv/lv${replyItem!.member!.level}.png',
|
||||
height: 13,
|
||||
replyItem!.member!.userSailing!.cardbg!['fan']
|
||||
['num_desc'],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
// title
|
||||
Container(
|
||||
@ -102,6 +162,8 @@ class ReplyItem extends StatelessWidget {
|
||||
style: const TextStyle(height: 1.65),
|
||||
TextSpan(
|
||||
children: [
|
||||
if (replyItem!.isTop!)
|
||||
WidgetSpan(child: UpTag(tagText: '置顶')),
|
||||
buildContent(context, replyItem!.content!),
|
||||
],
|
||||
),
|
||||
@ -116,6 +178,7 @@ class ReplyItem extends StatelessWidget {
|
||||
child: ReplyItemRow(
|
||||
replies: replyItem!.replies,
|
||||
replyControl: replyItem!.replyControl,
|
||||
f_rpid: replyItem!.rpid,
|
||||
),
|
||||
),
|
||||
],
|
||||
@ -136,26 +199,9 @@ class ReplyItem extends StatelessWidget {
|
||||
.labelMedium!
|
||||
.copyWith(color: Theme.of(context).colorScheme.outline),
|
||||
),
|
||||
if (replyItem!.isTop!) ...[
|
||||
Text(
|
||||
' • 置顶',
|
||||
style: TextStyle(
|
||||
fontSize: Theme.of(context).textTheme.labelMedium!.fontSize,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
),
|
||||
],
|
||||
if (replyControl!.isUpTop!) ...[
|
||||
Text(
|
||||
' • 超赞',
|
||||
style: TextStyle(
|
||||
fontSize: Theme.of(context).textTheme.labelMedium!.fontSize,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
),
|
||||
// const SizedBox(width: 4),
|
||||
],
|
||||
const Spacer(),
|
||||
if (replyControl!.isUpTop!)
|
||||
Icon(Icons.favorite, color: Colors.red[400], size: 18),
|
||||
SizedBox(
|
||||
height: 35,
|
||||
child: TextButton(
|
||||
@ -191,9 +237,11 @@ class ReplyItemRow extends StatelessWidget {
|
||||
super.key,
|
||||
this.replies,
|
||||
this.replyControl,
|
||||
this.f_rpid,
|
||||
});
|
||||
List? replies;
|
||||
ReplyControl? replyControl;
|
||||
int? f_rpid;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -202,7 +250,7 @@ class ReplyItemRow extends StatelessWidget {
|
||||
return Container(
|
||||
margin: const EdgeInsets.only(left: 42, right: 4, top: 0),
|
||||
child: Material(
|
||||
color: Theme.of(context).colorScheme.onInverseSurface.withOpacity(0.7),
|
||||
color: Theme.of(context).colorScheme.onInverseSurface,
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
clipBehavior: Clip.hardEdge,
|
||||
animationDuration: Duration.zero,
|
||||
@ -215,7 +263,7 @@ class ReplyItemRow extends StatelessWidget {
|
||||
if (extraRow == 1 && index == replies!.length) {
|
||||
// 有楼中楼回复,在最后显示
|
||||
return InkWell(
|
||||
onTap: () {},
|
||||
onTap: () => replyReply(context),
|
||||
child: Padding(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(vertical: 8, horizontal: 8),
|
||||
@ -243,23 +291,23 @@ class ReplyItemRow extends StatelessWidget {
|
||||
return InkWell(
|
||||
onTap: () {},
|
||||
child: Padding(
|
||||
padding: EdgeInsets.fromLTRB(8, index == 0 ? 8 : 4, 8, 4),
|
||||
padding: EdgeInsets.fromLTRB(
|
||||
8,
|
||||
index == 0 && (extraRow == 1 || replies!.length > 1)
|
||||
? 8
|
||||
: 5,
|
||||
8,
|
||||
5),
|
||||
child: Text.rich(
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 2,
|
||||
overflow: extraRow == 1
|
||||
? TextOverflow.ellipsis
|
||||
: TextOverflow.visible,
|
||||
maxLines: extraRow == 1 ? 2 : null,
|
||||
TextSpan(
|
||||
children: [
|
||||
if (replies![index].isUp)
|
||||
TextSpan(
|
||||
text: 'UP • ',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: Theme.of(context)
|
||||
.textTheme
|
||||
.labelMedium!
|
||||
.fontSize,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
WidgetSpan(
|
||||
child: UpTag(),
|
||||
),
|
||||
TextSpan(
|
||||
text: replies![index].member.uname + ' ',
|
||||
@ -286,6 +334,48 @@ class ReplyItemRow extends StatelessWidget {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void replyReply(context) {
|
||||
Get.bottomSheet(
|
||||
barrierColor: Colors.transparent,
|
||||
useRootNavigator: true,
|
||||
isScrollControlled: true,
|
||||
Container(
|
||||
height: Get.size.height - Get.size.width * 9 / 16 - 50,
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
child: Column(
|
||||
children: [
|
||||
AppBar(
|
||||
automaticallyImplyLeading: false,
|
||||
centerTitle: false,
|
||||
title: Text(
|
||||
'评论详情',
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.close),
|
||||
onPressed: () async {
|
||||
await Future.delayed(const Duration(milliseconds: 200));
|
||||
Get.back();
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
Expanded(
|
||||
child: VideoReplyPanel(
|
||||
oid: replies!.first.oid,
|
||||
rpid: f_rpid!,
|
||||
level: '2',
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
persistent: false,
|
||||
backgroundColor: Theme.of(context).bottomSheetTheme.backgroundColor,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
InlineSpan buildContent(BuildContext context, content) {
|
||||
@ -301,14 +391,16 @@ InlineSpan buildContent(BuildContext context, content) {
|
||||
RegExp(r"\[.*?\]"),
|
||||
onMatch: (Match match) {
|
||||
String matchStr = match[0]!;
|
||||
int size = content.emote[matchStr]['meta']['size'];
|
||||
if (content.emote.isNotEmpty) {
|
||||
if (content.emote.keys.contains(matchStr)) {
|
||||
spanChilds.add(
|
||||
WidgetSpan(
|
||||
child: NetworkImgLayer(
|
||||
src: content.emote[matchStr]['url'],
|
||||
width: 20,
|
||||
height: 20,
|
||||
type: 'emote',
|
||||
width: size * 20,
|
||||
height: size * 20,
|
||||
),
|
||||
),
|
||||
);
|
||||
@ -394,26 +486,95 @@ InlineSpan buildContent(BuildContext context, content) {
|
||||
|
||||
// 图片渲染
|
||||
if (content.pictures.isNotEmpty) {
|
||||
spanChilds.add(
|
||||
const TextSpan(text: '\n'),
|
||||
);
|
||||
for (var i = 0; i < content.pictures.length; i++) {
|
||||
spanChilds.add(
|
||||
WidgetSpan(
|
||||
child: SizedBox(
|
||||
height: 180,
|
||||
child: NetworkImgLayer(
|
||||
src: content.pictures[i]['img_src'],
|
||||
width: 200,
|
||||
height: 200 *
|
||||
content.pictures[i]['img_height'] /
|
||||
content.pictures[i]['img_width'],
|
||||
),
|
||||
),
|
||||
List<Widget> list = [];
|
||||
List picList = [];
|
||||
int len = content.pictures.length;
|
||||
for (var i = 0; i < len; i++) {
|
||||
picList.add(content.pictures[i]['img_src']);
|
||||
list.add(
|
||||
LayoutBuilder(
|
||||
builder: (context, BoxConstraints box) {
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
Get.toNamed('/preview',
|
||||
arguments: {'initialPage': i, 'imgList': picList});
|
||||
},
|
||||
child: NetworkImgLayer(
|
||||
src: content.pictures[i]['img_src'],
|
||||
width: box.maxWidth,
|
||||
height: box.maxWidth,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
spanChilds.add(
|
||||
WidgetSpan(
|
||||
child: LayoutBuilder(
|
||||
builder: (context, BoxConstraints box) {
|
||||
double maxWidth = box.maxWidth;
|
||||
double crossCount = len < 3 ? 2 : 3;
|
||||
double height = maxWidth /
|
||||
crossCount *
|
||||
(len % crossCount == 0
|
||||
? len ~/ crossCount
|
||||
: len ~/ crossCount + 1) +
|
||||
6;
|
||||
return Container(
|
||||
padding: const EdgeInsets.only(top: 6),
|
||||
height: height,
|
||||
child: GridView(
|
||||
padding: EdgeInsets.zero,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
// 子Item排列规则
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||
//横轴元素个数
|
||||
crossAxisCount: crossCount.toInt(),
|
||||
//纵轴间距
|
||||
mainAxisSpacing: 4.0,
|
||||
//横轴间距
|
||||
crossAxisSpacing: 4.0,
|
||||
//子组件宽高长度比例
|
||||
// childAspectRatio: 1,
|
||||
),
|
||||
//GridView中使用的子Widegt
|
||||
children: list,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
// spanChilds.add(TextSpan(text: matchMember));
|
||||
return TextSpan(children: spanChilds);
|
||||
}
|
||||
|
||||
class UpTag extends StatelessWidget {
|
||||
String? tagText;
|
||||
UpTag({super.key, this.tagText = 'UP'});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
width: tagText == 'UP' ? 28 : 38,
|
||||
height: tagText == 'UP' ? 17 : 19,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(3),
|
||||
// color: Theme.of(context).colorScheme.primary,
|
||||
border: Border.all(color: Theme.of(context).colorScheme.primary)),
|
||||
margin: const EdgeInsets.only(right: 4),
|
||||
// padding: const EdgeInsets.symmetric(vertical: 0.5, horizontal: 4),
|
||||
child: Center(
|
||||
child: Text(
|
||||
tagText!,
|
||||
style: TextStyle(
|
||||
fontSize: Theme.of(context).textTheme.labelSmall!.fontSize,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
import 'package:extended_nested_scroll_view/extended_nested_scroll_view.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
||||
@ -19,6 +20,10 @@ class _VideoDetailPageState extends State<VideoDetailPage> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final double statusBarHeight = MediaQuery.of(context).padding.top;
|
||||
final double pinnedHeaderHeight = statusBarHeight +
|
||||
kToolbarHeight +
|
||||
MediaQuery.of(context).size.width * 9 / 16;
|
||||
return DefaultTabController(
|
||||
initialIndex: videoDetailController.tabInitialIndex,
|
||||
length: videoDetailController.tabs.length, // tab的数量.
|
||||
@ -26,126 +31,115 @@ class _VideoDetailPageState extends State<VideoDetailPage> {
|
||||
top: false,
|
||||
bottom: false,
|
||||
child: Scaffold(
|
||||
body: NestedScrollView(
|
||||
body: ExtendedNestedScrollView(
|
||||
headerSliverBuilder:
|
||||
(BuildContext context, bool innerBoxIsScrolled) {
|
||||
return <Widget>[
|
||||
SliverOverlapAbsorber(
|
||||
handle:
|
||||
NestedScrollView.sliverOverlapAbsorberHandleFor(context),
|
||||
sliver: SliverAppBar(
|
||||
title: const Text("视频详情"),
|
||||
// floating: true,
|
||||
// snap: true,
|
||||
pinned: true,
|
||||
elevation: 0,
|
||||
scrolledUnderElevation: 0,
|
||||
forceElevated: innerBoxIsScrolled,
|
||||
expandedHeight: MediaQuery.of(context).size.width * 9 / 16,
|
||||
collapsedHeight: MediaQuery.of(context).size.width * 9 / 16,
|
||||
toolbarHeight: kToolbarHeight,
|
||||
flexibleSpace: FlexibleSpaceBar(
|
||||
background: Padding(
|
||||
padding: EdgeInsets.only(
|
||||
bottom: 50,
|
||||
top: MediaQuery.of(context).padding.top),
|
||||
child: LayoutBuilder(
|
||||
builder: (context, boxConstraints) {
|
||||
double maxWidth = boxConstraints.maxWidth;
|
||||
double maxHeight = boxConstraints.maxHeight;
|
||||
double PR = MediaQuery.of(context).devicePixelRatio;
|
||||
return Hero(
|
||||
tag: videoDetailController.heroTag,
|
||||
child: NetworkImgLayer(
|
||||
src: videoDetailController.videoItem['pic'],
|
||||
width: maxWidth,
|
||||
height: maxHeight,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
bottom: PreferredSize(
|
||||
preferredSize: const Size.fromHeight(50.0),
|
||||
child: Container(
|
||||
height: 50,
|
||||
decoration: BoxDecoration(
|
||||
border: Border(
|
||||
bottom: BorderSide(
|
||||
color: Theme.of(context)
|
||||
.dividerColor
|
||||
.withOpacity(0.1)))),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
Container(
|
||||
width: 280,
|
||||
margin: const EdgeInsets.only(left: 20),
|
||||
child: Obx(
|
||||
() => TabBar(
|
||||
splashBorderRadius: BorderRadius.circular(6),
|
||||
dividerColor: Colors.transparent,
|
||||
tabs: videoDetailController.tabs
|
||||
.map((String name) => Tab(text: name))
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
SliverAppBar(
|
||||
title: const Text("视频详情"),
|
||||
pinned: true,
|
||||
elevation: 0,
|
||||
scrolledUnderElevation: 0,
|
||||
forceElevated: innerBoxIsScrolled,
|
||||
expandedHeight: MediaQuery.of(context).size.width * 9 / 16,
|
||||
collapsedHeight: MediaQuery.of(context).size.width * 9 / 16,
|
||||
flexibleSpace: FlexibleSpaceBar(
|
||||
background: Padding(
|
||||
padding: EdgeInsets.only(
|
||||
top: MediaQuery.of(context).padding.top),
|
||||
child: LayoutBuilder(
|
||||
builder: (context, boxConstraints) {
|
||||
double maxWidth = boxConstraints.maxWidth;
|
||||
double maxHeight = boxConstraints.maxHeight;
|
||||
double PR = MediaQuery.of(context).devicePixelRatio;
|
||||
return Hero(
|
||||
tag: videoDetailController.heroTag,
|
||||
child: NetworkImgLayer(
|
||||
src: videoDetailController.videoItem['pic'],
|
||||
width: maxWidth,
|
||||
height: maxHeight,
|
||||
),
|
||||
// 弹幕开关
|
||||
// const Spacer(),
|
||||
// Flexible(
|
||||
// flex: 2,
|
||||
// child: Container(
|
||||
// height: 50,
|
||||
// ),
|
||||
// ),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
];
|
||||
},
|
||||
body: TabBarView(
|
||||
pinnedHeaderSliverHeightBuilder: () {
|
||||
return pinnedHeaderHeight;
|
||||
},
|
||||
onlyOneScrollInBody: true,
|
||||
body: Column(
|
||||
children: [
|
||||
Builder(builder: (context) {
|
||||
return CustomScrollView(
|
||||
key: const PageStorageKey<String>('简介'),
|
||||
slivers: <Widget>[
|
||||
SliverOverlapInjector(
|
||||
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(
|
||||
context),
|
||||
Container(
|
||||
height: 50,
|
||||
decoration: BoxDecoration(
|
||||
border: Border(
|
||||
bottom: BorderSide(
|
||||
color: Theme.of(context).dividerColor.withOpacity(0.1),
|
||||
),
|
||||
const VideoIntroPanel(),
|
||||
SliverPadding(
|
||||
padding: const EdgeInsets.only(top: 8, bottom: 5),
|
||||
sliver: SliverToBoxAdapter(
|
||||
child: Divider(
|
||||
height: 1,
|
||||
color:
|
||||
Theme.of(context).dividerColor.withOpacity(0.1),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
Container(
|
||||
width: 280,
|
||||
margin: const EdgeInsets.only(left: 20),
|
||||
child: Obx(
|
||||
() => TabBar(
|
||||
splashBorderRadius: BorderRadius.circular(6),
|
||||
dividerColor: Colors.transparent,
|
||||
tabs: videoDetailController.tabs
|
||||
.map((String name) => Tab(text: name))
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
),
|
||||
const RelatedVideoPanel(),
|
||||
// 弹幕开关
|
||||
// const Spacer(),
|
||||
// Flexible(
|
||||
// flex: 2,
|
||||
// child: Container(
|
||||
// height: 50,
|
||||
// ),
|
||||
// ),
|
||||
],
|
||||
);
|
||||
}),
|
||||
Builder(builder: (context) {
|
||||
return CustomScrollView(
|
||||
key: const PageStorageKey<String>('评论'),
|
||||
slivers: <Widget>[
|
||||
SliverOverlapInjector(
|
||||
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(
|
||||
context),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: TabBarView(
|
||||
children: [
|
||||
Builder(
|
||||
builder: (context) {
|
||||
return CustomScrollView(
|
||||
key: const PageStorageKey<String>('简介'),
|
||||
slivers: <Widget>[
|
||||
const VideoIntroPanel(),
|
||||
SliverPadding(
|
||||
padding:
|
||||
const EdgeInsets.only(top: 8, bottom: 5),
|
||||
sliver: SliverToBoxAdapter(
|
||||
child: Divider(
|
||||
height: 1,
|
||||
color: Theme.of(context)
|
||||
.dividerColor
|
||||
.withOpacity(0.1),
|
||||
),
|
||||
),
|
||||
),
|
||||
const RelatedVideoPanel(),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
const VideoReplyPanel()
|
||||
VideoReplyPanel()
|
||||
],
|
||||
);
|
||||
})
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
@ -1,6 +1,7 @@
|
||||
import 'package:get/get.dart';
|
||||
import 'package:pilipala/pages/home/index.dart';
|
||||
import 'package:pilipala/pages/hot/index.dart';
|
||||
import 'package:pilipala/pages/preview/index.dart';
|
||||
import 'package:pilipala/pages/video/detail/index.dart';
|
||||
|
||||
class Routes {
|
||||
@ -11,5 +12,7 @@ class Routes {
|
||||
GetPage(name: '/hot', page: () => const HotPage()),
|
||||
// 视频详情
|
||||
GetPage(name: '/video', page: () => const VideoDetailPage()),
|
||||
// 图片预览
|
||||
GetPage(name: '/preview', page: () => const ImagePreview())
|
||||
];
|
||||
}
|
||||
|
@ -1,10 +1,13 @@
|
||||
// 工具函数
|
||||
import 'dart:io';
|
||||
import 'dart:math';
|
||||
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
|
||||
import 'package:get/get_utils/get_utils.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
|
||||
class Utils {
|
||||
final ChromeSafariBrowser browser = ChromeSafariBrowser();
|
||||
|
||||
static Future<String> getCookiePath() async {
|
||||
Directory tempDir = await getApplicationSupportDirectory();
|
||||
String tempPath = "${tempDir.path}/.plpl/";
|
||||
@ -135,4 +138,22 @@ class Utils {
|
||||
static String makeHeroTag(v) {
|
||||
return v.toString() + Random().nextInt(9999).toString();
|
||||
}
|
||||
|
||||
static openURL(aUrl) async {
|
||||
try {
|
||||
await Utils().browser.open(
|
||||
url: Uri.parse(aUrl),
|
||||
options: ChromeSafariBrowserClassOptions(
|
||||
android: AndroidChromeCustomTabsOptions(
|
||||
shareState: CustomTabsShareState.SHARE_STATE_OFF,
|
||||
isSingleInstance: false,
|
||||
isTrustedWebActivity: false,
|
||||
keepAliveEnabled: true,
|
||||
),
|
||||
),
|
||||
);
|
||||
} catch (err) {
|
||||
await InAppBrowser.openWithSystemBrowser(url: Uri.parse(aUrl));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,9 +7,13 @@
|
||||
#include "generated_plugin_registrant.h"
|
||||
|
||||
#include <dynamic_color/dynamic_color_plugin.h>
|
||||
#include <url_launcher_linux/url_launcher_plugin.h>
|
||||
|
||||
void fl_register_plugins(FlPluginRegistry* registry) {
|
||||
g_autoptr(FlPluginRegistrar) dynamic_color_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "DynamicColorPlugin");
|
||||
dynamic_color_plugin_register_with_registrar(dynamic_color_registrar);
|
||||
g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
|
||||
url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);
|
||||
}
|
||||
|
@ -4,6 +4,7 @@
|
||||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
dynamic_color
|
||||
url_launcher_linux
|
||||
)
|
||||
|
||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||
|
@ -6,13 +6,19 @@ import FlutterMacOS
|
||||
import Foundation
|
||||
|
||||
import connectivity_plus
|
||||
import device_info_plus
|
||||
import dynamic_color
|
||||
import path_provider_foundation
|
||||
import share_plus
|
||||
import sqflite
|
||||
import url_launcher_macos
|
||||
|
||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||
ConnectivityPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlugin"))
|
||||
DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
|
||||
DynamicColorPlugin.register(with: registry.registrar(forPlugin: "DynamicColorPlugin"))
|
||||
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
||||
SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin"))
|
||||
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
|
||||
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
|
||||
}
|
||||
|
214
pubspec.lock
214
pubspec.lock
@ -97,6 +97,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.1"
|
||||
cross_file:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: cross_file
|
||||
sha256: "0b0036e8cccbfbe0555fd83c1d31a6f30b77a96b598b35a5d36dd41f718695e9"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.3.3+4"
|
||||
crypto:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -121,6 +129,22 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.8"
|
||||
device_info_plus:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: device_info_plus
|
||||
sha256: f52ab3b76b36ede4d135aab80194df8925b553686f0fa12226b4e2d658e45903
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "8.2.2"
|
||||
device_info_plus_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: device_info_plus_platform_interface
|
||||
sha256: d3b01d5868b50ae571cd1dc6e502fc94d956b665756180f7b16ead09e836fd64
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.0.0"
|
||||
dio:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -153,6 +177,30 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.6.3"
|
||||
extended_image:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: extended_image
|
||||
sha256: "75e4b0ad0f8f63eed7935ff2506c809a670f5e6dd0f61304525879d53fc41a17"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.0.2"
|
||||
extended_image_library:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: extended_image_library
|
||||
sha256: "550743b43ab093aed35ef234500fcc7a304cbac1eca47b0cc991e07e88750758"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.4.2"
|
||||
extended_nested_scroll_view:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: extended_nested_scroll_view
|
||||
sha256: fc55b8f7e2c78701320d7eccda3b256387290b8498f0363d8ffd6f16760949d7
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.0.0"
|
||||
fake_async:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -198,6 +246,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.3.0"
|
||||
flutter_inappwebview:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_inappwebview
|
||||
sha256: "1c370ac07de80a579a0047c94c5bb586128d4ef50c0f3f501d6e77010374a319"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.4.4"
|
||||
flutter_lints:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
@ -240,6 +296,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.1"
|
||||
http_client_helper:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: http_client_helper
|
||||
sha256: "1f32359bd07a064ad256d1f84ae5f973f69bc972e7287223fa198abe1d969c28"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.3"
|
||||
http_parser:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -248,6 +312,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.0.2"
|
||||
image_gallery_saver:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: image_gallery_saver
|
||||
sha256: be812580c7a320d3bf583af89cac6b376f170d48000aca75215a73285a3223a0
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.7.1"
|
||||
js:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -288,6 +360,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.8.0"
|
||||
mime:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: mime
|
||||
sha256: e4ff8e8564c03f255408decd16e7899da1733852a9110a58fe6d1b817684a63e
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.4"
|
||||
nm:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -368,6 +448,46 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.11.1"
|
||||
permission_handler:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: permission_handler
|
||||
sha256: "33c6a1253d1f95fd06fa74b65b7ba907ae9811f9d5c1d3150e51417d04b8d6a8"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "10.2.0"
|
||||
permission_handler_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_android
|
||||
sha256: "8028362b40c4a45298f1cbfccd227c8dd6caf0e27088a69f2ba2ab15464159e2"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "10.2.0"
|
||||
permission_handler_apple:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_apple
|
||||
sha256: ee96ac32f5a8e6f80756e25b25b9f8e535816c8e6665a96b6d70681f8c4f7e85
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "9.0.8"
|
||||
permission_handler_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_platform_interface
|
||||
sha256: "68abbc472002b5e6dfce47fe9898c6b7d8328d58b5d2524f75e277c07a97eb84"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.9.0"
|
||||
permission_handler_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_windows
|
||||
sha256: f67cab14b4328574938ecea2db3475dad7af7ead6afab6338772c5f88963e38b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.2"
|
||||
petitparser:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -408,6 +528,22 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.27.7"
|
||||
share_plus:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: share_plus
|
||||
sha256: b1f15232d41e9701ab2f04181f21610c36c83a12ae426b79b4bd011c567934b1
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.3.4"
|
||||
share_plus_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: share_plus_platform_interface
|
||||
sha256: "0c6e61471bd71b04a138b8b588fa388e66d8b005e6f2deda63371c5c505a0981"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.2.1"
|
||||
sky_engine:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
@ -493,6 +629,70 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.1"
|
||||
url_launcher:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: url_launcher
|
||||
sha256: "75f2846facd11168d007529d6cd8fcb2b750186bea046af9711f10b907e1587e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.1.10"
|
||||
url_launcher_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_android
|
||||
sha256: "22f8db4a72be26e9e3a4aa3f194b1f7afbc76d20ec141f84be1d787db2155cbd"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.0.31"
|
||||
url_launcher_ios:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_ios
|
||||
sha256: "9af7ea73259886b92199f9e42c116072f05ff9bea2dcb339ab935dfc957392c2"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.1.4"
|
||||
url_launcher_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_linux
|
||||
sha256: "207f4ddda99b95b4d4868320a352d374b0b7e05eefad95a4a26f57da413443f5"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.5"
|
||||
url_launcher_macos:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_macos
|
||||
sha256: "91ee3e75ea9dadf38036200c5d3743518f4a5eb77a8d13fda1ee5764373f185e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.5"
|
||||
url_launcher_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_platform_interface
|
||||
sha256: "6c9ca697a5ae218ce56cece69d46128169a58aa8653c1b01d26fcd4aad8c4370"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
url_launcher_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_web
|
||||
sha256: "81fe91b6c4f84f222d186a9d23c73157dc4c8e1c71489c4d08be1ad3b228f1aa"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.16"
|
||||
url_launcher_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_windows
|
||||
sha256: "254708f17f7c20a9c8c471f67d86d76d4a3f9c1591aad1e15292008aceb82771"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.6"
|
||||
uuid:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -509,14 +709,22 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.4"
|
||||
visibility_detector:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: visibility_detector
|
||||
sha256: "15c54a459ec2c17b4705450483f3d5a2858e733aee893dcee9d75fd04814940d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.3.3"
|
||||
win32:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: win32
|
||||
sha256: dd8f9344bc305ae2923e3d11a2a911d9a4e2c7dd6fe0ed10626d63211a69676e
|
||||
sha256: a6f0236dbda0f63aa9a25ad1ff9a9d8a4eaaa5012da0dc59d21afdb1dc361ca4
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.1.3"
|
||||
version: "3.1.4"
|
||||
xdg_directories:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -535,4 +743,4 @@ packages:
|
||||
version: "6.2.2"
|
||||
sdks:
|
||||
dart: ">=2.19.6 <3.0.0"
|
||||
flutter: ">=3.4.0-17.0.pre"
|
||||
flutter: ">=3.7.0"
|
||||
|
34
pubspec.yaml
34
pubspec.yaml
@ -48,10 +48,24 @@ dependencies:
|
||||
|
||||
# 图片
|
||||
cached_network_image: ^3.2.3
|
||||
|
||||
extended_image: ^7.0.2
|
||||
image_gallery_saver: ^1.7.1
|
||||
|
||||
# 存储
|
||||
path_provider: ^2.0.14
|
||||
|
||||
# 设备信息
|
||||
device_info_plus: ^8.2.0
|
||||
# 权限
|
||||
permission_handler: ^10.2.0
|
||||
# 分享
|
||||
share_plus: ^6.3.1
|
||||
# webView
|
||||
url_launcher: ^6.1.9
|
||||
flutter_inappwebview: 5.4.4
|
||||
|
||||
extended_nested_scroll_view: ^6.0.0
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
@ -88,17 +102,11 @@ flutter:
|
||||
# "family" key with the font family name, and a "fonts" key with a
|
||||
# list giving the asset and other descriptors for the font. For
|
||||
# example:
|
||||
# fonts:
|
||||
# - family: Schyler
|
||||
# fonts:
|
||||
# - asset: fonts/Schyler-Regular.ttf
|
||||
# - asset: fonts/Schyler-Italic.ttf
|
||||
# style: italic
|
||||
# - family: Trajan Pro
|
||||
# fonts:
|
||||
# - asset: fonts/TrajanPro.ttf
|
||||
# - asset: fonts/TrajanPro_Bold.ttf
|
||||
# weight: 700
|
||||
#
|
||||
fonts:
|
||||
- family: fansCard
|
||||
fonts:
|
||||
- asset: assets/fonts/fansCard.ttf
|
||||
|
||||
|
||||
# For details regarding fonts from package dependencies,
|
||||
# see https://flutter.dev/custom-fonts/#from-packages
|
||||
|
@ -8,10 +8,19 @@
|
||||
|
||||
#include <connectivity_plus/connectivity_plus_windows_plugin.h>
|
||||
#include <dynamic_color/dynamic_color_plugin_c_api.h>
|
||||
#include <permission_handler_windows/permission_handler_windows_plugin.h>
|
||||
#include <share_plus/share_plus_windows_plugin_c_api.h>
|
||||
#include <url_launcher_windows/url_launcher_windows.h>
|
||||
|
||||
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||
ConnectivityPlusWindowsPluginRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("ConnectivityPlusWindowsPlugin"));
|
||||
DynamicColorPluginCApiRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("DynamicColorPluginCApi"));
|
||||
PermissionHandlerWindowsPluginRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin"));
|
||||
SharePlusWindowsPluginCApiRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi"));
|
||||
UrlLauncherWindowsRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("UrlLauncherWindows"));
|
||||
}
|
||||
|
@ -5,6 +5,9 @@
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
connectivity_plus
|
||||
dynamic_color
|
||||
permission_handler_windows
|
||||
share_plus
|
||||
url_launcher_windows
|
||||
)
|
||||
|
||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||
|
Reference in New Issue
Block a user