Merge branch 'design' into alpha
This commit is contained in:
@ -97,6 +97,9 @@ class Api {
|
|||||||
// 操作用户关系
|
// 操作用户关系
|
||||||
static const String relationMod = '/x/relation/modify';
|
static const String relationMod = '/x/relation/modify';
|
||||||
|
|
||||||
|
// 相互关系查询
|
||||||
|
static const String relationSearch = '/x/space/wbi/acc/relation';
|
||||||
|
|
||||||
// 评论列表
|
// 评论列表
|
||||||
// https://api.bilibili.com/x/v2/reply/main?csrf=6e22efc1a47225ea25f901f922b5cfdd&mode=3&oid=254175381&pagination_str=%7B%22offset%22:%22%22%7D&plat=1&seek_rpid=0&type=11
|
// https://api.bilibili.com/x/v2/reply/main?csrf=6e22efc1a47225ea25f901f922b5cfdd&mode=3&oid=254175381&pagination_str=%7B%22offset%22:%22%22%7D&plat=1&seek_rpid=0&type=11
|
||||||
static const String replyList = '/x/v2/reply';
|
static const String replyList = '/x/v2/reply';
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import 'package:pilipala/models/user/fav_folder.dart';
|
|||||||
import 'package:pilipala/models/user/history.dart';
|
import 'package:pilipala/models/user/history.dart';
|
||||||
import 'package:pilipala/models/user/info.dart';
|
import 'package:pilipala/models/user/info.dart';
|
||||||
import 'package:pilipala/models/user/stat.dart';
|
import 'package:pilipala/models/user/stat.dart';
|
||||||
|
import 'package:pilipala/utils/wbi_sign.dart';
|
||||||
|
|
||||||
class UserHttp {
|
class UserHttp {
|
||||||
static Future<dynamic> userStat({required int mid}) async {
|
static Future<dynamic> userStat({required int mid}) async {
|
||||||
@ -248,4 +249,29 @@ class UserHttp {
|
|||||||
return {'status': false, 'msg': res.data['message']};
|
return {'status': false, 'msg': res.data['message']};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 相互关系查询
|
||||||
|
static Future relationSearch(int mid) async {
|
||||||
|
Map params = await WbiSign().makSign({
|
||||||
|
'mid': mid,
|
||||||
|
'token': '',
|
||||||
|
'platform': 'web',
|
||||||
|
'web_location': 1550101,
|
||||||
|
});
|
||||||
|
var res = await Request().get(
|
||||||
|
Api.relationSearch,
|
||||||
|
data: {
|
||||||
|
'mid': mid,
|
||||||
|
'w_rid': params['w_rid'],
|
||||||
|
'wts': params['wts'],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
if (res.data['code'] == 0) {
|
||||||
|
// relation 主动状态
|
||||||
|
// 被动状态
|
||||||
|
return {'status': true, 'data': res.data['data']};
|
||||||
|
} else {
|
||||||
|
return {'status': false, 'msg': res.data['message']};
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:pilipala/http/reply.dart';
|
import 'package:pilipala/http/reply.dart';
|
||||||
@ -17,6 +18,7 @@ class DynamicDetailController extends GetxController {
|
|||||||
RxString noMore = ''.obs;
|
RxString noMore = ''.obs;
|
||||||
RxList<ReplyItemModel> replyList = [ReplyItemModel()].obs;
|
RxList<ReplyItemModel> replyList = [ReplyItemModel()].obs;
|
||||||
RxInt acount = 0.obs;
|
RxInt acount = 0.obs;
|
||||||
|
final ScrollController scrollController = ScrollController();
|
||||||
|
|
||||||
ReplySortType _sortType = ReplySortType.time;
|
ReplySortType _sortType = ReplySortType.time;
|
||||||
RxString sortTypeTitle = ReplySortType.time.titles.obs;
|
RxString sortTypeTitle = ReplySortType.time.titles.obs;
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import 'dart:async';
|
|||||||
|
|
||||||
import 'package:easy_debounce/easy_throttle.dart';
|
import 'package:easy_debounce/easy_throttle.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/rendering.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:pilipala/common/skeleton/video_reply.dart';
|
import 'package:pilipala/common/skeleton/video_reply.dart';
|
||||||
import 'package:pilipala/common/widgets/http_error.dart';
|
import 'package:pilipala/common/widgets/http_error.dart';
|
||||||
@ -9,7 +10,10 @@ import 'package:pilipala/models/common/reply_type.dart';
|
|||||||
import 'package:pilipala/pages/dynamics/deatil/index.dart';
|
import 'package:pilipala/pages/dynamics/deatil/index.dart';
|
||||||
import 'package:pilipala/pages/dynamics/widgets/author_panel.dart';
|
import 'package:pilipala/pages/dynamics/widgets/author_panel.dart';
|
||||||
import 'package:pilipala/pages/video/detail/reply/widgets/reply_item.dart';
|
import 'package:pilipala/pages/video/detail/reply/widgets/reply_item.dart';
|
||||||
|
import 'package:pilipala/pages/video/detail/replyNew/index.dart';
|
||||||
import 'package:pilipala/pages/video/detail/replyReply/index.dart';
|
import 'package:pilipala/pages/video/detail/replyReply/index.dart';
|
||||||
|
import 'package:pilipala/utils/feed_back.dart';
|
||||||
|
import 'package:pilipala/utils/id_utils.dart';
|
||||||
|
|
||||||
import '../widgets/dynamic_panel.dart';
|
import '../widgets/dynamic_panel.dart';
|
||||||
|
|
||||||
@ -21,15 +25,18 @@ class DynamicDetailPage extends StatefulWidget {
|
|||||||
State<DynamicDetailPage> createState() => _DynamicDetailPageState();
|
State<DynamicDetailPage> createState() => _DynamicDetailPageState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _DynamicDetailPageState extends State<DynamicDetailPage> {
|
class _DynamicDetailPageState extends State<DynamicDetailPage>
|
||||||
late DynamicDetailController? _dynamicDetailController;
|
with TickerProviderStateMixin {
|
||||||
|
late DynamicDetailController _dynamicDetailController;
|
||||||
|
late AnimationController fabAnimationCtr;
|
||||||
Future? _futureBuilderFuture;
|
Future? _futureBuilderFuture;
|
||||||
late StreamController<bool> titleStreamC; // appBar title
|
late StreamController<bool> titleStreamC; // appBar title
|
||||||
final ScrollController scrollController = ScrollController();
|
late ScrollController scrollController;
|
||||||
bool _visibleTitle = false;
|
bool _visibleTitle = false;
|
||||||
String? action;
|
String? action;
|
||||||
// 回复类型
|
// 回复类型
|
||||||
late int type;
|
late int type;
|
||||||
|
bool _isFabVisible = true;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@ -50,37 +57,30 @@ class _DynamicDetailPageState extends State<DynamicDetailPage> {
|
|||||||
}
|
}
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
}
|
}
|
||||||
int commentType = Get.arguments['item'].basic!['comment_type'] ?? 11;
|
int commentType = 11;
|
||||||
|
try {
|
||||||
|
commentType = Get.arguments['item'].basic!['comment_type'];
|
||||||
|
} catch (_) {}
|
||||||
type = (commentType == 0) ? 11 : commentType;
|
type = (commentType == 0) ? 11 : commentType;
|
||||||
|
|
||||||
action =
|
action =
|
||||||
Get.arguments.containsKey('action') ? Get.arguments['action'] : null;
|
Get.arguments.containsKey('action') ? Get.arguments['action'] : null;
|
||||||
_dynamicDetailController =
|
_dynamicDetailController =
|
||||||
Get.put(DynamicDetailController(oid, type), tag: oid.toString());
|
Get.put(DynamicDetailController(oid, type), tag: oid.toString());
|
||||||
_futureBuilderFuture = _dynamicDetailController!.queryReplyList();
|
_futureBuilderFuture = _dynamicDetailController.queryReplyList();
|
||||||
titleStreamC = StreamController<bool>();
|
titleStreamC = StreamController<bool>();
|
||||||
scrollController.addListener(_listen);
|
|
||||||
if (action == 'comment') {
|
if (action == 'comment') {
|
||||||
_visibleTitle = true;
|
_visibleTitle = true;
|
||||||
titleStreamC.add(true);
|
titleStreamC.add(true);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
void _listen() async {
|
fabAnimationCtr = AnimationController(
|
||||||
if (scrollController.position.pixels >=
|
vsync: this,
|
||||||
scrollController.position.maxScrollExtent - 300) {
|
duration: const Duration(milliseconds: 300),
|
||||||
EasyThrottle.throttle('replylist', const Duration(seconds: 2), () {
|
);
|
||||||
_dynamicDetailController!.queryReplyList(reqType: 'onLoad');
|
fabAnimationCtr.forward();
|
||||||
});
|
// 滚动事件监听
|
||||||
}
|
scrollListener();
|
||||||
|
|
||||||
if (scrollController.offset > 55 && !_visibleTitle) {
|
|
||||||
_visibleTitle = true;
|
|
||||||
titleStreamC.add(true);
|
|
||||||
} else if (scrollController.offset <= 55 && _visibleTitle) {
|
|
||||||
_visibleTitle = false;
|
|
||||||
titleStreamC.add(false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void replyReply(replyItem) {
|
void replyReply(replyItem) {
|
||||||
@ -107,9 +107,58 @@ class _DynamicDetailPageState extends State<DynamicDetailPage> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void scrollListener() {
|
||||||
|
scrollController = _dynamicDetailController.scrollController;
|
||||||
|
scrollController.addListener(
|
||||||
|
() {
|
||||||
|
// 分页加载
|
||||||
|
if (scrollController.position.pixels >=
|
||||||
|
scrollController.position.maxScrollExtent - 300) {
|
||||||
|
EasyThrottle.throttle('replylist', const Duration(seconds: 2), () {
|
||||||
|
_dynamicDetailController.queryReplyList(reqType: 'onLoad');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 标题
|
||||||
|
if (scrollController.offset > 55 && !_visibleTitle) {
|
||||||
|
_visibleTitle = true;
|
||||||
|
titleStreamC.add(true);
|
||||||
|
} else if (scrollController.offset <= 55 && _visibleTitle) {
|
||||||
|
_visibleTitle = false;
|
||||||
|
titleStreamC.add(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// fab按钮
|
||||||
|
final ScrollDirection direction =
|
||||||
|
scrollController.position.userScrollDirection;
|
||||||
|
if (direction == ScrollDirection.forward) {
|
||||||
|
_showFab();
|
||||||
|
} else if (direction == ScrollDirection.reverse) {
|
||||||
|
_hideFab();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _showFab() {
|
||||||
|
if (!_isFabVisible) {
|
||||||
|
_isFabVisible = true;
|
||||||
|
fabAnimationCtr.forward();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _hideFab() {
|
||||||
|
if (_isFabVisible) {
|
||||||
|
_isFabVisible = false;
|
||||||
|
fabAnimationCtr.reverse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
scrollController.removeListener(() {});
|
scrollController.removeListener(() {});
|
||||||
|
fabAnimationCtr.dispose();
|
||||||
|
scrollController.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -136,155 +185,206 @@ class _DynamicDetailPageState extends State<DynamicDetailPage> {
|
|||||||
),
|
),
|
||||||
body: RefreshIndicator(
|
body: RefreshIndicator(
|
||||||
onRefresh: () async {
|
onRefresh: () async {
|
||||||
await _dynamicDetailController!.queryReplyList();
|
await _dynamicDetailController.queryReplyList();
|
||||||
},
|
},
|
||||||
child: CustomScrollView(
|
child: Stack(
|
||||||
controller: scrollController,
|
children: [
|
||||||
slivers: [
|
CustomScrollView(
|
||||||
if (action != 'comment')
|
controller: scrollController,
|
||||||
SliverToBoxAdapter(
|
slivers: [
|
||||||
child: DynamicPanel(
|
if (action != 'comment')
|
||||||
item: _dynamicDetailController!.item,
|
SliverToBoxAdapter(
|
||||||
source: 'detail',
|
child: DynamicPanel(
|
||||||
),
|
item: _dynamicDetailController.item,
|
||||||
),
|
source: 'detail',
|
||||||
SliverPersistentHeader(
|
|
||||||
delegate: _MySliverPersistentHeaderDelegate(
|
|
||||||
child: Container(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Theme.of(context).colorScheme.surface,
|
|
||||||
border: Border(
|
|
||||||
top: BorderSide(
|
|
||||||
width: 0.6,
|
|
||||||
color: Theme.of(context).dividerColor.withOpacity(0.05),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
height: 45,
|
SliverPersistentHeader(
|
||||||
padding: const EdgeInsets.only(left: 12, right: 6),
|
delegate: _MySliverPersistentHeaderDelegate(
|
||||||
child: Row(
|
child: Container(
|
||||||
children: [
|
decoration: BoxDecoration(
|
||||||
Obx(
|
color: Theme.of(context).colorScheme.surface,
|
||||||
() => AnimatedSwitcher(
|
border: Border(
|
||||||
duration: const Duration(milliseconds: 400),
|
top: BorderSide(
|
||||||
transitionBuilder:
|
width: 0.6,
|
||||||
(Widget child, Animation<double> animation) {
|
color: Theme.of(context)
|
||||||
return ScaleTransition(
|
.dividerColor
|
||||||
scale: animation, child: child);
|
.withOpacity(0.05),
|
||||||
},
|
|
||||||
child: Text(
|
|
||||||
'${_dynamicDetailController!.acount.value}',
|
|
||||||
key: ValueKey<int>(
|
|
||||||
_dynamicDetailController!.acount.value),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const Text('条回复'),
|
height: 45,
|
||||||
const Spacer(),
|
padding: const EdgeInsets.only(left: 12, right: 6),
|
||||||
SizedBox(
|
child: Row(
|
||||||
height: 35,
|
children: [
|
||||||
child: TextButton.icon(
|
Obx(
|
||||||
onPressed: () =>
|
() => AnimatedSwitcher(
|
||||||
_dynamicDetailController!.queryBySort(),
|
duration: const Duration(milliseconds: 400),
|
||||||
icon: const Icon(Icons.sort, size: 16),
|
transitionBuilder:
|
||||||
label: Obx(() => Text(
|
(Widget child, Animation<double> animation) {
|
||||||
_dynamicDetailController!.sortTypeLabel.value,
|
return ScaleTransition(
|
||||||
style: const TextStyle(fontSize: 13),
|
scale: animation, child: child);
|
||||||
)),
|
},
|
||||||
),
|
child: Text(
|
||||||
)
|
'${_dynamicDetailController.acount.value}',
|
||||||
],
|
key: ValueKey<int>(
|
||||||
),
|
_dynamicDetailController.acount.value),
|
||||||
),
|
|
||||||
),
|
|
||||||
pinned: true,
|
|
||||||
),
|
|
||||||
FutureBuilder(
|
|
||||||
future: _futureBuilderFuture,
|
|
||||||
builder: (context, snapshot) {
|
|
||||||
if (snapshot.connectionState == ConnectionState.done) {
|
|
||||||
Map data = snapshot.data as Map;
|
|
||||||
if (snapshot.data['status']) {
|
|
||||||
// 请求成功
|
|
||||||
return Obx(
|
|
||||||
() => _dynamicDetailController!.replyList.isEmpty &&
|
|
||||||
_dynamicDetailController!.isLoadingMore
|
|
||||||
? SliverList(
|
|
||||||
delegate:
|
|
||||||
SliverChildBuilderDelegate((context, index) {
|
|
||||||
return const VideoReplySkeleton();
|
|
||||||
}, childCount: 8),
|
|
||||||
)
|
|
||||||
: SliverList(
|
|
||||||
delegate: SliverChildBuilderDelegate(
|
|
||||||
(context, index) {
|
|
||||||
if (index ==
|
|
||||||
_dynamicDetailController!
|
|
||||||
.replyList.length) {
|
|
||||||
return Container(
|
|
||||||
padding: EdgeInsets.only(
|
|
||||||
bottom: MediaQuery.of(context)
|
|
||||||
.padding
|
|
||||||
.bottom),
|
|
||||||
height: MediaQuery.of(context)
|
|
||||||
.padding
|
|
||||||
.bottom +
|
|
||||||
100,
|
|
||||||
child: Center(
|
|
||||||
child: Obx(
|
|
||||||
() => Text(
|
|
||||||
_dynamicDetailController!
|
|
||||||
.noMore.value,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 12,
|
|
||||||
color: Theme.of(context)
|
|
||||||
.colorScheme
|
|
||||||
.outline,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return ReplyItem(
|
|
||||||
replyItem: _dynamicDetailController!
|
|
||||||
.replyList[index],
|
|
||||||
showReplyRow: true,
|
|
||||||
replyLevel: '1',
|
|
||||||
replyReply: (replyItem) =>
|
|
||||||
replyReply(replyItem),
|
|
||||||
replyType: ReplyType.values[type],
|
|
||||||
addReply: (replyItem) {
|
|
||||||
_dynamicDetailController!
|
|
||||||
.replyList[index].replies!
|
|
||||||
.add(replyItem);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
childCount:
|
|
||||||
_dynamicDetailController!.replyList.length +
|
|
||||||
1,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
const Text('条回复'),
|
||||||
|
const Spacer(),
|
||||||
|
SizedBox(
|
||||||
|
height: 35,
|
||||||
|
child: TextButton.icon(
|
||||||
|
onPressed: () =>
|
||||||
|
_dynamicDetailController.queryBySort(),
|
||||||
|
icon: const Icon(Icons.sort, size: 16),
|
||||||
|
label: Obx(() => Text(
|
||||||
|
_dynamicDetailController
|
||||||
|
.sortTypeLabel.value,
|
||||||
|
style: const TextStyle(fontSize: 13),
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
pinned: true,
|
||||||
|
),
|
||||||
|
FutureBuilder(
|
||||||
|
future: _futureBuilderFuture,
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
if (snapshot.connectionState == ConnectionState.done) {
|
||||||
|
Map data = snapshot.data as Map;
|
||||||
|
if (snapshot.data['status']) {
|
||||||
|
// 请求成功
|
||||||
|
return Obx(
|
||||||
|
() => _dynamicDetailController.replyList.isEmpty &&
|
||||||
|
_dynamicDetailController.isLoadingMore
|
||||||
|
? SliverList(
|
||||||
|
delegate: SliverChildBuilderDelegate(
|
||||||
|
(context, index) {
|
||||||
|
return const VideoReplySkeleton();
|
||||||
|
}, childCount: 8),
|
||||||
|
)
|
||||||
|
: SliverList(
|
||||||
|
delegate: SliverChildBuilderDelegate(
|
||||||
|
(context, index) {
|
||||||
|
if (index ==
|
||||||
|
_dynamicDetailController
|
||||||
|
.replyList.length) {
|
||||||
|
return Container(
|
||||||
|
padding: EdgeInsets.only(
|
||||||
|
bottom: MediaQuery.of(context)
|
||||||
|
.padding
|
||||||
|
.bottom),
|
||||||
|
height: MediaQuery.of(context)
|
||||||
|
.padding
|
||||||
|
.bottom +
|
||||||
|
100,
|
||||||
|
child: Center(
|
||||||
|
child: Obx(
|
||||||
|
() => Text(
|
||||||
|
_dynamicDetailController
|
||||||
|
.noMore.value,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
color: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.outline,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return ReplyItem(
|
||||||
|
replyItem: _dynamicDetailController
|
||||||
|
.replyList[index],
|
||||||
|
showReplyRow: true,
|
||||||
|
replyLevel: '1',
|
||||||
|
replyReply: (replyItem) =>
|
||||||
|
replyReply(replyItem),
|
||||||
|
replyType: ReplyType.values[type],
|
||||||
|
addReply: (replyItem) {
|
||||||
|
_dynamicDetailController
|
||||||
|
.replyList[index].replies!
|
||||||
|
.add(replyItem);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
childCount: _dynamicDetailController
|
||||||
|
.replyList.length +
|
||||||
|
1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// 请求错误
|
||||||
|
return HttpError(
|
||||||
|
errMsg: data['msg'],
|
||||||
|
fn: () => setState(() {}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 骨架屏
|
||||||
|
return SliverList(
|
||||||
|
delegate: SliverChildBuilderDelegate((context, index) {
|
||||||
|
return const VideoReplySkeleton();
|
||||||
|
}, childCount: 8),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Positioned(
|
||||||
|
bottom: MediaQuery.of(context).padding.bottom + 14,
|
||||||
|
right: 14,
|
||||||
|
child: SlideTransition(
|
||||||
|
position: Tween<Offset>(
|
||||||
|
begin: const Offset(0, 2),
|
||||||
|
end: const Offset(0, 0),
|
||||||
|
).animate(CurvedAnimation(
|
||||||
|
parent: fabAnimationCtr,
|
||||||
|
curve: Curves.easeInOut,
|
||||||
|
)),
|
||||||
|
child: FloatingActionButton(
|
||||||
|
heroTag: null,
|
||||||
|
onPressed: () {
|
||||||
|
feedBack();
|
||||||
|
showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
isScrollControlled: true,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return VideoReplyNewDialog(
|
||||||
|
oid: _dynamicDetailController.oid ??
|
||||||
|
IdUtils.bv2av(Get.parameters['bvid']!),
|
||||||
|
root: 0,
|
||||||
|
parent: 0,
|
||||||
|
replyType: ReplyType.values[type],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
).then(
|
||||||
|
(value) => {
|
||||||
|
// 完成评论,数据添加
|
||||||
|
if (value != null && value['data'] != null)
|
||||||
|
{
|
||||||
|
_dynamicDetailController.replyList
|
||||||
|
.add(value['data']),
|
||||||
|
_dynamicDetailController.acount.value++
|
||||||
|
}
|
||||||
|
},
|
||||||
);
|
);
|
||||||
} else {
|
},
|
||||||
// 请求错误
|
tooltip: '评论动态',
|
||||||
return HttpError(
|
child: const Icon(Icons.reply),
|
||||||
errMsg: data['msg'],
|
),
|
||||||
fn: () => setState(() {}),
|
),
|
||||||
);
|
),
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// 骨架屏
|
|
||||||
return SliverList(
|
|
||||||
delegate: SliverChildBuilderDelegate((context, index) {
|
|
||||||
return const VideoReplySkeleton();
|
|
||||||
}, childCount: 8),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@ -3,10 +3,12 @@ import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
|||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:pilipala/http/member.dart';
|
import 'package:pilipala/http/member.dart';
|
||||||
|
import 'package:pilipala/http/user.dart';
|
||||||
import 'package:pilipala/http/video.dart';
|
import 'package:pilipala/http/video.dart';
|
||||||
import 'package:pilipala/models/member/archive.dart';
|
import 'package:pilipala/models/member/archive.dart';
|
||||||
import 'package:pilipala/models/member/info.dart';
|
import 'package:pilipala/models/member/info.dart';
|
||||||
import 'package:pilipala/utils/storage.dart';
|
import 'package:pilipala/utils/storage.dart';
|
||||||
|
import 'package:share_plus/share_plus.dart';
|
||||||
|
|
||||||
class MemberController extends GetxController {
|
class MemberController extends GetxController {
|
||||||
late int mid;
|
late int mid;
|
||||||
@ -19,6 +21,8 @@ class MemberController extends GetxController {
|
|||||||
// 投稿列表
|
// 投稿列表
|
||||||
RxList<VListItemModel>? archiveList = [VListItemModel()].obs;
|
RxList<VListItemModel>? archiveList = [VListItemModel()].obs;
|
||||||
var userInfo;
|
var userInfo;
|
||||||
|
RxInt attribute = (-1).obs;
|
||||||
|
RxString attributeText = '关注'.obs;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onInit() {
|
void onInit() {
|
||||||
@ -28,6 +32,7 @@ class MemberController extends GetxController {
|
|||||||
ownerMid = userInfo != null ? userInfo.mid : -1;
|
ownerMid = userInfo != null ? userInfo.mid : -1;
|
||||||
face = Get.arguments['face'] ?? '';
|
face = Get.arguments['face'] ?? '';
|
||||||
heroTag = Get.arguments['heroTag'] ?? '';
|
heroTag = Get.arguments['heroTag'] ?? '';
|
||||||
|
relationSearch();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取用户信息
|
// 获取用户信息
|
||||||
@ -63,7 +68,10 @@ class MemberController extends GetxController {
|
|||||||
SmartDialog.showToast('账号未登录');
|
SmartDialog.showToast('账号未登录');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (attribute.value == 128) {
|
||||||
|
blockUser();
|
||||||
|
return;
|
||||||
|
}
|
||||||
SmartDialog.show(
|
SmartDialog.show(
|
||||||
useSystem: true,
|
useSystem: true,
|
||||||
animationType: SmartAnimationType.centerFade_otherSlide,
|
animationType: SmartAnimationType.centerFade_otherSlide,
|
||||||
@ -73,8 +81,12 @@ class MemberController extends GetxController {
|
|||||||
content: Text(memberInfo.value.isFollowed! ? '取消关注UP主?' : '关注UP主?'),
|
content: Text(memberInfo.value.isFollowed! ? '取消关注UP主?' : '关注UP主?'),
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => SmartDialog.dismiss(),
|
onPressed: () => SmartDialog.dismiss(),
|
||||||
child: const Text('点错了')),
|
child: Text(
|
||||||
|
'点错了',
|
||||||
|
style: TextStyle(color: Theme.of(context).colorScheme.outline),
|
||||||
|
),
|
||||||
|
),
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
await VideoHttp.relationMod(
|
await VideoHttp.relationMod(
|
||||||
@ -83,8 +95,7 @@ class MemberController extends GetxController {
|
|||||||
reSrc: 11,
|
reSrc: 11,
|
||||||
);
|
);
|
||||||
memberInfo.value.isFollowed = !memberInfo.value.isFollowed!;
|
memberInfo.value.isFollowed = !memberInfo.value.isFollowed!;
|
||||||
SmartDialog.dismiss();
|
relationSearch();
|
||||||
SmartDialog.showLoading();
|
|
||||||
SmartDialog.dismiss();
|
SmartDialog.dismiss();
|
||||||
memberInfo.update((val) {});
|
memberInfo.update((val) {});
|
||||||
},
|
},
|
||||||
@ -95,4 +106,69 @@ class MemberController extends GetxController {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 关系查询
|
||||||
|
Future relationSearch() async {
|
||||||
|
if (userInfo == null) return;
|
||||||
|
var res = await UserHttp.relationSearch(mid);
|
||||||
|
if (res['status']) {
|
||||||
|
attribute.value = res['data']['relation']['attribute'];
|
||||||
|
attributeText.value = attribute.value == 0
|
||||||
|
? '关注'
|
||||||
|
: attribute.value == 2
|
||||||
|
? '已关注'
|
||||||
|
: attribute.value == 2
|
||||||
|
? '已互粉'
|
||||||
|
: '已拉黑';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 拉黑用户
|
||||||
|
Future blockUser() async {
|
||||||
|
if (userInfo == null) {
|
||||||
|
SmartDialog.showToast('账号未登录');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
SmartDialog.show(
|
||||||
|
useSystem: true,
|
||||||
|
animationType: SmartAnimationType.centerFade_otherSlide,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return AlertDialog(
|
||||||
|
title: const Text('提示'),
|
||||||
|
content: Text(attribute.value != 128 ? '确定拉黑UP主?' : '从黑名单移除UP主'),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => SmartDialog.dismiss(),
|
||||||
|
child: Text(
|
||||||
|
'点错了',
|
||||||
|
style: TextStyle(color: Theme.of(context).colorScheme.outline),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () async {
|
||||||
|
var res = await VideoHttp.relationMod(
|
||||||
|
mid: mid,
|
||||||
|
act: attribute.value != 128 ? 5 : 6,
|
||||||
|
reSrc: 11,
|
||||||
|
);
|
||||||
|
SmartDialog.dismiss();
|
||||||
|
if (res['status']) {
|
||||||
|
attribute.value = attribute.value != 128 ? 128 : 0;
|
||||||
|
attributeText.value = attribute.value == 128 ? '已拉黑' : '关注';
|
||||||
|
memberInfo.value.isFollowed = false;
|
||||||
|
relationSearch();
|
||||||
|
memberInfo.update((val) {});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: const Text('确认'),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void shareUser() {
|
||||||
|
Share.share('${memberInfo.value.name} - https://space.bilibili.com/$mid');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -102,7 +102,35 @@ class _MemberPageState extends State<MemberPage>
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
IconButton(onPressed: () {}, icon: const Icon(Icons.more_vert)),
|
PopupMenuButton(
|
||||||
|
icon: const Icon(Icons.more_vert),
|
||||||
|
itemBuilder: (BuildContext context) => <PopupMenuEntry>[
|
||||||
|
PopupMenuItem(
|
||||||
|
onTap: () => _memberController.blockUser(),
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
const Icon(Icons.block, size: 19),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
Text(_memberController.attribute.value != 128
|
||||||
|
? '加入黑名单'
|
||||||
|
: '移除黑名单'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
PopupMenuItem(
|
||||||
|
onTap: () => _memberController.shareUser(),
|
||||||
|
child: const Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Icon(Icons.share_outlined, size: 19),
|
||||||
|
SizedBox(width: 10),
|
||||||
|
Text('分享UP主'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
const SizedBox(width: 4),
|
const SizedBox(width: 4),
|
||||||
],
|
],
|
||||||
flexibleSpace: FlexibleSpaceBar(
|
flexibleSpace: FlexibleSpaceBar(
|
||||||
|
|||||||
@ -15,62 +15,63 @@ Widget profile(ctr, {loadingStatus = false}) {
|
|||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Hero(
|
Hero(
|
||||||
tag: ctr.heroTag!,
|
tag: ctr.heroTag!,
|
||||||
child: Stack(
|
child: Stack(
|
||||||
children: [
|
children: [
|
||||||
NetworkImgLayer(
|
NetworkImgLayer(
|
||||||
width: 90,
|
width: 90,
|
||||||
height: 90,
|
height: 90,
|
||||||
type: 'avatar',
|
type: 'avatar',
|
||||||
src: !loadingStatus ? memberInfo.face : ctr.face,
|
src: !loadingStatus ? memberInfo.face : ctr.face,
|
||||||
),
|
),
|
||||||
if (!loadingStatus &&
|
if (!loadingStatus &&
|
||||||
memberInfo.liveRoom != null &&
|
memberInfo.liveRoom != null &&
|
||||||
memberInfo.liveRoom!.liveStatus == 1)
|
memberInfo.liveRoom!.liveStatus == 1)
|
||||||
Positioned(
|
Positioned(
|
||||||
bottom: 0,
|
bottom: 0,
|
||||||
left: 14,
|
left: 14,
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
LiveItemModel liveItem = LiveItemModel.fromJson({
|
LiveItemModel liveItem = LiveItemModel.fromJson({
|
||||||
'title': memberInfo.liveRoom!.title,
|
'title': memberInfo.liveRoom!.title,
|
||||||
'uname': memberInfo.name,
|
'uname': memberInfo.name,
|
||||||
'face': memberInfo.face,
|
'face': memberInfo.face,
|
||||||
'roomid': memberInfo.liveRoom!.roomId,
|
'roomid': memberInfo.liveRoom!.roomId,
|
||||||
'watched_show': memberInfo.liveRoom!.watchedShow,
|
'watched_show': memberInfo.liveRoom!.watchedShow,
|
||||||
});
|
});
|
||||||
Get.toNamed(
|
Get.toNamed(
|
||||||
'/liveRoom?roomid=${memberInfo.liveRoom!.roomId}',
|
'/liveRoom?roomid=${memberInfo.liveRoom!.roomId}',
|
||||||
arguments: {'liveItem': liveItem},
|
arguments: {'liveItem': liveItem},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: const EdgeInsets.fromLTRB(6, 2, 6, 2),
|
padding: const EdgeInsets.fromLTRB(6, 2, 6, 2),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Theme.of(context).colorScheme.primary,
|
color: Theme.of(context).colorScheme.primary,
|
||||||
borderRadius:
|
borderRadius:
|
||||||
const BorderRadius.all(Radius.circular(10)),
|
const BorderRadius.all(Radius.circular(10)),
|
||||||
),
|
|
||||||
child: Row(children: [
|
|
||||||
Image.asset(
|
|
||||||
'assets/images/live.gif',
|
|
||||||
height: 10,
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
' 直播中',
|
|
||||||
style: TextStyle(
|
|
||||||
color: Colors.white,
|
|
||||||
fontSize: Theme.of(context)
|
|
||||||
.textTheme
|
|
||||||
.labelSmall!
|
|
||||||
.fontSize),
|
|
||||||
)
|
|
||||||
]),
|
|
||||||
),
|
),
|
||||||
|
child: Row(children: [
|
||||||
|
Image.asset(
|
||||||
|
'assets/images/live.gif',
|
||||||
|
height: 10,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
' 直播中',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.labelSmall!
|
||||||
|
.fontSize),
|
||||||
|
)
|
||||||
|
]),
|
||||||
),
|
),
|
||||||
)
|
),
|
||||||
],
|
)
|
||||||
)),
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
const SizedBox(width: 12),
|
const SizedBox(width: 12),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Column(
|
child: Column(
|
||||||
@ -152,34 +153,41 @@ Widget profile(ctr, {loadingStatus = false}) {
|
|||||||
if (ctr.ownerMid != ctr.mid) ...[
|
if (ctr.ownerMid != ctr.mid) ...[
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
TextButton(
|
Obx(
|
||||||
onPressed: () => ctr.actionRelationMod(),
|
() => Expanded(
|
||||||
style: TextButton.styleFrom(
|
child: TextButton(
|
||||||
padding: const EdgeInsets.only(left: 42, right: 42),
|
onPressed: () => ctr.actionRelationMod(),
|
||||||
foregroundColor:
|
style: TextButton.styleFrom(
|
||||||
!loadingStatus && memberInfo.isFollowed!
|
foregroundColor: ctr.attribute.value == -1
|
||||||
? Theme.of(context).colorScheme.outline
|
? Colors.transparent
|
||||||
: Theme.of(context).colorScheme.onPrimary,
|
: ctr.attribute.value != 0
|
||||||
backgroundColor: !loadingStatus &&
|
? Theme.of(context).colorScheme.outline
|
||||||
memberInfo.isFollowed!
|
: Theme.of(context)
|
||||||
? Theme.of(context).colorScheme.onInverseSurface
|
.colorScheme
|
||||||
: Theme.of(context)
|
.onPrimary,
|
||||||
.colorScheme
|
backgroundColor: ctr.attribute.value != 0
|
||||||
.primary, // 设置按钮背景色
|
? Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.onInverseSurface
|
||||||
|
: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.primary, // 设置按钮背景色
|
||||||
|
),
|
||||||
|
child: Obx(() => Text(ctr.attributeText.value)),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
child: Text(!loadingStatus && memberInfo.isFollowed!
|
|
||||||
? '取关'
|
|
||||||
: '关注'),
|
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
TextButton(
|
Expanded(
|
||||||
onPressed: () {},
|
child: TextButton(
|
||||||
style: TextButton.styleFrom(
|
onPressed: () {},
|
||||||
padding: const EdgeInsets.only(left: 42, right: 42),
|
style: TextButton.styleFrom(
|
||||||
backgroundColor:
|
backgroundColor: Theme.of(context)
|
||||||
Theme.of(context).colorScheme.onInverseSurface,
|
.colorScheme
|
||||||
|
.onInverseSurface,
|
||||||
|
),
|
||||||
|
child: const Text('发消息'),
|
||||||
),
|
),
|
||||||
child: const Text('发消息'),
|
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|||||||
@ -62,6 +62,12 @@ class _VideoReplyPanelState extends State<VideoReplyPanel>
|
|||||||
vsync: this, duration: const Duration(milliseconds: 300));
|
vsync: this, duration: const Duration(milliseconds: 300));
|
||||||
|
|
||||||
_futureBuilderFuture = _videoReplyController.queryReplyList();
|
_futureBuilderFuture = _videoReplyController.queryReplyList();
|
||||||
|
|
||||||
|
fabAnimationCtr.forward();
|
||||||
|
scrollListener();
|
||||||
|
}
|
||||||
|
|
||||||
|
void scrollListener() {
|
||||||
scrollController = _videoReplyController.scrollController;
|
scrollController = _videoReplyController.scrollController;
|
||||||
scrollController.addListener(
|
scrollController.addListener(
|
||||||
() {
|
() {
|
||||||
@ -81,7 +87,6 @@ class _VideoReplyPanelState extends State<VideoReplyPanel>
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
fabAnimationCtr.forward();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _showFab() {
|
void _showFab() {
|
||||||
@ -112,9 +117,10 @@ class _VideoReplyPanelState extends State<VideoReplyPanel>
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
super.dispose();
|
scrollController.removeListener(() {});
|
||||||
fabAnimationCtr.dispose();
|
fabAnimationCtr.dispose();
|
||||||
scrollController.dispose();
|
scrollController.dispose();
|
||||||
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -128,7 +134,7 @@ class _VideoReplyPanelState extends State<VideoReplyPanel>
|
|||||||
child: Stack(
|
child: Stack(
|
||||||
children: [
|
children: [
|
||||||
CustomScrollView(
|
CustomScrollView(
|
||||||
controller: _videoReplyController.scrollController,
|
controller: scrollController,
|
||||||
key: const PageStorageKey<String>('评论'),
|
key: const PageStorageKey<String>('评论'),
|
||||||
slivers: <Widget>[
|
slivers: <Widget>[
|
||||||
SliverPersistentHeader(
|
SliverPersistentHeader(
|
||||||
|
|||||||
@ -744,11 +744,14 @@ InlineSpan buildContent(
|
|||||||
recognizer: TapGestureRecognizer()
|
recognizer: TapGestureRecognizer()
|
||||||
..onTap = () {
|
..onTap = () {
|
||||||
// 跳转到指定位置
|
// 跳转到指定位置
|
||||||
Get.find<VideoDetailController>(tag: Get.arguments['heroTag'])
|
try {
|
||||||
.plPlayerController
|
Get.find<VideoDetailController>(
|
||||||
.seekTo(
|
tag: Get.arguments['heroTag'])
|
||||||
Duration(seconds: Utils.duration(matchStr)),
|
.plPlayerController
|
||||||
);
|
.seekTo(
|
||||||
|
Duration(seconds: Utils.duration(matchStr)),
|
||||||
|
);
|
||||||
|
} catch (_) {}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user