diff --git a/Jsons/dynamic.json b/Jsons/dynamic.json new file mode 100644 index 0000000..3fed774 --- /dev/null +++ b/Jsons/dynamic.json @@ -0,0 +1,5 @@ +{ + "dynamic_id": 755129313256275991, + "pictures": "$[]picture", + "sent_at": 1674655644 +} diff --git a/Jsons/picture.json b/Jsons/picture.json new file mode 100644 index 0000000..1f63386 --- /dev/null +++ b/Jsons/picture.json @@ -0,0 +1,6 @@ +{ + "img_height": 736, + "img_size": 142.8000030517578, + "img_width": 728, + "img_src": "https://i0.hdslb.com/bfs/new_dyn/bcae4133696af428e6a0cc06efa11a37402993775.jpg" +} diff --git a/Jsons/pictures.json b/Jsons/pictures.json new file mode 100644 index 0000000..194b3a3 --- /dev/null +++ b/Jsons/pictures.json @@ -0,0 +1,5 @@ +{ + "page": 1, + "total": 1, + "result": "$[]dynamic" +} diff --git a/lib/common/Api.dart b/lib/common/Api.dart index 1b49c0a..bf1efcd 100644 --- a/lib/common/Api.dart +++ b/lib/common/Api.dart @@ -1,6 +1,6 @@ -import 'dart:convert'; - import 'package:dio/dio.dart'; +import 'package:eoe_fans/models/pictures.dart'; +import 'package:eoe_fans/models/picturesRequest.dart'; import 'package:eoe_fans/models/version.dart'; import 'package:eoe_fans/models/videos.dart'; import 'package:eoe_fans/models/iResponse.dart'; @@ -9,6 +9,7 @@ import 'package:flutter/cupertino.dart'; import 'Global.dart'; + class Api { Api([this.context]); @@ -56,6 +57,53 @@ class Api { return tmpVideos; } + Future picturesRecommend(PicturesRequest params) async { + var tmpPictures = Pictures() + ..page=0 + ..total=0 + ..result=[]; + try { + var paramsData = ({...params.toJson(), 'subscription-key': key}); + paramsData.removeWhere((key, value) => value == null); + var r = await dio.get('/pic/recommend', + queryParameters: paramsData); + + if (r.statusCode == 200) { + var picturesRes = + IResponse.fromJson(r.data, (json) => Pictures.fromJson(json)); + if (picturesRes.data != null) { + tmpPictures = picturesRes.data!; + } + } + } catch(e) { + print(e); + } + return tmpPictures; + } + + Future picturesLatest(PicturesRequest params) async { + var tmpPictures = Pictures() + ..page=0 + ..total=0 + ..result=[]; + try { + var paramsData = ({...params.toJson(), 'subscription-key': key}); + paramsData.removeWhere((key, value) => value == null); + var r = await dio.get('/pic/latest', + queryParameters: paramsData); + + if (r.statusCode == 200) { + var picturesRes = + IResponse.fromJson(r.data, (json) => Pictures.fromJson(json)); + if (picturesRes.data != null) { + tmpPictures = picturesRes.data!; + } + } + } catch(e) { + print(e); + } + return tmpPictures; + } Future version() async { var paramsData = ({'subscription-key': key}); paramsData.removeWhere((key, value) => value == null); diff --git a/lib/models/dynamic.dart b/lib/models/dynamic.dart new file mode 100644 index 0000000..7da0eb0 --- /dev/null +++ b/lib/models/dynamic.dart @@ -0,0 +1,15 @@ +import 'package:json_annotation/json_annotation.dart'; +import "picture.dart"; +part 'dynamic.g.dart'; + +@JsonSerializable() +class Dynamic { + Dynamic(); + + late num dynamic_id; + late List pictures; + late num sent_at; + + factory Dynamic.fromJson(Map json) => _$DynamicFromJson(json); + Map toJson() => _$DynamicToJson(this); +} diff --git a/lib/models/dynamic.g.dart b/lib/models/dynamic.g.dart new file mode 100644 index 0000000..853dc72 --- /dev/null +++ b/lib/models/dynamic.g.dart @@ -0,0 +1,20 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'dynamic.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +Dynamic _$DynamicFromJson(Map json) => Dynamic() + ..dynamic_id = json['dynamic_id'] as num + ..pictures = (json['pictures'] as List) + .map((e) => Picture.fromJson(e as Map)) + .toList() + ..sent_at = json['sent_at'] as num; + +Map _$DynamicToJson(Dynamic instance) => { + 'dynamic_id': instance.dynamic_id, + 'pictures': instance.pictures, + 'sent_at': instance.sent_at, + }; diff --git a/lib/models/index.dart b/lib/models/index.dart index dcdbf49..28761d9 100644 --- a/lib/models/index.dart +++ b/lib/models/index.dart @@ -1,4 +1,7 @@ export 'cacheConfig.dart' ; +export 'dynamic.dart' ; +export 'picture.dart' ; +export 'pictures.dart' ; export 'version.dart' ; export 'video.dart' ; export 'videos.dart' ; diff --git a/lib/models/picture.dart b/lib/models/picture.dart new file mode 100644 index 0000000..868932e --- /dev/null +++ b/lib/models/picture.dart @@ -0,0 +1,16 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'picture.g.dart'; + +@JsonSerializable() +class Picture { + Picture(); + + late num img_height; + late num img_size; + late num img_width; + late String img_src; + + factory Picture.fromJson(Map json) => _$PictureFromJson(json); + Map toJson() => _$PictureToJson(this); +} diff --git a/lib/models/picture.g.dart b/lib/models/picture.g.dart new file mode 100644 index 0000000..1e1d0c1 --- /dev/null +++ b/lib/models/picture.g.dart @@ -0,0 +1,20 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'picture.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +Picture _$PictureFromJson(Map json) => Picture() + ..img_height = json['img_height'] as num + ..img_size = json['img_size'] as num + ..img_width = json['img_width'] as num + ..img_src = json['img_src'] as String; + +Map _$PictureToJson(Picture instance) => { + 'img_height': instance.img_height, + 'img_size': instance.img_size, + 'img_width': instance.img_width, + 'img_src': instance.img_src, + }; diff --git a/lib/models/pictures.dart b/lib/models/pictures.dart new file mode 100644 index 0000000..78e008a --- /dev/null +++ b/lib/models/pictures.dart @@ -0,0 +1,23 @@ +import 'package:json_annotation/json_annotation.dart'; +import "dynamic.dart"; + +part 'pictures.g.dart'; + +enum PicturePageType { + latest, + recommend, +} + +@JsonSerializable() +class Pictures { + Pictures(); + + late num page; + late num total; + late List result; + + factory Pictures.fromJson(Map json) => + _$PicturesFromJson(json); + + Map toJson() => _$PicturesToJson(this); +} diff --git a/lib/models/pictures.g.dart b/lib/models/pictures.g.dart new file mode 100644 index 0000000..bf8bf6f --- /dev/null +++ b/lib/models/pictures.g.dart @@ -0,0 +1,20 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'pictures.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +Pictures _$PicturesFromJson(Map json) => Pictures() + ..page = json['page'] as num + ..total = json['total'] as num + ..result = (json['result'] as List) + .map((e) => Dynamic.fromJson(e as Map)) + .toList(); + +Map _$PicturesToJson(Pictures instance) => { + 'page': instance.page, + 'total': instance.total, + 'result': instance.result, + }; diff --git a/lib/models/picturesRequest.dart b/lib/models/picturesRequest.dart new file mode 100644 index 0000000..d6195ce --- /dev/null +++ b/lib/models/picturesRequest.dart @@ -0,0 +1,46 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'picturesRequest.g.dart'; + +enum PicturesTopic { + @JsonValue(29067608) + zao(id: 29067608, label: 'zao'), + @JsonValue(29069147) + mi(id: 29069147, label: 'mi'), + @JsonValue(28953983) + wan(id: 28953983, label: 'wan'), + @JsonValue(28948378) + mo(id: 28948378, label: 'mo'), + @JsonValue(28950030) + un(id: 28950030, label: 'un'), + @JsonValue(29156150) + eoe(id: 29156150, label: 'eoe'), + @JsonValue(0) + all(id: 0, label: 'all'); + + final int id; + final String label; + + const PicturesTopic({ + required this.id, + required this.label, + }); + + /// 解析从后台传来的值 + static PicturesTopic parse(int i) { + return PicturesTopic.values[i]; + } +} + +@JsonSerializable() +class PicturesRequest { + PicturesRequest(); + + late PicturesTopic topic_id; + late num page; + + factory PicturesRequest.fromJson(Map json) => + _$PicturesRequestFromJson(json); + + Map toJson() => _$PicturesRequestToJson(this); +} diff --git a/lib/models/picturesRequest.g.dart b/lib/models/picturesRequest.g.dart new file mode 100644 index 0000000..ff64919 --- /dev/null +++ b/lib/models/picturesRequest.g.dart @@ -0,0 +1,28 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'picturesRequest.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +PicturesRequest _$PicturesRequestFromJson(Map json) => + PicturesRequest() + ..topic_id = $enumDecode(_$PicturesTopicEnumMap, json['topic_id']) + ..page = json['page'] as num; + +Map _$PicturesRequestToJson(PicturesRequest instance) => + { + 'topic_id': _$PicturesTopicEnumMap[instance.topic_id]!, + 'page': instance.page, + }; + +const _$PicturesTopicEnumMap = { + PicturesTopic.zao: 29067608, + PicturesTopic.mi: 29069147, + PicturesTopic.wan: 28953983, + PicturesTopic.mo: 28948378, + PicturesTopic.un: 28950030, + PicturesTopic.eoe: 29156150, + PicturesTopic.all: 0, +}; diff --git a/lib/routes/mainPage.dart b/lib/routes/mainPage.dart index 0c06295..86a0453 100644 --- a/lib/routes/mainPage.dart +++ b/lib/routes/mainPage.dart @@ -19,12 +19,6 @@ class MainPage extends StatefulWidget { class _MainPageState extends State { int _selectedIndex = 0; - final List _bodyList = [ - const VideoPage(), - const PicturePage(), - const MusicPage(), - ]; - Future _checkAppInfo() async { var r = await Api().version(); @@ -52,6 +46,12 @@ class _MainPageState extends State { @override Widget build(BuildContext context) { + List _bodyList = [ + _selectedIndex == 0 ? const VideoPage() : Container(), + _selectedIndex == 1 ? const PicturePage() : Container(), + _selectedIndex == 2 ? const MusicPage() : Container(), + ]; + return Scaffold( body: Stack( children: [ @@ -66,9 +66,11 @@ class _MainPageState extends State { child: Container( decoration: BoxDecoration( image: DecorationImage( - image: AssetImage( - 'assets/${Provider.of(context).assets}/tail_bg.png'), - fit: BoxFit.fill), + image: AssetImage( + 'assets/${Provider.of(context).assets}/tail_bg.png', + ), + fit: BoxFit.fill, + ), color: Colors.transparent), child: BottomNavigationBar( elevation: 0, diff --git a/lib/routes/picture/pictureList.dart b/lib/routes/picture/pictureList.dart new file mode 100644 index 0000000..dfce8ee --- /dev/null +++ b/lib/routes/picture/pictureList.dart @@ -0,0 +1,128 @@ +import 'dart:math'; + +import 'package:eoe_fans/common/Api.dart'; +import 'package:eoe_fans/models/index.dart'; +import 'package:eoe_fans/models/picturesRequest.dart'; +import 'package:eoe_fans/routes/picture/pictureSwiper.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:pull_to_refresh/pull_to_refresh.dart'; + +class PictureList extends StatefulWidget { + const PictureList({Key? key, required this.pageType}) : super(key: key); + + final PicturePageType pageType; + + @override + State createState() => _PictureListState(); +} + +class _PictureListState extends State { + List items = ["1", "2", "3", "4", "5", "6", "7", "8"]; + RefreshController _refreshController = + RefreshController(initialRefresh: false); + + int _page = 0; + bool _loading = false; + List dynamicList = []; + + @override + void initState() { + _getPictures(); + super.initState(); + } + + _getPictures() async { + setState(() { + _loading = true; + _page++; + }); + PicturesRequest param = PicturesRequest() + ..page = _page + ..topic_id = PicturesTopic.all; + Pictures pictures = widget.pageType == PicturePageType.recommend + ? await Api(context).picturesRecommend(param) + : await Api(context).picturesLatest(param); + if (pictures.result.length != 0) { + setState(() { + dynamicList = [...dynamicList, ...pictures.result]; + _loading = false; + }); + } else { + await Future.delayed(Duration(seconds: 5), () { + _getPictures(); + }); + } + } + + _reloadPictures({int? page}) async { + setState(() { + dynamicList = []; + _page = page ?? 0; + }); + await _getPictures(); + } + + void _onRefresh() async { + // monitor network fetch + await _reloadPictures(); + // if failed,use refreshFailed() + _refreshController.refreshCompleted(); + } + + void _onLoading() async { + await _getPictures(); + _refreshController.loadComplete(); + } + + @override + Widget build(BuildContext context) { + return SmartRefresher( + enablePullDown: true, + enablePullUp: true, + header: WaterDropHeader(), + footer: CustomFooter(builder: (BuildContext context, LoadStatus? mode) { + Widget body; + if (mode == LoadStatus.idle) { + body = Text("上拉加载"); + } else if (mode == LoadStatus.loading) { + body = CupertinoActivityIndicator(); + } else if (mode == LoadStatus.failed) { + body = Text("加载失败!点击重试!"); + } else if (mode == LoadStatus.canLoading) { + body = Text("松手,加载更多!"); + } else { + body = Text("没有更多数据了!"); + } + return Container( + child: Center(child: body), + ); + }), + controller: _refreshController, + onRefresh: _onRefresh, + onLoading: _onLoading, + child: ListView.builder( + itemBuilder: (c, i) => Card( + clipBehavior: Clip.antiAliasWithSaveLayer, + child: SizedBox( + width: double.infinity, + height: 480, + child: PictureSwiper( + dynamicId: dynamicList[i].dynamic_id.toString(), + images: dynamicList[i].pictures.map((e) => e.img_src).toList(), + ), + ), + + // child: Center( + // child: Image( + // image: CachedNetworkImageProvider(dynamicList[i].pictures[0].img_src), + // fit: BoxFit.fitWidth, + // width: double.infinity, + // ), + // ), + ), + itemCount: dynamicList.length, + ), + ); + } +} diff --git a/lib/routes/picture/picturePage.dart b/lib/routes/picture/picturePage.dart index 34586ca..7eacaf9 100644 --- a/lib/routes/picture/picturePage.dart +++ b/lib/routes/picture/picturePage.dart @@ -1,7 +1,9 @@ -import 'dart:ffi'; - +import 'package:eoe_fans/models/pictures.dart'; +import 'package:eoe_fans/routes/picture/pictureList.dart'; +import 'package:eoe_fans/states/ProfileChangeNotifier.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; class PicturePage extends StatefulWidget { const PicturePage({Key? key}) : super(key: key); @@ -13,13 +15,65 @@ class PicturePage extends StatefulWidget { class _PicturePageState extends State { @override Widget build(BuildContext context) { - return Scaffold( - body: Container( - height: double.maxFinite, - padding: EdgeInsets.only(bottom: 50), - child: Image( - image: AssetImage('assets/jijiji.jpg'), - fit: BoxFit.cover, + return DefaultTabController( + length: 2, + child: Scaffold( + appBar: AppBar( + leading: GestureDetector( + onTap: () { + Navigator.pushNamed(context, '/setting'); + }, + child: Container( + margin: const EdgeInsets.only(left: 16.0), + child: const CircleAvatar( + child: ClipOval( + child: Image( + image: AssetImage('assets/eoe.webp'), + ), + ), + ), + ), + ), + centerTitle: true, + title: Container( + width: 300, + padding: const EdgeInsets.only(left: 20, right: 20), + child: const TabBar( + tabs: [ + Tab( + text: "推荐", + ), + Tab( + text: "最新", + ), + ], + ), + ), + actions: [ + IconButton( + icon: const Icon( + Icons.search, + ), + onPressed: () {}, + ), + ], + flexibleSpace: Container( + height: double.maxFinite, + child: Image( + image: AssetImage( + 'assets/${Provider.of(context).assets}/head_bg.jpg'), + fit: BoxFit.cover, + ), + ), + ), + body: Container( + padding: const EdgeInsets.only(left: 4, right: 4, top: 8, bottom: 40), + child: const TabBarView( + children: [ + PictureList(pageType: PicturePageType.recommend), + PictureList(pageType: PicturePageType.latest), + ], + ), ), ), ); diff --git a/lib/routes/picture/pictureSwiper.dart b/lib/routes/picture/pictureSwiper.dart new file mode 100644 index 0000000..2ba53c7 --- /dev/null +++ b/lib/routes/picture/pictureSwiper.dart @@ -0,0 +1,81 @@ +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:extended_image/extended_image.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_swiper_null_safety/flutter_swiper_null_safety.dart'; +import 'package:url_launcher/url_launcher.dart'; + +class PictureSwiper extends StatelessWidget { + const PictureSwiper({required this.images, this.dynamicId, Key? key}) + : super(key: key); + final String? dynamicId; + final List images; + + @override + Widget build(BuildContext context) { + return Stack( + children: [ + Swiper( + itemBuilder: (BuildContext context, int index) { + var image = CachedNetworkImageProvider(images[index]); + return Stack( + children: [ + Positioned( + top:0, + bottom: 0, + left: 0, + right: 0, + child: Container( + child: ExtendedImage( + image: image, + fit: BoxFit.cover, + ), + ), + ), + Positioned( + bottom: 0, + left: 0, + right: 0, + child: Container( + height: 40, + color: Color.fromRGBO(0, 0, 0, .3), + ), + ) + ], + ); + }, + itemCount: images.length, + onTap: (int index) async {}, + pagination: const SwiperPagination( + alignment: Alignment.bottomLeft, + builder: DotSwiperPaginationBuilder( + color: Colors.white30, // 其他点的颜色 + activeColor: Colors.white, + space: 2, // 点与点之间的距离 + activeSize: 15, // 当前点的大小 + ), + ), + autoplay: false, + ), + Positioned( + bottom: -4, + right: 8, + child: IconButton( + icon: Icon( + Icons.arrow_forward_ios, + size: 16, + color: Colors.white, + ), + onPressed: () async { + if (dynamicId != null) { + var _url = 'bilibili://following/detail/' + dynamicId!; + if (!await launchUrl(Uri.parse(_url))) { + throw 'Could not launch $_url'; + } + } + }, + ), + ) + ], + ); + } +} diff --git a/lib/routes/video/videoPage.dart b/lib/routes/video/videoPage.dart index 3b2b54a..29f4168 100644 --- a/lib/routes/video/videoPage.dart +++ b/lib/routes/video/videoPage.dart @@ -37,7 +37,7 @@ class _VideoPageState extends State { ), centerTitle: true, title: Container( - width: 300, + width: 360, padding: const EdgeInsets.only(left: 20, right: 20), child: const TabBar( tabs: [ @@ -71,7 +71,7 @@ class _VideoPageState extends State { ), ), body: Container( - padding: const EdgeInsets.only(left: 4, right: 4, top: 8), + padding: const EdgeInsets.only(left: 4, right: 4, top: 8,bottom: 40), child: const TabBarView( children: [ VideoList(origin: true), diff --git a/lib/routes/video/videoSwiper.dart b/lib/routes/video/videoSwiper.dart index 94a7c25..66a92d2 100644 --- a/lib/routes/video/videoSwiper.dart +++ b/lib/routes/video/videoSwiper.dart @@ -1,4 +1,3 @@ -import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:flutter_swiper_null_safety/flutter_swiper_null_safety.dart'; import 'package:url_launcher/url_launcher.dart'; diff --git a/pubspec.lock b/pubspec.lock index 5018faf..119a6c1 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -197,6 +197,20 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "4.0.6" + extended_image: + dependency: "direct main" + description: + name: extended_image + url: "https://pub.flutter-io.cn" + source: hosted + version: "6.3.4" + extended_image_library: + dependency: transitive + description: + name: extended_image_library + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.4.1" fake_async: dependency: transitive description: @@ -331,6 +345,13 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "0.13.5" + http_client_helper: + dependency: transitive + description: + name: http_client_helper + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.0.3" http_multi_server: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 5c23115..e29d9cf 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -49,6 +49,7 @@ dependencies: pull_to_refresh: ^2.0.0 settings_ui: ^2.0.2 hgg_app_upgrade: ^1.0.0 + extended_image: ^6.3.4 dev_dependencies: flutter_test: