mirror of
https://github.com/bggRGjQaUbCoE/PiliPlus.git
synced 2026-05-27 11:38:40 +00:00
@@ -36,6 +36,8 @@ class LiveController extends CommonListController with AccountMixin {
|
||||
|
||||
final followController = ScrollController();
|
||||
|
||||
bool showFirstFrame = false;
|
||||
|
||||
@override
|
||||
void checkIsEnd(int length) {
|
||||
if (count != null && length >= count!) {
|
||||
|
||||
@@ -21,6 +21,7 @@ import 'package:PiliPlus/utils/platform_utils.dart';
|
||||
import 'package:PiliPlus/utils/utils.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
|
||||
|
||||
class LivePage extends StatefulWidget {
|
||||
const LivePage({super.key});
|
||||
@@ -128,6 +129,20 @@ class _LivePageState extends State<LivePage>
|
||||
}),
|
||||
),
|
||||
),
|
||||
iconButton(
|
||||
size: 26,
|
||||
iconSize: 18,
|
||||
context: context,
|
||||
tooltip: '切换${controller.showFirstFrame ? '封面' : '首帧'}',
|
||||
icon: controller.showFirstFrame
|
||||
? const Icon(MdiIcons.alphaFBox)
|
||||
: const Icon(MdiIcons.image),
|
||||
onPressed: () {
|
||||
controller.showFirstFrame = !controller.showFirstFrame;
|
||||
setState(() {});
|
||||
},
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
iconButton(
|
||||
size: 26,
|
||||
iconSize: 16,
|
||||
@@ -224,9 +239,13 @@ class _LivePageState extends State<LivePage>
|
||||
if (item is LiveCardList) {
|
||||
return LiveCardVApp(
|
||||
item: item.cardData!.smallCardV1!,
|
||||
showFirstFrame: controller.showFirstFrame,
|
||||
);
|
||||
}
|
||||
return LiveCardVApp(item: item);
|
||||
return LiveCardVApp(
|
||||
item: item,
|
||||
showFirstFrame: controller.showFirstFrame,
|
||||
);
|
||||
},
|
||||
itemCount: response.length,
|
||||
)
|
||||
|
||||
@@ -10,10 +10,12 @@ import 'package:flutter/material.dart' hide LayoutBuilder;
|
||||
// 视频卡片 - 垂直布局
|
||||
class LiveCardVApp extends StatelessWidget {
|
||||
final CardLiveItem item;
|
||||
final bool showFirstFrame;
|
||||
|
||||
const LiveCardVApp({
|
||||
super.key,
|
||||
required this.item,
|
||||
this.showFirstFrame = false,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -40,7 +42,9 @@ class LiveCardVApp extends StatelessWidget {
|
||||
clipBehavior: Clip.none,
|
||||
children: [
|
||||
NetworkImgLayer(
|
||||
src: item.cover!,
|
||||
src: showFirstFrame
|
||||
? (item.systemCover ?? item.cover)
|
||||
: item.cover,
|
||||
width: maxWidth,
|
||||
height: maxHeight,
|
||||
type: .emote,
|
||||
|
||||
@@ -4,16 +4,19 @@ import 'package:PiliPlus/models_new/live/live_area_list/area_item.dart';
|
||||
import 'package:PiliPlus/models_new/live/live_area_list/area_list.dart';
|
||||
import 'package:PiliPlus/pages/common/common_list_controller.dart';
|
||||
import 'package:PiliPlus/utils/accounts.dart';
|
||||
import 'package:flutter/material.dart' show TabController;
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
class LiveAreaController
|
||||
extends CommonListController<List<AreaList>?, AreaList> {
|
||||
class LiveAreaController extends CommonListController<List<AreaList>?, AreaList>
|
||||
with GetSingleTickerProviderStateMixin {
|
||||
late final isLogin = Accounts.main.isLogin;
|
||||
|
||||
late final isEditing = false.obs;
|
||||
late final favInfo = {};
|
||||
|
||||
TabController? tabController;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
@@ -31,6 +34,16 @@ class LiveAreaController
|
||||
return super.onRefresh();
|
||||
}
|
||||
|
||||
@override
|
||||
bool customHandleResponse(bool isRefresh, Success<List<AreaList>?> response) {
|
||||
assert(tabController == null);
|
||||
final length = response.response?.length;
|
||||
if (length != null && length != 0) {
|
||||
tabController = TabController(length: length, vsync: this);
|
||||
}
|
||||
return super.customHandleResponse(isRefresh, response);
|
||||
}
|
||||
|
||||
Rx<LoadingState<List<AreaItem>>> favState =
|
||||
LoadingState<List<AreaItem>>.loading().obs;
|
||||
|
||||
@@ -65,4 +78,11 @@ class LiveAreaController
|
||||
isEditing.value = !isEditing.value;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void onClose() {
|
||||
tabController?.dispose();
|
||||
tabController = null;
|
||||
super.onClose();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,82 +80,78 @@ class _LiveAreaPageState extends State<LiveAreaPage> {
|
||||
Loading() => const SizedBox.shrink(),
|
||||
Success(:final response) =>
|
||||
response != null && response.isNotEmpty
|
||||
? DefaultTabController(
|
||||
length: response.length,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
TabBar(
|
||||
isScrollable: true,
|
||||
tabAlignment: TabAlignment.start,
|
||||
tabs: response.map((e) => Tab(text: e.name)).toList(),
|
||||
),
|
||||
Expanded(
|
||||
child: tabBarView(
|
||||
children: response
|
||||
.map(
|
||||
(e) => KeepAliveWrapper(
|
||||
builder: (context) {
|
||||
if (e.areaList.isNullOrEmpty) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
return GridView.builder(
|
||||
padding: EdgeInsets.only(
|
||||
top: 12,
|
||||
bottom: bottom + 100,
|
||||
),
|
||||
gridDelegate:
|
||||
const SliverGridDelegateWithMaxCrossAxisExtent(
|
||||
maxCrossAxisExtent: 100,
|
||||
mainAxisSpacing: 10,
|
||||
crossAxisSpacing: 10,
|
||||
mainAxisExtent: 80,
|
||||
),
|
||||
itemCount: e.areaList!.length,
|
||||
itemBuilder: (context, index) {
|
||||
final item = e.areaList![index];
|
||||
return _tagItem(
|
||||
theme: theme,
|
||||
item: item,
|
||||
onPressed: () {
|
||||
// success
|
||||
bool? isFav =
|
||||
_controller.favInfo[item.id];
|
||||
if (isFav == true) {
|
||||
_controller.favInfo[item.id] =
|
||||
false;
|
||||
? Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
TabBar(
|
||||
isScrollable: true,
|
||||
tabAlignment: TabAlignment.start,
|
||||
controller: _controller.tabController,
|
||||
tabs: response.map((e) => Tab(text: e.name)).toList(),
|
||||
),
|
||||
Expanded(
|
||||
child: tabBarView(
|
||||
controller: _controller.tabController,
|
||||
children: response
|
||||
.map(
|
||||
(e) => KeepAliveWrapper(
|
||||
builder: (context) {
|
||||
if (e.areaList.isNullOrEmpty) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
return GridView.builder(
|
||||
padding: EdgeInsets.only(
|
||||
top: 12,
|
||||
bottom: bottom + 100,
|
||||
),
|
||||
gridDelegate:
|
||||
const SliverGridDelegateWithMaxCrossAxisExtent(
|
||||
maxCrossAxisExtent: 100,
|
||||
mainAxisSpacing: 10,
|
||||
crossAxisSpacing: 10,
|
||||
mainAxisExtent: 80,
|
||||
),
|
||||
itemCount: e.areaList!.length,
|
||||
itemBuilder: (context, index) {
|
||||
final item = e.areaList![index];
|
||||
return _tagItem(
|
||||
theme: theme,
|
||||
item: item,
|
||||
onPressed: () {
|
||||
// success
|
||||
bool? isFav =
|
||||
_controller.favInfo[item.id];
|
||||
if (isFav == true) {
|
||||
_controller.favInfo[item.id] = false;
|
||||
_controller.favState
|
||||
..value.data.remove(item)
|
||||
..refresh();
|
||||
(context as Element).markNeedsBuild();
|
||||
} else {
|
||||
// check
|
||||
if (_controller
|
||||
.favState
|
||||
.value
|
||||
.isSuccess) {
|
||||
_controller.favInfo[item.id] = true;
|
||||
_controller.favState
|
||||
..value.data.remove(item)
|
||||
..value.data.add(item)
|
||||
..refresh();
|
||||
(context as Element)
|
||||
.markNeedsBuild();
|
||||
} else {
|
||||
// check
|
||||
if (_controller
|
||||
.favState
|
||||
.value
|
||||
.isSuccess) {
|
||||
_controller.favInfo[item.id] =
|
||||
true;
|
||||
_controller.favState
|
||||
..value.data.add(item)
|
||||
..refresh();
|
||||
(context as Element)
|
||||
.markNeedsBuild();
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
: scrollErrorWidget(onReload: _controller.onReload),
|
||||
Error(:final errMsg) => scrollErrorWidget(
|
||||
@@ -192,9 +188,7 @@ class _LiveAreaPageState extends State<LiveAreaPage> {
|
||||
const SizedBox(height: 8),
|
||||
if (response != null && response.isNotEmpty) ...[
|
||||
SortableWrap(
|
||||
onSortStart: (index) {
|
||||
_controller.isEditing.value = true;
|
||||
},
|
||||
onSortStart: (index) => _controller.isEditing.value = true,
|
||||
onSorted: (int oldIndex, int newIndex) {
|
||||
response.insert(newIndex, response.removeAt(oldIndex));
|
||||
},
|
||||
|
||||
@@ -17,10 +17,12 @@ class LiveAreaChildPage extends StatefulWidget {
|
||||
super.key,
|
||||
required this.areaId,
|
||||
required this.parentAreaId,
|
||||
required this.showFirstFrame,
|
||||
});
|
||||
|
||||
final dynamic areaId;
|
||||
final dynamic parentAreaId;
|
||||
final bool showFirstFrame;
|
||||
|
||||
@override
|
||||
State<LiveAreaChildPage> createState() => _LiveAreaChildPageState();
|
||||
@@ -120,7 +122,10 @@ class _LiveAreaChildPageState extends State<LiveAreaChildPage>
|
||||
if (index == response.length - 1) {
|
||||
_controller.onLoadMore();
|
||||
}
|
||||
return LiveCardVApp(item: response[index]);
|
||||
return LiveCardVApp(
|
||||
item: response[index],
|
||||
showFirstFrame: widget.showFirstFrame,
|
||||
);
|
||||
},
|
||||
itemCount: response.length,
|
||||
)
|
||||
|
||||
@@ -4,14 +4,19 @@ import 'package:PiliPlus/http/live.dart';
|
||||
import 'package:PiliPlus/http/loading_state.dart';
|
||||
import 'package:PiliPlus/models_new/live/live_area_list/area_item.dart';
|
||||
import 'package:PiliPlus/pages/common/common_list_controller.dart';
|
||||
import 'package:flutter/material.dart' show TabController;
|
||||
import 'package:get/get_state_manager/src/rx_flutter/rx_ticket_provider_mixin.dart';
|
||||
|
||||
class LiveAreaDetailController
|
||||
extends CommonListController<List<AreaItem>?, AreaItem> {
|
||||
extends CommonListController<List<AreaItem>?, AreaItem>
|
||||
with GetSingleTickerProviderStateMixin {
|
||||
LiveAreaDetailController(this.areaId, this.parentAreaId);
|
||||
final dynamic areaId;
|
||||
final dynamic parentAreaId;
|
||||
|
||||
late int initialIndex = 0;
|
||||
TabController? tabController;
|
||||
|
||||
bool showFirstFrame = false;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
@@ -22,7 +27,13 @@ class LiveAreaDetailController
|
||||
@override
|
||||
List<AreaItem>? getDataList(List<AreaItem>? response) {
|
||||
if (response != null && response.isNotEmpty) {
|
||||
initialIndex = max(0, response.indexWhere((e) => e.id == areaId));
|
||||
assert(tabController == null);
|
||||
final initialIndex = max(0, response.indexWhere((e) => e.id == areaId));
|
||||
tabController = TabController(
|
||||
length: response.length,
|
||||
initialIndex: initialIndex,
|
||||
vsync: this,
|
||||
);
|
||||
}
|
||||
return response;
|
||||
}
|
||||
@@ -30,4 +41,11 @@ class LiveAreaDetailController
|
||||
@override
|
||||
Future<LoadingState<List<AreaItem>?>> customGetData() =>
|
||||
LiveHttp.liveRoomAreaList(parentid: parentAreaId);
|
||||
|
||||
@override
|
||||
void onClose() {
|
||||
tabController?.dispose();
|
||||
tabController = null;
|
||||
super.onClose();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import 'package:PiliPlus/pages/live_area_detail/controller.dart';
|
||||
import 'package:PiliPlus/pages/live_search/view.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
|
||||
|
||||
class LiveAreaDetailPage extends StatefulWidget {
|
||||
const LiveAreaDetailPage({
|
||||
@@ -73,71 +74,81 @@ class _LiveAreaDetailPageState extends State<LiveAreaDetailPage> {
|
||||
Loading() => const SizedBox.shrink(),
|
||||
Success(:final response) =>
|
||||
response != null && response.isNotEmpty
|
||||
? DefaultTabController(
|
||||
initialIndex: _controller.initialIndex,
|
||||
length: response.length,
|
||||
child: Builder(
|
||||
builder: (context) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: TabBar(
|
||||
dividerHeight: 0,
|
||||
dividerColor: Colors.transparent,
|
||||
isScrollable: true,
|
||||
tabAlignment: TabAlignment.start,
|
||||
tabs: response
|
||||
.map((e) => Tab(text: e.name ?? ''))
|
||||
.toList(),
|
||||
onTap: (index) {
|
||||
try {
|
||||
if (!DefaultTabController.of(
|
||||
context,
|
||||
).indexIsChanging) {
|
||||
final item = response[index];
|
||||
Get.find<LiveAreaChildController>(
|
||||
tag: '${item.id}${item.parentId}',
|
||||
).animateToTop();
|
||||
}
|
||||
} catch (_) {}
|
||||
},
|
||||
),
|
||||
),
|
||||
iconButton(
|
||||
icon: const Icon(Icons.menu),
|
||||
onPressed: () =>
|
||||
_showTags(context, theme, bottom, response),
|
||||
),
|
||||
],
|
||||
? Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: TabBar(
|
||||
dividerHeight: 0,
|
||||
isScrollable: true,
|
||||
tabAlignment: TabAlignment.start,
|
||||
dividerColor: Colors.transparent,
|
||||
controller: _controller.tabController,
|
||||
tabs: response
|
||||
.map((e) => Tab(text: e.name ?? ''))
|
||||
.toList(),
|
||||
onTap: (index) {
|
||||
try {
|
||||
if (!_controller.tabController!.indexIsChanging) {
|
||||
final item = response[index];
|
||||
Get.find<LiveAreaChildController>(
|
||||
tag: '${item.id}${item.parentId}',
|
||||
).animateToTop();
|
||||
}
|
||||
} catch (_) {}
|
||||
},
|
||||
),
|
||||
const Divider(height: 1),
|
||||
Expanded(
|
||||
child: tabBarView(
|
||||
children: response
|
||||
.map(
|
||||
(e) => LiveAreaChildPage(
|
||||
areaId: e.id,
|
||||
parentAreaId: e.parentId,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
iconButton(
|
||||
iconSize: 20,
|
||||
tooltip:
|
||||
'切换${_controller.showFirstFrame ? '封面' : '首帧'}',
|
||||
icon: _controller.showFirstFrame
|
||||
? const Icon(MdiIcons.alphaFBox)
|
||||
: const Icon(MdiIcons.image),
|
||||
onPressed: () {
|
||||
_controller.showFirstFrame =
|
||||
!_controller.showFirstFrame;
|
||||
setState(() {});
|
||||
},
|
||||
),
|
||||
iconButton(
|
||||
iconSize: 20,
|
||||
tooltip: '显示菜单',
|
||||
icon: const Icon(Icons.menu),
|
||||
onPressed: () =>
|
||||
_showTags(context, theme, bottom, response),
|
||||
),
|
||||
],
|
||||
),
|
||||
const Divider(height: 1),
|
||||
Expanded(
|
||||
child: tabBarView(
|
||||
controller: _controller.tabController,
|
||||
children: response
|
||||
.map(
|
||||
(e) => LiveAreaChildPage(
|
||||
areaId: e.id,
|
||||
parentAreaId: e.parentId,
|
||||
showFirstFrame: _controller.showFirstFrame,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
: LiveAreaChildPage(
|
||||
areaId: widget.areaId,
|
||||
parentAreaId: widget.parentAreaId,
|
||||
showFirstFrame: _controller.showFirstFrame,
|
||||
),
|
||||
Error() => LiveAreaChildPage(
|
||||
areaId: widget.areaId,
|
||||
parentAreaId: widget.parentAreaId,
|
||||
showFirstFrame: _controller.showFirstFrame,
|
||||
),
|
||||
};
|
||||
}
|
||||
@@ -226,7 +237,7 @@ class _LiveAreaDetailPageState extends State<LiveAreaDetailPage> {
|
||||
item: list[index],
|
||||
onTap: () {
|
||||
Get.back();
|
||||
DefaultTabController.of(context).index = index;
|
||||
_controller.tabController?.index = index;
|
||||
},
|
||||
);
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user